Refatorando Código Legado: Caminhos Gradativos e Padrões Eficazes

Refatorando Código Legado: Caminhos Gradativos e Padrões Eficazes
“Código legado não é um problema, a forma como lidamos com ele é.”
Neste artigo vamos explorar como abordar um código antigo que já acumulou dívidas técnicas, sem interromper a operação da aplicação. Você aprenderá a mapear problemas, escolher padrões de projeto adequados e aplicar refatoração incremental usando ferramentas automatizadas. Tudo isso com exemplos práticos em Python, Java e JavaScript.
1. Por que refatorar código legado?
- Manutenção mais rápida – Código legível reduz o tempo de diagnóstico de bugs.
- Redução de riscos – Mudanças controladas evitam regressões inesperadas.
- Escalabilidade – Arquiteturas modulares facilitam a adoção de novas funcionalidades.
- Custo-benefício – Cada hora economizada na manutenção se traduz em investimento em inovação.
2. Preparando o terreno: auditoria e métricas
Antes de tocar no código, é essencial obter um panorama objetivo. As etapas abaixo ajudam a definir prioridades:
| Métrica | Ferramenta sugerida | O que indica |
|---|---|---|
| Complexidade ciclomática | SonarQube, radon (Python) | Funções muito complexas são candidatos a simplificação |
| Cobertura de código | JaCoCo (Java), Istanbul (JS) | Áreas pouco cobertas precisam de mais atenção antes da mudança |
| Acoplamento | Structure101, NDepend | Módulos fortemente acoplados dificultam a extração de responsabilidades |
| Duplicação | PMD, jscodeshift | Código repetido aumenta a manutenção e pode ser consolidado |
Dica: Crie um dashboard simples (por exemplo, usando Grafana) para monitorar essas métricas ao longo do processo. Assim, você visualiza o impacto de cada refatoração.
2.1. Identificando code smells
Alguns smells são quase universais:
| Smell | Sintoma | Remédio típico |
|---|---|---|
| Long Method | Funções com mais de 50 linhas | Extract Method |
| Large Class | Classe com muitas responsabilidades | Extract Class |
| Switch Statements | Condicionais extensas espalhadas | Polymorphism ou State |
| Data Clumps | Conjunto de parâmetros sempre passados juntos | Introduce Parameter Object |
Mapeie esses itens em uma planilha e atribua uma prioridade (alto, médio, baixo) baseada no risco de negócio.
3. Padrões de projeto que facilitam a migração
A escolha de padrões adequados pode transformar código confuso em uma estrutura clara e extensível. Veja os mais úteis em contextos legados:
| Padrão | Quando usar | Benefício |
|---|---|---|
| Extract Method | Métodos longos e difíceis de entender | Isola lógica, melhora legibilidade |
| Facade | Subsistemas complexos com APIs espalhadas | Simplifica a interface para clientes |
| Adapter | Integrações com bibliotecas antigas | Permite substituir a implementação sem mudar o código cliente |
| Decorator | Necessidade de adicionar funcionalidades sem alterar a classe original | Mantém o código aberto para extensão |
| Repository | Acesso direto a bancos de dados espalhado no código | Centraliza a persistência e facilita testes unitários |
Observação: Não confunda Facade com Adapter. O primeiro agrupa funcionalidades, já o segundo converte interfaces incompatíveis.
4. Refatoração incremental: técnicas e ferramentas
4.1. Estratégia de “Branch‑by‑Branch”
refactor/001-extract-method).Esse ciclo pode ser repetido dezenas de vezes, permitindo que o código evolua sem grandes “big‑bang”.
4.2. Ferramentas de automação
| Ferramenta | Linguagem | O que faz |
|---|---|---|
| jscodeshift | JavaScript/TypeScript | Aplica transformações AST (Abstract Syntax Tree) em lote |
| Rector | PHP | Refatora código seguindo regras configuráveis |
| IntelliJ Refactorings | Java/Kotlin | Conjunto de refatorações guiadas por IDE |
| rope | Python | Biblioteca para refatoração programática (rename, extract, move) |
| clang‑tidy | C/C++ | Detecta e corrige code smells automaticamente |
Essas ferramentas não substituem o julgamento humano, mas aceleram tarefas repetitivas e reduzem a chance de erro manual.
Exemplos Práticos
A seguir, três casos reais que demonstram como aplicar as técnicas descritas.
Exemplo 1 – Eliminando código duplicado com Extract Method (Python)
# Antes: duas funções quase idênticas
def calcular_imposto_bruto(valor, taxa):
imposto = valor taxa
return round(imposto, 2)
def calcular_imposto_liquido(valor, taxa, deducao):
imposto = valor taxa - deducao
return round(imposto, 2)
Refatoração: extrair cálculo comum
def _calcular_base(valor, taxa):
return valor taxa
def calcular_imposto_bruto(valor, taxa):
return round(_calcular_base(valor, taxa), 2)
def calcular_imposto_liquido(valor, taxa, deducao):
return round(_calcular_base(valor, taxa) - deducao, 2)
O que mudou?
- O cálculo da base (
valor taxa) foi isolado em_calcular_base. - Reduzimos duplicação e facilitamos futuros ajustes (ex.: mudança de fórmula).
Exemplo 2 – Substituindo condicionais complexas por Polymorphism (Java)
// Antes: switch longo que decide o tipo de pagamento
public double calcularDesconto(Pedido pedido, String tipoPagamento) {
switch (tipoPagamento) {
case "CARTAO":
return pedido.getValor() 0.95;
case "BOLETO":
return pedido.getValor() 0.97;
case "PIX":
return pedido.getValor() 0.98;
default:
return pedido.getValor();
}
}
Refatoração usando Polimorfismo
// Interface comum
public interface EstrategiaDesconto {
double aplicar(Pedido pedido);
}
// Implementações concretas
public class DescontoCartao implements EstrategiaDesconto {
public double aplicar(Pedido pedido) { return pedido.getValor() 0.95; }
}
public class DescontoBoleto implements EstrategiaDesconto {
public double aplicar(Pedido pedido) { return pedido.getValor() 0.97; }
}
public class DescontoPix implements EstrategiaDesconto {
public double aplicar(Pedido pedido) { return pedido.getValor() 0.98; }
}
// Contexto que delega
public class CalculadoraDesconto {
private final EstrategiaDesconto estrategia;
public CalculadoraDesconto(EstrategiaDesconto estrategia) {
this.estrategia = estrategia;
}
public double calcular(Pedido pedido) {
return estrategia.aplicar(pedido);
}
}
Benefícios:
- Cada forma de pagamento tem sua própria classe, facilitando a adição de novos tipos sem tocar no código existente.
- O
switchdesaparece, eliminando a fonte de erros ao inserir novos casos.
Exemplo 3 – Automatizando refatoração com jscodeshift (JavaScript)
Suponha que você queira renomear todas as variáveis chamadas data para payload em arquivos .js.
```bash
Instalação
npm install -g jscodeshift
Script de transformação (rename-data-to-payload.js)
module.exports = function(fileInfo, api) { const j = api.jscodeshift; return j(fileInfo.source) .find(j.Identifier, { name: 'data' }) .replaceWith(p => j.identifier('payload')) .to


