martes, 31 de marzo de 2009

¿Dónde está el Error?

Actualmente, el lenguaje de programación sobre el que estamos desarrollando nuestros productos es Java. Algunas de las principales ventajas de este lenguaje son el, casi-siempre-válido, write-once-run-everywhere (escribe una vez, ejecuta en cualquier parte), un entorno de que apoya muy bien el proceso de desarrollo (eclipse por ejemplo) y las infinitas bibliotecas (o librerías como prefieren llamarlas algunos por error) disponibles en internet para resolver problemas específicos.

En lo personal, una de las desventajas más importantes que yo siento tiene el lenguaje es el recolector de basura, básicamente porque como estrategia de programación, se trabaja en el escenario de recursos ilimitados, es decir, no hay un uso racional de los recursos del sistema operativo (memoria, disco, filedescriptors, etc.) y, si bien es cierto no es un problema en estricto rigor del lenguaje, si es de la manera en que se enseña a programar en él. Básicamente, porque el uso y administración de los recursos del Sistema Operativo no puede delegarse al recolector. Los que aprendieron a programar en C tendrán claro a qué me refiero con esto.

La otra desventaja que veo es la incorporación de las excepciones. Hasta el momento, no he logrado descubrir un escenario controlable en el que se haga uso de las excepciones y éstas no generen problemas secundarios difíciles de manejar. Los que aprendimos a programar en C, estamos acostumbrados a syntaxis como la siguiente:

char *sFile = ....;
FILE *fp = NULL;
fp = fopen( sFile, "r");

En donde, lo más importante, es que cualquier operación/función tiene un resultado determinado. En este caso la variable fp tiene el resultado de la operación, para este caso particular, NULL (0) en caso de error, <>0 en caso de éxito. En el caso de Java, una instrucción similar, sería la siguiente:

String sFile = ....;
File oFile = new File( sFile );
FileInputStream fis = new FileInputStream( oFile );

Y, la principal diferencia con el código en C , es básicamente que en esas tres líneas se pueden lanzar varias excepciones, produciendo un flujo no-determinado como se esperaba. Particularmente delicado es esto cuando hay recursos del Sistema Operativo en uso, como son los FileDescriptors o Streams de Java que, si no son cerrados adecuadamente, producirán a la larga pérdidas de memoria ya que el recolector de basura no es capaz de determinar correctamente si esos recursos deben o no ser liberados.

Para evitar lo anterior, es necesario "recubrir" el código con la sentencia try{...} catch{...} que resuelve el problema, pero que, a la larga, hace el código más complejo. Algunas de las instrucciones más problemáticas que típicamente causan los mayores problemas son las siguientes:

Ejemplo 1. Integer.parseInt( sValue );
Esta instrucción es bastante ingenua y simple, sin embargo, produce uno de los errores más innecesarios de programación en Java y es que, el código anterior, lanza una excepción si es que el valor sValue no es un valor que se puede interpretar (parsear) como entero en este caso.

Para solucionar esto, hay múltiples respuestas y alternativas, como por ejemplo, validar que el contenido del string sólo tenga números hasta utilizar una expresión regular. Cualquiera de las alternativas anteriores, según yo, complejiza el problema, por lo que la solución, es símplemente construir una función que atrape la excepción y retorne el valor encontrado. Lo importante acá es que efectivamente se use la función en vez de llenar el código de sentencias try{...}catch{...}.

int validaEntero( String sText, int iDefault )
{
  try
  {
    return Integer.parseInt( sText );
  }
  catch ( Exception ex )
  {
    return iDefault;
  }
}

Se pueden hacer versiones para tener más precisión (Integer, Long, Double, etc.). Por otro lado, la incorporación del valor por default permite asegurar que programáticamente se pueda definir/determinar la condición de error en la traducción (parsing)

Ejemplo 2. sValue.substring( 0, 10 );
Probablemente, ésta es una de las instrucciones simples y más peligrosas en el manejo de strings. La causa es simple: si el largo del string (sValue en el ejemplo) es menor al largo solicitado (10 en el ejemplo), se lanza una excepción por índice fuera de rango. En términos generales, está claro que las condiciones de borde debieran producir un error pero, el problema, es que en condiciones casi normales, también se produce el error. Además de la función que se podría hacer para controlar el error, lo sorprendente es que, según yo, para el caso más común (string más corto de lo solicitado), no se debería lanzar una excepción y se debiera retornar lo que se pudo.

En términos simples, si se quiere sacar(cortar) los primeros 10 caracteres de un string de 8... ¿Es suficiente con los 8 que hay? ¿Es necesariamente un error, que no haya 10?. La función que permite omitir este error, es la siguiente:

int left( String sText, int iLeft )
{
  if( sText == null || sText.length() == 0 )
    return sText;
  return sText.subString( 0, iLeft );
}

Ejemplo 3. BufferedInputStream bis = new BufferedInputStream( new FileInputStream( sFileName ) );
En este caso, además de los errores indicados anteriormente, hay un error no visible y que tiene que ver con el uso de los recursos del Sistema Operativo. En este caso, la creación del objeto BufferedInputStream a partir del objeto FileInputStream sin la declaración de una variable de por medio, no asegura que el recolector de basura realice el trabajo de liberación de los recursos asociados de manera correcta.

La manera de corregir este error, es declarando todas las variables intermedias. Adicionalmente, hay que asegurarse de declarlas de manera que estén disponibles para la sentencia finally{...} de manera de asegurar el correcto cierre de los recursos.

FileInputStream fin = null;
BufferedInputStream bis = null;

try
{
  fin = new FileInputStream( "c:/test.txt" );
  bis = new BufferedInputStream( fin );
  while( bis.available() > 0 )
  {
    // leer el archivo
  }
}
catch (Exception ex)
{
  // Procesar el error
  System.out.println("ERROR" + ex.getMessage() );
}
finally
{
  // Asegurar liberación de recursos del S.O.
  if( bis != null )
    bis.close();
  if( fin != null )
    fin.close();
}