Skip to content

Capítulo 12 — Sistemas legados: compreender antes de transformar

“Legado” costuma ser usado como sinônimo de:

  • framework velho;
  • linguagem antiga;
  • arquitetura ruim;
  • falta de testes;
  • dependências obsoletas.

Mas o risco real está em outra coisa:

o sistema continua carregando comportamento que alguém depende e que ninguém compreende completamente.

Isso muda a pergunta.

Não é:

“como reescrever?”

É:

“o que não podemos quebrar enquanto aprendemos?”

Antes de mudar, construa observabilidade comportamental

Section titled “Antes de mudar, construa observabilidade comportamental”

Fowler é direto sobre a relação entre refactoring e testes: quando o código existente não é self-testing, o primeiro passo é criar uma base de testes antes de refatorar. 1

A função desses testes não é provar que o design atual é bom.

É detectar quando a transformação altera comportamento que ainda precisa ser preservado.

No método deste livro, chamamos essa primeira camada de characterization cage.

Ela cerca o comportamento atual antes da cirurgia.

Characterization test não é aprovação do legado

Section titled “Characterization test não é aprovação do legado”

Um teste de caracterização pode registrar um comportamento estranho.

Isso não significa que o comportamento esteja correto do ponto de vista do negócio.

Significa apenas:

“é isso que o sistema faz hoje.”

Depois precisamos classificar:

  • comportamento intencional;
  • bug conhecido;
  • comportamento desconhecido;
  • comportamento dependido por terceiros;
  • comportamento que pode ser removido.

Essa distinção evita um erro comum: congelar para sempre todas as anomalias do sistema só porque foram capturadas em testes.

O primeiro artefato operacional deste capítulo é o Legacy Map.

Ele descreve o sistema antes da transformação.

Estrutura mínima:

  • entry points;
  • módulos;
  • dados;
  • integrações;
  • jobs;
  • filas;
  • eventos;
  • invariantes conhecidos;
  • owners;
  • interfaces externas;
  • pontos sem cobertura;
  • risco operacional;
  • áreas de alto acoplamento.

O mapa não precisa estar perfeito para ser útil.

Ele precisa deixar explícito o que sabemos e o que ainda é hipótese.

Cada área do mapa recebe um nível de confiança.

Exemplo:

  • alta: coberta por testes + documentação + owner;
  • média: testes parciais + comportamento observado;
  • baixa: sem testes, pouco uso conhecido;
  • desconhecida: impacto não mapeado.

Isso é método deste livro.

A função é orientar onde o agente pode agir com maior autonomia e onde precisa primeiro investigar.

Reverse engineering como coleta de evidência

Section titled “Reverse engineering como coleta de evidência”

Reverse engineering, neste método, não significa necessariamente decompilar ou reconstruir internals.

É recuperar estrutura e intenção a partir de evidências.

Fontes possíveis:

  • código;
  • testes existentes;
  • logs;
  • banco;
  • schemas;
  • tráfego;
  • documentação;
  • tickets;
  • commits;
  • especialistas;
  • usuários;
  • scripts operacionais;
  • dashboards.

Evans descreve knowledge crunching como combinação de múltiplas fontes — especialistas, usuários, documentos, sistemas existentes e feedback de implementação — para transformar informação dispersa em modelo compartilhado. 2

Isso é particularmente importante em legado.

O domínio pode estar escondido em:

  • ifs;
  • stored procedures;
  • nomes de colunas;
  • flags;
  • cron jobs;
  • validações duplicadas;
  • convenções não documentadas.

Código contém conhecimento, mas não contém todo o conhecimento

Section titled “Código contém conhecimento, mas não contém todo o conhecimento”

Uma regra pode existir no código sem explicar por que existe.

Exemplo:

if amount > limit * 1.1:
reject()

O código mostra um limite.

Não mostra necessariamente:

  • quem definiu;
  • por que 10%;
  • se ainda é válido;
  • quais exceções existem;
  • se outros sistemas dependem disso.

Por isso, reverse engineering técnico precisa encontrar o domínio.

Caso contrário, modernizamos a estrutura e perdemos a regra.

Em modernização, a implementação pode mudar radicalmente.

O que precisa sobreviver são invariantes relevantes.

Exemplos:

  • saldo nunca negativo;
  • pedido não pode ser faturado duas vezes;
  • identificador externo não muda;
  • evento deve manter ordem;
  • autorização precisa preceder execução.

O Legacy Map deve promover esses invariantes a artefatos explícitos.

Depois, testes e contracts os protegem.

Beck descreve refactoring seguro como uma sequência de passos pequenos apoiados por feedback concreto, justamente para evitar mudanças baseadas em longas cadeias de raciocínio não verificadas. 3

Para agentes, essa disciplina fica ainda mais importante.

Um agente pode produzir uma grande transformação rapidamente.

