Home
Refactoring: che cosa fare quando il codice puzza

06 Settembre 2019

Refactoring: che cosa fare quando il codice puzza

di

«Se puzza, cambialo».
– La nonna di Kent Beck, a proposito di filosofia della cura dei figli.

È il momento di scoprire in che modo riguardi la rifattorizzazione del software.

Decidere quando iniziare il refactoring (e quando fermarsi) è importante quanto conoscerne la meccanica.

È qui che arriva il dilemma. È facile spiegare come eliminare una variabile di istanza o come creare una gerarchia. Sono cose semplici. Cercare di spiegare quando devi fare queste cose non è altrettanto elementare. Invece di fare appello a qualche vaga nozione di estetica della programmazione (il che, francamente, è quello che di solito facciamo noi consulenti), volevo qualcosa di un po’ più concreto.

Mentre scrivevo la prima edizione de L’arte del Refactoring, riflettevo sul problema quando sono andato a trovare Kent Beck a Zurigo. Forse era sotto l’influsso degli odori di sua figlia nata da poco, ma se ne era uscito con l’idea di descrivere il quando del refactoring in termini di puzza.

Puzza, dirai, e questo dovrebbe essere meglio della vaghezza dell’estetica? Beh, sì. Abbiamo esaminato un sacco di codice, scritto per progetti che coprono tutta la gamma, da quelli di grandissimo successo a quelli quasi morti. Così facendo, abbiamo imparato a cercare nel codice certe strutture che fanno pensare alla possibilità del refactoring, e qualche volta lo chiedono a gran voce. (Passo al noi perché Kent ed io abbiamo scritto questo pezzo insieme. Puoi notare la differenza, perché le battute divertenti sono mie, le altre sono sue).

Una cosa che non daremo sono criteri precisi per quando è doveroso un refactoring. In base alla nostra esperienza, non esiste metrica che possa competere con l’intuizione di un essere umano informato. Forniremo piuttosto indicazioni dell’esistenza di problemi che possono essere risolti con una rifattorizzazione. Dovrai sviluppare una tua sensibilità per quante variabili di istanza o quante righe di codice in un metodo sono troppe.

L’elenco di rifattorizzazioni che segue è solo un assaggio di quello che è possibile. Il punto è sempre individuare la puzza di problemi nel codice ed eliminarla con la giusta rifattorizzazione, come nei prossimi esempi. Ne L’arte del Refactoring trovi un elenco molto più lungo e dettagliato.

1. Nome misterioso

Rimanere perplessi davanti a un testo per capire che cosa succede è una bella cosa se leggi un romanzo giallo, ma non quando leggi del codice. Possiamo fantasticare di essere controspioni, ma il nostro codice deve essere semplice e chiaro. Una delle parti più importanti di un codice chiaro sono buoni nomi, perciò dedichiamo molta riflessione ai nomi di funzioni, moduli, variabili, classi, perché comunichino chiaramente quello che fanno e come vadano usati.

Purtroppo, però, dare nomi è una delle due cose difficili della programmazione. Perciò probabilmente le rifattorizzazioni più comuni che eseguiamo sono i cambiamenti di nome: Change Function Declaration (per cambiare il nome di una funzione), Rename Variable e Rename Field. Molti spesso hanno paura a cambiare i nomi, perché pensano che non ne valga la pena, ma un buon nome può risparmiare ore di incomprensione in futuro. Ragionare su un nome delicato spesso ci ha portati a semplificazioni significative del nostro codice.

2. Codice duplicato

Se vedi la stessa struttura di codice in più di un punto, il programma starà meglio se trovi un modo per unificarle.

Il problema più semplice dovuto a codice duplicato si presenta quando hai la stessa espressione in due metodi della stessa classe. Allora tutto quello che devi fare è Extract Function e invocare il codice da entrambi i punti. Se hai codice simile, ma non proprio identico, vedi se puoi usare Slide Statements per disporre il codice in modo che elementi simili siano tutti radunati vicini per una facile estrazione. Se i frammenti duplicati sono in sottoclassi di una classe base comune, puoi usare Pull Up Method per evitare di chiamare l’una dall’altra.

3. Lista di parametri lunga

Agli inizi della programmazione, ci è stato insegnato di passare come parametri tutto ciò di cui ha bisogno una funzione. Era comprensibile, perché l’alternativa erano dati globali e i dati globali diventano rapidamente diabolici. Anche lunghe liste di parametri però sono spesso, a loro volta, fonte di confusione.

Se puoi ottenere un parametro chiedendolo a un altro parametro, puoi usare Replace Parameter with Query per eliminarlo. Anziché estrarre molti dati da una struttura di dati esistente, puoi usare Preserve Whole Object per passare invece la struttura di dati originale. Se vari parametri stanno sempre insieme, combinali con Introduce Parameter Object. Se un parametro è usato come flag per inviare a un comportamento diverso, usa Remove Flag Argument.

Le classi sono un ottimo modo per ridurre le dimensioni delle liste di parametri. Sono particolarmente utili quando più funzioni condividono vari valori di parametro. Poi, puoi usare Combine Functions into Class per catturare quei valori comuni come campi. Se indossiamo il cappellino della programmazione funzionale, possiamo dire che questo crea un insieme di funzioni parzialmente applicate.

4. Invidia di funzionalità

Quando modularizziamo un programma, cerchiamo di separare il codice in zone, in modo da massimizzare l’interazione all’interno di una zona e ridurre invece al minimo l’interazione fra zone diverse. Si ha un classico caso di invidia di funzionalità quando una funzione in un modulo passa più tempo a comunicare con funzioni o dati che si trovano all’interno di un altro modulo di quanto non ne passi all’interno del proprio modulo. Non si contano le volte che abbiamo visto una funzione invocare una mezza dozzina di metodi su un altro oggetto per calcolare un valore. Per fortuna, la cura per questo caso è ovvia: la funzione chiaramente vuole stare con i dati, perciò usa Move Function per portarcela. A volte, solo parte di una funzione soffre di invidia, nel qual caso usa Extract Function sul pezzo invidioso e Move Function, poi, per portarla nella casa dei suoi sogni.

Ovviamente non tutti i casi sono così chiari. Spesso una funzione usa funzionalità di vari moduli, perciò in quale dovrebbe stare? L’euristica che usiamo noi consiste nel determinare quale modulo abbia la maggior quantità di dati e mettere la funzione vicino a quei dati. Questo passo spesso è reso più facile se si usa Extract Function per suddividere la funzione in parti che vanno in posti diversi.

Ovviamente, esistono molti pattern raffinati che infrangono questa regola: vengono subito in mente Strategy e Visitor della Banda dei Quattro (Erich Gamma, Richard Helm, Ralph Johnson e John Vlissides, autori di Design Patterns: Elements of Reusable Object-Oriented Software), o Self Delegation di Kent Beck. Usali per combattere l’odore di cambiamento divergente. La regola empirica fondamentale è mettere insieme le cose che mutano insieme. I dati e il comportamento che fa riferimento a quei dati di solito cambiano insieme, ma le eccezioni non mancano. Quando si verifica qualche eccezione, spostiamo il comportamento per mantenere i cambiamenti in un posto solo. Strategy e Visitor consentono di modificare facilmente il comportamento perché isolano la piccola quantità di comportamento per cui è necessario un override, a costo di ulteriore indirezione.

5. Switch ripetuti

Parla con un vero evangelista dell’Object-Oriented e dopo un attimo comincerà a enumerare le malefatte degli enunciati switch. Sosterrà che qualsiasi enunciato switch che incontri ti implora di usare Replace Conditional with Polymorphism. Abbiamo sentito qualcuno sostenere addirittura che tutta la logica condizionale deve essere sostituita con il polimorfismo. buttando la maggior parte degli if nei cassonetti della storia.

Non siamo mai stati incondizionatamente contrari ai condizionali. In realtà la prima edizione di questo libro aveva un odore intitolato enunciati switch, ed era lì perché verso la fine degli anni Novanta il poliformismo purtroppo era poco apprezzato e vedevamo invece quali benefici avrebbe portato la sua adozione.

Oggi c’è più polimorfismo in circolazione, e non è pura cosmesi come era spesso quindici anni fa. Molti linguaggi inoltre supportano forme più raffinate di enunciati switch, che usano come base più che un po’ di codice primitivo. Ora perciò ci concentriamo sugli switch ripetuti, dove la stessa logica di diramazione in base a una condizione (o in un enunciato switch/case o in una cascata di if/else) spunta in luoghi diversi. Il problema di questi switch duplicati è che, ogni volta che si aggiunge una clausola, bisogna trovare tutti gli switch e aggiornarli. Nella lotta contro le forze oscure della ripetizione, il polimorfismo offre un’arma elegante per una base di codice più civilizzata.

6. Cicli

I cicli sono stati una parte centrale della programmazione sin dalla comparsa dei primi linguaggi, ma abbiamo la percezione che oggi la loro rilevanza sia scarsa. Li abbiamo trascurati all’epoca della prima edizione, ma Java, come la maggior parte degli altri linguaggi di quel tempo, non offriva un’alternativa migliore. Oggi, però, le funzioni di prima classe sono ampiamente supportate, perciò possiamo usare Replace Loop with Pipeline per mandare in pensione questi anacronismi. Le operazioni di pipeline, come filtri e mappe, ci aiutano a vedere rapidamente gli elementi inclusi nell’elaborazione e quello che su di essi viene fatto.

7. Generalità speculativa

Brian Foote ha suggerito questo nome per un odore a cui siamo molto sensibili. Lo si avverte quando qualcuno dice Oh, penso che un giorno avremo bisogno della capacità di fare questo tipo di cose e quindi aggiunge ogni genere di ganci e di casi speciali per gestire cose che non sono richieste. Il risultato spesso è più difficile da comprendere e da manutenere. Se tutto questo macchinario venisse usato, ne varrebbe la pena, ma se non è usato non è usato. Sta solo in mezzo ai piedi, perciò meglio liberarsene.

Se hai classi astratte che non fanno un granché, usa Collapse Hierarchy. Le deleghe non necessarie possono essere eliminate con Inline Function e Inline Class. Su funzioni con parametri inutilizzati bisogna agire con Change Function Declaration per eliminarli.

La generalità speculativa si può identificare quando gli unici utenti di una funzione o di una classe sono casi di test. Se ti capita di trovare una bestia simile, elimina il caso di test e applica Remove Dead Code.

8. Campo temporaneo

A volte si vede una classe in cui un campo è impostato solo in certe circostanze. Codice del genere è difficile da capire, perché ci si aspetta che un oggetto abbia bisogno di tutti i suoi campi e cercare di capire perché ci sia un campo che sembra non venga mai utilizzato può far impazzire.

Usa Extract Class per creare una casa per le povere variabili orfane. Usa Move Function per mettere tutto il codice che riguarda i campi nella nuova classe. Potresti riuscire anche a eliminare del codice condizionale utilizzando Introduce Special Case per creare una classe alternativa per quando le variabili non sono valide.

9. Intermediario

Una delle caratteristiche principali degli oggetti è l’incapsulamento, il nascondere al resto del mondo i dettagli interni. L’incapsulamento spesso è accompagnato dalla delega. Chiedi a una manager se è libera per una riunione; lei delega il messaggio alla sua agenda e ti dà una risposta. Tutto bene. Non c’è bisogno di sapere se la manager usa un’agenda di carta, un apparecchio elettronico o una segretaria per tenere traccia dei suoi appuntamenti.

La cosa però può spingersi troppo oltre. Guarda l’interfaccia di una classe e scopri che metà dei metodi delegano a quest’altra classe. Dopo un po’ viene il momento di usare Remove Middle Man e parlare all’oggetto che sa davvero che cosa succede. Se solo alcuni metodi non fanno molto, usa Inline Function per metterli inline nel chiamante. Se c’è altro comportamento, puoi usare Replace Superclass with Delegate o Replace Subclass with Delegate per infilare l’intermediario nell’oggetto reale. Questo permette di estendere il comportamento senza andare a caccia di tutte le deleghe.

10. Commenti

Non preoccupatevi, non vogliamo sostenere che non si debbano scrivere commenti. Nella nostra analogia olfattiva, i commenti non emanano cattivo odore; in effetti hanno un profumo dolce. Il motivo per cui li citiamo qui è che i commenti spesso sono usati come deodoranti. È sorprendente quanto spesso capiti di vedere codice finemente commentato e di notare che i commenti sono lì perché il codice è pessimo.

La nostra prima azione consiste nel rimuovere i cattivi odori rifattorizzando. Quando abbiamo finito, spesso scopriamo che i commenti sono superflui.

Se senti il bisogno di un commento per spiegare che cosa fa un blocco di codice, prova Extract Function. Se il metodo è già estratto ma senti ancora il bisogno di un commento per spiegare che cosa fa, usa Change Function Declaration per cambiarne il nome. Se devi fissare qualche regola sullo stato in cui deve essere il sistema, usa Introduce Assertion.

Quando senti il bisogno di scrivere un commento, prima prova a rifattorizzare il codice in modo che ogni commento risulti superfluo.

Un buon momento in cui usare un commento è quando non sai che cosa fare. Oltre a descrivere che cosa succede, i commenti possono indicare aree in cui non non ti senti in sicurezza. Un commento può anche spiegare perché hai fatto qualcosa. Questo tipo di informazione aiuta chi dovrà apportare modifiche in futuro, in particolare chi ha la memoria corta.

Questo articolo richiama contenuti dal capitolo 3 di L’arte del Refactoring.

L'autore

  • Martin Fowler
    Martin Fowler è un'autorità nel mondo dello sviluppo software. Malgrado il riconoscimento universale che gli viene attribuito, ama non prendersi sul serio autodefinendosi un autore, un consulente, un conferenziere e... un chiacchierone quando parla di software. Chief Scientist presso ThoughtWorks, Martin ha sposato da anni la metodologia Agile partecipando nel 2001 alla creazione del Manifesto per lo Sviluppo Agile di Software. La prima edizione di questo libro è quella che ne ha decretato il successo come autore, con oltre 100.000 copie vendute nella sola edizione inglese.

Iscriviti alla newsletter

Novità, promozioni e approfondimenti per imparare sempre qualcosa di nuovo

Gli argomenti che mi interessano:
Iscrivendomi dichiaro di aver preso visione dell’Informativa fornita ai sensi dell'art. 13 e 14 del Regolamento Europeo EU 679/2016.

Libri che potrebbero interessarti

Tutti i libri

L'arte del Refactoring

Guida alle tecniche per migliorare il design e la leggibilità del codice

46,65

66,89€ -30%

37,91

39,90€ -5%

26,99

di Martin Fowler

Clean Code

Guida per diventare bravi artigiani nello sviluppo agile di software

46,15

64,89€ -29%

37,91

39,90€ -5%

24,99

di Robert C. Martin

Clean Architecture

Guida per diventare abili progettisti di architetture software

46,15

64,89€ -29%

37,91

39,90€ -5%

24,99

di Robert C. Martin


Articoli che potrebbero interessarti

Tutti gli articoli