Refactorización de código legacy en Java: de la A a la Z
Trabajar con código heredado (legacy) en Java es un desafío que enfrenta todo desarrollador. El código antiguo a menudo carece de pruebas, contiene patrones obsoletos y es difícil de modificar. Sin embargo, reescribir completamente el proyecto no siempre es la mejor solución. En este artículo, analizaremos un enfoque probado para la refactorización de código legacy: desde el análisis hasta la implementación de cambios, con ejemplos concretos en Java.
1. Diagnóstico y preparación: antes de tocar el código
El primer paso y el más importante es entender con qué está tratando. No comience la refactorización editando líneas al azar. En su lugar:
- Mapa de dependencias: visualice qué clases dependen de cuáles. Use herramientas como IntelliJ IDEA Dependency Viewer o JDepend.
- Envoltura de pruebas (Characterization Tests): escriba pruebas que capturen el comportamiento actual del código. Esta es su red de seguridad.
- Identifique puntos de entrada: determine qué métodos se llaman con más frecuencia y dónde ocurren los cambios principales.
Ejemplo de una prueba de caracterización simple para un método que necesita ser refactorizado:
import org.junit.jupiter.api.Test;import static org.junit.jupiter.api.Assertions.assertEquals;
public class LegacyCalculatorTest {
@Test public void testLegacyCalculate() { LegacyCalculator calc = new LegacyCalculator(); // Capturamos el comportamiento actual int result = calc.calculate(10, 5, "add"); assertEquals(15, result, "Comportamiento actual: suma"); result = calc.calculate(10, 5, "sub"); assertEquals(5, result, "Comportamiento actual: resta"); }}Esta prueba no verifica la corrección de la lógica en un sentido absoluto; simplemente captura lo que el código hace actualmente. Después de la refactorización, la prueba debe pasar; de lo contrario, habrá cambiado el comportamiento.
2. Estrategia de refactorización "quirúrgica": método por método
Intentar refactorizar toda una clase de una vez es una forma segura de introducir errores. Use el enfoque de "arqueología de combate" (Combat Archaeology):
- Seleccione un método o un pequeño grupo de métodos relacionados.
- Escriba pruebas de caracterización para ellos (si no existen).
- Aplique una técnica de refactorización (por ejemplo, Extract Method o Rename Variable).
- Ejecute las pruebas. Deben pasar.
- Haga commit de los cambios con una descripción clara.
Ejemplo: refactorización de un método con "números mágicos" y una larga lista de parámetros.
Código original (malo):
public class OrderProcessor { public double process(double amount, int type, double discount) { double result; if (type == 1) { result = amount * 1.2; // 20% de recargo if (discount > 0.1) { result = result - result * 0.05; // 5% de descuento } } else if (type == 2) { result = amount * 1.5; // 50% de recargo } else { result = amount; } return result; }}Después de la refactorización (mejorado):
public class OrderProcessor { private static final double PREMIUM_MARKUP = 1.2; private static final double STANDARD_MARKUP = 1.5; private static final double LOYALTY_DISCOUNT = 0.05; private static final double DISCOUNT_THRESHOLD = 0.1;
public double process(OrderType type, double amount, double discount) { double basePrice = applyMarkup(amount, type); return applyLoyaltyDiscount(basePrice, discount); }
private double applyMarkup(double amount, OrderType type) { return switch (type) { case PREMIUM -> amount * PREMIUM_MARKUP; case STANDARD -> amount * STANDARD_MARKUP; case BASIC