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

Qualilogy Legacy Application Refactoring ReengineeringHemos visto en el post anterior cómo usar el dashboard SonarQube para estimar el esfuerzo de pruebas de caracterización, pruebas recomendadas por Michael Feathers en su libro «Working Effectively with Legacy Code».

Hemos clasificado los componentes de nuestra aplicación Legacy (Microsoft Word 1.1a) en diferentes grupos de funciones: las más simples con Complejidad Ciclomática (CC) de menos de 20 puntos, las complejas y muy complejas hasta 200 puntos de CC, y finalmente 6 componentes ‘monstruosos’.

Hemos definido una fórmula basada en la Complejidad Ciclomática y un factor de legibilidad (Reading Factor%) para evaluar el esfuerzo de pruebas en cada uno de estos grupos.

Evaluación de los resultados

El resultado total es de 234 días, que se reparten de la siguiente manera:

Tabla 1 – Distribución del esfuerzo de pruebas por categoría de componentes
Qualilogy-Legacy-TestEffort-Synthesis

  • 93.5 días para cubrir el 60% de las funciones con una CC inferior a 20 puntos, un 40% del esfuerzo total y el 44% del total de la Complejidad Ciclomática de la aplicación.
  • 92 días (39% del tiempo total) para las 503 funciones entre 20 y 100 puntos de CC, lo que representa 44% del total de CC.
  • 25 días (11% del tiempo total) para las 30 funciones entre 100 y 200 puntos de CC, lo que representa el 9% del total de CC.
  • 23.5 días o 10% del esfuerzo total para las 6 funciones más complejas, lo que representa el 3% de la CC de toda la aplicación.

Este esfuerzo nos permite alcanzar la cobertura del 65% de las funciones, es decir, las dos terceras partes de nuestra aplicación.

Pasé bastante tiempo en la fórmula para evaluar el esfuerzo de pruebas, y conseguir una valoración que me parezca lógica, coherente y comprensible, basandonos en el número de caminos lógicos en una función, representado por la Complejidad Ciclomática, y la mayor o menor facilidad de lectura. Por lo tanto, es así que procede un desarrollador para estimar el número de días que se puede necesitar para desarrollar un código de pruebas unitarias en una aplicación desconocida.

También quería que la regla sea precisa (pero simple), para que se puedan ajustar los parametros con el tamaño y la complejidad de los componentes. He jugado un poco con los factores ‘Readibility Factor%’ et ‘Characterization Test time’ con el fin de lograr un resultado para cada categoría de componentes que me parezca correcto: 54 minutos de programación de pruebas para una función entre 12 y 20 puntos de CC, 120 minutos para una función de entre 40 y 50 puntos de CC y 200 a 300 líneas de código, 240 minutos (medio día) para una función de entre 60 y 70 puntos DC y entre 500 y 700 líneas de código, etc.

Una vez realizadas las diferentes tablas de evaluación del esfuerzo con esta fórmula, mi primera reacción fue de encontrar los resultados finales bastante realistas. 234 días para una cobertura de pruebas de 2/3 de las 3 936 funciones distribuidas en 349 programas C: no parece inverosímil.
Incluso creo que no está subvaluado, teniendo en cuenta que los tiempos necesarios para completar las pruebas de los componentes más simples (<20 CC) a moderadamente complejos (20 <CC <50) son, pienso yo, estimados generosamente. Para los componentes más pesados, es difícil hacerme una idea clara de la precisión de esta evaluación. Pero tres días para una función compleja con 200 puntos y 750 líneas de CC de código no parecen inconsistentes.

La distribución del esfuerzo de la prueba también parece interesante:

  • Dos cifras muy similares acerca de 90 días para cada una de las dos primeras categorías, con cerca de 19 000 puntos de CC para cada categoría, en 3 400 componentes sencillos y también para 500 componentes complejos.
  • Dos cifras de nuevo de 25 días para 30 componentes muy complejos y 23 días para 6 componentes ‘monstruosos’.

Cuando digo que estos resultados parecen realistas, no quiero decir que sean completamente precisos y fiables, pero que pueden resistir a algunas objeciones.

Objeciones

Terminé el post anterior preguntando si esta estimación parecía correcta y la respuesta fue bastante positiva, que era ‘OK’, y la ‘escalabilidad’, es decir, el cambio de escala entre las diferentes categorías en función de la creciente CC, parecía bastante correcta.
Sin embargo, como consultor, tengo que prepararme para objeciones durante la presentación de estos resultados a un cliente. ¿Qué podrían preguntarme?

Un gerente o responsable funcional de la aplicación, respresentando a los usuarios (stakeholder) :     « Yo no entiendo: usted dice que tenemos 4 minutos por cada punto de la complejidad de los componentes más simples y 2 minutos por cada punto de la complejidad de los componentes más complejos. ¿No debería ser lo contrario? ».

Bueno, la escritura de un test requerirá el desarrollo de una función específica para esta prueba, con el fin de verificar uno o más dato(s) y valor(es) para cada camino lógico, y entonces por cada punto de complejidad. Si sólo tengo un único punto de CC, voy a dedicar menos tiempo a escribir una línea de prueba de estos datos y proporcionalmente más tiempo a desarrollar la función para compilar, ejecutar, verificar el resultado de la prueba y a veces modificar la función y probar de nuevo. Si tengo múltiples puntos de CC, voy a pasar un poco más tiempo para probar diferentes valores y proporcionalmente menos tiempo en la programación y ejecución de la función.

Por lo tanto, no vemos realmente diferencia cuando pasamos de una simple función de entre 12 y 20 puntos de CC con 54 minutos de realización de pruebas, a una función moderadamente compleja entre 20 y 30 puntos de CC y de 50 a 60 minutos de pruebas (según el tamaño de la función, en líneas de código).

Un miembro del equipo del proyecto: « Tu formula me va bien, pero creo que el factor de legibilidad (RF%) no es bastante progresivo. Pasamos de repente de 4 a 10 para las funciones muy complejas ».

De acuerdo.

Para tener una segunda hipótesis, es decir un rango alto para nuestra evaluación, cambiamos este factor de la siguiente manera:

  • Por menos de 100 líneas de código (LOC), dejo el RF% = 1.
  • Entre 100 y 200 LOC, aumento el RF% de 1.5 a 2.
  • Entre 200 y 300 LOC, el RF% sube de 2 a 3.
  • Entre 300 y 500 LOC, el RF% sube de 2.5 a 4.
  • Entre 500 y 700 LOC, el RF%  sube de 4 a 6.
  • Más allá de 700 LOC, quedamos con los valores actuales.

La nueva tabla con los resultados por estos valores :

Tabla 2 – Cálculo del esfuerzo de pruebas de las funciones muy complejas (hipótesis alta)

Qualilogy-Legacy-TestEffort-CCSup20_NewRFPasamos de 117.2 días a 131.8 días, un aumento de 14.6 días, pues un esfuerzo adicional de alrededor del 12% de la carga inicial. Así que el impacto de este cambio es relativamente moderado. Por otra parte, podemos ver que es más importante en los componentes de mayor tamaño (tiene sentido), que no son los más numerosos. Por ejemplo, subimos de 4 a 5 horas de esfuerzo para un componente de 60 a 70 puntos de CC, con más de 500 LOC, pero como tenemos un único en toda la aplicación …

El CIO : « ¿Cómo de fiable son estos resultados? ¿Hasta qué punto puedo confiar en estos números para decidir si externalizar esta aplicación? ».

Permítanme aclarar esto mediante la presentación del plan de acción.

Plan de acción

Tenemos un total de 234 días para esta operación de transferencia de conocimiento con desarrollo de pruebas de caracterización, o 248.8 días si modificamos el parámetro RF% como visto anteriormente, lo que representa entre 11.7 y 12.5 persona/meses sobre la base de 20 días por mes. Vamos a basar nuestro plan de acción sobre estas dos hipótesis, la primera ‘hipótesis media’, la segunda ‘hipótesis alta’.

De hecho, no podemos contar con 20 días al mes, porque supone que no falta nadie, o no tener vacaciones. Consideramos (en Francia) que una persona dispone de cinco semanas de vacaciones y dos semanas de días feriados. Puede variar de un año al otro, pero digamos que contando con días de baja, es una cifra usual. Esto nos da un total de:

52 semanas al año – 7 semanas de ausencia = 45 semanas trabajadas.
45 semanas x 5 días a la semana = 225 días al año.
225 días / 12 meses = 18,5 días de trabajo al mes.

Nuestra carga total de proyecto es de 234 días (hipótesis 1) o 248.8 días (hipótesis 2), que representan respectivamente 2.5 o 2.65 hombre/meses para un equipo de 5 personas y 18.5 días por mes.

5 personas: creo que es un equipo de tamaño correcto para encargarse de esta aplicación y garantizar su futuro mantenimiento. Dudo que el equipo original en Microsoft haya sido menos numeroso. Sobre la base de este equipo, nuestro proyecto de transferencia de conocimiento y desarrollo de pruebas sería inferior a 3 meses.

Sin embargo, tres meses es el tiempo sistemáticamente utilizado para la fase ‘transferencia de conocimiento’ en cualquier respuesta a un RFP. Menos de 3 meses, no es creíble: requeriría un mayor número de personas que el número de personas necesarias en las fases posteriores de desarrollo. Esto significaría no completar la fase de transferencia correctamente y por lo tanto poner en peligro la prestación de externalización.
Más allá de los 3 meses, se toma el riesgo de aumentar la carga y por lo tanto el coste del proyecto más allá de lo que ofrecen los competidores, y por lo tanto se pierde el contrato. Así que todo el mundo va a proponer un periodo de 3 meses, duración bastante aceptable para un CIO.

Entonces, vamos a basar nuestro plan de acción en una planificación de tres meses, con una carga de 2.5 (o 2.65 hombre/meses) para un equipo de 5 personas. Esto nos da un margen de 20% (3 – 2.5 = 0.5 = 20% de 2,5) o 13% (por un cargo de 2.65 hombre/meses).

La siguiente tabla resume estos resultados:

Qualilogy_Legacy_ActionPlanTable
Por lo general se evalua la carga de gestión del proyecto en un 20% del total del proyecto, pero creo que será más bajo en una transferencia de fase de conocimiento. En cualquier caso, tenemos un poco de margen si empezamos con una planificación de tres meses.

Así que me gustaría hacer las siguientes propuestas:

Empezar con las funciones más complejas

30 funciones muy complejas requieren 25.1 días en nuestra hipótesis 1 o 26.9 en nuestra hipótesis 2, y 6 funciones ‘monstruosas’ requieren 23.5 j. En total, voy a redondear hasta 50 días de carga, 10 días/hombre por un equipo de 5 personas.

Mi consejo es utilizar las dos primeras semanas de nuestro calendario de 3 meses para escribir las pruebas y documentar las funciones más complejas, con reuniones frecuentes (al menos 2 por semana) para ver si esta primera parte del proyecto puede cumplirse en las 2 semanas planificadas. Si este es el caso, podemos tener confianza para la realización del resto de nuestras pruebas.

Compartir tareas entre los dos equipos

Otra recomendación que me gustaría sugerir para esta primera fase: dividir el trabajo de desarrollo de pruebas por estos componentes más complejos entre los dos equipos, el primero que ya conoce el código actual, y el segundo que se llevará a cabo la transferencia de conocimiento. Podemos trabajar de dos maneras:

  • Desarrollo conjunto: un desarrollador del nuevo equipo programa pruebas con la ayuda de un desarrollador del equipo actual.
  • Desarrollo separado con transferencia de conocimiento: cada desarrollador programa sus pruebas de manera independiente sobre las funciones más complejas (no las mismas al mismo tiempo, obviamente). Ambos se encuentran en el final de la tarde para presentar su trabajo, hacer comentarios al respecto, explicar y facilitar la transferencia de conocimiento para el desarrollador del nuevo equipo.

La primera opción es, probablemente, la que va a optimizar la calidad de nuestra cobertura de pruebas, ya que tenemos los beneficios del descubrimiento y de la comprensión del código por un desarrollador del nuevo equipo, con la ayuda de un desarrollador que ya conoce este código. La desventaja es que requiere recursos adicionales del equipo actual, no directamente productivos como en la segunda opción.

Debo decir que tengo una ligera preferencia por la segunda opción, que me parece más beneficiosa. El desarrollador del nuevo equipo realiza pruebas de caracterización que le permitan descubrir y entender el comportamiento de la aplicación. En paralelo, un desarrollador del actual equipo escribe pruebas unitarias en un código conocido, antes de pasarlo al desarrollador del nuevo equipo. Es importante, sin embargo, reservar tiempo para que los dos comparten su trabajo y que el desarrollador del equipo actual, o verifica y completa el trabajo del desarrollador del nuevo equipo, o enseña y comenta las pruebas que él mismo desarrolló. Este tiempo compartido también debe permitir documentar las pruebas. Creo que una hora al día trabajando juntos al principio, debería permitir la transferencia de conocimiento. Sin embargo, probablemente pasará a 2 horas por día rápidamente.

En ambos casos, esta división del trabajo debería permitir:

  • Garantizar la transferencia de conocimiento y realizar pruebas de calidad en las funciones más complejas.
  • Un mejor seguimiento de esta fase del proyecto, probablemente la más difícil.
  • Asegurar el calendario y evitar cualquier contratiempo en el inicio del proyecto, beneficiandose de los conocimientos del código del equipo actual.

Modular los recursos más eficientemente

En ambos casos, necesitaremos recursos del equipo actual, pero de nuevo, durante un período relativamente corto de aproximadamente dos semanas. Sin embargo, es posible modular, más fácilmente en la segunda opción, con menos desarrolladores del equipo actual. Incluso podemos mezclar las dos opciones: por ejemplo, desarrollo conjunto de las 6 funciones ‘monstruosas’ y cada uno separado para desarrollar las otras 33 funciones de alta complejidad.

Creo que este tipo de recomendación se puede ofrecer en comité de proyecto, y ver lo que piensan los diferentes participantes. Entre desarrollo compartido o separado, con más o menos recursos de cada equipo. De hecho, se puede imaginar todas las variantes posibles.
Por ejemplo: 2 desarrolladores del equipo actual y tres desarrolladores del nuevo equipo, para un total de 5 personas, comparten el trabajo de escritura de pruebas sobre estos componentes más complejos. Durante el mismo tiempo, otros dos desarrolladores del nuevo equipo comienzan a trabajar sobre los componentes menos complejos. Con el fin de:

  • Asegúrarse de evitar retraso en la caracterización de los componentes más complejos, con dos personas que tienen el conocimiento …
  • … mientras se adquiere una primera visión de la tarea de escritura de pruebas en los componentes de entre 20 y 200 puntos de CC, para una mejor visibilidad de toda la aplicación y por lo tanto del proyecto.

En función de las distintas opciones, es evidente que hay que calcular la carga adicional en el equipo actual y los impactos en el calendario.

Modificar la cobertura de pruebas

Creo que empezar con los componentes más complejos, con cobertura de estos componentes de 100% de su Complejidad Ciclomática debe asegurar la transferencia de conocimiento sobre la parte más difícil, la más pesada y compleja de la aplicación, y por lo tanto el proyecto. Luego podemos ir a las 503 funciones de – relativamente – menor complejidad.

¿Qué hacer si nos estamos quedando atrás, encontrando retrasos? Si quieremos cumplir el plazo de tres meses que hemos decidido, vamos a tener que reducir la carga de trabajo y por lo tanto la cobertura de pruebas.

Se evaluó al 65% de la Complejidad Ciclomática de los componentes de la aplicación, 100% de los de más de 20 puntos de CC y 60% y para los demás. Decíamos en nuestro post anterior que el esfuerzo de pruebas corresponde (más o menos) a una distribución de Pareto, y que era casi lo mismo el tiempo para desarrollar el 70% o el 80% de las pruebas para un componente que el tiempo necesario para el 20% o 30% restante. Esto es relativo, pero una disminución menor en nuestra cobertura de pruebas debe permitir reducir la carga de trabajo de manera proporcionalmente mayor.

Por ejemplo, si en vez de 100% de cobertura de los componentes de entre 20 y 200 puntos de CC, tenemos la intención de reducir la cobertura hasta 80%, nuestra carga de trabajo cambia de la siguiente manera:

  • 93.7 días frente a 117.2 días en nuestra hipótesis 1, o 23.5 días, una disminucion del 20% de la carga inicial.
  • 105.4 días frente a 131.8 días en nuestra hipótesis 2, o 26.4 días para de nuevo una ganancia de 20%.

Podemos ver que cualquier retraso se puede reducir al ganar días con una reducción mínima de cobertura de pruebas, sin sacrificar demasiado la calidad de la transferencia de conocimiento.

Síntesis

Una de las tareas asignadas consiste en calcular el coste de la transferencia de conocimiento de la aplicación a un otro equipo y proponer una estrategia para un proyecto de este tipo.

Nos basamos en el concepto de caracterización de pruebas de Michael Feathers, con la doble ventaja de tener una cobertura de pruebas para nuestra aplicación Legacy y asegurar la transferencia de conocimiento.

Construimos una fórmula basada en la Complejidad Ciclomática y un factor de legibilidad, tratando de encontrar el equilibrio adecuado entre simplicidad y precisión. Examinamos las posibles objeciones a los resultados obtenidos con esta fórmula.

Por último, hemos construido un plan de acción, con diferentes propuestas que pueden convencer (o tranquilizar) a un CIO de la fiabilidad de nuestro análisis y la viabilidad de nuestro proyecto.

En nuestro próximo post, vamos a trabajar en nuestra segunda misión: la refactorización de nuestra aplicación Legacy, con la ayuda del plugin Sqale de SonarQube.

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

Deja una respuesta

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