MokaByte 90- 9mbre 2004 
Qualità e metriche nell'analisi e refactoring di applicazioni J2EE
di
Doriano Gallozzi

Reingegnerizzare una applicazione è molto gravoso, soprattutto in mancanza di adeguata documentazione. Resta solo il codice come fonte di informazioni, e spesso si tratta di centinaia di file. Scopriamo l'importanza di Packaging, Layering, Metriche, Refactoring. Non sono questioni accademiche, sono metodi e strumenti che possono semplificarci molto la vita

Introduzione
Chi ha coniato il termine "spaghetti code" è riuscito a esprimere, in modo molto efficace, la sensazione che si prova quando si ha a che fare con problematiche di reingegnerizzazione o anche di semplice manutenzione di una applicazione: centinaia di file con il codice letteralmente "arrotolato" su sè stesso, proprio come accade in una matassa di spaghetti. Lavorare con il codice scritto da altri può costituire un impegno estremamente gravoso, anche in virtù di fattori quali la carenza di documentazione aggiornata (che a volte manca del tutto), così come l'assenza nel progetto - allo stato odierno dell'arte - di quelle persone che a suo tempo sono state i principali protagonisti dello sviluppo. L'unica fonte di informazioni disponibile sembra essere il codice stesso, ma da dove incominciare? L'obiettivo del presente articolo è proprio questo, e cioè porre l'accento sull'importanza di principi quali quelli del Packaging e del Layering, introdurre il concetto di Metriche e presentare, in un esempio concreto, cosa significa applicare tutto questo ad una applicazione reale arrivando ad effettuarne il cosiddetto Refactoring.

 

Modularizzare il codice Java: i Package
Impiegare in modo corretto e appropriato l'astrazione è un concetto centrale nell'ambito dell' Object-Orientation. Tuttavia, troppe volte si trascura che ugualmente importante è la decomposizione del codice in moduli: una struttura modulare si rivela essenziale quando si affronta lo sviluppo e la manutenzione di applicazioni business-critical.
Naturalmente non si tratta di un concetto nuovo, già negli anni '70 principi quali flessibilità, comprensibilità e riduzione del tempo necessario allo sviluppo sono stati messi in evidenza (rif.[1]).
In ambito strettamente Java, modularizzare il codice significa innanzitutto basarsi sull'individuazione e l'uso dei cosiddetti Package, come descritto in rif.[2]: in particolare, viene evidenziato che ogni classe Java è parte di un Package, e che i diversi Package sono gerarchicamente strutturati ad albero.
Abbiamo appena parlato di Package senza darne una definizione: perchè? Perchè in Java i Package vengono definiti in modo implicito, non in quel modo rigoroso in cui sono definite classi e metodi. In Java una classe (o più precisamente quella che si definisce una compilation unit) dichiara semplicemente di trovarsi in un certo Package, e questo fatto è sufficiente ad indicare che quel determinato Package esiste. Tutto qui.
In sostanza un Package altro non è se non un raggruppamento (un "impacchettamento", appunto) di classi con i rispettivi metodi.
Anche i concetti di dipendenza e interfaccia, propri dei Package Java, sono definiti implicitamente, e cioè:

  • Dati i Package A e B, le dipendenze che esistono tra di essi sono costituite dall'insieme delle dipendenze che vanno dai tipi che appartengono ad A verso i tipi che appartengono a B. Pertanto, le dipendenze tra Package sono implicitamente dichiarate nelle definizioni di quei tipi - in A - che referenziano altri tipi - in B.
  • L'interfaccia di un Package è definita dall' insieme delle interfacce di tutti i tipi public (classi o interfacce) presenti nel Package. I tipi sono parte di un Package se le compilation unit cui appartengono contengono un "package statement" che si riferisce al Package medesimo.

Naturalmente, come è logico aspettarsi, una dipendenza verso un Package A implica anche una dipendenza verso il Package "padre di A". Ad esempio, una classe che dipende da java.util, dipende automaticamente anche da java.
Per illustrare più chiaramente la cosa, si veda in fig.1 lo schema UML delle dipendenze individuabili all'interno della libreria standard delle classi Java. E' interessante notare come, ad esempio, java.lang dipenda da java.util,java.io, java.nio e java.security.


