lunes, 5 de enero de 2009

Búsqueda de errores y depuración: una historia, un poco de metodología y algunos consejos.

Último día laboral de este 2008. 7.20 de la mañana (¡sí señor!), ya despabilado y mate en mano frente a la pantalla. Mientras inicia Windows, el entorno de desarrollo y el proyecto (y un largo etc.) leo un auto-recordatorio de baja tecnología (un papel suelto sobre el teclado) que dice "ver incidente 1269". Me acuerdo del protagonista de Memento quien, como yo, tiene el Garbage Collector demasiado activo: no recuerdo de qué se trata el maldito 1269.

Resumamos esquemática y velozmente algo que en realidad es un poco más complejo, pero no viene al caso: vendo una combinación de "fichas" de diferentes valores preestablecidos, eligiendo el usuario la combinación deseada. La pantalla pide primero el importe, luego ofrece la lista con las distintas fichas disponibles donde el usuario indicará la cantidad requerida de cada una.

Hay un tipo de ficha que es el más vendido. Por lo tanto cuando el usuario ingresa el importe y lo confirma se presenta completado por defecto el cuadro correspondiente a este tipo con el importe traducido en cantidad.

Como el usuario puede cambiar los valores, al final se comprueba que la entrada (el importe) sea igual a la salida (el valor de las fichas) y si está todo ok, se graba y listo.

Ya tenemos la primera máxima:

Ver el cuadro completo antes de bajar a los detalles: luego de una leída rápida al reporte del incidente hay que abocarse en obtener "the big picture": la mirada del cliente (humano o no), determinar el papel de esta funcionalidad dentro del sistema y, sobre todo: saber cómo debería funcionar.

Una nota: el caso en particular puede variar, pero en general un reporte de error o incidente sólo describe una situación, que no debería aceptarse como un error sin más análisis. Por ello el siguiente paso es reproducir la situación y clasificarla (por ejemplo: "error del sistema", o "error del entorno de ejecución", "nueva funcionalidad", o "error en el reporte de error" -valga la redundancia- o etiquetas similares).

Un poco de manoseo con la pantalla en cuestión no dejó lugar a dudas: si yo ingresaba un importe imposible de vender en fichas (digamos $10,31 y yo tengo fichas de $1 cada una) el sistema redondeaba la cantidad ofrecida por defecto (en mi ejemplo ofrecería vender 10 u 11 fichas). Al confirmar la transacción se leía un bonito mensaje "la transacción no suma 0" (una excepción de negocio medio genérica que se aplica a un sinfín de situaciones). Hasta ahí iba bastante bien aunque pensé que podría mejorar el mensaje. Pero cuando retrocedí y corregí el importe y volví a avanzar... ¡crash bum pang! Un error no controlado rompía la mitad de la pantalla. Ok. Hay un error, no cabe duda.

Esto da para un poco de teoría: el primer mensaje fue una excepción (controlada), el segundo un error. Escribí un poco más sobre eso hace poco: Errores y excepciones parte I, parte II y parte III. Pero citemos así seguimos adelante:

[...] Entiendo que una excepción es una situación inusual en la ejecución del sistema, pero contemplada al momento de diseñar el sistema o de escribir el código. [...] Un error, a diferencia de la excepción, es una respuesta o estado no contemplado en el sistema. [...]

Es decir, que el dinero ingresado no coincidiera con el valor de las fichas que salían era algo contemplado, se estaba chequeando (aunque me molestara el mensaje). Pero el posterior aterrizaje poco sutil de la aplicación, digamos, fue resultado de algo no contemplado por nadie (espero).

Así que me puse a leer ese feo mensaje de error. Completo, con stack trace y todo. Aunque ud. no lo crea, esto es algo que poca gente hace. Y así es como nos llenan de reportes de error del tipo "La conexión a la base de datos se ha caído." o "Disk Full". Es decir, situaciones que están claramente descritas en ese mensaje... que nadie lee.

Pero no son sólo los usuarios. Cierta ansiedad profesional nos lleva a enchufar el depurador, reproducir la situación, concentrarnos en la línea que tira el error y empezar desde ahí (tratando desesperadamente de no ir a ningún otro lado, como si todos los errores estuviesen en la última línea que se ejecutó del código). Mucha de esa información está ahí, delante nuestro.

Un dato importante obtenido al leer el mensaje que explotó la aplicación fue, justamente, "El tipo de ficha ya se encuentra incluido en la transacción". Es decir, era un mensaje codificado por nosotros, no un error descontrolado. En algún lado del código la situación fue tratada como una excepción (es decir contemplada), pero la aplicación explotó igual.

El siguiente punto de análisis es ¿qué debería haber pasado? Para corregir un error primero tenemos que saber cómo nos hubiese gustado que termine la historia. Como todo en sistemas, se empieza por la salida. ¿A dónde quiero llegar?

