Riscrivere o rifattorizzare software legacy: come decidere
Matteo Migliore

Matteo Migliore è un imprenditore e architetto software con oltre 27 anni di esperienza nello sviluppo di soluzioni basate su .NET e nell'evoluzione di architetture applicative per imprese e organizzazioni di alto profilo.

Ha guidato progetti enterprise, formato centinaia di sviluppatori e aiutato aziende di ogni dimensione a semplificare la complessità trasformando il software in guadagni per il business.

Un'azienda di serramenti di Treviso, 62 dipendenti, 24 milioni di fatturato, usa da dodici anni un gestionale di commesse scritto in C#: un client WPF, dei servizi WCF e un database SQL Server con 190 stored procedure. Sono 220.000 righe su .NET Framework 4.7. Ogni modifica richiede il 70% di tempo in più di quanto richiederebbe su un'applicazione moderna, e l'unico sviluppatore che conosce tutto il codice ha detto che tra due anni vuole fare altro.

Il titolare ha sentito due voci. Il consulente di un fornitore dice di riscrivere tutto, perché "su una base così non si costruisce". Lo sviluppatore interno dice di rifattorizzare, perché "il codice funziona e le regole sono tutte lì dentro". Hanno ragione entrambi, e la discussione non finisce mai, perché si litiga sul gusto e non su un numero.

Il numero esiste, ed è meno tecnico di quanto ci si aspetta: gli anni di vita che restano al software. Se il gestionale deve vivere meno di una certa soglia, rifattorizzare costa meno. Se deve vivere di più, costa meno riscrivere. La soglia, nel caso di Treviso, è di 4,6 anni, e questo articolo spiega da dove viene, come si sposta e come si misura.

Trovi il conto in euro delle tre strade (non fare niente, rifattorizzare, riscrivere), la misura che dice quanto è costoso rifattorizzare il tuo codice, le ipotesi che spostano di più il risultato e il modo di lavorare sul codice caldo senza rompere niente. Trovi anche il modello in C# per rifare il calcolo con i tuoi dati.

Una premessa onesta. Dal 1999 modernizzo sistemi in produzione da quindici anni, e ho costruito e venduto un gestionale legale multi-tenant. Il caso di Treviso è composto, non è di un cliente reale, ma i rapporti tra le cifre sono quelli che ritrovo ogni volta. Le ipotesi sono scritte una per una, così puoi sostituirle con le tue.

Che cosa significano davvero "rifattorizzare" e "riscrivere"?

Le due parole si usano come se fossero chiare, e non lo sono. Se due persone in una riunione le intendono in modo diverso, il preventivo che ne esce non confronta niente.

Rifattorizzare vuol dire cambiare la struttura del codice senza cambiare quello che fa. Martin Fowler lo definisce così nel suo libro sul tema: una serie di piccoli passi che mantengono il comportamento, ciascuno verificato da test. Il sistema resta in produzione per tutto il tempo e gli utenti non si accorgono di niente. Non è un progetto con una data di consegna: è un lavoro continuo, che si fa insieme alle modifiche richieste dal business.

Riscrivere vuol dire costruire un sistema nuovo che sostituisce quello vecchio. Qui ci sono due varianti che costano in modo molto diverso. La riscrittura "a scatto" sostituisce tutto in un giorno, dopo uno o due anni di sviluppo parallelo. La riscrittura "a moduli", quella del Strangler Fig pattern, sostituisce una funzione alla volta dietro una facciata. In questo articolo "riscrivere" significa la prima, perché la seconda ha già il suo conto, e la ritroveremo nell'ultima domanda dell'albero di decisione.

Esiste poi una terza voce che quasi nessun preventivo scrive: non fare niente. Non è un'opzione neutra. Costa ogni mese, perché ogni modifica paga la tassa del legacy. È il punto di partenza di ogni confronto, e se manca il confronto è truccato.

StradaChe cosa cambiaQuando arriva il beneficioChe cosa si rischia
Non fare nienteNienteMaiLa tassa del legacy ogni mese, e la persona chiave che se ne va
RifattorizzareLa struttura del codice che si tocca di piùDopo 2-3 mesi, poi in modo continuoPoco: ogni passo si annulla. Ma il sistema resta su una piattaforma vecchia
Riscrivere a scattoTutto, in un colpoAlla fine, dopo 18-24 mesiMolto: blocco delle modifiche, regole perse, progetto che si ferma

Fra le righe della tabella c'è la ragione per cui il confronto non si può fare a sentimento. Rifattorizzare costa poco e rende poco per volta. Riscrivere costa molto e rende molto, ma solo se arriva in fondo. Quale delle due vince dipende da quanto a lungo godrai del beneficio, e questo è il numero di cui parla l'articolo.

Perché la domanda "riscrivere o rifattorizzare" è posta male?

Quando in una riunione qualcuno dice "bisogna riscrivere", di solito sta parlando del codice. Dice che è brutto, che nessuno lo capisce, che le variabili si chiamano tmp2 e flag_ok. Quando qualcun altro risponde "basta rifattorizzare", sta parlando dello stesso codice, e dice che si può ripulire. Si discute della qualità del codice, e la qualità del codice non decide niente da sola.

Quello che decide è il flusso di denaro nel tempo. Ogni strada ha un costo di partenza e una tassa annuale sulle modifiche. Il codice brutto conta solo perché fa salire la tassa. Un modulo orribile che nessuno tocca da cinque anni non costa niente, e rifarlo sarebbe una spesa pura.

Nel 2000 Joel Spolsky scrisse un articolo diventato famoso, in cui definiva la riscrittura da zero il peggior errore strategico che un'azienda di software possa commettere, e portava l'esempio di Netscape, che per rifare il suo browser lasciò il campo ai concorrenti per circa tre anni. Ha ragione sul rischio, e ha ragione sul fatto che il vecchio codice contiene conoscenza. Non ha un numero che dica quando la riscrittura invece conviene, e quel numero esiste: dipende da quanto vive il sistema.