Velocidade de geração não reduz o risco de:

  • esquecer uma exceção;
  • perder uma regra implícita;
  • alterar timing;
  • mudar formato;
  • quebrar integração.

Por isso, o método prefere:

characterize
↓
isolate
↓
small change
↓
test
↓
inspect diff
↓
repeat

Structural change e behavioral change não deveriam se misturar

Section titled “Structural change e behavioral change não deveriam se misturar”

Quando possível, primeiro altere estrutura preservando comportamento.

Depois altere comportamento com Spec explícita.

Exemplo:

  1. extrair função sem mudar resultado;
  2. adicionar testes;
  3. introduzir interface;
  4. só então mudar regra de negócio.

Isso torna regressões mais localizáveis.

Um seam é um ponto onde conseguimos observar ou substituir comportamento sem reescrever tudo.

No método deste livro, procuramos seams como:

  • função;
  • interface;
  • adapter;
  • HTTP boundary;
  • queue;
  • repository;
  • CLI;
  • database view;
  • feature flag.

Criar seams reduz o raio da transformação.

Os dois artefatos trabalham juntos.

Explica onde estão as partes, relações e riscos.

Transforma comportamento observado em feedback executável.

O mapa orienta onde investigar.

A cage sinaliza quando uma mudança escapou do comportamento conhecido.

Não precisamos caracterizar o sistema inteiro antes de qualquer trabalho.

Priorize fluxos que concentram valor ou risco.

Exemplo:

  • login;
  • checkout;
  • pagamento;
  • emissão;
  • sincronização;
  • exportação;
  • fechamento.

Depois expanda por dependência.

Isso mantém a modernização incremental.

Observability como fonte de verdade temporária

Section titled “Observability como fonte de verdade temporária”

Em sistemas pouco testados, produção pode revelar comportamento que o código não torna óbvio.

Logs, traces e métricas ajudam a responder:

  • quais endpoints ainda são usados;
  • quais flags estão ativas;
  • quais jobs realmente rodam;
  • quais integrações recebem tráfego;
  • quais erros são recorrentes.

Mas evidência de runtime precisa ser tratado com cuidado.

Ausência de tráfego não prova ausência de uso.

Uma técnica possível é executar nova lógica sem substituir a antiga.

Exemplo:

current_result = legacy(input)
candidate_result = new_path(input)
compare(current_result, candidate_result)

Sem publicar o candidate_result.

Isso permite comparar comportamento antes do cutover.

No método deste livro, isso é uma opção de método deste livro, não uma recomendação universal.

Quando existe interface relativamente estável, podemos mover comportamento atrás dela aos poucos.

A sequência pode ser:

  1. capturar contrato;
  2. estabilizar adapter;
  3. caracterizar resposta;
  4. implementar caminho novo;
  5. comparar;
  6. rotear pequena parcela;
  7. observar;
  8. expandir.

O ponto central é evitar big-bang.

Modernização não é migração de tecnologia

Section titled “Modernização não é migração de tecnologia”

Trocar:

  • linguagem;
  • framework;
  • banco;
  • cloud;

sem recuperar regras não resolve o problema.

Podemos apenas recriar o legado em stack nova.

A mudança técnica precisa ser guiada por:

  • invariants;
  • domain model;
  • acceptance criteria;
  • evidência.

Quando o sistema é grande demais para caber em uma única janela, precisamos comprimir.

O método usa um Legacy Skeleton Map como método deste livro:

entry points
→ critical flows
→ domain rules
→ state stores
→ external contracts
→ risk zones
→ evidência pointers

Esse mapa é navegação, não verdade completa.

Um resumo sem ponteiro para a fonte vira opinião.

Cada item importante do skeleton map deve apontar para:

  • arquivo;
  • teste;
  • query;
  • log;
  • schema;
  • ticket;
  • entrevista;
  • commit.

Assim, o agente pode expandir contexto sob demanda.

Em legado, muitas interpretações começam como hipótese.

Exemplo:

Hipótese: este job é responsável por reconciliar pagamentos atrasados.

Antes de remover:

  • procurar caller;
  • observar scheduler;
  • consultar logs;
  • revisar banco;
  • perguntar owner;
  • capturar teste.

O método diferencia:

  • known;
  • inferred;
  • unknown.

Isso impede certeza artificial.

Uma mudança em legado deve registrar:

Quais resultados atuais precisam sobreviver?

Qual mudança é intencional?

Quais testes observam o estado atual?

Onde isolaremos a mudança?

O que pode quebrar fora do módulo?

Como voltar?

Quando o caminho novo assume?

Um anti-pattern é:

  1. abrir repositório;
  2. localizar arquivo parecido;
  3. reescrever;
  4. rodar testes existentes.

Em legado, testes existentes podem não representar o comportamento crítico.

O agente precisa primeiro descobrir:

  • quem chama;
  • quem consome;
  • quais dados toca;
  • quais regras carrega;
  • quais efeitos externos produz.

