Buenas prácticas para trabajar con código legacy

Federico Ocampo
Bancolombia Tech
Published in
7 min readMay 23, 2022

Si las aplicaciones legacy no fueran importantes, nadie se tomaría el trabajo de mantenerlas y desarrollar sobre ellas.

El código legacy, o código legado o heredado, no es otra cosa que código antiguo que aún sigue siendo ejecutado. También pueden encontrarse otras definiciones como: código de otro desarrollador o código sin soporte. Michael C. Feathers, autor de Working Effectively with Legacy Code, va un poco más allá diciendo que es simplemente código sin pruebas.

“Código que los desarrolladores temen” es otra de las definiciones encontradas; y no es tan exagerada, pues el código legacy puede ser impactante y traumático cuando tenemos que enfrentarlo. Sin embargo, sigue siendo más la regla que la excepción, y podemos encontrarlo hasta en las más grandes organizaciones.

Recuerdo cuando tuve que hacer mi primera modificación en una aplicación legacy. Tenía que agregar una nueva funcionalidad a un método de aproximadamente 150 o 200 líneas. Empecé entendiendo el método, detectando las dependencias e impactos del cambio. Sin embargo, en una revisión con uno de mis compañeros, viéndome un poco apurado, me aconsejó: “Si quieres terminar a tiempo, solo trabaja de la línea XX a YY… el resto no lo toques, ni siquiera lo veas”. Me sentí desarrollando a ciegas, sin saber lo que iba a pasar. La salida a producción fue de esas en las que sientes que todo va a salir mal y te preparas para lo peor.

Por fortuna todo salió bien, pero no siempre estas experiencias son así y a veces se vuelven pesadillas que solo queremos que terminen. En este artículo comparto algunas buenas prácticas que he aprendido con los años y me han servido al momento de implementar y soportar aplicaciones legacy.

1. Siempre versionar

Esto es el primer mandamiento del desarrollador; versionar es tener control sobre los cambios realizados en el código. Teniendo este control podemos hacer cambios sin miedo a perder la versión original. Cuando utilizamos versionamiento podemos empezar de cero en cualquier momento, cambiar entre diferentes versiones de una implementación o devolvernos en el tiempo para ver el código fuente en un momento especifico de la historia.

No hay problema si el proyecto no está siendo versionado, podemos empezar a utilizar versionamiento de manera simple. Teniendo la herramienta de Git instalada, podemos ejecutar el siguiente comando en la raíz del proyecto:

git init

Esto inicializará un repositorio git cuya base será tu proyecto. Lo siguiente sería generar una primera versión como la original, antes de realizar algún cambio. Ejecutando los siguientes comandos puedes crear una versión inicial del proyecto tal como lo recibiste:

# ejecutar esto desde la base del proyecto
git add .
git commit -m “first version”

Si el proyecto ya se encuentra versionado, podemos iniciar creando una rama basada en la rama principal (sea master o trunk) y ahí empezar el proceso de implementación. Por ejemplo, para crear una rama basada en la trunk, ejecutamos:

git checkout trunk
git pull
git checkout -b <tu-nueva-rama>

En este caso el comando git pull sirve para traer los cambios que puedan estar en el repositorio remoto.

2. Buscar la autonomía del ambiente local

Podemos definir el proceso de desarrollo como una iteración de desarrollar y probar. Esta iteración podría repetirse 10, 20 o hasta 50 veces mientras estamos desarrollando. Sin ambiente local de la aplicación, debemos sumarles a estos ciclos de prueba-error las tareas de despliegue, instalación, ejecución de pipelines, pruebas de código estático, etc. A la larga gastaremos más tiempo en estás tareas que escribiendo código y probando.

Por esta razón se vuelve imprescindible tener un ambiente local funcional en el que se puedan hacer pruebas sin pasar por estas actividades de despliegue. Esto permitirá que el proceso de implementación sea más ágil y podamos enfocarnos en el desarrollo de la solución.

El ambiente local nos permite fallar rápida y visiblemente para lograr detectar de manera temprana la presencia de bugs y así evitar comportamientos inesperados en los ambientes productivos.

Lo primero que debemos tener en cuenta es descargar las fuentes en nuestra máquina; compilamos el proyecto, descargamos dependencias y finalmente configuramos nuestro IDE de preferencia. Teniendo estos pasos listos estamos a medio camino de habilitar el ambiente local.

El siguiente paso sería la instalación del servidor de aplicación y base de datos. Esta parte puede consumir cierto tiempo y podemos sentir que no estamos generando valor, pero si pensamos en el tiempo que nos ahorraremos en el desarrollo, vale la pena el esfuerzo. A esto se suma que vamos a conocer el middleware (servidor de aplicación, base de datos) que soporta el código que vamos a trabajar. Este conocimiento es valioso a la hora de desarrollar nuevas funcionalidades o soportar.

Podemos sentir que no estamos generando valor, pero si pensamos en el tiempo que nos ahorraremos en la etapa desarrollo, vemos que vale la pena el esfuerzo.

Posiblemente será necesario hacer alguna modificación en el código para que se adecue al ambiente local. Por ejemplo, modificar una variable para que apunte a alguna ruta de nuestra máquina, o modificar el retorno de una función con un valor fijo. Esta clase de modificaciones pueden tolerarse siempre y cuando no sean muy complejas. Puedes apuntar estas modificaciones con comentarios TODO para revertirlas cuando vayas a desplegar entre ambientes. Por ejemplo:

public boolean isCardActive(String id) {
boolean active = false;
active = ExternalServices.getCardStatus(id);
// TODO: Borrar!! Solo para pruebas
active = true;
return active;
}

Por otro lado, las dependencias con otros sistemas a veces hacen imposible habilitar un ambiente local completamente. En este caso, debemos apoyarnos en unas buenas pruebas unitarias que simulen la integración con servicios externos (base de datos, servicios de terceros, etc.) para poder probar nuestros desarrollos.

3. Leer la documentación

La documentación de los sistemas legacy suele ser ambigua e incompleta, en el peor de los casos (el más frecuente) ni siquiera existe. Normalmente la documentación encontrada no corresponde con el estado actual de la aplicación, lo que puede llevar a suposiciones incorrectas. En todo caso, es bueno revisarla y enfocarse en los componentes que se piensan impactar. A veces en esta documentación encontramos algunas notas importantes o particularidades que pueden ahorrarnos un buen tiempo en la implementación o resolución de errores.

Si no hay una buena documentación, con mayor razón nuestras implementaciones, correcciones o mejoras deben estar muy bien documentadas. De lo contrarío nuestros cambios serán susceptibles a considerarse código legacy.

En algunas ocasiones podemos encontrar comentarios en el código que son fuentes muy fiables de conocimiento. Estos fueron redactados por el anterior desarrollador por lo que podemos suponer que tiene un dominio avanzado en las funcionalidades y lo que hace el código en sí.

Si no se encuentran estos comentarios, el ambiente local también nos puede ayudar a entender el código. A través del debug de la aplicación en el ambiente local podemos seguir el paso a paso del código en ejecución y tener una noción muy completa de su funcionamiento.

4. Menos es más

En el código fuente de los sistemas legacy es muy probable encontrar instrucciones que parecen repetitivas o inoficiosas. Entonces, inmediatamente decidimos que dicha instrucción se puede borrar, o refactorizar sin afectar el comportamiento. Por ejemplo, vemos que una variable se inicializa a nivel de clase, pero solo se usa en un método, entonces podemos hacerla local; o podemos inicializar las variables de una manera más optima pensando en mejor gestión de la memoria, y así entre otras pequeñas modificaciones que a simple vista son irrelevantes.

Esos pequeños cambios que normalmente no corresponden con el caso de uso a implementar pueden generar comportamientos inesperados en la aplicación, lo que conllevará a más tiempo en resolución de errores y más modificaciones del código

En el contexto del código legacy las modificaciones deben ser las mínimas y siempre enfocadas en un caso de uso definido, sea un soporte estructural, bug, vulnerabilidad o nueva funcionalidad.

Mientras estamos desarrollando podemos encontrar muchas oportunidades de mejora del código. En estos casos lo mejor es documentar estos hallazgos y dejarlos en el backlog de mejoras. De esta manera, los cambios se realizarán de manera organizada y en caso de un comportamiento inesperado o error de la aplicación, el análisis y resolución será menos complejo y los puntos de revisión más fáciles de identificar.

Versionar, configurar un ambiente local, leer la documentación y seguir el principio de “menos es más”, son solo algunas de las buenas prácticas que pueden ser de mucha ayuda a la hora de trabajar con aplicaciones legacy. La forma y orden en que se aplican no es estricta y puede variar. Si las incluimos en nuestra metodología de trabajo podemos ser más eficientes y desarrollar cómodamente en este tipo de aplicaciones que pueden parecer intocables y problemáticas.

Si quieres profundizar en este tema puedes buscar el libro de Michael C. Feathers: Working Effectively With Legacy Code. En este libro se explican, al estilo de preguntas frecuentes y con ejemplos, técnicas útiles para aplicar en código legacy.

--

--