Refatoração de código legado em Swift: técnicas avançadas

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Introdução: por que refatorar código legado em Swift é uma arte

Trabalhar com código legado em Swift é uma das tarefas mais complexas e importantes para desenvolvedores iOS/macOS. Projetos legados frequentemente contêm “objetos divinos” (God objects), dependências excessivas do UIKit, ausência de testes e arquitetura caótica. A refatoração avançada não é apenas renomear variáveis, mas uma transformação sistêmica da base de código, mantendo 100% da funcionalidade. Neste artigo, exploraremos técnicas que ajudam a transformar o caos em uma arquitetura sustentável.



1. Modularização: dividindo o monolito em componentes

O primeiro passo para um código organizado é criar módulos independentes. Em vez de um único target gigante, criamos frameworks separados ou módulos do Swift Package Manager.



Definição dos limites do módulo

Usamos o princípio “Alta Coesão, Baixo Acoplamento”. Exemplo: separamos a camada de rede, a camada de lógica de negócios e a camada de UI.



// Antes: tudo em um único arquivo ViewControllerclass LegacyViewController: UIViewController {    func fetchData() {        // Código URLSession aqui    }    func parseData() {        // Parsing JSON aqui    }}

// Depois: módulo Networkpublic class NetworkService { public func fetchData(completion: @escaping (Result<Data, Error>) -> Void) { // Camada de rede limpa }}


Migração via fachadas

Não quebre o código existente: crie uma fachada que delega chamadas ao novo módulo. Substitua gradualmente as chamadas antigas.



class LegacyNetworkFacade {    private let newService = NetworkService()        @objc func oldFetchMethod() {        newService.fetchData { result in            // Adaptação para a API antiga        }    }}


2. Injeção de Dependências (DI) em vez de singletons globais

Código legado frequentemente sofre com singletons: UserManager.shared, APIClient.shared. Isso torna os testes impossíveis. Migramos para DI.



Injeção manual via inicializador

// Problemaclass OldViewModel {    let api = APIClient.shared    let user = UserManager.shared}

// Soluçãoprotocol APIClientProtocol { /* ... */ }protocol UserManagerProtocol { /* ... */ }

class NewViewModel { private let api: APIClientProtocol private let userManager: UserManagerProtocol init(api: APIClientProtocol, userManager: UserManagerProtocol) { self.api = api self.userManager = userManager }}


Integração de contêiner DI (Swinject/Resolver)

Para projetos grandes, usamos bibliotecas. Exemplo com Resolver:

import Resolver

extension Resolver: ResolverRegistering { public static func registerAllServices() { register { NetworkService() as NetworkServiceProtocol } register { UserDefaultsStorage() as StorageProtocol } }}

class RefactoredViewModel { @Injected private var network: NetworkServiceProtocol @Injected private var storage: StorageProtocol}


3. Testes: refatoração protegida por testes

Sem testes, refatoração é roleta-russa. Criamos um “arcabouço de testes” (characterization tests) para o código legado.



Testes de caracterização para código legado

Registramos o comportamento atual como testes, mesmo que não seja ideal.

class LegacyCalculatorTests: XCTestCase {    func testLegacyMultiply() {        // Fixamos o comportamento atual        let result = LegacyCalculator().multiply(2, 3)        XCTAssertEqual(result, 6) // Mesmo que a lógica seja estranha    }}


Refatoração paralela com golden master

Comparamos a saída da implementação antiga com a nova. Usamos XCTAssertEqual para verificar identidade.



func testRefactoredMatchesL                

Blogs

Book Recommendations