Pular para o conteúdo
Fantástico Mundo de Jon
RSS

O que sobra de um PRD quando ele precisa caber numa mensagem de chat

Em novembro de 2024 nada abre o meu repositório, então o PRD tem que caber numa mensagem e voltar inteiro a cada turno. É isso que força a decisão que ninguém tomava: o que ali vira código e o que é só alinhamento entre pessoas.

Com pressa? Peça o TL;DR ao Claude — ele lê a página e resume.

O PRD do módulo de agendamento tinha quatro páginas e estava bom. Contexto do produto, duas personas com nome e dor, objetivos numerados, requisitos funcionais, métricas de sucesso e uma seção honesta de alternativas consideradas. Passou por três pessoas antes de chegar em mim e ninguém pediu mudança.

Colei ele inteiro no chat e pedi o cancelamento de agendamento. O que voltou apagava a linha do banco.

A regra estava escrita. Terceira página, português claro: cancelar não apaga o registro, porque histórico clínico não se apaga. O modelo leu essa frase junto com as outras todas e obedeceu a uma diferente, a da primeira página, que conta que a recepção perde a manhã inteira remarcando consulta por telefone. Voltou um cancelamento enxuto, rápido, com a rota já devolvendo o horário para a agenda, e um DELETE FROM agendamentos WHERE id = $1 no meio do serviço.

Ele atendeu a persona e passou por cima da regra.

Vale situar onde isso acontece, porque daqui a um ano a frase vai soar estranha: em novembro de 2024 nenhuma ferramenta que eu uso abre o meu repositório, lê os arquivos e decide o que mudar. O Cursor edita o arquivo aberto quando eu aceito a sugestão. O Aider aplica o patch que eu mandei aplicar. O Copilot completa a linha. Para começar um módulo do zero, o fluxo continua sendo eu descrevendo, o modelo devolvendo um bloco e eu colando no editor. Já escrevi sobre esse laço em outro post, inclusive sobre pedir o contrato antes do código e sobre o bloco de estado que eu carrego no topo de toda mensagem.

Este aqui é sobre o documento que existe antes disso tudo. Aquele que já estava pronto quando eu abri a aba, escrito para gente ler.

Como isso foi feito · novembro de 2024

Tarefa
módulo de agendamento de uma clínica
Documento
PRD de quatro páginas, escrito para pessoas
Ferramenta
ChatGPT Plus (GPT-4o) e Claude 3.5 Sonnet, no navegador
Método
copiar e colar; nada aqui lê o meu disco
Janela
128 mil tokens no GPT-4o; o documento cabe com folga

O que mais voltou errado veio da seção de métricas

O DELETE foi o erro grande e o mais fácil de explicar. O segundo demorei para entender.

A última seção do PRD tem uma métrica de adoção: 60% dos cancelamentos feitos pelo portal em três meses. É uma frase para o time de produto saber, daqui a um trimestre, se a entrega valeu a pena. Ela não descreve comportamento de sistema nenhum.

Na resposta, virou código. Voltou uma coluna origem na tabela de agendamentos, com os valores portal e recepcao, e uma rota GET /agendamentos/metricas devolvendo a proporção entre os dois. Nada disso estava nos requisitos funcionais. Estava na métrica, e o modelo fez a única coisa que dá para fazer com uma métrica dentro de um pedido de implementação: implementou ela.

O incômodo é que a decisão nem é ruim. Se alguém for medir aquilo de verdade um dia, vai precisar mais ou menos daquela coluna. É uma decisão de produto tomada por quem não tem contexto para tomar — e escondida no meio de um diff que eu pedi para outra coisa. Numa quinta-feira ruim, aquela coluna entra na migration e vira legado.

E ainda teve um terceiro, que é o que eu acho mais difícil de defender para alguém.

O PRD registrava, com toda a honestidade, que a equipe pensou em permitir cancelamento só até 24 horas antes da consulta e descartou a ideia, porque a equipe clínica achou o limite rígido demais para um primeiro momento. Está escrito lá que foi descartado, com essas palavras. Voltou implementado: uma validação de antecedência mínima de 24 horas, com mensagem de erro e teste em volta.

O leitor humano pula parágrafo, o modelo não

Quando alguém me dá quatro páginas para ler, eu não leio quatro páginas. Passo o olho, decido em dois segundos que a seção de personas não muda nada do que eu vou digitar hoje, e vou direto para os requisitos. Pular é uma habilidade tão automática que a gente nem chama de habilidade.

