Depurar Aplicaciones No Responsivas
Otros idiomas: English 한국어 Português 中文
Hay muchos tutoriales de depuradora que te enseñan cómo establecer puntos de interrupción de línea, registrar valores o evaluar expresiones. Si bien este conocimiento por sí solo te ofrece muchas herramientas para depurar tu aplicación, los escenarios del mundo real pueden ser un poco más complicados y requieren un enfoque más avanzado.
En este artículo, aprenderemos cómo localizar el código que provoca un bloqueo de la UI sin mucho conocimiento previo del proyecto y corregir el código defectuoso sobre la marcha.
El problema
Si quieres seguir el ejemplo, comienza clonando este repositorio: https://github.com/flounder4130/debugger-example
Supongamos que tienes una aplicación compleja que se bloquea cuando realizas alguna acción. Sabes cómo reproducir el error, pero la dificultad es que no sabes qué parte del código se encarga de esta funcionalidad.
En nuestra aplicación de ejemplo, el bloqueo ocurre cuando haces clic en el Button N. Sin embargo, no es tan fácil encontrar el código que es responsable de esta acción:
Veamos cómo podemos usar el depurador para encontrarlo.
Puntos de interrupción de método
La ventaja de los puntos de interrupción de método por encima de los puntos de interrupción de línea es que se pueden usar en jerarquías completas de clases. ¿Cómo es útil esto en nuestro caso?
Si observas el proyecto de ejemplo, verás que todas las clases de acción se derivan de la
interfaz Action
con un
solo método: perform()
.
Establecer un punto de interrupción de método en este método de la interfaz suspenderá la aplicación cada vez que se llame a uno de los métodos derivados. Para establecer un punto de interrupción de método, haz clic en la línea que declara el método.
Inicia la sesión de depuración y haz clic en el Button N. La aplicación queda suspendida en ActionImpl14
.
Ahora sabemos dónde está ubicado el código correspondiente a este botón.
Aunque en este artículo estamos centrados en encontrar el bug, esta técnica también puede ahorrarte mucho tiempo cuando quieras entender cómo funciona algo en una base de código grande.
Pausar aplicación
El enfoque con puntos de interrupción de método funciona bien, pero se basa en la suposición de que sabemos algo sobre la interfaz padre. ¿Qué pasa si esta suposición está equivocada, o no podemos usar este enfoque por alguna otra razón?
Bueno, incluso podemos hacerlo sin puntos de interrupción. Haz clic en el Button N, y mientras la aplicación se cuelga, ve a IntelliJ IDEA. Desde el menú principal, selecciona Run | Debugging Actions | Pause Program.
La aplicación se suspenderá, permitiéndonos examinar el estado actual de los hilos en la pestaña Threads & Variables. Esto nos da una idea de lo que la aplicación está haciendo en ese momento. Como está colgada, podemos identificar el método que causa el bloqueo y rastrearlo hasta el sitio de la llamada.
Este enfoque tiene algunas ventajas sobre un volcado de hilo más tradicional, que cubriremos en breve. Por ejemplo, te proporciona información sobre las variables en una forma conveniente y te permite controlar la ejecución adicional del programa.
Para más trucos y consejos con Pause consulta Depurar sin puntos de interrupción y Debugger.godMode()
Volcados de hilo
Finalmente, podemos utilizar un volcado de hilo, que no es estrictamente una característica del depurador. Está disponible independientemente de si estás utilizando el depurador.
Haz clic en el Button N. Mientras la aplicación se bloquea, ve a IntelliJ IDEA. Desde el menú principal, selecciona Run | Debugging Actions | Get Thread Dump.
Explora los hilos disponibles a la izquierda, y en AWT-EventQueue verás qué es lo que está causando el problema.
La desventaja de los volcados de hilo es que solo proporcionan una instantánea del estado del programa en el momento en que se hicieron. No puedes usar los volcados de hilo para explorar variables o controlar la ejecución del programa.
En nuestro ejemplo, no necesitamos recurrir a un volcado de hilo. Sin embargo, aún quería mencionar esta técnica ya que puede ser útil en otros casos, como cuando estás intentando depurar una aplicación que se ha lanzado sin el agente de depuración.
Entender el problema
Independientemente de la técnica de depuración, llegamos a ActionImpl14
.
En esta clase, alguien tenía la intención de realizar el trabajo en un
hilo separado, pero confundió
Thread.start()
con Thread.run()
,
que ejecuta el código en el mismo hilo que el código que realiza la llamada.
El analizador estático de IntelliJ IDEA incluso nos advierte sobre esto en tiempo de diseño:
Un método que hace un trabajo pesado (o duerme mucho en este caso) se llama en el hilo de la UI y lo bloquea hasta que el método termina. Es por eso que no podemos hacer nada en la UI durante un tiempo después de hacer clic en el Button N.
HotSwap
Ahora que hemos descubierto la causa del error, corrijamos el problema.
Podríamos detener el programa, recompilar el código y luego volver a ejecutarlo. Sin embargo, no siempre es conveniente volver a implementar toda la aplicación solo porque se hizo un pequeño cambio.
Hagámoslo de la manera inteligente. Primero, corrige el código utilizando la corrección rápida sugerida:
Después de que el código esté listo, haz clic en Run | Debugging Actions | Reload Changed Classes. Aparece un globo, confirmando que el nuevo código ha llegado a la VM.
Volvamos a la aplicación y comprobemos. Al hacer clic en Button N ya no se bloquea la aplicación.
Ten en cuenta que HotSwap tiene sus limitaciones. Si estás interesado en capacidades extendidas de HotSwap, podría ser una buena idea echar un vistazo a herramientas avanzadas como DCEVM o JRebel
Resumen
Usando nuestro razonamiento y un par de características del depurador, pudimos localizar el código que estaba causando un bloqueo de la UI en nuestro proyecto. Luego, procedimos a corregir el código sin perder tiempo en la recompilación y la redistribución, que puede ser larga en proyectos del mundo real.
Espero que encuentres útiles las técnicas descritas. ¡Hazme saber lo que piensas!
¡Estén atentos para más!