Moduli collegati accanto a un componente separato e a una freccia di ritorno, per illustrare un intervento su un confine senza sostituire l’intero sistema.

refactoring

Quando finanziare il refactoring del codice legacy

Il budget per il refactoring va legato a una modifica concreta, confrontando il costo delle attese con quello di un intervento dal rischio circoscritto.

Il costo della modifica rimasta senza stima

Nella riunione di pianificazione arrivi al modulo che nessuno vuole toccare, e la stima della nuova funzionalità resta sospesa finché non può guardarlo la persona che lo conosce. La richiesta sembra locale; nessuno, però, sa dire quali effetti produrrà altrove. Il costo del codice legacy è già lì, nella consegna in attesa e nell’attenzione sottratta ad altri lavori, anche se non compare come voce separata nel budget.

Per proporre un refactoring, parti da un rischio di modifica identificato: quale cambiamento serve al prodotto e che cosa impedisce al team di affrontarlo con un perimetro credibile. La paura, da sola, non dimostra che il software vada migliorato o sostituito. Un sistema stabile, senza nuove richieste, può meritare soltanto manutenzione operativa; il fatto che nessuno apprezzi il suo codice non cambia questa valutazione.

Porta alla discussione osservazioni già disponibili, cercandole nei ticket, nelle revisioni delle stime e nelle code review: indagini ripetute, attese per lo stesso esperto, attività di onboarding che non procedono senza il suo aiuto. Separale dalle ipotesi su ciò che un intervento potrebbe evitare in futuro. Sono elementi per scegliere dove spendere tempo, non una base da cui ricavare un ROI numerico che non hai misurato.

Le alternative e il lavoro che ciascuna rimanda

La scelta riguarda chi assorbe il costo e quale rischio resta dopo il lavoro. Prima di discutere il disegno del nuovo codice, confronta le alternative rispetto alla prossima richiesta reale: una soluzione sensata per una modifica isolata può diventare insufficiente se lo stesso vincolo attraversa più flussi.

Scelta Contenimento del rischio Effetto sulle consegne Trasferimento di conoscenza Condizione per sceglierla
Rinvio o mantenimento Il rischio di modifica resta; si evita di introdurne uno nuovo Nessun intervento ora; le richieste future possono tornare in attesa Limitato, salvo lavoro esplicito sul passaggio di consegne Nessuna domanda prevista che giustifichi l’intervento
Dismissione Elimina il rischio del componente solo dopo aver rimosso dipendenze e obblighi residui Richiede tempo per spostare utenti o flussi e gestire i dati Serve a capire che cosa si può davvero spegnere Il componente non serve più e c’è un percorso di uscita verificabile
Refactoring circoscritto nella consegna Riduce l’incertezza entro un confine verificato La funzionalità paga analisi, verifiche e pairing Un secondo sviluppatore ricostruisce il ragionamento insieme al primo Il rischio della modifica resta locale e il lavoro entra nel limite concordato
Intervento dedicato per fasi Affronta un vincolo condiviso, con verifiche a ogni fase Il responsabile di prodotto rimanda lavoro concordato; il team mantiene il sistema corrente Più persone assumono responsabilità su confini espliciti Le modifiche necessarie incontrano lo stesso ostacolo trasversale
Riscrittura Consente di cambiare un impianto inadatto, ma introduce rischi di equivalenza e migrazione Team e operations sostengono transizione e possibile esercizio parallelo Richiede di ricostruire contratti di business e procedure operative I vincoli dimostrati rendono insufficiente un percorso incrementale

Il rinvio esplicito è diverso dal lasciare che ogni ticket riapra la discussione. Se il componente non cambia, stabilisci quale nuova richiesta farà riesaminare la decisione. Se lo dismetti, verifica prima chi ne legge i dati e quali obblighi impongono di conservarli: togliere il deploy non equivale a eliminare il servizio che il sistema forniva.

Il refactoring nella consegna, invece, fa pagare alla funzionalità prevista il lavoro necessario a modificarla senza ampliare alla cieca il rischio. È una scelta difendibile quando produce un confine utile anche dopo quella consegna, purché non trasformi ogni imperfezione incontrata in un nuovo requisito tecnico.

Il perimetro tecnico di un refactoring circoscritto

Un diff piccolo può avere effetti estesi quando attraversa invarianti implicite o stato condiviso nascosto. Un helper apparentemente innocuo può aggiornare un contesto letto più avanti; un’integrazione può inviare un evento prima che la transazione sia completata. Senza verifiche del comportamento, non sai se stai cambiando una responsabilità locale o l’ordine su cui un altro componente fa affidamento.

Considera un esempio ipotetico: devi modificare una regola di validazione degli ordini, chiamata sia da un vecchio client sincrono sia da un consumer asincrono. Il client interpreta il rifiuto come risposta definitiva; il consumer può ricevere di nuovo lo stesso evento. Se non è chiaro chi impedisce di applicare due volte gli effetti, estrarre la validazione in una funzione condivisa non basta a circoscrivere il rischio.

Il contratto e la verifica dei due ingressi

I test di caratterizzazione possono fissare gli output e gli effetti oggi osservati per alcuni casi rappresentativi. Non dimostrano che quel comportamento sia corretto: potrebbero conservare un errore o una scelta ormai superata. Tieni distinto il comportamento esistente da quello richiesto e chiedi al responsabile di prodotto di decidere le ambiguità, per esempio quale stato dell’ordine debba governare una nuova validazione dopo un retry.

