Debito TecnicoArchitetturaSoftware Delivery

Software legacy: quando convivere, quando evolvere, quando riscrivere

Non tutto il software legacy va riscritto. Non tutto può essere lasciato com'è. La decisione dipende da tre variabili che quasi nessuno misura prima di scegliere. Una guida per chi deve decidere cosa fare con un sistema che funziona ma frena.

QMates· Software Advisory14 aprile 202611 min

Il problema della parola "legacy"

Nel linguaggio comune, "software legacy" è diventato un termine quasi esclusivamente negativo: vecchio, problematico, da sostituire al più presto. Questo frame emotivo porta a decisioni costose. Un sistema definito legacy viene messo automaticamente in lista di riscrittura, indipendentemente da quanto realmente rallenti la delivery, da quanto costerebbe sostituirlo e da quanto valore stia ancora generando.

La definizione più citata in letteratura è di Michael Feathers: il codice legacy è semplicemente codice senza test. Utile, ma ci limita a un sintomo. Operativamente preferiamo una definizione più ampia: un sistema legacy è un sistema che funziona in produzione e genera valore, ma che presenta almeno una di queste caratteristiche: tecnologie non più mantenute dalla community, conoscenza localizzata su pochissime persone, difficoltà crescente di modifica per qualsiasi ragione strutturale. Non è necessariamente vecchio nel senso temporale; può essere un sistema scritto tre anni fa che ha già sedimentato abbastanza debito da essere difficilmente modificabile.

La domanda non è "questo sistema è legacy?" ma "quanto costa mantenerlo com'è, quanto costerebbe modificarlo e quanto costerebbe sostituirlo?" Senza rispondere a queste tre domande con numeri, qualsiasi decisione è arbitraria.

Le tre variabili che osserviamo

Nella nostra esperienza, la decisione tra convivenza, evoluzione e riscrittura dipende principalmente da tre variabili che consigliamo di misurare, non stimare a intuito.

La prima è la frequenza di modifica. Un sistema che non cambia quasi mai ha un costo di mantenimento basso, indipendentemente da quanto sia tecnicamente complesso. Il debito tecnico si paga ogni volta che si modifica il sistema: se il sistema non viene modificato, il debito è latente ma non attivo. Un modulo legacy che fa una sola cosa, è stabile da anni e non è sul percorso critico della crescita può restare com'è indefinitamente. La decisione di intervenire diventa urgente solo nel momento in cui quella stabilità viene messa in discussione da nuovi requisiti.

La seconda è il peso sulle dipendenze. Un sistema legacy isolato, con poche connessioni verso il resto dell'architettura, è meno problematico di un sistema legacy che è al centro di una rete di dipendenze. Se ogni nuova feature richiede di passare attraverso quel sistema, il suo debito si moltiplica su ogni modifica del sistema più ampio. La posizione architetturale del sistema legacy è spesso più rilevante del suo stato interno.

La terza è il rischio di perdita della conoscenza. Questo è il fattore più sottovalutato. Un sistema che funziona bene ma che solo due persone conoscono davvero è un rischio operativo concreto. Quando quelle persone non sono disponibili — per ferie, malattia, o perché lasciano — quella parte del sistema diventa opaca. Il rischio di perdita della conoscenza può trasformare un sistema che era gestibile in un'emergenza in tempi molto brevi.

Scenario uno: convivere

Convivere con un sistema legacy è la scelta giusta quando le tre variabili dicono che il costo di modifica o sostituzione è superiore al beneficio.

Il profilo tipico è un sistema con alta stabilità (frequenza di modifica bassa), basso peso sulle dipendenze (poche connessioni verso il resto dell'architettura) e conoscenza distribuita su più persone o documentata. In questo scenario, il sistema legacy non è un problema operativo; è un asset che continua a generare valore con costi di mantenimento accettabili.

La convivenza non significa ignorare il sistema. Significa trattarlo come una componente stabile con tre impegni specifici: non aggiungere responsabilità (ogni nuova funzionalità va realizzata altrove), documentare la conoscenza esistente (non per riscriverlo, ma per ridurre il rischio di dipendenza da singole persone), e monitorare le variabili che potrebbero cambiare la valutazione (un'integrazione strategica che richiede di toccarlo, il pensionamento di un componente da cui dipende, la partenza della persona chiave).

Il segnale che la convivenza ha smesso di essere la scelta giusta è quando uno di questi trigger si materializza. Fino a quel momento, ogni energia spesa in refactoring o riscrittura di un sistema stabile è energia sottratta a problemi più urgenti.

Scenario due: evolvere progressivamente

L'evoluzione progressiva è la scelta giusta nella maggioranza dei casi in cui il sistema legacy è attivo sul percorso critico della crescita ma non è così degradato da richiedere una sostituzione completa. È anche la strategia meno romantica e la più efficace.

Evolvere un sistema legacy significa applicare sistematicamente i principi del refactoring progressivo descritti nell'articolo precedente, con alcune considerazioni specifiche per i sistemi con alto accumulo storico di debito.

Il primo principio è non toccare mai una parte del sistema senza averla prima coperta di test. Nei sistemi legacy, la copertura di test è quasi sempre inadeguata. Aggiungere test sul comportamento attuale — anche su comportamenti che non sono il comportamento desiderato — è il prerequisito per qualsiasi modifica sicura. Questa fase richiede tempo ma non è opzionale: senza la rete di sicurezza dei test, ogni modifica è un rischio non quantificato.

Il secondo principio è isolare prima di modificare. Prima di ristrutturare una parte del sistema legacy, si isola quella parte da tutto il resto attraverso interfacce chiare. L'isolamento permette di testare in modo indipendente, di sostituire internamente senza impatto esterno e di verificare il comportamento su casi specifici. È l'applicazione del pattern strangler fig a scala di modulo invece che di sistema intero.

Il terzo principio, specifico dei sistemi legacy, è non cercare di capire tutto prima di iniziare. Nei sistemi con anni di storia e poca documentazione, la comprensione completa prima dell'intervento è spesso impossibile. L'approccio efficace è quello esplorativo: si circoscrive un'area, si aggiungono test, si interviene, si osserva cosa emerge. La conoscenza del sistema viene acquisita in modo incrementale, attraverso il processo di modifica sicura.

Scenario tre: riscrivere

La riscrittura completa è giustificata in un numero limitato di scenari. Identificarli con precisione è importante proprio perché la tendenza emotiva è di applicare la riscrittura a problemi che potrebbero essere risolti con interventi più mirati e meno rischiosi.

Il primo scenario in cui la riscrittura è giustificata è quando il sistema si appoggia su tecnologie che non sono più manutenute e il cui supporto sta diventando un rischio di sicurezza o di continuità operativa. Non la tecnologia vecchia in sé — che, come discusso, non è debito se funziona — ma la tecnologia che non riceve più patch di sicurezza, che non ha più una community attiva, o che è incompatibile con i requisiti regolatori che l'organizzazione deve rispettare. In questo caso il rischio è esterno al sistema e non può essere gestito con refactoring interno.

Il secondo scenario è quando l'architettura del sistema è talmente vincolante da rendere qualsiasi evoluzione più costosa della riscrittura. Questo threshold è raramente raggiunto ma esiste: sistemi con accoppiamento così pervasivo da rendere impossibile qualsiasi isolamento, sistemi dove il modello dei dati è talmente disallineato dai requisiti attuali da richiedere una ristrutturazione completa, sistemi dove il costo di ogni singola modifica supera il suo valore di business. Il test operativo: se il cost of change medio supera stabilmente il valore di ogni singola feature, la riscrittura potrebbe avere un ritorno positivo nel medio termine.

Il terzo scenario è la discontinuità strategica: un cambio di business model, un'acquisizione, un cambio di piattaforma tecnologica che richiede di reimplementare da zero. In questo caso non è il debito tecnico che giustifica la riscrittura ma il cambiamento di requisiti, che rende il sistema esistente inadeguato indipendentemente dalla sua qualità tecnica.

In tutti gli altri scenari, la riscrittura è più rischiosa di quanto sembri e meno necessaria di quanto il debito accumulato faccia sentire.

Come decidere: un framework in tre domande

Quando si deve prendere una decisione su un sistema legacy, queste tre domande, in sequenza, producono una risposta più affidabile dell'intuizione:

Quanto costerà ogni modifica in questo sistema nei prossimi dodici mesi? Stima il numero di modifiche necessarie e il costo medio di ciascuna, dato il debito attuale. Questo è il costo della convivenza: non il costo di mantenere il sistema invariato, ma il costo di ogni cambiamento che il business richiederà comunque.

Quanto costerebbe portare il sistema a uno stato in cui quelle modifiche costassero la metà? Questo è il costo dell'evoluzione progressiva. Non il costo di raggiungere un ideale tecnico; il costo di dimezzare il costo operativo delle modifiche future. Se questo investimento si ripaga entro sei-dodici mesi di modifiche più veloci, l'evoluzione è giustificata.

Quanto costerebbe sostituire completamente il sistema, inclusi i costi nascosti: migrazione dei dati, parallelo operativo, rischio di regressioni, perdita di conoscenza del dominio incorporata nel vecchio sistema? Questo è il costo della riscrittura. Spesso è significativamente più alto delle stime iniziali, proprio perché i costi nascosti vengono sottovalutati. Se questo costo è superiore al costo dell'evoluzione progressiva per un periodo ragionevole, la riscrittura non è giustificata economicamente.

Il caso speciale: il sistema legacy che nessuno vuole toccare

C'è un caso specifico che merita attenzione separata: il sistema legacy che il team evita sistematicamente, su cui le modifiche vengono rimandarte il più possibile e che genera ansia ogni volta che si avvicina una release che lo tocca. Questo pattern non è sempre un segnale che il sistema va riscritto; è quasi sempre un segnale che mancano i test e la conoscenza necessari per intervenire con confidenza.

Il rimedio non è la riscrittura ma la stabilizzazione: aggiungere test sul comportamento attuale, documentare le parti più opache, introdurre monitoraggio che renda visibili i problemi prima che diventino emergenze. Queste azioni possono trasformare un sistema che nessuno vuole toccare in un sistema su cui il team sa intervenire, anche se non è bello internamente.

La stabilizzazione però non basta da sola se non è accompagnata da pratiche tecniche che mantengono il sistema sotto controllo nel tempo. Code review sistematica, deploy frequenti e reversibili, refactoring incrementale nel flusso normale di lavoro, pipeline CI funzionante e rispettata: senza questi meccanismi, il debito tende a riacquisirsi alla stessa velocità con cui lo si riduce. I test proteggono dalle regressioni, il monitoraggio rende visibili i problemi, ma sono le pratiche quotidiane del team a determinare se il sistema migliora o si degrada di sprint in sprint. È questa la differenza tra un intervento di emergenza e uno stato permanente.

Cosa fare concretamente

  1. Applica il framework delle tre domande al tuo sistema legacy più critico: non tutti i sistemi legacy contemporaneamente, solo quello che genera più attrito. Stima il costo della convivenza, il costo dell'evoluzione e il costo della riscrittura con numeri concreti, non con impressioni. Il risultato spesso sorprende: sistemi che sembravano richiedere una riscrittura risultano gestibili con un'evoluzione mirata; sistemi che sembravano stabili risultano avere un costo di convivenza già alto.
  2. Identifica il trigger che cambierebbe la tua decisione attuale: se hai deciso di convivere, qual è il segnale che ti farebbe cambiare idea? L'uscita della persona chiave? Una nuova integrazione richiesta dal business? Una vulnerabilità di sicurezza non patchabile? Avere chiaro il trigger permette di monitorare le variabili rilevanti invece di rivalutare continuamente una decisione già presa.
  3. Documenta la conoscenza prima di qualsiasi altra cosa: qualunque sia la strategia scelta, la documentazione della conoscenza attuale è sempre il primo passo. Non la documentazione del codice — quella richiede tempo e diventa obsoleta rapidamente — ma la documentazione delle decisioni e dei vincoli: perché questo sistema è strutturato così, quali edge case gestisce in modo non ovvio, dove si trova il comportamento che il business considera implicito. Questa conoscenza è il valore più difficile da ricostituire dopo una perdita.

La prospettiva QMates

La domanda che più spesso ci viene posta su un sistema legacy è "dobbiamo riscriverlo?". La risposta che quasi sempre diamo non è sì o no; è "dipende da queste tre variabili, che vanno misurate prima di decidere". In molti casi l'analisi rivela che il sistema legacy non è il problema principale: il problema è che non ci sono test che permettano di toccarlo in sicurezza, o che la conoscenza è concentrata su una persona che sta pensando di lasciare, o che mancano gli strumenti di monitoraggio per capire cosa sta facendo in produzione.

La riscrittura è raramente la risposta corretta alla domanda corretta. È spesso la risposta emotivamente soddisfacente alla domanda sbagliata. Un sistema che entra sotto controllo — con test, conoscenza distribuita, monitoraggio e pratiche tecniche condivise — smette di essere legacy nel senso operativo del termine: diventa un sistema che genera valore con costi prevedibili, indipendentemente dall'età della tecnologia. Un sistema nuovo senza queste pratiche non resterà nuovo a lungo.

Se vuoi capire qual è la strategia giusta per il tuo sistema specifico, il nostro servizio di innesto è progettato per trasferire il metodo all'interno del team: non per sostituirlo, ma per costruire la capacità interna di gestire il debito in modo autonomo e sistematico. È l'approccio che dura.

Raccontaci dove sei bloccato

Prototipo fragile, legacy pesante o delivery imprevedibile – partiamo da lì