Refatoração de código legado em Java: nível avançado
Trabalhar com código herdado (legado) é uma realidade inevitável para qualquer desenvolvedor Java profissional. Muitas vezes, esse código não possui testes, contém dependências confusas, classes monolíticas e padrões obsoletos. A simples refatoração "de olho" é perigosa – pode levar a regressões e parar processos de negócio. Neste artigo, abordaremos técnicas avançadas de refatoração de código Java legado que o ajudarão a modernizar o sistema com segurança, sem interromper seu funcionamento.
1. Estratégia "Três Passos": proteção antes das mudanças
O primeiro e mais importante princípio ao trabalhar com código legado é a segurança. Nunca inicie uma refatoração sem uma rede de proteção. Use a estratégia "Três Passos":
- Entender e isolar – identificar os limites do módulo a ser alterado.
- Cobrir com testes (Testes de Caracterização) – fixar o comportamento atual.
- Refatorar – somente após isso, fazer as alterações.
Para escrever testes de caracterização, use a abordagem do "teste mestre dourado" (Golden Master). Execute o código com um conjunto de dados de entrada, salve a saída. Em seguida, após a refatoração, compare a nova saída com o padrão.
// Exemplo: Teste de Caracterização para um método legadopublic class LegacyOrderProcessorTest {
@Test public void testLegacyProcessOrder() { // 1. Fixamos os dados de entrada Order order = new Order("LEGACY-001", List.of("item1", "item2")); LegacyOrderProcessor processor = new LegacyOrderProcessor();
// 2. Executamos o código legado String result = processor.processOrder(order);
// 3. Comparamos com o padrão pré-salvo (Golden Master) String expected = "Processed: LEGACY-001 - items: [item1, item2]"; assertEquals(expected, result); }}Este teste não verifica a "correção" da lógica, apenas fixa seu estado atual. Após a refatoração, o teste deve passar, garantindo a preservação do comportamento.
2. Desmembrando classes monolíticas: técnica "Extrair Interface" (Extract Interface)
Classes grandes com dezenas de métodos e múltiplas dependências são uma marca típica do código legado. Em vez de tentar reescrever tudo de uma vez, use o Extract Interface.
Crie uma interface que descreva o contrato público da classe. Em seguida, substitua o uso direto da classe pela interface. Isso permite:
- Isolar dependências para teste.
- Extrair gradualmente a implementação para novas classes menores.
- Introduzir objetos Mock sem modificar o código cliente.
// Classe legada originalpublic class HugeDataManager { public void saveData(String data) { /* ... lógica complexa */ } public String loadData(String key) { /* ... */ } public void deleteAll() { /* ... operação perigosa */ } // ... mais 50 métodos}
// Passo 1: Extraímos a interfacepublic interface DataRepository { void saveData(String data); String loadData(String key);}
// Passo 2: A classe legada implementa a interfacepublic class HugeDataManager implements DataRepository { @Override public void saveData(String data) { /* ... */ } @Override public String loadData(String key) { /* ... */ } // deleteAll() permanece apenas na classe, não na interface}
// Passo 3: O código cliente trabalha através da interfacepublic class DataService { private final DataRepository repository;
public DataService(DataRepository repository) { this.repository = repository; }}Agora você pode escrever um teste para DataService usando um Mock para DataRepository, sem tocar no complexo HugeDataManager.
3. Trabalhando com coleções e loops obsoletos
Código Java legado (Java 7