Post

Spec-Driven Development: por que planejar importa mais do que nunca com IA

Use especificações evolutivas para orientar a implementação com IA, avaliar arquitetura e custos de manutenção e conectar critérios de aceitação à verificação.

Spec-Driven Development: por que planejar importa mais do que nunca com IA

Agentes de programação com IA podem fazer uma alteração grande antes que um revisor perceba que o requisito foi entendido incorretamente. Uma especificação curta ajuda a antecipar essas divergências, quando mudar a decisão custa menos do que substituir a implementação.

Agentes também podem inspecionar código e fazer perguntas. O problema não é a incapacidade de esclarecer dúvidas; é que um fluxo de trabalho pode recompensar a implementação imediata sem tornar a incerteza visível.

Spec-Driven Development (SDD) dá aos requisitos e critérios de aceitação um papel explícito na implementação e verificação. O rótulo abrange diferentes fluxos de trabalho, não um método universal. O GitHub Spec Kit é um conjunto concreto de ferramentas organizado em especificações, planos e tarefas de implementação.

Este artigo dá continuidade a Harness Engineering: ferramentas de execução precisam de um objetivo claro.

Especifique o comportamento antes de escolher uma implementação

Uma especificação deve registrar as decisões necessárias para avaliar uma alteração. Ela não precisa prever cada linha de código.

Para um bug pequeno, uma reprodução e o resultado esperado podem ser suficientes. Uma alteração em uma API compartilhada merece mais detalhes: compatibilidade, comportamento de erros, requisições concorrentes e expectativas para a entrada em produção.

Quando a tarefa for substancial, separe três artefatos:

  • Especificação: comportamento observável, restrições e exclusões.
  • Plano: implementação proposta e etapas de verificação.
  • Evidências: verificações e observações que mostram o que a implementação realmente faz.

Um bom plano não corrige um requisito incorreto. Passar nas verificações não comprova um comportamento que elas nunca exercitaram.

Exemplo contínuo: especifique os descontos no checkout

O artigo sobre prompts apresenta o pedido. Transforme as decisões de produto confirmadas em uma especificação versionada: pricing é responsável pela elegibilidade e pelo cálculo, web exibe a cotação retornada e orders persiste a cotação aceita. Sem código, o checkout mantém o comportamento atual.

Antes da implementação, resolva por quanto tempo uma cotação é válida, se pode ser reutilizada, qual política de arredondamento se aplica e o que ocorre quando a elegibilidade muda antes do envio. Os casos abaixo pressupõem que a equipe decidiu que uma cotação expirada exige uma nova cotação e a confirmação do cliente.

Caso de aceitação Configuração e ação Evidência independente
Código válido Prepare um carrinho e um código elegíveis; faça a cotação e envie Total exibido e valor persistido correspondem ao esperado definido em dados de teste aprovados pela pessoa revisora
Código expirado Prepare um código expirado em um horário controlado pelo relógio de teste O erro acordado aparece; nenhum pedido com desconto é criado
Sem código Envie os dados de teste de checkout existentes Total e comportamento existentes permanecem iguais
Cotação expira antes do envio Avance o relógio de teste além do prazo de validade acordado O envio solicita nova cotação e confirmação; nenhum pedido usa a cotação expirada
Divergência de valores entre serviços Exercite em conjunto as revisões candidatas de web, orders e pricing A UI, a resposta da API e a asserção no banco identificam a mesma cotação e o mesmo valor

Os valores esperados devem vir das regras e dados de teste acordados. Copiar o cálculo da aplicação para o resultado esperado do teste pode reproduzir o mesmo erro. Registre um ID estável para cada caso na especificação, no teste e no relatório da execução para que os revisores possam rastrear cada requisito até sua evidência.

Exemplo: trate eventos duplicados de notificação

“Envie uma notificação quando um pedido for enviado” deixa sem definição a semântica de entrega. A seguir está uma especificação mais fácil de revisar para um processo ilustrativo que recebe eventos:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# Processo de notificação de envio

## Objetivo
Envie uma notificação para cada evento de envio, inclusive quando o sistema de mensagens
entregar o evento novamente.

## Entrada e suposições
- A entrada contém event_id, order_id, recipient e shipped_at.
- O sistema que publica o evento preserva event_id em uma nova entrega.
- Valide a entrada conforme o esquema de evento versionado antes de enviar.
- O provedor de notificações aceita uma chave de idempotência e a mantém
  por pelo menos 7 dias. Verifique essa garantia antes da implementação.

## Comportamento
- Use event_id como chave de idempotência do provedor em todas as tentativas.
- Persista a entrega bem-sucedida antes de confirmar a mensagem ao sistema de mensagens.
- Uma nova entrega já marcada como bem-sucedida é confirmada sem novo envio.
- Entregas concorrentes do mesmo evento devem usar a mesma chave de idempotência.
- Tente novamente falhas transitórias com intervalos crescentes, por no máximo 24 horas.
- Encaminhe eventos inválidos e retries esgotados para uma fila de eventos que não puderam ser processados.
- Não reproduza eventos mais antigos que a janela de deduplicação do provedor
  sem uma decisão explícita de uma pessoa operadora.

## Casos de aceitação
- A primeira entrega envia e registra o sucesso.
- Uma nova entrega depois do sucesso não envia outra notificação.
- Entregas concorrentes não geram envios duplicados ao provedor.
- Um crash após o sucesso do provedor, mas antes da persistência local, repete
  usando a mesma chave e não duplica a notificação.
- Eventos malformados nunca chegam ao provedor.

## Fora do escopo
Alterar modelos de mensagem, adicionar canais e criar ferramentas para reprocessar eventos em lote.

A garantia do provedor é uma dependência a verificar, não um fato estabelecido por tê-la registrado. Se o provedor não conseguir deduplicar requisições, a falha entre o envio e o registro de sucesso se torna uma concessão de produto: notificações duplicadas possíveis ou notificações possivelmente perdidas. Resolva essa decisão antes de prometer uma notificação por evento.

Uma especificação é valiosa quando expõe essa incerteza. Associe a garantia de entrega a testes de falha explícitos:

Caso de aceitação Falha ou configuração Asserção
Nova entrega após sucesso Entregue o mesmo evento duas vezes Um efeito no provedor; sucesso continua registrado; segunda entrega confirmada
Entregas concorrentes Libere dois processos de trabalho para o mesmo evento usando uma barreira Ambos usam a mesma chave; o serviço de teste que simula o provedor com deduplicação registra um efeito
Falha após sucesso do provedor Encerre o processo de trabalho após a aceitação pelo provedor e antes da persistência local; entregue o evento novamente A nova tentativa usa a chave original; um efeito no provedor; sucesso acaba persistido
Evento inválido Forneça um evento que viole o schema Nenhuma chamada ao provedor; resultado de dead-letter registrado

Configure o serviço de teste que simula o provedor para contar efeitos separadamente das requisições: uma nova tentativa pode fazer outra requisição sem gerar outra notificação. Esses testes verificam o processo que recebe eventos em relação ao contrato simulado do provedor; confirmar a garantia do provedor real continua sendo uma evidência separada.

Para testes de aceitação executáveis, faça referência também a um roteiro de execução versionado com os comandos exatos de compilação e implantação, serviços necessários, verificações de prontidão, URLs da aplicação, perfis de teste e asserções no banco de dados. Registre referências de credenciais e métodos de provisionamento em vez dos valores das senhas. O artigo sobre Loop Engineering mostra como fornecer aos agentes acesso a testes de ponta a ponta com credenciais de escopo limitado e ambientes isolados.

Planeje com base no repositório real

Peça ao agente que localize o processo que recebe eventos, a camada de persistência e os dados de teste antes de propor mudanças. Um plano útil nomeia os componentes afetados, descreve o tratamento de falhas e associa casos de aceitação a verificações.

Se a ferramenta oferecer um modo de planejamento, confirme suas permissões em vez de presumir que o rótulo signifique execução somente para leitura. Uma instrução escrita para evitar edições não é o mesmo que uma restrição de acesso a arquivos imposta.

A profundidade da revisão deve acompanhar o impacto. Uma correção local de formatação raramente precisa de uma etapa de aprovação separada. Uma alteração na semântica de entrega ou em um contrato compartilhado se beneficia de revisão antes da implementação.

Gerar código inicial rapidamente pode aumentar o custo de manutenção

Um agente pode gerar rapidamente interfaces, adapters, command handlers, mapeamentos e testes. Isso facilita implementar um padrão, mas não demonstra que ele seja apropriado. Cada abstração passa a fazer parte do sistema que futuras pessoas desenvolvedoras e agentes terão de entender e modificar.