Un’invariante da concordare nell’esempio potrebbe essere che un ordine rifiutato non avvii l’evasione, nemmeno alla riconsegna dell’evento. Dopo l’approvazione del responsabile di prodotto, puoi scegliere un confine di intervento: separare la decisione di validazione dagli effetti, mantenendo negli adattatori la gestione specifica dei due ingressi. Prima di spostare codice, verifica dove vive lo stato condiviso e quali chiamanti dipendono dall’ordine degli effetti. La separazione aiuta soltanto se rende quelle dipendenze esplicite; aggiungere un’astrazione sopra lo stesso stato nascosto le lascia intatte.

Il controllo deve comprendere il percorso di errore, non soltanto l’ordine accettato: rifiuto, timeout dell’integrazione, riconsegna dell’evento dopo un effetto già applicato. Scegli i casi in base al confine che stai cambiando, senza chiamarli una verifica dell’intero sistema. È questo il valore di un’architettura semplice da capire: poter seguire una responsabilità e i suoi effetti senza ricostruire ogni volta tutto il flusso.

Il lavoro a due serve al trasferimento del ragionamento, non solo a raddoppiare gli occhi sulla PR. Chiedi al secondo sviluppatore di spiegare perché la modifica conserva l’invariante, dove può fallire e quale verifica manca ancora. Se per rispondere deve aspettare di nuovo l’esperto, il passaggio di conoscenza non è concluso.

Le prove per un mandato dedicato o una riscrittura

Il refactoring circoscritto perde senso quando il confine continua ad allargarsi: per modificare una regola devi cambiare responsabilità condivise da flussi estranei alla richiesta, oppure non puoi osservare gli effetti senza intervenire su più integrazioni. Fermarti in quel punto evita di finanziare un progetto trasversale attraverso una stima nata per una singola funzionalità.

Per un intervento dedicato, presenta al CTO il lavoro di prodotto da rimandare, il vincolo da rimuovere e la verifica prevista al termine della prima fase. Conserva un percorso ordinario per le altre consegne, evitando che tutto il team dipenda dal completamento del nuovo disegno. Se l’attesa nasce soprattutto dalla disponibilità dell’unico revisore, considera anche le dipendenze organizzative che rallentano le consegne: una diversa struttura del codice non libera da sola il suo calendario.

La riscrittura richiede prove ulteriori: per esempio, un vincolo del modello corrente che impedisce il comportamento necessario e che non riesci a rimuovere per fasi con un costo operativo accettabile. Devi comunque ricostruire i contratti di business, migrare i dati e decidere come convivranno vecchio e nuovo sistema. Il repository nuovo elimina il vecchio codice, non queste responsabilità.

Un mandato per un intervento di refactoring dovrebbe quindi contenere un limite di indagine e una data di riesame. Chi approva sceglie tra una funzionalità rimandata e la riduzione di un rischio nominato; al riesame valuta prove del comportamento, comprensione condivisa e rischio residuo. Se il perimetro non regge, il team torna a chiedere una decisione, invece di proseguire su un’autorizzazione implicita.

Il costo del recupero dopo il deploy

La possibilità di ripristinare il codice non garantisce la possibilità di recuperare lo stato. Nell’esempio degli ordini, un rollback non ritira un evento già consumato né annulla gli effetti applicati da un’integrazione. Occorre sapere come individuare gli ordini interessati e chi può riconciliare i dati senza duplicare ulteriori operazioni.

Prova il percorso di recupero in un ambiente adatto, includendo il punto in cui interrompere l’elaborazione e le informazioni necessarie a ricostruire gli effetti. Una modifica distruttiva allo schema o una doppia scrittura tra sistemi può togliere la possibilità di tornare alla versione precedente, oppure lasciare dati divergenti. In questi casi servono una strategia di migrazione e responsabilità operative esplicite; non basta promettere che il deploy sarà reversibile.

Concorda anche una condizione di arresto prima dell’intervento: un effetto esterno non verificabile, un contratto ancora ambiguo o l’impossibilità di riconciliare i dati possono impedire di procedere. È un risultato dell’indagine, non un invito ad aumentare il budget senza discuterne.

Domande frequenti

Come procedere senza un budget dedicato?

Proponi una piccola indagine legata alla prossima consegna, concordandone prima il limite con chi possiede la priorità. Il risultato può essere una decisione di rinvio o la prova che serve un mandato separato; non deve per forza essere una PR di refactoring.

Il sistema è stabile e non sono previste modifiche: serve intervenire?

La stabilità senza nuova domanda sostiene il mantenimento, salvo rischi operativi o obblighi che richiedano un intervento. Puoi documentare le procedure essenziali e verificare le dipendenze senza avviare una riorganizzazione del codice per renderlo più gradevole.

Da dove partire se i test esistenti non sono affidabili?

Crea una verifica mirata e ripetibile sul comportamento che devi preservare, controllando anche le dipendenze del test. Se non puoi distinguere una regressione da un errore dell’ambiente, quella verifica non autorizza la modifica: prima va reso leggibile il suo risultato.

Che cosa fare se l’unico esperto non è disponibile?

Ricostruisci il flusso dai materiali verificabili, come chiamanti, incidenti e decisioni registrate, annotando ciò che resta incerto. Se manca una conoscenza indispensabile per gestire un effetto irreversibile, rimanda quel cambiamento e fai approvare l’attesa; non trasformare l’assenza dell’esperto in un’ipotesi sul contratto.

Scegli una modifica già prevista, concorda l’invariante osservabile, il recupero e la condizione di arresto, poi coinvolgi un secondo sviluppatore prima del più piccolo intervento che rispetti quei limiti.

← Tutte le noteArchivio completo →