Oltre lo specs-to-code: design concept, ubiquitous language e TDD per non annegare nell'output AI
Tra il 2024 e il 2025 si è formato in Silicon Valley un movimento che ha preso il nome di specs-to-code: la promessa è che lo sviluppo del software si riduca a scrivere una specifica in linguaggio naturale, lasciare che un agente AI la trasformi in codice, e quando qualcosa non funziona modificare solo la spec e rieseguire il "compilatore" probabilistico. Il codice diventa un dettaglio implementativo che nessuno deve più leggere. È una promessa seducente, e non funziona. Ogni iterazione produce codice peggiore della precedente, perché manca un'infrastruttura che non si chiama specifica e che ho visto erodere nei team che adottano l'AI senza disciplina formale.
Nei primi due articoli di questa serie ho misurato il debito di comprensione e ho mostrato la cascata sistemica che produce su sicurezza, produttività dei senior e formazione dei junior. In questo articolo passo dalla diagnosi alla prima parte della risposta tecnica: cinque discipline pre-AI che oggi contano più di prima e che, applicate insieme, invertono la traiettoria di degrado. Sono pratiche con quaranta o vent'anni di letteratura ingegneristica alle spalle. Funzionano perché lavorano sull'unico ingrediente che il modello probabilistico non possiede: l'intento esplicito.
Perché lo "specs-to-code" naïve produce codice peggiore ad ogni iterazione?
Risposta secca: perché lo specs-to-code naïve presuppone che la specifica catturi tutto quello che il sistema deve fare, mentre nella pratica una specifica è la proiezione bidimensionale di un sistema tridimensionale. Ogni rigenerazione che ricostruisce il codice partendo solo dalla specifica perde la dimensione che la spec non aveva mai catturato (le invarianti implicite, le decisioni architettoniche storiche, le interazioni di sistema), e il modello la riempie con pattern matching dal training set, introducendo varianti progressivamente più scollegate dal sistema reale.
Il fenomeno è una manifestazione moderna della software entropy che Hunt e Thomas avevano descritto in The Pragmatic Programmer nel 1999: ogni modifica fatta a un sistema senza pensare al sistema nel suo complesso aumenta il disordine totale. Lo specs-to-code naïve è esattamente questo, applicato a velocità AI: ogni rigenerazione tratta la specifica come un blocco isolato, ignora il design accumulato nel codice esistente, e produce un nuovo blocco di output che incorpora una visione progressivamente più incoerente. Iterazione dopo iterazione, il codice diverge dal design originale e converge verso il pattern medio del training set.
Kent Beck ha sintetizzato il principio inverso in una formulazione che gli ingegneri italiani senior conoscono bene: investire ogni giorno nel design del sistema. Lo specs-to-code naïve fa l'opposto. Disinveste dal design ogni volta che rigenera. La specifica è un asset di cui valorizzi la persistenza; il design è un asset che eroda con ogni rigenerazione che non lo aggiorna esplicitamente. Sul lungo periodo è una scommessa perdente, perché il valore composto di un design coerente cresce esponenzialmente nel tempo (ogni nuova feature è più facile da aggiungere) mentre il valore composto della disgregazione decresce esponenzialmente (ogni nuova feature richiede di capire cosa è successo prima).
Nella mia pipeline personale per task tecnici con Claude Code, la specifica markdown è il primo artifact ma non è mai l'unico. Il codice generato passa attraverso un secondo gate che è la verifica esplicita contro il design accumulato: le decisioni architettoniche registrate nelle ADR del repository, le invarianti di dominio definite nel modello concettuale, e i pattern di integrazione cross-modulo che il sistema ha consolidato nel tempo. Senza questo secondo gate, ogni rigenerazione introduce piccole variazioni che, sommate, producono lo stesso effetto di entropia che vedo nelle codebase AI-coded da otto-dodici mesi: indistinguibilità delle convenzioni, duplicazione strutturale, perdita di senso architettonico.
Cos'è il design concept e perché conta più di qualunque PRD?
Risposta secca: il design concept è la teoria invisibile e condivisa di cosa stai costruendo, vive tra le persone (o tra te e l'agente AI) e non in nessun documento. Conta più di qualunque PRD perché determina come il documento viene letto, interpretato, applicato. Senza design concept condiviso, lo stesso PRD produce cinque implementazioni diverse a seconda di chi lo legge.
Il termine viene da una linea di pensiero classica del software engineering, ma il problema operativo è che il design concept non si scrive: si negozia. Quando due ingegneri umani discutono un'architettura, attraversano un processo iterativo di interrogazione reciproca in cui ciascuno verifica che l'altro abbia interiorizzato la stessa visione del sistema. Le domande non sono retoriche, sono di stress test: cosa succede se la load raddoppia, cosa succede se il provider esterno cambia formato, cosa succede se il regulator chiede una feature di audit. Ogni risposta convergente conferma che il design concept è condiviso. Ogni risposta divergente identifica un'assunzione che non era esplicitata.
Quando il secondo ingegnere è un agente AI, la stessa disciplina è ancora più necessaria, perché l'agente non ha né intuito né memoria persistente del progetto. È diventato un pattern affermato nell'ecosistema di Claude Code lo skill che istruisce l'agente a interrogare l'utente "senza pietà" su ogni branch dell'albero decisionale prima di scrivere codice: 40, 60, fino a 100 domande, una alla volta, ciascuna con una raccomandazione iniziale, ciascuna che risolve una dipendenza tra decisioni. Il flusso conversazionale che ne risulta è abbondante in token e all'apparenza inefficiente, ma è esattamente il processo di costruzione del design concept condiviso che, fatto bene, sostituisce il PRD come asset primario di ingresso.
Se gestisci team di sviluppo che hanno introdotto Claude Code, Cursor o Copilot e ti accorgi che gli output divergono settimana dopo settimana dalla visione architettonica iniziale, nel mio hub dedicato all'AI per aziende sul lato sviluppo trovi il pattern di workflow che applico per costruire e mantenere design concept condiviso fra developer e agenti AI, con artefatti riutilizzabili e gating concreto sul codice generato.
Operativamente, nel mio workflow Claude Code la fase di interrogazione precede sempre la fase di generazione. Ho un set di skill che istruisce l'agente a non scrivere codice fino a quando non ha ricostruito il design concept del task corrente: quali sono i confini del modulo, quali interfacce vengono toccate, quali invarianti devono essere preservate, quali decisioni precedenti vincolano questa nuova. Solo quando l'agente è in grado di formulare il design concept in modo che io riconosca la mia visione del sistema, autorizzo la transizione alla generazione di codice. Funziona meglio del default plan mode, che è eager nel produrre un piano scritto e povero nel verificare che il piano sia condiviso. Il design concept non è un documento, è una conversazione.
Cos'è l'ubiquitous language e come si usa con un LLM?
L'ubiquitous language è il vocabolario condiviso che permette a developer, domain expert e (oggi) agenti AI di parlare dello stesso sistema usando gli stessi termini con gli stessi significati. È un concetto che Eric Evans ha sistematizzato in Domain-Driven Design nel 2003, ed è uno di quei contributi che invecchiano benissimo: vent'anni dopo è esattamente lo strumento che separa il prompt che produce output allineato dal prompt che produce output verboso e generico.
Martin Fowler ha riassunto il punto in modo asciutto: l'ubiquitous language è la pratica di costruire un linguaggio comune e rigoroso fra developer e domain expert, basato sul modello di dominio incorporato nel software. Il problema che risolve è la divergenza linguistica: domain expert che parlano in termini di business mai pienamente assorbiti dal codice, developer che parlano in termini funzionali mai pienamente comprensibili dal business. La conseguenza, in entrambi i casi, è codice che incorpora assunzioni implicite mai esplicitate, e specifiche che nessuno verifica davvero perché vivono in un linguaggio diverso.
Quando il "domain expert" è il developer e l'altro lato della conversazione è un agente AI, il problema linguistico si amplifica. L'agente, di default, parla la lingua del suo training set. La lingua del training set è la lingua media di milioni di repository, e quasi mai è la lingua specifica del tuo dominio di business. Risultato: l'agente è verboso perché deve continuamente disambiguare termini che lui usa in modo generico ma che nel tuo sistema hanno significato preciso. La soluzione, sistematizzata negli ultimi mesi nelle pratiche di Claude Code, è esattamente l'ubiquitous language di Evans applicata all'interazione con l'agente: un file markdown nel repository che contiene il glossario dei termini di dominio, esteso con i termini tecnici specifici del progetto, mantenuto come asset di prima classe.
L'effetto pratico, leggendo le tracce di pensiero dei modelli reasoning quando hanno accesso al glossario, è duplice. Primo: il pensiero diventa meno verboso, perché l'agente non deve più disambiguare ad ogni passaggio. Secondo: l'output di codice diventa più allineato all'implementazione pianificata, perché l'agente sta navigando nello stesso spazio semantico del developer. Nel mio orchestrator interno tengo aperto il file UBIQUITOUS_LANGUAGE.md durante tutte le sessioni di pianificazione, e il glossario stesso evolve con il sistema: nuovi concetti vengono aggiunti man mano che emergono, ambiguità vengono flaggate e risolte esplicitamente, sinonimi vengono ridotti a un termine canonico.
C'è una coppia naturale tra ubiquitous language e framework di prompt strutturato come CO-STAR: il framework definisce la struttura del prompt, l'ubiquitous language ne definisce il vocabolario, e l'output dell'agente diventa molto più predicibile quando entrambi sono presenti.
Quanto vale TypeScript come guardrail contro l'AI slop?
Il dato empirico più solido che abbiamo sul valore di TypeScript come guardrail contro il codice AI-generated viene da un paper di ETH Zurich e UC Berkeley pubblicato nel 2025 con il titolo Type-Constrained Code Generation with Language Models (arXiv:2504.09246). I ricercatori hanno valutato gli LLM su task di sintesi, traduzione e riparazione del codice, e nel benchmark di baseline hanno trovato che, in media, il 94% degli errori di compilazione prodotti dagli LLM sono fallimenti di type check. Non bug logici sottili. Errori di tipo: passaggio di un tipo per un altro, accesso a proprietà inesistenti, signature mismatch.
Letto al contrario, il dato dice una cosa molto chiara: un type system rigoroso intercetta sistematicamente la classe più frequente di errori che l'AI introduce. Ed è esattamente questa proprietà che spiega la dinamica osservata da GitHub nel suo Octoverse 2025, dove TypeScript ha superato Python e JavaScript come linguaggio più contribuito su GitHub, con una crescita anno-su-anno del 66% nei contributor mensili. Non è una preferenza estetica. È un comportamento adattivo del mercato: i developer che usano AI in modo intensivo migrano verso linguaggi tipizzati perché il type checker fa da prima linea di difesa contro l'output probabilistico dell'agente.
Il principio si generalizza: i type system sono solo uno dei feedback loop possibili. Per applicazioni frontend, dare all'agente accesso a un browser per ispezionare il rendering reale è un altro feedback loop. Per backend, gli automated test sono il feedback loop classico. La proprietà comune è che ogni feedback loop accorcia il ciclo tra "ho generato codice" e "so se funziona". Più stretto è il ciclo, meno spazio ha l'agente per accumulare assunzioni divergenti prima di essere corretto.
Hunt e Thomas avevano formulato la regola operativa nel modo più preciso possibile: il rate di feedback è il tuo limite di velocità. Se gli passi davanti, stai outrunning your headlights, stai guidando più veloce di quanto la luce dei tuoi fari illumini la strada. L'incidente è una questione di tempo. Gli agenti AI di default outrunnan i loro headlights costantemente: producono grandi quantità di codice e poi pensano "forse dovrei fare type check" o "forse dovrei eseguire un test". Il setup ingegneristicamente corretto è invertire l'ordine: l'agente non avanza fino a quando il feedback loop non ha confermato che l'ultimo passo è solido.
Nel mio MCP server di code review automatizzata ho stratificato tre feedback loop per ogni patch generata: type check immediato (TypeScript/PHPStan/mypy a seconda del linguaggio), unit test sul perimetro modificato, e validazione semantica contro le ADR del repository. Una patch può avanzare al commit solo se attraversa tutti e tre. È più lento del flow "genera-tutto-e-controlla-alla-fine", ma il throughput finale è più alto perché elimina il tempo che lo stesso flow lento spende in iterazioni di debug a posteriori.
Perché il TDD diventa più importante con l'AI, non meno?
Il Test-Driven Development non era una pratica universale prima dell'AI, e di certo non lo era nelle codebase italiane di PMI che ho audito negli ultimi cinque anni. La domanda razionale è: se non era universale prima, perché dovrebbe diventarlo ora? La risposta è strutturale al modo in cui l'AI scrive codice. L'agente, per natura del suo training, tende a produrre grosse quantità di codice in un singolo passaggio. La forma di pensiero "scrivo un test, lo faccio passare, refactorizzo, scrivo il prossimo test" è opposta a quella default dell'agente. TDD è il vincolo metodologico che forza l'agente a passi piccoli e deliberati.
Quando istruisci l'agente a operare in modalità TDD, il flusso cambia in modo netto: prima il test che descrive il comportamento desiderato, poi il codice minimo per farlo passare, poi il refactoring per consolidare il design. Tre passi distinti, ciascuno con un feedback loop esplicito (il test che fallisce, il test che passa, la suite che resta verde dopo il refactor). L'agente che lavora così non può "outrunning the headlights" perché il test scritto prima funge da contratto operativo del passo successivo.
C'è una proprietà sistemica del TDD che diventa cruciale nell'era AI: una codebase ben testata è una codebase facile da modificare, perché ogni cambiamento ha un riferimento esplicito di cosa deve continuare a funzionare. Una codebase facile da modificare è una codebase su cui l'AI può lavorare senza distruggere assunzioni implicite, perché le assunzioni sono esplicitate nei test. Il loop si chiude: il TDD non è solo una disciplina che migliora il codice scritto, è la pratica che rende possibile la collaborazione efficiente con l'agente AI.
Lo svantaggio onesto: TDD è difficile, e il fatto che sia difficile non è un caso. Scrivere un test prima del codice richiede di decidere a priori quale unità testare, cosa mockare, quali comportamenti specificare. Sono decisioni interdipendenti, e in una codebase mal strutturata sono insormontabili. Per questo TDD funziona davvero solo in codebase che hanno struttura modulare pulita: moduli con interfacce chiare e implementazioni nascoste sono testabili al confine; moduli con interfacce complesse e implementazioni distribuite sono testabili solo a costo di mock estensivi che fragilizzano la suite. Tornerò sui dettagli architetturali nel prossimo articolo della serie, dove discuto di moduli profondi e dell'integrazione AI come dependency strutturale, non come tool di produzione.
Le cinque discipline che ho descritto in questo articolo (rifiuto dello specs-to-code naïve, design concept condiviso, ubiquitous language, feedback loops stratificati, TDD) non sono novità del 2026. Erano la stack ingegneristica raccomandata da chiunque avesse esperienza profonda di software design già negli anni 2000. Quello che è cambiato è il margine di errore: prima dell'AI il team poteva permettersi di applicarle in modo intermittente, perché il codice scritto da umani esperti aveva una sua coerenza intrinseca anche quando la disciplina formale era ridotta. Ora, con il codice generato da modelli probabilistici che non hanno memoria persistente del progetto, l'intermittenza non è più un'opzione: o queste pratiche sono parte stabile del workflow, o l'output AI accelera il debito di comprensione invece di accelerare il valore. Se la tua organizzazione sta valutando come strutturare il workflow AI per gli sviluppatori senior e junior insieme, e ti rendi conto che le pratiche pre-AI dovrebbero essere rinforzate proprio mentre il marketing AI dice il contrario, il modulo di preventivo gratuito ti permette di descrivere lo stato attuale del tuo team e ti rispondo direttamente quale di queste cinque discipline è il punto di leva più alto per il tuo contesto specifico. Nel prossimo articolo passo dalle discipline di processo all'architettura del codebase: deep modules, AI come dependency, e il debito che nessun modello futuro refactorerà al posto tuo.