Garantizar el no tener bugs en un programa al 100 % como tal es inevitable. Cuando programamos pueden aparecer diferentes tipos de errores (de ahora en adelante usaremos el termino en inglés bugs para referirnos a ellos).

Aterrizando en el preDeggug ¿Porque debugear? Desarrollos, testers, escenarios, OK y KO

En la actualidad, hay que destacar que cada vez se hace más hincapié en la detección de bugs, especialidad que ha multiplicado exponencialmente la demanda de puestos de los buscadores de errores, los conocidos bajo el nombre de testers.

Los testers, son los especialistas en el testeo del software y gran parte de su trabajo es la definición de escenarios, con escenarios nos referimos a situaciones en las que se verificará el correcto funcionamiento del software. Es decir, son los encargados de definir las condiciones para realizar las pruebas, el entorno, las condiciones, etc. Para posteriormente reproducir estos escenarios con la finalidad de tratar de garantizar el correcto funcionamiento de la aplicación.

En caso de no ser así, y que el test nos diera un KO (error/bug) y no un OK (succes), normalmente, sería faena del desarrollador que ha realizado ese desarrollo el detectar y solventar los bugs.

Dada mi experiencia, en la mayoría de empresas, los tester solo prueban/verifican la funcionalidad y suele ser el programador el responsable del funcionamiento del desarrollo. Ante el hipotético caso en que un tester encuentre un fallo al testear un escenario, nos lo informará que el escenario ha dado un KO y seremos nosotros, los desarrolladores, los encargados de encontrar el bug y posteriormente de resolverlo.

Una vez resuelto y verificado el problema por nosotros, pasará la verificación del tester finalizando en OK o en el caso de volver a ser un KO, se repetirá el proceso nuevamente.

¿Qué es debugear/depurar?

Con el fin de ayudarnos a detectar/encontrar más fácilmente algunos tipos de errores que en algunos determinados casos hasta podrían hasta ser difíciles de reproducir en condiciones normales, la JVM (Java Machine Virtual o Máquina Virtual de Java) nos proporciona una herramienta conocida bajo el término de degugger o debugador.

Una herramienta que surge para cubrir las necesidades de encontrar algunos tipos de errores de una forma más sencilla gracias a lo que conocemos bajo el nombre de depurar (o debugear) el código del programa. Esta herramienta tiene como finalidad otorgarnos la capacidad de realizar depuraciones de código que incluso podremos realizar desde una máquina remota. Permitiéndonos llegar a desmenuzar el código hasta el punto de llegar a ver instrucción a instrucción y de forma visible por donde pasa el flujo de nuestro programa. Además también disponemos de varias opciones que nos permitirán el añadir una especie de marcas en nuestro código (los breakpoints) e ir saltando entre ellas viendo únicamente determinados puntos del programa, etc.

Algunos de estos errores hay que destacar que sin la herramienta de debugger serían difíciles de reproducir en condiciones normales, y gracias al debugger podremos obtener información muy importante sobre cómo se está ejecutando el código, útil para corregir errores y modificar su funcionamiento.

Todos los entornos de trabajo nos permiten realizar el proceso de debugear de forma muy sencilla y Eclipse el que estamos usando nosotros obviamente no será menos. Para ello, primeramente debemos de tener un proyecto con su correspondiente código que debugear. Veremos unos ejemplos un poco más abajo.

Además, es ideal para ayudarnos a entender mejor el código en casos en los que entender el código se hace sumamente difícil debido a su complejidad, el proceso de depuración, nos ayudará a ejecutar el código de una forma más interactiva observando el código fuente y las variables de ejecución muy fácilmente.

Permitiéndonos con ello el entender mejor su funcionamiento  analizando detenidamente, instrucción a instrucción, nuestro código y utilizando la capacidad que nos otorga el modo de depuración (debugger) que disponemos al ejecutar nuestro código en la máquina virtual de Java.

¿Qué es un flujo de un programa? ¿Cómo funciona?

