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.
  • requestId identifica uma requisição recebida pela API. eventId identifica um evento publicado para processamento. Dois requestId sugerem duas chamadas; o mesmo eventId processado 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

Fluxo: Sintoma reproduzível, Depurar: localizar a primeira divergência, Causa confirmada?, Nova observação que separa hipóteses, Correção mínima + teste de regressão, Refatorar: preservar comportamento protegido, Migrar: expandir, mover e somente depois contrairSintoma reproduzívelDepurar: localizar aprimeira divergênciaCausa confirmada?Nova observação quesepara hipótesesCorreção mínima + testede regressãoRefatorar: preservarcomportamento protegidoMigrar: expandir, movere somente depoiscontrair
Ler o fluxo em texto
  1. 1. Sintoma reproduzível
  2. 2. Depurar: localizar a primeira divergência
  3. 3. Causa confirmada?
  4. 4. Nova observação que separa hipóteses
  5. 5. Correção mínima + teste de regressão
  6. 6. Refatorar: preservar comportamento protegido
  7. 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.ts

A 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:

  1. Expandir: adicionar status_code; aceitar e, quando necessário, produzir as duas representações. Nada antigo é removido.
  2. Migrar: atualizar escritores e leitores em lotes; executar backfill; medir registros ausentes, divergentes e clientes ainda no formato antigo.
  3. 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:

  1. tabela das fases expandir, migrar e contrair;
  2. versões compatíveis de leitura e escrita em cada fase;
  3. teste de caracterização do contrato antigo;
  4. backfill reexecutável com checkpoint;
  5. métricas e consulta de reconciliação;
  6. critério objetivo para iniciar a contração;
  7. 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

Revisado em 9 de agosto de 2026.

Teste de fixação

Comprove o que você aprendeu

Responda todas as questões. O gabarito comentado só aparece depois do envio.

1. Como distinguir retry do frontend de reentrega do consumer em uma aprovação duplicada?
2. Antes de refatorar um módulo legado sem especificação confiável, qual primeiro passo protege comportamento?
3. Um job de backfill reinicia após queda e duplica registros. Que propriedade estava ausente?

Consulta universal

O que você quer encontrar?

Títulos, capítulos, conceitos, termos, laboratórios e ferramentas em uma única busca.

Digite pelo menos dois caracteres.