Durante lo sviluppo di un’ap­pli­ca­zio­ne nei codici sorgente si ac­cu­mu­la­no punti strut­tu­ra­ti in modo non pulito, che mettono a rischio la com­pa­ti­bi­li­tà e la pos­si­bi­li­tà di applicare un programma. Due sono le possibili soluzioni: un codice sorgente com­ple­ta­men­te nuovo o una ri­strut­tu­ra­zio­ne a piccoli step. Molti pro­gram­ma­to­ri e aziende si orientano sempre di più verso il code re­fac­to­ring per ot­ti­miz­za­re a lungo termine un software fun­zio­nan­te o per renderlo più chiaro e leggibile per altri pro­gram­ma­to­ri.

Nel caso del re­fac­to­ring ci si pone la seguente domanda: qual è il problema del codice che può essere risolto e cosa si può fare per ri­sol­ver­lo? Nel frattempo il re­fac­to­ring è diventato una delle basi dello studio della pro­gram­ma­zio­ne e sta ac­qui­sen­do sempre più im­por­tan­za. Quali metodi vengono uti­liz­za­ti e quali sono i vantaggi e gli svantaggi?

Cos’è il re­fac­to­ring?

La pro­gram­ma­zio­ne di un software è un processo lungo e com­pli­ca­to al quale a volte lavorano più svi­lup­pa­to­ri e il testo sorgente viene spesso rie­la­bo­ra­to, adattato o ampliato. La pressione in termini di tempo e pratiche obsolete provocano però l’ac­cu­mu­lar­si di punti poco lineari nel codice sorgente, i co­sid­det­ti code smell. I punti deboli, ac­cre­sciu­ti nel tempo, mettono a rischio l’ap­pli­ca­bi­li­tà e la com­pa­ti­bi­li­tà di un programma. Per evitare l’erosione continua e il peg­gio­ra­men­to di un software è ne­ces­sa­rio il re­fac­to­ring.

Il re­fac­to­ring può essere pa­ra­go­na­to alla revisione di un libro, che non comporta la nascita di un libro to­tal­men­te nuovo, bensì di un testo più facile da com­pren­de­re. Così come diversi sono gli approcci durante la revisione tramite tron­ca­tu­re, ri­for­mu­la­zio­ni, tagli e modifiche, anche nel code re­fac­to­ring troviamo metodi come l’in­cap­su­la­zio­ne, la ri­for­mat­ta­zio­ne e l’estra­zio­ne per ot­ti­miz­za­re un codice senza cambiarne il fulcro.

Questo processo chia­ra­men­te è meno costoso rispetto alla rea­liz­za­zio­ne di una struttura del codice com­ple­ta­men­te nuova. Il re­fac­to­ring ha un ruolo im­por­tan­te so­prat­tut­to nello sviluppo di software di tipo in­te­rat­ti­vo e in­cre­men­ta­le e anche nello sviluppo software agile, perché i pro­gram­ma­to­ri mo­di­fi­ca­no co­stan­te­men­te un software tramite questo modello ciclico. Il re­fac­to­ring quindi è una fase di lavoro costante.

Quando un codice sorgente viene eroso: spaghetti code

Per prima cosa è ne­ces­sa­rio capire come invecchia un codice e come può mutare in un fa­mi­ge­ra­to “spaghetti code”. A causa del poco tempo a di­spo­si­zio­ne, della mancanza di espe­rien­za o di istru­zio­ni poco chiare e di comandi inu­til­men­te com­pli­ca­ti, la pro­gram­ma­zio­ne di un codice porta a perdite di fun­zio­na­li­tà. Un codice viene eroso in maniera pro­por­zio­na­le alla velocità e alla com­ples­si­tà del suo campo di ap­pli­ca­zio­ne.

Si chiama spaghetti code un codice sorgente confuso e il­leg­gi­bi­le, il cui lin­guag­gio è dif­fi­cil­men­te com­pren­si­bi­le per i pro­gram­ma­to­ri. Semplici esempi di codice confuso sono quei comandi di salto superflui (GOTO) che istrui­sco­no un programma a saltare da una parte all’altra del codice sorgente, oppure for/while loop o istru­zio­ni if non necessari.

Sono so­prat­tut­to i progetti ai quali lavorano più svi­lup­pa­to­ri software ad avere dei testi sorgente poco leggibili. Se il codice sorgente passa per più mani e se già l’originale presenta dei punti deboli, è difficile evitare un aumento di grovigli ri­cor­ren­do a “soluzioni di emergenza” e a una costosa revisione del codice. Nel suo scenario peggiore uno spaghetti code può mettere a re­pen­ta­glio l’intero sviluppo di un software; in tal caso nemmeno il code re­fac­to­ring può eliminare il problema.

Non così gravi sono poi i code smell e code rot. Con il passare del tempo, un codice, a causa di elementi non puliti, può risultare poco lineare. I punti poco chiari peg­gio­ra­no ul­te­rior­men­te in seguito all’in­ter­ven­to di altri pro­gram­ma­to­ri o alle esten­sio­ni. Se non si opera un re­fac­to­ring alle prime av­vi­sa­glie di un code smell, il codice sorgente perde a vista d’occhio d’integrità e tramite il code rot (dall’inglese rot “marcio”) perde la sua funzione.

Qual è lo scopo del re­fac­to­ring?

Il re­fac­to­ring mira a un codice pulito e semplice, in una parola migliore. Se il codice è ef­fi­cien­te, i nuovi elementi del codice possono essere integrati in maniera migliore senza che si creino nuovi errori. I pro­gram­ma­to­ri che possono leggere un codice senza sforzo, si orientano più ra­pi­da­men­te e possono eliminare o evitare in maniera più semplice i bug. Un altro compito del re­fac­to­ring è una miglior analisi degli errori e ma­nu­te­ni­bi­li­tà del software. I pro­gram­ma­to­ri che ve­ri­fi­ca­no un codice implicano quindi un lavoro minore.

Quali sono le fonti di errore che vengono eliminate con il re­fac­to­ring?

Le tecniche che vengono impiegate nel re­fac­to­ring sono tanto varie quanto gli errori che sono in grado di eliminare. Il code re­fac­to­ring si definisce in base ai suoi errori ed indica gli step necessari per eliminare un problema o trovare ra­pi­da­men­te una soluzione. Le fonti di errori che possono essere eliminate tramite il re­fac­to­ring sono tra le altre:

  • metodi confusi o troppo lunghi: le catene o i blocchi di comandi sono così lunghi che terze persone non riescono a com­pren­de­re la logica del software.
  • Du­pli­ca­zio­ni del codice (ri­don­dan­ze): un codice confuso presenta spesso delle ri­don­dan­ze che, nel caso di ma­nu­ten­zio­ni, devono essere mo­di­fi­ca­te in ciascun punto se­pa­ra­ta­men­te e quindi implicano perdite di tempo e costi notevoli.
  • Elenchi di parametri ec­ces­si­va­men­te lunghi: gli oggetti non vengono at­tri­bui­ti di­ret­ta­men­te a un metodo, bensì i loro attributi vengono trasmessi a un elenco di parametri.
  • Classi con troppe funzioni: classi con troppe funzioni definite come metodi, de­no­mi­na­te anche come God object, che rendono quasi im­pos­si­bi­le un ade­gua­men­to del software.
  • Classi con troppo poche funzioni: classi con così poche funzioni definite come metodi da essere inutili.
  • Codici troppo generali con casi speciali: funzioni con eccezioni troppo spe­ci­fi­che, che so­prag­giun­go­no molto raramente o non si ve­ri­fi­ca­no affatto e che quindi com­pli­ca­no l’in­se­ri­men­to di esten­sio­ni ne­ces­sa­rie.
  • Middle man: una classe separata fa da in­ter­me­dia­rio tra metodi e classi diverse, invece che portare a richiami di metodi di­ret­ta­men­te in una classe.

Come si procede nel re­fac­to­ring?

Il re­fac­to­ring dovrebbe sempre essere ef­fet­tua­to prima della modifica di una funzione di un programma. Il modo migliore per eseguirlo è procedere a pic­co­lis­si­mi step, testando le modifiche del codice tramite processi di sviluppo software come Test Driven De­ve­lo­p­ment (TDD) e Con­ti­nuous In­te­gra­tion (CI). Per farla breve i TDD e i CI co­sti­tui­sco­no i continui test di nuovi, piccoli segmenti di codice, che vengono creati e integrati dai pro­gram­ma­to­ri e le cui fun­zio­na­li­tà vengono testate tramite procedure spesso au­to­ma­tiz­za­te.

Vale la seguente regola: mo­di­fi­ca­re il programma in­ter­na­men­te solo a piccoli step, senza influire sulla funzione esterna. Dopo ogni modifica andrebbe ef­fet­tua­to, per quanto possibile, un test di fun­zio­na­men­to au­to­ma­tiz­za­to.

Quali sono le tecniche a di­spo­si­zio­ne?

Ci sono numerose concrete tecniche di re­fac­to­ring. Una pa­no­ra­mi­ca esaustiva si trova nell’opera più completa sul re­fac­to­ring, ossia quella di Martin Fowler e Kent Beck: “Re­fac­to­ring: Improving the Design of Existing Code”. Di seguito una breve sintesi:

Approccio rosso-verde

L’approccio rosso-verde è un metodo test driven (a sviluppo guidato) dello sviluppo software agile. Viene uti­liz­za­to quando si vuole integrare una nuova funzione in un codice già esistente. Il rosso rap­pre­sen­ta la prima serie di test prima dell’im­ple­men­ta­zio­ne di una nuova funzione in un codice. Il verde invece sta per il segmento di codice più semplice possibile, ne­ces­sa­rio perché la funzione superi il test. Ne segue un’esten­sio­ne con test costanti per eliminare codici che pre­sen­ta­no errori e per au­men­tar­ne la fun­zio­na­li­tà. L’approccio rosso-verde è uno dei fon­da­men­ti per il re­fac­to­ring continuo nello sviluppo continuo del software.

Branching-by-Ab­strac­tion

Questo sistema di re­fac­to­ring descrive un cam­bia­men­to graduale di un sistema e il passaggio delle posizioni di codice im­ple­men­ta­te pre­ce­den­te­men­te nei nuovi segmenti integrati. Il Branching-by-Ab­strac­tion viene so­li­ta­men­te impiegato quando si ef­fet­tua­no grossi cam­bia­men­ti che ri­guar­da­no la gerarchia delle classi, l’ere­di­ta­rie­tà e l'e­stra­zio­ne. Im­ple­men­tan­do un’astra­zio­ne, che resta collegata ad una vecchia im­ple­men­ta­zio­ne, è possibile collegare altri metodi e classi con l’astra­zio­ne stessa e la fun­zio­na­li­tà del vecchio segmento di codice può essere so­sti­tui­ta tramite l’astra­zio­ne.

Ciò spesso è rea­liz­za­to con metodi pull-up o push-down. Questi collegano una nuova e migliore funzione con l’astra­zio­ne e tra­smet­to­no a quest’ultima i col­le­ga­men­ti. Così, una classe inferiore viene spostata verso l’alto (pull-up) o parti di una classe superiore passano ad una classe inferiore (push-down).

Le vecchie funzioni possono poi essere can­cel­la­te senza mettere a re­pen­ta­glio l’intera fun­zio­na­li­tà. Tramite queste modifiche capillari il sistema funziona senza che ci siano cam­bia­men­ti, mentre so­sti­tui­te i codici unclean con quelli clean segmento per segmento.

Metodi di com­pi­la­zio­ne

Il re­fac­to­ring dovrebbe rendere i metodi di un codice il più leggibili possibile. Già nella fase di lettura, nel migliore dei casi, la logica in­trin­se­ca di un metodo appare chiara anche ai pro­gram­ma­to­ri esterni. Per una creazione di metodi ef­fi­cien­te ci sono diverse tecniche nel re­fac­to­ring. Lo scopo di ogni modifica è quello di stan­dar­diz­za­re i metodi, eliminare le du­pli­ca­zio­ni e dividere i metodi troppo lunghi in segmenti separati che siano più ac­ces­si­bi­li a modifiche suc­ces­si­ve.

Tra queste tecniche vi sono per esempio:

  • estra­zio­ne dei metodi
  • po­si­zio­na­men­to metodi inline
  • eli­mi­na­zio­ne di variabili tem­po­ra­nee
  • so­sti­tu­zio­ne di variabili tem­po­ra­nee per mezzo di metodi di richiesta
  • in­tro­du­zio­ne di variabili de­scrit­ti­ve
  • se­pa­ra­zio­ne di variabili tem­po­ra­nee
  • eli­mi­na­zio­ne di as­se­gna­zio­ni alle variabili dei parametri
  • so­sti­tu­zio­ne di un metodo con un oggetto-metodo
  • so­sti­tu­zio­ne di un algoritmo

Spostare le proprietà tra le classi

Per mi­glio­ra­re un codice, a volte è ne­ces­sa­rio spostare attributi o metodi tra le classi. A tal fine sono di­spo­ni­bi­li le seguenti tecniche:

  • spo­sta­men­to metodo
  • spo­sta­men­to attributo
  • estra­zio­ne classe
  • po­si­zio­na­men­to classe inline
  • na­scon­de­re delegato
  • eli­mi­na­zio­ne classe al centro
  • in­tro­du­zio­ne metodo esterno
  • in­tro­du­zio­ne esten­sio­ne locale

Or­ga­niz­za­zio­ne dati

Questo metodo ha come scopo quello di sud­di­vi­de­re i dati in classi e mantenere queste ultime il più possibile chiare e com­pren­si­bi­li. I col­le­ga­men­ti non necessari tra classi, che possono dan­neg­gia­re la fun­zio­na­li­tà dei software alla più piccola modifica, vanno eliminati e suddivisi in classi coerenti.

Esempi di queste tecniche sono:

  • in­cap­su­la­men­to dei propri accessi agli attributi
  • so­sti­tu­zio­ne dei propri attributi con ri­fe­ri­men­ti ad oggetti
  • so­sti­tu­zio­ne di valore con ri­fe­ri­men­to
  • so­sti­tu­zio­ne di ri­fe­ri­men­to con valore
  • ac­cop­pia­men­to dei dati os­ser­va­bi­li
  • in­cap­su­la­men­to di attributi
  • so­sti­tu­zio­ne di record di dati con classi di dati

Sem­pli­fi­ca­zio­ne di espres­sio­ni con­di­zio­na­li

Sarebbe bene sem­pli­fi­ca­re il più possibile le espres­sio­ni con­di­zio­na­li durante il re­fac­to­ring. A tal fine sono di­spo­ni­bi­li le seguenti tecniche:

  • scom­po­si­zio­ne delle con­di­zio­ni
  • ri­con­giun­gi­men­to di espres­sio­ni con­di­zio­na­li
  • ri­con­giun­gi­men­to di istru­zio­ni ripetute in espres­sio­ni con­di­zio­na­li
  • eli­mi­na­zio­ne di in­ter­rut­to­ri di controllo
  • so­sti­tu­zio­ne di espres­sio­ni con­di­zio­na­li con guardie
  • so­sti­tu­zio­ne della dif­fe­ren­zia­zio­ne con la po­li­mor­fia
  • in­tro­du­zio­ne di oggetti nulli

Sem­pli­fi­ca­zio­ne delle chiamate dei metodi

Le chiamate dei metodi possono essere ef­fet­tua­te tra le altre cose at­tra­ver­so i seguenti metodi in maniera più rapida e semplice:

  • ri­de­no­mi­na­zio­ne metodi
  • in­se­ri­men­to parametri
  • eli­mi­na­zio­ne parametri
  • so­sti­tu­zio­ne parametri tramite metodi espliciti
  • so­sti­tu­zio­ne di codici errore con eccezioni

Esempio di re­fac­to­ring: ri­de­no­mi­na­zio­ne metodi

Nel seguente esempio si nota che nel codice originale la de­no­mi­na­zio­ne del metodo non rende la sua fun­zio­na­li­tà chiara e facile da capire. Il metodo dovrebbe tra­smet­te­re il codice di av­via­men­to postale dell’indirizzo di un ufficio, ma non mostra questo compito di­ret­ta­men­te nel codice. Per formulare in maniera più chiara il codice, si può ricorrere al code re­fac­to­ring per ri­no­mi­na­re il metodo.

Prima:

String getPostalCode() {
	return (theOfficePostalCode+“/“+theOfficeNumber);
}
System.out.print(getPostalCode());

Dopo:

String getOfficePostalCode() {
	return (theOfficePostalCode+“/“+theOfficeNumber);
}
System.out.print(getOfficePostalCode());

Re­fac­to­ring: quali sono i vantaggi e gli svantaggi?

Vantaggi Svantaggi
Una migliore com­pren­si­bi­li­tà facilita la ma­nu­te­ni­bi­li­tà e la pos­si­bi­li­tà di ampliare un software. Un re­fac­to­ring non preciso potrebbe im­ple­men­ta­re nuovi bug ed errori nel codice.
La ri­strut­tu­ra­zio­ne del codice sorgente è possibile senza mo­di­fi­car­ne la fun­zio­na­li­tà. Non esiste una de­fi­ni­zio­ne chiara di “clean code”.
Una miglior leg­gi­bi­li­tà aumenta la com­pren­si­bi­li­tà del codice per altri pro­gram­ma­to­ri. Un codice migliore spesso non è ri­co­no­sci­bi­le per il cliente perché la fun­zio­na­li­tà resta la stessa, quindi i vantaggi non sono evidenti.
L’eli­mi­na­zio­ne delle ri­don­dan­ze e delle du­pli­ca­zio­ni aumenta l’ef­fi­cien­za del codice. In caso di team numerosi che lavorano al re­fac­to­ring, lo sforzo di coor­di­na­men­to potrebbe essere ina­spet­ta­ta­men­te elevato.
I metodi coerenti im­pe­di­sco­no che le modifiche apportate a livello locale possano influire su altre parti del codice.  
Un codice pulito con metodi e classi più brevi e coerenti è ca­rat­te­riz­za­to da una maggior te­sta­bi­li­tà.  

In linea di massima, nel re­fac­to­ring bisogna inserire nuove funzioni solamente quando il codice sorgente esistente resta inal­te­ra­to. Vanno apportate modifiche al codice sorgente, ovvero si esegue un re­fac­to­ring solo se non si in­se­ri­sco­no nuove funzioni.

Vai al menu prin­ci­pa­le