Refatoração de código legado em Swift: do A ao Z
\nTrabalhar com código legado (legacy code) é uma parte inevitável da vida do desenvolvedor iOS/macOS. Frequentemente, é um código escrito nas primeiras versões do Swift ou até mesmo em Objective-C, sem testes, com "números mágicos" e UIViewController gigantescos. Neste artigo, vamos analisar uma estratégia passo a passo para refatorar esse código, transformando-o em um projeto moderno, sustentável e testável.
1. Preparação e segurança: antes de tocar no código
\nA regra mais importante da refatoração: não quebre o que funciona. Sem testes, qualquer mudança é um salto no escuro.
\n- \n
- Cobertura com testes (Characterization Tests): Escreva testes que capturem o comportamento atual. Mesmo que o código pareça incorreto, primeiro documente seu funcionamento. \n
- Sistema de controle de versão: Faça um commit com o estado atual. Use um branch de funcionalidade. \n
- Ferramentas: Xcode com
Edit > Refactor(Renomear, Extrair Método), além de linters (SwiftLint) para manter um estilo consistente. \n
// Exemplo de "congelamento" do comportamento para um método antigo\nclass LegacyCalculator {\n func calculateTotal(items: [Int]) -> Int {\n // Lógica antiga e ilegível\n var result = 0\n for i in items {\n result += i * 2\n }\n return result + 10\n }\n}\n\n// Teste que captura o comportamento\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\n2. Estratégia "Saco de pancadas": Variáveis globais e singletons
\nO código legado é notoriamente conhecido por seus estados globais e singletons. Isso torna o código fortemente acoplado e imprevisível.
\nProblema:
\nclass NetworkManager {\n static let shared = NetworkManager()\n var baseURL: String = "http://old.api.com"\n func fetchData() { ... }\n}\n\nSolução: Injeção de dependência (Dependency Injection). Crie um protocolo e passe as dependências através do inicializador.
\nprotocol 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\nEssa abordagem permite substituir facilmente os serviços nos testes e torna o código modular.
\n\n3. Combatendo o "Controlador Gordo" (Massive View Controller)
\nClássico do legado iOS: um UIViewController que faz tudo — desde requisições de rede até formatação da interface. A refatoração passa pela separação de responsabilidades (SRP).
Passo 1: Extraia a lógica de negócios para um modelo separado (ViewModel).
\n// Antes no controlador\nfunc buttonTapped() {\n let data = fetchData()\n let total = data.reduce(0, +)\n label.text = "Soma: \\(total)"\n}\n\n// Depois: ViewModel\nclass MyViewModel {\n func calculateTotal(data: [Int]) -> String {\n let total = data.reduce(0, +)\n return "Soma: \\(total)"\n }\n}\n\n\nPasso 2: Extraia a configuração da interface para métodos ou fábricas separadas.
\n- \n
- Crie uma classe
ViewBuilderpara hierarquias complexas. \n - Use
UICollectionViewDiffableDataSourceem vez do antigodataSource. \n
4. Modernização: de Objective-C para Swift e novas APIs
\nFrequentemente, o código legado contém uma mistura de Objective-C e Swift. Ao refatorar, é importante atualizar construções obsoletas:
\n\n- \n
- Substituir
NSErrorporthrows: O Swift moderno usa exceções para tratamento de erros. \n - Abandonar
!(