Confronto

Refactoring vs Riscrittura: Come Prendere la Decisione Giusta

Refactoring vs riscrittura: come decidere tra miglioramento incrementale e sostituzione completa e perché la maggior parte delle riscritture grandi fallisce.

Albero decisionale per scegliere tra refactoring software e riscrittura completa basato sull'analisi del codebase

In questo articolo:

Il dibattito refactoring vs riscrittura è uno che ogni organizzazione di ingegneria affronta a un certo punto. La posta in gioco è alta: una riscrittura completa che fallisce può costare anni di tempo di ingegneria e milioni in costo opportunità. Uno sforzo di refactoring troppo conservativo può lasciare l’organizzazione intrappolata con un sistema non manutenibile a tempo indeterminato. La risposta giusta dipende da fattori che raramente vengono analizzati con sufficiente rigore prima che la decisione venga presa.

La Domanda che Ogni Team di Ingegneria Affronta Prima o Poi

La conversazione di solito inizia con un sintomo: i deployment richiedono tre giorni, ogni nuova funzionalità richiede di toccare quindici file, gli incidenti avvengono settimanalmente e gli ingegneri sono demoralizzati perché il codice è difficile con cui lavorare. La conclusione a cui arriva il team è che il sistema è “troppo rotto per essere riparato” e deve essere ricostruito da zero.

Questa conclusione è spesso sbagliata, ma è comprensibile. Il costo di lavorare con il sistema esistente è visibile e quotidiano. Il costo di una riscrittura è distribuito su mesi ed è facile da sottovalutare.

La decisione refactoring vs riscrittura richiede di rispondere a diverse domande specifiche. Il problema è architetturale (la struttura fondamentale non può supportare ciò di cui si ha bisogno) oppure è di qualità del codice (la struttura potrebbe funzionare ma è implementata male)? La tecnologia è genuinamente obsoleta, o il problema è la manutenibilità? Si ha la specifica comportamentale, sia come test che come documentazione, che una riscrittura avrebbe bisogno di riprodurre? E, in modo critico: l’azienda può permettersi la riduzione della velocità durante una riscrittura?

Perché le Riscritture Software Falliscono: i Pattern di Fallimento Prevedibili

I progetti di riscrittura software su larga scala falliscono a un tasso elevato. I pattern di fallimento sono ben documentati e si ripetono in modo coerente nelle organizzazioni.

Second system syndrome. Il nuovo sistema è progettato per risolvere tutti i problemi del vecchio sistema più aggiungere le funzionalità che si volevano sempre. L’ambito si espande durante il design. Al momento dell’inizio dell’implementazione, il nuovo sistema è più complesso di quello vecchio.

Specifica incompleta. Il comportamento del sistema legacy è la specifica. Include migliaia di casi limite, workaround per problemi specifici dei clienti, comportamenti impliciti da cui i chiamanti dipendono, e bug che sono in produzione abbastanza a lungo da essere diventati funzionalità. Un team di riscrittura li scopre in modo incrementale, e ogni scoperta estende la timeline.

Il target che si muove. Mentre la riscrittura è in corso, il business continua. Arrivano nuovi requisiti. Il sistema legacy riceve patch. Dopo 12 mesi di riscrittura, il nuovo sistema è ancora in fase di recupero rispetto allo stato attuale del sistema legacy, che ha continuato ad evolversi.

Il lancio big bang. La riscrittura produce un nuovo sistema che deve sostituire quello vecchio in un unico punto di cutover. Tutte le differenze non rilevate tra i due sistemi diventano incidenti il giorno del lancio.

I team più propensi a sostenere una riscrittura completa sono quelli che non hanno ancora vissuto una riscrittura fallita. I team che l’hanno vissuta sono significativamente più cauti.

Quando il Refactoring è la Risposta Giusta

Il refactoring è la risposta giusta quando l’architettura fondamentale del sistema può supportare ciò di cui si ha bisogno, ma l’implementazione è scarsa. L’architettura è la struttura di come il sistema è organizzato. Un’implementazione scarsa significa: dipendenze aggrovigliate, astrazioni mancanti, pattern incoerenti, copertura dei test inadeguata, comportamento non documentato.

Questi problemi sono risolvibili in modo incrementale. Non richiedono di ricominciare da capo. Richiedono uno sforzo sostenuto e disciplinato nel tempo.

Il refactoring è anche la risposta giusta quando la specifica comportamentale non è disponibile o è costosa da ricostruire. Se non si riesce a caratterizzare in modo affidabile cosa fa il sistema, non si può verificare che una riscrittura sia corretta. Il refactoring con test di caratterizzazione consente di migliorare il sistema costruendo la specifica simultaneamente.

Il nostro intervento di tech debt solution inizia tipicamente con una valutazione della natura dei problemi del sistema. Nella maggior parte dei casi, i problemi sono a livello di implementazione, e il refactoring è l’approccio corretto. Il refactoring incrementale, combinato con una migliore copertura dei test e CI, produce sistemi misurabilmente migliori nel corso di 6-12 mesi senza il profilo di rischio di una riscrittura.

Quando una Riscrittura è Genuinamente Giustificata

Una riscrittura è giustificata in un insieme ristretto di circostanze. La tecnologia è così obsoleta che non esiste un percorso praticabile per eseguirla in un’infrastruttura moderna. Oppure l’architettura è genuinamente incompatibile con ciò di cui il business ha bisogno: un sistema single-tenant che deve diventare multi-tenant, un sistema sincrono che deve diventare event-driven su scala.

Anche in questi casi, una riscrittura completa dell’intero sistema è raramente necessaria. L’approccio corretto è riscrivere i componenti specifici dove il vincolo è vincolante, usando lo strangler fig pattern per sostituirli in modo incrementale mentre il resto del sistema continua ad operare.

La valutazione onesta della maggior parte delle decisioni di “riscrittura”: il team vuole sollievo dal dolore del sistema attuale, e una riscrittura sembra un sollievo. Un programma di refactoring disciplinato fornisce anche sollievo, più gradualmente, ma senza il rischio esistenziale che porta una riscrittura fallita.

Il Percorso di Mezzo: la Sostituzione Mirata

Tra il refactoring completo e la riscrittura completa, c’è un percorso di mezzo che funziona bene per la maggior parte delle situazioni: la sostituzione mirata di sottosistemi specifici usando pattern di migrazione incrementale.

Si identificano le parti del sistema che stanno genuinamente bloccando il progresso. Tipicamente si tratta di una piccola frazione del codebase totale, spesso il 20-30% che rappresenta l’80% del carico di manutenzione. Si applica lo strangler fig o il branch by abstraction a quei componenti specifici. Il resto del sistema rimane in atto e continua ad operare.

Questo approccio combina la velocità del ricominciare da zero su problemi delimitati con la sicurezza della migrazione incrementale. Evita sia il conservatorismo del puro refactoring incrementale nelle aree ad alta criticità sia il rischio della sostituzione dell’intero sistema.

Per un intervento di legacy modernization, questo significa tipicamente: valutare il sistema completo, identificare i sottosistemi ad alto costo, estrarli in modo incrementale usando pattern di migrazione, lasciare in esecuzione le parti stabili del sistema legacy. Il sistema migliora dove conta di più, e il rischio è concentrato in passi piccoli e reversibili.

Conclusione

Refactoring vs riscrittura non è una scelta binaria. Le opzioni realistiche sono: refactoring incrementale, sostituzione mirata di sottosistemi specifici e riscrittura completa. Le riscritture complete sono giustificate in circostanze tecniche ristrette e falliscono ad alto tasso quando usate come soluzione generale al debito tecnico accumulato. Il refactoring incrementale è a minor rischio ma richiede un impegno sostenuto. La sostituzione mirata è il percorso pragmatico di mezzo per la maggior parte dei sistemi che hanno problemi gravi in aree delimitate. La decisione dovrebbe essere basata su un’analisi tecnica specifica, non su quanto il sistema attuale sia doloroso con cui lavorare.

Hai un codebase con questi problemi? Parliamo del tuo sistema