Michael Feathers, nel suo libro sul codice legacy, dà una definizione che ho trovato molto utile: legacy è il codice senza test. Non il codice vecchio, non il codice brutto, ma il codice che non puoi cambiare con fiducia. Se accetti la definizione, la domanda cambia. Non chiede più "lo riscriviamo?", chiede "quanto costa portare questo codice in uno stato in cui lo posso cambiare con fiducia, e quanto dura il beneficio?".

Ci sono tre domande, e vanno in questo ordine.

  1. Per quanti anni terrò vivo questo software? Se la risposta è "non lo so", il resto non serve: la prima misura è scrivere una data e una ragione.
  2. Quanta della logica che si tocca di più si può provare senza aprire una form e senza un database? Dalla risposta dipende quanto costa rifattorizzare.
  3. La piattaforma regge ancora? Se il sistema gira su qualcosa che non riceve più aggiornamenti di sicurezza o che non si può installare su un server nuovo, né la pulizia né la riscrittura risolvono il problema: va prima spostata la base.

Le prime due le trasformiamo in numeri nelle prossime sezioni. La terza è un sì o un no, e se è un no cambia il percorso, come si vede nell'albero di decisione verso la fine. Prima, però, ci serve il caso.

Quali sono le ipotesi del caso di Treviso?

Per confrontare tre strade servono ipotesi esplicite. Le metto tutte qui, in una tabella, perché sono il punto più discutibile del calcolo e voglio che tu possa contestarle una per una.

IpotesiValoreCome l'ho scelta
Dimensione del sistema220.000 righe di C#, T-SQL e XAMLUn gestionale di commesse di una PMI di produzione, dopo dodici anni
Modifiche richieste ogni anno500 giornate, misurate a velocità "moderna"Circa due persone a tempo pieno su correzioni ed evoluzioni
Tariffa media420 euro a giornataMix di interni ed esterni
Moltiplicatore del legacy1,7Ogni modifica costa il 70% in più, per orientamento, regressioni e collaudi manuali
Infrastruttura14.000 euro l'anno sul vecchio, 9.000 sul nuovoServer Windows, licenze, backup
Codice caldoIl 30% delle righe riceve il 75% delle modificheRegola che ritrovo quasi ovunque. Si misura con la storia di Git
Giornate per 1.000 righe, logica isolabile1,0Solo scrivere test di caratterizzazione: il codice è già separato
Giornate per 1.000 righe, logica incastrata6,0Prima si scrivono i test, poi si estrae la logica da form e stored procedure
Avvio del rifattorizzare60 giornatePipeline di build, test di sistema, misura iniziale
Moltiplicatore del codice caldo dopo1,2Il codice ripulito costa ancora un poco più di uno nuovo: la piattaforma resta vecchia
Riscrittura3,5 giornate per 1.000 righe, cioè 770 giornateSistema nuovo, senza riuso del codice, con l'apprendimento delle regole
Blocco durante la riscritturaIl 40% delle modifiche non si fa per 18 mesiIl resto si fa sul vecchio, a costo pieno
Probabilità che la riscrittura si fermi25%Opinione di mestiere, la discuto nella sezione sulla robustezza

Due ipotesi meritano una parola. La prima è il 30% delle righe che riceve il 75% delle modifiche. Nel gestionale di Treviso significa 66.000 righe calde su 220.000. Questa concentrazione è il motivo per cui rifattorizzare costa poco: non si pulisce tutto il codice, si pulisce quello dove si lavora. Se nel tuo sistema le modifiche sono distribuite in modo uniforme, il calcolo cambia, e per questo l'articolo sul Strangler Fig parte proprio da quella misura.

La seconda è la differenza tra 1,0 e 6,0 giornate per 1.000 righe. È l'intuizione centrale dell'articolo: estrarre la logica da una form o da una stored procedure costa sei volte di più che metterla sotto test quando è già in una classe a parte. Con 3,5 giornate per 1.000 righe la riscrittura sta in mezzo, e per questo la quota di codice isolabile sposta il risultato.

Il resto sono costi che chiunque può verificare sulla propria contabilità. Il conto parte da qui.

Quanto costa non fare niente?

Il punto di partenza è il costo annuale del sistema com'è oggi. Il metodo esteso sta nella guida al costo del software legacy. Qui ne uso la forma corta.

500 giornate × 1,7 × 420 euro = 357.000 euro, più 14.000 di infrastruttura, cioè 371.000 euro l'anno. In cinque anni sono 1.855.000 euro, in otto 2.968.000.

Prima di commentare il numero, una precisazione che evita un malinteso comune: non è denaro buttato. Cinquecento giornate di modifiche servirebbero anche con un sistema nuovo, perché il business cambia. La parte che un sistema migliore toglie è la differenza tra 1,7 e 1,0, cioè 147.000 euro l'anno di attrito, più 5.000 di infrastruttura. Questo è il tesoro che le due strade provano a recuperare.

Il secondo malinteso è considerare il conto fisso. Non lo è, per tre ragioni che puntano nella stessa direzione.

  • La persona chiave. Lo sviluppatore interno che conosce tutto e vuole andarsene non è un rischio astratto. Quando se ne va, il moltiplicatore sale perché nessuno sa dove mettere le mani, e l'attrito passa da 1,7 a 2,2 in pochi mesi. Con 500 giornate l'anno sono 105.000 euro in più.
  • La piattaforma. .NET Framework 4.7 riceve ancora correzioni di sicurezza dentro Windows, ma ogni componente di terze parti che smette di supportarlo chiude una porta. Nel conto non c'è il giorno in cui un controllo grafico non si installa più.
  • Il business. Le richieste sono sempre più difficili da soddisfare: integrazione con un portale, firma digitale, fatturazione elettronica nuova. Ogni volta si paga un'integrazione "a mano" che un'architettura moderna darebbe quasi gratis.

Quindi 371.000 euro l'anno è la stima più ottimistica del costo di non fare niente: i tre fenomeni lo fanno solo salire. Se la stima ti sembra alta, ti consiglio di fare l'esercizio con i tuoi numeri prima di continuare, perché ogni cifra successiva si confronta con questa.

Un sistema legacy non costa perché è vecchio. Costa perché ogni modifica paga una tassa del 70%, e la paga ogni volta.

Quanto costa rifattorizzare, e da che cosa dipende?

Qui entra il numero che sposta tutto il calcolo: la quota di codice caldo la cui logica di business è isolabile. Chiamo isolabile una classe che si può provare con un test automatico senza aprire una form, senza una connessione a un database, senza il contesto di una pagina web. Il suo contrario è la logica incastrata: il calcolo dello sconto dentro il click di un pulsante, la regola di prezzo dentro una stored procedure di 600 righe, la validazione fatta con una query dentro il code-behind.

Perché conta? Perché per rifattorizzare in sicurezza servono test, e per scrivere test bisogna poter chiamare il codice da fuori. Se la logica è già in una classe, scrivere i test di caratterizzazione costa poco: nel modello 1,0 giornate per 1.000 righe. Se è incastrata, prima bisogna estrarla senza rompere niente, e la prima estrazione si fa senza rete: nel modello 6,0 giornate per 1.000 righe.

Nel gestionale di Treviso ho misurato che il 72% delle righe calde è isolabile. Il conto è questo.

  • Righe calde: 66.000 (il 30% di 220.000).
  • Giornate: 60 di avvio, più 66 × (0,72 × 1,0 + 0,28 × 6,0) = 66 × 2,4 = 158,4. Totale 218,4 giornate.
  • Costo: 218,4 × 420 = 91.728 euro, spalmati il 60% nel primo anno e il 40% nel secondo.
  • Effetto: le modifiche sul codice caldo passano da 1,7 a 1,2. Il 75% delle modifiche costa il 29% in meno, il 25% resta com'è. Il moltiplicatore medio scende da 1,7 a 1,325.

Il costo annuale va da 371.000 a 292.250 euro, con un risparmio di 78.750 euro l'anno a regime. L'investimento si ripaga in poco più di un anno.

Dove vive la logica nel codice caldo: a Treviso il 72% delle 66.000 righe è isolabile, in un secondo caso solo il 20%

Se invece la quota fosse del 20%, come nel secondo caso del grafico, le giornate sarebbero 60 + 66 × (0,2 × 1,0 + 0,8 × 6,0) = 60 + 330 = 390, per un costo di 163.800 euro. Quasi il doppio, per lo stesso risultato. E mentre il costo di partenza sale, il beneficio a regime non cambia, quindi l'incrocio con la riscrittura arriva prima. Lo vedremo tra poco.

Come si misura la quota isolabile in un pomeriggio

Il metodo è grezzo, ma l'ordine di grandezza è quello che serve. Si prendono i file toccati negli ultimi dodici mesi, si sommano le loro righe, e si separano quelli che per percorso o per contenuto sono incastrati: code-behind, file XAML.cs, form, viste, SQL, e qualunque file che apre una connessione o mostra una finestra.

git log --since="12 months ago" --name-only --pretty=format: -- '*.cs' '*.sql' \
  | sort -u | grep -v '^$' > calde.txt

tot=0; inc=0
while read -r f; do
  [ -f "$f" ] || continue
  righe=$(wc -l < "$f")
  tot=$((tot + righe))
  if [[ "$f" == *.xaml.cs || "$f" == *.aspx.cs || "$f" == *.Designer.cs \
     || "$f" == *.sql ]] \
     || grep -qE 'SqlConnection|MessageBox|HttpContext' "$f"; then
    inc=$((inc + righe))
  fi
done < calde.txt

echo "righe calde: $tot, incastrate: $inc, isolabili: $((100 * (tot - inc) / tot))%"

Il risultato è una stima per difetto della quota isolabile, perché una classe che contiene una sola chiamata al database viene contata tutta come incastrata. Per una decisione da centinaia di migliaia di euro la rifinisci a mano sui dieci file più grandi, ma la distanza tra il 72% e il 20% si vede subito, e cambia la strada.

Quanto costa riscrivere, con il blocco e il rischio?

Passiamo all'altra strada. La riscrittura a scatto ha un costo nominale, e due costi che i preventivi non scrivono.

Il costo nominale. 220.000 righe a 3,5 giornate ogni 1.000 sono 770 giornate, cioè 323.400 euro. Le giornate per riga sono meno di quelle del rifattorizzare il codice incastrato (6,0), perché in un sistema nuovo non c'è niente da districare. Contano però l'apprendimento delle regole di business, i test di accettazione con gli utenti e la migrazione dei dati, che nelle 3,5 giornate sono già incluse.

La spesa è distribuita su diciotto mesi: due terzi nel primo anno, un terzo nel secondo. Durante quel periodo si paga ancora il vecchio, con 14.000 euro l'anno di infrastruttura, più 20.000 euro di affiancamento nel secondo anno, quando i due sistemi convivono.

Il blocco delle modifiche. Per diciotto mesi il team lavora sul nuovo, e non si possono fare tutte le modifiche richieste al vecchio. Nel modello il 40% di quelle richieste non si fa: sono 500 × 0,4 × 1,5 anni = 300 giornate, che a 420 euro valgono 126.000 euro di lavoro che il business voleva e non ha avuto. È una stima prudente, perché ho contato solo il costo di sviluppo e non il beneficio che quelle modifiche avrebbero portato.

Il rischio. Con una probabilità del 25% che il progetto si fermi prima del traguardo, e con l'80% della spesa fatta fino a quel punto che non si recupera, il rischio atteso è 0,25 × 323.400 × 0,8 = 64.680 euro. Non è un costo che pagherai di sicuro: è il prezzo di una polizza che nessuno ti vende, e per questo si mette nel conto.