Até o mesmo modelo pode precisar redescobrir essa estrutura em uma sessão posterior. A conversa original pode estar indisponível ou incompleta, e a implementação pode ter evoluído. Contratos duráveis, testes e registros de decisão ajudam; ter gerado o código uma vez não elimina o custo de entendê-lo novamente.

Revise a implementação do padrão

O nome de um padrão, por si só, não revela seu custo. Estas são concessões de design a examinar, não uma classificação de arquiteturas por eficiência de IA:

Escolha Quando o limite se justifica Problema a investigar
Clean Architecture ou ports and adapters As regras de domínio precisam de isolamento de transporte, persistência ou sistemas externos Camadas que apenas repassam chamadas e mapeamentos repetidos que exigem edições coordenadas sem isolar mudanças relevantes
CQRS Os modelos de leitura e escrita têm responsabilidades ou requisitos substancialmente diferentes Aplicar modelos separados a um CRUD simples e adicionar infraestrutura de projeção sem necessidade demonstrada
Event sourcing O histórico de eventos e os requisitos de reconstrução justificam o modelo Tratar a evolução de eventos, o replay e a recuperação de projeções como gratuitos porque foi fácil gerar handlers
Interfaces e injeção de dependência Um limite real precisa de substituição ou testes independentes Uma interface, factory e registro para cada tipo concreto, independentemente das necessidades de quem usa o componente

Para saber mais sobre avaliação de concessões arquiteturais e registro de decisões quando não há uma prática universalmente melhor, consulte o capítulo 1 de Software Architecture: The Hard Parts.

Clean Architecture enfatiza a direção das dependências e a separação de responsabilidades; não prescreve a mesma quantidade de camadas para toda aplicação. Consulte a explicação original de Robert C. Martin.

CQRS separa modelos de comando e consulta. Isso não exige por si só event sourcing, bancos de dados separados ou mensagens assíncronas. Essas escolhas adicionais têm seus próprios custos. A discussão de CQRS por Martin Fowler também alerta contra sua aplicação quando a complexidade não se justifica.

Teste a próxima alteração, não apenas a geração inicial

Considere o pedido ilustrativo para adicionar uma observação opcional de entrega a um pedido. Em um design, a mudança pertence à validação da requisição, ao modelo de pedido, à persistência e aos testes. Uma implementação desnecessariamente elaborada talvez também exija um objeto de comando, handler, mapper, registro de interface, schema de evento e projeção de leitura. Esta é uma comparação hipotética, não um benchmark nem uma lista de componentes exigidos por CQRS.

A estrutura extra pode aumentar o trabalho para localizar arquivos afetados, manter representações alinhadas, recriar dados de teste e diagnosticar atualizações parciais. Mais contexto recuperado e tentativas repetidas de correção também podem aumentar o uso do modelo. Por outro lado, um limite bem escolhido pode conter a mudança e reduzir esse trabalho. Menos arquivos não é automaticamente melhor se isso significar duplicar regras ou espalhar dependências pela aplicação.

Antes de aceitar uma adição arquitetural substancial, experimente uma alteração futura representativa em um protótipo isolado. Compare alternativas com comportamento e requisitos de verificação equivalentes. Registre a configuração do modelo e das ferramentas, repita as tarefas quando viável e examine:

  • Se a alteração está correta, incluindo outros sistemas que usam o componente e não foram considerados e regressões.
  • Arquivos inspecionados e alterados, chamadas de ferramentas e tentativas de correção.
  • Uso de entrada e saída, uso de cache, custo cobrado e tempo decorrido.
  • Esforço de revisão humana e qualquer trabalho adicional de implantação ou recuperação.

Isso mede a manutenção de forma mais direta do que a rapidez com que um agente produz o primeiro código gerado. Não remova limites úteis de um sistema existente apenas para reduzir a contagem de tokens; considere o risco de migração e os requisitos atendidos por esses limites.

Inclua contenção arquitetural no plano

Acrescente um requisito de revisão como este à especificação de uma funcionalidade substancial:

1
2
3
4
5
6
7
Siga a arquitetura existente e as convenções da linguagem-alvo.
Antes de introduzir uma nova camada, interface, fila ou modelo de leitura:
- Identifique o requisito concreto que ela atende.
- Compare-a com a implementação compatível mais simples.
- Rastreie uma provável alteração futura nos dois designs.
- Explique consequências para testes, entrada em produção e manutenção contínua.
Registre a decisão e as condições que justificariam revisitá-la.

