Pular para o conteúdo

Como eu reviso PR gerado por IA sem virar borracha

Para revisar PR gerado por IA sem virar borracha, eu mudo a pergunta da revisão: em vez de “esse código parece certo?”, eu pergunto “o que esse código faz que ninguém pediu, e o que ele deixou de fazer que deveria?”. Código de IA quase sempre parece certo. Ele é bem formatado, tem nomes bons e passa nos testes que o próprio agente escreveu. É exatamente por isso que ele é perigoso de aprovar no automático.

Neste post eu mostro o checklist que uso quando reviso um pull request feito com Cursor, Claude Code, Copilot ou qualquer outro agente, seja meu ou de alguém do time.

O problema: o PR bonito demais

Quando uma pessoa escreve código ruim, geralmente dá para perceber: nome estranho, função gigante, comentário confuso. A revisão humana foi treinada para achar esses sinais.

Código gerado por IA não tem esses sinais. Ele tem outros problemas, mais silenciosos:

  • Resolve o problema que o agente entendeu, não o que você pediu.
  • Inventa uma função utilitária que já existe no projeto, com comportamento um pouco diferente.
  • Engole erro com um try/catch que só loga e segue.
  • Ajusta o teste para passar, em vez de ajustar o código.
  • Mexe em arquivos que não tinham nada a ver com a tarefa.

Se eu reviso com o mesmo olhar que uso para código humano, eu aprovo tudo isso. Daí o “virar borracha”: carimbar o que a máquina fez.

Antes de abrir o diff

Eu faço três coisas antes de ler qualquer linha:

  1. Releio o pedido original. A issue, o prompt, a descrição da tarefa. Eu preciso ter na cabeça o que deveria mudar antes de ver o que mudou.
  2. Olho a lista de arquivos alterados. Se a tarefa era “corrigir validação de e-mail no cadastro” e o PR mexe em 14 arquivos, incluindo configuração de build, já tem algo errado.
  3. Confiro o tamanho. PR gerado por IA tende a crescer. Se passou de umas poucas centenas de linhas sem um bom motivo, eu peço para quebrar.

Um comando que uso muito para isso:

git diff --stat main...HEAD

Screenshot descrito: saída do git diff --stat no terminal, com a lista de arquivos e as barrinhas de + e -. Num PR que deveria mexer só no formulário de cadastro, aparece também package-lock.json e tsconfig.json. São esses dois que eu abro primeiro.

O checklist

1. Escopo: mudou só o que devia?

  • Algum arquivo fora do escopo da tarefa foi alterado?
  • Teve “refatoração de brinde” que ninguém pediu?
  • Alguma formatação em massa escondendo mudança real no meio?
  • Dependência nova no package.json, requirements.txt ou equivalente?

Mudança fora do escopo não é necessariamente errada, mas precisa ir para outro PR. Misturar aumenta muito a chance de algo passar sem ninguém ver.

2. Segurança

  • Input de usuário chega em SQL, comando de shell, caminho de arquivo ou HTML sem tratamento?
  • Rota nova tem checagem de autenticação e de autorização? (Estar logado não é o mesmo que poder acessar aquele recurso.)
  • Algum segredo, token ou URL interna hardcoded?
  • Log novo imprimindo dado pessoal, token ou payload inteiro?
  • Configuração de CORS, cookies ou headers ficou mais permissiva?

Tenho um post separado só sobre isso, mais detalhado: checklist de segurança antes de colar código do ChatGPT em produção.

3. Testes: eles testam alguma coisa?

Esse é o ponto onde eu mais pego problema. Agentes são ótimos em fazer teste passar. Nem sempre isso significa testar o comportamento.

  • O teste falharia se o código estivesse errado? (Se não falharia, não é teste.)
  • Algum teste existente foi alterado? Por quê? Mudou a expectativa ou só o valor esperado para bater com o novo resultado?
  • Tem skip, only, xit ou teste comentado?
  • O mock é tão completo que o teste só testa o próprio mock?
  • Tem teste do caso de erro, ou só do caminho feliz?

Um truque simples que eu uso: quebro o código de propósito e rodo o teste.

# troco a condição principal da função nova por algo errado
# e rodo só os testes relacionados
npm test -- tests/services/desconto.test.ts

Se os testes continuam verdes com o código quebrado, eles não estão protegendo nada. Volto o código e peço testes de verdade.

4. Regras de negócio

O agente sabe programar. Ele não sabe que, na sua empresa, desconto não acumula com cupom, que pedido acima de certo valor precisa de aprovação, ou que usuário de teste não pode receber e-mail. Se essa regra não estava no pedido, ele provavelmente não aplicou.

  • As regras de negócio envolvidas foram respeitadas? Liste-as e confira uma a uma.
  • O comportamento em casos de borda (zero, vazio, nulo, data no passado, fuso horário) faz sentido para o negócio?
  • Alguma regra existente foi “simplificada” sem querer?

5. Tratamento de erro

  • Algum catch engolindo erro e seguindo em frente?
  • Erro retornado para o cliente vaza stack trace ou detalhe interno?
  • Falha numa chamada externa deixa o sistema em estado inconsistente (cobrou mas não registrou, por exemplo)?