Figura 1
- Dipendenze all'interno della Java Class Library

Applicare in pratica e in modo efficace il cosiddetto Package Design però è tutt'altro che banale, ed è per questo che esiste a tale scopo un insieme di linee guida.
Robert C. Martin, in un testo fondamentale (rif.[3]), enuncia una serie di principi, quelli dell'Agile Development, secondo i quali "l'obiettivo di un progetto basato sull'uso dei Package porta ad una struttura, anche essa basata su Package, facile da comprendere, collaudare, manutenere ed estendere".

Tra questi principi possiamo ricordare ad esempio:

  • Principio di Dipendenza Aciclica (ADP), secondo cui le dipendenze nel grafo dei Package non devono costituire cicli
  • Principio di Dipendenza Stabile (SDP), secondo cui i Package Stabili sono quelli più difficili da modificare
  • Principio delle Astrazioni Stabili (SAP), riferito al numero di classi astratte e interfacce presenti

e diversi altri (si veda rif.[3] per una trattazione completa).

 

Migliorare la comprensione della struttura: il Layering
Quando si affronta il disegno della architettura di un Sistema e si inizia a definirne la struttura e a suddividerla in Package, un approccio collaudato e molto efficace consiste nel progettare in modo cosiddetto "layered" (cioè a "strati").
I vantaggi di tale approccio consistono nell' ottenere un disegno estremamente più facile da comprendere e quindi da manutenere.
Senza addentrarci ulteriormente nelle metodologie algoritmiche di Layering esistenti in letteratura (si consulti rif.[4] per maggiori informazioni) proponiamo, a titolo di esempio, la fig.2, che rappresenta il risultato della applicazione di un algoritmo di layering alla libreria standard java


Figura 2
- Layering applicato a java

Sempre secondo rif.[3], se nell'ambito di una architettura complessa si ha cura di rendere ciascun Package conforme al principio ADP, si può essere sicuri di aver ottenuto un disegno "layered".

 

Qualità nello sviluppo: le Metriche
Progettare o analizzare secondo i principi del Package Design e tenendo presenti i dettami del Layering. Siamo arrivati fin qui.
Ma come facciamo a sapere fino a che punto il nostro disegno progettuale - il modello grafico di una applicazione nuova ovvero quello che ci siamo costruiti a partire da una applicazione preesistente per analizzarla - è coerente con Packaging e Layering?
E "quanto" lo è, ossia in che misura percentuale?
E' proprio a questo punto che interviene la necessità di poter disporre delle cosiddette Metriche. Esse sono sostanzialmente un insieme di indicatori che permettono di verificare quanto la struttura del sistema in esame è percentualmente aderente a determinate regole, ad esempio quelle del Packaging e Layering.
Esistono diversi tipi di Metriche (Metriche di Base, Metriche di Stabilità, Metriche di Qualità), ciascuna dedicata ad aspetti diversi della struttura di un Sistema.
Le Metriche di Base, ad esempio, ci permettono di evidenziare la quantità con cui determinati elementi (subpackages, classi astratte, interfacce) compaiono nel nostro disegno progettuale.
Nella pratica tuttavia, le Metriche di Qualità si rivelano di gran lunga più interessanti, proprio in quanto basate sui Principi del Packaging descritti più sopra.
E' proprio a queste ultime che dedicheremo ora un pò più di attenzione.

 

Un esempio concreto: JMeter
Prendiamo in considerazione, tra le Metriche di Qualità, quella derivata direttamente dal Principio ADP sopra descritto, denominata Metrica di Dipendenza Aciclica (ADP/ADPR), una delle più diffuse e apprezzate.
Vogliamo ora applicare tale Metrica a un'applicazione reale e ai suoi Package.
Il nostro obiettivo, secondo quanto prescritto da essa, consiste nell'individuare ed eliminare i cicli formati dalle dipendenze indesiderate.
Come prima cosa, calcoliamo la percentuale di quelle dipendenze che, al contrario, non determinano cicli, e che quindi non necessitano di essere rimosse.
Se ad esempio troveremo un valore pari a 100%, potremo concludere che non ci sono cicli nella nostra struttura, essa cioè è completamente conforme al principio ADP.
Nel caso in cui il valore raggiunto sia ad esempio 90%, occorrerà cercare quali sono le dipendenze da rimuovere e poi applicare tali modifiche al codice della applicazione complessiva.
L'applicazione scelta per illustrare un esempio concreto di applicazione delle Metriche è Jmeter (rif.[5]), che nasce nella community Apache come applicazione destinata alla effettuazione di test statici e dinamici su diverse configurazioni di software, permettendo anche di simulare carichi su rete.
Esaminiamo Jmeter dal punto di vista della Metrica ADP, con l'intento di vedere se è possibile migliorarne la struttura.
Servendoci del tool Compuware OptimalAdvisor [rif.(6)], specifico per questo genere di problematiche, costruiamo innanzitutto un modello grafico UML di Jmeter (fig.3).


