miércoles, 18 de febrero de 2015

TDM, la alternativa a TDD para proyectos de mantenimiento

 

Mantenimiento dirigido por pruebas. ¿Alguna vez escucharon algo parecido? ¡Suena lindo! Ahora bien, ¿de qué se trata?

Si  hemos escuchado hablar antes de las maravillas del TDD (Test-Driven  Development) y de cómo puede llegar a reducir hasta un 90% la densidad de defectos previo a la liberación del código, y todo con la magia de tomar como referente los requisitos de pruebas antes de escribir código (“primero escribir pruebas unitarias y luego programar”), sabemos que este método, que requiere de un esfuerzo importante, al ser bien aplicado genera excelentes resultados. Asegura que no existan líneas código innecesario, le evita en muchos casos al desarrollador la tarea de depurar el código, y lo más importante, genera un alto nivel de confianza ya que asegura que todo el código está cubierto por una prueba.

¡Genial! Hemos encontrado una manera de mejorar el desarrollo, las pruebas y la comunicación entre los miembros del equipo con una metodología que permite avanzar dentro del proyecto más rápidamente que las metodologías tradicionales y realizar entregas con mayor calidad en nuestros productos. Hasta aquí vamos bien. Sin embargo, es también una estadística que la mayor parte de los trabajos que hacemos vienen de código heredado. De hecho se dice que el 90% del coste de un desarrollo de software está en el mantenimiento y su evolución. “Un estudio de 1994 decía que como media, las grandes empresas y multinacionales mantenían 35 millones de líneas de código y que esta cantidad se esperaba que se doblara cada 7 años”  y es aquí donde se encuentra el punto central de este artículo: ¿Qué hacer cuando tenemos que mejorar un código heredado? Llegó el momento de hablar sobre TDM.

TDM se basa en adaptar las metodologías utilizadas en TDD para el mantenimiento de código, partiendo de la premisa de que en realidad el código heredado es el más común de los casos que vamos a tratar en nuestros entornos laborales, y que además es bastante probable que no exista  buena documentación a nivel de análisis de requisitos y diseño por lo cual, no estamos contando con la existencia de la misma. Para garantizar que esta metodología funcione en estas instancias, lo primero que debemos hacer es estudiar el código existente. El detalle aquí es que podemos hablar de proyectos con años de existencia y muchas líneas de código, que por temas de costos o porque realmente funcionan bien, nuestro cliente sencillamente no quiere cambiar, y mucho menos comenzar desde cero. Entonces, ¿Por dónde empezar?

Existen dos principios. El primero prioriza la creación de los casos de prueba para comprender cómo funciona el código. En el segundo, antes de introducir cualquier cambio en el código heredado debemos construir un set de pruebas unitarias que cubran el comportamiento del objeto que deseamos modificar, lo que nos va a permitir tener el control total sobre cualquier comportamiento no deseado del sistema.

Estos principios pueden ser muy ambiguos, ya que si el código es demasiado extenso seguimos con dudas. ¿A qué funciones debemos dedicar nuestro set de pruebas? ¿En qué parte del código? La idea principal de TDM es recurrir a las heurísticas, como por ejemplo el tamaño de la función, la frecuencia de modificación, frecuencia de corrección de defectos, entre otros.

Las mismas se pueden clasificar de la siguiente manera:

Modificación:

  • MFM - (Most frequently modified) Las funciones que fueron modificadas más veces desde el inicio del proyecto, tienden a decaer con el tiempo permitiendo la aparición de defectos.
  • MRM - (Most recently modified) Las funciones que fueron modificadas recientemente, tienen mayor tendencia a tener defectos (debido a los cambios recientes).

Corrección de Defectos:

  • MFF - (Most frecuently fixed) Las funciones que fueron corregidas más veces desde el inicio del proyecto y las funciones que fueron corregidas muchas veces en el pasado, tienen más probabilidades de tener defectos en el futuro.
  • MRF - (Most recently fixed) Las funciones que fueron corregidas recientemente, tienen más probabilidades de tener defectos en el futuro.

Tamaño:

  • LM - (Largest Modified) Las funciones modificadas más grandes, en términos de número total de líneas de código (incluyendo comentarios y líneas en blanco) y las funciones más grandes tienden a tener más defectos que las funciones más pequeñas.
  • LF - (Largest fixed) Las funciones corregidas más grandes, en términos de número total de líneas de código (incluyendo comentarios y líneas en blanco) y las funciones más grandes que necesitaron ser corregidas tienden a tener más defectos que las funciones más pequeñas que han tenido menos correcciones.

Riesgos:

  • SR - (Size Risk) Funciones de mayor riesgo, definidas por el número de cambios de corrección de defectos dividido por el tamaño de la función en líneas de código. Como las funciones más grandes pueden naturalmente necesitar más correcciones que las funciones más pequeñas, se normaliza el número de corrección de defectos por el tamaño de la función. Esta heurística señalará las pequeñas funciones que se corrigen mucho (es decir, las que tienen alta densidad de defectos).
  • CR - (Change Risk) Las funciones más riesgosas definidas por el número de corrección de defectos divididas por el total de número de cambios. Por ejemplo, una función que cambia 10 veces y 9 de ellas era para corregir defectos, debería tener una prioridad mayor de ser testeada que una función que fue cambiada 10 veces, pero sólo una fue para corregir defectos.


    Aleatorio:
  • Random - Seleccionar funciones de manera aleatoria para escribir casos de prueba puede considerarse como el escenario de línea base, por lo tanto usamos el rendimiento de la heurística aleatoria para comparar el rendimiento de las otras heurísticas.

Entre las cosas que encontré mientras escribía este artículo, hay una afirmación en la que se expresa que de todos los métodos anteriormente descriptos, los que mejores resultados generan son LM (Largest Modified) y LF (Largest Fixed) “Aún combinando diferentes criterios para intentar mejorar el rendimiento, LM y LF se mostraron imbatibles”.

Beneficios:

Pensado para obtener resultados similares a los que se generan utilizando TDD, nos pide que nos centremos primero en el resultado final y luego en la solución para lograrlo, aunque es un poco más complejo en cuanto a la consideración de la existencia previa del código, el diseño de la solución parece más simple y pragmático, el uso de TDM reduce la cantidad de errores mejorando la calidad del producto. El resultado final es un sistema más sólido ya que garantiza que por el despliegue de las pruebas, el momento y la forma en que se realizan los cambios funcionen, en comparación con los métodos tradicionales en los que el código sólo se prueba mucho tiempo después de su ejecución, obligando al desarrollador a pensar en pequeños trozos de código que pueden ser probados de manera aislada e independiente. 

Obstáculos y Dificultades: 

Uno de los principales obstáculos es la reticencia inicial del equipo para adoptar ésta técnica. Principalmente para los desarrolladores que deben escribir más código de lo usual y pueden pensar que el pase a pruebas es una pérdida de tiempo. La curva de aprendizaje es alta, y para los lenguajes de programación que no contemplan el marco de pruebas unitarias puede ser una tarea difícil. Al no existir una documentación adecuada, el desarrollador desconoce la funcionalidad total del sistema, por lo que siente, naturalmente, temor de que al modificar una porción del código pueda generar defectos en otro lugar. Sin embargo, el hecho de que es necesario desplegar más código que en los enfoques tradicionales no es suficiente para superar los beneficios de obtener un producto final con menos errores y los períodos de mantenimiento más cortos.                            

TDM ciertamente no es la solución a todos los problemas, pero ayuda a los desarrolladores a mejorar la programación, el modo de confianza, modular, flexible y fácil de mantener soluciones que satisfagan las necesidades del cliente en la entrega.

Aparentemente es una metodología que nos puede ahorrar muchos dolores de cabeza. Ojalá pronto podamos tener la experiencia de primera mano y podamos generar nuestras propias conclusiones. Adicionalmente, en mi experiencia personal, luego de haber tenido una pequeña iniciación en UX (User Experience) me pregunto si un mix de estas metodologías sería aplicable.  

¿Ustedes que opinan?
Bibliografía: 

H. Muller, K. Wong, and S. Tilley, “Understanding software systems using reverse engineering technology”.
¡Gracias Ninnette Carolina Fernández por tu contribución!