Este autoestudo examina por que contratos, schemas e regras de negócio devem ser especificados antes da implementação e de que modo a especificação disciplina o desenvolvimento de software assistido por Inteligência Artificial.
Compreender o conceito de Spec-Driven Development (SDD): como especificações técnicas rigorosas, fundamentadas em requisitos funcionais e não funcionais, guiam o desenvolvimento de software, garantem alinhamento entre times e restringem a geração de código por Inteligência Artificial a resultados verificáveis. Ao longo do texto, cada termo técnico é definido no ponto em que aparece, de modo que a leitura não pressuponha vocabulário prévio.
É comum iniciar a implementação antes de qualquer definição formal do comportamento esperado. Essa prática resulta em retrabalho, inconsistência de contratos de API e desalinhamento com as partes interessadas. O Spec-Driven Development (SDD) é uma metodologia de desenvolvimento de software na qual a especificação técnica constitui a única fonte de verdade (Single Source of Truth — SSoT).
Entende-se por Single Source of Truth (SSoT), em português fonte única de verdade, o princípio de arquitetura da informação segundo o qual cada informação relevante do sistema reside em um único artefato autoritativo, do qual todas as demais representações são derivadas. Artefato autoritativo é aquele que detém precedência formal em caso de conflito: quando duas versões da mesma informação divergem, a fonte única prevalece e as cópias devem ser reconciliadas com ela, jamais o inverso. No SDD, a especificação assume esse papel, de modo que código, testes e documentação permaneçam derivações verificáveis de um mesmo artefato, e não versões concorrentes sujeitas a divergência.
No SDD, antes de qualquer implementação, a equipe redige especificações que descrevem o comportamento planejado do sistema. Essas especificações contêm, no mínimo:
Uma especificação tecnicamente completa não se limita a descrever o que o sistema faz. Ela define também como o sistema deve se comportar sob variação de carga, falha, uso indevido ou mudança de contexto — as chamadas propriedades não funcionais.
A ISO/IEC 25010:2023, publicada conjuntamente pela International Organization for Standardization (ISO) e pela International Electrotechnical Commission (IEC), é a norma internacional que define um modelo de qualidade de produto de software, sucedendo a ISO/IEC 9126. Seu propósito não é apenas catalogar características, mas fornecer vocabulário e taxonomia comuns que permitam especificar, medir e avaliar atributos de qualidade de forma objetiva, impedindo que requisitos não funcionais permaneçam implícitos ou sujeitos a interpretação subjetiva. O modelo organiza a qualidade em nove características: a adequação funcional trata da correção das funções entregues; as oito restantes tratam de qualidade não funcional e são particularmente relevantes quando parte do código é gerada por IA:
| Característica | Subcaracterísticas |
|---|---|
| Eficiência de desempenho | Comportamento temporal, utilização de recursos, capacidade. |
| Compatibilidade | Coexistência e interoperabilidade com outros sistemas. |
| Capacidade de interação | Apreensibilidade, operabilidade, proteção contra erro do usuário, inclusividade, assistência ao usuário. |
| Confiabilidade | Ausência de falhas (faultlessness), disponibilidade, tolerância a falhas, recuperabilidade. |
| Segurança | Confidencialidade, integridade, não repúdio, responsabilização, autenticidade. |
| Manutenibilidade | Modularidade, reusabilidade, analisabilidade, modificabilidade, testabilidade. |
| Flexibilidade | Adaptabilidade, escalabilidade, instalabilidade, substituibilidade. |
| Segurança operacional | Restrição operacional, identificação de risco, fail-safe, alerta de perigo. |
Cada subcaracterística decompõe-se em critérios mensuráveis. Um requisito enunciado como "a aplicação deve restabelecer o serviço em até trinta segundos após a queda da conexão de rede" corresponde à confiabilidade, na subcaracterística de recuperabilidade, e passa a ser verificável por critério objetivo em lugar de permanecer expectativa tácita.
Uma especificação madura no ecossistema de dados e software é composta por quatro pilares:
Rotas, métodos HTTP, códigos de status e payload de requisição/resposta usando especificações de mercado como OpenAPI v3.
Definição explícita de tipos, chaves, índices e restrições de integridade no banco de dados e nos modelos de tráfego de dados.
Critérios de aceitação escritos em linguagem compreensível por humanos e computadores (ex.: sintaxe Gherkin/BDD).
Critérios mensuráveis para cada característica não funcional da ISO/IEC 25010:2023 aplicável ao módulo especificado.
OpenAPI e Swagger. OpenAPI é uma especificação padronizada e legível por máquina, expressa em formato estruturado (YAML ou JSON), destinada a descrever de maneira formal APIs HTTP — tipicamente de estilo REST —, incluindo rotas, métodos HTTP, parâmetros, esquemas de payload e códigos de status de resposta. Swagger designa o conjunto de ferramentas associado a essa especificação, nome que historicamente identificava a própria especificação antes de esta ser doada à OpenAPI Initiative e renomeada; tais ferramentas geram documentação interativa, validadores e artefatos de código a partir do documento. A especificação constitui um contrato explícito entre o produtor e o consumidor da API, definindo com precisão a interface de cada operação — suas entradas, saídas e códigos de status —, e pode ser acordada antes da implementação. Um documento OpenAPI pode declarar, por exemplo, que a rota GET /usuarios/{id} recebe um identificador inteiro no caminho e retorna, com código 200, um objeto contendo os campos nome e e-mail, ou o código 404 quando o identificador não corresponde a nenhum registro.
Gherkin e BDD. O Behavior-Driven Development (BDD), ou desenvolvimento orientado a comportamento, descreve o comportamento esperado de um sistema por meio de cenários concretos, formulados em linguagem compreensível tanto por equipes técnicas quanto por partes interessadas sem formação técnica. Gherkin é a linguagem estruturada que materializa esses cenários no formato Dado/Quando/Então (Given/When/Then), simultaneamente legível por humanos e interpretável por ferramentas de automação: "Dado" estabelece o contexto inicial, "Quando" identifica a ação ou o evento e "Então" define o resultado esperado. Cada cenário expressa um critério de aceitação verificável e versionado, mantendo a especificação como fonte de verdade continuamente confrontada com a implementação. Exemplo: "Dado que uma conta possui saldo de 100 unidades; Quando o titular solicita um saque de 30 unidades; Então o saldo resultante deve ser de 70 unidades e a operação deve ser registrada como concluída."
A especificação não nasce pronta. Ela resulta de um processo estruturado de elicitação — a atividade sistemática de levantar, extrair e explicitar requisitos junto aos stakeholders (usuários, clientes, especialistas de domínio e demais partes interessadas), convertendo necessidades tácitas e frequentemente ambíguas em enunciados verificáveis. Constitui a etapa inicial da engenharia de requisitos e recorre a técnicas como entrevistas, questionários, observação direta, análise de documentos e prototipação. Quando organizada em etapas encadeadas de coleta, refinamento e validação, a elicitação assume a forma de um pipeline. Um pipeline consolidado segue seis estágios sequenciais, dos quais apenas o último admite participação de Inteligência Artificial:
Análise as-is. A expressão as-is, do inglês "como está", designa a representação do estado atual de um processo ou sistema, tal como opera antes de qualquer intervenção. A análise as-is documenta os fluxos, atores, regras e limitações vigentes, constituindo uma linha de base factual que se contrapõe ao estado futuro desejado, denominado to-be. Sua finalidade é assegurar que toda especificação de mudança parta de uma compreensão rigorosa daquilo que efetivamente existe, evitando suposições descoladas da realidade operacional. Ao modelar a emissão de segunda via de um documento, por exemplo, a análise as-is registra que a solicitação é hoje recebida por correio eletrônico, conferida manualmente e respondida em até dois dias úteis, sem automação — o estado atual, não a solução pretendida.
| Elemento (posição) | Significado |
|---|---|
| Input — entrada (esquerda) | O que a função consome ou transforma. |
| Control — controle (topo) | Condições, regras e restrições que governam a execução. |
| Output — saída (direita) | O resultado produzido pela função. |
| Mechanism — mecanismo (base) | Os recursos que realizam a função. |
Na função "Aprovar crédito", a entrada é a solicitação do cliente, o controle é a política de risco vigente, a saída é a decisão de aprovação ou recusa, e o mecanismo é o analista apoiado pelo sistema de crédito. Distinguir Input de Control evidencia que a especificação atua como o controle que governa cada função, enquanto a análise as-is fornece a base factual sobre a qual os requisitos são formulados.
| Viewpoint | Preocupação isolada |
|---|---|
| Enterprise | Propósito, papéis, políticas e regras de negócio. |
| Information | Semântica e estrutura dos dados manipulados. |
| Computational | Decomposição funcional em objetos e interfaces. |
| Engineering | Mecanismos de distribuição e infraestrutura de comunicação. |
| Technology | Escolhas concretas de tecnologia da implementação. |
Em um sistema de reserva de salas, o Enterprise fixa que apenas usuários autenticados reservam e define a política de cancelamento; o Information modela as entidades Sala, Reserva e Usuário; o Computational decompõe o sistema em serviços com interfaces como "consultar disponibilidade" e "confirmar reserva"; o Engineering determina a comunicação por chamadas remotas com replicação de estado; e o Technology fixa o uso de contêineres e de um banco de dados relacional. Cada especificação torna-se rastreável e verificável em seu próprio nível de abstração.
Contratos comportamentais. Os contratos comportamentais mencionados no estágio 5 constituem a especificação formal das obrigações e garantias de uma operação ou módulo, conforme a abordagem de projeto por contrato (Design by Contract). Compõem-se de três cláusulas: a pré-condição, que estabelece o que deve ser verdadeiro antes da invocação e cuja satisfação é responsabilidade do chamador; a pós-condição, que descreve o que a operação garante ao terminar, desde que a pré-condição tenha sido satisfeita; e a invariante, propriedade que deve valer em todos os estados observáveis do objeto — após sua construção e entre as operações públicas —, podendo ser temporariamente violada durante a execução interna de uma operação. Em uma pilha de capacidade fixa, a inserção tem por pré-condição que a pilha não esteja cheia, por pós-condição que o elemento inserido ocupe o topo e o tamanho seja incrementado em uma unidade, e por invariante que o número de elementos permaneça entre zero e a capacidade máxima. Tais cláusulas tornam explícita a distribuição de responsabilidades entre chamador e operação chamada, delimitando de forma inequívoca a origem de uma falha.
Especificações legíveis por máquina. Os documentos do estágio 4 são redigidos em formato estruturado — tipicamente Markdown, YAML ou JSON —, cuja sintaxe permite processamento automático por ferramentas e por modelos de linguagem, em oposição ao texto livre em prosa. Ao adotar estrutura formal, com campos, chaves nomeadas, hierarquia e tipos declarados, tais documentos reduzem a ambiguidade interpretativa da linguagem natural e servem simultaneamente como comunicação inteligível para humanos e como entrada consumível por processos automatizados, habilitando validação, versionamento e geração de artefatos derivados.
Docker Compose e conteinerização. A conteinerização, referida no estágio 3, é a técnica de empacotar uma aplicação em conjunto com suas dependências, bibliotecas e configurações em uma unidade isolada denominada contêiner, a qual compartilha o núcleo (kernel) do sistema operacional hospedeiro e executa de maneira consistente na maioria dos ambientes compatíveis. O Docker Compose é uma ferramenta que descreve, por meio de um arquivo declarativo em YAML, a definição e a execução coordenada de múltiplos contêineres em um único host — por exemplo, uma aplicação e seu banco de dados —, especificando serviços, redes e volumes; a orquestração distribuída em escala pertence a ferramentas como Kubernetes ou Docker Swarm. Seu emprego favorece a reprodutibilidade: uma vez que o ambiente de execução passa a ser especificado explicitamente em código, a implantação torna-se mais previsível entre as máquinas de desenvolvimento, teste e produção. No SDD, o arquivo de composição atua como especificação executável do ambiente, convertendo a infraestrutura de conhecimento tácito em artefato versionado e verificável.
Com o avanço de assistentes de codificação por IA, o SDD tornou-se ainda mais relevante. Modelos de linguagem geram código sintaticamente correto com facilidade, mas o resultado frequentemente carece de alinhamento com requisitos não funcionais quando a instrução recebida é vaga ou informal.
Uma especificação bem estruturada cumpre três papéis simultâneos diante de um modelo generativo: restringe o espaço de soluções possíveis, evitando decisões arquiteturais improvisadas; codifica conhecimento de domínio que o modelo não possui; e define metas de qualidade verificáveis, conferíveis após a geração. Nenhum desses papéis é cumprido por um prompt informal.
Gerar código a partir de uma especificação não encerra o processo. O resultado deve ser verificado contra as metas definidas para cada característica não funcional, usando ferramental apropriado a cada uma:
| Característica | Estratégia de verificação |
|---|---|
| Eficiência de desempenho | Perfilamento de tempo de resposta. |
| Compatibilidade | Testes de integração e contrato. |
| Confiabilidade | Injeção de falhas; teste de fallback. |
| Segurança | Análise estática e auditoria de código. |
| Manutenibilidade | Complexidade ciclomática e taxa de duplicação. |
| Flexibilidade | Teste de variação de configuração. |
Perfilamento (profiling). Entende-se por perfilamento a medição, em tempo de execução, do consumo de tempo de processamento e de recursos (memória, chamadas de sistema, operações de entrada e saída) atribuído a cada trecho de um programa. A técnica instrumenta ou amostra a execução para associar esses custos a funções, laços ou linhas específicas, revelando onde o programa concentra seu esforço. Distingue-se da análise estática por operar sobre a execução real, e não sobre o texto do código. Requisitos não funcionais de eficiência de desempenho só podem ser verificados objetivamente por meio dele, que converte as metas declaradas na especificação em medições comparáveis.
Injeção de falhas (fault injection). Constitui a técnica de teste que introduz deliberadamente condições anômalas em um sistema em execução — indisponibilidade de um serviço, latência excessiva, erro de rede —, a fim de observar seu comportamento sob adversidade. Exercita explicitamente as rotas de tratamento de erro e os mecanismos de contingência que os testes de caminho feliz não alcançam, evidenciando se o sistema degrada de forma controlada e retorna a um estado consistente após a remoção da falha. Em um serviço que consome uma API externa de câmbio, suspende-se temporariamente o contêiner dessa dependência durante os testes e verifica-se se o serviço aplica timeout, retorna valor em cache e registra o incidente, em vez de propagar a falha ao usuário.
Degradação graciosa (graceful degradation) e fallback. Degradação graciosa é a propriedade pela qual um sistema mantém operação parcial, com funcionalidade reduzida, quando um de seus componentes ou dependências falha, torna-se indisponível ou é submetido a sobrecarga, em vez de interromper por completo. O fallback é o comportamento alternativo acionado nesse cenário: um caminho substituto que preserva a continuidade do serviço, ainda que sob garantias menores. Um serviço de recomendação que consulta uma API externa de preferências pode, ao detectá-la indisponível, retornar uma lista padrão armazenada localmente — resultado menos personalizado, porém funcional. A especificação deve declarar de forma explícita o comportamento esperado diante da falha de cada dependência, convertendo a degradação graciosa em requisito verificável.
Análise estática de código. É o exame do código-fonte sem execução do programa, com o objetivo de detectar defeitos, vulnerabilidades e violações de padrão. A técnica inspeciona a estrutura textual e sintática — declarações, fluxos de controle, dependências, expressões — para identificar problemas como segredos codificados diretamente no fonte, uso de construções perigosas (por exemplo, a função eval()) e ausência de validação de entradas. Distingue-se da análise dinâmica, que avalia o comportamento durante a execução: a estática opera antes e independentemente da execução, alcançando caminhos que os testes dinâmicos podem não exercitar. Ambas são complementares. No SDD, a análise estática constitui um gate automatizado que verifica se o código respeita as restrições de segurança e de padrão fixadas na especificação.
Complexidade ciclomática. Métrica proposta por McCabe que quantifica o número de caminhos linearmente independentes no grafo de fluxo de controle de um módulo ou função. Cada estrutura de decisão — condicional, laço ou operador lógico de curto-circuito — introduz uma ramificação e incrementa o valor. Seu valor corresponde ao número de casos de teste de um conjunto-base de caminhos independentes: opera como limite superior para a cobertura de ramos e como limite inferior para a cobertura de todos os caminhos de execução possíveis. Uma função que apenas retorna a soma de dois parâmetros tem complexidade 1; ao acrescentar duas condicionais que tratam valores nulos e negativos, o número de caminhos independentes sobe para 3, exigindo três casos de teste para cobrir o conjunto-base. Valores elevados indicam lógica mais ramificada, o que eleva o esforço de teste e dificulta a compreensão e a alteração seguras — constituindo, assim, um proxy quantitativo de testabilidade e manutenibilidade verificável por análise estática.
Taxa de duplicação de código. Métrica que quantifica a proporção de trechos idênticos ou substancialmente semelhantes presentes em uma base de código, em relação ao seu volume total. É um indicador de manutenibilidade, comumente obtido por análise estática. Valores elevados sinalizam que uma mesma lógica se encontra replicada em múltiplos pontos, de modo que a correção de um defeito ou a alteração de uma regra deve ser propagada a todas as ocorrências, sob pena de inconsistência. Uma rotina de validação de e-mail copiada em cinco módulos exige, ao corrigir uma falha, a edição das cinco cópias; a omissão de qualquer uma mantém o defeito ativo. A especificação pode estabelecer um limiar máximo tolerado, assegurando que o código derivado dela preserve a manutenibilidade prescrita.
async e await demarcam funções cuja execução pode ser suspensa no ponto de espera e retomada quando o resultado estiver disponível, liberando a thread para processar outras tarefas; a E/S não bloqueante, de forma complementar, designa chamadas de sistema que retornam de imediato e sinalizam a conclusão posteriormente. Um servidor que consulta um banco de dados com await passa a atender outras requisições enquanto aguarda a resposta. O caráter assíncrono de uma operação e seus requisitos de latência e concorrência devem constar do contrato, para que a verificação avalie o comportamento sob carga, e não apenas a correção do resultado.
Quando a verificação aponta desvio — um teste que falha, uma meta de complexidade excedida, um segredo codificado diretamente no código —, o relatório de qualidade retorna à IA geradora junto às especificações originais, com instrução explícita sobre o que corrigir. O código é regenerado e reverificado. O ciclo se repete até que todas as metas sejam atendidas ou até que um número máximo de iterações seja atingido.
Abaixo, um exemplo simplificado de especificação técnica em Markdown para um endpoint de criação de cotações em um microsserviço de faturamento, incluindo metas de qualidade não funcional.
POST /cotacoes cria uma cotação a partir dos dados enviados no corpo da requisição, ao passo que GET /cotacoes recupera as cotações existentes.
# Especificação: Endpoint de Cotação (Billing Service)
## Informações Gerais
* **Rota:** `/api/v1/quotes`
* **Método:** `POST`
* **Content-Type:** `application/json`
* **Cabeçalho `Idempotency-Key`:** UUID único da requisição, obrigatório. Requisições repetidas com a mesma chave não geram cotações duplicadas.
## Payload de Requisição
```json
{
"customerId": "uuid (obrigatório)",
"items": [
{
"productId": "string (obrigatório)",
"quantity": "integer (obrigatório, mínimo 1)",
"priceUnit": "float (obrigatório, positivo)"
}
]
}
```
## Regras de Negócio e Validações
1. **Validação de Cliente:** o `customerId` deve ser um UUID válido e pertencer a um cliente ativo no banco de dados. Caso contrário, retornar `400 Bad Request` com o erro correspondente.
2. **Cálculo do Total:** o microsserviço deve calcular o valor total da cotação multiplicando `quantity` * `priceUnit` para cada item e somando os resultados.
3. **Desconto por Quantidade:** se a quantidade de qualquer item for maior ou igual a 10, deve ser aplicado um desconto de 5% sobre o preço unitário desse item.
## Respostas Esperadas
* `201 Created`: cotação gerada com sucesso. Retorna o ID da cotação criada e o total final.
* `400 Bad Request`: payload inválido ou falha em regra de negócio.
* `500 Internal Server Error`: erro inesperado do servidor.
## Metas de Qualidade Não Funcional
* **Segurança:** nenhum segredo codificado diretamente; validação de `customerId` obrigatória antes de qualquer cálculo.
* **Manutenibilidade:** lógica de desconto isolada em função própria, testável isoladamente do cálculo do total.
* **Confiabilidade:** falha de acesso ao banco de dados deve retornar `500` com mensagem genérica, sem expor stack trace ao cliente.
* **Confiabilidade (idempotência):** a submissão repetida de uma mesma cotação, identificada pelo cabeçalho `Idempotency-Key`, não deve gerar registros duplicados.
customerId e items é o payload; o cabeçalho Content-Type: application/json apenas descreve o formato desse corpo e não integra a carga útil, tal como a linha inicial POST /api/v1/quotes é metadado de transporte. O UUID (Universally Unique Identifier, Identificador Único Universal) é um valor de 128 bits, comumente representado por 32 dígitos hexadecimais em cinco grupos separados por hifens no formato 8-4-4-4-12 (por exemplo, 550e8400-e29b-41d4-a716-446655440000). Destina-se a identificar entidades sem depender de uma autoridade central, pois a probabilidade de colisão entre valores gerados de forma independente é desprezível. A validação do formato do customerId permite rejeitar entradas malformadas já na fronteira do sistema.
Esta seção propõe um exercício de aplicação do Spec-Driven Development.