Figura 3
- Modello Grafico di JMeter

Il modello grafico ottenuto in tal modo mostra le dipendenze tra Package, raffigurate con linee tratteggiate, e il valore percentuale di compatibilità di Jmeter stessa con la Metrica prescelta. Nel nostro caso, come si nota nell'angolo in alto a destra, il valore percentuale rilevato è dell' 88% rispetto alla Metrica ADP. Questo significa che occorre effettuare un certo lavoro di analisi per individuare tutte quelle dipendenze che determinano la presenza di cicli indesiderati.
Come trovarle? Come rimuoverle?
Sempre guardando la figura 3, si può constatare come a ciascun Package sia stato assegnato un colore differente, che va dal rosso (Package contenente un elevato numero di dipendenze indesiderate) al verde (Package sul quale non è più necessario intervenire). Le sfumature di colore intermedie tra questi due estremi evidenziano in modo molto preciso il grado di criticità del Package.
Un'altra importantissima informazione che viene fornita riguarda le dipendenze su cui occorre lavorare. Esse vengono rappresentate con una linea tratteggiata in grigio o in rosso, indicando in quest'ultimo caso che si tratta di dipendenze critiche, che andrebbero rimosse. A ciascuna di esse inoltre è associato un coefficiente numerico, detto "peso", che indica il numero di "referenze" che esistono tra i Package che "corrono" su quella linea tratteggiata: in pratica, corrisponde alla quantità di lavoro che occorre spendere su quella determinata dipendenza per rimuoverla. Vogliamo avere maggiori informazioni su di essa prima di procedere alla rimozione? E' sufficiente selezionarla, e ciò permette di visualizzare la lista completa delle dipendenze che sussistono tra le classi nei rispettivi Package (fig. 4).


Figura 4
- Visualizzazione Dipendenze

Accanto a ciascuna dipendenza in rosso, viene posta anche una icona a forma di lampadina che fornisce una serie di suggerimento pratico (tramite menu pop-up) sulle azioni da svolgere materialmente per eliminare una determinata dipendenza. Selezionando una delle possibilità offerte, viene attuata automaticamente l'azione prescelta.
Procedendo in tal modo, è possibile pervenire rapidamente ad una struttura con un valore percentuale sempre più alto, rispetto alla Metrica scelta (ADP). In figura 5 un esempio di come Jmeter evolve man mano che si eliminano le dipendenze indesiderate.


Figura 5
- Evoluzione della struttura di JMeter

Procedendo per analisi e approssimazioni successive è possibile pervenire ad un modello che sia completamente aderente alla Metrica ADP prescelta, che presenti cioè un valore percentuale pari a 100% (fig. 6).


Figura 6
- Jmeter completamente ADP compliant

E' importante ricordare che tutte le azioni di modifica effettuate finora (rimozione dipendenze, spostamento di una classe da un Package ad un altro e così via) sono state effettuate sul modello UML che di Jmeter è stato costruito quando abbiamo iniziato l'analisi. Il passo finale consisterà nel riflettere tali modifiche sul codice sottostante, reingegnerizzando completamente l'applicazione di partenza. Procediamo quindi con una ricapitolazione finale complessiva di tutte le modifiche sin qui effettuate sul modello (fig.7).


Figura 7
- Analisi riassuntiva delle modifiche effettuate

