«He estado teniendo noches de insomnio tratando de añadir características en el código que adquirimos de otra empresa. Estoy tratando con la forma más pura de Código Legado»

«Estoy teniendo un tiempo real difícil de tratar con el código enmarañado y desestructurado con el que tengo que trabajar pero no entiendo nada. Código heredado»

Código heredado es un término que probablemente tiene muchas definiciones diferentes como -código adquirido de otra persona, código escrito por otra persona, código que es difícil de entender o código escrito en tecnologías obsoletas. Cualquiera que sea la definición, la mayoría de nosotros creemos que el código heredado es aterrador.

Pregunta> ¿Cómo definirías el código heredado?

Definiendo el código heredado

Michael Feathers en su libro «Working Effectively with Legacy Code» define el código heredado como, código sin pruebas.

Código sin pruebas es un mal código. No importa lo bien escrito que esté; lo bien estructurado que esté; lo bien encapsulado que esté.Sin pruebas no hay manera de saber si nuestro código está mejorando o empeorando.

Bueno, una versión ligeramente modificada de esta definición es «el código sin pruebas unitarias se llama código heredado». Siempre es mejor tener pruebas lo más cerca posible del código (pruebas unitarias > pruebas de integración > pruebas de UI). Por lo tanto, no sería injusto llamar a un código sin pruebas unitarias un código heredado.

Trabajando con código heredado

Pregunta> ¿Qué enfoque tomará si usted fuera a hacer un cambio en el código heredado?

La mayoría de nosotros podría decir, «Voy a hacer el cambio y llamarlo un día, ¿por qué molestarse en mejorar el código». El razonamiento detrás de este proceso de pensamiento podría ser –

  • No tengo suficiente tiempo para refactorizar el código, Preferiría hacer un cambio y completar mi historia
  • ¿Por qué arriesgarme a cambiar la estructura del código que ha estado funcionando en producción durante mucho tiempo
  • ¿Cuál es el beneficio general de refactorizar el código heredado

Michael Feathers llama a este estilo de hacer un cambio como Editar y Rezar. Planificas y haces tus cambios y cuando terminas, rezas y rezas más fuerte para que tus cambios sean correctos.

Con este estilo, uno sólo puede contribuir a aumentar el código Legacy.

Hay un estilo diferente de hacer cambios que es Cubrir y Modificar. Construir una red de seguridad, hacer cambios en el sistema, dejar que la red de seguridad proporcione retroalimentación y trabajar en esas retroalimentaciones.

Se puede asumir con seguridad que Cubrir y Modificar es una manera de ir a tratar con el código heredado.

Pregunta> Pero, ¿debería incluso pasar tiempo escribiendo pruebas en el código heredado o incluso pensar en refactorizar un código heredado?

¿Debería dedicar tiempo a pensar en refactorizar el código heredado?

La regla del Boy Scout

La idea detrás de la regla del Boy Scout, como dijo el tío Bob, es bastante simple: ¡Deja el código más limpio de lo que lo encontraste! Siempre que toques un código antiguo, debes limpiarlo adecuadamente. No te limites a aplicar un atajo que haga el código más difícil de entender, sino que trátalo con cuidado. No basta con escribir bien el código, hay que mantenerlo limpio a lo largo del tiempo.

Tenemos un mensaje muy fuerte cuando se aplica la regla del Boy Scout al código heredado «deja un rastro de comprensión detrás de ti para que otros lo sigan», lo que significa que refactorizaremos el código para hacerlo más comprensible. Y para refactorizar, construiremos una Red de Seguridad a su alrededor.

Ahora que entendemos que no podemos tomar atajos la única opción que nos queda es escribir algunas pruebas, refactorizar el código y proceder con el desarrollo. Preguntas>

  • ¿Qué pruebas debemos escribir?
  • ¿Cuánto debemos refactorizar?

Qué pruebas escribir

En casi todos los sistemas heredados, lo que hace el sistema es más importante que lo que se supone que hace.

Pruebas de caracterización, las pruebas que necesitamos cuando queremos preservar el comportamiento se llaman como pruebas de caracterización. Una prueba de caracterización es una prueba que caracteriza el comportamiento real de un trozo de código. No hay «Bueno, debería hacer esto» o «Creo que hace aquello». Las pruebas documentan el comportamiento actual real del sistema.

Escribiendo la prueba de caracterización