E é exatamente ela que faz um PRD funcionar. O documento consegue servir cinco leitores diferentes justamente porque cada um pega a sua parte e ignora o resto sem nem perceber que ignorou.

Colado num prompt, o documento perde o leitor que sabia pular.

Não vou fingir que sei descrever o que acontece dentro do modelo. O que dá para observar é o efeito: cada frase do texto entra na conta de “o que essa pessoa quer que eu faça”. “A recepção perde a manhã remarcando por telefone” e “cancelar não apaga o registro” chegam do mesmo jeito, uma atrás da outra, sem nada marcando qual das duas decide o comportamento do sistema. Num documento lido por gente, o parágrafo motivacional é inofensivo: custa dois segundos de quem lê e às vezes ganha o apoio de alguém numa reunião. Num prompt, ele não é inofensivo. É texto disputando espaço com a linha que decide se o registro vai ser apagado.

E disputa em vantagem, o que é a parte perversa. Repare na assimetria de um PRD bem escrito: a informação mais importante para o negócio aparece três vezes, na introdução, no objetivo e na persona, cada vez com palavras um pouco diferentes. A informação mais importante para o código aparece uma vez só, no meio de uma lista, entre vírgulas, numa oração subordinada.

O documento não está errado. Ele está com as proporções do leitor certo, que não é esse.

O que fica e o que sai

O critério que eu uso é bruto e funciona: fica o que decide o que o código faz, sai o que explica por que alguém quis o código.

trecho vai no bloco por quê
cancelar grava canceladoEm sim decide o que o código faz
duração vem do procedimento sim decide um campo e uma consulta
dataHora em ISO, banco em UTC sim formato e unidade
não tocar no cadastro de pacientes sim fronteira do diff
cancelar consulta em andamento → 409 sim caso de borda verificável
a recepção remarca tudo por telefone não justifica a entrega, não muda o código
Ana, recepcionista, 34 anos não persona
60% pelo portal em três meses não métrica de adoção
limite de 24h, descartado não vira requisito se ficar
pedido do cliente na reunião de outubro não histórico da decisão

Do lado que fica há cinco tipos de frase, e vale nomear os cinco porque a lista cabe na cabeça: a regra que decide comportamento, o dado com nome e tipo, o formato de entrada e saída, o que não fazer, e o critério que dá para verificar. Se a frase não é uma dessas cinco coisas, ela está no documento para convencer alguém, e convencer é um trabalho que já foi feito quando o PRD foi aprovado.

Do lado que sai, a que mais dói cortar é a justificativa. Ela parece útil, parece que ajuda o modelo a “entender o problema”, e é a mais fácil de defender numa discussão. Só que a defesa é sempre a mesma frase vaga sobre contexto ajudar, e eu nunca consegui apontar uma linha de código que ela mudou para melhor.

Quando eu fico na dúvida sobre um parágrafo, faço o teste burro: apago ele, mando o pedido numa aba nova e comparo com o que tinha voltado antes. Se o código é o mesmo, o parágrafo não era do prompt, era do documento. Leva dois minutos e resolve praticamente toda discussão sobre o que cortar, inclusive as que eu tenho comigo mesmo.

Um caso merece regra própria, que é a alternativa descartada. Ela não pode simplesmente sumir, senão daqui a duas semanas alguém propõe a mesma coisa de novo. O que ela não pode é ir para o prompt contando a história da reunião. Se eu quero que o limite de 24 horas fique de fora, isso vira uma linha na seção do que não fazer: sem limite de antecedência para cancelar nesta entrega. Uma ordem curta, sem a reunião em volta — e a reunião fica no PRD, onde ela serve para alguém.

A mesma seção, antes e depois

Isto é a seção de cancelamento do PRD como ela existe no documento, e eu diria que está bem escrita para o que foi feita. Uma pessoa lê e entende o problema, a restrição e o que está em jogo.

## 3.2 Cancelamento

Cancelamentos são hoje o principal ponto de atrito da recepção. Segundo a Ana
(recepcionista, persona 1), boa parte da agenda do dia é remarcada por telefone,
e cada ligação come um tempo que ela não tem. O objetivo desta entrega é permitir
que o próprio paciente cancele pelo portal, reduzindo o volume de ligações e
liberando a recepção para o atendimento presencial.

Consideramos permitir o cancelamento apenas até 24h antes da consulta e
descartamos: a equipe clínica achou o limite rígido demais neste primeiro
momento. Podemos revisitar depois do piloto.

