Spec Driven Development : écrire la spec avant le code, et la garder vivante

Une demande arrive sous la forme d'un ticket de trois lignes : « Relancer automatiquement les clients qui n'ont pas payé leur facture ». Deux jours plus tard, le code est là, les tests passent, la démo tourne. Et en revue, la première question tombe : « Et si le client a payé une partie seulement ? ». Personne ne se l'était posée. Le code répond quand même quelque chose, par défaut, sans que personne ne l'ait décidé.
Le Spec Driven Development (SDD) part de ce constat : le coût d'une ambiguïté grimpe à chaque étape où elle survit. Découverte dans une spec, elle coûte une phrase. Découverte en revue de code, elle coûte une réécriture. Découverte en production, elle coûte un incident. Cet article explique ce qu'est le SDD, pourquoi il revient sur le devant de la scène, quand il vaut la peine, comment l'appliquer sans tomber dans la paperasse, et ce qui existe à côté.
Le quoi : la spec comme source de vérité#
Le principe tient en une phrase : on écrit d'abord ce que le système doit faire, sous une forme précise et vérifiable, et le code est dérivé de cette description. La spec n'est pas un document qu'on remplit pour la forme avant de coder ce qu'on avait en tête. C'est l'artefact de référence : quand le code et la spec divergent, l'un des deux a un bug, et on doit savoir lequel.
En pratique, le SDD sépare trois niveaux qu'on a l'habitude de mélanger dans un même ticket ou une même conversation :
SPEC PLAN TÂCHES
Quoi et pourquoi ──▶ Comment ──▶ Dans quel ordre
(comportement, (architecture, (étapes petites,
règles métier, choix techniques, vérifiables,
critères contraintes) chacune testable)
d'acceptation)
│ │
└──────────── les tests vérifient la spec ◀────────┘
- La spec décrit le comportement observable, du point de vue de l'utilisateur ou du système appelant. Elle ne parle ni de base de données ni de framework.
- Le plan traduit la spec en décisions techniques : quels modules sont touchés, quel modèle de données, quelles contraintes (performance, sécurité, compatibilité).
- Les tâches découpent le plan en étapes assez petites pour être codées, testées et relues une par une.
Cette séparation n'a rien de nouveau en soi. Ce qui est propre au SDD, c'est l'ordre (on ne passe pas au plan tant que la spec contient des questions ouvertes) et le statut de la spec (elle reste dans le repo, versionnée à côté du code, et elle évolue avec lui).
Trois niveaux d'engagement#
Tout le monde ne met pas la même chose derrière « SDD ». Birgitta Böckeler, dans une analyse publiée sur le site de Martin Fowler, distingue trois niveaux qui aident à s'y retrouver :
| Niveau | Ce qu'on fait de la spec | Ce que ça implique |
|---|---|---|
| Spec-first | Écrite avant le code, pour guider une tâche | Elle peut être jetée une fois la feature livrée |
| Spec-anchored | Conservée et mise à jour à chaque évolution | Elle devient une documentation vivante du comportement |
| Spec-as-source | Seule chose éditée par un humain, le code est régénéré | Le code n'est plus un artefact qu'on maintient à la main |
Le premier niveau est facile à adopter demain matin. Le deuxième demande de la discipline mais rapporte le plus. Le troisième reste aujourd'hui expérimental : il suppose une génération de code assez fiable pour qu'on arrête de relire ce qui sort, ce qui n'est pas mon cas sur des projets réels.
Pourquoi le SDD revient maintenant#
Écrire une spec avant de coder, c'est ce que faisaient déjà les équipes des années 1990, avec des documents de 80 pages validés en comité. L'agilité a réagi, à juste titre, contre ces specs figées écrites six mois avant la première ligne de code. Pourquoi le sujet revient-il ?
Parce que les agents de code exécutent ce qu'on leur demande, pas ce qu'on voulait dire. Un développeur humain à qui on donne un ticket flou pose des questions, va voir le métier, se souvient de la réunion de la semaine dernière. Un agent comble les trous avec l'hypothèse la plus probable, et produit un code qui a l'air juste. Le vibe coding (on décrit vaguement, on regarde ce qui sort, on corrige au feeling) marche pour un prototype. Sur une base de code qui doit vivre, il produit exactement le scénario du ticket de relance : un comportement que personne n'a choisi.
La spec donne à l'agent ce qu'un humain aurait obtenu en posant des questions. Et elle donne au relecteur une référence : on ne relit plus un diff de 400 lignes en se demandant « est-ce que c'est bien ce qu'on voulait », on vérifie qu'il implémente les critères de la spec.
Plusieurs outils se sont construits sur cette idée en 2025 :
- GitHub Spec Kit, un kit open source qui outille le cycle sous forme de
commandes (
/specifypour la spec,/planpour le plan technique,/taskspour le découpage, puis l'implémentation), avec un fichier « constitution » qui fixe les principes non négociables du projet. - Kiro, l'IDE d'AWS, qui produit pour chaque feature un
requirements.md(exigences au format EARS), undesign.mdet untasks.md. - Tessl, qui explore le niveau spec-as-source.
Aucun de ces outils n'est nécessaire pour pratiquer le SDD. Trois fichiers
Markdown dans un dossier specs/ suffisent, et c'est ce que je montre plus
bas.
Le SDD n'est pas lié à l'IA. Une équipe sans aucun agent de code en tire les mêmes bénéfices : ambiguïtés levées tôt, revue plus simple, tests dérivés des critères. Les agents ont simplement rendu le coût d'une spec absente plus visible, et plus rapide à payer.
Un exemple complet : la relance des factures impayées#
Reprenons le ticket de départ et déroulons les trois étapes.
Étape 1 : la spec#
# Spec : relance automatique des factures impayées
## Contexte
Aujourd'hui les relances sont faites à la main, en retard, et certaines
sont oubliées. On veut une première relance automatique, sans jamais
relancer un client qui a déjà payé.
## Parcours utilisateur
En tant que gérant, je veux que les clients en retard de paiement
reçoivent un rappel, afin de ne plus suivre les échéances à la main.
## Règles métier
- R1. Une facture est « en retard » quand sa date d'échéance est
dépassée de plus de 7 jours calendaires et qu'il reste un montant dû.
- R2. Un paiement partiel ne suspend pas la relance. Le rappel indique
le montant restant dû, pas le montant initial.
- R3. Une facture est relancée au plus une fois tous les 14 jours.
- R4. Aucune relance n'est envoyée pour une facture marquée « litige ».
- R5. Au-delà de 3 relances, la facture passe en « à traiter
manuellement » et n'est plus relancée automatiquement.
## Critères d'acceptation
- CA1. Étant donné une facture de 1 000 € échue depuis 8 jours et
impayée, quand le traitement quotidien s'exécute, alors un rappel
mentionnant 1 000 € est envoyé.
- CA2. Étant donné une facture de 1 000 € échue depuis 8 jours avec
400 € déjà payés, quand le traitement s'exécute, alors le rappel
mentionne 600 €.
- CA3. Étant donné une facture relancée il y a 10 jours, quand le
traitement s'exécute, alors aucun rappel n'est envoyé.
- CA4. Étant donné une facture en litige échue depuis 30 jours, quand
le traitement s'exécute, alors aucun rappel n'est envoyé.
- CA5. Étant donné une facture déjà relancée 3 fois, quand le
traitement s'exécute, alors elle passe en « à traiter manuellement »
et aucun rappel n'est envoyé.
## Hors périmètre
- Relances par SMS ou courrier.
- Pénalités de retard.
- Modification du modèle d'email par le gérant.
## Questions ouvertes
- [x] Jours calendaires ou ouvrés ? Calendaires (validé avec le gérant).
- [x] Paiement partiel : relancer ou non ? Oui, sur le reste dû (R2).Quelques remarques sur ce document, parce que c'est là que se joue l'essentiel.
La question du paiement partiel est tranchée par écrit (R2 et CA2). C'est exactement la question qui aurait surgi en revue de code. Elle a été posée en écrivant les exemples chiffrés : impossible d'écrire CA2 sans décider de ce qui se passe.
Les critères sont des exemples, pas des intentions. « Le système relance correctement les clients » n'est pas un critère : on ne peut pas le vérifier. « Un rappel mentionnant 600 € est envoyé » en est un : un test peut le prouver ou le réfuter.
Le hors périmètre est explicite. C'est la section la plus sous-estimée. Sans elle, quelqu'un (humain ou agent) ajoutera des pénalités de retard « parce que c'était logique », et la revue deviendra une négociation.
Les questions ouvertes sont cochées. Tant qu'une case reste vide, on ne passe pas au plan. C'est la règle la plus simple du SDD et la plus efficace.
Pour les exigences, le format EARS (Easy Approach to Requirements Syntax) donne une structure qui évite les phrases floues : « QUAND une facture est échue depuis plus de 7 jours, LE SYSTÈME DOIT envoyer un rappel ». Le format Étant donné / Quand / Alors, hérité du BDD, fait le même travail pour les critères d'acceptation. Peu importe lequel on choisit, l'important est qu'une phrase décrive un déclencheur et un résultat observable.
Étape 2 : le plan#
Le plan est l'endroit où les choix techniques apparaissent, et seulement là.
# Plan : relance automatique des factures impayées
## Approche
- Un job planifié quotidien (6h) dans le module `billing`.
- Le calcul de l'éligibilité est une fonction pure du domaine :
`eligibilite(facture, historiqueRelances, aujourdHui)`, sans accès
base ni horloge système. Toutes les règles R1 à R5 y vivent.
- L'envoi passe par le port `NotificationPort` déjà existant.
## Modèle de données
- Nouvelle table `relance` (facture_id, envoyee_le, montant_rappele).
- Nouveau statut de facture `A_TRAITER_MANUELLEMENT`.
## Contraintes
- Le job doit être idempotent : relancé deux fois le même jour, il
n'envoie pas deux rappels (R3 le garantit si l'insertion en base
précède l'envoi dans la même transaction).
- Aucune donnée client dans les logs.
## Risques
- Volume : environ 2 000 factures ouvertes, pas de pagination nécessaire.On remarque que le plan référence les règles de la spec (R1 à R5, R3) au lieu de les réécrire. Si une règle change, elle change à un seul endroit.
Étape 3 : les tâches#
# Tâches
- [ ] T1. Fonction `eligibilite` + tests CA1 à CA5 (domaine pur)
- [ ] T2. Table `relance` et migration
- [ ] T3. Statut `A_TRAITER_MANUELLEMENT`
- [ ] T4. Job quotidien : charge les factures, appelle `eligibilite`,
enregistre la relance puis notifie
- [ ] T5. Test d'intégration : double exécution le même jour = un seul envoiChaque tâche produit un changement relisible seul, et chacune dit comment on saura qu'elle est terminée.
Du critère au test#
Le lien entre la spec et le code passe par les tests. Chaque critère d'acceptation devient un test, et le nom du test pointe vers le critère :
class EligibiliteRelanceTest {
private val aujourdHui = LocalDate.of(2026, 3, 20)
@Test
fun `CA2 - un paiement partiel est relancé sur le montant restant dû`() {
val facture = facture(
montant = 1_000.euros,
dejaPaye = 400.euros,
echeance = aujourdHui.minusDays(8),
)
val decision = eligibilite(facture, historique = emptyList(), aujourdHui)
assertThat(decision).isEqualTo(Decision.Relancer(montant = 600.euros))
}
@Test
fun `CA4 - une facture en litige n'est jamais relancée`() {
val facture = facture(
montant = 1_000.euros,
echeance = aujourdHui.minusDays(30),
statut = Statut.LITIGE,
)
val decision = eligibilite(facture, historique = emptyList(), aujourdHui)
assertThat(decision).isEqualTo(Decision.Ignorer)
}
}Le jour où quelqu'un demande « pourquoi on relance un client qui a déjà payé 400 € ? », la réponse est traçable : la règle R2, le critère CA2, le test du même nom. Et si le métier change d'avis, on modifie la spec d'abord, le test ensuite, le code en dernier. C'est le même cycle que le TDD, décrit dans l'article sur le vrai TDD avec Claude Code, mais avec une étape en amont : le test ne sort plus de la tête du développeur, il sort d'un exemple validé par le métier.
Quand le SDD vaut la peine, et quand il ne sert à rien#
Une spec a un coût : du temps d'écriture, du temps de relecture, et du temps de maintenance si on la garde. Elle se rentabilise quand ce coût est inférieur à celui des ambiguïtés qu'elle évite.
| Situation | SDD utile ? | Pourquoi |
|---|---|---|
| Feature avec des règles métier (facturation, droits, calculs) | Oui | Les cas limites sont nombreux et coûteux à découvrir tard |
| Travail confié à un agent de code sur plusieurs fichiers | Oui | L'agent comble les trous par des suppositions |
| Plusieurs équipes ou un client impliqués | Oui | La spec sert de contrat et évite les « ce n'est pas ce qu'on avait dit » |
| Évolution d'une feature existante mal documentée | Oui | Écrire la spec du comportement actuel révèle souvent des bugs |
| Correction d'un bug d'une ligne | Non | Le test de non-régression suffit |
| Prototype jetable, exploration technique | Non | On cherche à apprendre, pas à livrer un comportement précis |
| Changement purement technique (montée de version, renommage) | Non | Aucun comportement observable ne change |
Un repère simple : si on ne peut pas écrire au moins trois critères d'acceptation différents pour la demande, elle est probablement trop petite pour une spec. Si on n'arrive pas à en écrire un seul, elle est trop floue pour être codée, et c'est justement le signe qu'il faut écrire la spec.
L'appliquer au quotidien#
Une organisation de fichiers minimale#
specs/
├── 012-relance-factures/
│ ├── spec.md
│ ├── plan.md
│ └── tasks.md
└── 013-export-comptable/
└── spec.md
Un dossier par feature, numéroté, dans le repo. Pas dans Confluence, pas dans Jira : si la spec n'est pas versionnée avec le code, elle divergera du code dans les semaines qui suivent.
Le déroulé d'une feature#
- Écrire le brouillon de spec en 20 à 30 minutes. Contexte, règles, critères chiffrés, hors périmètre. Laisser les questions ouvertes apparaître au lieu de les trancher seul.
- Faire relire la spec, pas le code. Par le métier, le PO, ou un autre développeur. Une spec d'une page se relit en cinq minutes, un diff de 400 lignes non. C'est à ce moment qu'on règle les désaccords, quand ils ne coûtent qu'une phrase.
- Fermer les questions ouvertes. Chaque réponse devient une règle ou un critère.
- Écrire le plan, en référençant les règles de la spec.
- Découper en tâches, chacune testable et relisible seule.
- Implémenter tâche par tâche, en commençant par les tests qui correspondent aux critères.
- En revue de code, vérifier la conformité à la spec en plus de la qualité du code. La PR contient un lien vers la spec, et les nouveaux tests portent le nom des critères.
Quand la spec change en cours de route#
Elle changera, c'est normal. On découvre pendant l'implémentation un cas qu'on n'avait pas vu. La règle : on met à jour la spec dans la même PR que le code. Si le cas découvert change le comportement attendu, il repasse par une validation rapide. Si on laisse le code évoluer seul, on revient à la situation de départ, avec en prime un document qui ment.
Avec un agent de code#
Le déroulé est le même, avec deux ajustements. D'abord, on demande à l'agent de relire la spec et de lister les ambiguïtés avant d'écrire le plan : il est très bon pour trouver les trous (« que se passe-t-il si l'échéance tombe un 29 février ? »). Ensuite, on lui fait implémenter une tâche à la fois, en relisant entre chaque. C'est l'équivalent du mode plan avant toute tâche non triviale, appliqué à l'échelle d'une feature entière. Les principes stables du projet (architecture, conventions, commandes de test) vont dans un fichier de contexte type CLAUDE.md, pas dans chaque spec.
Faire écrire la spec entière par un agent, puis la valider en diagonale, est le moyen le plus rapide de perdre tout l'intérêt du SDD. L'agent produira un document long, bien structuré et plausible, avec des règles que personne n'a décidées. On a déplacé l'ambiguïté du code vers la spec, sans la lever. L'agent peut rédiger, reformuler, chercher les trous. Les décisions, elles, doivent venir de quelqu'un qui connaît le métier.
Les pièges#
Refaire le cycle en V sans le dire. Si la spec de chaque feature prend deux semaines et passe par trois comités, on a réinventé le document de 80 pages. Une spec SDD couvre une feature, pas un produit, et elle s'écrit en heures, pas en semaines. Le cycle reste court et itératif.
Écrire du pseudo-code dans la spec. « Faire une requête sur la table facture avec un LEFT JOIN sur paiement » n'a rien à faire dans la spec. Dès que la spec parle d'implémentation, elle fige des choix qui appartiennent au plan, et elle devient illisible pour le métier.
Des critères invérifiables. « Le traitement doit être rapide », « l'email doit être clair ». Si aucun test ne peut échouer, ce n'est pas un critère. « Le traitement de 2 000 factures prend moins de 30 secondes » en est un.
La spec morte. Écrite avec soin, jamais mise à jour. Six mois plus tard, elle décrit un comportement qui n'existe plus, et quelqu'un s'y fie. Une spec périmée est pire que pas de spec. Si l'équipe ne compte pas la maintenir, il vaut mieux assumer le niveau spec-first et l'archiver une fois la feature livrée.
La spec pour tout. Imposer une spec pour renommer une variable ou monter une dépendance transforme une bonne pratique en bureaucratie, et l'équipe finira par contourner la règle y compris là où elle était utile.
Oublier le hors périmètre. Sans lui, chaque relecteur projette ses attentes, et la PR devient le lieu où l'on découvre ce que chacun croyait inclus.
Confondre spec et plan. Mélanger le « quoi » et le « comment » dans le même document empêche de changer l'un sans toucher l'autre. Le jour où l'on remplace le job quotidien par un événement Kafka, la spec ne devrait pas bouger d'une ligne.
Les autres approches, et comment elles s'articulent#
Le SDD ne remplace pas les pratiques existantes, il se place en amont de la plupart d'entre elles.
| Approche | Ce qu'elle pilote | Relation avec le SDD |
|---|---|---|
| TDD | Le design du code, test par test | Complémentaire : les critères de la spec donnent les premiers tests |
| BDD | Le comportement, via des scénarios exécutables (Gherkin) | Très proche : le BDD rend les critères exécutables, le SDD ajoute le plan et les tâches |
| DDD | Le modèle et le langage du domaine | Complémentaire : le langage ubiquitaire rend la spec plus précise |
| Contract-first / API-first | Le contrat d'interface (OpenAPI, AsyncAPI, schéma Avro) | Cas particulier du SDD appliqué à une frontière technique |
| ADR | Une décision d'architecture et ses raisons | Complémentaire : l'ADR explique un choix du plan, pas le comportement |
| Design by contract | Préconditions, postconditions, invariants dans le code | Même idée, au niveau d'une fonction plutôt que d'une feature |
| Vibe coding | Rien d'écrit, on itère sur le résultat | L'opposé : utile pour explorer, risqué pour livrer |
Le BDD mérite une précision, car la confusion est fréquente. Le BDD part de
scénarios Étant donné / Quand / Alors écrits avec le métier, puis les rend
exécutables (avec Cucumber par exemple). Le SDD reprend souvent ce format pour
ses critères, mais ne demande pas qu'ils soient exécutables tels quels, et il
ajoute les étapes plan et tâches. On peut très bien faire du SDD dont les
critères sont des fichiers .feature : c'est même une combinaison solide.
Le contract-first est probablement la forme de SDD que beaucoup d'équipes pratiquent déjà sans le nommer : écrire le fichier OpenAPI avant l'implémentation, le faire valider par les consommateurs de l'API, générer les clients. Le principe est identique, appliqué à une interface au lieu d'un comportement métier. C'est aussi l'esprit du Schema Registry pour Kafka : le schéma est le contrat, le code s'y conforme.
La checklist d'une bonne spec#
Avant de passer au plan, je vérifie ces points :
- Le contexte explique le problème, pas la solution.
- Chaque règle métier est numérotée et tient en une ou deux phrases.
- Chaque critère d'acceptation contient des valeurs concrètes (montants, dates, statuts).
- Au moins un critère couvre un cas limite ou un cas d'erreur.
- Le hors périmètre liste ce qu'on pourrait croire inclus.
- Aucune question ouverte ne reste sans réponse.
- Aucun terme technique d'implémentation (table, endpoint, classe).
- Quelqu'un d'autre que l'auteur l'a relue.
Ce qu'il faut retenir#
Le SDD ne consiste pas à écrire plus de documentation. Il consiste à déplacer les décisions au moment où elles coûtent le moins cher : avant le code, sous forme d'exemples précis, relus par les bonnes personnes. La spec devient le point de référence commun du métier, du développeur, du relecteur et de l'agent de code s'il y en a un.
Pour commencer, pas besoin d'outil ni de processus d'équipe : sur la prochaine feature qui touche à des règles métier, écrire une page avec des règles numérotées, cinq critères chiffrés et une section hors périmètre, et la faire relire avant d'ouvrir l'IDE. Les questions qui surgiront pendant cette relecture sont exactement celles qu'on aurait découvertes en revue de code, ou en production.


