Introducción: por qué la refactorización de código legacy en Kotlin no es solo reescribir
Trabajar con código heredado (legacy code) en Kotlin no es solo un reemplazo mecánico de construcciones de Java por la sintaxis del lenguaje. Es un proceso estratégico dirigido a mejorar la legibilidad, seguridad y rendimiento del código sin cambiar su comportamiento externo. En este artículo, exploraremos técnicas avanzadas de refactorización que te ayudarán a transformar un caótico "zoológico" de clases en un sistema ordenado y mantenible. No abordaremos conceptos básicos como var vs val; nos centraremos en lo que realmente cambia la arquitectura.
1. Migración desde Java: reemplazo de getters, setters y clases utilitarias
El problema más común del código legacy es la abundancia de clases Java con getters y setters explícitos, así como clases utilitarias estáticas. Kotlin ofrece soluciones elegantes.
1.1. Acceso basado en propiedades en lugar de JavaBeans
En lugar de escribir user.getName() y user.setName("Alice"), usa las propiedades de Kotlin. Esto no es solo azúcar sintáctico: obtienes una interfaz de acceso unificada que se puede sobrescribir.
// Java legacypublic class User { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; }}
// Kotlin refactorizadodata class User(val name: String) // O con var, si se necesita mutabilidadSi la lógica del getter/setter es más compleja (por ejemplo, validación), usa accesores personalizados:
class User { var name: String = "" set(value) { field = value.trim() // Recorte automático de espacios }}1.2. Funciones de extensión en lugar de utilidades estáticas
El código legacy a menudo contiene clases como StringUtils o FileUtils. En Kotlin, se reemplazan con funciones de extensión. Esto hace que el código sea más legible y se acerque al estilo de "método de objeto".
// Java: StringUtils.isNullOrEmpty(str)// Kotlin: str.isNullOrEmpty() (ya está en stdlib)
// Ejemplo de extensión propiafun String.isValidEmail(): Boolean { return this.contains("@") && this.contains(".")}
// Usoval email = "user@example.com"if (email.isValidEmail()) { ... }2. Trabajo con null safety: reemplazo de Optional y comprobaciones de null
El código Java heredado a menudo sufre de NullPointerException. Kotlin resuelve este problema a nivel de tipos, pero durante la refactorización se deben reemplazar cuidadosamente Optional y las cadenas de comprobaciones de null.
2.1. Reemplazo de Optional por tipos anulables
En lugar de Optional<String>, usa String?. Esto reduce la sobrecarga de creación de objetos y hace que el código sea más conciso.
// Javapublic Optional<String> findName() { ... }
// Kotlinfun findName(): String? { ... }Para cadenas de llamadas, usa el operador ?. y el operador Elvis ?::
// En lugar de if (user != null && user.address != null) ...val city = user?.address?.city ?: "Unknown"2.2. Uso de require() y check() para invariantes
En lugar de comprobaciones manuales al inicio de las funciones, usa funciones integradas:
fun processUser(user: User?) { requireNotNull(user) { "User must not be null" } // o require(user != null) { ... } // Trabajo posterior con user (smart cast)}3. Refactorización de lógica condicional: clases selladas y when
Grandes bloques de if-else o switch son una señal de código legacy. En Kotlin, se pueden reemplazar con sealed class y un when exhaustivo.
3.1. Reemplazo de enum con campos por sealed class
Si tienes un enum que almacena diferentes tipos de datos para cada valor