A developer performing some refactoring on a PC

Quante volte ci siamo trovati ad affrontare un codice sorgente già in produzione, sviluppato da altri e del quale abbiamo poche informazioni?

Le Difficoltà Iniziali

L’approccio a un lavoro del genere può essere impegnativo: potremmo trascorrere ore solo cercando di comprendere il pensiero dell’originale sviluppatore. Di recente, durante le nostre sessioni di coaching tecnico, abbiamo affrontato una situazione simile. Vogliamo condividere con voi come abbiamo approcciato il lavoro insieme al team per apportare le modifiche richieste in sicurezza e migliorare la comprensione della codebase.

Nel nostro caso, abbiamo dovuto apportare modifiche a un servizio che si occupava di integrare dati tra sistemi interni e partner esterni. Anche se la modifica richiesta sembrava banale (aggiungere un caso particolare), l’implementazione si è rivelata più complessa del previsto. Una parte significativa della logica di integrazione era racchiusa in un unico “brain method”, una funzione di oltre un centinaio di righe di codice difficilmente comprensibile al primo sguardo.

Facciamo Chiarezza

Prima di procedere con le modifiche richieste, abbiamo deciso di applicare un refactoring per rendere la logica più comprensibile e agevolare l’introduzione del nuovo caso particolare.

Il primo passo è stato verificare l’esistenza di test automatici per ridurre il rischio di introdurre regressioni. Fortunatamente, avevamo già una buona base di test, ma se non fosse stato così, sarebbe stato opportuno aggiungerne qualcuno per mettere in esercizio il sistema e verificare i risultati ottenuti.
(Approfondiremo i test di caratterizzazione e approval test in un’altra puntata, per il momento soffermiamoci sul refactoring.)

Dopo esserci assicurati una buona copertura dei test, abbiamo esaminato nel dettaglio la funzione in questione, aggiungendo commenti per aiutarci a comprenderla meglio. Dopo una prima lettura e l’aggiunta dei commenti, è diventato evidente che alcune parti dell’implementazione potevano essere estratte in metodi più descrittivi, esprimendo chiaramente la logica di business che si voleva ottenere.

Ad esempio, una condizione ipotetica che controlla l’applicazione delle spese di spedizione gratuite potrebbe essere espressa così:

if ordine.totaleSpesa > 100 && ordine.scontoApplicato == 0 {
    // Gestione spese di spedizione gratuite...
}

Grazie al refactoring, la condizione potrebbe essere modificata per rendere più esplicito il comportamento richiesto, nascondendo i dettagli implementativi e semplificando la comprensione del codice:

if ordine.okPerSpedizioneGratuita() {
    // Gestione spese di spedizione gratuite...
}

Dopo aver verificato che i test automatici fossero ancora rispettati, abbiamo estratto piccoli pezzi di codice consistenti in funzioni dedicate, permettendoci di comprendere meglio la logica esistente.

Continuare a Migliorare la Codebase

Questo primo intervento ha messo in evidenza ulteriori possibili miglioramenti. Abbiamo individuato variabili non utilizzate, frammenti di logica coesi ma dispersi e nomi poco espliciti. Sono stati apportati numerosi piccoli miglioramenti, permettendoci di ottenere una comprensione completa del codice esistente.

A questo punto, l’introduzione del nuovo caso particolare richiesto è stata un’operazione quasi automatica: abbiamo aggiunto test che descrivevano la nuova casistica e l’abbiamo implementata con sorprendente semplicità.

I Risultati Ottenuti

Alla fine del lavoro, il metodo originale è stato ridotto di più della metà, passando da oltre 100 a meno di 50 righe di codice (nuovi sviluppi inclusi). Questo ha migliorato la leggibilità, la comprensione delle logiche di business e la facilità di manutenzione.

Il team ha acquisito padronanza del codice legacy e ha gettato le basi per un software più manutenibile in futuro, lasciando la codebase in una situazione migliore rispetto all’inizio.

Costruisci il tuo percorso verso l’eccellenza tecnica