O paciente deve conseguir cancelar um agendamento futuro pelo portal. O horário
cancelado volta a ficar disponível para outros pacientes. O cancelamento não
apaga o registro, já que o histórico clínico precisa ser preservado.

Métrica de sucesso: 60% dos cancelamentos feitos pelo portal em três meses.

E isto é o que eu colo no chat no lugar dele.

CANCELAMENTO

- cancelar = gravar canceladoEm (timestamp UTC). Nunca DELETE.
- agendamento com canceladoEm preenchido não aparece na agenda do dia
  nem na lista do paciente, e continua existindo para consulta por id
- o horário volta para a lista de disponíveis na mesma requisição
- cancela: o paciente dono do agendamento ou qualquer conta da recepção.
  Outro paciente → 403
- consulta já iniciada (dataHora menor que agora) → 409, não cancela
- consulta já cancelada → 409, não é erro de servidor
- sem limite de antecedência nesta entrega
- resposta 200 com o agendamento inteiro e canceladoEm preenchido

não fazer: não mexer no cadastro de pacientes, não criar tabela de
histórico, não adicionar notificação por e-mail nesta tarefa

O documento encolheu, e é fácil olhar para os dois blocos e concluir que o segundo é um resumo do primeiro. Não é, e essa é a parte que eu mais demorei a entender.

Cortar é mais difícil que resumir

Um resumo preserva as proporções. Encurta tudo na mesma medida, inclusive a regra, que é justamente a frase que não pode perder precisão. Peça o resumo do PRD acima e é bem provável que “o cancelamento não apaga o registro” volte como “cancelamento preserva o histórico”, que soa igual, é mais curto e não diz o que gravar.

O corte é o contrário disso. Umas frases ficam inteiras, palavra por palavra, e outras somem por completo. Não existe meia frase no bloco.

E tem um efeito colateral que sozinho já pagaria o exercício: metade das linhas do bloco de cima não estava no PRD. “O histórico clínico precisa ser preservado” diz o que não pode acontecer e não diz o que gravar no lugar. Para escrever a linha do canceladoEm eu tive que decidir uma coisa que ninguém tinha decidido. O mesmo com o paciente que tenta cancelar o agendamento de outro, que virou o 403, e com o segundo cancelamento da mesma consulta, que virou 409 em vez de estourar no banco.

Nenhum desses buracos estava escondido. Eles estavam à vista, no documento aprovado por três pessoas, e ninguém viu porque leitor humano preenche buraco sem perceber que preencheu. Eu li “cancelar um agendamento futuro” e completei sozinho, em silêncio, com o comportamento óbvio para mim. O modelo também completa, só que ele completa com o comportamento mais frequente da internet, e em clínica o mais frequente da internet costuma ser o errado.

Reescrever o PRD para uma máquina que não sabe pular acabou virando a revisão mais rigorosa que aquele documento recebeu. Isso não é uma vantagem que eu tinha previsto.

O documento volta inteiro em toda mensagem

Aqui entra o detalhe que muda a economia da coisa, e ele vem da mecânica do chat, antes de qualquer coisa que o modelo faça. A conversa não tem memória. Cada mensagem que eu mando leva junto tudo que já foi dito antes, e o documento colado no primeiro turno é reenviado no segundo, no sétimo e no décimo quinto.

Dá para transformar isso em dinheiro, e um dia eu faço essa conta direito. Não é o que me incomoda aqui.

O que incomoda é qualitativo. Cada parágrafo que não vira código é pago em atenção, toda vez, e a conversa vai ficando mais pesada exatamente na direção errada: o pedido de agora está no fim do prompt, e a regra do canceladoEm vai afundando na terceira página de uma mensagem enviada há dez turnos. No outro post eu já tinha reparado nisso pelo lado do bloco de estado, que também vai perdendo força conforme a thread se estica. Com quatro páginas na frente, o efeito chega mais cedo.

E a thread se estica mais rápido, porque o documento inteiro produz mais resposta errada, e cada resposta errada custa uma rodada de correção, e cada rodada de correção reenvia tudo de novo. É um laço que se alimenta.

Tem ainda um efeito de segunda ordem, que é o mais bobo e o que mais me pegou na prática. Bloco curto eu recolo. Quando o modelo começa a derrapar no turno oito, eu seleciono aquelas quinze linhas, colo de novo no topo do pedido e sigo. Quatro páginas eu não recolo, porque dá preguiça, porque quebra o ritmo, porque parece exagero. Então o documento longo é o que está presente menos vezes na conversa, mesmo tendo sido colado uma vez a mais no começo. O curto ganha por comparecer.

