Prompt engineering tem má reputação no meio de engenharia — evoca imagens de pessoas digitando "por favor" e "seja um especialista em X" esperando resultados mágicos. Essa reputação é parcialmente merecida: muita da literatura sobre prompting vem de contextos de criação de conteúdo e não se traduz diretamente para desenvolvimento de software. Mas há um corpo de técnicas genuinamente úteis para engenharia — estruturado, testável, com resultado mensurável — que é distinto do "prompt hacking" popular.
A diferença entre um prompt de engenharia bom e um ruim não é vocabulário nem tom — é estrutura de informação. Um prompt bom fornece ao modelo o contexto necessário para produzir output correto, os exemplos que eliminam ambiguidade, e as restrições que delimitam o espaço de soluções aceitáveis. Um prompt ruim deixa o modelo preencher as lacunas com o que parece mais plausível — o que frequentemente não é o que o engenheiro queria.
Este conceito cobre as técnicas que têm evidência empírica de eficácia em tarefas de engenharia: chain-of-thought para problemas de design, few-shot com exemplos do próprio repositório, prompts de geração versus prompts de revisão (que têm estrutura radicalmente diferente), como fornecer contexto de repositório de forma eficaz, e como pedir crítica genuína em vez de validação. Também aborda a iteração de prompt como habilidade técnica — o mesmo ciclo de hipótese-observação-ajuste que se aplica a debugging se aplica a refinar prompts.
Prompting é uma habilidade que se deteriora se não for praticada com intenção. O engenheiro que usa "escreva um endpoint para X" para todas as tarefas vai ter resultados mediocres e vai concluir que LLMs não são úteis para engenharia séria — quando o problema é a instrução, não o modelo.
Chain-of-thought para problemas de design
Chain-of-thought (Wei et al., 2022, NeurIPS) é a técnica de pedir ao modelo que raciocine passo a passo antes de produzir a resposta final. Em tarefas de código simples, o chain-of-thought não muda muito — o modelo já "sabe" como gerar um endpoint simples. Para problemas de design com trade-offs reais, a diferença é substancial.
Sem chain-of-thought, um prompt como "qual abordagem devo usar para implementar cache distribuído nesse sistema?" produz a resposta que parece mais plausível dado o prompt — frequentemente uma lista de opções sem análise do contexto específico. Com chain-of-thought explícito, o modelo analisa o contexto fornecido, deriva implicações de cada opção para esse contexto específico, e produz uma recomendação com raciocínio rastreável.
A instrução mais simples de chain-of-thought para design é: "antes de recomendar, pense em voz alta sobre os trade-offs de cada alternativa dado o contexto que forneci". Variações mais explícitas: "liste as alternativas que você consideraria, para cada uma identifique os prós e contras no contexto deste sistema, e só então faça a recomendação". O passo adicional de raciocínio visível serve dois propósitos: produz output de melhor qualidade porque o modelo "verifica o trabalho" enquanto raciocina, e produz raciocínio que o engenheiro pode verificar e questionar — em vez de uma conclusão opaca.
Chain-of-thought é especialmente útil para: escolha entre alternativas arquiteturais com trade-offs não-óbvios, diagnóstico de bugs onde a causa não é imediata, planejamento de migration complexa onde a ordem das etapas importa, e análise de performance onde múltiplos fatores interagem. Para geração de código simples, o overhead de chain-of-thought geralmente não compensa.
Few-shot com exemplos do repositório
Few-shot prompting é a técnica de fornecer exemplos de input-output que demonstram o padrão desejado antes de fazer o pedido principal. Em código, a versão mais eficaz não é fornecer exemplos genéricos — é referenciar código existente no repositório que já demonstra o padrão correto.
A diferença importa porque o repositório captura as convenções específicas do projeto: naming, estrutura de erro, estilo de teste, padrões de logging. Um exemplo genérico de "como escrever um handler HTTP em Go" pode usar padrões que não são os desse repositório. Um exemplo tirado do próprio repositório é, por definição, correto para o contexto.
A instrução few-shot com código existente como referência tem a estrutura: "olhando o arquivo X como referência de padrão, implemente Y seguindo as mesmas convenções". O agente lê o arquivo de referência, extrai as convenções implícitas (como errors são retornados, como logging é feito, como dependências são injetadas, como testes são estruturados), e aplica essas convenções na implementação nova. Isso é mais eficaz do que tentar descrever todas as convenções em texto — o código é a fonte mais compacta e precisa de sua própria especificação.
Para tarefas de geração de testes, a técnica é ainda mais valiosa: referenciar um arquivo de teste existente para um módulo similar garante que o novo arquivo de teste usa o mesmo setup, os mesmos helpers, os mesmos padrões de assertion — testes que se integram naturalmente na base existente em vez de introduzir um estilo diferente que o time vai precisar normalizar depois.
O código existente no repositório é o melhor exemplo few-shot disponível — é a especificação viva das convenções do time. Em vez de descrever o que você quer em prosa, mostre um arquivo que já faz algo similar e peça "siga esse padrão". A instrução fica mais curta, o contexto fica mais rico, e o output converge mais rapidamente com o que o time espera.
Prompts de geração versus prompts de revisão
Geração e revisão de código são tarefas fundamentalmente diferentes, e os prompts que funcionam para uma não funcionam para a outra. Confundir as duas estruturas é um dos erros mais comuns.
Prompts de geração fornecem contexto e especificam comportamento. A estrutura: contexto do sistema, referência a código existente, descrição do comportamento esperado, restrições explícitas. O objetivo é dar ao modelo tudo que precisa para produzir código correto de primeira — ou pelo menos muito próximo. A qualidade do prompt de geração determina quantas iterações são necessárias antes de chegar ao resultado aceitável.
Prompts de revisão fornecem o código e especificam o que verificar. A estrutura é diferente: apresentar o código, especificar o contexto em que ele vai rodar, e pedir análise em dimensões específicas. "Revise este código" produz output genérico. "Revise este handler de pagamentos especificamente para: (1) bugs de concorrência dado que múltiplas instâncias vão rodar simultaneamente, (2) vazamentos de informação na mensagem de erro que chegaria ao cliente, (3) casos onde o estado do pedido pode ficar inconsistente se a chamada ao serviço de pagamento falhar no meio" produz análise focada e acionável.
A especificidade do que revisar importa porque os LLMs tendem a produzir revisão de código em dois extremos problemáticos: muito superficial (só estilo e nomes) ou muito abrangente (lista de 15 observações sem distinção de criticidade). Especificar as dimensões de revisão calibra o output para os riscos que o engenheiro já identificou como mais importantes para esse código específico.
Um padrão de revisão em duas passagens é especialmente eficaz: primeiro pedir revisão de correção (o código faz o que deveria fazer? casos de borda estão tratados?), depois pedir revisão de qualidade (há problemas de performance não-locais? o estilo está consistente com a base? a naming comunica a intenção corretamente?). Separar as duas passagens evita que a revisão de qualidade ofusque problemas de correção — que são mais críticos.
Fornecendo contexto de repositório de forma eficaz
O contexto que o LLM recebe determina a qualidade do output. Em sessões de chat sem agente, o contexto é o que o engenheiro cola manualmente no prompt. Em sessões com agente (Claude Code, Cursor agent mode), o agente pode ler arquivos do repositório — mas ainda precisa de instrução sobre o que ler.
A armadilha é fornecer contexto demais (colar arquivos inteiros quando só uma seção é relevante, incluindo código que não tem relação com a tarefa) ou de menos (descrever o sistema em prosa quando o código é mais preciso). O meio-termo eficaz: fornecer os tipos e interfaces que o código novo vai usar, um ou dois exemplos de código similar existente, e qualquer convenção que não seja óbvia pelo código mas que importa para a tarefa.
Em projetos com Claude Code, o CLAUDE.md cobre as convenções globais. O que precisa ser fornecido por instrução é o contexto local da tarefa: quais interfaces o novo código vai implementar, quais módulos vai chamar, qual comportamento de módulos adjacentes é relevante. A instrução não precisa explicar o sistema inteiro — precisa dar ao agente uma janela suficientemente ampla do sistema para que ele não tome decisões incompatíveis com o contexto imediato.
// PROMPT DE GERAÇÃO (estrutura correta)
// ----------------------------------------
// Contexto: OrderService em projeto de e-commerce (.NET 10,
// Minimal API). Ver IOrderRepository em Contracts/IOrderRepository.cs
// e o handler existente em Features/Orders/GetOrderHandler.cs
// como referência de padrão.
//
// Tarefa: implementar Features/Orders/CancelOrderHandler.cs
//
// Comportamento:
// - Recebe CancelOrderCommand (OrderId, UserId, Reason)
// - Valida que o pedido pertence ao UserId
// - Valida que o status permite cancelamento
// (só Pending ou Processing — não Shipped/Delivered)
// - Atualiza status para Cancelled, salva CancellationReason
// - Publica OrderCancelledEvent via IEventBus
// - Retorna Result<OrderDto> (não lança exception)
//
// Restrições:
// - Idempotente: cancelar pedido já cancelado → Result.Ok
// - Não cancelar se pagamento já capturado (chamar
// IPaymentService.IsPaymentCapturedAsync primeiro)
// PROMPT DE REVISÃO (estrutura diferente)
// ----------------------------------------
// Revise o handler CancelOrderHandler.cs abaixo.
// Contexto: vai rodar em múltiplas instâncias simultâneas,
// o banco é Postgres com READ COMMITTED.
//
// Analise especificamente:
// 1. Race condition: dois cancelamentos simultâneos do mesmo
// pedido — o status pode ser corrompido?
// 2. Consistência: se PublishEventAsync falhar após SaveAsync,
// o estado fica inconsistente?
// 3. Vazamento de info: a mensagem de erro que chega ao
// cliente revela detalhes internos?
// 4. Missing case: há status de pedido que deveria poder
// ser cancelado mas não está coberto?
O prompt de geração especifica o que construir; o de revisão especifica o que verificar. Mesmos problemas respondidos por estruturas de prompt completamente diferentes.
# Sem chain-of-thought (output genérico):
# "Qual é a melhor forma de implementar cache
# para os resultados de busca de produtos?"
# → Resposta: Redis, Memcached, ou in-memory...
# (lista de opções sem análise do contexto)
# Com chain-of-thought explícito (output específico):
"""
Preciso decidir a estratégia de cache para o endpoint
GET /api/v1/search?q={query}&category={cat}&page={n}
Contexto do sistema:
- FastAPI, 3 instâncias atrás de load balancer
- 50k produtos, índice de busca atualizado a cada 5 minutos
- 80% do tráfego é as mesmas 200 queries top
- Latência atual: 400ms (Elasticsearch); alvo: <80ms
- Redis disponível na infra, Memcached não
Antes de recomendar, raciocine sobre:
1. Quais são as alternativas viáveis dado o contexto?
2. Para cada alternativa: qual a granularidade da chave
de cache (query+cat+page? só query+cat?), qual TTL faz
sentido dado que o índice atualiza a cada 5min, e como
a invalidação funcionaria quando o índice atualizar?
3. O que acontece em cache miss durante spike de tráfego
(thundering herd)? Como cada alternativa lida com isso?
4. Qual a complexidade de implementação de cada uma?
Depois do raciocínio, dê uma recomendação concreta com
justificativa baseada nos pontos acima.
"""
O chain-of-thought explicita os eixos de análise antes de pedir a conclusão. O output passa a ser rastreável — o engenheiro pode discordar de um passo específico do raciocínio, não só da conclusão.
// Sem few-shot (output pode não seguir convenções):
// "Cria um handler HTTP para DELETE /users/{id}"
// → Pode usar qualquer estilo de erro, qualquer logging...
// Com few-shot referenciando código existente:
//
// "Olha o handler em internal/handlers/products.go,
// especificamente a função handleDeleteProduct (linha 87).
// Cria um handler equivalente handleDeleteUser em
// internal/handlers/users.go seguindo:
// - Mesmo padrão de extração de path param (chi.URLParam)
// - Mesmo wrapping de erro (writeError helper)
// - Mesmo padrão de logging (h.log.With(...).Info(...))
// - Mesmo padrão de resposta 204 em sucesso
//
// Diferenças específicas do handler de usuário:
// - Soft delete (marca deleted_at, não remove)
// - Verificar se usuário tem recursos ativos antes de
// deletar (chamar h.userSvc.HasActiveResources)
// - Se tem recursos ativos: 409 Conflict com lista
// dos recursos (não pode deletar)"
//
// Resultado: handler que se integra naturalmente
// na base sem introduzir estilo diferente
Referenciar linha específica do arquivo de referência ajuda o agente a focar no padrão correto. "Siga o mesmo estilo" sem referência concreta é ambíguo — "siga handleDeleteProduct na linha 87" é preciso.
O problema do assistente que concorda — como pedir crítica genuína
LLMs têm viés de concordância: tendem a validar a abordagem que o usuário está considerando em vez de oferecer crítica genuína. Esse comportamento é consequência do treinamento por feedback humano — humanos tendem a dar feedback positivo para respostas que validam suas ideias. O resultado é um modelo que diz "boa abordagem, você poderia adicionar X e Y" quando a resposta honesta seria "essa abordagem tem um problema fundamental".
Para obter crítica genuína, o prompt precisa tornar explícito que você quer discordância. Algumas estruturas que funcionam:
"Steel-man o argumento oposto." Em vez de perguntar "essa abordagem é boa?", perguntar "qual é o melhor argumento contra essa abordagem?" obriga o modelo a identificar as fraquezas reais em vez de validar o que foi proposto. Após o steel-man, você pode avaliar se as objeções são relevantes para o seu contexto.
"Assuma que estou errado e explique por quê." Instrução direta para o modelo adotar posição crítica. Funciona especialmente bem para decisões de design: "assuma que escolher GraphQL para essa API foi uma decisão ruim — qual é o argumento mais convincente para essa posição?" produz análise de trade-offs mais honesta do que "o que você acha de usar GraphQL aqui?"
"O que eu não considerei?" Pergunta aberta que convida o modelo a identificar lacunas no raciocínio. Mais eficaz quando combinada com contexto rico: "dado o design que descrevi, o que eu não considerei que poderia ser um problema em produção?" Permite que o modelo traga dimensões que o engenheiro não explicitou — performance sob carga, comportamento de falha, custo de operação, complexidade de migração futura.
Separar geração de avaliação. Nunca pedir ao mesmo modelo, no mesmo turno, para gerar uma solução e avaliá-la. O modelo vai avaliar o que acabou de gerar positivamente — ele está literalmente argumentando a favor do próprio output. A avaliação crítica deve ser uma sessão separada, idealmente com um novo contexto que não carrega o viés de ter gerado a solução avaliada.
Iteração de prompt como habilidade técnica
Engenheiros aprendem a debugar código iterativamente: formulam uma hipótese sobre a causa do bug, testam, observam o resultado, refinam a hipótese. O mesmo ciclo se aplica a refinar prompts. A diferença é que bugs têm uma causa objetiva — o prompt "incorreto" é o que não produz o output desejado, que é uma definição mais subjetiva mas ainda assim verificável.
O ciclo de iteração de prompt tem quatro passos. Observar o problema no output: o que especificamente está errado? O código usa a abstração errada? Ignora um caso de borda? Usa estilo inconsistente? Ser específico sobre o que está errado é o primeiro passo — "o output não está bom" não leva a lugar nenhum. Diagnosticar a causa no prompt: o prompt fornecia contexto suficiente para o modelo saber o que está errado? A restrição violada estava explícita? O exemplo de referência demonstrava o padrão correto? Ajustar o prompt: adicionar o contexto faltante, tornar a restrição explícita, referenciar um exemplo melhor. Verificar o resultado: o ajuste resolveu o problema específico sem introduzir um novo?
Uma observação prática: quando o mesmo ajuste de contexto resolve o problema repetidamente em domínios similares, ele deve entrar no CLAUDE.md em vez de ser repetido em cada instrução. O CLAUDE.md é o acúmulo de aprendizados sobre o que o agente precisa saber para esse repositório — construído iterativamente à medida que problemas de contexto aparecem.
Quando o output do LLM não está correto, a tendência é refinar a instrução de forma vaga — "faça melhor", "seja mais cuidadoso", "leve em conta todos os casos". Essas instruções não especificam o que está errado e raramente produzem melhora. A instrução de correção eficaz é específica: "o handler não verifica se o usuário é dono do recurso antes de modificar — adicione essa verificação seguindo o padrão de ownership check em ProductHandler.cs linha 43".
Prompts para tarefas específicas de engenharia
Algumas tarefas de engenharia têm estruturas de prompt que convergem consistentemente para bons resultados. São pontos de partida, não fórmulas — devem ser adaptadas ao contexto específico.
Diagnóstico de bug: "Tenho um bug onde [comportamento observado] acontece quando [condições]. Esperava [comportamento correto]. Código relevante: [snippet]. Antes de propor solução, liste as hipóteses de causa mais prováveis em ordem de probabilidade, com o raciocínio de cada uma. Depois indique quais evidências adicionais confirmariam ou descartariam cada hipótese."
Refactoring com preservação de comportamento: "Refatore [código] para [objetivo de refactoring — extrair método, reduzir duplicação, melhorar naming]. Requisito crítico: o comportamento observável não pode mudar. Os testes existentes devem continuar passando. Liste cada mudança que você vai fazer e confirme que nenhuma altera comportamento."
Explicação de código não-familiar: "Explique o que esse código faz assumindo que eu sei [linguagem e padrões gerais] mas não conheço [domínio específico / biblioteca / contexto do sistema]. Foque no porquê das decisões, não no quê — o quê eu consigo ler no código; o que não consigo inferir são as motivações e trade-offs."
Análise de performance: "Esse código vai rodar com [escala: N usuários, M req/s, K registros]. Analise onde estão os bottlenecks mais prováveis — não só os óbvios, mas os que aparecem sob carga real. Para cada bottleneck, estime a ordem de magnitude do impacto e sugira a verificação para confirmar antes de otimizar."
Como praticar
- Manter um log de prompts. Durante uma semana, documente cada instrução que você deu a um LLM para tarefa de engenharia e o resultado (bom, razoável, ruim). Ao final, identifique padrões: quais estruturas de prompt consistentemente produziram bons resultados? Quais produziram output que precisou de muita correção? Esse log é o ponto de partida para um vocabulário de prompts eficazes para o seu contexto específico.
- Exercício de crítica forçada. Tome uma decisão de design que você tomou recentemente e que está satisfeito com ela. Escreva um prompt que pede ao LLM o melhor argumento contra essa decisão (steel-man). Avalie se os argumentos levantados são relevantes para o seu contexto. Se algum for, você identificou uma fraqueza real que pode mitigar. Se nenhum for, sua decisão está mais robustamente justificada do que antes.
- Comparar geração com e sem chain-of-thought. Escolha um problema de design com trade-offs genuínos (escolha de banco de dados, estratégia de cache, abordagem de autenticação). Gere um prompt sem chain-of-thought e um com. Compare os outputs: o com chain-of-thought identificou aspectos relevantes do contexto que o sem chain-of-thought ignorou? O raciocínio visível permitiu identificar um passo com o qual você discorda? Esse exercício calibra quando o overhead de chain-of-thought compensa.
Referências para aprofundar
- paper Chain-of-Thought Prompting Elicits Reasoning in Large Language Models — Wei et al., NeurIPS (2022).
- paper Large Language Models are Zero-Shot Reasoners — Kojima et al., NeurIPS (2022).
- artigo Prompt Engineering Guide — DAIR.AI (2024).
- docs Anthropic Prompt Engineering Documentation — Anthropic (2025).
- artigo Sycophancy in AI: How AI Assistants Learn to Agree — Anthropic Research (2023).
- artigo How to get AI to critique your ideas — Ethan Mollick (2024).
- paper Few-Shot Learners are Better Code Generators — Gao et al. (2023).
- artigo Structured Prompting for Code Generation — Simon Willison (2024).
- livro The Art of Prompt Engineering — Valentina Alto (2024).
- vídeo Prompt Engineering for Developers — Andrew Ng, DeepLearning.AI (2023).
- artigo Beyond the Hype: Practical LLM Prompting for Developers — Hamel Husain (2024).
- paper Constitutional AI: Harmlessness from AI Feedback — Bai et al., Anthropic (2022).