Il costo a regime, quando il nuovo sistema è in produzione, è il più basso delle tre strade: 500 giornate a 1,0 per 420 euro, più 9.000 di infrastruttura, cioè 219.000 euro l'anno. Ogni anno risparmia 152.000 euro rispetto a non fare niente, contro i 78.750 del rifattorizzare. Per questo, a lungo termine, la riscrittura vince. Il suo problema è arrivarci.

VoceImporto in euro
Costo nominale della riscrittura (770 giornate)323.400
Modifiche che non si fanno per 18 mesi126.000
Rischio atteso di fermarsi64.680
Spesa aggiuntiva rispetto al nominale190.680
Costo annuale a regime219.000

I 190.680 euro in più del nominale sono il 59% del costo dichiarato della riscrittura. Se il tuo preventivo la dà per "323.000 euro, tutto compreso", leggi i numeri sotto: probabilmente non comprende il blocco e non comprende il rischio.

Qual è il numero che decide: gli anni di vita residua

Abbiamo le tre strade. Ora basta mettere in fila i costi anno per anno. Nel caso di Treviso, con il 72% di logica isolabile, ecco i costi annuali.

AnnoNon fare nienteRifattorizzareRiscrivere (solo costi diretti)
1371.000426.037443.800
2371.000368.316351.400
3371.000292.250219.000
4371.000292.250219.000
5371.000292.250219.000

Due cose saltano all'occhio. Nel primo anno, rifattorizzare costa 55.037 euro più di non fare niente, e riscrivere 72.800 in più. Dal terzo anno la riscrittura costa 73.250 euro meno del rifattorizzare, ogni anno. Il motivo è il moltiplicatore: a regime vale 1,0 per la riscrittura e 1,325 per il rifattorizzare.

La tabella sopra non dice ancora tutto, perché per la riscrittura mancano i 190.680 euro di blocco e rischio. Li aggiungo nella tabella che conta, quella dei costi cumulati, dove ogni riga risponde alla domanda "se tengo il software vivo per N anni, quanto spendo in totale?".

Anni di vitaNon fare nienteRifattorizzareRiscrivere, con blocco e rischioStrada più economica
31.113.0001.086.6031.204.880Rifattorizzare
41.484.0001.378.8531.423.880Rifattorizzare
51.855.0001.671.1031.642.880Riscrivere
62.226.0001.963.3531.861.880Riscrivere
82.968.0002.547.8532.299.880Riscrivere
Costo cumulato in euro per anni di vita residua: non fare niente, rifattorizzare il codice caldo e riscrivere con blocco e rischio, con l'incrocio a 4,6 anni

La tabella dice una cosa chiara. A quattro anni la riscrittura costa 45.027 euro più del rifattorizzare. A cinque anni costa 28.221 euro in meno. L'incrocio, interpolando, è a 4,6 anni. Sotto quella soglia conviene rifattorizzare il codice caldo, sopra conviene riscrivere. A otto anni il vantaggio della riscrittura è di 247.973 euro.

C'è una seconda soglia, più bassa. Rifattorizzare batte non fare niente solo da 2,7 anni in su. Se il software deve vivere meno di così, in questa configurazione l'investimento non si ripaga, e la strada giusta è non toccarlo e curare solo il minimo: i backup, le chiavi di cifratura e i test sulle parti che si rompono.

Le due soglie dividono il tempo in tre zone, e a ciascuna corrisponde una strada.

  • Meno di circa 3 anni di vita: non toccarlo. Il costo di partenza non si ripaga.
  • Da 3 a 4,6 anni: rifattorizza il codice caldo. È la strada più economica e anche la più sicura, perché ogni passo si annulla.
  • Più di 4,6 anni: riscrivi, preferibilmente a moduli. A questo punto la tassa del 70% pesa più del costo di liberarsene.

Il numero che decide, dunque, non è tecnico. È una data. La domanda da fare al titolare non è "il codice è brutto?", è "fino a quando ti serve questo software?". Se la risposta è "finché l'azienda esiste", la zona è la terza. Se è "finché non cambiamo gestionale tra tre anni", è la prima. La cosa più frequente che vedo, però, è che nessuno ha una risposta, e il software ha vissuto per dodici anni senza che nessuno decidesse quanto doveva vivere.

Come cambia la soglia quando cambia la quota isolabile?

Finora abbiamo guardato il caso di Treviso, con il 72% di codice isolabile. La quota sposta la soglia perché cambia il costo di rifattorizzare, mentre la riscrittura costa lo stesso. Ecco la soglia per cinque valori di quota.

Codice caldo isolabileGiornate di rifattorizzazioneCosto in euroIncrocio con la riscritturaSoglia con non fare niente
20%390163.8003,6 anni3,6 anni
40%324136.0804,0 anni3,2 anni
60%258108.3604,4 anni2,9 anni
72% (Treviso)21891.7284,6 anni2,7 anni
80%19280.6404,8 anni2,5 anni
Anni di incrocio tra rifattorizzare e riscrivere in funzione della quota di codice caldo isolabile: da 3,6 anni con il 20% a 4,8 anni con l'80%

Il risultato sorprende chi si aspetta un grande effetto. Passare dal 20% all'80% di codice isolabile sposta l'incrocio di poco più di un anno, da 3,6 a 4,8. Sposta molto di più il costo di partenza, che scende da 163.800 a 80.640 euro, e il tempo di rientro. In altre parole, la quota isolabile non decide se rifattorizzare, decide quanto costa e quanto in fretta ripaga.

La lettura pratica è questa. Se hai molta logica incastrata, rifattorizzare costa tanto e conviene soltanto per orizzonti brevi, sotto i 3,6 anni. Se hai molta logica isolabile, rifattorizzare è un investimento piccolo e ripaga in fretta, ma comunque la riscrittura a lungo termine vince. La ragione è la piattaforma: il codice pulito sta sempre su un'architettura vecchia, e il moltiplicatore non scende mai sotto 1,2.

La quota isolabile ha però un altro effetto, che il modello a quattro anni non misura. Più è alta, più il lavoro è reversibile e frazionabile. Con il 72% puoi cominciare da un modulo, vedere i risultati dopo un mese e fermarti. Con il 20% il primo mese è dedicato quasi solo a estrarre codice e a scrivere test, e per vedere il primo beneficio devi fidarti.

Quando la quota è sotto il 25%, i casi che ho seguito si dividono in due. In una parte il rifattorizzare ha funzionato, a patto di partire da un solo modulo e di fermarsi a ogni passo. Nell'altra, la maggior parte dei tentativi si è fermata al secondo mese, perché il costo apparente era troppo alto e il beneficio ancora invisibile. Se ti trovi in quella zona, la prima misura è scegliere un modulo piccolo e provare, prima di impegnare il budget intero.

Quanto sono robusti questi numeri?

Un incrocio a 4,6 anni suona preciso, e non lo è. Dipende da ipotesi che ho scelto io, e la cosa onesta è far vedere quanto si muove se le cambi. Ho ripetuto il calcolo cambiando una sola ipotesi alla volta, con la quota isolabile al 72%.

Ipotesi cambiataValore di baseValore alternativoIncrocio
Probabilità che la riscrittura si fermi25%10% / 40%4,1 / 5,1 anni
Giornate per 1.000 righe di riscrittura3,52,5 / 5,03,1 / 6,9 anni
Moltiplicatore del legacy1,71,4 / 2,06,1 / 3,6 anni
Moltiplicatore del codice caldo dopo la pulizia1,21,07,0 anni
Caso di base4,6 anni

Il campo di variazione va da 3,1 a 7,0 anni. Non è piccolo, e l'ipotesi più sensibile è il costo della riscrittura per riga. Se il tuo sistema nuovo costa 5 giornate ogni 1.000 righe invece di 3,5, l'incrocio sale a 6,9 anni e la riscrittura non conviene quasi mai. È l'ipotesi su cui i preventivi sono meno affidabili, perché una riscrittura sbaglia la stima molto più spesso di un intervento di pulizia.

Il secondo punto di sensibilità è il moltiplicatore del legacy. Se la tua tassa è del 40% (1,4) invece del 70%, l'attrito da eliminare è più piccolo e la riscrittura ci mette 6,1 anni per ripagarsi. Se è del 100% (2,0), l'incrocio scende a 3,6 anni. Questo significa che prima di scegliere devi misurare il tuo moltiplicatore: non con un'opinione, con il confronto tra il tempo stimato dai tuoi sviluppatori per una modifica tipica e il tempo che richiederebbe in un progetto pulito. Ne parlo più avanti.

Il terzo è la qualità del risultato del rifattorizzare. Se il codice ripulito arrivasse a costare quanto uno nuovo (1,0), l'incrocio salirebbe a 7,0 anni: la pulizia basterebbe quasi sempre. In pratica non ci si arriva, perché la piattaforma vecchia continua a imporre le sue regole, ma il valore 1,2 è un'ipotesi che si può verificare dopo i primi due mesi di lavoro.

Ne ricavo una regola per usare questi numeri senza illudersi. Non cercare l'anno esatto: cerca di capire in quale delle tre zone cadi con margine. Se la vita residua prevista è di due anni e mezzo, siamo ben dentro la prima zona in tutti gli scenari. Se è di dieci, siamo nella terza in tutti gli scenari tranne uno. Se è di quattro, siamo nella zona grigia e conviene un'altra misura: una prova piccola, a basso costo, che riduca l'incertezza sulle ipotesi più sensibili.

Come si rifattorizza il codice caldo senza rompere niente?

Supponiamo che il numero ti dica di rifattorizzare. Il rischio è fare quello che si fa di solito: aprire il file più brutto e cominciare a riordinarlo. Così si rompono cose che funzionavano, e dopo due incidenti in produzione la direzione dice di smettere. Il metodo giusto ha quattro passi, nell'ordine.

1. Misurare il codice caldo e non toccare il resto

Si usa lo storico di Git, come nello script di prima, ma ordinando i file per numero di modifiche invece di sommare le righe. I primi venti file sono il perimetro. Tutto il resto, per ora, non esiste. Il vantaggio è psicologico oltre che economico: un perimetro di venti file è un obiettivo che si capisce.

2. Scrivere i test di caratterizzazione prima di toccare il codice

Un test di caratterizzazione non dice che il codice è giusto: dice che fa quello che fa oggi. Si chiama la funzione con dati reali, si registra il risultato, e il test diventa la rete. Se dopo la pulizia il risultato cambia, qualcosa si è rotto, o si è corretto un errore nascosto, e in entrambi i casi lo si vuole sapere.

public class CalcoloPrezzoCommessaTests
{
    [Theory]
    [InlineData("SER-0412", 12, 18_450.00)]
    [InlineData("SER-0987", 3, 4_320.50)]
    [InlineData("SER-1530", 40, 61_800.00)]
    public void Il_prezzo_resta_quello_registrato(string articolo, int quantita, decimal atteso)
    {
        var listino = ListinoDiProva.DaFile("listino-2026-09.json");
        var calcolo = new CalcoloPrezzoCommessa(listino);

        decimal prezzo = calcolo.Calcola(articolo, quantita, ClienteConScontoStorico.Rossi);

        Assert.Equal(atteso, prezzo);
    }
}

I valori "attesi" non vengono da una specifica: vengono da un'esecuzione del sistema vecchio, con gli stessi dati. Se il numero è sbagliato, lo è anche oggi, e il test lo conserva finché non decidi di correggerlo. Questa è la differenza tra pulire e riscrivere dall'immaginazione.

3. Estrarre la logica incastrata con la tecnica della cucitura

Feathers la chiama "seam", cucitura: un punto del codice dove si può cambiare il comportamento senza modificarlo. Il caso più comune è la logica dentro il gestore di un pulsante. Si crea una classe nuova, si sposta il calcolo, e il gestore ne chiama l'unico metodo. Il cambiamento è meccanico, e per questo lo si fa con strumenti dell'ambiente di sviluppo e non a mano.

L'ordine delle estrazioni conta: si comincia dai calcoli puri (prezzi, sconti, scadenze, arrotondamenti), che non toccano il database, e si finisce dalle regole che leggono dati. Le stored procedure sono l'ultimo passo, e di solito non si traducono: si fa un test che le chiama con dati noti e si mettono sotto una classe che le incapsula.

4. Ripulire un modulo per volta, e fermarsi dopo ogni modulo

Dopo ogni modulo si misura di nuovo: tempo medio di una modifica, numero di regressioni, giornate spese. Se il moltiplicatore è sceso, si va avanti. Se non è sceso, o è sceso meno di quanto previsto, ci si ferma e si ricalcola la soglia con il valore vero. Il bello di questa strada è che ogni modulo porta una misura, e la misura corregge l'ipotesi più sensibile del modello.

In generale, i primi due moduli richiedono più tempo del previsto, perché si costruisce l'attrezzatura: la pipeline, i dati di prova, l'abitudine al test. Dal terzo il passo si accelera. Se dopo cinque moduli la squadra non è almeno il 30% più veloce, vuol dire che la logica incastrata è più incastrata di come l'avevi misurata, ed è il momento di guardare l'altra colonna della tabella.

E se il problema non è il codice ma la piattaforma?

Finora ho dato per scontato che il sistema giri su una base che regge. A volte non è così, e allora né la pulizia né la riscrittura del codice risolvono l'urgenza. Le situazioni sono tre.

  • Il runtime non riceve più aggiornamenti. Un'applicazione su un framework fuori supporto, o su un sistema operativo che l'azienda non può più installare, è un rischio di sicurezza anche se il codice è perfetto. Il caso tipico è un'applicazione che dipende da un componente a 32 bit di cui non esiste più la versione aggiornata.
  • Il modello di distribuzione è superato. Un programma desktop che deve girare su dieci sedi con installazioni a mano è un problema che il codice pulito non risolve. Serve un modello web o un client con aggiornamento automatico.
  • Il database è l'ostacolo. Una stored procedure da 600 righe che fa il lavoro dell'applicazione non si rifattorizza in sicurezza, e a volte la scelta vera è quella di spostare la logica fuori dal database, indipendentemente da tutto il resto.

In questi casi si cambia il punto di partenza. Si migra prima la piattaforma, a pezzi, con una facciata e un modulo alla volta: è il lavoro che descrivo nella guida alla migrazione da .NET Framework a .NET 10. Se il programma è un'applicazione desktop, l'articolo su Windows Forms nel 2026 mostra le tre scelte, tenerlo, portarlo o riscriverlo, con i costi.

Albero di decisione in quattro domande: la piattaforma regge, il software vive meno di tre anni, vive meno degli anni di incrocio, altrimenti riscrivi a moduli

L'albero di decisione tiene insieme tutto quello che abbiamo visto, in quattro domande e in questo ordine.

  1. La piattaforma regge ancora quattro anni? Se no, migra prima la piattaforma, a pezzi. Le altre domande si fanno dopo.
  2. Il software vive meno di tre anni? Se sì, non toccarlo e cura solo il minimo.
  3. Vive meno degli anni di incrocio, tra 3,6 e 4,8 secondo la quota isolabile? Se sì, rifattorizza il codice caldo.
  4. Altrimenti, riscrivi a moduli. La riscrittura a scatto, quella con tutti i rischi, ha senso quasi solo se i moduli non si possono separare, e in quel caso conviene capire perché, prima di spendere.

Una nota sulla quarta riga. Il modello confronta la riscrittura a scatto, perché è quella che si propone più spesso. Nella pratica, quando la risposta è "riscrivi", quasi sempre la strada giusta è la variante a moduli, con la facciata, perché toglie quasi tutto il rischio e il blocco, e l'ho misurata nell'articolo sullo Strangler Fig. Lo scatto resta sul tavolo solo quando il sistema è piccolo o quando non c'è nessuna cucitura dove inserire la facciata.

Come si rifà il calcolo con i dati della tua azienda?

Il modello sta in poche righe, e conviene averlo in forma eseguibile. È C# puro, senza dipendenze: lo incolli in un'applicazione console con .NET 10, sostituisci le ipotesi e leggi i totali. Qui ne riporto il cuore. Con i valori di Treviso restituisce 1.671.103 euro per il rifattorizzare e 1.642.880 per la riscrittura a cinque anni.

var h = new Ipotesi();
for (int anni = 3; anni <= 8; anni++)
    Console.WriteLine($"{anni} anni: rifattorizza {Modello.Rifattorizza(h, anni, 0.72).Sum():N0}, riscrive {Modello.Riscrive(h, anni):N0}");

record Ipotesi(
    double Tariffa = 420, double GiornateModifiche = 500, double Moltiplicatore = 1.7,
    double InfraVecchia = 14_000, double InfraNuova = 9_000,
    double Righe = 220_000, double QuotaRigheCalde = 0.30, double QuotaModificheCalde = 0.75,
    double MoltCaldoDopo = 1.2, double GgIsolabile = 1.0, double GgBloccato = 6.0, double Avvio = 60,
    double GgRiscrittura1000 = 3.5, double Blocco = 0.40, double PFallimento = 0.25);

static class Modello
{
    public static double[] Rifattorizza(Ipotesi h, int anni, double q)
    {
        double gg = h.Avvio + h.Righe * h.QuotaRigheCalde / 1000 * (q * h.GgIsolabile + (1 - q) * h.GgBloccato);
        double inv = gg * h.Tariffa;
        double dopo = h.QuotaModificheCalde * h.MoltCaldoDopo + (1 - h.QuotaModificheCalde) * h.Moltiplicatore;
        var mult = new[] { h.Moltiplicatore, (h.Moltiplicatore + dopo) / 2 };
        return Enumerable.Range(0, anni).Select(i =>
            h.GiornateModifiche * (i < 2 ? mult[i] : dopo) * h.Tariffa + h.InfraVecchia +
            (i == 0 ? inv * 0.6 : i == 1 ? inv * 0.4 : 0)).ToArray();
    }

