▶ Slides ← Módulo
Módulo 11 · Engenharia de Software · Computação 2

Spec-Driven Development

Aula 1 — Material de Leitura · Prof. Afonso Brandão · 04/08/2026

Sobre este encontro

Spec-Driven Development · 04/08/2026 · Prof. Afonso

Objetivo de aprendizagem

Ao final do encontro, o estudante deve ser capaz de escrever uma especificação executável com anatomia clara, rastreada em ADRs, que orienta a implementação, os testes e a geração de código assistida por modelos generativos.

Estratégia do encontro

Exposição em quatro blocos com construção incremental de uma especificação de referência; o encerramento é uma demonstração conduzida pelo professor sobre um sistema de biblioteca, e a aplicação ao projeto do grupo ocorre após a aula, conforme roteiro do material.

Estrutura do encontro

  1. Bloco 1 (30 min) — Por que SDD: problema, motivação, SSoT, requisito funcional e não funcional, qualidade ISO/IEC 25010
  2. Bloco 2 (35 min) — Anatomia da especificação: seis seções, ADR e rastreabilidade, pipeline de elicitação
  3. Bloco 3 (30 min) — Modelagem como código: OpenAPI e Gherkin; entidade-relacionamento, classes e sequência em Mermaid
  4. Bloco 4 (25 min) — Especificação e IA: verificação iterativa e demonstração ao vivo

1. Por que especificar antes

Iniciar a implementação sem uma definição formal do comportamento esperado produz um conjunto previsível de consequências: retrabalho, contratos de API inconsistentes entre times, desalinhamento com quem pediu o sistema, testes que cobrem o que foi construído em vez do que era exigido e dívida técnica acumulada por decisões nunca registradas.

O ponto não é falta de capacidade técnica. É que a ambiguidade só se manifesta quando o código já existe, e nesse momento corrigi-la custa muito mais do que teria custado resolvê-la por escrito.

Spec-Driven Development (SDD) é a metodologia na qual a especificação técnica é a única fonte de verdadeSingle Source of Truth, SSoT. O código deriva da especificação, os testes a verificam, a documentação a reflete e a geração assistida por inteligência artificial opera dentro dos seus limites.

Erro comum

Tratar a especificação como documentação escrita depois da entrega. Se ela não precede o código, ela não governa nada — apenas descreve.

2. Vocabulário: SSoT, requisito funcional e não funcional

Single Source of Truth

Princípio de arquitetura da informação segundo o qual cada informação relevante reside em um único artefato autoritário, do qual todas as demais representações derivam. Quando duas versões divergem, a fonte única prevalece: as cópias são reconciliadas com ela, jamais o inverso.

Requisito funcional (RF)

Descreve o que o sistema deve fazer — funções e comportamentos observáveis. Exemplo: "O sistema deve permitir o registro de usuário a partir de nome e endereço de e-mail."

Requisito não funcional (RNF)

Descreve como o sistema deve se comportar — qualidade, desempenho, segurança, operação. Exemplo: "O registro deve concluir em no máximo 2 segundos sob carga nominal."

Critério de aceitação de um RNF

Um requisito não funcional sem número, janela de medição e instrumento não é requisito — é desejo. "Deve ser rápido" não é verificável; "p95 ≤ 200 ms medido em cache frio, com falha declarada acima disso" é.

SDD e TDD não competem

TDD (Test-Driven Development) escreve testes antes do código e fixa a correção interna da implementação. SDD descreve interfaces, contratos e metas antes, e é o que fundamenta o TDD. A especificação define a verdade; o TDD a implementa. Um projeto pode ter TDD sem SDD — e nesse caso testa corretamente a coisa errada.

3. Qualidade não funcional: ISO/IEC 25010:2023

A norma ISO/IEC 25010 define um modelo de qualidade de produto de software com nove características. Sua função em uma especificação é impedir que requisitos de qualidade permaneçam implícitos ou subjetivos: para cada característica pertinente ao componente, atribui-se uma métrica, um limite e um instrumento.

