Nel software si parla di progetto greenfield quando si parte da un foglio bianco, e di brownfield quando si eredita un sistema esistente. Il primo è quasi sempre preferito da chi scrive codice, ed è facile capire perché: nessun compromesso ereditato, nessuna scorciatoia altrui, tecnologie scelte oggi e non cinque anni fa.
Vale però la pena essere precisi su cosa si compra e cosa si perde, perché la voce di costo principale non è quella che si immagina.
Quello che si perde davvero#
Che cosa si perde davvero in una riscrittura. Non il codice — quellosi rilegge. Le REGOLE IMPLICITE, che non stanno scritte da nessuna parte: if (cliente.codice.startsWith('X') && data < '2019-01-01') { salta = true; // ← perché? } - il caso limite gestito nel 2019 dopo una segnalazione; - l'eccezione per quel cliente che ha un contratto diverso; - il controllo aggiunto dopo l'incidente del venerdì; - il ritardo di 200 ms messo lì perché il fornitore rispondeva prima di aver finito di scrivere.Ogni riga di questo tipo costa un incidente. Il sistema esistente necontiene centinaia, e la loro somma è la ragione per cui funziona.La riscrittura riparte da zero incidenti — e li rifà tutti.Questa è la ragione per cui le riscritture sforano i tempi in modo così sistematico da essere quasi una legge di natura. Il preventivo si basa sulle funzionalità, che sono note e si contano. Il tempo se ne va nelle regole implicite, che non sono note e non si contano — si riscoprono, una per una, ciascuna al costo di un utente arrabbiato.
Archeologia: recuperarle prima#
# Prima di riscrivere: l'archeologia. La storia del codice contiene# le informazioni che la documentazione non ha.# 1. Chi ha scritto questa riga strana, e in quale commit?git log -p -L 245,252:src/fatturazione.jscommit 8d10cc4 2019-03-14 Escludi i clienti X dal ricalcolo retroattivo Segnalato da Contabilità: il ricalcolo applicava le nuove condizioni anche ai contratti chiusi prima del 2019. Vedi #287.# 2. Dove si concentra il dolore: i file toccati piu' spesso sono# quelli che il nuovo sistema DEVE gestire meglio.git log --format=format: --name-only --since='2 years ago' \> | grep -v '^$' | sort | uniq -c | sort -rn | head -5 287 src/fatturazione.js 193 src/export/tracciato.js 41 src/auth.js# 3. I commit che dicono "fix" sui file critici: sono l'elenco dei# casi limite che il nuovo sistema dovra' superare.git log --oneline --grep='fix\|bug\|hotfix' -- src/fatturazione.js | wc -l64La seconda query merita attenzione perché produce un'informazione che nessuno ha: i file toccati più spesso sono quelli dove il dominio è più instabile — e sono esattamente quelli che il nuovo sistema deve gestire meglio, non semplicemente di nuovo. Se il nuovo progetto ha la stessa struttura del vecchio in quei punti, la riscrittura non risolverà niente.
L'alternativa: sostituire un pezzo alla volta#
// L'alternativa alla riscrittura totale: il nuovo cresce ATTORNO al// vecchio, che resta in produzione finché non è stato svuotato.//// ┌─────────── proxy ───────────┐// │ /api/fatture/* ──▶ NUOVO │ ← migrato, in produzione// │ /api/ordini/* ──▶ NUOVO │ ← migrato// │ /api/* ──▶ VECCHIO│ ← tutto il resto, intatto// └─────────────────────────────┘//// Ogni pezzo migra quando è pronto, e il rischio di ogni passo è// piccolo e reversibile. Il contrario del "grande giorno".app.use('/api/fatture', proxy(NUOVO));app.use('/api/ordini', proxy(NUOVO));app.use('/api', proxy(VECCHIO)); // rete di sicurezzaIl modello è noto come strangler fig, dalla pianta che cresce attorno a un albero fino a sostituirlo. Il vecchio sistema resta in produzione e continua a servire tutto ciò che non è stato ancora migrato; ogni pezzo passa al nuovo quando è pronto, e se qualcosa non va si torna indietro cambiando una riga di instradamento.
Il vantaggio non è tecnico ma di gestione del rischio: si sostituisce un evento raro e catastrofico — il «grande giorno» in cui tutto passa al nuovo sistema — con una serie di eventi piccoli e reversibili. E ogni passo produce valore subito, invece di produrlo tutto alla fine, che è anche il motivo per cui il progetto non viene cancellato a metà.
La tecnica che rende la migrazione verificabile#
// La tecnica che rende la migrazione verificabile invece che sperata:// il nuovo sistema riceve il traffico VERO in parallelo, e il suo// risultato viene confrontato — ma non servito.app.post('/api/calcola', async (req, res) => { const vecchio = await sistemaVecchio.calcola(req.body); // Il nuovo gira in parallelo. Non blocca, non può rompere niente. sistemaNuovo.calcola(req.body) .then(nuovo => { if (JSON.stringify(nuovo) !== JSON.stringify(vecchio)) { registro.divergenza({ input: req.body, vecchio, nuovo }); } }) .catch(e => registro.erroreNuovo(e)); res.json(vecchio); // ← l'utente riceve sempre il vecchio});// Dopo due settimane il registro delle divergenze è l'elenco esatto// delle regole implicite che il nuovo sistema non conosceva. È la// specifica che nessuno aveva mai scritto — ricavata dai dati veri,// non dalla memoria di qualcuno.Questa è la parte più preziosa dell'intero approccio, e viene usata molto meno di quanto meriti. Il nuovo sistema riceve il traffico vero, con i dati veri e i casi limite veri, ma le sue risposte non vengono servite a nessuno: vengono confrontate.
Dopo due settimane, il registro delle divergenze è la specifica che nessuno aveva mai scritto — ricavata dal comportamento osservato invece che dalla memoria di qualcuno. Ogni riga è una regola implicita che il nuovo sistema non conosceva, con l'input esatto che la fa emergere. È il modo per scoprire i casi limite prima che li scoprano gli utenti.
Quando la ricostruzione totale ha senso#
Quando la ricostruzione totale è la scelta giusta: ✓ Il dominio è ancora ben compreso da chi c'è adesso (le regole implicite si possono ricostruire perché qualcuno le ricorda, o perché sono poche) ✓ Il sistema è piccolo abbastanza da essere riscritto in settimane, non in trimestri ✓ Il vincolo è tecnologico e insormontabile: una piattaforma non più supportata, un linguaggio senza aggiornamenti di sicurezza, una dipendenza abbandonata ✓ I requisiti sono CAMBIATI: non state riscrivendo lo stesso sistema, ne state costruendo uno diversoQuando invece conviene sostituire un pezzo alla volta: ✗ Il sistema ha più di qualche anno e nessuno lo conosce per intero ✗ Contiene logica di dominio complessa (fiscale, contrattuale, normativa) accumulata nel tempo ✗ Deve restare in funzione durante tutta la transizione ✗ Il motivo della riscrittura è "il codice è brutto" — che è un giudizio estetico, non una misuraLa riga da sottolineare è l'ultima della seconda lista. «Il codice è brutto» è la motivazione più diffusa delle riscritture e la meno solida: è un giudizio estetico, spesso reversibile con una rifattorizzazione mirata, e non predice il costo di manutenzione. Le misure che lo predicono sono altre — il tempo per aggiungere una funzionalità piccola, il tasso di regressione dopo ogni modifica, il numero di persone che sanno toccare quel file — e valgono la pena di essere raccolte prima di decidere.
Le tre trappole del foglio bianco#
// Le tre trappole del foglio bianco, in ordine di frequenza.// 1. Costruire per requisiti immaginari.// Senza un sistema esistente che dice cosa serve davvero, si// costruisce per quello che POTREBBE servire. Ogni astrazione// "per il futuro" è una scommessa, e la maggior parte si perde.// 2. Scegliere lo stack per curiosità.// Il foglio bianco è l'occasione di provare quella tecnologia// nuova. È anche il progetto che qualcun altro manterrà.// Il criterio non è "qual è il migliore" ma "chi lo manterrà// fra tre anni, e cosa sa fare?".// 3. Rimandare le fondamenta.// Log strutturati, gestione degli errori, migrazioni, ambienti,// backup verificati: sul foglio bianco costano un giorno.// Aggiunti dopo costano una settimana ciascuno, e vengono// aggiunti solo dopo il primo incidente che li rendeva necessari.La seconda è quella su cui vale la pena essere severi con sé stessi. Il progetto nuovo è l'occasione di usare la tecnologia che si voleva provare; è anche il progetto che qualcuno dovrà mantenere per anni. Il criterio non è «qual è la scelta migliore in astratto» ma «chi lo manterrà fra tre anni, e con che cosa sa lavorare» — una domanda che ha risposte diverse a seconda che il committente abbia un reparto tecnico o si rivolga di volta in volta a un professionista esterno.
Le fondamenta del primo giorno#
# Le fondamenta che vale la pena mettere il primo giorno, quando# costano un'ora. Dopo, ciascuna costa giorni.git init && npm init -y# 1. Un solo comando per avviare. Se ne servono tre, in due# settimane nessuno ricordera' l'ordine.cat package.json | grep -A3 '"scripts"' "scripts": { "avvia": "node server.js", "build": "node build.cjs", "verifica": "node --test && node scripts/verifica-pubblico.sh"# 2. Configurazione dall'ambiente, con un esempio versionato.cat .env.esempioPORT=3000DATABASE_URL=file:./dati.db# CHIAVE_API= chiedere a <chi># 3. Un endpoint di salute dal primo giorno: e' cio' che il# monitoraggio interroghera' fra sei mesi.curl -s localhost:3000/api/health{"status":"ok","uptime":12.4}# 4. Un test che gira. Uno solo, ma che gira in CI: la prima volta# e' facile, la centesima e' impossibile.| Situazione | Approccio | Rischio principale |
|---|---|---|
| Prodotto nuovo, dominio nuovo | greenfield | costruire per requisiti immaginari |
| Sistema piccolo, dominio noto, tecnologia obsoleta | greenfield | sottovalutare i casi limite |
| Gestionale stratificato, logica normativa | un pezzo alla volta | la transizione dura più del previsto |
| Deve restare in funzione durante la migrazione | un pezzo alla volta | due sistemi da mantenere insieme |
| Il motivo è «il codice è brutto» | rifattorizzare | riscrivere e ritrovarsi allo stesso punto |
| I requisiti sono cambiati davvero | greenfield | nessuno: non è una riscrittura |
L'ultima riga è la distinzione che scioglie molte discussioni. Se il sistema nuovo deve fare le stesse cose del vecchio, è una riscrittura, e le riscritture hanno il profilo di rischio descritto sopra. Se deve fare cose diverse, non state riscrivendo niente: state costruendo un prodotto nuovo, e il vecchio è al massimo una fonte di informazioni sul dominio.