6. Código duplicado e invenções

  • O agente criou uma função que já existe no projeto?
  • Usou uma API, método ou opção de biblioteca que não existe na versão que vocês usam?
  • Importou pacote que nem está instalado, ou com nome parecido com um pacote conhecido?

Diffs que parecem certos mas estão errados

Esses são os padrões que mais me enganaram. Os exemplos abaixo são simplificados, criados para este post, mas o tipo de erro é real.

O operador trocado

// pedido: frete grátis para compras a partir de R$ 200
if (pedido.total > 200) {
  pedido.frete = 0;
}

“A partir de 200” inclui 200. O certo é >=. O teste gerado usou 250 como exemplo, então passou.

O await que sumiu

async function cancelarPedido(id: string) {
  pedidoRepo.marcarCancelado(id); // sem await
  await notificarCliente(id);
}

Funciona no teste local, porque o banco responde rápido. Em produção, se marcarCancelado falhar, ninguém fica sabendo e o cliente recebe e-mail de cancelamento de um pedido que continua ativo.

O catch silencioso

try {
  await gateway.estornar(pagamentoId);
} catch (e) {
  console.log(e);
}
return { ok: true };

A função retorna sucesso mesmo quando o estorno falhou. No diff, parece um tratamento de erro “responsável”.

O teste que foi ajustado

- expect(calcularJuros(1000, 3)).toBe(30.30);
+ expect(calcularJuros(1000, 3)).toBe(30);

O agente mudou o cálculo, o teste quebrou, e ele “corrigiu” o teste. Toda alteração em valor esperado de teste existente merece uma pergunta: o que mudou na regra?

A autorização que ficou para trás

router.get('/faturas/:id', autenticar, async (req, res) => {
  const fatura = await faturaRepo.buscar(req.params.id);
  res.json(fatura);
});

Tem autenticação, então passa no olho. Mas qualquer usuário logado pode ver a fatura de qualquer outro, só trocando o id. Falta checar se a fatura pertence a req.user.

Como eu uso a própria IA na revisão (com cuidado)

Eu uso IA para ajudar a revisar, mas nunca como revisora final. O que funciona para mim:

  • Peço para outro modelo, numa conversa nova, listar riscos do diff, não para dizer se está bom. “Liste o que pode dar errado em produção com essa mudança” gera respostas mais úteis que “revise esse código”.
  • Peço para gerar casos de borda que os testes não cobrem. Depois eu decido quais fazem sentido.
  • Nunca aceito “parece bom” de uma IA como aprovação. A responsabilidade do merge é de quem clica no botão.

Um prompt que uso com frequência:

Você está revisando este diff antes de ir para produção.
Não diga se está bom. Liste:
1. Comportamentos que mudaram e não estão descritos no PR.
2. Inputs que podem quebrar ou abusar dessa mudança.
3. Testes que passariam mesmo se o código estivesse errado.
4. Qualquer arquivo alterado que pareça fora do escopo.
Contexto da tarefa: [cole a issue aqui]

O que eu peço para quem abre PR com IA

No time, combinamos algumas regras que deixaram a revisão bem mais fácil:

  1. A descrição do PR diz que foi feito com ajuda de agente e qual era o pedido.
  2. Quem abriu o PR leu o diff inteiro antes de pedir revisão. Parece óbvio, mas não é.
  3. PR pequeno. Uma tarefa, um PR.
  4. Teste novo precisa falhar sem a mudança. Se possível, mostrar isso no PR.

Checklist rápido para copiar

  • [ ] Reli o pedido original antes do diff
  • [ ] Só arquivos do escopo foram alterados
  • [ ] Nenhuma dependência nova sem justificativa
  • [ ] Input externo tratado; auth e autorização checadas
  • [ ] Nenhum segredo ou dado pessoal em código ou log
  • [ ] Testes falham se eu quebrar o código
  • [ ] Nenhum teste existente “ajustado” sem motivo
  • [ ] Regras de negócio listadas e conferidas
  • [ ] Nenhum catch silencioso, nenhum await faltando
  • [ ] Nenhuma função duplicada ou API inventada

Perguntas frequentes

Revisar PR de IA demora mais que PR humano?

Para mim, no começo demorava. Com o checklist, o tempo ficou parecido, porque eu sei exatamente onde olhar. O que não dá é revisar mais rápido só porque o código “parece limpo”.

Posso deixar uma IA aprovar PR automaticamente?

Eu não faço isso. Uso IA para apontar riscos, mas a decisão de merge é humana. Se der problema em produção, “a IA aprovou” não é uma resposta aceitável.

E se o PR for enorme?

Peço para quebrar. Revisar 2 mil linhas geradas de uma vez é garantia de virar borracha. Melhor vários PRs pequenos.

Preciso saber se o PR foi feito com IA?

Ajuda bastante. Saber disso muda onde eu presto atenção: mais em escopo, testes ajustados e regras de negócio, menos em estilo.

Qual o item mais importante do checklist?

Quebrar o código e ver se o teste falha. É rápido e já revelou mais problemas para mim do que qualquer outra checagem.

Se quiser ver como eu configuro as ferramentas que geram esses PRs, leia meu setup Cursor + Claude Code em 2026.