Aplicación Legacy – ¿Refactorización o reingeniería? (VI)

Qualilogy -Application Legacy - Effort de testHemos presentado en los dos articulos anteriores la noción de prueba (unitaria) de caracterización propuesta por Michael Feathers en su libro « Working Effectively with Legacy Code ».

Hemos visto brevemente cómo podemos utilizar este tipo de pruebas con el fin de adquirir los conocimientos del comportamiento de las aplicaciones. Digo brevemente porque, idealmente, tendríamos que desarrollar y presentar algunas pruebas como ejemplo, pero esta serie es ya muy larga. Mejor leer el libro de Michael Feathers.

Estas pruebas facilitarán la transferencia de conocimientos de nuestra aplicación Legacy, y cualquier operación posterior de refactorización o de reingeniería será más rápida y más segura.

Cobertura de pruebas

¿Que debería ser el alcance de esta operación de caracterización? ¿Cuándo podemos considerar que nuestra cobertura de pruebas es suficiente, y empezar a hacer cambios en el código? ¿Es posible cuantificar el esfuerzo que representa?

Michael Feathers recomienda escribir tantas pruebas que consideramos necesario para cada bloque de código que tenemos que cambiar en el futuro. ¿Sin embargo, que sucede si no se proporciona ningún cambio de código en el futuro?

No es un caso raro: cuando una empresa compra un editor de software, es posible que no desee cambiar el producto, sino simplemente ofrecer apoyo hasta que desaparezca de muerte natural, cuando ningún más cliente paga el mantenimiento.
Otro caso: conozco departamentos de TI que han perdido casi por completo el conocimiento de grupos enteros de aplicaciones Cobol, PL1, Natural/Adabase, Oracle Forms, etc. Estas aplicaciones son:

  • A menudo criticas, debido a que son en el corazón histórico del sistema de información.
  • Completamente probadas, sin muchos defectos.
  • Evoluan poco, y más bien en su interfaz para conectar con nuevas aplicaciones, que en su lógica de negocio.

Una estrategia posible para estos CIOs es la de subcontratar el código, pero cuidando bien la fase de transferencia de conocimientos, con el fin de evitar ‘romper’ lo que ya funciona.

La misión que se nos ha asignado es calcular el coste de la transferencia del conocimiento de nuestra aplicación Legacy a otro equipo. ¿Cómo podemos estimar el esfuerzo de descubrir el código a través de estas pruebas de caracterización? ¿Existe una fórmula que evalúa este esfuerzo con un plan de recursos y un calendario?

Complejidad y legibilidad

Siempre he considerado en las diversas auditorías que realizo, que la Complejidad Ciclomática es representativa del esfuerzo de test.

Una aplicación reciente, pequeña, no crítica, dentro de la empresa y sin usuarios externos (intranet por ejemplo), con cerca de 6 000 puntos de CC, puede validarse con pruebas de integración por el equipo de proyecto, sin pasar por una fase formalizada de control de calidad.

Una aplicación más antigua, abierta al exterior – por ejemplo, un front-end de otras aplicaciones y software de pedidos, facturación, inventario, etc. – pues crítica para la empresa, y con más de 60 000 puntos de CC: fase de control de calidad requerida por un equipo de probadores especializados, con juegos de prueba formalizados, y si es posible automatizada.

Word_ComplexityHemos visto que nuestra aplicación tenía 43 846 puntos de CC, distribuidos en 3 936 funciones y 349 archivos.

La distribución de la Complejidad Ciclomática entre estas funciones es la siguiente:

Tabla 1 – Complejidad ciclomática de las funciones dentro de Word 1.1aWord_CCDistribFns

No voy a considerar una cobertura de pruebas del 100% de la Complejidad Ciclomática, porque como probablemente sabes, más allá de un cierto límite, el tiempo para escribir nuevas pruebas se hace más largo. El esfuerzo de pruebas corresponde (aproximadamente) a una ley de Pareto según la cual es posible escribir 80% de las pruebas en el 20% del tiempo.

De hecho, creo que 80/20 es un poco optimista y estoy pensando que el 60% de las pruebas se puede realizar en el 50% del tiempo, y un 40% adicional requerirá otro 50%. Nuestro principal objetivo es llevar a cabo en primer lugar una transferencia de conocimientos a un nuevo equipo, no lograr una cobertura de 100%.

Sin embargo, las funciones más complejas requieren una mayor atención, ya que tienen un mayor riesgo de introducir un defecto en caso de cambio. Estas funciones también son posibles candidatas para refactorización, por lo que es deseable una mejor ‘caracterización’, sobre todo si son difíciles de leer, con un gran número de líneas de código o de defectos que afecten a la mantenibilidad. Por ello, incrementaremos nuestra exigencia para estas pruebas.

Por lo tanto, voy a considerar las siguientes hipótesis:

  • Para las funciones con una CC menor o igual a 20 puntos, la cobertura de pruebas será el 60% de la Complejidad Ciclomática.
  • Para las funciones con más de 20 puntos de CC, queremos una cobertura del 100% de la Complejidad Ciclomática.

