Refactorización de código legacy en PHP: nivel avanzado
Trabajar con código legacy (obsoleto) en PHP es una realidad inevitable para muchos desarrolladores. A menudo, este código es una aplicación monolítica sin pruebas, con dependencias globales y una mezcla de lógica de negocio con la presentación. Una simple refactorización "a ojo" puede provocar errores fatales. En este artículo, veremos técnicas avanzadas de refactorización que le permitirán modernizar sistemas legacy de forma segura y eficiente.
1. Estrategia "Seam": creación de puntos de ruptura
El primer paso en la refactorización de código legacy es crear costuras (seams). Una costura es un lugar donde puede cambiar el comportamiento del código sin editarlo directamente. Esto es especialmente importante cuando el código no está cubierto por pruebas y no puede ejecutarlo de forma aislada.
Consideremos un ejemplo clásico: trabajar directamente con la base de datos dentro de un método.
<?php// Código legacyclass UserReport { public function getActiveUsers() { $db = new PDO('mysql:host=localhost;dbname=test', 'root', ''); $stmt = $db->query("SELECT * FROM users WHERE active = 1"); return $stmt->fetchAll(); }}?>Problema: new PDO(...) crea una dependencia rígida. Para probar este método, necesita tener una base de datos real.
Solución: inyección de dependencia a través del constructor.
<?php// Refactorización: creamos una costura mediante DIclass UserReport { private PDO $pdo;
public function __construct(PDO $pdo) { $this->pdo = $pdo; }
public function getActiveUsers(): array { $stmt = $this->pdo->query("SELECT * FROM users WHERE active = 1"); return $stmt->fetchAll(); }}
// Uso$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '');$report = new UserReport($pdo);?>Ahora puede reemplazar PDO con un objeto mock en las pruebas. Esta es una costura simple pero efectiva.
2. Patrón "Sprout Method" y "Sprout Class"
Cuando no puede modificar un método existente (por ejemplo, debido a la falta de pruebas), use la estrategia "Sprout": cree código nuevo junto al antiguo, cambiando gradualmente las llamadas.
Ejemplo: tenemos un método enorme que valida y guarda un usuario.
<?phpclass UserService { public function saveUser(array $data): bool { // 100 líneas de validación, registro y guardado $email = $data['email'] ?? ''; if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { throw new InvalidArgumentException('Invalid email'); } // ... mucho otro código $db = new PDO(...); $stmt = $db->prepare("INSERT INTO users ..."); // ... }}?>En lugar de refactorizar todo el método de una vez, cree una nueva clase para la validación.
<?phpclass UserValidator { public function validate(array $data): void { if (!filter_var($data['email'] ?? '', FILTER_VALIDATE_EMAIL)) { throw new InvalidArgumentException('Invalid email'); } // Agregue otras comprobaciones }}
class UserService { private UserValidator $validator;
public function __construct(UserValidator $validator) { $this->validator = $validator; }
public function saveUser(array $data): bool { $this->validator->validate($data); // El resto del código permanece por ahora }}?>Ahora puede escribir pruebas unitarias para UserValidator sin tocar el código antiguo. Poco a poco irá extrayendo la lógica de saveUser en nuevas clases.
3. Eliminación de estados globales y estáticos
El código legacy a menudo usa variables globales, singletons y métodos estáticos. Esto hace que el código sea prácticamente imp