Refactoring del codice legacy: tornare a cambiarlo senza paura, senza fermare la roadmap

Entriamo nel vostro codice insieme al team e lo rendiamo di nuovo facile da cambiare, un passo alla volta, senza riscriverlo da zero e senza lasciare la roadmap ferma ad aspettare.

Il software funziona, ma ogni modifica costa più della precedente

Quasi nessuno ci chiama perché il codice è brutto. Ci chiamano quando il prodotto vende, il team è capace e, nonostante questo, ogni cambiamento al sistema chiede settimane e si porta dietro qualche regressione.

  • Ogni funzionalità richiede più tempo di quella precedente
  • Piccole modifiche producono regressioni inattese in punti lontani del sistema
  • Ci sono parti del codice che il team preferisce non toccare
  • I test automatici sono pochi e ogni rilascio dipende da lunghi giri di verifica manuale
  • Solo una o due persone conoscono davvero i moduli critici
  • Una modifica di prodotto obbliga a toccare molti moduli che con quella modifica non hanno niente a che fare
  • Nel team si comincia a parlare di riscrivere tutto da zero

È attrito, e si concentra proprio sulle modifiche che hanno valore per il business, rendendole ogni mese un po' più care

Refactoring incrementale, dentro il vostro codice

Il refactoring del codice legacy rende di nuovo modificabile un sistema in produzione cambiandone la struttura senza cambiarne il comportamento, a piccoli passi protetti dai test. Non riscriviamo il sistema e non lavoriamo su una copia a parte. Il refactoring avviene nel codice che va in produzione, a piccoli passi reversibili, mentre il team continua a rilasciare funzionalità; ogni passo lascia il sistema funzionante e un po' più facile da cambiare di prima.

Il sistema che avete incorpora anni di conoscenza del dominio, compresi casi limite che nessuno ricorda più di aver gestito. Un percorso incrementale questa conoscenza la conserva, mentre una riscrittura deve ricostruirla da capo e quasi sempre la paga più del previsto.

Sui tempi preferiamo essere chiari fin dall'inizio: i primi progressi misurabili arrivano dopo almeno tre-sei mesi. Il percorso completo dura di più e dipende da quanto debito c'è e da dove si trova; il piano di uscita ne fissa le condizioni di chiusura fin dal primo giorno.

Due modalità, scelte in base al danno

Non tutto il debito chiede lo stesso intervento. Per questo lavoriamo in due modalità diverse, e la scelta fra le due non la facciamo a priori.

La modalità principale

Team di scopo

Quando il debito blocca la delivery su aree centrali del prodotto

Una coppia di developer QMates si unisce ad alcuni developer del vostro team e forma un piccolo team di esperti con un obiettivo preciso: rimettere in movimento la delivery dove oggi è ferma. Mentre lavora sul codice, il team di scopo porta le tecniche al resto del team, che le vede applicate sul proprio sistema prima ancora di usarle da solo.

La modalità leggera

Refactoring opportunistico

Quando il danno è contenuto e il sistema regge ancora il ritmo del prodotto

I team continuano a rilasciare funzionalità e, prima di aggiungerne una, migliorano il codice che quella funzionalità deve toccare. Il refactoring non diventa un cantiere separato con un budget suo: segue la roadmap e si concentra dove il prodotto sta andando.

Quale delle due modalità serve lo stabilisce l'assessment del debito tecnico, il primo passo del percorso. Misura dove il debito pesa davvero e quanto danno sta facendo; da lì si decide se serve un team di scopo o se basta il refactoring opportunistico.

Le tecniche che usiamo, e a cosa servono

Sono tecniche note a chi lavora da anni sul codice legacy. La differenza la fanno l'ordine in cui si applicano e il criterio con cui si sceglie dove applicarle.

  1. 01

    Test di caratterizzazione come rete di sicurezza

    Prima di cambiare una parte del sistema ne fotografiamo il comportamento attuale con test che lo descrivono così com'è, anche dove è sbagliato. Non dicono cosa il sistema dovrebbe fare, dicono cosa fa oggi, e segnalano subito se una modifica lo cambia senza volerlo. È la tecnica descritta da Michael Feathers in Working Effectively with Legacy Code, ed è il prerequisito di tutto il resto.

  2. 02

    Refactoring degli hotspot

    Il debito si paga dove il codice cambia spesso. Incrociamo lo storico delle modifiche con la complessità del codice e interveniamo prima sugli hotspot, le poche aree in cui complessità e frequenza di modifica si sommano; un modulo complicato che nessuno tocca da anni può restare com'è.

  3. 03

    Confini dei moduli e accoppiamento

    Quando una modifica di prodotto attraversa dieci moduli, il problema sta nei confini. Rendiamo espliciti i confini fra le parti del sistema, riduciamo le dipendenze che li attraversano e spostiamo le responsabilità dove il dominio le vuole, così che una modifica tocchi un posto solo.

  4. 04

    Strangler Fig per il monolite

    Quando una parte del monolite va sostituita, non esiste un giorno X del passaggio. Il nuovo codice cresce accanto al vecchio dietro un confine chiaro e prende in carico una funzione alla volta, finché il vecchio non serve più; il monolite continua a funzionare per tutto il tempo.

  5. 05

    Architettura solo dove serve

    Microservizi, eventi o nuovi strati non sono un obiettivo in sé. Interveniamo sull'architettura quando un vincolo strutturale impedisce al prodotto di evolvere, e solo in quel punto: un monolite modulare con confini chiari può reggere bene per anni.

Come misuriamo i progressi

All'inizio del percorso scegliamo con voi poche misure legate a ciò che oggi costa troppo, e le seguiamo nel tempo. Quali misure scegliere dipende dal sistema e da cosa sta frenando il business; per esempio:

  • il lead time delle modifiche
  • le regressioni che arrivano in produzione
  • il tempo che serve per un rilascio
  • il tempo per portare in produzione una nuova funzionalità

Non promettiamo numeri prima di aver visto il codice. Promettiamo di misurare dall'inizio, così che i progressi, o la loro assenza, siano visibili a tutti e non affidati a un'impressione.

Dove l'abbiamo già fatto

Due percorsi reali, nel codice dei clienti e insieme ai loro team.

SureVIVE

Il contesto
Un software critico per il coordinamento dei soccorsi, operativo giorno e notte, in cui un'architettura non più adatta e parti di codice legacy bloccavano la crescita del prodotto e dell'organizzazione.
Cosa abbiamo fatto
Un percorso di refactoring che ha lavorato su testabilità e design e ha anticipato le funzionalità di prodotto che ne avevano bisogno, procedendo insieme a loro.
Il risultato
Debito tecnico ridotto del 15% e funzionalità prima irrealizzabili diventate sviluppabili, con nuove opportunità di vendita per il prodotto.
Leggi il caso studio

Zextras

Il contesto
Una suite di collaborazione B2B sviluppata da più di tre gruppi distribuiti, con un team cresciuto da 5 a circa 30 developer e una delivery che si inceppava spesso.
Cosa abbiamo fatto
In dodici mesi, corsi di quattro giorni sulle pratiche tecniche per ogni team e una coppia QMates nel team core, che ha aiutato a costruire i feature team e ha guidato una parte del refactoring per estrarre la porzione di codebase che serviva.
Il risultato
Bug diminuiti del 15% e cycle time migliorato; il lead time è rimasto invariato perché i rilasci seguono una cadenza fissa.
Leggi il caso studio

Il team prosegue da solo

Un refactoring riuscito lascia un codice migliore e un team che non ha più bisogno di noi. Le tecniche passano al team mentre lavoriamo insieme, in coppia sul codice vero, e la nostra presenza cala man mano che il team le usa senza di noi.

Il piano di uscita si decide all'inizio

  1. 01Fissiamo quali competenze devono restare nel team e come ci accorgiamo che sono rimaste
  2. 02Riduciamo la presenza QMates per gradi, mentre il team prende in carico le aree rifattorizzate
  3. 03Lasciamo test, confini e misure che il team sa leggere e mantenere
  4. 04Chiudiamo quando il team porta avanti il refactoring nel lavoro di tutti i giorni

Quando non consigliamo di riscrivere, e quando sì

La domanda che ci fanno più spesso è se il sistema vada riscritto. Quasi sempre rispondiamo di no, e non per principio: una riscrittura costa più di quanto sembri, perché deve ricostruire anche la conoscenza nascosta nel codice esistente.

Non consigliamo di riscrivere quando

  • il sistema è in produzione e genera valore, anche se cambiarlo è faticoso
  • il problema si concentra in alcune aree e non in tutto il codice
  • il dolore principale è la mancanza di test o di conoscenza, che una riscrittura non risolve
  • il business non può permettersi mesi di funzionalità ferme o due sistemi da mantenere in parallelo

Consideriamo la riscrittura quando

  • la tecnologia non riceve più aggiornamenti di sicurezza ed è diventata un rischio per la continuità del servizio
  • l'accoppiamento è così pervasivo che nessuna parte si può isolare, e ogni modifica costa stabilmente più del valore che porta
  • il business cambia in modo radicale (un nuovo modello, un'acquisizione, una nuova piattaforma) e il sistema esistente non serve più, a prescindere dalla sua qualità

Anche in questi casi la sostituzione avviene per parti, con lo Strangler Fig, e non con un passaggio unico. Le variabili da valutare prima di decidere le abbiamo raccolte nella guida su quando convivere con il software legacy, quando evolverlo e quando riscriverlo.

Cosa non facciamo

  • Non riscriviamo tutto da zero come prima scelta
  • Non fermiamo la roadmap per aprire un cantiere di refactoring
  • Non trasformiamo il refactoring in una migrazione al cloud fine a sé stessa
  • Non offriamo garanzie generiche: preferiamo passi piccoli, verificati dai test e reversibili
  • Non facciamo solo formazione: scriviamo codice insieme al vostro team
  • Non creiamo dipendenza dalla nostra presenza

Interveniamo dove il cambiamento costa di più, e ci fermiamo quando il team sa andare avanti da solo

È il momento giusto se

  • Il prodotto cresce, ma il codice frena ogni nuova funzionalità
  • State valutando una riscrittura e volete capire se esiste una strada meno rischiosa
  • Avete ereditato un sistema legacy che il team fatica a capire e a modificare
  • Il debito tecnico perde sempre la priorità contro le funzionalità e intanto continua a crescere

Se invece il problema è un singolo nodo tecnico, urgente e circoscritto, la risposta più rapida è l'Intervento Mirato.

Domande frequenti

Lavorate anche su codice senza test?
Sì, ed è il caso più frequente. Si parte dai test di caratterizzazione, che descrivono il comportamento attuale del sistema anche dove è scorretto e diventano la rete di sicurezza per modificare il codice senza doverne capire subito ogni dettaglio. I test crescono insieme al refactoring, a partire dalle aree che si toccano più spesso.
Scrivete codice o fate formazione?
Scriviamo codice. Lavoriamo nel vostro repository, in coppia con i vostri developer, su funzionalità e refactoring veri, e le tecniche passano al team mentre si lavora. Quando serve un momento di formazione dedicato lo affianchiamo al lavoro sul codice, senza sostituirlo.
Come convive il refactoring con lo sviluppo delle funzionalità?
Il refactoring si fa dentro la delivery, non al posto suo. Con il refactoring opportunistico si migliora il codice che la prossima funzionalità deve toccare; con il team di scopo si sblocca un'area mentre il resto del team continua a rilasciare. In nessuno dei due casi la roadmap si ferma.
Lavorate anche sui monoliti?
Sì. Spesso un monolite non va spezzato ma reso modulare, con confini chiari al suo interno. Dove una parte va davvero sostituita usiamo lo Strangler Fig: il nuovo codice cresce accanto al vecchio e prende in carico una funzione alla volta, mentre il monolite continua a funzionare.
Quanto dura un percorso di refactoring del codice legacy?
I primi progressi misurabili arrivano dopo almeno tre-sei mesi. La durata complessiva dipende da quanto debito c'è e da dove si trova, e il piano di uscita, con le condizioni per chiudere, si definisce all'inizio.
Da dove si parte?
Dall'assessment del debito tecnico, che produce una mappa del debito con priorità e un piano d'intervento e indica anche quale modalità di refactoring serve. Se il team preferisce procedere da solo con quel piano, può farlo.

Il codice legacy frena ogni nuova funzionalità?

Partiamo da una call gratuita: ci raccontate il sistema e vi diciamo se serve prima un assessment

Trattiamo i tuoi dati per rispondere alla richiesta. Leggi la Privacy Policy.