SonarQube tiene una regla ‘Avoid too complex function’, que enumera las funciones más allá de 20 puntos de CC, con su número exacto. Esto nos permitió calcular la siguiente distribución:

Tabla 2 – Distribución de las funciones más complejas Word_CCDistribLargestFns

Otra regla ‘Function/method should not have too many lines’ lista de nuevo las funciones con más de 100 líneas de código, y el número exacto de ellos.
Así que puedo cruzar estas dos listas para identificar las funciones con más de 20 puntos de CC y más de 100 líneas de código:

Tabla 3 – Distribución de las funciones más complejas por tamaño (LOC)Qualilogy-Legacy-CC-LOC

No he incluido en la tabla anterior las 6 funciones más complejas, más allá de 200 puntos de CC, que también se encuentran en programas complejos o con un gran número de líneas:

Tabla 4 – 6 programas con las funciones más complejas

Qualilogy-Legacy-TestEffort-6max

Estimación del esfuerzo de pruebas

Así llegamos a una clasificación de los diferentes componentes en tres categorías, desde las funciones más simples con pocas líneas hasta las funciones complejas de tamaño grande a muy grande.

Para calcular una medida del esfuerzo de pruebas, me basaré en la siguiente fórmula:

Test Effort = Code Reading Time + Characterization Test

con: Code Reading time = CC/2 (mn) x Readibility Factor%

y: Characterization Test = CC x N (mn)

Utilizo tres variables en esta fórmula:

  • Code Reading (CR) time es el tiempo necesario para ‘leer’ una función y deducir las pruebas de caracterización correspondientes.
  • Readibility Factor% (RF%) es un factor de la legibilidad del código.
  • Characterization Test (CT) es el tiempo de escritura y ejecución de estas pruebas, con el número N de minutos dependiendo de la Complejidad Ciclomática, y que adaptaré según el tipo de componente.

Recordemos que una prueba de caracterización se utiliza para describir el comportamiento de un bloque de código y por lo tanto, a diferencia de una prueba unitaria o una prueba de regresión, no tiene como objetivo verificar que el código se comporta correctamente y hace lo que debe hacer, sino como se comporta la aplicación.

No se requiere entonces una comprensión completa de la función y de cada una de sus variables, constantes, parámetros y valores de entrada / salida. ¿Por eso digo ‘leer’ la función, es decir, leer de manera suficientemente rápida para poder empezar a escribir pruebas de caracterización.

Sin embargo, una función que será algo compleja y difícil de leer, con ‘goto’, ‘switch’, etc. será menos fácil de entender. Voy a utilizar un factor de facilidad de lectura – Code Reading (CR) – para modular el esfuerzo para descifrar la función.

También voy a ajustar el tiempo de realización de las pruebas de caracterización, ya que será diferente en función del número de puntos de CC. Vimos en el último post que la función más compleja de nuestra aplicación incluye ‘switch’ con condiciones en diferentes variables rápidamente comprensibles y fáciles de probar. En tal caso, no necesitáremos mucho más tiempo para probar un único ‘switch’ con 8 o 10 puntos de CC que un ‘if … else’ con 2 o 3 puntos de CC.

Una vez más, voy a considerar las siguientes hipótesis:

  • Para las funciones con una CC igual o inferior a 20 puntos, el tiempo de escritura de las pruebas será de 4 minutos por cada punto de CC.
  • Para las funciones con más de 20 puntos de CC, el tiempo de escritura de las pruebas será de 2 minutos por punto de CC.

Con esta formula, una función igual a una Complejidad Ciclomática de:

  • 1 punto, requiere medio minuto de tiempo de comprensión y 4 minutos de realización de la(s) prueba(s), para un total de 4 minutos y 30 segundos.
  • 2 puntos, requiere 1 minuto de tiempo de lectura y 8 minutos de finalización de las pruebas, para un total de 9 minutos.
  • 8 puntos, requiere 4 minutos de tiempo de lectura y 32 minutos de realización de pruebas, para un total de 36 minutos.
  • 12 puntos, requiere 6 minutos de lectura y 48 minutos para realizar las pruebas, con un total de 54 minutos.

De hecho, no tengo la CC exacta para funciones con menos de 20 puntos, así que voy a suponer que el tiempo de realización de las pruebas será de 9 minutos para las funciones de 2-4 puntos de CC, 36 minutos para funciones de 8-10 puntos de CC, 54 minutos para las funciones de 12 hasta 20 puntos de CC, etc.

Esto supone un factor de legibilidad del código (RF%) = 1. Voy a modificar el valor de este párametro cuando las funciones serán más complejas (más allá de 20 puntos) o menos legibles.

Estas cifras me parecen bastante realistas, o al menos, no parecen subestimadas. Puedo perfectamente presentar esta hipótesis de cálculo a un equipo de proyecto o directivos: ellos pueden entender que es una aproximación, una base aceptable para proseguir con nuestra estimación.

Vamos a ver lo que nos da todo esto, en primer lugar con las funciones dentro de los 20 puntos de Complejidad Ciclomática:

