Legacy — non è semplicemente codice vecchio. È un sistema funzionante che porta soldi all’azienda ma rallenta lo sviluppo. Nello sviluppo mobile, il legacy può essere scritto in Objective-C, usare librerie obsolete o pattern architetturali superati. Secondo un rapporto di CAST Software (2024), l’età media di una riga di codice nei progetti enterprise supera i 14 anni. La strategia di lavoro con il legacy determina se diventerà un freno o rimarrà un asset gestibile.
Punti chiave
Legacy — codice o sistema che continua a funzionare in produzione ma non soddisfa più gli standard di qualità moderni. Il legacy può essere scritto in un linguaggio obsoleto (ad esempio, Objective-C invece di Swift), usare librerie non supportate o pattern architetturali considerati da tempo anti-pattern.
La caratteristica principale del legacy è l’assenza di test. Secondo la definizione di Michael Feathers (2004), il codice legacy è codice senza test. Se non è possibile modificare il comportamento in modo sicuro, il sistema è in stato legacy indipendentemente dall’età. Codice nuovo senza test unitari è legacy dal primo giorno.
Il legacy non è necessariamente negativo. Un sistema ben progettato in Java 8 può essere più affidabile e comprensibile di codice caotico in Kotlin con coroutine. L’età del codice non è un indicatore di qualità — ciò che conta è quanto facilmente il sistema può essere modificato ed esteso.
Ogni sistema di successo diventa legacy col tempo. È un processo naturale: le tecnologie si evolvono più velocemente di quanto il codice possa essere riscritto. Un’app scritta 5 anni fa in Swift 2 è legacy oggi, anche se era moderna al momento della creazione.
Il valore commerciale del legacy è spesso sottovalutato. Il sistema funziona in modo affidabile, elabora transazioni, memorizza dati — riscriverlo comporta rischi. Secondo il Standish Group (2024), il 35% dei progetti di riscrittura completa termina con un fallimento. Economicamente non è giustificato liberarsi del legacy, ma imparare a lavorarci.
Le migliori strategie sono la migrazione graduale, incapsulare il vecchio codice dietro nuove interfacce e test automatizzati. Il legacy diventa un problema solo quando smette di essere modificabile a un costo prevedibile.
Mancanza di test automatizzati — l’indicatore principale. Se dopo aver modificato una singola riga uno sviluppatore non può eseguire test e confermare che nulla si è rotto — hai a che fare con legacy. Un segnale aggiuntivo: il processo di deploy richiede ore e passaggi manuali.
La documentazione non corrisponde al codice — un altro indicatore. I diagrammi architetturali sono obsoleti, i commenti descrivono un comportamento che è già cambiato. Il tempo di apprendimento per un nuovo sviluppatore supera un mese — segno di elevata complessità e bassa manutenibilità.
Segnali aggiuntivi: architettura monolitica senza confini chiari, test manuali come metodo principale di verifica, pipeline CI lunga (più di 30 minuti), utilizzo di librerie senza versioni aggiornate e incapacità di aggiornare le dipendenze senza rompere i moduli correlati.
Il fenomeno del codice fragile — una modifica in un punto ne rompe tre altri. È conseguenza dell’alto accoppiamento, quando i moduli sanno troppo l’uno dell’altro. Più alto è l’accoppiamento, più velocemente il sistema passa nella categoria legacy.
Riduzione della velocità — il rischio principale. Aggiungere una funzionalità semplice richiede ore di studio del codice e giorni di test. Secondo Stripe (2024), gli sviluppatori spendono il 33% del loro tempo per superare il debito tecnico, direttamente correlato alla presenza di moduli legacy nel progetto.
Perdita di competenze — gli autori del codice originale lasciano l’azienda e la documentazione è incompleta. I nuovi sviluppatori temono di toccare moduli sconosciuti, portando all’effetto “codice congelato”: il modulo non si evolve ma continua a funzionare. Il bus factor di tali sistemi è criticamente basso.
Sicurezza — le librerie obsolete contengono vulnerabilità note. Usare OpenSSL 1.0.2 o versioni vecchie di Jackson in progetti Java è una via diretta a incidenti di sicurezza che possono costare reputazione e clienti all’azienda.
Demotivazione del team — lavorare con legacy senza una strategia di miglioramento riduce la soddisfazione degli sviluppatori. Il team smette di essere orgoglioso del prodotto, il turnover aumenta, rallentando ulteriormente lo sviluppo del sistema.
Characterization test — il primo passo prima di qualsiasi modifica al codice legacy. Esegui il codice con dati di input noti e registra l’output atteso. Questi test catturano il comportamento attuale come specifica. Golden master testing è una variante in cui l’output viene confrontato con un file di riferimento.
Analisi dei seam — trovare punti in cui l’accoppiamento può essere rotto senza modificare il comportamento. Michael Feathers identifica diversi tipi di seam: preprocessor seam, object seam, link seam. Object seam è il più comune: sostituire un oggetto reale con uno stub di test attraverso un’interfaccia.
Sprout method e Sprout class — tecniche per aggiungere nuovo codice accanto al vecchio, non al suo interno. Invece di modificare un metodo esistente, crea un nuovo metodo con la logica desiderata e chiamalo da quello vecchio. Ciò minimizza il rischio di rompere il codice funzionante.
class LegacyPaymentProcessor {
def process(payment) {
// 200 righe di codice legacy che non dovrebbero essere toccate
logPayment(payment) // metodo sprout
}
def logPayment(payment) {
// nuovo codice aggiunto accanto al legacy
}
}
Pattern Strangler Fig — l’approccio raccomandato per la migrazione del legacy. Un nuovo modulo viene creato in parallelo, il traffico viene gradualmente reindirizzato dal vecchio al nuovo. Il vecchio modulo muore naturalmente quando smette di ricevere richieste. Il pattern minimizza i rischi e consente il rollback in caso di problemi.
Branch by Abstraction — tecnica in cui viene creata un’astrazione sopra l’implementazione vecchia e quella nuova. Il codice client passa all’astrazione e l’implementazione vecchia viene gradualmente sostituita. Esempio: sostituire il livello di rete da AFNetworking ad Alamofire tramite un protocollo unificato NetworkService.
Migrazione graduale — suddividere la transizione in piccoli passi: incapsulare il vecchio modulo → scrivere test → creare un nuovo modulo → eseguire in parallelo → rimuovere il vecchio modulo. Ogni passo termina con uno stato stabile del sistema, consentendo il deploy in qualsiasi momento.
Domande frequenti
La riscrittura completa è l’opzione più rischiosa. Solo il 25% dei progetti di Big Rewrite ha successo nei tempi previsti. È meglio applicare il pattern Strangler Fig: sostituire i moduli gradualmente senza fermare il prodotto. Ogni iterazione porta valore commerciale e i rischi sono distribuiti nel tempo.
Inizia con characterization test: esegui il modulo con dati noti, registra il risultato. Golden master testing è un modo semplice per catturare il comportamento. Aggiungi test ogni volta che tocchi una riga di codice. Tra 6 mesi avrai una struttura che protegge dalle regressioni.
Se il sistema è stabile, non richiede modifiche frequenti e non influisce sulla velocità di sviluppo di altri moduli — lascialo stare. Se funziona, non toccarlo è un approccio ragionevole per moduli legacy isolati con bassa frequenza di modifiche. Tocca il codice solo quando sono necessarie modifiche commerciali.
Usa il versionamento semantico e aggiorna per fasi: patch → minor → major. Scrivi test di compatibilità per ogni libreria. Dependabot o Renovate automatizzano la creazione di PR di aggiornamento. Se una libreria è obsoleta, pianifica la sua sostituzione tramite un’astrazione.
Il debito tecnico è una metafora per stimare il costo dei miglioramenti rinviati. Il legacy è un sistema o codice specifico che è già diventato obsoleto. Il debito tecnico può accumularsi in un mese, il legacy richiede tempo. Non tutto il debito tecnico diventa legacy, ma ogni legacy contiene debito tecnico.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche