Refactorización de código legacy en Java: guía paso a paso
Trabajar con código heredado (legacy) en Java es una de las tareas más frecuentes y complejas que enfrentan los desarrolladores. A menudo, este código no tiene pruebas, está escrito en un estilo monolítico y contiene múltiples antipatrones. Sin embargo, reescribir completamente el sistema es costoso y arriesgado. Es mucho más efectivo aplicar una refactorización metódica. En este artículo, analizaremos un enfoque práctico para la refactorización de código Java legacy que permitirá hacerlo más limpio, seguro y mantenible.
1. Preparación: análisis y creación de una "red de seguridad"
Antes de cambiar el código, es necesario entender qué hace y cómo no romper la lógica existente. El principio principal de la refactorización de código legacy es no cambiar el comportamiento. Para ello, necesitamos crear una "red de seguridad": un conjunto de pruebas.
Comience estudiando el código. Encuentre los puntos de entrada (métodos que se llaman desde fuera) e intente determinar los resultados esperados. Incluso si el código está mal escrito, hace algo correctamente.
Después del análisis, escriba pruebas de caracterización (Characterization Tests). Estas son pruebas que fijan el comportamiento actual del código. No verifican si la lógica funciona correctamente, solo documentan lo que sucede en este momento.
Ejemplo: tenemos una clase monstruo que procesa pedidos.
public class OrderProcessor { public void process(Order order) { // 200 líneas de código espagueti if (order.getTotal() > 100) { order.setStatus("VIP"); } else { order.setStatus("STANDARD"); } // ... mucha otra lógica }}No sabemos qué hay dentro, pero podemos escribir una prueba:
@Testpublic void testProcessOrderWithHighTotal() { // Given Order order = new Order(); order.setTotal(150); OrderProcessor processor = new OrderProcessor();
// When processor.process(order);
// Then (fijamos el comportamiento actual) assertEquals("VIP", order.getStatus());}Ejecute las pruebas. Si pasan, ha fijado el comportamiento actual. Ahora tiene protección contra regresiones.
2. Extraer método (Extract Method)
La refactorización más segura y efectiva para métodos grandes. La idea es simple: tomamos un fragmento de código que hace una sola cosa y lo extraemos a un método separado con un nombre claro.
Consideremos un ejemplo de código malo:
public double calculateEmployeePay(Employee emp) { double basePay = emp.getBasePay(); double overtimePay = 0; if (emp.getHoursWorked() > 40) { overtimePay = (emp.getHoursWorked() - 40) * basePay * 1.5; } double tax = basePay * 0.13; double insurance = 5000; return basePay + overtimePay - tax - insurance;}Aquí se mezclan el cálculo de horas extra, impuestos y seguro. Separémoslos:
public double calculateEmployeePay(Employee emp) { double basePay = emp.getBasePay(); double overtimePay = calculateOvertimePay(emp, basePay); double tax = calculateTax(basePay); double insurance = getInsuranceCost(); return basePay + overtimePay - tax - insurance;}
private double calculateOvertimePay(Employee emp, double basePay) { if (emp.getHoursWorked() > 40) { return (emp.getHoursWorked() - 40) * basePay * 1.5; } return 0;}
private double calculateTax(double basePay) { return basePay * 0.13;}
private double getInsuranceCost() { return 5000;}Ahora cada método se encarga de una sola tarea. El código se ha vuelto más legible y es más fácil de probar por separado.
3. Eliminación de duplicación (DRY)
El código legacy a menudo sufre de copiar y pegar. El mismo fragmento de lógica puede aparecer en diferentes clases. Esto es peligroso: al cambiar los requisitos, podría olvidarse de corregir uno de los duplicados.
Supongamos que en dos servicios diferentes