Una prueba de caracterización, por definición, documenta el comportamiento actual real del sistema de la misma manera que se ejecuta en el entorno de producción.

Escribamos una prueba de caracterización para un objeto Cliente que genera una declaración de texto para algunas películas alquiladas por un cliente.

import static com.code.legacy.movie.MovieType.CHILDREN;
import static org.junit.Assert.assertEquals;public void shouldGenerateTextStatement(){ Customer john = new Customer("John");
Movie childrenMovie = new Movie("Toy Story", CHILDREN);
int daysRented = 3;
Rental rental = new Rental(childrenMovie, daysRented); john.addRental(rental); String statement = john.generateTextStatement();
assertEquals("", statement);
}

Esta prueba intenta entender (o caracterizar) la generación de la «Declaración de Texto» para un cliente dado una película infantil alquilada por 3 días. Como no entendemos el sistema (al menos por ahora), esperamos que el extracto esté en blanco o contenga algún valor ficticio.

Ejecutamos la prueba y dejamos que falle. Cuando lo haga, habremos averiguado qué hace realmente el código bajo esa condición.

java.lang.AssertionError: 
Expected :""
Actual :Rental Record for John, Total amount owed = 12.5. You earned 4 frequent renter points.

Ahora, que conocemos el comportamiento del código, podemos seguir adelante y cambiar el test.

import static com.code.legacy.movie.MovieType.CHILDREN;
import static org.junit.Assert.assertEquals;public void shouldGenerateTextStatement(){
String expectedStatement = "Rental Record for John, Total amount owed = 12.5. You earned 4 frequent renter points"; Customer john = new Customer("John");
Movie childrenMovie = new Movie("Toy Story", CHILDREN);
int daysRented = 3;
Rental rental = new Rental(childrenMovie, daysRented);
john.addRental(rental); Sting statement = john.generateTextStatement();
assertEquals(expectedStatement, statement);
}

Espera, ¿acabamos de copiar la salida generada por el código y colocada en nuestro test. Sí, eso es exactamente lo que hicimos.

No estamos tratando de encontrar errores en este momento. Estamos tratando de poner en un mecanismo para encontrar errores más tarde, los errores que se muestran como las diferencias del comportamiento actual del sistema. Cuando adoptamos esta perspectiva, nuestra visión de las pruebas es diferente: no tienen ninguna autoridad moral; simplemente se sientan allí documentando lo que el sistema realmente hace. En esta etapa, es muy importante tener ese conocimiento de lo que realmente hace el sistema en algún lugar.

Pregunta> ¿Cuál es el número total de pruebas que escribimos para caracterizar un sistema?

Respuesta> Es infinito. Podríamos dedicar una buena parte de nuestra vida a escribir caso tras caso para cualquier clase de un código heredado.

Pregunta> ¿Cuándo paramos entonces? Hay alguna forma de saber qué casos son más importantes que otros?

Respuesta> Mira el código que estamos caracterizando. El propio código nos puede dar ideas sobre lo que hace, y si tenemos preguntas, los tests son una forma ideal de plantearlas. En ese momento, escribe una o varias pruebas que cubran una parte suficientemente buena del código.

Pregunta> ¿Cubre eso todo el código?

Respuesta> Puede que no. Pero entonces hacemos el siguiente paso. Pensamos en los cambios que queremos hacer en el código y tratamos de averiguar si las pruebas que tenemos detectarán algún problema que podamos causar. Si no lo hacen, añadimos más pruebas hasta que nos sintamos seguros de que lo harán.

¿Cuánto hay que refactorizar?

Hay mucho que refactorizar en el código heredado y no podemos refactorizarlo todo. Para responder a esto tenemos que volver a entender nuestro propósito de refactorizar el código heredado.

Queremos refactorizar el código heredado para dejarlo más limpio de lo que era cuando llegó a nosotros y para hacerlo comprensible para los demás.

Dicho esto, queremos hacer el sistema mejor manteniendo el enfoque en la tarea. No queremos volvernos locos con la refactorización intentando reescribir todo el sistema en pocos días. Lo que queremos hacer es «refactorizar el código que se interpone en nuestro camino para implementar cualquier nuevo cambio». Intentaremos entenderlo mejor con un ejemplo en el próximo artículo.

Conclusión

Articles

Deja una respuesta

Tu dirección de correo electrónico no será publicada.