Nos capítulos anteriores você aprendeu a pedir código novo, a controlar o que o agente pode executar e a usar o Codex para entender um repositório. Agora vamos usar tudo isso para a tarefa que mais consome tempo de quem programa: encontrar e corrigir bugs. Ao final deste capítulo você será capaz de transformar um relato confuso de erro em um pedido preciso, exigir que o Codex reproduza o problema antes de mexer no código, cobrar a explicação da causa raiz e verificar a correção com uma evidência objetiva, em vez de confiar na promessa de que agora funciona.
O ciclo da depuração assistida
Chamo de depuração assistida o fluxo em que você define o problema e o critério de sucesso, e o agente faz o trabalho pesado de investigar, hipotetizar e testar. Esse fluxo tem cinco passos, e vale seguir na ordem:
- Relato. Você descreve o sintoma, o comportamento esperado, a mensagem de erro e como reproduzir.
- Reprodução. O Codex cria um teste ou um script que falha por causa do bug, e roda para provar que falha.
- Hipóteses. O agente lista causas possíveis e diz como vai descartar cada uma.
- Correção. Só depois de identificar a causa raiz ele altera o código, no menor escopo possível.
- Verificação. O teste de reprodução passa, a suíte existente continua verde e você lê o diff.
Pular o passo dois é o erro mais comum. Sem reprodução, o agente adivinha, e você fica sem meio de saber se a mudança resolveu algo. Com reprodução, existe um antes e um depois mensuráveis.
O relato de bug que o Codex precisa
O relato de bug é a versão do prompt estruturado do capítulo cinco aplicada a defeitos. Ele tem seis itens:
- O que você fez, com dados concretos de entrada.
- O que esperava que acontecesse.
- O que aconteceu de fato.
- A mensagem de erro completa, incluindo o stack trace, que é a lista de chamadas de função empilhadas no momento da falha, do ponto de entrada até a linha que estourou.
- Ambiente e frequência: versão da linguagem, sistema operacional, se falha sempre ou às vezes.
- Escopo negativo: o que ele não deve mudar enquanto investiga.
Cole o stack trace inteiro, sem cortar. As linhas do meio, que parecem ruído de biblioteca, são justamente as que revelam por onde o valor errado passou. Se o erro aparece no navegador ou em um painel de monitoramento, use a entrada por imagem que vimos no capítulo cinco e envie a captura de tela.
- Ouça o áudio com a tela desligada
- Ganhe Certificado após a conclusão
- + de 5000 cursos para você explorar!
Baixar o aplicativo
Bug um: exceção em tempo de execução
Este é o caso mais fácil, porque o programa já te entrega o endereço do crime. Suponha uma API em Python que quebra ao gerar um recibo. O relato na sessão do Codex CLI fica assim:
Bug: POST /pedidos/42/recibo retorna erro 500.
Esperado: PDF do recibo com o total do pedido.
Obtido: exceção abaixo.
Traceback (most recent call last):
File "app/api/pedidos.py", line 88, in gerar_recibo
total = calcular_total(pedido)
File "app/servicos/faturamento.py", line 31, in calcular_total
return sum(i.preco * i.quantidade for i in pedido.itens)
TypeError: unsupported operand type(s) for *: 'NoneType' and 'int'
Reproduz sempre com o pedido 42; o pedido 41 funciona.
Passo 1: escreva um teste que falhe com esse mesmo erro.
Passo 2: explique a causa raiz antes de corrigir.
Nao altere o schema do banco nem outros endpoints.
Repare que o pedido nomeia um caso que funciona e um que falha. Essa comparação é ouro para o agente, porque delimita a diferença entre os dois estados. O Codex vai ler os arquivos citados, criar um teste de reprodução com um item de preço nulo e rodar a suíte para mostrar a falha.
A partir daí, exija a causa raiz, ou seja, a condição que originou o valor inválido, não o lugar onde ele explodiu. Neste exemplo, a causa raiz provavelmente está no cadastro que permitiu salvar um item sem preço, ou na importação que gravou nulo. A pergunta certa é: como um item sem preço chegou ao banco de dados? Peça ao agente duas correções separadas, uma que impede o dado inválido de entrar e outra que faz o cálculo falhar de forma clara e explicada quando o dado antigo aparecer.
Bug dois: erro de lógica sem exceção
Aqui nada quebra. O programa responde com serenidade um número errado, e isso é bem pior. O relato precisa trazer a tabela de valores: entrada, saída esperada, saída obtida. Um exemplo de desconto progressivo:
Bug de logica em app/servicos/desconto.py.
Regra: 5 por cento acima de 100 reais, 10 por cento acima de 500.
Casos:
100,00 -> esperado 100,00 | obtido 95,00
500,00 -> esperado 475,00 | obtido 450,00
500,01 -> esperado 450,01 | obtido 450,01
Escreva testes parametrizados com esses tres casos,
mostre quais deles falham (o caso 500,01 ja passa e serve de controle),
e so depois proponha a correcao.
Nao mude a assinatura da funcao.
Com tabela de casos, o agente identifica o padrão em segundos: as comparações usam maior ou igual onde deveriam usar maior. Esse é o tipo de defeito em que o Codex brilha, porque o problema é estritamente local e você forneceu o oráculo, isto é, a resposta certa. Sem os valores esperados, ele tenderia a reescrever a função inteira segundo a interpretação dele da regra de negócio, e você trocaria um bug conhecido por um desconhecido.
Para erros de lógica cuja origem no tempo você não sabe, existe um atalho poderoso. Peça ao agente para usar o git bisect, um comando do Git que faz busca binária no histórico de commits, testando versões intermediárias até apontar qual commit introduziu a falha. Dê a ele o comando que distingue bom de ruim e deixe rodar. Antes disso, comite ou guarde com git stash tudo o que estiver pendente, porque o bisect substitui a árvore de trabalho por commits antigos e pode levar embora alterações não salvas; ao terminar, rode git bisect reset para voltar ao seu branch. O resultado é um commit e um autor, o que costuma explicar a intenção por trás do erro.
Bug três: comportamento intermitente
Um bug intermitente é aquele que falha em algumas execuções e passa em outras, com o mesmo código. As causas típicas são poucas e vale listá-las no pedido, para o agente investigar uma por uma:
- Condição de corrida, quando duas tarefas concorrentes acessam o mesmo recurso e o resultado depende de quem chega primeiro.
- Dependência de ordem entre testes, com estado compartilhado que um teste deixa sujo para o próximo.
- Tempo e fuso horário, como cálculos que quebram na virada do dia, do mês ou no horário de verão.
- Ordem não garantida de conjuntos ou de consultas sem cláusula de ordenação. Em Python, dicionários preservam a ordem de inserção desde a versão 3.7, mas conjuntos não, e a ordem deles pode mudar de uma execução para outra.
- Rede ou serviço externo lento, escondido atrás de um tempo limite curto.
O pedido ao Codex deve começar por medir a frequência. Algo como: rode este teste cem vezes seguidas, registre quantas vezes falhou, e depois rode em ordem aleatória e isoladamente, comparando as taxas. Se falha isolado, não é dependência de ordem. Se só falha em paralelo, suspeite de estado compartilhado. Esse tipo de experimento repetitivo é exatamente onde o modo automático, com permissão de escrita na pasta de trabalho, economiza muito do seu tempo.
Depois da causa identificada, exija uma correção determinística. Injetar o relógio como parâmetro, fixar a semente do gerador aleatório, ordenar explicitamente a consulta ou usar um bloqueio são correções reais. Adicionar uma espera de dois segundos não é.
Como evitar correções superficiais
Uma correção superficial é a mudança que faz o sintoma desaparecer sem tocar na causa. O Codex é otimizado para deixar os testes verdes, e às vezes o caminho mais curto para o verde é justamente o errado. Aprenda a reconhecer estes padrões no diff:
- Um bloco try com except genérico que engole a exceção e segue em frente.
- Um valor padrão inventado para substituir o dado nulo, escondendo a origem do nulo.
- Aumento de tempo limite ou inclusão de nova tentativa sem explicação de por que a primeira falhou.
- Alteração da asserção do teste para caber no resultado atual do código.
- Marcação do teste como ignorado ou esperado como falha.
A defesa é pedir a explicação antes da edição, em formato fixo. Uma frase que funciona muito bem: antes de alterar qualquer arquivo, responda em três itens: causa raiz, evidência no código que a comprova com arquivo e linha, e por que a sua correção elimina a causa e não apenas o sintoma. Se não tiver evidência, diga que não tem e proponha um experimento. Quando o agente não encontra a causa, é melhor que ele admita e proponha logs temporários do que chute uma mudança plausível.
Vale ainda registrar no AGENTS.md uma regra permanente de depuração, no espírito do capítulo seis: toda correção de bug precisa vir acompanhada de um teste de regressão, isto é, um teste que falharia na versão anterior do código e passa depois da correção. Com essa regra, você não precisa repetir a exigência em cada sessão.
Fechando o ciclo
Antes de aceitar a correção, faça três verificações. Primeiro, peça para o agente desfazer a correção de verdade, guardando o diff com git stash, rodar o teste de reprodução, mostrar que ele volta a falhar e só então reaplicar a mudança; se o teste passa com e sem a correção, ele não testa nada. Conferir isso apenas por raciocínio não conta como evidência. Segundo, rode a suíte completa, não só o teste novo, para flagrar efeitos colaterais. Terceiro, leia o diff com o comando de sessão apropriado e confirme que o escopo negativo foi respeitado.
Depois, faça um commit pequeno, com mensagem que descreva a causa raiz e não apenas o sintoma. Algo como corrige cálculo de desconto que aplicava faixa a partir do limite inclusive vale mais, seis meses depois, do que corrige bug do desconto.
Recapitulando: a depuração assistida tem cinco passos, relato, reprodução, hipóteses, correção e verificação, e a reprodução é o passo que você não pode dispensar. O relato de bug precisa de entrada concreta, comportamento esperado, comportamento obtido, stack trace completo, ambiente e escopo negativo. Exceções em tempo de execução pedem que você siga do ponto da falha até a origem do dado inválido. Erros de lógica pedem uma tabela de casos que sirva de oráculo, e o git bisect quando a origem no histórico é desconhecida. Bugs intermitentes pedem medição de frequência, isolamento e correção determinística. Em todos os casos, cobre a causa raiz com evidência em arquivo e linha, desconfie de except genérico, tempo limite maior e asserção ajustada, e feche sempre com um teste de regressão mais a suíte inteira passando.