Como identificar e eliminar code smells em bases de código legadas
1. Fundamentos dos Code Smells em Sistemas Legados
Code smells são indicadores superficiais de problemas mais profundos no design do software. Em bases legadas, esses cheiros não são apenas incômodos estéticos — eles representam barreiras reais para manutenção, evolução e compreensão do sistema. Um método excessivamente longo, uma classe que faz tudo, ou condicionais aninhadas em cascata são sintomas de um código que envelheceu mal.
A diferença entre um smell técnico e débito arquitetural é sutil mas importante. Smells são locais e identificáveis por padrões; débito arquitetural é estrutural e exige replanejamento. Por exemplo, um método de 200 linhas é um smell; uma arquitetura monolítica que impede deploy independente é débito arquitetural. Ambos coexistem em legados, mas a abordagem de remediação difere.
O impacto é direto: código com smells tem maior custo de manutenção, menor testabilidade e maior probabilidade de introduzir bugs ao ser modificado. Estudos mostram que equipes gastam até 60% do tempo apenas tentando entender código legado antes de alterá-lo.
2. Estratégias de Varredura e Detecção Automatizada
Antes de refatorar, é preciso mapear o território. Ferramentas de análise estática são o primeiro radar. SonarQube, por exemplo, detecta métodos longos, classes grandes, duplicação e complexidade ciclomática excessiva. PMD e ESLint oferecem regras similares para Java e JavaScript, respectivamente.
// Exemplo de saída de análise estática (SonarQube)
Arquivo: src/processador_pedidos.js
Linha 45: Método "calcularTotal" possui 85 linhas (limite: 20)
Linha 120: Complexidade ciclomática = 18 (limite: 10)
Linha 200: Duplicação detectada com bloco em src/processador_faturas.js (78%)
Métricas como complexidade ciclomática, acoplamento aferente (Ca) e coesão (LCOM) ajudam a quantificar o problema. Um método com complexidade acima de 10 geralmente merece atenção. Scripts de busca textual com regex também são úteis para detectar anti-padrões:
# Script para detectar God Class (muitos métodos públicos)
grep -r "public function\|public void\|public def" src/ | cut -d: -f1 | sort | uniq -c | sort -nr | head -20
3. Identificação Manual e Leitura Crítica de Código
Ferramentas não capturam tudo. Smells semânticos como "código morto" ou "comentários que explicam o que o código deveria dizer" exigem olhos humanos. A técnica de leitura por camadas (começar pela interface, depois lógica, depois dados) ajuda a entender o fluxo sem se perder em detalhes.
Padrões frequentes em legados:
- Código morto: variáveis não utilizadas, funções sem chamadores, branches condicionais que nunca são executados
- Duplicação: blocos idênticos espalhados por diferentes módulos
- Comentários excessivos: indicam que o código não é autoexplicativo
// Exemplo de código morto identificado manualmente
function calcularFrete(tipo, peso) {
if (tipo === 'expresso') {
// Este bloco nunca é executado desde 2019
// return peso * 2.5;
}
return peso * 1.2; // única branch ativa
}
A arte está em diferenciar smells reais de peculiaridades de domínio. Um loop complexo em um algoritmo financeiro pode ser necessário; um método de 300 linhas em uma tela de cadastro provavelmente não.
4. Priorização e Planejamento da Remediação
Nem todo smell merece ação imediata. A priorização deve considerar:
- Criticidade: código executado em cada requisição vs. rotina de batch noturna
- Frequência de mudança: arquivos que aparecem em 80% dos commits recentes (hotspots)
- Risco: smells em áreas sem cobertura de testes
Crie um backlog de refatoração baseado em hotspots de mudança. Use o gráfico de frequência de alterações vs. complexidade para identificar os maiores vilões.
# Hotspots identificados por git log
git log --name-only --pretty=format: | sort | uniq -c | sort -nr | head -10
142 src/controlador_pagamentos.py
98 src/validador_regras.py
73 src/gerador_relatorios.py
Estratégias incrementais funcionam melhor: Boy Scout Rule (deixe o código mais limpo do que encontrou) e Strangler Fig Pattern (substitua gradualmente funcionalidades legadas por novas implementações).
5. Técnicas de Refatoração para Smells Comuns
Long Method: extraia blocos coesos em métodos privados com nomes descritivos.
// Antes: método de 80 linhas
function processarPedido(pedido) {
// validação (20 linhas)
// cálculo de impostos (30 linhas)
// atualização de estoque (20 linhas)
// notificação (10 linhas)
}
// Depois: método delegador
function processarPedido(pedido) {
validarPedido(pedido);
calcularImpostos(pedido);
atualizarEstoque(pedido);
notificarCliente(pedido);
}
God Class: extraia responsabilidades para classes específicas. Se uma classe gerencia usuários, pedidos e relatórios, separe em UsuarioService, PedidoService e RelatorioService.
Condicionais complexas: substitua por polimorfismo ou Strategy Pattern.
// Antes: switch gigante
function calcularDesconto(tipoCliente, valor) {
if (tipoCliente === 'ouro') return valor * 0.2;
if (tipoCliente === 'prata') return valor * 0.1;
if (tipoCliente === 'bronze') return valor * 0.05;
return 0;
}
// Depois: Strategy Pattern
const estrategias = {
ouro: (v) => v * 0.2,
prata: (v) => v * 0.1,
bronze: (v) => v * 0.05
};
function calcularDesconto(tipoCliente, valor) {
return (estrategias[tipoCliente] || (() => 0))(valor);
}
Duplicação: use Template Method para algoritmos que variam em etapas específicas, ou composição funcional para lógicas reutilizáveis.
6. Validação e Mitigação de Riscos Durante a Refatoração
A rede de segurança são os testes. Para código legado sem testes, crie testes de caracterização (golden master) antes de tocar no código. Eles capturam o comportamento atual, mesmo que imperfeito, e garantem que a refatoração não o altere.
// Teste de caracterização para método crítico
test('calcularTotal deve manter comportamento após refatoração', () => {
const pedido = { itens: [{ preco: 100, quantidade: 2 }], desconto: 0.1 };
const resultadoAntigo = calcularTotalAntigo(pedido);
const resultadoNovo = calcularTotalNovo(pedido);
expect(resultadoNovo).toBe(resultadoAntigo);
});
Use feature toggles para habilitar/desabilitar novas implementações sem deploy, e branch by abstraction para substituir componentes gradualmente. Integração contínua com validação automática (lint, testes, análise estática) garante que o código não regrida.
7. Cultura e Sustentabilidade a Longo Prazo
Refatorar é um hábito, não um projeto. Documente decisões arquiteturais com ADRs (Architecture Decision Records) para que futuras alterações não reintroduzam smells.
# ADR-001: Substituição do método gigante calcularTotal
# Contexto: Método com 120 linhas e complexidade 25
# Decisão: Extrair em 4 métodos menores usando Strategy Pattern
# Consequências: Redução de complexidade para 8, maior testabilidade
Treine a equipe em princípios SOLID e design patterns. Estabeleça gates de qualidade no pipeline de CI/CD:
- Complexidade ciclomática máxima por método: 10
- Duplicação máxima: 5%
- Cobertura de testes mínima: 80%
Esses gates previnem regressões e educam a equipe sobre boas práticas continuamente.
Referências
- Refactoring Guru - Code Smells Catalog — Catálogo completo de code smells com exemplos e soluções de refatoração
- SonarQube Documentation - Rules and Metrics — Guia oficial de regras de análise estática para detectar smells automaticamente
- Martin Fowler - Refactoring: Improving the Design of Existing Code — O livro clássico sobre refatoração, com técnicas específicas para eliminar smells
- Michael Feathers - Working Effectively with Legacy Code — Estratégias práticas para lidar com bases legadas, incluindo testes de caracterização
- ThoughtWorks - Strangler Fig Application Pattern — Artigo de Martin Fowler sobre o padrão de substituição gradual de sistemas legados
- ESLint - Rules for Detecting Code Smells in JavaScript — Documentação oficial com regras para identificar complexidade, duplicação e outros smells