Vamos a comenzar a ver el debuging, pero para entenderlo tenemos que entender previamente como se ejecuta el flujo de un programa.

Los programas se ejecutan de forma secuencial instrucción a instrucción de arriba a abajo y de izquierda a derecha. De una forma muy similar a la que leemos las personas. El flujo de un programa se ejecuta secuencialmente e ininterrumpidamente a excepción de nosotros escribamos una instrucción que detenga el flujo y una vez ejecutada esa instrucción el flujo continuará su ejecución. Vamos a ver dos par de ejemplos, uno que no detiene el flujo (el ejemplo 1) y otro que si (el ejemplo 2). 

Siguiendo el flujo de nuestro programa podremos ser capaces de conocer mejor los métodos, las clases, las funciones, las clases, etc.  Es decir, podremos desmenuzar es como si nos metiéremos dentro del ordenador y ejecutáramos sentencia a sentencia cada una de las instrucciones que tenemos en nuestro código. Ello nos ayudará tanto a entender mejor nuestro código, como a detectar mejor posibles errores de nuestro código.

Ejemplo 1

Ejemplo 1, un programa que ejecuta de inicio a fin continuadamente su flujo de ejecución:

Si iniciamos nuestro programa mediante a la combinación de teclas CONTROL+F11 o como ya bien sabéis desde el botón verde de la barra de tareas:

Podremos ver que el programa se ejecuta al completo desde inicio a fin.

Y una vez finalizado, nos aparece en la consola terminated y no nos permite detener la ejecución del programa ya que se ha finalizado por completo la ejecución del flujo.

Esto se produce debido a que no hay ninguna sentencia que detenga el flujo. Pero ¿Y si lo detenemos nosotros? Para ello necesitamos entender varios conceptos nuevo, el de iniciar el bugger y el de añadir un breakpoint. Pero antes nos detendremos un momento a ver el ejemplo 2.

Ejemplo 2

Un programa que detiene el flujo del programa en una instrucción:

Si nos fijamos ahora al ejecutar el programa, este no detiene su flujo y podemos finalizar el programa ya que su flujo aún continúa en ejecución.

Una vez añadimos un nombre y pulsamos aceptar:

Y finalmente pulsamos Aceptar:

Nos creemos que funciona correctamente, se lo pasamos al tester y nos da un KO y nos preguntan que ¿Qué pasa si le damos a Cancelar cuando nos pide que introduzcamos el nombre? Pues ya os digo que nos “peta” ¡BOOOMMMM! arrojándonos uno de los errores más famoso de todos, el NullPointerException.

Debugenado el ejemplo 1

Debugear el programa desde Eclipse es bastante sencillo. Tendremos que pulsar sobre el bichito verde que tenemos en la barra de tareas (o bien pulsar F11). Pero eso si ¡No me lo mateis eh! ¡Que este es inofensivo! ¡Y no hace nada! ¡Solo debugea!

Si debugeamos el ejemplo más sencillo el 1, pulsando el bichito, podremos ver que:

El programa se ejecuta de inicio a fin y por el momento no nos detiene el flujo de ejecución. ¿Porque no se ha detenido? ¿No habíamos dicho que se tenía que podía llegar a ver hasta instrucción a instrucción? Para que se detenga en algún punto, necesitamos un breakpoint.

En el hipotético caso que no aparezca la ventana de Debugger, podemos abrirla pulsando sobre Windows > Show View > Debug

¿Qué es un breakpoint?

Un breakpoint (o punto de ruptura o punto de parada) es el mecanismo que nos va a permitir detener el flujo de ejecución de un programa en una instrucción en concreto. Sería “similar” a lo que hacemos con el flujo del programa en el ejemplo 2, concretamente en la línea 21, en la que llamamos a m.inputName(); que contiene un JOptionPane -> JOptionPane.showInputDialog(null, Introduce tu nombre:); que nos ordena que introduzcamos nuestro nombre y que detiene el flujo de ejecución. Pero con la diferencia que mediante al breakpoint, no es necesario utilizar una instrucción que nos detenga el flujo ya que lo que estamos haciendo realmente es decirle a la JVM mediante a estas marcas (los breakpoints) que se detenga antes de continuar ejecutando el resto de instrucciones independienmente de que la sentencia sea un System.out.println que no detiene la ejecución o sea un JOptionPane que sí que lo detiene si hacemos una marca y le damos al bichito detendremos la ejecución.

