Trabalhando com legado: estratégias de manutenção

1. Diagnóstico inicial: entendendo o legado antes de tocar no código

Antes de qualquer intervenção em um sistema legado, é fundamental realizar um diagnóstico preciso. O primeiro passo é o mapeamento de dependências e dívida técnica. Ferramentas como ferramentas de análise estática e visualizadores de dependências ajudam a identificar o grau de acoplamento entre módulos. Métricas como acoplamento aferente (Ca) e eferente (Ce), profundidade de herança (DIT) e falta de coesão (LCOM) são indicadores objetivos.

A análise de cobertura de testes é outro pilar. Código legado frequentemente possui zonas protegidas por testes e "terras de ninguém" onde qualquer alteração pode quebrar funcionalidades críticas. Ferramentas de cobertura mostram exatamente onde os testes faltam. Por exemplo, um relatório pode indicar:

Cobertura de linhas: 34%
Cobertura de branches: 21%
Módulos críticos sem cobertura: módulo-de-pagamento, validador-de-regras-fiscais

Identificar padrões comuns em código legado é igualmente essencial. God classes (classes que fazem tudo), copy-paste generalizado e acoplamento excessivo são sinais de alerta. Um exemplo de god class pode ser identificado por métricas como número de métodos (>30) e linhas por classe (>500).

2. Estratégias de intervenção cirúrgica: o mínimo de risco, o máximo de valor

A técnica do "Strangler Fig" (figueira estranguladora) é uma das mais eficazes para substituir funcionalidades gradualmente. Em vez de reescrever todo o sistema, cria-se uma nova implementação ao lado da antiga, redirecionando chamadas aos poucos. Um exemplo prático de roteamento:

# Configuração de proxy reverso (exemplo conceitual)
if (funcionalidade_antiga && !migrada) {
    encaminhar para servico_legado;
} else {
    encaminhar para novo_servico;
}

Refatoração em passos de bebê significa fazer mudanças atômicas e reversíveis. Cada commit deve representar uma transformação mínima que não quebre a funcionalidade. Por exemplo, renomear uma variável ou extrair um método são passos seguros. O uso de feature flags isola alterações em produção, permitindo ativar ou desativar funcionalidades sem deploy:

if (feature_flags.esta_ativa("nova_regra_tributaria")) {
    aplicar_nova_regra();
} else {
    aplicar_regra_legado();
}

3. Construindo uma rede de segurança: testes como aliados

Testes de caracterização são essenciais para congelar o comportamento existente quando não há documentação. Eles capturam a saída atual do sistema para entradas conhecidas, sem julgar se o comportamento está "certo". O processo é simples:

  1. Executar o sistema com entradas reais
  2. Registrar a saída
  3. Criar um teste que compare futuras execuções com essa saída

Para adicionar testes em código não testável, a inversão de dependências e o uso de seams (costuras) são cruciais. Um seam é um ponto onde você pode alterar o comportamento sem modificar o código diretamente. Por exemplo, substituir uma chamada a um banco de dados por um mock:

// Código original (não testável)
function calcularTotal(pedido) {
    return pedido.itens * banco.buscarTaxa(pedido.categoria);
}

// Com seam (injeção de dependência)
function calcularTotal(pedido, obterTaxa) {
    return pedido.itens * obterTaxa(pedido.categoria);
}

Integração contínua com legado exige cuidado para evitar quebras silenciosas. Pipelines devem executar testes de caracterização e integração em ambientes isolados, com notificações claras para falhas.

4. Documentação viva: registrando o que o código não conta

Decisões de negócio enterradas em código legado precisam ser extraídas e documentadas. Regras como "se o valor for maior que 1000, aplicar desconto de 5%" muitas vezes têm origens obscuras. Entrevistas com stakeholders e análise de logs podem revelar o "porquê".

ADRs (Architecture Decision Records) são uma ferramenta poderosa para registrar decisões arquiteturais durante a manutenção de legado. Um ADR típico inclui contexto, decisão, consequências e alternativas consideradas. Exemplo:

# ADR-023: Substituir módulo de relatórios legado

## Contexto
O módulo de relatórios atual usa uma biblioteca descontinuada.

## Decisão
Migrar para a biblioteca X, mantendo compatibilidade via adaptador.

## Consequências
+ Manutenibilidade futura
- Esforço inicial de 2 semanas

Comentários que ajudam explicam o "porquê" de escolhas não óbvias. Comentários que poluem repetem o que o código já diz ou estão desatualizados. A regra é: documente a intenção, não a implementação.

5. Gestão de mudanças em times: comunicação e processo

Negociar prioridades entre features novas e manutenção de legado requer dados objetivos. Apresente métricas como tempo médio para corrigir bugs no legado versus em código novo, ou o custo de oportunidade de não refatorar. Use a "regra do escoteiro": deixe o código mais limpo do que encontrou, mesmo em pequenas alterações.

Code review para código legado deve focar em segurança e legibilidade. Pergunte: "Essa mudança quebra algo existente?" e "O código ficou mais claro?". Evite discussões estéticas; priorize a consistência com o estilo já existente.

Onboarding de novos membros em sistemas legados exige um roteiro claro: comece por áreas com boa cobertura de testes, forneça um mapa das dependências e incentive perguntas sobre decisões históricas.

6. Ferramentas e automatizações que salvam o dia

Linters e formatters como ESLint, Prettier ou ferramentas específicas de linguagem impõem consistência mesmo em código antigo. Configure-os para rodar automaticamente em commits ou pipelines.

Análise estática com ferramentas como SonarQube detecta code smells específicos de legado: complexidade ciclomática alta, duplicação de código, métodos muito longos. Crie um perfil de qualidade com regras prioritárias para o sistema legado.

Scripts de migração automatizada transformam repetição em automação segura. Por exemplo, um script para substituir chamadas de API antigas por novas:

# Script de migração (exemplo conceitual)
para cada arquivo em src/:
    substituir("api_antiga.buscarCliente", "api_nova.obterCliente")
    substituir("api_antiga.salvarPedido", "api_nova.registrarPedido")
    executar testes de caracterização

7. Quando o legado precisa morrer: critérios para reescrita ou aposentadoria

O custo de manutenção versus custo de reescrita deve ser analisado objetivamente. Calcule o custo acumulado de manutenção nos últimos 12 meses (horas de desenvolvedor, bugs críticos, downtime) e compare com o custo estimado de reescrita (incluindo migração de dados, testes, treinamento). Use a fórmula:

Custo_manutencao_anual > (Custo_reescrita * 2) → considerar reescrita

Estratégias de descomissionamento (sunsetting) devem respeitar o usuário. Comunique com antecedência, ofereça alternativas, migre dados gradualmente e mantenha um período de coexistência.

O mito da reescrita total (big bang) é perigoso. Reescrever tudo de uma vez raramente funciona, pois o conhecimento de negócio embutido no legado é perdido. Prefira substituições incrementais, mesmo que levem mais tempo. A convivência controlada com o legado é quase sempre melhor que uma reescrita arriscada.


Referências