Um prompt de code review deve dizer ao modelo o que procurar, qual a gravidade de cada problema e que prova apresentar. "Revise este código" traz uma lista simpática de sugestões em que uma falha de segurança fica entre uma dica de docstring e uma preferência de nome. Um prompt de revisão com checklist, níveis de gravidade e pedido de diffs traz um relatório sobre o qual você pode agir, um apontamento por vez.
De "revise isto" a um checklist com gravidade
O código das duas abas tem dois bugs reais. Compare como cada resposta os apresenta.
def get_user(conn, username):
cur = conn.cursor()
cur.execute(f"SELECT id, email FROM users WHERE name = '{username}'")
row = cur.fetchone()
return {"id": row[0], "email": row[1]}No geral, a função é curta e fácil de acompanhar. Algumas sugestões:
- Acrescente anotações de tipo, por exemplo
username: str, para deixar a assinatura mais clara. - Acrescente uma docstring explicando o que a função retorna.
- Use consultas parametrizadas. Montar SQL com f-strings pode levar a SQL injection.
- Trate usuários inexistentes.
fetchone()pode retornarNone. - Nomes:
fetch_usertalvez descreva melhor o acesso ao banco.
A primeira resposta encontrou os dois bugs, e esse é o problema de julgar uma revisão pelo que ela menciona. A injection está em terceiro lugar em uma lista de cinco, amenizada como "pode levar a", sem correção, ao lado de uma dica de docstring. Quem passasse o olho acrescentaria anotações de tipo e seguiria em frente.
O segundo prompt mudou quatro coisas:
- Escopo. "Só bugs, segurança e tratamento de erros. Ignore estilo" tira o ruído. Estilo é trabalho de um linter e de um formatador, que são mais rápidos e nunca discordam de si mesmos.
- Gravidade. Obriga o modelo a ordenar, e uma lista ordenada diz por onde começar.
- Uma entrada que dispare o problema. "
x' OR '1'='1" transforma um risco vago em uma demonstração que você pode rodar. É também o seu melhor filtro contra falsos positivos, como mostra a última seção. - Diffs. Um diff pequeno por apontamento pode ser aplicado ou rejeitado sozinho. Uma função reescrita mistura cada correção com mudanças que ninguém pediu.
"Se não houver nada em um nível de gravidade, não invente" importa mais do que parece. Quando alguém pede uma revisão, o modelo tende a produzir apontamentos mesmo quando há pouco a encontrar, e os mais fracos incham a lista. Uma permissão explícita para não relatar nada em um nível corta esse enchimento.
O contexto de que o revisor precisa
Um revisor humano sabe para que serve o código e de onde vêm as entradas. O modelo sabe só o que você cola. "username vem direto de um formulário de login" é a frase que transforma SQL injection em um apontamento de gravidade alta, e não em um risco teórico. Contexto útil para uma revisão:
- De onde vêm as entradas: usuários, outro serviço interno, um arquivo de configuração que você controla.
- O que chama o código e o que espera receber de volta.
- O ambiente de execução: versão da linguagem, framework, driver do banco. A correção da injection acima usa
?porque esse é o marcador do sqlite3; alguns outros drivers usam%s. - O que mais importa para você nesta mudança: correção, segurança, desempenho ou os três.
Um prompt de revisão reutilizável
Este bloco transforma o checklist em um template. Mude a linguagem, o foco e o código e mantenha as regras. Desligar a parte de restrições é um jeito rápido de ver quanto ruído volta sem ela.
async function loadAll(ids) {
const results = [];
ids.forEach(async (id) => {
const res = await fetch(`/api/items/${id}`);
results.push(await res.json());
});
return results;
}1. Alta: a função retorna antes de qualquer requisição terminar (linha 3)
O forEach não espera callbacks async. O loadAll retorna o array results vazio na hora, e os push acontecem depois. Quem chama sempre recebe [].
2. Média: requisições com falha são tratadas como dados (linha 5)
Uma resposta 404 ou 500 não é uma exceção no fetch. Um corpo de erro em JSON é lido e colocado no array como se fosse um item, e um corpo que não é JSON faz o res.json() gerar um erro dentro de um callback que ninguém espera.
Correção para os dois, que também mantém os resultados na mesma ordem de ids:
async function loadAll(ids) {
- const results = [];
- ids.forEach(async (id) => {
- const res = await fetch(`/api/items/${id}`);
- results.push(await res.json());
- });
- return results;
+ return Promise.all(
+ ids.map(async (id) => {
+ const res = await fetch(`/api/items/${id}`);
+ if (!res.ok) throw new Error(`Item ${id}: HTTP ${res.status}`);
+ return res.json();
+ })
+ );
}
A linha de papel define um padrão ("problemas reais e nada mais"), que é onde o prompt de papel mais ajuda. Se um programa lê a revisão, por exemplo um script que publica os apontamentos como comentários em um pull request, peça JSON com campos fixos em vez de texto corrido; saída estruturada mostra como.
Como lidar com falsos positivos
Alguns apontamentos vão estar errados. O modelo não vê quem chama a função e valida a entrada, a restrição do banco que torna um caso impossível ou o motivo de uma linha estranha ser intencional. Ele também pode errar sobre como uma biblioteca se comporta; alucinação de IA explica por que uma afirmação confiante não é uma afirmação verificada.
Teste cada apontamento com a entrada que o dispara. Se você não consegue produzir uma entrada que chegue ao problema no seu sistema, conteste com o contexto que faltava em vez de aceitar a correção:
function formatPrice(cents) {
return `$${(cents / 100).toFixed(2)}`;
}
Ela só é chamada por `renderCart`, que retorna antes se `Number.isInteger(cents)` não for verdadeiro. Isso ainda é um problema? Se não for, retire o apontamento.Com essa verificação, não: o formatPrice só recebe inteiros, então o apontamento não se aplica e eu o retiro.
Uma coisa que a verificação não cobre: inteiros negativos passam em Number.isInteger, e formatPrice(-500) retorna "$-5.00". Se reembolsos ou descontos puderem chegar a esta função, talvez você queira "-$5.00". Se não puderem, não há nada a mudar.
Um modelo que recebe uma evidência nova deve retirar o apontamento ou explicar por que ele continua valendo com essa evidência em vista. As duas coisas são úteis. Um modelo que simplesmente concorda com toda contestação não está revisando, então julgue a resposta pelos motivos, não por ter concordado ou não. Para código que o próprio modelo escreveu, faça a revisão em uma conversa nova, para que ela não seja feita pelo mesmo raciocínio que produziu o código. Os mesmos hábitos de escopo, contexto e testes deixam o código melhor desde o início; veja prompts para escrever código.
Perguntas frequentes
Qual é um bom prompt para code review?
Diga o que revisar (bugs, segurança, tratamento de erros), peça uma gravidade para cada apontamento e exija uma entrada concreta que dispare o problema, mais um diff que o corrija. Mande o modelo ignorar formatação e nomes, a não ser que você peça. Dê a ele o contexto que um revisor humano teria: para que serve o código e o que o chama.
A IA pode substituir o code review humano?
Não, mas é uma primeira passada útil. Ela é rápida e boa em padrões comuns de bugs, como None não tratado, await esquecido e SQL montado com strings. Ela não conhece as regras do seu produto, o resto da base de código ou o motivo de uma decisão, e alguns apontamentos dela vão estar errados. Use antes de uma revisão humana, não no lugar dela.
Por que um code review de IA aponta problemas que não existem?
O modelo vê só o código que você colou. Se um valor é validado por quem chama a função, ou se um caso nunca pode acontecer no seu sistema, o modelo não tem como saber, então aponta o risco mesmo assim. Pedir uma entrada concreta que cause a falha filtra muitos desses casos: um apontamento sem nenhuma entrada realista que o dispare costuma ser um falso positivo.
Devo pedir à IA que reescreva o meu código durante a revisão?
Peça diffs pequenos, um por apontamento, em vez de um arquivo reescrito. Uma reescrita completa mistura as correções com mudanças que ninguém pediu, e você teria de revisar a reescrita também. Os diffs deixam você aceitar ou rejeitar cada apontamento separadamente.