Recolección de Evidencias

El primer paso para intentar resolver cualquier problema es obtener tanta información como sea posible. Si podemos imagninarnos la escena de un crimen,sabemos que todo está chequeado, catalogado y analizado antes de alcanzar cualquier conclusión. Cuando se depura un programa, no tenemos armas, muestras de pelo, ni huellas dactilares, pero existen cantidad de evidencias que podemos obtener y que podrían contener o apuntar a la solución última. Esta sección explíca como recoger esas evidencias.

Instalación y Entorno

La plataforma Java TM es una tecnología cambiante y de rápido movimiento. Podríamos tener más de una versión instalada en nuestro sistema, y esas versiones podrían haber sido instaladas como parte de la instalación de otros productos. En un entorno con versiones mezcladas, un programa puede experimentar problemas debido a los cambios de la plataforma en las nuevas versiones.

Por ejemplo, si las clases, las librerías, o las entradas de registro de Window de instalaciones anteriores permanecen en nuenstro sistema después de una actualización, hay una oportunidad de que la mezcla del nuevl software sea la causante de nuestros problemas y necesita ser investigada y eliminada. Las oportunidades para los problemas relacionados con la mezcla de versiones de software se ha incrementado con el uso de diferentes versiones de herramientas para desarrollar software de la plataforma Java.

La sección sobre Problemas con Versiones al final de este capítulo proporciona una lista completa de las principales versiones de la plataforma Java para ayudarnos a resolver nuestros problemas con versiones de software.

Path de Clases

En la plataforma Java 2, la variable de entorno CLASSPATH es necesaria para especificar a la propia aplicación dónde están sus clases, y no las clases de la plataforma Java como en versiones anteriores. Por eso es posible que nuestro CLASSPATH apunte a las clases de la plataforma Java desde versiones anteriores y nos cause problemas.

Para examinar el CLASSPATH, tecleamos esto en la línea de comando:

Windows 95/98/NT:
echo %CLASSPATH%

Unix:
echo $CLASSPATH

Las clases Java se cargan en primer lugar, primera forma básica de la lista CLASSPATH. Si la variable CLASSPATH contiene una referencia a un fichero lib/classes.zip, que apunta a una instalación diferente de la plataforma Java, esto peude causar que se cargen clases incomplatibles.


Nota: En la plataforma Java 2, las clases del sistema se eligen antes de cualquier clases de la lista CLASSPATH para minimizar de que se caeguen clases Java anteriores a la clase Java 2 del mismo nombre.

La variable CLASSPATH puede obtener su configuración desde la línea de comandos o desde las selecciones de configuración como aquellas especificadas en el Entorno de Usuario sobre Windows NT, un fichero autoexec.bat, o un fichero de arranque del shell .cshrc sobre Unix.

Podemos controlar las clases de la Máquina Virtual Java usadas para compilar nuestros programas con una opción especial de la línea de comandos que nos permite suministrar el CLASSPATH que querramos. La opción y parámetro de la plataforma Java 2 -Xbootclasspath classpath, y las versiones anteriores usan -classpath classpath y -sysclasspath classpath. Sin importar la versión que estamos ejecutando, el parámetro classpath especifica el classpath del sistema y del usuario, y los ficheros zip o JAR a usar en la compilación.

Para compilar y ejecutar el programa Myapp.java con un CLASSPATH suministrado en la línea de comandos, usamos las siguientes instrucciones:

Windows 95/98/NT:

En este ejemplo, la plataforma Java está instalada en el directorio C:\java. Tecleamos los siguiente en una sóla línea:

javac -J-Xbootclasspath:c\java\lib\tools.jar;c:
\java\jre\lib\rt.jar;c:\java\jre\lib\i18n.jar;.  
  Myapp.java

No necesitamos la bandera -J para ejecutar el programa Myapp compilado, sólo tecleamos esto en una sóla línea:

java -Xbootclasspath:c:\java\jre\lib\rt.jar;c:
\java\jre\lib\i18n.jar;.  Myapp

Sistemas Unix:

En este ejemplo, la plataforma Java está instalada en el directorio /usr/local/java. Tecleamos todo en una sóla línea:

javac -J-Xbootclasspath:/usr/local/java/lib/tools.jar:
/usr/local/java/jre/lib/rt.jar:
/usr/local/java/jre/lib/i18n.jar:. Myapp.java

No necesitamos la bandera -J para ejecutar el programa Myapp compilado, sólo tecleamos esto en un sóla línea:

java -Xbootclasspath:/usr/local/java/jre/lib/rt.jar:
/usr/local/java/jre/lib/i18n.jar:. Myapp

Carga de Clases

Otra forma de analizar problemas con el CLASSPATH es localizar desde dónde está cargando las clases nuestra aplicación. La opción -verbose del comando java muestra de donde vienen los ficheros .zip o .jar cuando se carga. De esta forma, podremos decir si vienen del fichero zip de la plataforma Java o desde algún fichero JAR de la aplicación.

Por ejemplo, una aplicación podría estar usando la clase Password que escribimos para ella o podría estar cargando la clase Password desde la herramienta IDE instalado.

Deberíasmos ver cada nombre de fichero zip o Jar como se vé aquí:

$ java -verbose SalesReport
[Opened /usr/local/java/jdk1.2/solaris/jre/lib/rt.jar 
        in 498 ms]
[Opened /usr/local/java/jdk1.2/solaris/jre/lib/i18n.jar 
        in 60 ms]
[Loaded java.lang.NoClassDefFoundError from 
	/usr/local/java/jdk1.2/solaris/jre/lib/rt.jar]
[Loaded java.lang.Class from 
	/usr/local/java/jdk1.2/solaris/jre/lib/rt.jar]
[Loaded java.lang.Object from 
	/usr/local/java/jdk1.2/solaris/jre/lib/rt.jar]

Incluir Código de Depurado

Una forma común de añadir código de diagnóstico a una aplicación es usa sentencias System.out.println en posiciones estratégicas de la aplicación. Esta técnica está bien durante el desarrollo, pero debemos acordarnos de eliminarlas todas antes de liberar nuestro producto. Sin embargo hay otras aproximaciones que son tan sencillas y que no afectan al rendimiento de nuestra aplicación, y no muestra mensajes que no queremos que vea el cliente.

Activar la Información de Depuración en Tiempo de Ejecución

La primera alternativa a las clásicas sentencias de depuración println es activar la información de depuración en el momento de la ejecución. Una ventaja de esto es que no necesitamos recompilar ningún código si aparecen problemas mientras hacemos pruebase en la oficina del cliente.

Otra ventaja es que algunas veces los problemas de software pueden ser atribuidos a condiciones de carrera donde el mismo segmento de código se convierte en impredecible debido al tiempo entre cada iteracción del programa. Si controlamos el código de operación desde la línea de comandos en lugar de añadir sentencias de depuración println, podemos arreglar la secuencia de problemas que causa las condiciones de carrera que vienen desde el código println. Esta técnica también nos evita tener que añadir y eliminar las sentencias println y tener que recompilar nuestro código.

Esta técnica requiere que usemos una propiedad del sistema como bandera de depurado y que incluyamos código en la aplicación para comprobar que el valor de esta propiedad del sistema. Para activar la información de depuración desde la línea de comandos en el momento de la ejecución, arrancamos la aplicación y seleccionamos la propiedad del sistema debug a true de esta forma:

java -Ddebug=true TestRuntime

El código fuente que necesita la clase TestRuntime para examinar esta propiedad y configurar la bandera booleana debug de es el siguiente:

public class TestRuntime {
    boolean debugmode; //global flag that we test

    public TestRuntime () {

       String dprop=System.getProperty("debug");

       if ((dprop !=null) && (dprop.equals("yes"))){
            debugmode=true;
       }

       if (debugmode) {
           System.err.println("debug mode!");
       }
    }
}

Crear Versiones de Depuración y Producción en Tiempo de Compilación

Como se mencionó antes, un problem con la adición de sentencias System.out.println para depurar nuesto código es que debemos eliminarlas antes de liberar nuestro producto. Además de añadir código innecesario, las sentecias de depuración println pueden contener información que no queremos que vea el cliente.

Una forma de eliminar las sentencias de depuración System.out.println de nuestro código es usar la siguiente optimización del compilador para eleminar los corchetes pre-determinados de nuestos código en el momento de la compilazión y activar alguna algo similar a un depurador pre-procesador.

Este ejemplo usa una bandera booleana estática dmode que cuando se selecciona a false resulta en la eliminación el código de depuración y de las sentencias de depuración. Cuando el valor de dmode se selecciona a true, el código es incluido en el fichero class compilado y está disponible en la aplicación para propósitos de depuración.

class Debug {

  //set dmode to false to compile out debug code
  public static final boolean dmode=true;
}

public class TestCompiletime {

  if (Debug.dmode) {                       // These 
    System.err.println("Debug message");   // are 
    }                                      // removed
}

Usar Métodos de Diagnósticos

Podemos usar métodos de diagnóstico para solicitar información de depuración desde la máquina virtual Java (JVM). Los dos siguientes métodos de la clase Runtime siguel las llamadas a métodos y los bytes codes de la JVM que usa nuestra aplicación. Como estos dos métodos producen cantidad de informacióne s mejor seguir pequeñas cantidades de código, incluso tan pequeñas como una línea a la vez.

Para permitie seguir las llamadas, tenemos que arrancan la JVM con los comandos del intérprete java_g o java -Xdebug.

Para listar cada método cuando es invocado durante la ejecución, añadimos la siguiente línea antes del código donde queremos empezar a seguir la pista y añadimos la correspondiente línea traceMethodCalls con el argumento seleccionado a false para desactivar el seguimiento. La información de seguimiento se muestra en la salida estándard.

// set boolean argument to false to disable
Runtime.getRuntime().traceMethodCalls(true);
callMyCode();
Runtime.getRuntime().traceMethodCalls(false);
Para ver cada línea en bytecodes cuando se ejecutan, añadimos la siguiente línea al código de nuestra aplicación:
// set boolean argument to false to disable
Runtime.getRuntime().traceInstructions(true);
callMyCode();
Runtime.getRuntime().traceInstructions(false);
También podemos añadir la siguiente línea para que nuestra aplicación vuelque la pila usando el método dumpStack de la clase Thread. La salida de este volcado de pila se explica en Análisis y Seguimiento de la Pila, pero ahora podemos pensar en la pila como un apunte de los threads que se están ejecutando en la JVM.
Thread.currentThread().dumpStack();

Añadir Información de Depurado

La información de variables locales no está incluida en el corazón de las clases del sistema de la plataforma Java. Por eso, si usamos una herramienta de depuración para listar variables lcoales para clases del sistema donde coloquemos comandos stop , obtendremos la siguiente salida, incluso cuando compilemos con la bandera -g como sugiere la salida. Esta salida es de una sesión jdb:
main[1] locals
No local variables: try compiling with -g
Para obtener acceso a la información de variables lcoales, tenemos que obtener el fuente (src.zip o src.jar) y recompilarlo con una bandera debug. Podemos obtener el fuente de la mayoría de las clases java.* classes con la descarga de los binarios desde java.sun.com.

Una vez hayamos descargado el fichero src.zip o src.jar, extraemos sólo los ficheros que necesitamos. Por ejemplo, para extraer la clase String, tecleamos esto en la línea de comandos:

unzip /tmp/src.zip src/java/lang/String.java
o
jar -xf /tmp/src.jar src/java/lang/String.java
Recompilamos la clase o clases extraidas con la opción -g. También podemos añadir nuestros propios diagnósticos adicionales sobre el fichero fuente en este momento.
javac -g src/java/lang/String.java

El compilador Java 2 javac ofrece más opciones que sólo la opción original -g para código de depuración, y podemos reducir el tamaño de nuestras clases usando -g:none, que nos ofrece una reducción de un 10% del tamaño.

Para ejecutar la aplicación con las nuevas clases compiladas, necesitamos usar la opcioón bootclasspath para que esas clases se utilicen en primer lugar.

Tecleamos lo siguiente en una sóla línea con espacio antes de myapp.

Plataforma Java 2 Win95/NT:

Este ejemplo asume que la plataforma Java está instalada en c:\java, y los ficheros fuente están en c:\java\src:

jdb -Xbootclasspath:c:\java\src;c:\java\jre\lib\rt.jar;c:
\java\jre\i18n.jar;.  myapp

Sistemas Unix:

Este ejemplo asume que la plataforma Java está instalada en /usr/local/java, y los ficheros fuente están en /usr/local/java/src.

jdb -Xbootclasspath:/usr/java/src;
/usr/java/jre/lib/rt.jar;
/usr/java/jre/i18n.jar;.  myapp

La siguiente vez que ejecutemos el comando locals veremos los campos internos de la clase que deseamos analizar.


Ozito