A IA pode ajudar a explorar e questionar essas alternativas. A decisão de aceitação deve depender das restrições reais do sistema e da capacidade da equipe de mantê-lo, inclusive em futuras sessões com agentes.

Mantenha o processo iterativo

Especificações são compatíveis com desenvolvimento Agile. Os princípios Agile valorizam explicitamente excelência técnica, entregas frequentes e resposta a requisitos que mudam.

Uma especificação pequena pode evoluir quando um protótipo ou teste revela informações ausentes. Evite tratar a tarefa como uma “microcascata” em sentido único, na qual uma suposição inicial não pode ser revisitada.

flowchart TD
    accTitle: Feedback entre especificação e implementação
    accDescr: Especifique o comportamento, planeje, revise decisões, implemente um incremento e verifique os casos de aceitação. Defeitos retornam à implementação; lacunas nos requisitos voltam à especificação. Entregue quando as evidências derem suporte à mudança.
    S["Especificar o comportamento"] --> P["Planejar com base no código"]
    P --> R["Revisar decisões relevantes"]
    R --> I["Implementar um pequeno incremento"]
    I --> V["Verificar casos de aceitação"]
    V -- "Defeito na implementação" --> I
    V -- "Lacuna no requisito" --> S
    V -- "Evidências dão suporte à mudança" --> D["Entregar e observar"]

Mantenha a especificação junto ao código quando ela registrar um contrato contínuo. Atualize ambos quando o comportamento mudar e preserve o motivo de decisões relevantes.

Mantenha prompts reutilizados alinhados à intenção

Em Structured-Prompt-Driven Development, Wei Zhang e Jessie Jie Xia tratam prompts reutilizáveis como artefatos versionados da equipe e os mantêm explicitamente sincronizados com o código. Adote essa prática quando um prompt orientar mudanças futuras; um pedido descartável não precisa da própria especificação permanente.

Para o checkout, uma mudança de requisito relativa à expiração da cotação deve atualizar a especificação da funcionalidade, o prompt reutilizável de implementação e os casos de aceitação na mesma revisão. Uma refatoração que mude a localização do cliente de pricing deve atualizar as referências mantidas a ele. Preserve o motivo de uma decisão de comportamento, não a transcrição de cada tentativa de geração.

Sincronizar não pode transformar um erro de implementação em requisito. Se o código aceitar cotações expiradas, compare-o com a política acordada antes de mudar a especificação para corresponder ao código. Registre mudanças deliberadas de política separadamente de correções. Prefira links para contratos e testes autoritativos a cópias de detalhes que ficarão desatualizadas.

Restrinja o trabalho repetido com vocabulário de domínio

Em DSLs Enable Reliable Use of LLMs, Unmesh Joshi descreve o uso de pequenas linguagens de domínio e validadores para restringir as escolhas do modelo. A distinção útil está entre descobrir um modelo de domínio por experimentação e gerar novos casos dentro de um modelo estabelecido.

Se cenários de checkout se repetirem, uma equipe poderia definir este vocabulário ilustrativo de testes:

1
2
3
4
5
6
scenario: expired-quote
given: eligible-cart-with-valid-quote
when: advance-clock-past-quote-expiry-and-submit
expect:
  - requote-required
  - no-order-created

Este YAML não é um teste executável por si só. Um executor mantido pela equipe precisa validar as etapas permitidas, rejeitar nomes desconhecidos e implementar suas semânticas. Dados de teste independentes devem estabelecer o comportamento esperado. Um analisador aceitar o cenário não prova que o executor ou a aplicação estejam corretos.

Comece por helpers de teste existentes ou APIs de domínio tipadas. Crie uma linguagem personalizada apenas quando cenários repetidos justificarem manter seu vocabulário, validador e mecanismo de execução. Mantenha aberta à experimentação a descoberta de comportamentos de produto ainda desconhecidos antes de codificá-los como uma operação fixa.

Invista planejamento onde ele reduz a incerteza

Nem toda falha vem de requisitos pouco especificados. Contexto ausente, uso incorreto de ferramentas, falhas de dependência e erros de implementação também importam. Planejar tem um custo; use o planejamento para responder a perguntas concretas em vez de produzir documentação para cada edição.

O resultado útil é uma mudança cujo comportamento, suposições e verificação estejam claros para o revisor. O artigo final conecta essa especificação à execução: Loop Engineering.

Esta postagem está licenciada sob CC BY 4.0 pelo autor.