    public static double Riscrive(Ipotesi h, int anni)
    {
        double inv = h.Righe / 1000 * h.GgRiscrittura1000 * h.Tariffa;
        double a1 = h.GiornateModifiche * (1 - h.Blocco) * h.Moltiplicatore * h.Tariffa + h.InfraVecchia + inv * 2 / 3;
        double a2 = h.GiornateModifiche * (0.5 * (1 - h.Blocco) * h.Moltiplicatore + 0.5) * h.Tariffa
                    + (h.InfraVecchia + h.InfraNuova) / 2 + inv / 3 + 20_000;
        double resto = (h.GiornateModifiche * h.Tariffa + h.InfraNuova) * (anni - 2);
        double congelate = h.GiornateModifiche * h.Blocco * 1.5 * h.Tariffa;
        double rischio = h.PFallimento * inv * 0.8;
        return a1 + a2 + resto + congelate + rischio;
    }
}

Per usarlo servono tre misure, e nessuna richiede un consulente. Le giornate di modifiche le trovi nel registro dei ticket o nelle ore degli ultimi dodici mesi. La quota isolabile la stimi con lo script che ho mostrato. Il moltiplicatore del legacy lo ottieni facendo stimare ai tuoi sviluppatori, a freddo, cinque modifiche già fatte, e confrontando il tempo reale con quello che avrebbero impiegato in un progetto pulito: il rapporto è il tuo 1,7.

Il resto sono ipotesi che puoi lasciare dove sono, a patto di guardare la tabella di sensibilità e di non fidarti di un solo scenario. Una scheda con tre scenari, ottimistico, di base e pessimistico, vale più di un numero preciso.

Il modo più utile di usare il modello è farlo girare tre volte, cambiando solo due parametri. Nello scenario ottimistico per la riscrittura metti 2,5 giornate ogni 1.000 righe e il 10% di probabilità di fermarsi. In quello pessimistico metti 5,0 giornate e il 40%. Quello di base resta com'è. Se la tua data di vita residua cade nella stessa zona in tutti e tre gli scenari, la decisione è solida e non serve discuterne oltre. Se cade in zone diverse, il problema non è il modello: è che la riscrittura è troppo incerta per essere scelta oggi, e il passo giusto è una prova piccola che riduca l'incertezza.

Un ultimo consiglio pratico. Conserva il file con le ipotesi insieme al codice, nel repository, e rifai il calcolo ogni sei mesi con i numeri veri: il moltiplicatore misurato, le giornate spese, la quota isolabile aggiornata. Un modello che si corregge con i dati diventa uno strumento di governo, mentre un preventivo scritto una volta resta un'opinione datata.

Quali errori ho visto fare più spesso?

Dopo ventisei anni di mestiere, i modi di sbagliare questa decisione sono pochi e si ripetono. Li elenco perché ti fanno risparmiare più di qualsiasi formula.

Confondere il fastidio con il costo. Il codice che il team odia non è per questo il più caro. Il modulo che nessuno vuole aprire spesso è fermo da anni e non genera spesa. Guardo sempre prima lo storico delle modifiche e poi le opinioni, perché le opinioni sono rumorose e i commit no.

Rifattorizzare per sempre senza una data di fine. Una pulizia senza misura diventa un hobby. Nei progetti che hanno funzionato c'era un obiettivo scritto: "dopo il terzo modulo, il tempo medio di una modifica scende del 25%". Quando non scendeva, lo si diceva, e si cambiava strada.

Riscrivere senza congelare l'ambito. Il nuovo sistema parte per replicare il vecchio, e dopo sei mesi la direzione chiede cose che il vecchio non faceva. Si finisce per costruire un terzo sistema che non è né l'uno né l'altro. La riscrittura funziona quando l'ambito del primo rilascio è identico a quello del vecchio, senza sorprese.

Sottovalutare le regole che nessuno ricorda. In dodici anni il codice assorbe centinaia di casi particolari: il cliente che paga a 75 giorni, lo sconto che non vale sui telai in alluminio, l'arrotondamento che il commercialista vuole alla terza cifra. Nessuno li ha scritti. Se riscrivi senza test di caratterizzazione, li perdi, e te ne accorgi quando un cliente protesta.

Non scrivere la data di vita residua. È l'errore più grave, perché rende impossibile la decisione. Quando chiedo "per quanto tempo vi serve questo software?" nella metà dei casi non c'è una risposta, e il software viene mantenuto in vita per inerzia. Scrivere "almeno sette anni, perché è il cuore dell'azienda" cambia tutto, in meglio.

Scegliere in base a chi propone. Un fornitore che vende riscritture propone di riscrivere. Un consulente che vende pulizie propone di rifattorizzare. Io faccio entrambe le cose, e per questo la mia prima domanda è sempre sui numeri e mai sulla soluzione. Se chi ti propone una strada non ti mostra il conto dell'altra, hai ricevuto un'opinione.

Come si decide in una settimana?

La decisione non richiede un progetto. Richiede cinque giorni di lavoro di una o due persone, e produce una scheda di una pagina con tre numeri e una raccomandazione. Il calendario che uso è questo.

  • Giorno 1: la data. Con la direzione si scrive per quanti anni il software deve vivere e perché. Si scrivono tre scenari: minimo, probabile, massimo. Se non si trova un accordo, la differenza tra gli scenari è già un'informazione.
  • Giorno 2: il codice caldo. Si estrae la storia di Git degli ultimi dodici mesi, si individuano i file toccati e si calcola la quota che riceve il 75% delle modifiche. Se è molto diversa dal 30%, si cambia l'ipotesi del modello.
  • Giorno 3: la quota isolabile. Si esegue lo script sul codice caldo e si rifinisce a mano sui dieci file più grandi. Il risultato è un numero tra 0 e 100.
  • Giorno 4: il moltiplicatore. Si fanno stimare cinque modifiche già fatte, a freddo, a due sviluppatori che non si parlano. Il rapporto tra il tempo reale e la stima pulita è il moltiplicatore.
  • Giorno 5: il modello e la scheda. Si inseriscono i numeri nel modello e si leggono tre cose: in quale zona cade la data, quanto vale l'incrocio, quanto cambia nello scenario pessimistico. La scheda finisce con una decisione e con un primo passo a basso costo.

