Introdução: por que refatorar código legado em Swift?
Trabalhar com código legado é uma parte inevitável da vida de um desenvolvedor iOS/macOS. Projetos escritos em versões antigas do Swift (2.x, 3.x) ou mesmo em Objective-C frequentemente contêm classes monolíticas, duplicação de lógica e falta de modularidade. Sem refatoração, esse código se torna uma fonte de bugs e retarda a adição de novas funcionalidades.
Neste artigo, vamos explorar estratégias práticas para refatorar código legado em Swift. Você aprenderá como melhorar a arquitetura com segurança, sem quebrar a funcionalidade existente. Todos os exemplos são aplicáveis tanto para aplicativos iOS quanto macOS.
1. Análise e preparação: por onde começar?
Antes de alterar o código, é necessário entender seu estado atual. Sem análise, você corre o risco de quebrar funcionalidades que estão funcionando.
1.1. Cobertura de testes (Characterization Tests)
O primeiro passo é escrever testes de caracterização. Eles fixam o comportamento atual do código, mesmo que pareça incorreto. Somente depois disso você pode fazer alterações com segurança.
// Exemplo de função legada: UserManager.swiftclass UserManager { func fetchUser(by id: Int) -> String { // Implementação antiga com force unwrap let dict = ["id": id, "name": "João"] as [String : Any] return dict["name"] as! String }}
// Teste de caracterizaçãoimport XCTestclass UserManagerTests: XCTestCase { func testFetchUserReturnsName() { let manager = UserManager() let result = manager.fetchUser(by: 1) XCTAssertEqual(result, "João") }}1.2. Inventário de "más práticas"
Faça uma lista de padrões problemáticos no código:
- Force unwrapping (
!) — principal fonte de crashes. - Singletons globais — dificultam os testes.
- Números e strings mágicas — reduzem a legibilidade.
- Métodos longos (mais de 50 linhas) — violam o SRP.
- Falta de tipos (uso de
Any).
2. Refatoração passo a passo: do simples ao complexo
A refatoração de código legado é melhor feita em pequenos passos. Cada alteração deve ser reversível e acompanhada de testes.
2.1. Eliminando o force unwrapping
Substitua ! por construções seguras: guard let ou if let.
// Antes da refatoraçãoclass NetworkService { func loadData() -> Data { let url = URL(string: "https://api.example.com")! return try! Data(contentsOf: url) }}
// Depois da refatoraçãoclass NetworkService { enum NetworkError: Error { case invalidURL case dataLoadingFailed } func loadData() throws -> Data { guard let url = URL(string: "https://api.example.com") else { throw NetworkError.invalidURL } do { return try Data(contentsOf: url) } catch { throw NetworkError.dataLoadingFailed } }}2.2. Decomposição de classes "divinas"
Classes legadas frequentemente violam o princípio da responsabilidade única (SRP). Divida-as em entidades menores.
// Problema: a classe faz tudoclass ProfileViewController: UIViewController { func fetchProfile() { /* requisição de rede */ } func parseJSON() { /* parsing */ } func updateUI() { /* atualização da UI */ } func saveToCoreData() { /* salvamento */ }}
// Solução: divisão em módulosprotocol ProfileRepository { func fetchProfile(completion: @escaping (Result<Profile, Error>) -> Void)}
class ProfileNetworkRepository: ProfileRepository { func fetchProfile(completion: @escaping (Result<Profile, Error>) -> Void) { // Lógica de rede