Ora esaminiamo se le diverse componenti di codice (classi, interfacce) su cui effettuare materialmente le modifiche esistono, sono scrivibili e non sono state nel frattempo modificate da altri (fig.8).


Figura 8
- Verifica ultima della attuazione del Refactoring

L'ultimo passo consiste semplicemente nel "cliccare" il bottone "Refactor!" (in basso a sinistra in figura), per attuare materialmente sul codice tutte le azioni prescelte e il gioco è fatto!
Abbiamo reingegnerizzato la struttura attuando il cosiddetto "Refactoring", quello che viene definito come un processo di "Miglioramento a livello strutturale dei componenti di una applicazione".

 

Conclusioni
Abbiamo percorso passo dopo passo un processo di analisi progressivo, attraverso Packaging, Layering, Metriche, fino ad arrivare al Refactoring. Conoscere e saper applicare questi concetti è qualcosa che va ben oltre la pura ricerca metodologica di tipo accademico. Nella vita informatica reale, capita di doversi confrontare tanto con problematiche di sviluppo ex-novo, quanto con questioni di analisi e manutenzione su applicazioni preesistenti, quindi con codice scritto da altri.
Ecco quindi che paradigmi quali Packaging, Layering, Metriche possono rivelarsi vitali, ma occorre anche tenere presente che questo può non bastare.
Come evidenziato attraverso l'esempio di Jmeter e osservato anche in rif.[7] infatti, è di estrema importanza poter disporre anche di adeguati strumenti in grado di venire in nostro aiuto anche e soprattutto quando non siamo stati noi i principali attori durante le fasi iniziali di sviluppo. Strumenti che ci permettano di affrontare la analisi completa di una applicazione preesistente offrendoci la possibilità di applicare Metriche basate sui principi del Packaging e del Layering per poi supportarci durante tutte le fasi del processo di Refactoring, e in modo quanto più possibile automatizzato.

 

Bibliografia
[1] D.Parnas, Carnegie-Mellon University, "On the criteria to be used in decomposing systems into modules", Communications of the ACM, Vol. 15, No. 12, 1972, pp. 1053 - 1058 http://www.acm.org/classics/may96/
[2] J. Gosling, B. Joy, G. Steele, G. Bracha, "The Java Language Specification", Chapter 7 Packages. Online version at http://java.sun.com/docs/books/jls/second_edition/html/packages.doc.html#60384
[3] R.C Martin, "Agile Software Development: Principles, Patterns, and Practices", Prentice Hall 2002.
[4] Compuware Lab "Layering", http://javacentral.compuware.com/products/optimaladvisor/documentation/v3.1/2095-21-10-57-59.html
[5] Apache Community "Jmeter" http://jakarta.apache.org/jmeter/
[6] Compuware Lab "OptimalAdvisor" - http://javacentral.compuware.com/products/optimaladvisor/
[7] E. Hautus, "Improving Java Software through Package Structure Analysis" The 6th IASTED International Conference Software Engineering and Applications, 2002, online copy at http://www.xs4all.nl/~ehautus/papers/PASTA.pdf

Doriano Gallozzi è nato a Roma nel 1964 e si è laureato in Ingegneria Elettronica (indirizzo Informatica) nel 1989. Da sempre interessato a problematiche di analisi, progettazione e sviluppo di Sistemi Informativi, ha collaborato per diversi anni con aziende del settore informatico italiano (gruppo ENI, gruppo Telecom Italia), dove ha acquisito diverse esperienze tanto nel campo della progettazione e sviluppo del software (in ambiente M/F come in ambiente distribuito) quanto nel campo dei RDBMS (DBA su diversi progetti per clienti finali quali TELECOM Italia). Da gennaio 2000 collabora con la Divisione Prevendita di Compuware Italia. La sua attività verte principalmente sulla piattaforma J2EE e tecnologie correlate, ma anche su ambiti tecnologici quali l'Enterprise Application Integration, i Portali Web, gli strumenti di Business Process Modeling.


 


MokaByte® è un marchio registrato da MokaByte s.r.l. 
Java®, Jini® e tutti i nomi derivati sono marchi registrati da Sun Microsystems.
Tutti i diritti riservati. E' vietata la riproduzione anche parziale.
Per comunicazioni inviare una mail a info@mokabyte.it