Il primo passo è sempre piccolo e annullabile. Se la zona è la seconda, è un modulo da ripulire in quattro settimane, con la misura del moltiplicatore prima e dopo. Se è la terza, è la facciata e il primo modulo da sostituire, come nel caso del Strangler Fig. Se è la prima, è una lista di cose minime da mettere in sicurezza e una data per ripetere la misura.

Alla fine della settimana hai tre cose che prima non avevi: un numero di anni scritto, una quota di codice isolabile e un costo annuale dell'attrito. Con queste tre, la riunione con la direzione cambia argomento: non si litiga più tra riscrivere e rifattorizzare, si decide quale strada porta più valore nei prossimi anni, e quanto si è disposti a spendere per i primi due mesi.

Che cosa sarebbe successo a Treviso?

Torno all'azienda di serramenti. Il titolare aveva due voci, il fornitore che voleva riscrivere e lo sviluppatore che voleva ripulire. Nessuno gli aveva chiesto per quanti anni voleva tenere il gestionale.

Con il conto in mano avrebbe visto che non fare niente costava 371.000 euro l'anno, e che il 72% del codice caldo era isolabile, quindi che rifattorizzare costava 91.728 euro e che a quattro anni di vita costava 45.027 euro meno di una riscrittura. Avrebbe visto che la riscrittura, con blocco e rischio, costava 190.680 euro in più del nominale, e che l'incrocio era a 4,6 anni.

La domanda successiva sarebbe stata quella decisiva: quanti anni? Se la risposta fosse stata "almeno otto", avrebbe scelto di riscrivere a moduli, risparmiando 247.973 euro sul periodo rispetto al ripulire. Se fosse stata "tre o quattro, poi cambiamo gestionale", avrebbe ripulito i venti file caldi, speso 91.728 euro e risparmiato 105.147 euro in quattro anni rispetto a non fare niente.

In entrambi i casi avrebbe deciso con un numero e non con una discussione. È questa la differenza, e vale più dell'esito.

Se hai un sistema simile, scritto anni fa da qualcuno che non c'è più, e vuoi sapere in quale zona cade il tuo, il percorso comincia con la modernizzazione del software legacy. Per fare il conto dei costi nascosti trovi la guida al costo del software legacy, e se ti serve una persona che guardi il codice con te c'è la consulenza software.

In cinque righe. Scrivi per quanti anni terrai vivo il software, perché senza una data non si decide. Misura la quota di codice caldo isolabile, perché dice quanto costa ripulire. Sotto circa tre anni non toccarlo, fino a 4,6 ripulisci il codice caldo, oltre riscrivi a moduli. Scrivi i test di caratterizzazione prima di spostare una riga. Dopo ogni modulo misura di nuovo, e correggi il modello con il numero vero.

Domande frequenti

Dipende da quanti anni deve ancora vivere. Nel caso descritto, sotto circa 3 anni conviene non toccarlo, fino a 4,6 anni conviene rifattorizzare il codice caldo, oltre conviene riscrivere. A cinque anni la riscrittura costa 1.642.880 euro contro 1.671.103 del rifattorizzare, e a otto anni risparmia 247.973 euro.

Cambiare la struttura del codice senza cambiare quello che fa, con piccoli passi verificati da test. Il sistema resta in produzione e gli utenti non si accorgono di niente. Non ha una data di consegna: è un lavoro continuo, fatto insieme alle modifiche richieste dal business.

Per un sistema di 220.000 righe, 770 giornate, cioè 323.400 euro a 420 euro al giorno. Ma il conto vero aggiunge 126.000 euro di modifiche che non si fanno per 18 mesi e 64.680 euro di rischio atteso di fermarsi: 190.680 euro in più, il 59% del costo dichiarato.

È la logica di business che si prova con un test automatico senza aprire una form e senza un database. Conta perché per rifattorizzare servono test: con il 72% di codice caldo isolabile servono 218 giornate e 91.728 euro, con il 20% servono 390 giornate e 163.800 euro.

Con la storia di Git degli ultimi dodici mesi: si prendono i file toccati, si sommano le righe e si separano quelli incastrati, cioè code-behind, XAML.cs, SQL e file che aprono connessioni o finestre. Lo script dell'articolo lo fa in un pomeriggio e dà una stima per difetto.

L'incrocio a 4,6 anni si muove tra 3,1 e 7,0 cambiando una ipotesi alla volta. La più sensibile è il costo della riscrittura per riga: a 5 giornate ogni 1.000 righe l'incrocio sale a 6,9 anni. Per questo si cerca la zona in cui si cade con margine, non l'anno esatto.

Lascia i tuoi dati nel form qui sotto

Matteo Migliore

Matteo Migliore è un imprenditore e architetto software con oltre 27 anni di esperienza nello sviluppo di soluzioni basate su .NET e nell'evoluzione di architetture applicative per imprese e organizzazioni di alto profilo.

Nel corso della sua carriera ha collaborato con realtà come Cotonella, Il Sole 24 Ore, FIAT e NATO, guidando team nello sviluppo di piattaforme scalabili e modernizzando ecosistemi legacy complessi.

Ha formato centinaia di sviluppatori e affiancato aziende di ogni dimensione nel trasformare il software in un vantaggio competitivo, riducendo il debito tecnico e portando risultati concreti in tempi misurabili.

Stai leggendo perché vuoi smettere di rattoppare software fragile.Scopri il metodo per progettare sistemi che reggono nel tempo.