Esplora E-book
Categorie
Esplora Audiolibri
Categorie
Esplora Riviste
Categorie
Esplora Documenti
Categorie
Vantaggi
Quali possono essere i principali benefici derivanti dall’utilizzo del
refactoring?
Come sostenuto anche dai principali esperti in materia come, ad esempio,
Sandro Pedrazzini e Martin Fowler, è possibile distinguere i seguenti
vantaggi:
Code Smell
Il termine “code smell” è stato coniato da Kent Beck e Martin Fowler, autori
anche di “Refactoring: Improving the Design of Existing Code”, con l’obiettivo
di indicare una serie di caratteristiche che il codice sorgente può avere e che
sono riconosciute unanimemente come difetto di programmazione.
Duplicated code;
Long method;
Large class;
Switch;
Lazy class.
DUPLICATED CODE
Un possibile esempio di code smell è il codice ridondante. Succede spesso
che all’interno di una classe siano presenti porzioni di codice identiche o
semplicemente ridondanti. Potrebbe accadere, infatti, che due metodi della
medesima classe contengano analoghe istruzioni. In tal caso, occorre
applicare il refactoring Extract Method, ossia occorre creare un nuovo metodo
contenente tali istruzioni ridondanti e, al contempo, modificare i metodi iniziali
poiché, al termine del refactoring, questi ultimi dovranno contenere solo una
chiamata al nuovo metodo appena creato.
LONG METHOD
Nella moderna programmazione ad oggetti si tende, spesso, ad utilizzare
metodi abbastanza corti per facilitare la comprensione e l’utilizzo del software
che si sta progettando. Come sostiene anche Martin Fowler, infatti,
“ogniqualvolta si ha la necessità di commentare qualcosa, invece occorre
scrivere un metodo”. Tale metodo, che conterrà il codice che si voleva
commentare, è fondamentale poiché consente al programmatore di ridurre la
distanza semantica tra ciò che il metodo fa e come lo fa. Per fare ciò, nella
maggior parte dei casi, si applica il refactoring Extract Method.
LARGE CLASS
Un altro esempio di code smell sono le large class, ossia quelle classi che
fanno troppo, anche quello che non dovrebbero fare. Una large class può,
dunque, essere vista come un insieme di troppe variabili di istanza che, di
conseguenza, possono produrre una notevole quantità di codice ridondante.
Analogamente ai metodi, se all’interno del codice sono presenti una o più
large class, allora occorre verificare se hanno alcuni elementi in comune e, in
caso affermativo, occorre estrarre tali linee di codice in modo da ridurre le
classi in questione.
SWITCH
Quando si sviluppa codice Object Oriented occorre cercare di evitare
l’inserimento, all’interno del codice, di istruzioni come, ad esempio, switch. Se
ciò non avviene, allora tali istruzioni potrebbero ridurre notevolmente la
qualità del software che stiamo producendo e si avrebbe un caso di “code
smell”.
LAZY CLASS
La creazione di una nuova classe, all’interno di un progetto software,
comporta naturalmente ulteriori costi al programmatore. È necessario, infatti,
mantenerla e carpirne il comportamento in ogni momento dello sviluppo. Se
una classe non è indispensabile per la creazione di un software, è possibile,
dunque, eliminarla. Questo potrebbe succedere, ad esempio, quando una
classe aggiunta inizialmente poi non è stata realizzata concretamente, ma
solo pianificata.
“La prima volta che si sviluppa qualcosa, si fa e basta. La seconda volta che
si sviluppa qualcosa di simile al codice già creato precedentemente, ci
potrebbe essere il rischio di incorrere in eventuali duplicazioni, ma si lascia
correre e si va avanti comunque. La terza volta che si crea qualcosa di simile,
si esegue il refactoring”.
Tipi di refactoring
Il refactoring, dunque, è un metodo che viene utilizzato per fattorizzare il
codice, riorganizzarlo e revisionarlo al fine di soddisfare al meglio le esigenze
del cliente. Naturalmente, esistono molti tipi di refactoring, ma,
generalizzando, possiamo distinguere le seguenti 3 macro-categorie:
o Refactoring “Extract”;
o Refactoring “Push/Pull”;
o Refactoring complessi.
REFACTORING “EXTRACT”
EXTRACT SUBCLASS
EXTRACT CLASS
Data una classe esistente, il refactoring Extract Class provvede a creare una
nuova classe aggregata alla classe esistente. Ecco un possibile esempio:
EXTRACT HIERARCHY
Ecco un esempio:
EXTRACT METHOD
stampa_pubblicita();
System.out.println(“Nome : ”+nome);
System.out.println(“Importo : ”+importo);
che diventa:
void stampa(String nome, double importo){
stampa_pubblicita();
stampa_dettagli(nome, importo);
System.out.println(“Nome : ”+nome);
System.out.println(“Importo : ”+importo);
EXTRACT SUPERCLASS
Dopo aver analizzato i refactoring di tipo Extract, vediamo ora alcuni tipi di
refactoring che utilizzano le operazioni di Push e Pull.
...
che diventa:
public class Manager extends Employee{
...
PULL UP FIELD
Pull Up Field, invece, si occupa di spostare nella superclasse un campo in
comune tra tutte le sottoclassi. Un possibile esempio può essere il seguente:
PULL UP METHOD
REFACTORING COMPLESSI
Esempio di refactoring
Per approfondire ulteriormente il refactoring Separate Domain from
Presentation andiamo ad analizzare un esempio di codice software in cui, da
una struttura unica di partenza, occorre derivare una struttura modulare,
ossai una struttura in cui il modello e la presentazione siano separati.
Per fare ciò occorre applicare il modello MVC ed il pattern Observer.
PATTERN OBSERVER
Il pattern Observer è un design pattern comportamentale utilizzato per tenere
sotto controllo lo stato di diversi oggetti. Tale pattern si basa su uno o più
oggetti osservatori (o Observer) ed un oggetto Subject. Esso, dunque,
definisce una dipendenza uno-molti tra oggetti tali che, quando un oggetto
cambia il suo stato (Subject), tutti gli altri devono esserne informati e
aggiornarsi automaticamente. Da notare è che il Subject non conosce con
quale tipo di Observer sta comunicando, sa solo che è un Observer.
Il pattern Observer ha senso quando la dipendenza non è nota, non è fissata
a priori, ossia quando le dipendenze si aggiornano dinamicamente. Ad
esempio, quando viene modificata la lista degli Observer, quando si creano
nuovi Subject, …
ESEMPIO
Supponiamo che inizialmente il programma abbia un’interfaccia grafica
contenente tre campi di testo e che i contenuti dei campi di testo sia in
relazione tra di loro. Il valore del terzo campo, infatti, corrisponde alla somma
del primo con il secondo.
Il nostro obiettivo è separare la logica del programma, ossia la relazione che
intercorre tra i vari campi di testo, e la presentazione, ossia i campi di testo.
import javax.swing.JFrame;
import javax.swing.JTextField;
Esempio(){
...
}
/**
* metodo utilizzato quando si modifica il contenuto del primo campo di testo
* e, di conseguenza, si vuole aggiornare il valore della somma
*/
void start_check(){
calcola_somma();
}
/**
* metodo utilizzato quando si modifica il contenuto del secondo campo di testo
* e, di conseguenza, si vuole aggiornare il valore della somma
*/
void end_check(){
calcola_somma();
}
/**
* metodo utilizzato quando si modifica il contenuto del terzo campo di testo
* e, di conseguenza, si vuole calcolare il valore del secondo addendo
*/
void check_somma(){
calcola_addendo2();
}
/**
* Dati i due addendi, questo metodo che calcola effettivamente la somma
*/
void calcola_somma(){
int add1 = Integer.parseInt(addendo1.getText());
int add2 = Integer.parseInt(addendo2.getText());
somma.setText(String.valueOf(add1+add2));
}
/**
* Dato il primo addendo e il valore della somma, il metodo calcola_addendo2()
* provvede a calcolare il valore del secondo addendo
*/
void calcola_addendo2(){
int add1 = Integer.parseInt(addendo1.getText());
int som = Integer.parseInt(somma.getText());
addendo2.setText(String.valueOf(som-add1));
}
/**
* Tramite questa classe l'utente può modificare il valore nel campo
* di testo del secondo addendo e poi premere il tasto enter della tastiera
*
*/
class EndAction implements ActionListener{
public void actionPerformed(ActionEvent event){
end_check();
}
}
/**
* Tramite questa classe l'utente può modificare il valore nel campo
* di testo del primo addendo e poi premere il tasto enter della tastiera
*
*/
class StartAction implements ActionListener{
public void actionPerformed(ActionEvent event){
start_check();
}
}
/**
* Tramite questa classe l'utente può modificare il valore nel campo
* di testo della somma e poi premere il tasto enter della tastiera
*
*/
class SumAction implements ActionListener{
public void actionPerformed(ActionEvent event){
check_somma();
}
}
import java.awt.*;
import java.util.*;
Modello(){
addendo1="0";
addendo2="0";
somma="0";
}
/**
* ritorna il valore del primo addendo
*/
String getaddendo1(){
return addendo1;
}
/**
* setta il valore del primo addendo
*/
void setaddendo1(String arg){
addendo1 = arg;
notifyObservers();
}
/**
* ritorna il valore del secondo addendo
*/
String getaddendo2(){
return addendo2;
}
/**
* setta il valore del secondo addendo
*/
void setaddendo2(String arg){
addendo2 = arg;
notifyObservers();
}
/**
* ritorna il valore della somma
*/
String getsomma(){
return somma;
}
/**
* setta il valore della somma
*/
void setsomma(String arg){
somma = arg;
notifyObservers();
}
/**
* si aggiunge un elemento alla lista degli osservatori
*/
public void addObserver(Observer o){
Observers.add(o);
}
/**
* metodo che provvede a notificare a tutti gli osservatori un aggiornamento
* dello stato dell'oggetto osservato
*/
protected void notifyObservers(){
Iterator i = Observers.iterator();
while(i.hasNext()){
Observer o = (Observer)i.next();
o.update(this);
}
}
/**
* Dato il primo addendo e il valore della somma, il metodo calcola_addendo2()
* provvede a calcolare il valore del secondo addendo
*/
void calcola_addendo2(){
int add1 = Integer.parseInt(getaddendo1());
int som = Integer.parseInt(getsomma());
setaddendo2(String.valueOf(som-add1));
}
/**
* Dati i due addendi, questo metodo che calcola effettivamente la somma
*/
void calcola_somma(){
int add1 = Integer.parseInt(getaddendo1());
int add2 = Integer.parseInt(getaddendo2());
setsomma(String.valueOf(add1+add2));
}
import javax.swing.JFrame;
import javax.swing.JTextField;
View(Modello m){
model = m;
model.addObserver(this);
update(model);
}
/**
* restituisce il valore del secondo addendo
*/
String getaddendo2(){
return model.getaddendo2();
}
/**
* metodo che provvede a settare il valore del secondo addendo
*/
void setaddendo2(String arg){
model.setaddendo2(arg);
}
/**
* restituisce il valore del primo addendo
*/
String getaddendo1(){
return model.getaddendo1();
}
/**
* metodo che provvede a settare il valore del primo addendo
*/
void setaddendo1(String arg){
model.setaddendo1(arg);
}
/**
* restituisce il valore della somma
*/
String getsomma(){
return model.getsomma();
}
/**
* metodo che provvede a settare il valore della somma
*/
void setsomma(String arg){
model.setsomma(arg);
}
/**
* metodo utilizzato quando si modifica il contenuto del primo campo di testo
* e, di conseguenza, si vuole aggiornare il valore della somma
*/
void start_check(){
setaddendo1(addendo1.getText());
calcola_somma();
}
/**
* metodo utilizzato quando si modifica il contenuto del secondo campo di testo
* e, di conseguenza, si vuole aggiornare il valore della somma
*/
void end_check(){
setaddendo2(addendo2.getText());
calcola_somma();
}
/**
* metodo utilizzato quando si modifica il contenuto del terzo campo di testo
* e, di conseguenza, si vuole calcolare il valore del secondo addendo
*/
void check_somma(){
setsomma(somma.getText());
calcola_addendo2();
}
/**
* Dato il primo addendo e il valore della somma, il metodo calcola_addendo2()
* provvede a calcolare il valore del secondo addendo
*/
void calcola_addendo2(){
model.calcola_addendo2();
}
/**
* Dati i due addendi, questo metodo che calcola effettivamente la somma
*/
void calcola_somma(){
model.calcola_somma();
}