Tabla 5 – Cálculo del esfuerzo de pruebas en las funciones más simples (<20 CC)

Calcul de l'effort de test sur les fonctions les moins complexes

Contamos 3 397 funciones con menos de 20 puntos de CC, para las que tenemos el propósito de cubrir con pruebas el 60% de la Complejidad Ciclomática, y por lo tanto, equivalente a 2 039 funciones. Pues, podemos ver por ejemplo:

  • 413 de 689 funciones con 1 punto de CC, y un coste unitario de prueba de 4.5 minutos, representan hasta 31 horas de realización de pruebas, o cerca de 4 días (8 horas al día).
  • 522 de 870 funciones con 2-4 puntos de CC, y un coste unitario de prueba de 9 minutos representan 78.3 horas o cerca de 10 días, para un total acumulado (con los anteriores 4 días) de casi 14 días.
  • 295 de 491 funciones con 12 a 20 puntos de CC, y un coste por cada prueba de 54 minutos representan aproximadamente 33 días de trabajo, un tercio de los 93,5 días requeridos en total.

Guardamos este número en mente por ahora y pasamos a las funciones más complejas. Hemos dicho que para ellas:

  • Queremos una cobertura de pruebas equivalente al 100% de la Complejidad Ciclomática.
  • Calculamos el tiempo de realización de una prueba de caracterización con el número de puntos de CC x 2 minutos.

También voy a ajustar el factor de legibilidad (RF%) de la siguiente manera:

  • Por menos de 100 líneas de código (LOC), RF% = 1.
  • De 100 hasta 200 LOC, RF% = 1.5.
  • De 200 hasta 300 LOC, RF% = 2.
  • De 300 hasta 500 LOC, RF% = 2.5.
  • De 500 hasta 700 LOC, RF% = 4.
  • Más allá de 700 LOC (pero menos de 200 CC), RF% = 10. Son sólo cuatro funciones, sin incluir las 6 funciones más complejas, que veremos por separado.

Aquí está la tabla correspondiente:

Tabla 6 – Cálculo del esfuerzo de pruebas de las funciones complejas (20 <CC <200))

Calcul de l'effort de test pour les fonctions complexes à très complexes

A modo de explicación y para facilitar la comprensión de esta tabla:

  • 137 funciones, con menos de 100 LOC y una CC entre 20 y 30 puntos, cada una representando una carga de prueba de 40 minutos (RF% = 1) y una cobertura de pruebas del 100% de la CC, requieren 14,3 días de trabajo.
  • 95 funciones, con una CC entre 20 y 30 puntos y un tamaño de entre 100 y 200 LOC, pues con un factor de legibilidad (RF%) igual a 1.5, y entonces una carga de pruebas de 55 minutos, requieren 10.9 días de trabajo. Con la carga anterior, la suma es igual a 14.3 + 10.9 = 25.2 días.
  • 1 función con una CC entre 60 y 70 puntos y un tamaño de entre 500 y 700 LOC, con un RF% de 4, tendrá un tiempo de lectura de 120 minutos (60/2 x 4) y un tiempo de 120 minutos para programar las pruebas, con un total de 4 horas o medio día.
  • 4 funciones con una CC entre 100 y 200 puntos y más de 700 LOC, con un RF% de 10, tendrán un tiempo estimado de 700 minutos por función, por un total de casi 6 días de trabajo para ‘caracterizar’ estas 4 funciones.

Hice un cálculo específico para cada una de las 6 funciones pesadas y complejas:

TTabla 7 – Cálculo del esfuerzo de pruebas para las funciones más complejas

Qualilogy-Legacy-TestEffort-CC6max

Excepto la función en el programa ‘command2.c’ con menos de 400 líneas, y por lo tanto un factor RF% de 2.5, he asignado un RF% de 10 para las otras funciones y un RF% de 20 por la función más importante en el programa ‘RTFOUT.c’ (que ya hemos comentado en el post anterior).

Síntesis

Con las hipótesis que hemos elegido, llegamos a un total de 234 días para la realización de las pruebas de caracterización para nuestra aplicación Legacy en C, con el objetivo de transferir el conocimiento a un nuevo equipo, o durante un outsourcing.

Estos 234 días, un poco menos de 12 meses / hombre (sobre la base de 20 días por mes) se distribuyen de la siguiente manera:

  • 93.5 días para la cobertura de pruebas de 60% de la Complejidad Ciclomática total de 3 397 funcions con menos de 20 puntos de CC.
  • 117 días para una cobertura total de las 533  funciones entre 20 y 200 puntos de CC.
  • 23.5 días para caracterizar las 6 funciones más complejas y más grandes.

¿Qué tal estos números? ¿Son nuestras suposiciones correctas o cuestionables? Si presentamos nuestros resultados a los equipos de proyecto (actual o nuevo) y a los directivos, ¿qué problemas pueden surgir y cómo responder? ¿Qué plan de acción podemos presentar?

Voy a dejar que piensas acerca de esto, a la espera de reflexionar sobre estos puntos en nuestro próximo post.

Esta entrada está también disponible en Lire cet article en français y Read that post in english.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *