El peor o más difícil bug 🐞 que me he encontrado

El peor o más difícil bug que me he encontrado

Alguien me preguntaba cuál ha sido el peor bug que me he encontrado y he resuelto.

En una aplicación de uso masivo en la que trabajaba, encontramos que 3 de cada 1000 transacciones (0,003% de las veces) quedaba registrada con el nombre de otro cliente. En el equipo sospechábamos que tenía algo que ver con la concurrencia, pero lo curioso del caso es que no tenía nada que ver con:

  1. La hora.
  2. La región del país.
  3. Datos coincidentes entre los clientes.
  4. Datos en común entre los usuarios que registraban la información de los usuarios: oficina, jefe, etc.
  5. Tampoco era simultaneidad o transacciones creadas casi que, al mismo tiempo, podía haber horas de diferencia entre los datos ingresados.

Dada la poca frecuencia no se asignó al caso más que unas horas de investigación en las cuales no se halló la solución y durante casi un año solo debatíamos de vez en cuando sobre el extraño fenómeno y sus posibles causas; hasta que un día llegué a la solución intentando solucionar otra cosa.

El error se producía por una acumulación de anti-patrones (lo contrario a patrones de diseño) y otra clase de errores humanos durante el desarrollo en una clase del proyecto que: 

  1. Rompía el Principio de Responsabilidad Única (SRP: Single Responsibility Principle) de SOLID que puede resumirse como “una clase, una tarea”. En este caso, la clase exponía al tiempo dos servicios.
  2. Presentaba el code-smell (la traducción al español es muy chistosa) de “Variable Shadowing” o “reúso de nombres de variables en ámbitos diferentes”, lo cual generalmente solo genera confusiones entre desarrolladores al momento de la lectura del código, pero que en conjunto al punto anterior generaba el extraño bug. Ver Do not reuse variable names in subscopes.
  3. Todo lo anterior, era difícil de percibir a simple vista, dado el hecho de que la clase tenía más de 1000 líneas, siendo un buen ejemplo del anti-patrón de la “Clase dios” (God class). No obstante, el análisis estático de código mediante SonarQube había reportado el code-smell y todos en el equipo (yo incluso) lo tomamos como una sugerencia estética.

Explicación

Tras hallar el problema y solución, pudimos entender exactamente porque ocurría la situación: una mezcla entre el comportamiento del sistema y una parte del comportamiento del usuario que desconocíamos:

  1. Para garantizar la disponibilidad de la aplicación, se aplica una estrategia de escalabilidad horizontal, teniendo generalmente una instancia y tres hilos, y llegando hasta nueve hilos en las horas pico.
  2. El comportamiento habitual de los usuarios hacía que estos procesaran a un cliente de forma rápida por todo el flujo de la aplicación. Así, que cuando el flujo de la aplicación llamaba al segundo servicio de la clase dios, respondía la misma instancia que había ejecutado el primer servicio.
  3. No obstante, la dinámica del negocio hacía que algunas veces se pausara el flujo de la aplicación mientras usuario y cliente negociaban o aclaraban términos. Al reanudar el flujo, se podía dar la situación de que este fuera atenido por un hilo diferente, que tenía en la variable global los datos de otro cliente.

Intentando intervenir lo menos posible y dar solución rápida, reconociendo que en la clase exponía dos servicios al tiempo, eliminé las variables globales para la clase y modifiqué todos los métodos y objetos involucrados para que se comunicaran los valores mediante parámetros.

Teoría de las ventanas rotas

Poco antes de hallar la solución, dado que era imposible reproducir la situación en ambiente de pruebas, se me ocurrió poner unos puntos de reportes (logging) en los lugares donde sospechaba que se podía dar el cruzamiento de datos y lanzar una excepción en el caso de que se llegaran datos intercambiados al final del flujo. En ese momento nos dimos cuenta de que el caso no se presentaba 3 veces cada 1000 transacciones (0,003% de las veces), sino unas 30 cada 200 (15%), o sea, era algo muy común y simplemente los usuarios aprendieron a convivir con el problema, volviendo a pasar rápidamente al usuario por el flujo, lo que garantizaba que todo el flujo fuera atendido por el mismo hilo de ejecución.

Esta situación refleja claramente la Teoría de las Ventanas Rotas en el ámbito de los productos de Software: no arreglar a tiempo los defectos (ventanas rotas) conduce a comportamientos que no deseamos en la sociedad (usuarios) como vandalismo y apatía, siendo este último el que más afecta en el software. Nuestros usuarios no reportaban esta situación tan común, pues no creían que los tomaríamos en serio (apatía); nosotros nunca le dimos prioridad porque creíamos que la situación se presentaba muy pocas veces en el mes.

Conclusión

En retrospectiva se pueden realizar dos acciones ante esta situación, una es preventiva y la otra es correctiva:

  • Tomar muy en serio las recomendaciones del análisis estático de código, implementando una cultura de indicadores al 100 % dentro de la definición de hecho (Definition of Done) de cada característica (feature).
  • Darles máxima prioridad a los defectos apenas son reportados, sin importar su frecuencia. En caso de que no se halle la solución, se debe plantear dejar puntos de registros de errores e incluso lanzamiento de excepciones que impidan que la situación se produzca por completo.

Author: Alex Andrade

Magister Ingeniería de Software, MBA y Especialista en Gerencia de Proyectos Tel: +57-317-241-5118

Deja un comentario

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.