CaracterísticaO que cobre
Adequação funcionalCorreção, completude e pertinência das funções entregues.
Eficiência de desempenhoTempo de resposta, uso de recursos e capacidade.
CompatibilidadeCoexistência e interoperabilidade com outros sistemas.
Interação (usabilidade)Apreensibilidade, operabilidade e proteção contra erro do usuário.
ConfiabilidadeMaturidade, disponibilidade, tolerância a falhas e recuperabilidade.
SegurançaConfidencialidade, integridade, não repúdio e autenticidade.
ManutenibilidadeModularidade, reusabilidade, testabilidade e modificabilidade.
FlexibilidadeAdaptabilidade, escalabilidade e instalabilidade.
Proteção (safety)Operação em estado seguro, alerta de risco e recuperação controlada.
Aplicação seletiva

Nem toda característica se aplica a todo componente. Interação é irrelevante em um pipeline de dados sem interface; proteção é decisiva em sistema embarcado. Declare explicitamente as descartadas — a omissão silenciosa é indistinguível do esquecimento.

4. Anatomia de uma especificação

Uma especificação madura tem seis seções. A ordem importa: cada uma restringe a seguinte.

Seção 1 — Visão e escopo

Por que o sistema existe, quem são os usuários e qual problema resolve. Inclui o que está fora de escopo. Exemplo: "Sistema de cotação que reduz o tempo de resposta ao cliente de 24 h para menos de 5 min."

Seção 2 — Requisitos funcionais

Numerados (RF-001, RF-002) para que testes e código possam citá-los. Cada um declara descrição, pré-condições e pós-condições. Exemplo: "RF-001: usuário autenticado cria cotação. Pré: sessão válida. Pós: quoteId gerado e persistido."

Seção 3 — Requisitos não funcionais

Numerados (RNF-001), cada um com métrica, limite numérico e instrumento de verificação. Exemplo: "RNF-001: latência p95 ≤ 100 ms, medida no gateway, em janela de 30 dias."

Seção 4 — Contrato de API

Rotas, métodos, parâmetros, payloads, códigos de status e exemplos, expressos em OpenAPI 3.1. Deve incluir ao menos um caminho de erro por rota.

Seção 5 — Modelo de dados

Entidades, atributos, tipos, chaves e restrições de integridade — sem citar produto. Exemplo: Quote { id: UUID, chave primária; customerId: UUID, chave estrangeira; total: Decimal, não nulo; createdAt: Timestamp }.

Seção 6 — Cenários de teste

Critérios de aceite em Gherkin, um por regra de negócio. Cada cenário deve falhar antes de a implementação existir.

Fronteira que não se atravessa

A especificação não menciona linguagem, framework ou banco de dados. "Usar PostgreSQL" é decisão de tecnologia e pertence a um ADR, não a um requisito. Misturar as duas coisas impede trocar a tecnologia sem reescrever o comportamento esperado.

5. ADR — Architecture Decision Record

Um ADR registra uma decisão arquitetural significativa: o contexto que a motivou, as alternativas consideradas, a escolha feita e as consequências assumidas. Captura o porquê da estrutura, não o como da implementação.

Estrutura

  • Título — identificador e enunciado. ADR-001: processamento assíncrono de cotações via fila.
  • Statusproposto, aceito, substituído ou obsoleto.
  • Contexto — a restrição real que exige uma decisão.
  • Decisão — a escolha, em termos estruturais, sem produto.
  • Alternativas — ao menos uma opção descartada, com o motivo.
  • Consequências — o que fica pior, não apenas o que melhora.

Exemplo mínimo

# ADR-001 — Processamento assíncrono de cotações
Status: aceito

Contexto:   RNF-002 exige resposta em menos de 2 s. O cálculo de
            desconto por volume percorre o catálogo e não cabe na
            janela síncrona.

Decisão:    A criação da cotação responde imediatamente com o
            identificador; o cálculo ocorre fora do ciclo de
            requisição, em fila de processamento.

Descartado: Cálculo síncrono com cache do catálogo — o cache
            invalidaria a cada alteração de preço, sem ganho
            estável.

Consequências: introduz um componente de fila e um estado
            intermediário "em processamento" visível ao cliente.
            Exige garantia de entrega e política de reprocessamento.
Por que o ADR vem antes da spec

O ADR justifica a estrutura que aparecerá na especificação. Sem ele, a spec parece arbitrária a quem chega depois: "por que existe uma fila aqui?" passa a ter resposta rastreável — ADR-001. Um ADR escrito depois da decisão, para justificá-la, é ata, não raciocínio.