As quatro páginas continuam existindo

Nada do que eu cortei foi jogado fora, e essa é a parte que costuma ser mal entendida.

O PRD continua no lugar dele, com as personas, a métrica e a alternativa descartada. Ele responde perguntas que o bloco não responde e nunca vai responder: por que estamos fazendo isso, para quem, o que decidimos não fazer e por quê, como vamos saber se deu certo. Quem herdar esse módulo daqui a um ano vai precisar exatamente dessas quatro páginas.

São dois documentos com dois leitores. Um deles é derivado do outro, e a direção da derivação importa: o PRD vem primeiro, e o bloco é o que sobra dele depois do corte. Nunca o contrário: um bloco de quinze linhas não gera a discussão de produto que produziu ele.

Na prática, o bloco virou um arquivo de texto no repositório, ao lado do código, versionado junto. Ele não é lido por nada além de mim: eu abro, seleciono, copio e colo na aba do navegador. É um trabalho manual e meio ridículo de descrever em voz alta, mas a alternativa é reescrever de memória a mesma coisa toda vez que eu abro uma thread nova. No dia em que alguma ferramenta abrir esse arquivo sozinha, o trabalho de mantê-lo já vai estar feito.

Quem escreve o bloco, aliás, sou eu, não quem escreveu o PRD. O corte é uma decisão técnica: exige saber o que vira campo, o que vira validação e o que vira nada. Pedir para o time de produto entregar o PRD “já no formato do prompt” é transferir para a pessoa errada uma decisão que ela não tem como tomar.

Onde eu não faço isso

O corte custa tempo e o custo não some quando a tarefa é pequena. Não escrevo bloco nenhum para renomear um campo, ajustar uma mensagem de erro ou subir uma versão de dependência: o bloco demora mais que a tarefa.

Também não escrevo quando estou explorando. Se eu ainda não sei o que quero, o bloco é justamente aquilo que eu estou tentando descobrir, e aí o pedido vago é a ferramenta certa. Peço duas ou três abordagens, escolho uma, e o bloco nasce depois, da que eu escolhi.

E não escrevo para protótipo que morre na sexta-feira. Se o código não vai ser mantido, decisão silenciosa do modelo não custa nada: não existe ninguém para pagar a conta depois.

Sobra o caso do meio, que é onde mora quase todo trabalho de verdade: código que outra pessoa vai manter, inclusive eu daqui a três meses, ou erro que não aparece na hora. O DELETE do começo deste post é dos dois tipos ao mesmo tempo.

O que o corte não conserta

O modelo não fica mais inteligente porque eu apaguei três páginas. Ele só para de gastar atenção com texto que não vira código. É uma diferença pequena de enunciar e grande na prática, porque explica o que continua quebrando depois do corte.

Se eu escrever a regra errada no bloco, ela volta implementada com precisão. O erro deixa de ser do modelo e passa a ser meu, o que é melhor, porque um erro meu está escrito num arquivo que eu posso reler. Não é mágica: o bug só troca de lugar, e o lugar novo é mais fácil de vigiar.

E há uma classe inteira de problema que o corte não toca. No outro post eu mostrei um middleware de autenticação que usava jwt.decode no lugar de jwt.verify, ou seja, que aceitava qualquer token forjado com cara de token válido. Nenhum documento meu contém uma frase sobre isso, e nenhum corte de PRD faria essa frase aparecer. O problema ali não era o que estava no prompt: era o modelo completando com o padrão mais comum da internet num lugar onde o padrão mais comum é inseguro.

Falta medir o que interessa, e eu vou dizer o que é para não empurrar opinião com cara de conta: quantas rodadas até o diff entrar sem retoque, com o PRD inteiro e com o bloco cortado, mesma tarefa, thread nova a cada tentativa, e a contagem feita na mão. Enquanto esse número não existir, tudo que eu afirmei aqui sobre ganho é observação minha, e é assim que você deve ler. Tenho o DELETE, tenho a coluna origem, tenho a validação de 24 horas que ninguém pediu. Não tenho a tabela.

O que eu não pretendo mais fazer, com número ou sem número, é colar documento inteiro achando que o modelo vai separar o que interessa. Ele não separa e não tem como avisar que não separou. A resposta volta plausível, bem formatada e obediente a alguma frase que estava lá.

Todo parágrafo que eu não cortar, o modelo vai levar a sério. Inclusive o que eu escrevi só para convencer alguém numa reunião.