A maioria dos desenvolvedores usa LLMs ao contrário. Descrevemos o que queremos em cinco palavras, recebemos 200 linhas de código e passamos a próxima hora depurando suposições que o modelo fez.
Passei três meses executando o workflow oposto: escrever uma explicação completa do design antes de pedir uma única linha de código. O código resultante tinha menos bugs, mas a verdadeira melhoria foi na minha própria compreensão.
O que é Literate Programming com LLMs?
Donald Knuth cunhou o termo “literate programming” em 1984. A ideia era simples: escrever prosa que explique seu programa e depois entrelaçar código nessa prosa. A maioria dos desenvolvedores ignorou porque manter dois artefatos era tedioso. Os LLMs mudam esse cálculo. A prosa não é mais documentação. É o prompt.
Quando você explica sua abordagem a um LLM antes de pedir código, está fazendo algo diferente do prompt engineering padrão. Você não está otimizando tokens para o modelo. Você está se forçando a articular constraints, edge cases e dependências antes de o modelo alucinar uma arquitetura para você. O LLM se torna um rubber duck que responde, mas apenas depois que você fez o pensamento real.
O Experimento: 30 Dias de Explicar Primeiro
Escolhi 12 tarefas reais do meu backlog, desde um parser de CSV até uma estratégia de reconexão WebSocket. Para cada uma, usei duas abordagens:
- O atalho: Colar a descrição do ticket no LLM e pedir código.
- A explicação: Escrever de 300 a 500 palavras descrevendo o problema, os constraints, a estratégia de tratamento de erros e os test cases. Depois pedir ao LLM para implementar esse design.
Avaliei o output em três critérios: bugs encontrados em code review, tempo até o merge e número de follow-up prompts necessários para corrigir problemas.
Os números foram contundentes. O código explanation-first exigiu uma média de 1.2 follow-up prompts, comparado a 4.7 para a abordagem de atalho. O code review capturou 0.3 bugs por submission explanation-first versus 2.1 bugs para o código de atalho.
A ressalva era o tempo. Escrever a explicação adicionou de 15 a 20 minutos no início de cada tarefa. Se isso vale a pena depende do que você está construindo.
Como Estruturar uma Explicação que Realmente Funciona
Uma boa explicação não é apenas um prompt mais longo. É um design document com uma estrutura específica. Aqui está o template que refinei após a primeira semana:
## Problem
[What are we solving and why?]
## Constraints
[What can't we do? What formats, protocols, or dependencies must we respect?]
## Edge Cases
[What happens when input is null, empty, malformed, or hostile?]
## Error Handling Strategy
[Fail fast or fail graceful? What do we log? What do we return?]
## Test Cases
[The specific inputs and expected outputs that prove this works.]
Aqui está um exemplo real. Precisava de uma função que validasse uma assinatura de webhook usando HMAC-SHA256, mas com uma ressalva: o header de assinatura podia usar dois formatos diferentes dependendo de qual serviço enviou a requisição.
Problem: Validar assinaturas de webhook recebidas. Alguns remetentes usam X-Signature: sha256=<hex> e outros usam X-Signature: <hex> sem o prefixo. Precisamos suportar ambos sem quebrar integrações existentes.
Constraints: Deve usar o module crypto do Node.js. Deve usar comparação em tempo constante para prevenir timing attacks. Deve retornar um boolean claro, nunca lançar em input malformado.
Edge Cases: Header está ausente. Header é string vazia. Header tem prefixo desconhecido como sha512=. String hex tem comprimento ímpar ou contém caracteres não hex.
Error Handling Strategy: Registrar a razão específica da falha no nível debug, mas retornar false para todos os casos inválidos. Nunca vazar detalhes da assinatura em mensagens de erro.
Test Cases:
- Valid
sha256=abc123...matches computed signature. - Valid bare
abc123...matches computed signature. - Missing header returns
false. - Wrong signature returns
false. - Non-hex characters in signature return
false.
Então acrescentei: “Implement this function in TypeScript.”
O modelo retornou isto:
import { createHmac, timingSafeEqual } from 'crypto';
export function validateWebhookSignature(
payload: string,
header: string | undefined,
secret: string
): boolean {
if (!header || header.length === 0) {
return false;
}
const hexPattern = /^[0-9a-fA-F]+$/;
let signatureHex: string;
const prefixMatch = header.match(/^sha256=(.+)$/);
if (prefixMatch) {
signatureHex = prefixMatch[1];
} else {
signatureHex = header;
}
if (!hexPattern.test(signatureHex) || signatureHex.length % 2 !== 0) {
return false;
}
const expected = createHmac('sha256', secret).update(payload).digest();
const actual = Buffer.from(signatureHex, 'hex');
if (actual.length !== expected.length) {
return false;
}
return timingSafeEqual(actual, expected);
}
Isso compilou na primeira tentativa. Mais importante, lidou com cada edge case que especifiquei porque já tinha feito o pensamento. O modelo não precisou adivinhar o que eu queria dizer com “validate”.
Onde Ajuda e Onde Perde Tempo
Essa abordagem brilha para qualquer coisa com branching logic, dependências externas ou implicações de segurança. O validador de webhook acima é um exemplo perfeito. O modelo não pode conhecer seu threat model a menos que você diga.
Mas para boilerplate, é exagero. Não escrevo 500 palavras para gerar um componente de formulário React ou uma migration de banco de dados padrão. Para esses, a abordagem de atalho funciona bem porque os patterns estão bem representados nos training data e os stakes são baixos.
A armadilha real é o meio-termo: tarefas que parecem simples mas não são. Um parser de CSV parece direto até você encontrar campos entre aspas contendo vírgulas, ou marcadores BOM, ou mixed line endings. São essas as tarefas onde pular a explicação custa mais tempo depois.
Um Workflow que Você Pode Roubar Hoje
Você não precisa mudar todo o seu processo. Experimente isso para uma tarefa esta semana:
- Abra seu chat com o LLM e escreva a explicação primeiro. Não mencione código ainda.
- Relia sua explicação. Se não conseguir articular os edge cases, pare. Você não está pronto para escrever código ainda, e definitivamente não está pronto para ter um LLM escrevendo por você.
- Anexe seu pedido de implementação à mesma mensagem.
- Revise o output contra seus próprios test cases, não contra sua intuição.
O passo que a maioria pula é o número dois. Se sua explicação é vaga, o LLM vai espelhar essa vagueza de volta para você na forma de bugs sutis. A explicação é um smoke test para sua própria compreensão.
O Real Benefício Não é o Código
Após 30 dias, notei algo inesperado. O código estava melhor, mas minhas habilidades de design melhoraram mais rápido. Escrever explicações te força a nomear suas suposições explicitamente. Quando faz isso, frequentemente percebe que sua abordagem está errada antes de escrever uma única linha de código.
O LLM ainda está digitando. Mas você está fazendo o pensamento, que sempre foi o bottleneck.
Se quiser experimentar isso você mesmo, comece com seu próximo bug fix. Antes de pedir ajuda a um LLM, escreva dois parágrafos explicando o que acha que está acontecendo e por quê. Você pode resolver antes de terminar de escrever.