6. A sequência documental

Antes de qualquer linha de código, e antes de qualquer escolha de tecnologia, cinco artefatos se sucedem. Cada um responde a uma pergunta distinta.

ArtefatoAutoriaResponde a
1. ProblemaProduto e stakeholdersO que não funciona hoje e por quê. Contexto de negócio, lacunas, restrições e métricas de sucesso. Não é técnico.
2. ADRsArquitetura e liderança técnicaComo a solução se estrutura. Padrões, decomposição, limites de módulo. Independente de tecnologia.
3. EspecificaçãoArquitetura e timeQual é o comportamento esperado. RF, RNF, contrato de API, modelo de dados, cenários. Ainda sem tecnologia.
4. Escolha de tecnologiaArquiteturaCom o quê será construído. Linguagem, banco, framework — registrado em ADR próprio e justificado contra a especificação.
5. Plano de implementaçãoTime, com apoio de IAQuais módulos realizam a especificação. Classes, funções, fluxo.
Invariante

Problema → ADR → especificação → tecnologia → implementação. A escolha de tecnologia entra em quarto lugar, não em primeiro. Quando ela antecede a especificação, o comportamento esperado passa a ser derivado da ferramenta disponível — e não o contrário.

7. Pipeline de elicitação

Da análise do negócio até a implementação, seis estágios:

  1. Modelagem de processos (IDEF0). Análise as-is: o que a organização faz hoje, quais são os atores, os fluxos e as lacunas.
  2. Modelagem arquitetural (RM-ODP). Casos de uso, entidades e diagramas de sequência, organizados em pontos de vista.
  3. Esqueleto do software. Estrutura de diretórios, limites de módulo, composição de ambiente. Projeto vazio, mas estruturalmente completo.
  4. Documentos de especificação. Consolidação em Markdown e YAML, formatos legíveis por máquina — autoria integralmente humana.
  5. Testes que falham (TDD). Contratos comportamentais expressos como pré-condições, pós-condições e invariantes.
  6. Implementação. Somente aqui o código é escrito, manualmente ou com apoio de IA, para fazer os testes passarem e atender às metas não funcionais.

RM-ODP e os cinco pontos de vista

O modelo ISO/IEC 10746 descreve sistemas distribuídos em cinco perspectivas complementares, o que evita misturar intenção de negócio, modelo de dados e detalhe tecnológico no mesmo documento:

  • Enterprise — propósito, papéis e políticas.
  • Information — semântica e estrutura da informação.
  • Computational — decomposição funcional e interfaces.
  • Engineering — distribuição, comunicação e infraestrutura.
  • Technology — escolhas concretas de produto.
Observação

A ordem dos pontos de vista espelha a sequência documental da seção 6: Technology é o último, não o primeiro.

8. Linguagens de contrato: OpenAPI e Gherkin

OpenAPI 3.1

Especificação padronizada, legível por máquina, expressa em YAML ou JSON. Descreve rotas, métodos, parâmetros, schemas e códigos de status, constituindo o contrato explícito entre quem produz e quem consome a API. A partir dela é possível gerar documentação navegável, validadores de requisição e esqueletos de cliente.

paths:
  /api/v1/quotes/{id}:
    get:
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string, format: uuid }
      responses:
        '200': { description: Cotação encontrada }
        '403': { description: Cotação pertence a outro cliente }
        '404': { description: Cotação inexistente }

Gherkin

Linguagem estruturada de cenários no formato Dado/Quando/Então, legível pelo negócio e interpretável por ferramenta de teste. Cada cenário corresponde a um critério de aceite verificável.

Cenário: desconto por volume
  Dado um pedido com 12 unidades do produto A
  Quando calculo o total da cotação
  Então o desconto aplicado é de 5%

Cenário: cliente inativo
  Dado um cliente com cadastro inativo
  Quando solicito uma cotação
  Então a resposta é 400 e nenhuma cotação é persistida
Erro comum

Escrever cenários que descrevem cliques de interface. Gherkin fixa regra de negócio, não roteiro de navegação — um cenário que menciona botões quebra a cada mudança de layout, sem que nenhuma regra tenha mudado.

9. Modelagem como código

Entende-se por modelagem como código (modeling as code) a prática de descrever os modelos do sistema em notação textual — Mermaid, PlantUML ou Structurizr DSL — mantida no mesmo repositório da especificação. O diagrama é gerado a partir do texto por ferramenta; a fonte de verdade é o texto, não a imagem.

A consequência prática é dupla: o modelo passa a ser revisado por diff em pull request, como qualquer outro artefato, e a divergência entre o que foi modelado e o que foi implementado torna-se visível no histórico. Um diagrama exportado como imagem e anexado a um documento não oferece nenhuma dessas garantias.

Três modelos são exigidos em toda especificação: o modelo de dados, que fundamenta o schema de persistência; o modelo estático, que delimita responsabilidades entre classes; e o modelo dinâmico, que fixa a ordem das interações e os caminhos de erro. Os exemplos a seguir descrevem um sistema de biblioteca com três entidades persistidas e cinco classes de domínio.

9.1 Modelo de dados: entidade-relacionamento

O diagrama entidade-relacionamento declara entidades, atributos, chaves primárias e estrangeiras e cardinalidades. É o antecedente do schema: nenhuma tabela deve existir sem correspondência aqui.

# modelos/dados.mmd
erDiagram
  LIVRO  ||--o{ EMPRESTIMO : origina
  LEITOR ||--o{ EMPRESTIMO : realiza
  LIVRO {
    uuid id PK
    text isbn UK
    text titulo
    int exemplares
  }
  LEITOR {
    uuid id PK
    text email UK
    bool ativo
  }
  EMPRESTIMO {
    uuid id PK
    uuid livro_id FK
    uuid leitor_id FK
    date prevista
    date efetiva
  }
erDiagram LIVRO ||--o{ EMPRESTIMO : origina LEITOR ||--o{ EMPRESTIMO : realiza LIVRO { uuid id PK text isbn UK text titulo int exemplares } LEITOR { uuid id PK text email UK bool ativo } EMPRESTIMO { uuid id PK uuid livro_id FK uuid leitor_id FK date prevista date efetiva }
Decisão que o diagrama torna explícita

A cardinalidade ||--o{ fixa que um livro origina zero ou muitos empréstimos. O campo efetiva nulo identifica empréstimo em aberto: trata-se de decisão de modelagem que deve constar da especificação, sob pena de ser reinventada de forma divergente por cada implementação.

9.2 Modelo estático: diagrama de classes

O diagrama de classes descreve a estrutura do domínio em memória: atributos, operações e dependências entre classes. Nem toda classe corresponde a uma tabela — a distinção entre estrutura persistida e estrutura de comportamento é uma das razões de existir o modelo estático.

# modelos/estatico.mmd
classDiagram
  direction LR
  class Livro {
    +UUID id
    +String isbn
    +int exemplares
    +disponivel() bool
  }
  class Leitor {
    +UUID id
    +bool ativo
  }
  class Emprestimo {
    +Date prevista
    +Date efetiva
    +emAtraso(hoje) bool
  }
  class PoliticaEmprestimo {
    +int prazoDias
    +int limitePorLeitor
    +validar(leitor, livro)
  }
  class ServicoEmprestimo {
    +registrar(leitor, livro)
    +devolver(emprestimo)
  }
  ServicoEmprestimo --> PoliticaEmprestimo
  ServicoEmprestimo --> Emprestimo
  Emprestimo --> Livro
  Emprestimo --> Leitor
classDiagram direction LR class Livro { +UUID id +String isbn +int exemplares +disponivel() bool } class Leitor { +UUID id +bool ativo } class Emprestimo { +Date prevista +Date efetiva +emAtraso(hoje) bool } class PoliticaEmprestimo { +int prazoDias +int limitePorLeitor +validar(leitor, livro) } class ServicoEmprestimo { +registrar(leitor, livro) +devolver(emprestimo) } ServicoEmprestimo --> PoliticaEmprestimo ServicoEmprestimo --> Emprestimo Emprestimo --> Livro Emprestimo --> Leitor

Das cinco classes, três correspondem às entidades persistidas e duas existem apenas em memória. PoliticaEmprestimo concentra as regras de prazo e limite, que mudam por decisão institucional e não devem exigir alteração de esquema; ServicoEmprestimo orquestra a operação sem conhecer detalhes de persistência.

9.3 Modelo dinâmico: diagrama de sequência

O diagrama de sequência fixa a ordem das mensagens trocadas em uma operação e, sobretudo, os caminhos de erro. O bloco alt representa a bifurcação entre recusa e aprovação; cada ramo corresponde a um cenário Gherkin e a um código de status declarado no contrato OpenAPI.

# modelos/dinamico.mmd
sequenceDiagram
  actor A as Atendente
  participant API as POST /emprestimos
  participant S as ServicoEmprestimo
  participant P as PoliticaEmprestimo
  participant R as Repositorio
  A->>API: leitor_id, livro_id
  API->>S: registrar(leitor, livro)
  S->>P: validar(leitor, livro)
  alt limite excedido ou sem exemplar
    P-->>S: recusa(motivo)
    S-->>API: 422 motivo
  else apto
    P-->>S: aprovado(prazo 14d)
    S->>R: salvar(emprestimo)
    R-->>S: emprestimo
    S-->>API: 201 emprestimo
  end
sequenceDiagram actor A as Atendente participant API as POST /emprestimos participant S as ServicoEmprestimo participant P as PoliticaEmprestimo participant R as Repositorio A->>API: leitor_id, livro_id API->>S: registrar(leitor, livro) S->>P: validar(leitor, livro) alt limite excedido ou sem exemplar P-->>S: recusa(motivo) S-->>API: 422 motivo else apto P-->>S: aprovado(prazo 14d) S->>R: salvar(emprestimo) R-->>S: emprestimo S-->>API: 201 emprestimo end
Erro comum

Modelar apenas o caminho de sucesso. A ausência do ramo de recusa no modelo dinâmico produz implementação sem tratamento de erro e contrato sem código de falha, defeito que só se manifesta em produção.

10. Especificação e inteligência artificial

Modelos generativos produzem código sintaticamente correto com facilidade. O que não produzem, a partir de instrução vaga, é aderência a requisitos não funcionais e a conhecimento de domínio ausente do enunciado. Diante disso, a especificação cumpre três papéis:

  • Restringe — limita o espaço de soluções e impede decisões arquiteturais improvisadas pelo modelo.
  • Codifica — carrega conhecimento de domínio que o modelo não possui internamente.
  • Verifica — define metas mensuráveis aplicadas depois da geração.

Comportamentos que o modelo não infere

Há classes de requisito que nunca emergem de uma instrução em linguagem livre. O processamento de eventos complexos é o caso típico: análise de fluxos contínuos que correlacionam múltiplos eventos ao longo do tempo — sequências, ausência de evento, agregações em janela. Um requisito como "alertar após três falhas de autenticação em menos de um minuto" depende de janela, ordem e critério de reinício que precisam estar declarados na especificação.

Método de trabalho

Passe a especificação e os testes como contexto, em vez de descrever a tarefa em linguagem livre. Diante de um desvio, peça correção contra o desvio medido — sem reabrir a especificação. Limite o número de iterações e escale quando o limite for atingido.

11. Verificação e refinamento iterativo

Cada característica de qualidade tem estratégia própria de verificação. Associar cada RNF a um instrumento é o que separa uma meta de uma intenção.

CaracterísticaEstratégia de verificação
Eficiência de desempenhoPerfilamento de tempo e de consumo de recursos sob carga representativa.
ConfiabilidadeInjeção de falhas, teste de degradação e verificação do caminho de recuperação.
SegurançaAnálise estática, varredura de segredos e auditoria de dependências.
ManutenibilidadeComplexidade ciclomática, acoplamento e taxa de duplicação.

O ciclo

O código é gerado, verificado contra as metas e, havendo desvio, o relatório retorna ao modelo acompanhado da especificação original e da instrução de correção. Segue-se nova geração e nova verificação, até que todas as metas sejam atendidas ou até o limite de iterações definido — momento em que o caso é escalado e o trade-off, registrado.

Verificação não é etapa opcional

Sem instrumento de medida, a especificação permanece uma intenção não confirmada. Código que passa nos testes funcionais e viola a meta de latência não atende ao requisito: compilar não é cumprir.

12. Exemplo completo: endpoint de cotação

Especificação reduzida, mas completa nas seis seções, do endpoint usado como referência na aula.

# POST /api/v1/quotes — Criar cotação

## Visão
Permitir que o cliente obtenha o preço de um conjunto de itens
sem intervenção humana, reduzindo o tempo de resposta comercial.

## Requisitos funcionais
RF-001  Criar cotação a partir de customerId e lista de itens.
RF-002  Aplicar desconto de 5% quando a quantidade do item for ≥ 10.
RF-003  Rejeitar cotação de cliente inativo.

## Requisitos não funcionais
RNF-001 Latência p95 ≤ 100 ms, medida no gateway.
RNF-002 Idempotência garantida pelo cabeçalho Idempotency-Key.
RNF-003 Erro de banco resulta em 500 genérico, sem stack trace.
RNF-004 Lógica de desconto isolada em função própria e testável.

## Contrato
POST /api/v1/quotes
Cabeçalho: Idempotency-Key (UUID, obrigatório)
Corpo: { customerId: uuid,
         items: [ { productId: uuid, quantity: int ≥ 1,
                    priceUnit: decimal > 0 } ] }
201 → { quoteId: uuid, total: decimal }
400 → payload inválido ou cliente inativo
500 → erro interno

## Modelo de dados
Quote     { id UUID PK, customerId UUID FK, total Decimal NOT NULL,
            createdAt Timestamp NOT NULL }
QuoteItem { quoteId UUID FK, productId UUID, quantity Int,
            priceUnit Decimal, PK (quoteId, productId) }

## Cenários
Dado 12 unidades a 10,00, Quando crio a cotação,
  Então o total é 114,00 e o desconto aplicado é de 5%.
Dado a mesma Idempotency-Key de uma cotação existente,
  Quando repito a requisição, Então retorna o mesmo quoteId
  e nenhuma cotação nova é criada.
O que observar no exemplo

Nenhuma linha menciona linguagem, framework ou banco. O total de 114,00 é verificável por cálculo — 12 × 10,00 = 120,00, menos 5% — e o cenário falha até existir implementação. As quatro metas não funcionais têm instrumento associado.

13. Aplicação após a aula

A aula reserva o encerramento para a demonstração conduzida pelo professor sobre o sistema de biblioteca. A aplicação do método pelo estudante ocorre após a aula, sobre o projeto do próprio grupo, no roteiro abaixo.

  1. Escolha uma funcionalidade do projeto do seu grupo — por exemplo, cancelamento de pedido ou consulta de faturamento.
  2. Escreva a especificação em Markdown com as seis seções: visão, RF numerados, RNF numerados, contrato, modelo de dados e cenários. No mínimo três metas não funcionais verificáveis, cada uma com métrica, limite e instrumento.
  3. Registre em um ADR curto a principal decisão estrutural, incluindo a alternativa descartada e a consequência assumida.
  4. Produza os três modelos em Mermaid, versionados junto da especificação: entidade-relacionamento, diagrama de classes e diagrama de sequência da operação principal, este último com o caminho de erro explícito.
  5. Escreva os cenários Gherkin que fixam as regras. Confirme que todos falham antes de existir implementação.
  6. Passe a especificação e os testes como contexto para um modelo generativo e peça a implementação do endpoint.
  7. Execute análise estática, perfilamento de latência e um teste de falha para validar as metas não funcionais.
  8. Havendo desvio, devolva o resultado medido ao modelo e peça refinamento sem alterar a especificação original.
Questão de fechamento

O código gerado a partir de especificação e testes escritos previamente ficou mais aderente ao esperado do que o produzido sem essa disciplina prévia? Onde exatamente a diferença apareceu — no comportamento funcional ou nas metas de qualidade?

14. Referências

  • ISO/IEC 25010:2023 — Systems and software Quality Requirements and Evaluation: product quality model.
  • ISO/IEC 10746 — Reference Model for Open Distributed Processing (RM-ODP).
  • OpenAPI Specification 3.1 — OpenAPI Initiative.
  • Cucumber — Gherkin Reference.
  • MADR — Markdown Any Decision Records.
  • Nygard, M. — Documenting Architecture Decisions.