Refactorización de código legacy en Swift: de la A a la Z

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Refactorización de código legacy en Swift: de la A a la Z

\n

Trabajar con código obsoleto (legacy code) es una parte inevitable de la vida del desarrollador iOS/macOS. A menudo es código escrito en versiones tempranas de Swift o incluso Objective-C, carente de pruebas, con "números mágicos" y UIViewController gigantescos. En este artículo, analizaremos una estrategia paso a paso para refactorizar dicho código, transformándolo en un proyecto moderno, mantenible y testeable.

\n\n

1. Preparación y seguridad: antes de tocar el código

\n

La regla más importante de la refactorización: no rompas lo que funciona. Sin pruebas, cualquier cambio es un salto a lo desconocido.

\n
    \n
  • Pruebas de caracterización (Characterization Tests): Escriba pruebas que capturen el comportamiento actual. Incluso si el código parece incorrecto, primero documente su funcionamiento.
  • \n
  • Sistema de control de versiones: Haga un commit con el estado actual. Use una rama de características (feature branch).
  • \n
  • Herramientas: Xcode con Edit > Refactor (Rename, Extract Method), así como linters (SwiftLint) para mantener un estilo uniforme.
  • \n
\n\n
// Ejemplo de "congelación" del comportamiento para un método antiguo\nclass LegacyCalculator {\n    func calculateTotal(items: [Int]) -> Int {\n        // Lógica antigua y poco legible\n        var result = 0\n        for i in items {\n            result += i * 2\n        }\n        return result + 10\n    }\n}\n\n// Prueba que captura el comportamiento\nfunc testCalculateTotal() {\n    let sut = LegacyCalculator()\n    let result = sut.calculateTotal(items: [1, 2, 3])\n    XCTAssertEqual(result, 22) // (1+2+3)*2 + 10 = 22\n}\n
\n\n

2. Estrategia "El chivo expiatorio": Variables globales y singletons

\n

El código legacy es tristemente conocido por sus estados globales y singletons. Esto hace que el código esté fuertemente acoplado y sea impredecible.

\n

Problema:

\n
class NetworkManager {\n    static let shared = NetworkManager()\n    var baseURL: String = "http://old.api.com"\n    func fetchData() { ... }\n}\n
\n

Solución: Inyección de dependencias (Dependency Injection). Creamos un protocolo y pasamos las dependencias a través del inicializador.

\n
protocol NetworkService {\n    func fetchData()\n}\n\nclass ModernViewModel {\n    private let network: NetworkService\n    \n    init(network: NetworkService) {\n        self.network = network\n    }\n    \n    func load() {\n        network.fetchData()\n    }\n}\n
\n

Este enfoque permite intercambiar fácilmente los servicios en las pruebas y hace que el código sea modular.

\n\n

3. Lucha contra el "Controlador masivo" (Massive View Controller)

\n

Un clásico del legacy en iOS: un UIViewController que se encarga de todo, desde solicitudes de red hasta el formateo de la UI. La refactorización se realiza mediante la separación de responsabilidades (SRP).

\n\n

Paso 1: Extraiga la lógica de negocio en un modelo separado (ViewModel).

\n
// Estaba en el controlador\nfunc buttonTapped() {\n    let data = fetchData()\n    let total = data.reduce(0, +)\n    label.text = "Suma: \\(total)"\n}\n\n// Ahora: ViewModel\nclass MyViewModel {\n    func calculateTotal(data: [Int]) -> String {\n        let total = data.reduce(0, +)\n        return "Suma: \\(total)"\n    }\n}\n
\n\n

Paso 2: Extraiga la configuración de la UI en métodos o fábricas separadas.

\n
    \n
  • Cree una clase ViewBuilder para jerarquías complejas.
  • \n
  • Use UICollectionViewDiffableDataSource en lugar del antiguo dataSource.
  • \n
\n\n

4. Modernización: de Objective-C a Swift y nuevas APIs

\n

A menudo, el código legacy contiene una mezcla de Objective-C y Swift. Al refactorizar, es importante actualizar las construcciones obsoletas:

\n\n
    \n
  • Reemplazar NSError por throws: El Swift moderno usa excepciones para el manejo de errores.
  • \n
  • Eliminar ! (
  • \n

Blogs

Book Recommendations