Pensé un poco en la situación, ordenando los problemas cronológicamente:

  • En principio, me debería haber informado de que no se podía vender por ese importe cuando lo confirmé y no dejarme seguir hasta el final de la operación. Los errores de entrada se informan lo antes posible.

  • Después, al confirmar toda la operación, el mensaje podría haber sido algo así como "No puede conformar el importe indicado con las fichas disponibles" en vez de eso tan extraño de "La transacción no suma 0".

  • Para seguir, al ir hacia atrás y volver a intentar se ve que el sistema intentó ingresar a la operación el mismo tipo de ficha dos veces, o aún más probable, que no haya borrado lo que estaba antes de reintentar.

    Eso se deduce del segundo mensaje, "El tipo de ficha ya se encuentra incluido en la transacción". No puedo decir que vendo 5 fichas de $1 por un lado y otras 5 de $1 por el otro en la misma operación, tiene que detallarse una y sólo una vez cada tipo de ficha: son 10 fichas de $1.

    Una nota importante: mal por el front-end, que primero se olvidó de borrar y después no contempló la posibilidad de una excepción. Bien por el negocio que controló la situación salvando las papas del fuego al cancelar la operación y arrojar una excepción con un mensaje decente. Es como si hubiese gritado dramáticamente "¡no puedo hacer eso, soy demasiado bueno, prefiero morir!"). ¿O hubiesen preferido quedar mejor con el cliente, que no pase nada, ni reportes, ni llamadas desesperadas, ni nada y encontrarse a fin de mes con 10.000 ventas que no cierran?.

  • Pero ese último mensaje debería haberse mostrado prolijamente como el primero, sin embargo en la pantalla apareció como un error no controlado.

    Otra nota: si luego de este bodoque siguen con ganas de leer los artículos citados, verán que esta situación indica que una excepción lanzada en la capa de negocio hizo "explotar" a la capa de presentación porque debido a un error ella no se lo esperaba. El problema no es el mensaje, sino que la capa de presentación no lo esperara y mostrara correctamente.

Me gustaría que los líderes de proyecto que lean esto lo tengan en mente cuando en su próximo ataque de ansiedad (ya sé que uds. ponen la cara) pretendan que arreglemos "esa bobada de los centavos" en 10 o 15 minutos o renieguen de que "el sistema explotó por una boludez tontería (pero nosotros tenemos que aguantarlos, y no tenemos a nadie más para descargar tensiones... lo que me recuerda "Reacciones ante los errores en un sistema informático").

Como podemos ver, que el sistema explote por una bobada es de por sí un error grave que trasciende esa bobada.

Ante este tipo de situaciones tenemos dos alternativas: resolver la bobada, y luego la otra y la otra y la otra (porque bobadas hay muchas) o resolver el problema de fondo logrando que el sistema resista las bobadas dejándonos más tranquilos y con más tiempo para trabajar en lo que nos gusta.

Para ser más explícito y utilizar el ejemplo: hay una larga cadena de errores, pero el último es el peor. Si ese mensaje se hubiese mostrado correctamente en vez de hacer explotar la pantalla,

  • el usuario se habría dado cuenta de que con reiniciar la operación bastaba para seguir trabajando,
  • lo hubiese percibido como una desprolijidad menor y no como un error de la aplicación,
  • y así la prioridad de todo el asunto baja bastante, ¿no?

Por supuesto, es más fácil arreglar el problema puntual de los centavos que solucionar todo eso.

Pero por suerte no le explotó al cliente en producción sino a nuestro equipo de pruebas (bien por ellos) con lo cual en vez de nervioso me sentía contento y aliviado. La misma sensación que debe tener un acróbata cuando tras fallar una pirueta aterriza suavemente en su red de seguridad.

No lo aclaré antes porque le resta dramatismo a todo.

Para finalizar:

  • ¿Programador principiante? Mira la línea en la que saltó el error y no entiende nada. Va hacia atrás y donde puede poner un "if" para evitar la situación lo enchufa, probablemente rompiendo otra cosa.
  • ¿Programador desganado? Arregla lo de los centavos (el primer problema) y todo lo demás desaparece... por un rato.
  • ¿Programador apurado? Arregla el último problema (el del mensaje) y manda el arreglo. Deja los demás registrados detalladamente con una solución propuesta para después.
  • ¿Yo en el último día de trabajo del año? En apenas 9 horitas lo tengo todo resuelto, cuando alguien me pida explicaciones le paso un link a este post, aunque seguramente no llegue al final.

¿Y cómo se corrige eso? Del último al primero:

  • que el mensaje de excepción no explote la pantalla (y lo pruebo),
  • que borre la basura de la operación incompleta evitando que tire ese mensaje (y lo pruebo),
  • cambiando el mensaje al final de la transacción para que diga claramente que no puede vender ese importe en fichas porque no hay forma (y lo pruebo),
  • y por último agregando un control para que controle esto apenas ingresado el importe (y lo pruebo).

Y así, mi catedral no sólo es más bonita, también es más resistente.

Un tip: vean que de esta manera voy probando todos los arreglos a medida que los voy implementando con el mismo caso de prueba. Si empiezan por el final, luego probar todo es un problema.

Espero que hayan llegado hasta aquí leyendo todo, despiertos y con ganas de vivir...

Ah, por cierto, cuando volvía para casa a las 18.00 me dí cuenta de un pequeño detalle que introduje en el último arreglo: si quiero vender $10.50 y tengo fichas de $1 y de $0.50 (operación 100% válida) pero tengo como predeterminada la ficha de $1 el sistema va a tratar de proponer $10.50 en fichas de $1, no va a poder y por obra y gracia de mis controles prematuros va a trabar toda la operación sin darle posibilidad al usuario de modificar las cantidades.

En resumen: corregí un buen par de problemas graves, pero por un pequeño error lo dejé, a ojos del mundo, peor que como estaba. ¡Ah! Y para eso tardé todo el día.

¿Me acordaré de corregirlo cuando vuelva? Seguro que sí, porque ese día es hoy y alguien me va a avisar. Magias de la programación de posts.

No hay comentarios.: