El código legacy es inevitable.
Por mucho que intentemos crear la arquitectura perfecta, que tengamos cuidado al crear código nuevo, que tengamos todo el tiempo del mundo para estructurar nuestro código perfectamente… Tarde o temprano surgen problemas.
¿Cuáles son las causas de la aparición de código legacy?
Normalmente suele surgir porque por mucho que intentemos predecir el futuro, al final aparecen casos de uso con los que no contamos, funcionalidades que cambian, otras que surgen…
Y el código que una vez fue creado para solucionar un problema, ahora tiene que solucionar otros distintos.
Con el paso del tiempo, todas estas pequeñas inconsistencias harán que tengamos que pelearnos con un código que ya no es el ideal para solucionar el problema que resuelve.
Y esto es en el mejor de los casos. En el peor, tendrás que pelearte con código que estuvo mal diseñado desde sus inicios.
La problemática de trabajar con código legacy
Uno de los mayores problemas al trabajar con código legacy suele ser la imposibilidad de añadir código testado a las clases.
Podemos llegar a asumir que no merezca la pena que se escriban tests para el código antiguo, ya que normalmente no estará preparado para ello y la cantidad de problemas y tiempo necesario para refactorizar los cambios puede ser inasumible en el momento en el que necesitamos tocarlo.
Pero lo ideal sería que cualquier código nuevo que añadamos sí que incluya sus tests correspondientes.
¿Cómo podemos hacer esto?
Sprout method
Una de las ideas que nos deja el libro de Working Effectively with Legacy Code (libro que te recomiendo si te interesan estos temas), de Michael Feathers, es la de los _Sprout methods_.
Suponiendo que nuestra nueva funcionalidad afecte a un único punto del código, podemos extraer ese código a un método, y hacer testing de ese método de forma independiente.
Estos serían los pasos necesarios para incluir el nuevo código:
- Identifica dónde necesitas realizar el cambio en el código
- Si el cambio puede ser formulado como una única secuencia de sentencias en un único lugar de un método, escribe la llamada al nuevo método que realizará el trabajo y coméntala.
- Determina qué variables locales necesitas del método original, y pásalos como argumento de la llamada.
- Determina si el método extraído necesitará devolver valores al método original. De se así, cambia la llamada para que el valor de retorno sea asignado a una variable.
- Implementa el método extraído mediante TDD.
- Elimina el comentario del método original para habilitar la llamada.
Habrá ocasiones en las que no sea nada fácil instanciar un objeto y ejecutar este método sin tener que modificar algo de la clase.
La única solución que queda llegados a este punto es la de hacer el método estático. Parece ir un poco en contra de toda lógica, pero va a ser la única forma que tendremos de hacer este código testable.
Cuando estamos trabajando con código legacy, necesitamos hacer algunas concesiones que no nos plantearíamos en código hecho desde cero.
Ejemplo
Aunque el ejemplo es bastante sencillo, vamos a verlo rápidamente. Supongamos que tenemos un método que recorre una lista de registros y los guarda en base de datos:
public void saveRecords(List records) {
for (Record record : records) {
db.save(record);
}
}
Un nuevo requisito nos pide ahora que sólo guardemos los 10 últimos registros, que el resto son irrelevantes. Siguiendo la idea del _Sprout method_ podemos hacer:
public void saveRecords(List records) {
List latestRecords = filterTenLatestRecords(records);
for (Record record : latestRecords) {
db.save(record);
}
}
List filterTenLatestRecords(List records) {
int size = records.size()
if (size <= 10) {
return records;
}
return records.subList(size - 10, size);
}
Ahora podemos probar el nuevo método sin mayor problema en los tests.
Sprout Class
Como hemos visto, a veces el _Sprout method_ se nos puede quedar un poco corto, porque nos sea muy difícil instanciar la clase para hacer nuestros tests.
Otra alternativa interesante sería la de crearse una clase que implemente el código nuevo, y testear esa clase.
El peligro que tiene esta solución es que es posible que la división de clases resultante no sea la más lógica, y el código acabe siendo difícil de comprender .
Nunca es fácil trabajar con código legacy
Cuando trabajamos con este tipo de código, nos van a surgir impedimentos a cada paso.
Pero es una parte más de nuestra profesión, y conocer mecanismos como este y muchos otros que existen, nos ayudará a lidiar con ello de forma mucho más llevadera.
Si te interesa la temática de trabajar con código legacy, házmelo saber en la sección de comentarios, y seguiremos hablando sobre ello en futuros artículos.
En Genbeta Dev | Cómo plantarle cara al Legacy Code