Si no hemos definimos ningún punto de parada, el debugger se comportará de la misma forma que cuando ejecutamos nuestro programa de forma tradicional. Ya que es necesario definir puntos de interrupción para poder debugear el programa.

Por tanto, los breakpoints son similares a los stops en lo que a tráfico se refiere. Y  obligan a la JVM a detenerse si o si, independientemente que el tipo de instrucción detenga el flujo o no y hasta que nosotros le otorguemos el permiso de continuar avanzando. 

También, podemos afirmar que un punto de parada especifica dónde se detendrá la ejecución del programa durante su depuración. Una vez detenido, podremos ver los valores de las variables, modificar su contenido, etc. (lo veremos un poquito más abajo)

¿Cómo añadir un breakpoint en Java?

Para crear un breakpoint, tenemos varias opciones:

  • Situarnos sobre la línea y pulsar la combinación de teclas CTRL + Shift + B
  • Situarnos en la línea e ir a la opción de la barra de tareas Run > Toggle BreakPoint
  • Pulsar botón izquierdo sobre el margen de la línea y pulsar sobre la opción Toggle BreakPoint
  • Hacer doble click con el botón derecho justo un poco más adelante del inicio de la línea sobre el margen (sería la parte que se ve azul de la imagen inferior)

El resultado final de las 4 será un puntito en el lateral del margen como el siguiente:

¿Cuantos breakpoints puedo poner?

Podemos poner tantos breakpoints como queramos en la misma clase e incluso en otras clases que estén fueran de la clase que tenemos actualmente en abierta en la pantalla. Hasta un máximo de las líneas que tenemos en nuestras clases sin contar los corchetes de cierre. Un ejemplo sería, sobre la clase 1:

¿Cómo ver todos los breakpoints que he añadido?

Existen varias formas de ver los breakpoints que hemos añadido y son:

  • Realizar una debugación que nos llevará por todos los breakpoints por los que pasé el flujo. Aunque de esta manera puede ser que tengamos breakpoints añadidos sobre clases sobre las que no trabajamos en ese escenario y que estos no seamos capaces de detectarlos.
  • Visitando la pestaña de breakpoints (si no nos aparece, vamos a la ruta Windows > Show View > Breakpoints)

Debuggeando el ejemplo 1 con los breakpoints

Si arrancamos el debugger del programa del ejemplo 1, como ya hemos visto pulsando sobre el bichito, podemos ver como esta vez sí que el flujo se detiene en la línea 3 justo en nuestro primer breakpoint. 

Y podemos comprobar como Eclipse nos indica la línea actual sobre la que estamos situados poniéndola de un color verde y poniendo en el margen donde ponemos los breakpoints una pequeña flecha azul. Hay que matizar que esta línea no se ha ejecutado aún. 

Para que el flujo de ejecución continúe, tenemos que avanzar el flujo.

¿Cómo avanzando la ejecución hacía las siguientes instrucciones? 

Para ejecutar la sentencia seleccionada (la que está en verde y con una flecha en el margen), necesitamos continuar avanzando con la ejecución del flujo del programa. Ya que aún no se ha ejecutado. Para ello, Eclipse nos brinda un conjunto de comandos de depuración mediante a dos maneras:

  • Los iconos de la barra de tareas:

  • Desde la barra de tareas visitando Run y escogiendo la opción deseada:

Dentro del avance del flujo mediante al debug, tenemos distintas maneras de avanzar nuestro flujo. Vamos a ver un resumen:

Key Descripción

F5

(Step into)

