Mudar sem adivinhar nem quebrar
Este capítulo separa três trabalhos que parecem semelhantes, mas exigem evidências diferentes: depurar encontra a causa de um comportamento incorreto; refatorar melhora a estrutura preservando o comportamento; migrar muda um contrato ou uma representação ao longo do tempo. Um incidente de aprovação duplicada de ordem de serviço ensina a construir uma linha do tempo, formular hipóteses concorrentes e executar um experimento que realmente as diferencie. Em seguida, o leitor protege um módulo legado com testes de caracterização e planeja a troca do campo `status` por `status_code` usando expandir–migrar–contrair. O exemplo executável mostra por que `requestId` e `eventId` respondem perguntas distintas. Ao final, cada mudança possui objetivo, condição de parada, rollback e pacote de evidência reproduzível.
Mudar sem adivinhar nem quebrar
Uma correção que criou três problemas
Antes do incidente, situe as peças. Frontend é a interface usada pela pessoa. API é a interface pela qual programas solicitam dados ou ações ao servidor. Fila guarda mensagens para processamento posterior; o componente que retira e processa uma mensagem é chamado de consumer. ERP é o sistema empresarial que, neste exemplo, recebe a aprovação da ordem de serviço.
Um supervisor toca uma vez em Aprovar e o sistema de manutenção registra duas aprovações da mesma ordem de serviço (OS). Um agente de programação examina o frontend, vê que o botão continua habilitado durante a espera e conclui: “o usuário clicou duas vezes”. Ele desabilita o botão, aproveita para reorganizar o módulo e troca o campo textual status por um número. O sintoma desaparece no teste manual, mas reaparece quando a fila entrega a mesma mensagem novamente. Um cliente antigo deixa de entender a resposta da API e o banco fica com metade dos registros no formato novo.
O erro não foi apenas uma linha de código. Três trabalhos diferentes foram misturados:
- Depuração (debugging) busca a causa de um comportamento observado.
- Refatoração (refactoring) altera a estrutura interna sem mudar o comportamento prometido.
- Migração (migration) conduz dados, contratos ou consumidores de uma representação antiga para outra.
Cada trabalho responde a uma pergunta diferente. “Por que duplicou?” pede diagnóstico. “Como reduzir a complexidade deste módulo?” pede refatoração. “Como substituir um campo sem quebrar clientes?” pede migração. Um agente pode ajudar nos três, mas deve concluir um raciocínio verificável antes de ampliar o escopo.
Vocabulário antes do procedimento
- Sintoma é o efeito percebido, como “duas aprovações”. Não é necessariamente a causa.
- Hipótese é uma explicação que ainda precisa ser testada.
- Contrato é o comportamento que outra parte pode esperar de um componente, como campos, respostas e erros de uma API.
- Sistema legado é um sistema existente que continua importante, mesmo quando sua documentação ou estrutura já não atendem bem à equipe.
- Experimento discriminante produz resultados diferentes conforme a hipótese verdadeira. Ele serve para separar explicações concorrentes.
- Primeira divergência é a primeira fronteira na linha do tempo em que o observado deixa de corresponder ao esperado.
- Trace, aqui no sentido de rastreamento distribuído, conecta operações de uma mesma solicitação através de serviços. Não é sinônimo de despejar todos os dados em logs.
requestIdidentifica uma requisição recebida pela API.eventIdidentifica um evento publicado para processamento. DoisrequestIdsugerem duas chamadas; o mesmoeventIdprocessado duas vezes sugere reentrega.- Consumer é o componente que consome uma mensagem da fila. Outbox é um padrão que registra a mudança de negócio e o evento a publicar na mesma transação local.
- Teste de regressão falha quando um defeito corrigido reaparece.
- Teste de caracterização registra o comportamento relevante de um sistema legado antes de sua estrutura ser alterada. Ele não declara que todo comportamento antigo é desejável.
- Backfill preenche ou transforma dados existentes depois que uma nova representação foi introduzida.
- Idempotência significa que repetir a mesma operação lógica não produz efeitos adicionais indevidos.
- Rollback é a estratégia para voltar a um estado operacional conhecido. Nem toda migração de dados é reversível; nesse caso, cópia, restauração e reconciliação precisam ser planejadas.
O modelo mental: investigar, preservar, transicionar
Ler o fluxo em texto
- 1. Sintoma reproduzível
- 2. Depurar: localizar a primeira divergência
- 3. Causa confirmada?
- 4. Nova observação que separa hipóteses
- 5. Correção mínima + teste de regressão
- 6. Refatorar: preservar comportamento protegido
- 7. Migrar: expandir, mover e somente depois contrair
Leia o fluxo da esquerda para a direita. O ciclo retorna enquanto a causa não estiver confirmada. Refatoração e migração aparecem depois da correção porque ampliar uma mudança durante a investigação elimina pistas. O diagrama não proíbe uma refatoração necessária para tornar o código testável; ele exige que esse passo seja pequeno, explicitado e separado da hipótese causal.
Depuração: do sintoma à causa confirmada
1. Descreva esperado e observado
Use uma entrada concreta. Em vez de “às vezes duplica”, registre: usuário supervisor-12, OS 742, estado inicial PENDING, uma ação de aprovação e duas linhas de auditoria ao final. Inclua versão, ambiente e marcas de data e hora (timestamps).
2. Reproduza no menor ambiente fiel
O menor ambiente não é sempre uma função isolada. Se a hipótese envolve reentrega da fila, o experimento precisa simular essa reentrega. Simplificar demais pode remover justamente a causa.
3. Construa uma linha do tempo
Marque fronteiras: navegador → API → banco → outbox → fila → consumer → ERP. Procure a primeira divergência, não apenas o último erro. Um erro no ERP pode ser consequência de uma duplicação anterior.
4. Formule hipóteses concorrentes
- H1: o navegador enviou duas requisições.
- H2: a API repetiu a gravação de uma requisição.
- H3: a fila entregou o mesmo evento novamente e o consumer reaplicou o efeito.
Uma explicação única convida o agente a procurar apenas evidências que a confirmem.
5. Escolha o experimento que separa as hipóteses
O código a seguir classifica três linhas do tempo sintéticas. Ele está em TypeScript, linguagem que acrescenta tipos ao JavaScript, e será executado pelo Node.js, o programa que roda JavaScript fora do navegador. Ele não implementa observabilidade de produção — a capacidade de entender o estado interno por sinais como logs, métricas e traces —; demonstra como identificadores em fronteiras diferentes discriminam causas. Set é uma coleção que mantém apenas valores únicos; assert.equal encerra o programa com erro se o resultado real diferir do esperado.
import assert from "node:assert/strict";
type Observation = { requestId: string; eventId: string };
function diagnose(observations: Observation[]): string {
const requests = new Set(observations.map((item) => item.requestId));
const events = new Set(observations.map((item) => item.eventId));
if (requests.size > 1) return "duas-requisicoes";
if (events.size < observations.length) return "reentrega-do-evento";
return "evidencia-insuficiente";
}
assert.equal(diagnose([
{ requestId: "req-1", eventId: "evt-1" },
{ requestId: "req-2", eventId: "evt-2" },
]), "duas-requisicoes");
assert.equal(diagnose([
{ requestId: "req-1", eventId: "evt-1" },
{ requestId: "req-1", eventId: "evt-1" },
]), "reentrega-do-evento");
assert.equal(diagnose([
{ requestId: "req-1", eventId: "evt-1" },
]), "evidencia-insuficiente");
console.log("3 cenários diagnósticos passaram");Salve o bloco como diagnose.ts e execute com Node.js 22.18 ou posterior:
node diagnose.tsA saída esperada é 3 cenários diagnósticos passaram. Desde a versão 22.18, o Node remove os tipos necessários para executar este exemplo, mas não faz verificação de tipos; em um projeto, mantenha o typecheck — verificação dos tipos pelo compilador TypeScript — no fluxo de testes. Observe a terceira asserção: um único registro não autoriza inventar uma causa. Em um sistema real, também seriam necessários identidade da operação, timestamps, resultado e correlação segura entre serviços. Logs não devem expor tokens, senhas, dados pessoais desnecessários ou descrições sensíveis da OS. Ao terminar o exercício isolado, apague somente o arquivo temporário diagnose.ts que você criou.
Quando uma regressão surgiu em algum ponto da história, git bisect pode usar busca binária entre uma revisão conhecida como boa e outra conhecida como ruim. A ferramenta localiza o primeiro commit associado à mudança observável; ela não explica, sozinha, a causa de negócio. O teste executado em cada revisão precisa ser confiável.
Refatoração: melhorar estrutura sem mudar a promessa
Depois de corrigir a causa mínima e adicionar o teste de regressão, pode existir uma razão para reorganizar o módulo. Antes disso, crie testes de caracterização para os comportamentos que precisam permanecer: autorização, transição de estado, auditoria, idempotência e resposta em falha do ERP.
Faça um passo estrutural por vez. Renomear, extrair função e alterar a representação do estado no mesmo diff — conjunto de linhas adicionadas, removidas e modificadas — dificulta descobrir qual passo introduziu uma regressão. Mantenha testes verdes entre passos e revise consumidores que uma busca textual pode não encontrar: SQL (Structured Query Language, linguagem de consulta ao banco), configuração, serialização — conversão de dados para armazenamento ou transmissão — e reflexão, quando o programa descobre e chama elementos dinamicamente.
Um teste de caracterização pode revelar comportamento ruim. Não o eternize silenciosamente. Registre: “comportamento atual protegido durante esta refatoração; correção futura no ticket X, com especificação própria”. Assim, preservar estrutura não vira aprovar uma regra incorreta.
Migração: mudar o contrato sem um salto irreversível
Suponha que status: "APPROVED" deva virar status_code: 30. Remover o campo antigo na primeira implantação (deploy) quebra clientes ainda não atualizados. A mudança paralela, também chamada expandir–migrar–contrair, divide a transição:
- Expandir: adicionar
status_code; aceitar e, quando necessário, produzir as duas representações. Nada antigo é removido. - Migrar: atualizar escritores e leitores em lotes; executar backfill; medir registros ausentes, divergentes e clientes ainda no formato antigo.
- Contrair: somente depois de a telemetria — métricas e registros operacionais — e a reconciliação provarem a migração, parar de usar e remover
status.
O backfill deve ser uma migração idempotente: ao reiniciar depois de uma queda, ele reconhece itens já processados ou calcula novamente o mesmo estado sem duplicar efeitos. Use lotes pequenos, checkpoint — ponto persistido de retomada —, limites de carga e relatório de reconciliação. Execute a migração com uma identidade de serviço que possua somente as permissões necessárias, preserve auditoria e teste a restauração do backup antes da fase destrutiva. Antes da contração, defina rollback. Depois de destruir a representação antiga, “voltar o código” pode não restaurar os dados.
Como era e como funciona agora
Antes dos agentes de programação, grandes reescritas já eram arriscadas porque acumulavam muitas decisões num único lote. Hoje, a IA consegue produzir esse lote mais rapidamente e com explicações convincentes. O princípio de engenharia não mudou: passos pequenos, contratos observáveis e reversibilidade continuam reduzindo risco.
O que mudou foi o controle necessário ao redor do agente. Um pedido como “corrija e modernize o módulo” mistura objetivos e oferece uma condição de parada subjetiva. Um ticket — unidade rastreável de trabalho — melhor fixa a reprodução, os arquivos permitidos, o teste que deve passar, as verificações amplas e o que fica fora do escopo. Nesta enciclopédia, Era Maestro designa o trabalho em que uma pessoa coordena agentes especializados. Mesmo assim, multiplicar agentes não substitui uma observação discriminante nem um responsável humano pela fase destrutiva da migração.
Falhas frequentes e diagnóstico
| Sintoma | Causa provável | Verificação | Controle |
|---|---|---|---|
| A correção funciona no clique manual, mas falha na fila | investigou-se apenas o frontend | repetir o mesmo eventId |
idempotência no consumer e teste de regressão |
| Ninguém consegue revisar o diff | depuração, refatoração e migração foram misturadas | classificar cada alteração por objetivo | separar commits ou tickets coerentes |
| O backfill cria duplicatas após reinício | a migração não é idempotente | interromper e executar novamente em dados de teste | chave estável, checkpoint e reconciliação |
| O rollback do código não recupera o serviço | dados ou contrato antigo foram removidos cedo | simular rollback antes de contrair | manter leitura compatível e backup testado |
| O agente apresenta uma causa sem reprodução | causalidade plausível foi confundida com evidência | pedir hipótese alternativa e teste separador | não corrigir antes da primeira divergência |
Exercício guiado
Use os três cenários do exemplo. Acrescente o campo effectId, identificador do efeito aplicado no ERP, e uma quarta hipótese: a API gravou uma vez, mas o ERP aplicou o mesmo efeito duas vezes. Antes de codificar, escreva qual combinação de identificadores favorece cada hipótese. Implemente uma classificação que possa retornar evidencia-insuficiente e crie pelo menos um teste para cada resultado.
O exercício passa quando outra pessoa executa os testes e confirma que cada conjunto de observações favorece somente a hipótese declarada. Se duas explicações continuarem compatíveis com os mesmos dados, a resposta correta é coletar outra observação, não escolher uma por intuição.
Desafio independente
Planeje a migração de status para status_code no sistema de manutenção. Entregue:
- tabela das fases expandir, migrar e contrair;
- versões compatíveis de leitura e escrita em cada fase;
- teste de caracterização do contrato antigo;
- backfill reexecutável com checkpoint;
- métricas e consulta de reconciliação;
- critério objetivo para iniciar a contração;
- rollback de cada fase e a decisão que exige aprovação humana.
A evidência de conclusão é um ensaio: interrompa o backfill, execute-o outra vez e prove que o estado final não contém duplicatas; simule um cliente antigo durante a fase de migração; tente o rollback antes de remover o campo antigo.
Conclusão: uma mudança, uma afirmação verificável
Depurar prova uma causa; refatorar prova preservação de comportamento; migrar prova uma transição compatível e recuperável. O mesmo diff pode conter linhas relacionadas aos três trabalhos, mas o plano, os testes e a revisão não devem confundi-los.
Ao terminar, recupere sem consultar: qual é a diferença entre sintoma e causa? Que observação separa duas requisições de uma reentrega? O que um teste de caracterização protege? Por que a fase de contração deve ser a última? Essas respostas preparam o próximo passo: delegar mudanças a agentes somente quando escopo, permissões, verificações e condições de parada estiverem explícitos.
Fontes e data de revisão
- Node.js — execução de TypeScript: remoção nativa de tipos, limitações de sintaxe e ausência de verificação de tipos.
- Git —
git bisect: busca binária por uma revisão que alterou uma propriedade observável. - OpenTelemetry — traces: modelo de rastreamento de uma solicitação através de componentes.
- Martin Fowler — Parallel Change: expandir, migrar e contrair uma mudança incompatível.
- Martin Fowler — Refactoring: refatoração como alteração da estrutura preservando comportamento observável.
- PostgreSQL — alterar definição de tabela: operações de evolução de esquema e seus efeitos.
- NIST SP 800-218 — Secure Software Development Framework: práticas de desenvolvimento seguro e verificação.
Revisado em 9 de agosto de 2026.
Comprove o que você aprendeu
Responda todas as questões. O gabarito comentado só aparece depois do envio.