Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
Ing. R. Turco
1
Introduzione
Nell’Object Oriented mi sono sempre sforzato di verificare l’applicabilità di ogni teoria appresa,
così da poter riusare, tutto o parte di essa, in modo ripetibile e tracciabile: teoria, analisi,
disegno, pattern, documentazione, etc.
In [DR2] si è visto che per la fase di costruzione, sia per un Rational Unified Process o per altri
processi produttivi come Agile o Estreme Programming, le iterazioni sono incrementali per le
funzionalità; mentre sono iterative a riguardo del codice appena realizzato.
Il refactoring, quindi, potrebbe essere definito come “la parte iterativa dell’iterazione di
processo produttivo, finalizzata a migliorare il codice, a renderlo stabile, riusabile, manutenibile
e flessibile, fermo restando il comportamento finale del codice stesso”.
Di per sé è già una grande definizione! Tuttavia questa definizione è solo una vista di una
medaglia a due facce!!
Il refactoring è, in realtà, sia di natura preventiva che adeguativa, per cui parlerei
dell’esistenza di due tipi di refactoring:
Il “Design Refactoring”;
L’”Implementation Refactoring”.
Design Refactoring
Il Disegn Refactoring è finalizzato a migliorare il modello del disegno per ottenere stabilità,
riusabilità, manutenibilità e flessibilità del sistema. E’ preventivo, perché, se il sistema
s’implementa secondo le sue direttive, si ottiene un prodotto che ha già in sé delle “best
practices”, grazie al disegno.
Il Design Refactoring, difatti, si basa essenzialmente su alcuni principi, che vedremo in seguito,
e sull’uso dei Pattern [DR5][DR6].
L’obiettivo del Design Refactoring è di tendere a migliorare la qualità del prodotto poiché le
scelte vengono prese preventivamente nell’architettura e nel disegno (Pattern architetturali,
Design Pattern, etc) per cui si tende a ridurre il numero di errori del prodotto.
Io applico il Design Refactoring appena dopo la modellazione UML a colori [DR3], perché con
quest’ultima tendo prima a capire il modello; poi col Design Refactoring, invece, tendo a
migliorare il sistema, introducendo gli adeguati Pattern architetturali ed i vari Design Pattern.
Fermarsi solo alla modellazione UML a colori servirebbe solo ad individuare un modello che
rispecchia la realtà, conferendogli stabilità, ma non si è ancora fatto nulla per problematiche di
riusabilità e flessibilità, che sono quelle per cui l’Object Oriented è ritenuto estremamente
valido.
2
Booch ha, inoltre, affermato che un modo per vedere “la qualità di un sistema ben strutturato
è quello di osservare come i suoi disegnatori e implementatori abbiano applicato i concetti dei
Pattern”.
Implementation Refactoring
La risposta è : entrambi, nel loro giusto peso, secondo l’accezione di cui sopra.
Occorre scrivere il software solo quando si ha una chiara visione del modello, e, comunque,
solo dopo averne fatto del sano Design Refactoring; per cui un Implementation Refactoring
dovrebbe essere di entità contenuta, al punto di poterlo sostenere facilmente nell’ambito di
un’iterazione!
Inoltre se gli sviluppatori hanno uno strumento, con cui sono sia in grado di disegnare che
d’implementare (Vedi Together per Java o Rational Rose), l’attività è possibile in maniera
integrata e secondo un continuo che porta dall’OOA all’ OOD e all’OOP.
Modellazione e Design
Refactoring
Prodotto
Fig. 1
Anche se si dovessero ricevere dei requisiti imprevisti che possono impattare sull’architettura,
il consiglio è prima di modellare: è indispensabile! [DR3].
L’Implementation Refactoring assume maggiore peso solo per software non noto, ma anche
qui è possibile con degli strumenti fare il reverse engineering ed ottenerne il modello; poi
lavorare su esso, fare il forward engineering e l’implementazione di dettaglio. E’ un processo
continuo (fig. 1).
3
Refactoring: le “best practices”
In [DR3] si parla di Guidelines, Pattern e anti-Pattern (Pattern sconsigliati).
Anche per il refactoring non sarebbe difficile snocciolare una serie di “Guidilines”, di ciò che
non bisogna fare e cosa, invece, occorre fare.
Tuttavia in [DR3] si dimostra che le “Guidelines” sono spesso incomplete, prive di contesto, di
formulazione del problema e non forniscono alcuna soluzione ed esempi. Al contrario i Pattern
[DR5][DR6] e gli anti-Pattern.
Per cui esiste un modo completo ed originale per parlare di refactoring in modo rigoroso, come
in [DR4]. Si può partire da alcuni principi di base dimostrabili fino ad arrivare alle “best
practices” del Design Refactoring o dell’Implementation Refactoring.
Occorre innanzitutto capire quali sono i sintomi che possono segnalare che un sistema software
si sta guastando.
Rigidità
E’ la tendenza da parte del software ad essere difficilmente cambiabile. Una modifica anche
semplice comporta un innesco a catena di cambiamenti.
Fragilità
E’ la tendenza del software a spezzarsi in molti pezzi ad ogni modifica, fino a diventare
immanutenibile.
Immobilità
E’ la tendenza ad una difficile riusabilità del software per altri progetti o per altri parti dello
stesso software.
Se la viscosità del disegno è alta, ovvero per preservare il disegno occorre fare una modifica
abbastanza difficile, è altamente probabile che gli sviluppatori non seguiranno tale strada ed il
disegno non sarà preservato.
Viscosità dell’ambiente
Se l’ambiente di compilazione o quello di versioning è lento e inefficiente gli sviluppatori
prenderanno decisioni di sviluppo che riducono l’impatto dell’ambiente; questo però spesso
anche a discapito della necessità di preservare il disegno.
4
L’Importante è gestire correttamente i rischi correlati alla variazione di una parte dei requisiti e
mantenere un minimo di flessibilità del software in modo che possa essere aperto ai
cambiamenti facilmente.
I principi ed i rimedi
Nel seguito esamineremo dei principi che costituiscono un rimedio al degrado dovuto alla
dipendenza tra i moduli software.
Il principio OCP è tipico dell’Object Oriented: un nostro modulo deve poter essere esteso per
ereditarietà senza che sia necessario modificarlo internamente. La chiave dell’OCP è
l’astrazione object oriented.
Un esempio C++.
Supponiamo di avere del software di Logon che, purtroppo, richiede di essere modificato ogni
volta a causa dell’aggiunta di un nuovo tipo di modem.
struct Modem {
typedef enum Type { hayes, courrier, ernie } type;
};
struct Hayes {
Modem::Type type;
// etc
};
struct Courrier {
Modem::Type type;
// etc
};
struct Ernie {
Modem::Type type;
// etc
};
Qui l’OCP aiuta a capire che sin dall’inizio occorreva progettare il tutto secondo il diagramma in
fig. 1.
5
Fig. 2
class Modem{
public:
virtual void Dial( const string& pno) = 0;
virtual void Send( char ) = 0;
virtual char Recv() = 0;
virtual void Hungup() = 0;
}
Personalmente preferisco la prima soluzione, che da un maggior controllo sui tipi in gioco.
Il principio DBC insiste sul fatto che pre-condizioni e post-condizioni devono essere rispettate
dal disegno per contratto. Il contratto è fra chi ha implementato le classi e chi le usa!
6
Vediamo perché.
Es:
void User(Base&);
Derived d ;
User(d) ;
In matematica ricordiamo tutti che un cerchio è un caso particolare di ellisse. Per cui
concettualmente vale il diagramma di fig. 3
Fig. 3
Ma è corretto?
Fig. 4
Ma soprattutto una circonferenza è caratterizzata solo dal centro del cerchio e dalla maggiore
ascissa (il raggio).
Anche se i due fuochi si fanno coincidere come centro del cerchio, forse qualche problema
rimane.
7
Una prima implementazione “difettosa” di metodo sarebbe la seguente:
Apparentemente uso un solo punto, ma se applico il DBC scopriamo che c’è un errore tale che
il principio LCP non è rispettato:
void f( Ellisse& e ){
Point a (-1, 0);
Point b (1,0) ;
e.setfocus(a,b) ;
e.setMajorAxis(3);
assert(e.getFocusA() == a);
assert(e.getFocusB() == b);
assert(e.getMajorAxis() == 3);
}
Per far sì che l’LCP sia valido, ovvero che sia possibile la sostituibilità, occorre che il contratto
delle classe base sia onorato anche dalle classi derivate.
Le violazioni LCP sono una delle cause di errori che vengono rivelate troppo tardi, quando si è
costruito molto software ed il costo per porvi riparo potrebbe essere elevato.
L’unico modo per evitare un tale errore è di segnalare in fase di disegno le pre-condizioni e le
post-condizioni e poi farle implementare con delle assert, nei metodi a cui vengono passati
parametri.
Una “pezza” implementativa per arginare il danno di una violazione del LCP è di inserire, poi,
degli if-else nel metodo per testare che l’oggetto di input sia effettivamente la classe attesa,
altrimenti viene alzata un’eccezione.
Per cui una violazione LCP conduce, come pezza, ad una latente violazione OCP.
L’OCP è un obiettivo architetturale dell’Object Oriented, il DIP è il concetto base che consente
l’OCP.
Ci sono alcune considerazioni da fare. Usando tale tecnica è possibile anche creare degli “hot-
spot” o degli “hinge-points” che permettono di agganciare altro software (rispetto dell’OCP);
quindi si possono creare dei framework.
Se usiamo una interfaccia e, poi, deriviamo le altre classi, non possiamo istanziare
un’interfaccia. Sembrerebbe che questo ci metta KO definitivamente!
Fortunatamente i Design Pattern ci forniscono molti meccanismi tra cui la ABSTRACT FACTORY
o il FACTORY METHOD, etc.
Per cui astrarre è possibile ed utile, ma poi dobbiamo usare i Design Pattern [DR5][DR6].
8
Il DIP ci suggerisce, cioè, che un sano Design Refactoring che porti a vantaggi di stabilità,
flessibilità e riusabilità passa inevitabilmente attraverso l’uso e la scelta di Design Pattern.
In fig. 5 si vedono due client che dipendono dai metodi di un’unica classe Service o interfaccia
che li serve tutti.
Fig. 5
Se un cambiamento è fatto ad un metodo del Client A, ci può essere un’influenza sul Client B.
Fig. 6
9
Principi di “Package Architecture”
Finora abbiamo visto principi generali dell’object oriented, che insistono sull’astrazione.
Questo vuol dire che un cliente usa (o riusa) il componente all’interno di una release di
prodotto; per cui la granularità del riuso è pari a quella della release del prodotto o del gruppo
classi in un package.
I grandi progetti hanno una “rete di package” e la loro manutenzione, test, gestione non è
banale.
Più alto è il numero di classi e package da cambiare più è alto il lavoro da fare per fare la
rebuild ed i test.
Un’idea potrebbe essere di raggruppare insieme in un package le classi che pensiamo cambino
insieme. Questo porta a ridurre l’impatto del cambiamento nel sistema.
Questa tecnica però va ponderata attentamente, perché potrebbe essere a discapito della
logica di mettere insieme le classi correlate. Va bene, invece, quando la cosa riguarda
comunque classi correlate logicamente.
Potrebbe essere una tecnica adottata momentaneamente, cioè nella fase di movimentazione
del prodotto; mentre nella fase matura del prodotto si riportano le classi in package diversi
usando un Implementation Refactoring.
Ci è capitato nella vita di fare sicuramente un upgrade di sistema operativo e poi, a volte, un
applicativo di nostro interesse non funzionava più AS IS; ma occorreva fare un “porting”.
Ebbene se fossero raccolte insieme delle classi che non sono riusate insieme potrebbe
succedere che facendo un cambiamento a parte di esse devo fare uno sforzo a testarle tutte e
10
riverificarle tutte anche per la parte che non è d’interesse per il funzionamento di un certo
applicativo.
I tre principi REP, CCP, CRP sono mutuamente esclusivi: non possono essere soddisfatti
contemporaneamente, perché diretti a figure professionali diverse. REP e CRP interessano a chi
fa riuso, CCP interessa a chi fa manutenzione.
Il CCP tende a fare i package quanto più grandi è possibile, almeno fino a comprendere tutte le
classi che variano insieme. Il CRP tende a fare package piccoli.
Fig. 7
Supponiamo che per inviare un errore a video dal package CommErr decido di chiamare un
oggetto della GUI. In questo caso le dipendenze dei package si trasformano come quelle in fig.
8.
11
Fig. 8
In questo caso si è introdotto un ciclo. Ma soprattutto un extra-lavoro! Infatti chi sta lavorando
con Protocol per ricostruire la sua test suite ha bisogno di:
- CommErr
- GUI
- ModemControl
- Analysis
Mentre in fig. 7 chi lavorava su Protocol avrebbe avuto bisogno solo di CommErr!!!
Le soluzioni sono:
- introdurre un altro package e cambiare il verso della dipendenza, per spezzare il ciclo
(fig. 9);
- usare il DIP e l’ISP
12
Fig. 9
Il concetto di stabilità non ha niente a che vedere con la frequenza dei cambiamenti.
Un package X da cui dipendono altri package è detto stabile, perché un suo cambiamento
influenza i package sottostanti. O dicendola in altro modo X ha dei buoni motivi per non
cambiare. Il package X è detto anche indipendente ma una sua eventuale variazione influenza
gli altri package.
Metrica: Stabilità
Indichiamo con:
Ca incomming dependencies
Ce outgoing dependencies
I Instability
I = Ce/(Ca+Ce)
13
Riformulando SDP conviene “Dipendere sopra package dove I è più basso del proprio”
E’ una conclusione a cui siamo arrivati dimostrando una violazione del SDP e che discende
dall’OCP.
Metrica: Astrattezza
Indichiamo con:
A = Na/Nc
Il grafico I-A
Fig. 10
Nel grafico I-A il punto in alto a destra indica la massima astrattezza e nessuna dipendenza
entrante (A=1, I=1) ed è una zona inutile.
14
Il punto in basso a sinistra è di nessuna astrattezza (package concreti) e vi sono dipendenze
entranti (A=0, I=0). E’ il caso peggiore per un package.
E’ importante, quindi, massimizzare la distanza tra le due zone (A=1, I=1) e (A=0, I=0) con la
linea chiamata Main sequence
Metrica: Distanza
Se conosciamo A ed I del nostro package possiamo sapere quanto il nostro package è lontano
dalla Main Sequence:
Metriche di un modello
E’ possibile utilizzare, quindi, un add-in per Rational Rose, facilmente reperibile su Internet,
che permette di valutare le metriche definite da Robert Martin. E, quindi valutare il modello
realizzato, sia dopo la modellazione a colori, sia dopo il Design Refactoring.
Pattern
I Pattern, in generale, servono, quindi, nella fase di Design Refactoring e sono dei “building
block” precostituiti e già ampiamente collaudati in molti progetti nel mondo.
Riusare un pattern è, quindi, garanzia di una corretta soluzione al nostro contesto e problema.
15
Fig. 11
Il non farlo (under engeneering) è ovviamente sconsigliato. Il farlo troppo (over engineering) è
un costo aggiuntivo inutile.
L’Over Engineering si verifica quando il sistema lo si vuole rendere troppo flessibile o più
sofisticato del necessario.
Introdurre codice in più del necessario o più sofisticato o complesso porta a manutenzione e
costi maggiori.
16
Conclusioni
La teoria e l’esperienza sul campo mi hanno condotto ad assemblare un mio metodo di lavoro
per la modellazione dei sistemi:
cerco di ridurre i rischi dei requisiti [DR2];
eseguo la modellazione del dominio con UML a colori, per comprendere il dominio e tutti
gli oggetti in gioco;
eseguo un Design Refactoring, applicando i concetti dei Pattern;
consiglio gli implementatori, quando necessario, su come comportarsi con i package, la
scelta degli algoritmi necessari, l’introduzione del Design by Contract nel codice e
l’Implementation Refactoring in generale.
L’esperienza mi ha dimostrato che, in questo modo, avviene una crescita culturale dell’intero
team, un’ aumento della qualità prodotta, un maggior riuso, un minor rifacimento, una minore
quantità di lavoro implementativi, un minor numero di errori, un team meno stressato.
La tecnica è valida per ogni processo produttivo: Rational Unified Process, Agile, Estreme
Programmino (XP).
17
INDICE
Refactoring: la teoria in pratica ............................................................................................................ 1
Introduzione ......................................................................................................................................... 2
Il refactoring: la giusta definizione .................................................................................................. 2
Design Refactoring ...................................................................................................................... 2
Implementation Refactoring ........................................................................................................ 3
Design Refactoring o Implementation Refactoring? ................................................................... 3
Refactoring: le “best practices”........................................................................................................ 4
I sintomi della degradazione del software........................................................................................ 4
Rigidità ......................................................................................................................................... 4
Fragilità ........................................................................................................................................ 4
Immobilità .................................................................................................................................... 4
Viscosità del disegno ................................................................................................................... 4
Viscosità dell’ambiente ................................................................................................................ 4
Cambiamento dei requisiti ........................................................................................................... 4
Le cause della degradazione ............................................................................................................ 5
I principi ed i rimedi......................................................................................................................... 5
Open Closed Principle (OCP) ...................................................................................................... 5
Liskov Substitution Principle (LSP) ............................................................................................ 6
Dependency Inversion Principle (DIP) ........................................................................................ 8
Interface Segregation Principle (ISP) .......................................................................................... 9
Principi di “Package Architecture” ................................................................................................ 10
Release Reuse Equivalency Principle (REP) ............................................................................. 10
Common Closure Principle (CCP) ............................................................................................. 10
Common Reuse Principle (CRP) ............................................................................................... 10
Principi di “Package Coupling” ..................................................................................................... 11
Acyclic Dependencies Principle (ADP) ..................................................................................... 11
Stable Dependencies Principle (SDP) ........................................................................................ 13
Metrica: Stabilità ................................................................................................................... 13
Stable Abstraction Principle (SAP)............................................................................................ 14
Metrica: Astrattezza ............................................................................................................... 14
Il grafico I-A .............................................................................................................................. 14
Metrica: Distanza ................................................................................................................... 15
Metriche di un modello .......................................................................................................... 15
Pattern ............................................................................................................................................ 15
Under Engineering e Over Engineering ......................................................................................... 16
Conclusioni ........................................................................................................................................ 17
Riferimenti ......................................................................................................................................... 19
18
Riferimenti
[DR1] Martin Fowler – UML Distilled – Prima edizione italiana
[DR2] Rosario Turco – Usabilità e ripetibilità dei processi produttivi software
[DR3] Rosario Turco – Modellare con l’UML ed i colori
[DR4] Robert C. Martin – Design Principles and Design Patterns
[DR5] Gamma, Helm, Johnson,Vlissides – Design Patterns – Elementi per il riuso di software a
oggetti – Prima edizione italiana
[DR6] Rosario Turco - Pattern e la “GRASP Oriented Analysis”
19