F5 Ejecutará la instrucción seleccionada y pasará a la siguiente. Si le damos pasará a la siguiente línea en caso de no haber otra finalizará la ejecución del programa.

  • Ejecutamos el modo debug, y tenemos el flujo de ejecución del programa de la siguiente manera:

  • Pulsamos F5:

Hay que matizar que este tipo de debug, nos permitirá ver inclusive las clases internas sobre las que trabajamos en Java. Si volvemos a pulsar F5, podemos ver que nos lleva a PrintStream que nosotros no hemos programado pero si que utilizamos inderectamente con nuestro programa.

Este es el debugeo más profundo que podemos realizar.

F6

(Step Over)

F6 Ejecutará las instrucciones sin ejecutar los métodos internos. Si pulsamos F6 pasamos a la siguiente instrucción de nuestro programa.

  • Ejecutamos el modo debug, y tenemos el flujo de ejecución del programa de la siguiente manera:

Y si volvemos a pulsarlo, a la siguiente (SIN PASAR POR LOS MÉTODOS/CLASES INTERNAS DE JAVA)

F7

(Step Return)

F7 Para ver este ejemplo utilizaremos el Ejemplo 1 pero con unas leves modificaciones:

Este comando nos permite volver al método que llama a un método una vez estamos dentro del método que recibe la llamada. ¿Que lío verdad? Vamos a verlo más detenidamente con el ejemplo

  • Ejecutamos el modo debug, y tenemos el flujo de ejecución del programa de la siguiente manera:
  • Pulsamos sobre F8 y pasamos al siguiente breakpoint:
  • Pulsamos sobre F8 y pasamos al siguiente breakpoint que nos mete dentro del método addTxt que estamos llamando en la línea 6.
  • Y es ahora, cuando si pulsamos F7 podremos volver al método que realiza la llamada de la función. En este caso, todo está en la misma clase y es sencillo pero se utiliza mucho en proyectos muy grandes.
  • Si ahora volvemos a pulsar F8 no volveremos a adentrarnos en el método anterior, sino que nos irá al breakpoint de la última línea:
  • Y ahora si volvemos a pulsar F8, o F6, finalizaremos la ejecución y el flujo del programa.

F8

(Resume)

F8 Le indica a la JVM que reanude la ejecución de nuestro código hasta que alcance el siguiente punto de interrupción o punto de observación. Si no hay más breakpoints, el sistema sale del modo de depuración y ejecuta el resto del programa normalmente.

En el resto del programa, que no haya un breakpoint, no significa que no exista una instrucción que detenga el flujo del programa como el JOptionPane del ejemplo2. En el hipotético caso que no exista ningún breakpoint ni ninguna instrucción que detenga el flujo, el programa continuará hasta finalizar ejecución.

  • Ejecutamos el modo debug, y tenemos el flujo de ejecución del programa de la siguiente manera:

  • Tras pulsar F8, el debug pasa al siguiente breakpoint:

  • Si volvemos a pulsar F8 al no existir más breakpoints, se finalizará el programa.

¿Qué errores puedo debugear? ¿Y cuáles no?

Algunos de estos bugs (aunque existen muchos más motivos) nacen debidos a:

  • Errores de sintaxis: no se pueden debugear debido a que le programa no será capaz de realizar exitosamente su compilación. Podemos afirmar por tanto, que hay un error de “ortográfico” en nuestro software que nos impide realizar la compilación. Un ejemplo podría ser que no existe un punto y coma (;) al final de una instrucción. En estos casos, el propio IDE nos mostraría una alerta en la ventana de inspección del código con una X en roja o una bombillita con una X en su interior y por tanto, como no hemos realizado exitosamente la compilación no podremos ejecutar ni posteriormente debugear dicho código con el fin de buscar bugs debido a que si no escribes bien el código Java no será capaz de reconocerlo. Po tanto, no se pueden debugear. Un ejemplo podría ser:

  • Errores de lógica: si no tenemos errores de sintaxis ni errores de Excepciones y nuestro programa no hace lo que queremos posiblemente nos encontremos ante un error de lógica del código. Un ejemplo puede ser este código que no contempla que la longitud del texto sea igual a 10 ya que no entraría en ninguno de los IFS.

  • Errores en tiempo de ejecución: serían las excepciones. Dentro de estás se subdividen en:
    • Excepciones controladas (IOException): 
    • Excepciones no controladas (RuntimeException):

Un ejemplo podría ser el del ejemplo 2, un error de ejecución:

En este caso, si buscamos el error java.lang.NullPointerException en la API de Java, (os dejo el link aquí) podemos observar que realmente extiende de RuntimeException. Por lo que estamos hablando de una excepción no controlada.

¿Cuándo se produce ese error? Pues para saberlo, como ya sabemos lo ideal es debugear el código:

Si vamos añadiendo breakpoints y hacemos un debug (os aconsejo ir paso a paso con F6), podemos ver que justamente es en la instrucción 22 cuando se produce el error:

Para resolverlo:

También lo he solucionado de otra manera, lo he reformateado y lo he hecho de otra manera con métodos que se pueden reutilizar y recursividad.

¿Cuál os gusta más? 🙂

¿Cómo eliminamos los breakpoints?

Para eliminar los breakpoints, tenemos varias opciones:

  • Desmarcar los breakpoints tenemos que hacer lo mismo que para ponerlo pero sobre una instrucción que tenga un breakpoint ya puesto. La forma más sencilla es haciendo doble click  sobre los breakpoints que ya tenemos puestos (pulsando justo encima del punto).
  • Ir a la pestaña de Breakpoints y pulsar sobre el breakpoint a eliminar y pulsar la X simple. En el caso de querer eliminarlos todos, pulsaremos sobre las X dobles.
  • Desde Run > Remove All Breakpoints para eliminarlos todos o Run > Toggle Breakpoint situados sobre la línea del breakpoint a eliminar.

¿Cómo saltar breakpoints?

En muchas ocasiones queremos mantener la marca del breakpoint (el punto de ruptura) pero no queremos que se realice la parada en este punto durante la debugación que vamos a iniciar. Para ello, el debugger nos proporciona mecanismos para mantener esa marca pero sin que se detenernos el flujo en dicho breakpoint.

Para ello si basándonos en el ejemplo 1 si ponemos 4 breakpoints, podemos observar que los breakpoints hasta ahora son círculos normales de color azul. Eso significa que los breakpoints detendrán el flujo.

Para saltarlos, tenemos varias opciones:

Deshabilitar todos los Breakpoints

Bien desde la ventana de Breakpoints:

O bien desde Run > Disable All Breakpoints:

En ambos casos, suponen que el breakpoint saldrá con una línea lateral como si se hubiera puesto el cinturón del coche.

Si ahora le damos al debugger, comprobaremos que se ejecutará sin pasar por ninguno de los breakpoints.

Si pulsamos el botón sin más nuevamente, podemos volver a activarlos:

El deshabilitar todos los breakpoints es ideal para tener un proceso de depuración iniciado en el que queremos finalizar el flujo de ejecución hasta el final sin “matarlo” pulsando el botón de STOP,  como suelo decir “deshabilitar en caliente” entonces iniciamos el debug, llegamos al punto que deseemos del programa. En nuestro caso será la primera instrucción:

Pulsamos sobre la ventana de Breakpoints y seleccionamos la opción de Skip All Breakpoints  

Inmediatamente después de pulsarlo, podremos ver como el programa empieza a ejecutarse y al no haber instrucciones que requieren una detención del flujo del programa, la ejecución de este, llega hasta el final eso si cuando lo reanudemos pulsando F8. Ya que actualmente se encuentra detenida, aunque con los breakpoints desactivados.

Deshabilitar algunos Breakpoints

Hasta ahora hemos visto como deshabilitar todos los breakpoints, pero que pasa si solamente queremos deshabilitar uno o varios, es decir algunos y no todos como en el ejemplo anterior.

Para ello, seguiremos trabajando sobre el ejemplo 1 con los 4 breakpoints:

Para deshabilitar algunos breakpoints, tenemos varias opciones, vamos a verlas:

  • Pulsar sobre el breakpoint con botón derecho e ir a la opción de Disable Breakoint o usar la combinación de teclas SHIFT + doble click

  • Pulsar sobre el breakpoint con botón derecho desde la ventana de Breakpoints y seguidamente sobre Disable.

En nuestro caso, vamos a deshabilitar los dos breakpoints de las líneas 5 y 6. Para ello, ya sabéis como hacerlo ¡Así que vosotros solitos!

El resultado tiene que ser el siguiente:

Si ahora, ejecutamos el programa en modo debug, y vamos pulsando sobre F8, podremos ver que nos pasa de la línea 3 a la línea 8:

Y finalmente se finaliza el ejecución del programa:

Este tipo de omisión de breakpoints es ideal para seguir manteniendo los breakpoins pero sin tener necesidad de detenerte en ellos.

Modificando el valor de una variable desde el debug

Una de las cosas de las que hemos hablado al principio de este artículo, al hablar del debugger, era que el debug, nos permitía reproducir condiciones que propiciaran errores que en condiciones normales serían difíciles de reproducir.

Vamos a ver un ejemplo de ello con un código nuevo:

Si debugeamos el ejemplo, podemos ver que tenemos una cadena de texto de 12 caracteres de longitud. 

Pulsamos F6 y vamos pasando instrucción por instrucción. Pasamos por el if y al no cumplirse la condición de que sea menor que 10 no entramos en él.

Vamos a la siguiente instrucción el bloque else if y vemos que tenemos un una condición en su interior que evalúa si txt es mayor  10 caracteres. 

En este caso, esta condición si que se cumple y por tanto, el flujo entra en el bloque y ejecuta la sentencia.

Hasta aquí todo bien, pero imaginaros que queremos hacer una prueba con el mismo texto pero sin espacios Javadesde0. En este caso, tan solo tendríamos que modificar la variable txt. Tarea sencilla ya que hemos cogido un ejemplo sencillito para ver su funcionamiento. Pero en otros casos reproducir el bug desde el debug, puede ser una tarea difícil de realizar ya que quizás para testear el caso, necesitamos modificar varias variables para poder por ejemplo entrar en un if. Por lo que no vamos a modificar el valor de la variable txt directamente, sino que lo vamos a realizar desde el modo debug. 

Para ello vamos a ir a la ventana Variables, Si no nos aparece iremos a Window>Show View>Variables si no sale le daremos a Other y buscaremos Variables

En este momento no nos aparece la variable txt debido a que la línea 3 no se ha ejecutado aún. 

Si le damos a F6 y avanzamos a la siguiente instrucción vemos como nos aparece la variable Java desde 0 y si abrimos el value, podemos ver que es un array de 11 elementos empezando por desde 0. Por lo que son 12 elementos en total, el equivalente a su longitud.

Ahora, podemos situarnos sobre la variable haciendo un click encima con el botón derecho y veremos que el contenido se pone en azul

Y ahora modificamos el valor. Tras la modificación, podremos observar como el array se ha reducido a 10 elementos y por tanto, ahora la variable txt tiene una longitud de 10 caracteres. Una cosa a matizar y que es bastante importante es que no hayamos entrado en el if ya que sino evaluaría el valor original del txt antes de modificar la variable por debugger.

Una vez modificada, pulsamos de nuevo F6 y comprobamos que no entra en el primer if (ni lo hará en el segundo)

Y que si avanzamos el flujo hasta el final (hasta que se quite la línea verde de nuestro código) podremos comprobar que en la consola no se ha ejecuta nada.

Conclusión

Bueno, este largo tutorial llega a su fin, espero que os haya gustado y que os hayan quedado más claros muchos conceptos. Aunque esto es solo una parte de todo lo que se puede llegar a profundizar en el debugeo con Java y Eclipse, el cual es un mundo y os puedo asegurar que da para muchos más artículos.

¡Un saludo javer@s!