Che cosa succede al vostro progetto se chi lo ha realizzato non è più raggiungibile, o se serve un'altra persona per estenderlo? Se il progetto è una scatola nera, la risposta è che siete in ostaggio — non di qualcuno in malafede, ma della semplice mancanza di una mappa.
Il problema è che quasi tutte le documentazioni esistono e non servono. Vale la pena partire da come si distingue una cosa dall'altra.
La prova del nove#
La prova del nove di una documentazione utile, e l'unica che conta: Una persona competente, che non ha mai visto questo progetto, riesce a metterlo in esecuzione in locale e a pubblicare una modifica banale — senza chiedere niente a nessuno?Se la risposta è no, la documentazione non esiste: esiste un testo.Il modo per scoprirlo non è rileggere il documento. È darlo aqualcuno e stare zitti mentre ci prova, annotando ogni punto in cuisi blocca. Ogni interruzione è una riga mancante.Il README che supera la prova#
# Nome del progettoUna riga: che cosa fa e per chi.## Avvio in locale git clone … npm ci cp .env.esempio .env # i valori da chiedere a: <chi> npm run avvia # → http://localhost:3000## Struttura server.js rotte, header, file statici site/layout.js shell HTML condivisa (head, navbar, footer, SEO) site/pages.js pagine principali site/articoli/ un file per articolo del blog src/js, src/css SORGENTI — mai serviti in chiaro public/*-build artefatti di build — questi sì dati/ immagini## Pubblicazione npm run build # obbligatorio dopo ogni modifica a src/ rsync -avz --exclude src --exclude .claude ./ utente@host:/home/sito/ ssh utente@host 'cd /home/sito && npm ci --omit=dev && pm2 restart sito'## Decisioni che sorprendono- Nessun framework frontend: vedi docs/adr/0003.- SQLite e non PostgreSQL: una macchina sola, carico in lettura.- Il JS è offuscato: scelta del committente, non requisito tecnico.## Quando qualcosa non va- Il sito non riparte → pm2 logs sito --lines 100- Le modifiche non si vedono → manca npm run build- 502 dal proxy → il processo node è caduto: pm2 listQuattro sezioni, e la struttura non è casuale. Avvio per primo, perché è la prima cosa che serve. Struttura come mappa per orientarsi, con la responsabilità di ciascuna cartella e non solo il nome. Pubblicazione con i comandi esatti, non con una descrizione a parole. E soprattutto la quarta.
«Decisioni che sorprendono» è la sezione che nessuno scrive e che vale più delle altre tre. Elenca le scelte non ovvie che qualcuno, arrivando, sarebbe tentato di «correggere» — riaprendo il problema che quella scelta risolveva.
Il registro delle decisioni#
Perché un registro delle decisioni vale più di un diagramma: Un diagramma descrive com'è il sistema OGGI. Il codice descrive com'è il sistema oggi, e meglio. Nessuno dei due dice PERCHÉ.E il perché è l'unica informazione che non si ricava rileggendo.Senza, ogni scelta non ovvia diventa, per chi arriva dopo: - un errore da correggere (e la correzione riapre il problema che quella scelta risolveva), oppure - un tabù da non toccare, che nessuno osa rimuovere nemmeno quando la ragione è scaduta da anni.Un ADR è mezza pagina. Si scrive nel commit che introduce la scelta,e non si aggiorna mai: se la decisione cambia, se ne scrive un altroche supera il precedente. La storia resta leggibile.# 0003 — Nessun framework frontendStato: accettataData: 2026-01-15## ContestoSito multi-pagina, contenuto prevalentemente statico, interattivitàlimitata a: rivelazione dei contatti, canvas decorativo, indicelaterale degli articoli. Nessuno stato condiviso fra schermate.## DecisioneFrontend in JavaScript nativo, moduli ES compilati in un bundle unicocon esbuild. Nessuna dipendenza runtime nel browser oltre a GSAP.## Conseguenze+ Bundle di ~17 KB contro i ~140 KB di un framework medio+ Nessun aggiornamento di dipendenze frontend da inseguire− Un eventuale passaggio a interfacce con stato condiviso richiede una riscrittura, non una migrazione− Chi conosce solo React ha una curva di ingresso su questo progetto## Quando riconsiderarlaSe compaiono più di due schermate che condividono lo stesso stato,o se la sincronizzazione manuale del DOM supera i tre punti perazione. Vedi /blog/frontend-vanilla-typescript-vite.Mezza pagina, cinque sezioni. Quella che fa la differenza è l'ultima: «quando riconsiderarla». Trasforma una decisione da dogma in ipotesi con una data di scadenza, e dà a chi arriverà dopo il permesso esplicito di cambiarla — insieme al criterio per capire se è il momento.
Un ADR non si aggiorna mai. Se la decisione cambia, se ne scrive uno nuovo che dichiara di superare il precedente, e il vecchio resta con stato «superata». La storia del ragionamento resta leggibile, ed è spesso più utile della conclusione.
Il vero problema: la divergenza#
# Il problema della documentazione non e' scriverla: e' che diverge.# Un documento che descrive un sistema che non esiste piu' e' PEGGIO# dell'assenza di documentazione, perche' chi lo legge prende# decisioni sbagliate con fiducia.# Rimedio 1 — sta nel repository, e si aggiorna nello STESSO commit.# Se aggiornarla e' un'attivita' separata, non si fa.git show --stat HEAD site/articoli/index.js | 2 +- README.md | 4 +-- ← nello stesso commit# Rimedio 2 — i comandi nel README sono ESEGUIBILI e vengono eseguiti.# Un blocco di comandi che nessuno prova e' un blocco che marcisce.grep -A3 '## Avvio in locale' README.md | tail -3 | sh -n# (in CI: si estraggono e si eseguono davvero su un ambiente pulito)# Rimedio 3 — la struttura delle cartelle si genera, non si trascrive.tree -L 2 -I 'node_modules|.git' --dirsfirstCiò che non va scritto a mano#
// La regola generale: quello che si può ricavare dal codice NON va// scritto a mano, perché è esattamente la parte che diverge per prima.// ✓ Generabile — non scrivetelo:// - elenco delle rotte → dal router// - schema del database → dalle migrazioni// - elenco delle dipendenze → da package.json// - variabili d'ambiente usate → da una ricerca su process.env// - struttura delle cartelle → da tree// Esempio: le rotte, ricavate dal router invece che trascritte.function elencoRotte(app) { return app.router.stack .filter(s => s.route) .map(s => `${Object.keys(s.route.methods)[0].toUpperCase()} ${s.route.path}`) .sort();}// npm run rotte > docs/rotte.txt ← e in CI si verifica che sia aggiornato// ✗ NON generabile — è questo che va scritto a mano, ed è poco:// - perché una scelta è stata fatta// - cosa succede se si sbaglia// - chi contattare quando si rompe// - quali vincoli non sono negoziabili e perchéÈ la regola operativa che risolve la divergenza alla radice: l'elenco delle rotte, lo schema, le dipendenze e la struttura delle cartelle non si trascrivono. Si generano, e in integrazione continua si verifica che il file generato coincida con quello nel repository. Se non coincide, la build fallisce — e la documentazione non può più divergere, perché nessuno può pubblicare senza allinearla.
Quello che resta da scrivere a mano è poco, e non è ricavabile da nessuna parte: perché una scelta è stata fatta, cosa succede se si sbaglia, chi contattare, quali vincoli non sono negoziabili.
Il runbook#
## Il processo non riparte dopo il deploySintomo: il proxy risponde 502, pm2 list mostra "errored".1. Guardare l'errore vero, non il 502: pm2 logs sito --lines 100 --err2. Cause in ordine di frequenza: a) manca una dipendenza → "npm ci --omit=dev" non è stato eseguito b) la porta 3000 è occupata da un processo zombie → "lsof -i :3000" c) una variabile d'ambiente è assente → il codice fallisce all'avvio3. Rientro rapido (se la causa non è chiara e il sito è giù): cd /home/sito && git checkout <tag-precedente> && npm ci --omit=dev pm2 restart sito4. Verifica: curl -s -o /dev/null -w '%{http_code}' https://www.esempio.it/api/healthQuesta pagina si scrive DOPO il primo incidente, non prima: soloallora si sa quali sono le cause reali e in quale ordine.La documentazione operativa ha una regola sua: si scrive dopo. Un runbook redatto in anticipo elenca le cause che si immaginano; uno scritto dopo il primo incidente elenca quelle che si sono verificate davvero, nell'ordine in cui conviene controllarle. E la sezione più preziosa è il «rientro rapido»: cosa fare quando il sito è giù, la causa non è chiara e la priorità è tornare online, non capire.
| Documento | Risponde a | Si aggiorna | Se manca |
|---|---|---|---|
| README | come lo avvio, dove sta cosa | a ogni cambio di struttura | una settimana di lettura del codice |
| ADR | perché è fatto così | mai: se ne scrive uno nuovo | le scelte vengono disfatte e rifatte |
| Runbook | è rotto, cosa faccio | dopo ogni incidente | si improvvisa alle tre di notte |
| .env.esempio | quali variabili servono | con il codice che le legge | errori all'avvio senza spiegazione |
| Rotte / schema | cosa espone, com'è fatto | generati | divergono entro un mese |
Una nota finale sul destinatario, che cambia il modo di scrivere: il lettore più probabile di questa documentazione siete voi fra diciotto mesi, sotto pressione, dopo aver lavorato su altri tre progetti. Non ricorderete perché quella riga è lì. Scrivere per quella persona — che è competente ma ha dimenticato tutto — produce documenti molto più utili che scrivere per un ipotetico nuovo collaboratore.