Con questo articolo iniziamo ad addentrarci nella spiegazione delle differenti tecniche di refactoring che possono essere applicate al codice sorgente; come accennato nel primo articolo, la maggior parte delle operazioni di refactoring sono state studiate per poter lavorare correttamente a fianco della programmazione ad oggetti. Questo non significa che le tecniche non siano applicabili anche a paradigmi di programmazione differenti, ma sicuramente l'operazione risulta un po' più laboriosa ed alcune volte meno efficace.
Questo articolo parlerà di alcune delle tecniche più comuni che hanno a che vedere con i metodi. Ho deciso di iniziare con questo gruppo poiché queste tecniche di refactoring sono quelle più utilizzate: spesso sono infatti utilizzate anche in cooperazione con altre tecniche per ottenere risultati più complessi.
Prima di iniziare ricordo che è fondamentale effettuare i test il più frequentemente possibile: modificate il codice, testate e se tutto funziona potete eliminare il codice eventualmente in eccesso e procedere.
Inline Method
La prima tecnica che mi accingo a descrivere non è sicuramente la più utilizzata, ma rende bene la concezione che sta alla base del refactoring. Come detto nell'articolo precedente, uno dei principali obiettivi del refactoring è generare codice che sia leggibile e di conseguenza gestibile nel tempo anche da mani diverse. La leggibilità del codice spesso è soggettiva, ma una chiarezza nei nomi dei metodi è un passo molto importante per permettere a chiunque di avere una comprensione omogenea delle operazioni svolte.
Il nome di un metodo deve essere autoesplicativo, fungendo da commento di se stesso. Durante le operazioni di refactoring (come quella che descriverò successivamente) accade spesso di aggiungere metodi al proprio codice. Questi metodi hanno un nome che descrive molto bene il contenuto e su questi metodi verranno operate altre tecniche di refactoring. Alcune volte accade che il corpo di un metodo sia esplicativo quanto il suo nome, ed in questo momento interviene la tecnica dell'Inline Method: sostituire alla chiamata del metodo il codice contenuto nel suo corpo.
Come sappiamo uno dei principi fondamentali del refactoring è il cercare di separare il più possibile le operazioni in metodi. Questo consente a sua volta di facilitare altre tecniche di refactoring, migliorare la leggibilità e molto altro. Troppa frammentazione comunque, soprattutto se non necessaria, può causare problemi e quindi in quei casi in cui abbiamo un corpo del metodo autoesplicativo, operiamo l'Inline Method.
Vediamo un esempio di questa tecnica:
class Person { public function drive() { if( $this->olderThan( 17 ) ) { // Guida }else { // Genera un errore } } public function olderThan( $min_age ) { return ( $this->age > $min_age ); } }
Il metodo olderThan
in questo caso può essere tranquillamente inserito nel corpo del chiamate. Nel caso specifico sostituiremo anche la variabile locale con l'argomento passato alla chiamata del metodo:
class Person { public function drive() { if( $this->age > 17 ) { // Guida }else { // Genera un errore } } }
Ci sono alcuni punti da tenere in considerazione quando si applica questa tecnica: in primis è importante assicurarsi che il metodo non sia polimorfico e che quindi non sia sovrascritto da eventuali sottoclassi della classe in cui è definito. In secondo caso se applicare l'Inline Method non risultasse un'operazione immediata (come nel caso in cui si ha a che fare con ricorsione) allora significa che la tecnica non va adottata.
L'Inline Method viene spesso utilizzato anche quando si ha la sensazione che una parte del proprio codice non sia stata ristrutturata bene: in questo caso si applica la tecnica a tutti i metodi interessati e successivamente si procede con il refactoring alla ricerca di una migliore struttura. L'importante, come per tutte le tecniche di refactoring, è non abusare: ci sono situazioni in cui applicare questa operazione può creare problemi al resto del codice e quindi in questi casi l'Inline Method deve essere evitato e deve essere affiancata qualche tecnica che ne limiti le problematiche.
Extract Method
Questa tecnica di refactoring è una delle più utilizzate in assoluto, sia da sola sia in combinazione con altre tecniche per raggiungere obiettivi più complessi. Avere metodi con un corpo breve e dei nomi autoesplicativi aiuta a raggiungere l'obiettivo di leggibilità che si prefigge il refactoring; ogni volta in cui lavoriamo su un metodo troppo lungo, che svolge troppe operazioni differenti o in cui alcune porzioni di codice necessitano di commenti che ne descrivono l'utilità, è opportuno applicare questa tecnica creando un nuovo metodo con nome descrittivo in cui inseriremo la porzione di codice precedentemente commentata.
Applicare l'Extract Method è molto utile anche perché facilita il riutilizzo del codice: con metodi snelli che svolgono compiti ben specifici è molto più semplice essere in grado di utilizzarli in molte situazioni in cui invece avremmo operato con delle ripetizioni di codice oppure con algoritmi alternativi solo leggermente diversi.
Operare l'Extract Method purtroppo non è così semplice. Ci sono situazioni in cui tutto fila liscio, come nel caso in cui fosse necessario estrarre un metodo che lavora in modo completamente indipendente dal contesto in cui si trova, mentre ci sono situazioni in cui le operazioni da eseguire sono più articolate.
Procediamo per esempi cercando di identificare le varie situazioni possibili. Il primo caso è il più semplice: estraiamo il metodo da un blocco di codice che non opera in alcun modo con lo scope locale del corpo originale:
class Page { public function printHead() { echo "<head>"; printMetadata(); if( !is_null( $this->title ) ) { echo "<title>".$this->title."</title>"; } echo "<head>"; } }
Possiamo estrarre il codice che stampa il titolo:
class Page { public function printHead() { echo "<head>"; printMetadata(); printTitle(); echo "<head>"; } private function printTitle() { if( !is_null( $this->title ) ) { echo "<title>".$this->title."</title>"; } } }
Il secondo caso che prendiamo in considerazione è quando utilizziamo delle variabili locali nel nostro codice che non vengono poi utilizzate successivamente.
class Page { private function printScriptsAndStyles() { $scripts = ""; $styles = ""; foreach( $this->externalFiles as $value ) { if( array_pop( explode( ".", $value ) ) == 'js' ) { $scripts .= '<script language="javascript" type="text/javascript" src="'.$value.'"></script>'; }else { $styles .= '<style type="text/css" src="'.$value.'"></script>'; } } $generated_data = $scripts."n".$styles; echo "<!-- automatically generated -->"; echo $generated_data; echo "<!-- end -->"; } }
In questo caso (in cui ci potremmo applicare varie tecniche refactoring) vogliamo estrarre un metodo che stampi del codice anteponendovi un commento che indica che questo codice è stato generato automaticamente. Lo vediamo nella pagina successiva.
Per estrarre un metodo che stampi del codice con un commento che indica che questo codice è stato generato automaticamente è necessario prendere in considerazione l'utilizzo della variabili locali, che diventeranno argomenti del metodo dato che non vengono più utilizzate:
class Page { private function printScriptsAndStyles() { $scripts = ""; $styles = ""; foreach( $this->externalFiles as $value ) { if( array_pop( explode( ".", $value ) ) == 'js' ) { $scripts .= '<script language="javascript" type="text/javascript" src="'.$value.'"></script>'; }else { $styles .= '<style type="text/css" src="'.$value.'"></script>'; } } $generated_data = $scripts."n".$styles; printAutomaticallyGeneratedCode( $generated_data ); } private function printAutomaticallyGeneratedCode( $code ) { echo "<!-- automatically generated -->"; echo $code; echo "<!-- end -->"; } }
L'ultima situazione in cui ci possiamo trovare è quella in cui le variabili locali vengono elaborate dal blocco di codice che dobbiamo estrarre. Nel più semplice dei casi la variabile elaborata è una sola, e quindi opereremo in modo che il metodo generato restituisca il risultato dell'operazione, che poi assegneremo nuovamente alla variabile locale.
class Cart { public function printCart() { $total = 0; foreach( $this->products as $product ) { $total += $product->price; } echo "Total: ".$total; } }
In questo caso la variabile $total
viene elaborata dal codice che estrarremo, e successivamente utilizzata. Come descritto poche righe fa quindi operiamo in modo da restituire la variabile locale modificata al fine di poterla riutilizzare successivamente.
class Cart
{
public function printCart()
{
// $total ora è inutile e può essere rimossa
$total = $this->calculateTotal();
echo "Total: ".$total;
}
private function calculateTotal()
{
$result = 0;
foreach( $this->products as $product )
{
$result += $product->price;
}
return $result;
}
}
Come pratica comunque è buona cosa rinominare la variabile restituita in result
: in questo modo sapremo subito qual è il valore che viene restituito. I dettagli aggiuntivi sul risultato verranno dati dal nome del metodo stesso, quindi la variabile non è necessario che abbia un nome autoesplicativo.
Nel caso in cui invece le variabili su cui opera il blocco di codice siano più di una, è necessario intervenire con tecniche combinate più complesse, che illustrerò quando avremo una buona padronanza dei concetti base del refactoring.