Em sistema com cobertura parcial, verde não significa segurança.

Pode significar apenas que a área afetada não está sendo observada.

Por isso, antes de confiar no gate, pergunte:

que comportamento este teste realmente cobre?

Falha típica: limpeza estética antes de entendimento

Section titled “Falha típica: limpeza estética antes de entendimento”

Renomear, mover e reorganizar pode parecer inofensivo.

Mas em legado:

  • reflection;
  • scripts;
  • imports dinâmicos;
  • configs;
  • paths;
  • deploy tooling;

podem depender da estrutura atual.

Primeiro capture dependências.

Depois limpe.

Rewrite total promete simplicidade.

Mas também remove feedback incremental.

Durante meses, dois sistemas passam a divergir:

  • regras novas entram no legado;
  • o rewrite fica atrás;
  • comportamento não documentado reaparece tarde.

Quando possível, prefira transformação por fatias verificáveis.

Falha típica: domínio reconstruído só pelo código

Section titled “Falha típica: domínio reconstruído só pelo código”

Código representa implementação.

Domínio representa significado.

Reconstruir regra apenas por nomes técnicos pode perder exceções e práticas humanas.

Evans enfatiza colaboração entre desenvolvedores e especialistas para transformar informação em conhecimento de domínio compartilhado. 2

Há pontos onde a decisão não deve ser automatizada:

  • decidir se comportamento estranho é bug ou contrato;
  • escolher regra canônica quando fontes divergem;
  • aprovar remoção de integração;
  • confirmar invariant de negócio;
  • autorizar cutover irreversível.

O agente pode preparar evidência.

A decisão continua explícita.

O método deste livro usa a seguinte sequência para legado:

  1. Map
  2. Observe
  3. Characterize
  4. Model
  5. Isolate
  6. Change
  7. Verify
  8. Compare
  9. Cut over
  10. Learn

Essa sequência é método deste livro.

Ela não afirma que toda modernização precisa seguir exatamente dez passos.

Ela força a compreensão a aparecer antes da transformação.

Da compreensão à arquitetura de evidência

Section titled “Da compreensão à arquitetura de evidência”

Legacy comprehension deixa uma conclusão importante: testes, logs, contracts, runtime observations e maps não são acessórios dispersos. Juntos, eles formam a base para decidir se uma mudança pode ser aceita.

O próximo capítulo transforma essa prática em arquitetura deliberada.

A pergunta deixa de ser apenas “temos testes?” e passa a ser:

“qual conjunto de evidências cobre os riscos desta mudança, e o que ainda permanece sem prova?”

É a passagem de compreensão local para Evidence Architecture.

Em legado, cada interpretação é uma trust boundary. Código antigo, documentação, logs, tickets e memória humana podem divergir. Nenhuma fonte deve ganhar autoridade automática; conflitos precisam ser registrados e resolvidos por evidência e ownership.

Antes de transformação estrutural, o sistema precisa de evidência suficiente do comportamento relevante: characterization tests, contracts, runtime observations, schemas e invariants. O Legacy Map deve distinguir fato observado, inferência e desconhecido.

Mudanças em legado devem ser pequenas e reversíveis sempre que possível. Cada etapa deve permitir retorno ao estado anterior; cutovers precisam de estratégia explícita, especialmente quando envolvem dados, protocolos ou integrações externas.

Rastreabilidade deste capítulo:

  • CLM-044 — Fowler, necessidade de tornar código existente self-testing antes de refactoring;
  • CLM-045 — Beck, refactoring em passos pequenos com feedback concreto;
  • CLM-046 — Evans, knowledge crunching a partir de especialistas, usuários, documentos, sistemas legados e feedback de implementação.

Legacy Map, Confidence Map, Characterization Cage, Legacy Skeleton Map e protocolo Map→Observe→Characterize→Model→Isolate→Change→Verify→Compare→Cut over→Learn são método deste livro.

Os comparáveis de Chris Ford e Jayaratchagan permanecem fonte limitada e não foram usados como autoridade factual neste texto.

Fontes de acesso limitado não foram usadas como autoridade factual neste capítulo.

Recursos relacionados:

  • Legacy Map template;
  • Characterization Cage checklist;
  • Confidence Map;
  • Legacy Skeleton Map;
  • modernization plan;
  • cutover checklist;
  • futura comparação legacy vs candidate behavior;
  • futura visualização de provenance e confidence por subsystem.

  1. Martin Fowler, Refactoring (1999), Chapter 4. Building Tests, pp. 74–75. Rastreabilidade interna: CLM-044. ↩

  2. Eric Evans, Domain-Driven Design (2004), Knowledge Crunching, pp. 47–48. Rastreabilidade interna: CLM-046. ↩ ↩2

  3. Kent Beck, Test-Driven Development: By Example (2002), Chapter 31: Refactoring, pp. 202–203. Rastreabilidade interna: CLM-045. ↩