MACXIM: uno strumento per la misurazione di applicazioni Java. Tesi di laurea di: Vincenzo Pandico Matricola: 612160 Relatore: Prof. Vieri Del Bianco Correlatore: Prof. Sandro Morasca Dott. Davide Taibi Anno Accademico 2007‐2008 Università degli Studi dell’Insubria FACOLTÀ DI SCIENZE MATEMATICHE FISICHE E NATURALI Corso di Laurea specialistica in Informatica MACXIM: uno strumento per la misurazione di applicazioni Java. Tesi di laurea di: Vincenzo Pandico Matricola: 612160 Relatore: Prof. Vieri Del Bianco Correlatore: Prof. Sandro Morasca Dott. Davide Taibi Anno Accademico 2007‐2008 Executive summary Quando si parla di valutazione, si deve prendere in considerazione il fatto che l’analisi può risultare molto difficile, soprattutto quando ad essere presi in esame sono progetti di grandi dimensioni, complessi ed articolati. Si deve considerare il codice scritto, le librerie utilizzate, l’ambiente in cui il prodotto viene sviluppato. Effettuare valutazioni con una metodologia manuale può risultare molto problematico e portare ad errori di stima. Il risultato di questo lavoro di tesi è stato la realizzazione di uno strumento di misurazione che prende il nome di MACXIM (Model And Code XML‐ based Integrated Meter). Questa applicazione permette di effettuare misurazioni metriche su progetti Java in maniera automatizzata. Il lavoro è cominciato a partire dallo studio di cosa si intendesse per misurazioni metriche e come queste potessero essere effettuate sul codice di un progetto. Il codice Java è infatti fonte di metriche, che possono essere utilizzate per misurare la quantità e le qualità del codice prodotto e di conseguenza la produttività del team di sviluppo. L’utilizzo della tecnologia XML per rappresentare le entità da misurare e i risultati delle misurazioni dà a MACXIM la possibilità di integrarsi facilmente con altre applicazioni e di rendere i suoi risultati portabili. Sommario Capitolo 1............................................................................................................................ 8 Introduzione ....................................................................................................................... 8 1.1 Obiettivi .................................................................................................................... 9 1.2 Struttura dello sviluppo MACXIM e della tesi........................................................... 9 Capitolo 2.......................................................................................................................... 12 Analisi del codice .............................................................................................................. 12 2.1 Analisi automatizzata e valutazione .......................................................................13 2.2 Qualità .................................................................................................................... 14 2.3 La misurazione del codice ...................................................................................... 15 2.4 Analisi di tipo statico............................................................................................... 17 2.4.1 Revisione del codice ........................................................................................ 18 2.4.2 Dipendenze del codice..................................................................................... 18 2.4.3 Complessità del codice .................................................................................... 18 2.5 Benefici dovuti all’analisi di tipo statico .................................................................19 2.6 Tipologie di analisi ..................................................................................................19 2.6.1 Analisi sintattica............................................................................................... 22 2.6.2 Analisi del flusso .............................................................................................. 22 2.7 Tecniche di analisi del codice................................................................................. 24 2.7.1 Codice sorgente ............................................................................................... 24 2.7.2 Codice compilato ............................................................................................. 26 Capitolo 3.......................................................................................................................... 29 Tecnologie utilizzate .........................................................................................................29 3.1 Java ......................................................................................................................... 29 3.1.1 Caratteristiche e qualità.................................................................................. 29 3.1.2 Linguaggio .......................................................................................................32 3.1.3 Ambiente Java.................................................................................................32 3.2 Abstract Syntax Tree............................................................................................... 33 3.3 XML ........................................................................................................................ 35 3.4 XQuery e XQueryX .................................................................................................38 3.5 eXist ....................................................................................................................... 40 Capitolo 4.......................................................................................................................... 42 Architettura dell’applicazione .......................................................................................... 42 4.1 Architettura ............................................................................................................43 4.2 MACXIM: descrizione generale............................................................................... 45 4.2.1 Rappresentazione XML di sorgenti Java .......................................................... 46 4.2.2 Rappresentazione XML delle metriche e dei risultati ......................................47 4.3 Flusso delle informazioni: dall’estrazione ai risultati.............................................. 48 4.3.1 Estrazione delle informazioni .........................................................................51 4.3.2 Misurazione del software ............................................................................... 51 4.3.2 Visualizzazione dei risultati ............................................................................. 51 Capitolo 5.......................................................................................................................... 53 Modulo estrazione informazioni ...................................................................................... 53 5.1 Abstract Syntax Tree............................................................................................... 53 5.2 Abstract Syntax Tree: Parsing del codice ................................................................ 54 5.2 Abstract Syntax Tree: Node .................................................................................... 55 5.3 Ottenere informazioni da un nodo AST. .................................................................58 5.4 Pattern Visitor.........................................................................................................61 5.5 Rappresentazione XML di un sorgente Java. .......................................................... 64 5.6 Inserimento delle informazioni nel database XML ................................................ 68 5.6.1 Struttura del database XML ............................................................................69 Capitolo 6.......................................................................................................................... 71 Modulo misurazione e visualizzazione ............................................................................. 71 6.1 Esecuzione delle misurazioni .................................................................................. 71 6.1 Metriche in MACXIM .............................................................................................. 73 6.1.1 Metriche di codice ........................................................................................... 73 6.1.2 Complessità Ciclomatica .................................................................................. 76 6.1.3 Altre metriche..................................................................................................78 Capitolo 7.......................................................................................................................... 81 Conclusioni ....................................................................................................................... 81 Bibliografia........................................................................................................................ 83 Capitolo 1 Introduzione La misurazione del software consiste, per definizione, nella quantificazione delle caratteristiche di un prodotto applicativo e nella possibilità di stimare e pianificare lo sforzo produttivo necessario per la realizzazione di progetti. Questa misurazione assume un ruolo di crescente importanza nel controllo dei progetti di sviluppo con l’obiettivo di migliorare la qualità del prodotto finale. Tradizionalmente, i parametri con i quali si può misurare o definire la qualità del software vengono classificati in due categorie: • Qualità esterne: qualità del software così come è percepita dai suoi utenti, e includono correttezza, affidabilità, robustezza, efficienza e usabilità. • Qualità esterne: qualità del software così come è percepita dagli sviluppatori e includono verificabilità, manutenibilità, riparabilità, evolvibilità, riusabilità, portabilità, leggibilità e modularità. Esiste un legame che correla queste due categorie, si può infatti affermare che un software mal scritto tende a funzionare male. In particolare in questo studio vengono prese in considerazione solo le qualità interne riguardanti la struttura del codice. Il software prodotto con modello di sviluppo Open Source possiede caratteristiche particolari ed interessanti che ne contraddistinguono il processo di sviluppo. Tra queste sicuramente la natura del codice di programmazione, disponibile e modulare, che permette di adattare il prodotto alle esigenze dell'utente. 1.1 Obiettivi L’obiettivo della tesi è strettamente legato alla caratteristica di visibilità del codice e consiste nella progettazione e sviluppo di uno strumento di misurazione: MACXIM (Model And Code XML‐based Integrated Meter), che permetta l'effettuazione di analisi quantitative di software open source java riguardanti il codice del prodotto analizzato. In questa ottica lo strumento realizzato consentirà la creazione di una base di dati di conoscenza, che permetterà di valutare correttamente alcune delle qualità del prodotto di interesse e ne consentirà il confronto con prodotti similari. 1.2 Struttura dello sviluppo MACXIM e della tesi Una volta individuati i requisiti, abbiamo implementato lo strumento di analisi MACXIM. IL processo per ottenere i risultati finali parte dalla preparazione dei dati da analizzare. Successivamente viene segue l'analisi vera e propria. Infine le informazioni ottenute vengono rielaborate e presentate. Entrando nello specifico si è suddivisa la realizzazione in moduli per permetterne la riusabilità. Un primo modulo permette l'estrazione delle informazioni a partire dal codice sorgente fornito. Un secondo modulo, data una rappresentazione delle informazioni estratte ne permette l'analisi. La raccolta dei risultati derivanti da analisi di vari progetti potranno infine costituire materiale per indagini statistiche sulle qualità del software. Nel secondo capitolo vengono descritte alcune tecniche per l’analisi del codice, fondamentali per lo sviluppo di MACXIM. Nel terzo capitolo viene fatta una panoramica sulle tecnologie utilizzate e necessarie per spiegare lo sviluppo dello strumento. Nel capitolo seguente, il quarto, viene mostrata l’architettura del software, spiegando le scelte intraprese. Nel quinto e sesto capitolo verranno trattati rispettivamente il modulo preposto all’estrazione delle informazione a partire dalle classi Java e il modulo anteposto all’analisi delle informazioni estratte. Al termine di quest’analisi verranno fatte alcune considerazioni sullo strumento realizzato e sui suoi possibili sviluppi futuri. Capitolo 2 Analisi del codice Con "analisi del codice" si intende analizzare staticamente il codice per controllare se soddisfa uniformemente le aspettative riguardanti la sicurezza, l'affidabilità, le prestazioni e la manutenibilità. Eseguita correttamente questa operazione fornisce le fondamenta per la produzione di codice di qualità evitando errori strutturali. Non importa quanto uno sviluppatore sia esperto ed organizzato perché, nonostante tutte le sue buone intenzioni, continuerà a scrivere codice che probabilmente avrà qualche bug. La maggior parte delle applicazioni software sono diventate così complesse che è quasi impossibile scrivere codice che soddisfi i requisiti senza creare comportamenti indesiderati nel sistema. Applicazioni di tale livello di complessità sono spesso costituite da un numero molto elevato di componenti e svariate migliaia di righe di codice. Per facilitare la comprensione e l'implementazione di sistemi complessi esistono processi di sviluppo agile. Ma anche con l'utilizzo di queste tecniche continua a presentarsi un largo numero di bugs all'interno dei software. Dove con il termine bug si identifica un errore nella scrittura di un software in grado di causare un funzionamento errato o diverso da quello atteso nell’esecuzione del programma. Anche se questo è un passo nella giusta direzione, per arrivare a standard di qualità è necessario avere strumenti di analisi automatizzata. Il momento migliore per scoprire problemi è quando si effettua una revisione del codice appena scritto. Con l'aiuto di strumenti di analisi di tipo statico la maggior parte della rivisitazione del codice può essere effettuata automaticamente. Le Misurazioni di tipo statico forniscono un meccanismo per poter realizzare tool per la revisione di codice attraverso i quali è possibile trovare difetti nella fase di implementazione. Analisi di questo tipo permettono di scovare bug nel codice ancor prima dell’esecuzione del programma stesso. L’individuazione di bug permette di conseguenza di individuare le soluzioni per una corretta implementazione migliorando la qualità della fase produttiva e l’affidabilità del prodotto finale. Le analisi di tipo statico aiutano inoltre a far rispettare convenzioni di codifica, rendendo più facile la manutenibilità. Queste sono le motivazioni che hanno spinto a prendere in considerazione questo tipo di analisi come supporto alla produzione di applicazioni di qualità. 2.1 Analisi automatizzata e valutazione Quando si parla di valutazione, bisogna prendere in considerazione il fatto che l’analisi può risultare molto complessa, soprattutto quando ad essere presi in considerazione sono progetti di grandi dimensioni, complessi ed articolati. Si deve considerare il codice scritto, le librerie utilizzate, l’ambiente in cui il prodotto viene sviluppato. Effettuare valutazioni di questo tipo con una metodologia manuale può risultare molto difficile e portare ad errori di valutazione. La soluzione migliore consiste nel prendere in considerazione un tipo analisi che possa essere applicata in maniera automatizzata così da migliorare il processo complessivo che porta al perfezionamento del prodotto. Per questo motivo l’approccio deve essere di tipo quantitativo, in modo da fornire misure paragonabili, deve poter essere ripetibile e, vista la complessità delle operazioni, automatizzato. Anche avendo dati quantificati, derivanti da analisi, rimane il problema della valutazione di questi. Valutare un’applicazione e paragonarla ad un’altra infatti non è assolutamente una procedura agevole, specialmente quando si paragonano prodotti di domini differenti. In questa situazione ci si può porre la questione di come interpretare una misurazione, potrebbe, ad esempio, essere valutata in base a dei valori medi oppure avere una scala graduata con cui avere un confronto. Si possono introdurre delle soglie, stabilire cioè dei valori minimi che devono essere rispettati. In questo modo per avere un prodotto di qualità devono essere soddisfatti dei requisiti minimi, ma in un confronto tra software rimarrebbe la questione di interpretare correttamente la distanza tra due o più misurazioni. Probabilmente una valutazione di tipo relativo è più interessante specialmente per comparazioni del tipo “il programma X è due volte un certo valore di qualità rispetto ad Y”. L'analisi automatica è un approccio interessante, tuttavia bisogna fare delle considerazioni di cui tenere conto per la realizzazione di un tale approccio. Dobbiamo avere delle metriche per progetto che siano il più possibile automatiche, che siano comparabili da progetto a progetto e che siano il più significative possibile. 2.2 Qualità Le qualità che il software deve possedere sono molteplici in relazione all’ambito in cui verranno utilizzati. In applicazioni critiche come in medicina, aviazione, attività finanziarie, affidabilità, correttezza e sicurezza assumono un ruolo importante mentre per applicazioni destinate ad un utente, che non richiede come qualità fondamentale l’affidabilità solitamente vengono richieste altre qualità quali elevate prestazioni ed usabilità del prodotto. Il produttore invece desidera un software con elevata manutenibilità, verificabilità e riusabilità, in modo da aumentare il rendimento del prodotto, in quanto saranno necessarie minori risorse per individuarne e correggerne i difetti, ed esso potrà essere più facilmente fatto evolvere o riusato in nuovi progetti. Attraverso l'utilizzo di misurazioni è possibile quantificare le qualità e le caratteristiche del progetto in analisi e, a partire da queste misure, sarà poi possibile individuarne le inefficienze, punti di partenza per ogni futuro miglioramento del prodotto. Dunque la misurazione di varie metriche di un prodotto software rappresenta un'insieme di informazioni fondamentali per stimare la qualità del progetto e le misurazioni sul codice ne rappresentano una parte fondamentale. 2.3 La misurazione del codice I metodi quantitativi , che ottengono risultati misurabili e non descrittivi, si sono dimostrati strumenti potenti in altri campi della scienza, per questo motivo per la scienza dei computer pratici e teorici si è studiata una tecnica per mettere a punto un simile approccio nello sviluppo di software. Tom DeMarco ha asserito che " You cannot control what you cannot measure" [1]. Quantificare le qualità e le caratteristiche del progetto in analisi, a partire da una serie di misurazioni, permette di individuarne le inefficienze le quali sono i punti di partenza per ogni futuro miglioramento del prodotto. Avere delle misurazioni è utile inoltre per controllare lo sviluppo del prodotto, [Figura 1]. Figura 1 Uso delle metriche per controllare le fasi di sviluppo del software. Rendere il processo di revisione del codice più gestibile e prevedibile mediante un'analisi statica dello sviluppo del software migliora la produttività e l'affidabilità del prodotto finale. Tuttavia il più grande vantaggio è la capacità di individuare i difetti in fase di codifica, che incidono direttamente sull'affidabilità del software. Per ottenere questi benefici è però indispensabile applicare l'analisi di tipo statico con gli strumenti giusti, con corrette regole di configurazione, di modo da poter essere un meccanismo molto potente per ottenere una misurazione quantitativa che aiuti a migliorare l'accuratezza globale del progetto. MACXIM è realizzato per effettuare delle misurazioni sul codice e quindi per misurare quello che abbiamo appena descritto. 2.4 Analisi di tipo statico Analisi statica significa lo studio di qualcosa che non cambia. In termini software può essere ridefinita come lo studio del codice sorgente non ancora in esecuzione. Sappiamo che i debugger ci permettono di analizzare il codice durante la sua esecuzione, ma possiamo conoscere molto dal codice senza dover eseguire l'applicazione. Ad esempio se si analizzano i file sorgenti di un’applicazione è possibile garantire che il codice sorgente aderisca ad uno standard di codifica predefinito. È anche possibile individuare problemi di prestazioni più comuni. Si possono anche esaminare le classi importate per capire le relazioni di dipendenza. Per fare tutto questo non vi è la necessità né di compilare il programma né di eseguirlo. Tuttavia ci sono molti tipi di analisi di tipo statico categorizzati a seconda dei valori che forniscono. 2.4.1 Revisione del codice Negli strumenti che effettuano l’analisi del codice in maniera automatizzata ogni file sorgente è caricato ed analizzato da un parser il quale scorre il codice alla ricerca di particolari patterns che violano delle regole prestabilite. In alcuni linguaggi, come C++, molte di queste regole sono insite nel compilatore o disponibili in programmi esterni. In altri linguaggi, come Java, il compilatore controlla poco sotto l'aspetto della revisione. La revisione del codice è un buon strumento per forzare alcuni standard del codice, scovare problemi di base relativi alle performance e trovare possibili abusi nell'utilizzo delle API (Application Programming Interface). La revisione del codice può inoltre includere forme di analisi più approfondite come il flusso di dati, controllo di flusso, e così via. 2.4.2 Dipendenze del codice Piuttosto che esaminare il formato di singoli file sorgente, gli strumenti che analizzano le dipendenze del codice esaminano le relazioni tra i file di origine (in genere le classi) per creare una mappa generale dell'architettura del programma. Strumenti di questo tipo sono comunemente utilizzati per scoprire design pattern funzionali oppure non funzionali a seconda delle esigenze. 2.4.3 Complessità del codice Gli strumenti che analizzano la complessità del codice del programma effettuano delle metriche del software stabilite per determinare quando risulta inutilmente complesso. Quando un particolare blocco di codice supera una certa soglia di valutazione metrica, può essere marcato come candidato per una ristrutturazione in modo da migliorarne la manutenibilità. 2.5 Benefici dovuti all’analisi di tipo statico I benefici dovuti ad analisi di tipo statico non solo apportano miglioramenti qualitativi, ma anche, aspetto di almeno altrettanta importanza, portano a risparmiare tempo e denaro. Un aspetto inerente al risparmio di tempo dovuto a strumenti che permettono analisi di tipo statico è abbastanza ovvio: ci vuole meno tempo per ottenere codice di maggiore qualità. Il risparmio di denaro è strettamente legato alla qualità del codice, in quanto la scrittura di codice con pochi difetti porta al risparmio legato alla risoluzione dei problemi che si possono riscontrare. Scoprire difetti durante il processo di sviluppo costa meno e risolvere bug mentre in ogni fase successiva diventa sempre più costoso. 2.6 Tipologie di analisi In generale, molti progetti spendono più della metà del loro ciclo di vita nella revisione del codice e nella prevenzione di difetti [2]. Questo sforzo può essere significativamente ridotto automatizzando il processo di revisione del codice. L'automatizzazione inoltre aiuta nel realizzare la consistenza in termini di norme di codifica e buone pratiche di realizzazione. Qui ci focalizzeremo su Java esplorando le differenti tecniche usate per ottenere una revisione automatica del codice. Per prima cosa viene descritto il ruolo, come illustra la Figura 2, delle analisi statiche nel ciclo di vita dello sviluppo del software e i partecipanti coinvolti nel processo: Figura 2 Processo di analisi statica. Gli sviluppatori sono responsabili della scrittura del codice e dell'effettuazione delle analisi statiche al fine di identificare e risolvere eventuali difetti e problemi. Gli "architetti" sono responsabili della selezione degli strumenti di analisi statiche e della configurazione delle regole. I consulenti della qualità del software sono responsabili dei difetti delle analisi e della prevenzione. Sempre dalla Figura 2 si può notare che l'analisi statica non compromette l'importanza dei programmatori e degli architetti del software, perché la selezione di tool affidabili e appropriati di analisi statiche è critica. L'automatizzazione di analisi statiche implica che gli sviluppatori debbano garantire l'individuazione di errori e la loro risoluzione. In breve, gli sviluppatori dovrebbero fornirsi di un processo di revisione affidabile e automatizzata che permetta al team di sviluppo di concentrarsi su altri importanti processi di sviluppo per soddisfare le richieste funzionali. Prima di entrare nei dettagli delle varie tecniche usate per automatizzare le misurazioni in MACXIM, è importante comprendere i parametri base richiesti per automatizzare strumenti di revisione dal punto di vista di Java e visionare quali tipologie di misurazioni è possibile effettuare avendo a disposizione un codice sorgente. Le tipologie di analisi possibili per effettuare misurazioni sono varie, di seguito ne vedremo alcune ma vediamone alcune. 2.6.1 Analisi sintattica L'analisi sintattica è fatta determinando la struttura del codice java in input e comparandola con modelli predefiniti. I difetti più comuni usando questa metodologia vengono riscontrati utilizzando delle convenzioni, come l'uso di una tipologia di nomi standard, oppure avere sempre la clausola di default negli statement switch (costrutti di controllo utilizzati quando è necessario eseguire una serie di controlli sulla stessa variabile). Per esempio, vedi Figura 3, l'assenza della clausola di default potrebbe nascondere potenziali bugs che potrebbero essere rilevati da questa clausola. Figura 3 Switch statement. switch (expression) { case c1: statements // do these if expression== c1 break; case c2: statements // do these if expression == c2 break; } 2.6.2 Analisi del flusso L'analisi del flusso di dati tiene traccia degli oggetti (variabili) e del loro stato (valore del dato) in un particolare momento di esecuzione di un metodo del programma. Questa metodologia monitora la situazione delle variabili in tutti i suoi possibili stati, predicendo così, ad esempio, delle possibili eccezioni dovute a puntatori nulli oppure oggetti di un database che non sono stati chiusi. La figura 4 mostra l'oggetto connessione al database "con2" che non è stato chiuso in tutti i possibili flussi. Questa situazione potrebbe portare ad uno stato critico. Inoltre, se le connessioni a una base di dati sono una risorsa limitata, tenerli in vita potrebbe creare altri problemi. Figura 4 class TestResourceLeak{ public CoreResponse process(Entity entity) throws ResourceException { CoreResponse coreresponse = new CoreResponse(); DatabaseConnection dbcon = new DatabaseConnection(); Connection con1 = null; Connection con2 = null; //getting the Data Base Connection try{ con1 = dbcon.getConnection(); con2 = dbcon.getConnection(); ... } catch(Exception e) { con1.close(); throw new ResourceException(e.getMessage(),e) ; } con1.close(); return coreresponse; } } 2.7 Tecniche di analisi del codice Fino ad ora abbiamo considerato alcuni possibili tipi di analisi che si possono effettuare sul software, ma adesso prendiamo in esame come effettivamente queste misurazioni possono essere fatte, tenendo presente che da qui in avanti le considerazioni riguarderanno il linguaggio Java. Le tecniche di ispezione possono essere categorizzate in due tipologie che possono essere applicate sul codice sorgente Java (.java File) oppure sul bytecode generato dal compilatore (.class File). 2.7.1 Codice sorgente L’ispezione del codice sorgente permette di prendere codice Java sorgente in input. Tecniche di questo tipo per prima cosa scansionano il file sorgente usando un parser, successivamente eseguono regole predefinite su questo codice sorgente. La comprensione profonda del linguaggio è imprescindibile per ogni tool che vuole avere la capacità di identificare bugs o problemi in un particolare linguaggio di programmazione. Ci sono numerosi parser del linguaggio Java che si attengono alle specifiche del linguaggio Java (JLS), come ad esempio JavaCC (https://javacc.dev.java.net/) e ANTLR (http://www.antlr.org/). L’aspetto importante è che strumenti di questo tipo abbiano la capacità di scansione del codice in modo strutturato e forniscano delle APIs che semplifichino le regole di implementazione. Il Java parser inoltre semplifica il sorgente Java convertendo il codice in una struttura ad albero conosciuta come "Abstract Syntax Tree". La Figura 5 mostra un abstract syntax tree (AST) generato per codice Java usando JavaCC e JJTree parser (https://javacc.dev.java.net/) per costruire un programma che riconosca l'abbinamento tra il codice e le specifiche della grammatica. Infine genera un AST del file codice sorgente Java. L’utilizzo di questa struttura verrà meglio spiegata nel capitolo 5, poiché rappresenta il nodo centrale per l’estrazione delle informazioni necessarie a MACXIM per eseguire le proprie metriche. Figura 5 Abstarct Syntax Tree generato. public class GenerateAST { private String printFuncName() { System.out.println(funcName + "Generate AST"); } } CompilationUnit TypeDeclaration ClassDeclaration:(public) UnmodifiedClassDeclaration(GenerateAST) ClassBody ClassBodyDeclaration MethodDeclaration:(private) ResultType Type Name:String MethodDeclarator(printFuncName) FormalParameters Block BlockStatement Statement StatementExpression PrimaryExpression PrimaryPrefix Name:System.out.println PrimarySuffix Arguments ArgumentList Expression AdditiveExpression:+ PrimaryExpression PrimaryPrefix Name:funcName PrimaryExpression PrimaryPrefix Literal:"Generate AST" 2.7.2 Codice compilato La tecnica di scansione di bytecode Java analizza il codice compilato, cioè i file con estensione .class creati dalla compilazione del codice sorgente. Questo approccio, noto come “reflection”, utilizza delle librerie per accedere al bytecode Java e implementa modelli e regole predefiniti usando queste librerie. Le librerie Java bytecode aiutano l'accesso al compilato fornendo interfacce di astrazione del livello sorgente. Possiamo così leggere una classe e le sue informazioni senza conoscere in modo dettagliato il bytecode. L'API reflection è così un'infrastruttura che permette di ispezionare un oggetto a runtime, al fine di scoprire la classe di appartenenza, la sua composizione in termini di metodi, campi, interfacce implementate, i modificatori utilizzati e persino di lavorare su ciascuno di questi elementi in modo simile a quanto si può fare usando gli appositi operatori del linguaggio durante la stesura di un programma. La "Reflection" consente dunque a Java l'abilità di ispezionare dinamicamente all'interno del proprio codice delle classi caricate. Le API Java Reflection forniscono un meccanismo per prendere le informazioni di una classe (super classe, nomi di metodi...) che sono usati per implementare regole come gerarchie di ereditarietà o il numero massimo di metodi in una classe. La scelta effettuata per la realizzazione di MACXIM è quella di analizzare solo il codice sorgente. Questo perché il nostro strumento si vuole presentare e collocare in ambito open source, motivo per cui suppone che il codice sorgente sia disponibile ed aperto. Capitolo 3 Tecnologie utilizzate In questo capitolo saranno introdotte brevemente le tecnologie utilizzate per realizzare MACXIM. Questa panoramica consente di poter meglio comprendere le scelte per lo sviluppo dello strumento, 3.1 Java Il linguaggio di programmazione Java è stato creato verso la metà degli anni novanta, e per questo è ancora in fase evolutiva, tanto che ogni anno circa ne viene rilasciata una nuova release. Da linguaggio nato solo per la rete è divenuto un vero e proprio linguaggio di programmazione, paragonabile, dal punto di vista delle funzionalità, al C++. 3.1.1 Caratteristiche e qualità Java venne creato per soddisfare le seguenti caratteristiche [3]: • Il tuo linguaggio di programmazione è orientato agli oggetti, ed è semplice. • Il tuo ciclo di sviluppo è molto veloce perché Java è interpretato. Il ciclo compile-link-load-test-debug è obsoleto, ora devi compilare ed eseguire. • Le tue applicazioni sono portabili attraverso più piattaforme. Scrivi le applicazioni una sola volta, e non avrai mai bisogno di "portarle", esse sono eseguibili senza modifiche su diverse piattaforme hardware e su diversi sistemi operativi. • Le tue applicazioni sono sicure perché il sistema run-time Java gestisce la memoria per te. • Le tue applicazioni grafiche interattive sono ad alte prestazioni perché più thread simultanei possono essere attivi, e le tue applicazioni supportano il multithreading incorporato nell’ambiente Java. • Le tue applicazioni sono adattabili ai cambiamenti di ambiente perché puoi scaricare dinamicamente moduli di codice da qualunque parte sulla rete. • I tuoi utenti possono fidarsi delle tue applicazioni esse sono sicure, anche se essi scaricano codice da Internet; il sistema run-time Java ha incorporato protezioni contro virus e altre manipolazioni. Java supporta applicazioni che saranno eseguite su architetture hardware e sistemi operativi diversi. Per risolvere questa diversità di ambienti operativi, il compilatore Java genera il bytecode, un formato di codice intermedio tra il codice ad alto livello e quello macchina, progettato per essere efficientemente trasportato su piattaforme hardware e software diverse. Esso viene poi eseguito da una virtual machine, Java Virtual Machine (JVM), cioè da un interprete. L’interprete Java può così eseguire i bytecode Java su ogni macchina sulla quale l’interprete e il sistema run‐ time è stato portato. JVM è la specifica di una macchina astratta per la quale il compilatore Java genera il codice. Una specifica implementazione della JVM per piattaforme hardware e software specifiche provvede alla realizzazione concreta di questa macchina virtuale. La compilazione dei sorgenti java avviene con controlli severi, ma il linguaggio è dinamico nella fase di link. Le classi infatti sono collegate solo quando occorre. In questo modo la fase di link di un programma è semplice e leggera e il ciclo di sviluppo del software diventa molto più rapido. Grazie a queste caratteristiche è così possibile eseguire su hardware e sistema operativo diverso programmi scritti in Java ed aspettarsi lo stesso comportamento durante la fase della sua esecuzione. Una Java Virtual Machine è implementata anche nei vari Browser per poter eseguire programmi Java remoti nella rete, i cosidetti Applet. Un Java applet è un particolare tipo di applicazione che può essere avviata all’interno del browser dell’utente, eseguendo codice scaricato da un server web remoto. Questo codice viene eseguito in un’area altamente ristretta, che protegge l’utente dalla possibilità che il codice sia malevolo o abbia un comportamento non desiderato. Chi pubblica il codice può applicare un certificato che usa per firmare digitalmente le applet dichiarandole “sicure”, dando loro il permesso di uscire dall’area ristretta e accedere al filesystem e al network, presumibilmente con l’approvazione e sotto il controllo dell’utente. Java ha introdotto la possibilità di creare applicazioni multi‐thread, ovvero applicazioni che svolgono in modo concorrente molteplici attività. In Java è anche stato introdotto, aspetto di nostro interesse ai fini dello studio e realizzazione di MACXIM, il supporto per la riflessione, ovvero la capacità di un programma di agire sulla propria struttura e di utilizzare classi caricate dinamicamente dall’esterno. Aspetto questo, che permette di analizzare il codice Java dall’interno. Tra gli argomenti che depongono spesso a favore di Java nella scelta del linguaggio di implementazione di un progetto software moderno, inoltre, si deve certamente sottolineare la notevole quantità delle librerie standard di cui il linguaggio è dotato. Questo contribuisce a renderlo altamente integrabile con le altre tecnologie. 3.1.2 Linguaggio Progettato per creare software altamente affidabile, fornisce ampi controlli in fase di compilazione, seguito da ulteriori controlli in fase di esecuzione. La filosofia object‐oriented di Java ha come obiettivo la creazione di un linguaggio estremamente semplice, facile da apprendere e volto a produrre programmi affidabili. Affidabilità che si cerca di garantire anche attraverso la gestione automatica della memoria. Oltre a semplificare il lavoro del programmatore, tali scelte sono orientate ad aumentare la sicurezza, intesa sia come protezione da errori accidentali che come prevenzione nei confronti di operazioni illecite da parte di estranei. 3.1.3 Ambiente Java Esaminiamo adesso la struttura dell’ambiente Java. Semplificando possiamo dire che la compilazione di codice sorgente Java porta alla creazione del Java Byte Code, che verrà eseguito da un interprete la cui implementazione dipende dalla piattaforma di esecuzione. Da quanto detto si evidenzia la portabilità del codice Java, una tra le più importanti qualità di tale linguaggio. In figura 6 viene schematizzato l’ambiente Java. Figura 6 Ambiente Java Caricatore di bytecode Codice Java Compilatore Java Verificatore di bytecode Network o file system Interprete Java Bytecode Java Generatore di codice Run Time Hardware 3.2 Abstract Syntax Tree Un Abstract Sytanx Tree, o semplicemente Syntax Tree, è una rappresentazione ad albero della sintassi di un qualche codice sorgente( che è stato scritto tramite un linguaggio di programmazione). Ogni Nodo dell'albero denota un costrutto che occorre nel codice sorgente. L'albero è astratto nel senso che potrebbe non rappresentare qualche costrutto presente nel codice originale. Un classico esempio di omissione è il raggruppamento delle parentesi poiché in un AST il raggruppamento degli operandi e dei costrutti è implicito. Un AST è spesso costruito da un parser come parte del processo di compilazione del codice sorgente. Una volta costruito l'albero, le informazioni aggiuntive sono inserite nell'AST da un processo seguente, ad esempio l'analisi semantica. Questa tecnologia permette così di avere una rappresentazione del codice sorgente da cui si possono ricavare molte informazioni. Esistono vari strumenti di questo tipo, per l'implementazione di MACXIM si è deciso di utilizzare il framework Abstract Syntax Tree per Eclipse [4]. Questo tool permette di mappare codice sorgente Java in forma di albero. Il flusso delle informazioni per estrarre i dati di interesse da parte di MACXIM, per quanto riguarda l'utilizzo dell'AST, è il seguente, [Figura 7]: Figura 7 1 Sorgente Java. Per cominciare è necessario fornire il codice da analizzare. 2 Parsing. Il codice sorgente fornito dal passaggio precedente viene analizzato. Tutto ciò di cui si ha bisogno per questo passo è fornito dalla classe org.eclipse.jdt.core.dom.ASTParser. 3 Rappresentazione. Dal passo precedente si ottiene un Abstract Syntax Tree (AST). Si tratta di un modello ad albero che rappresenta interamente la fonte fornita dal primo passo. Manipolare l'AST. A questo punto è possibile usufruire delle informazioni della rappresentazione ad albero del codice sorgente Java. Tutte le classi rilevanti per utilizzare la struttura AST sono localizzate nel package org.eclipse.jdt.core.dom del plug‐in org.eclipse.jdt.core. 3.3 XML XML (eXtensible Markup Language) [5], è nato come raccomandazione del W3C (World Wide Web Consortium) ed è stato pensato nell’ottica di una rivisitazione di SGML (Standard Generalized Markup Language). La tecnologia XML permette di rappresentare informazioni di tipo testuale e strutturato. E’ un linguaggio di tipo descrittivo che permette all’utilizzatore di definire un proprio linguaggio di markup. Per questo motivo possiamo definire XML come un metalinguaggio, finalizzato alla rappresentazione di contenuti testuali organizzati in gerarchia. XML non possiede un insieme predefinito di marcatori, come invece è per l’HTML, chiamati TAG, ma permette di definirne di nuovi, e di stabilire come essi potranno essere organizzati in strutture gerarchiche. Questi TAG ci permettono di leggere le informazioni in essi contenuti, in base al loro nome e non necessariamente alla loro posizione. A ogni documento XML possiamo associare la definizione della struttura al suo interno utilizzata, al fine non solo di renderlo facilmente leggibile anche a chi non lo ha mai visto, ma anche di rendere rapido il processo di verifica per il controllo che tale struttura sia rispettata nel documento. Questa struttura, le regole che stabiliscono quali sono i TAG e come essi possono essere organizzati in uno specifico file XML, sono contenute in un file detto DTD (Document Type Definition). In tal senso si può dire che un documento XML è ben formato se rispetta tutte le regole sintattiche previste dal linguaggio, mentre è definito valido se, e solo se, esso è conforme alle regole presenti nella DTD ad esso associato. Infatti un documento leggibile non implica che abbia anche un senso dal punto di vista semantico. I DTD presentano alcune limitazioni. Ad esempio, secondo i DTD il contenuto dei TAG è sempre testo. Non è prevista la possibilità di definire tipi di dato e pertanto non è possibile ottenere un controllo accurato sul contenuto dei TAG e sul valore degli attributi. Non c’è possibilità di comporre documenti XML che facciano riferimento a DTD diversi. Occorre inoltre precisare che la sintassi di un DTD è slegata dal mondo XML. A prima vista quest’ultimo aspetto può essere considerato di poco conto, ma ha invece ha la sua importanza se consideriamo che in linea di principio non possiamo riutilizzare gli stessi strumenti che usiamo per XML quando lavoriamo con un DTD. A questi problemi cercano di dare una soluzione gli XML Schema Definition (XSD). XML come abbiamo detto è un metalinguaggio, quindi può essere utilizzato per rappresentare altri linguaggi. XSD è un documento XML che utilizza un insieme di TAG speciali per definire la struttura di un documento XML. Il vincolo che dobbiamo rispettare nella creazione di uno schema è l’uso dei TAG definiti dall’XML Schema Language, definito dal W3C. Una delle principali novità introdotte dagli XML Schema rispetto ai DTD è la possibilità non solo di definire il tipo di dato di un elemento, ma anche di poter personalizzare un tipo di dato. Esistono tipologie di dato: semplici e complessi. I tipi di dato semplici sono relativi a quegli elementi che non possono contenere altri elementi e non prevedono attributi. Sono previsti numerosi tipi di dato predefiniti. È possibile definire tipi semplici personalizzati derivandoli da quelli predefiniti. Ad esempio, possiamo definire un tipo di dato come restrizione del tipo stringa, vincolando i valori ad uno specifico insieme di stringhe o ad un pattern individuato da un’espressione regolare. I tipi di dato complessi si riferiscono alla definizione degli elementi con attributi e che possono contenere altri elementi. La definizione del tipo complesso consiste generalmente nella definizione della struttura prevista dall’elemento. Se l’elemento può contenere altri elementi possiamo definire l’insieme degli elementi che possono stare al suo interno come sequenza, come insieme di valori alternativi o come gruppo. Grazie a queste caratteristiche possiamo trasmettere contenuti XML tra diversi sistemi senza avere vincoli né in termini di piattaforma né di protocolli. Basta che sia supportato il testo. Queste qualità rendono XML una tecnologia decisamente molto interessante e finalizzata a essere utilizzata quando vogliamo gestire lo scambio di informazioni tra diverse realtà, così come ogni qual volta sia necessario mantenere i dati puri separati dal loro aspetto grafico. 3.4 XQuery e XQueryX XQuery [6] (XML Query Language) è un linguaggio di programmazione specificato dal W3C nato con l'obbiettivo di recuperare agevolmente informazioni da documenti e basi di dati XML. Può essere visto come una sorta di SQL per XML. Ma a differenza di SQL, che opera su tabelle relazionali, XQuery usa delle strutture dati organizzate nell'ordine in cui appaiono nel documento XML sorgente. Tutte le espressioni XQuery devono rispettare questo ordine tranne nel caso in cui sia diversamente specificato nell’ espressione stessa. XQuery non è un linguaggio basato su XML ed è costituito da una sintassi semplice basata sulle espressioni di XPath per la selezione di specifiche porzioni di documenti XML, con l'aggiunta delle cosiddette espressioni FLWOR per la formulazione di query complesse. Dove FLOWR è un acronimo che deriva dai seguenti costrutti: • For che fornisce un meccanismo iterativo. • Let che definisce un meccanismo per l'assegnamento. • Where che consente di filtrare i risultati tramite condizioni booleane. • Order by che offre un meccanismo di ordinamento. • Return che indica l'espressione da valutare e restituire. Una query in XQuery è così composta da un’espressione che legge una sequenza di nodi XML oppure un singolo valore e restituisce come risultato una sequenza di nodi od un singolo valore. XQueryX [7] (XML Syntax for XQuery) è un linguaggio basato su XML per esprimere le interrogazioni XQuery e gli elementi che costituiscono la sua sintassi consentono di riprodurre, mediante tag XML, le espressioni XQuery. Una versione XML delle espressioni XQuery è molto utile perché ci permette di utilizzare tutti gli strumenti XML (parser XML, XSLT, ecc.) per creare, analizzare e manipolare le query sui documenti XML. L’interrogazione avrà però una sintassi più complicata e meno leggibile rispetto alla versione XQuery poiché ciascuna espressione XQuery viene mappata in un apposito tag XQueryX. Questa maggiore complessità ha portato a non utilizzare XQueryX in MACXIM. Si è così scelto di definire un formato XML per la memorizzazione delle query. Formato che permette di definire nuovi documenti meno strutturati di XQueryX ma con il vantaggio di essere più facilmente controllabili e modificabili. 3.5 eXist eXist [8] è un database che permette di memorizzare documenti in formato XML ed è un prodotto open‐source. Il supporto a XQuery offre la possibilità di costruire su eXist applicazioni web complete, con l’ausilio della sola tecnologia dei fogli di stile Xslt. Le XQuery interrogano il database in vari modi: attraverso una XQueryServlet, piuttosto che un XQueryGenerator o mediante l’uso di un Api. Per realizzare l’interfaccia utente per la misurazione è stato scelto il primo metodo: il servlet XQueryServlet processa i file XQuery dell’applicazione, per poi restituire l’output HTML al browser. Il database può funzionare in vari modi. Come processo server stand‐ alone, richiamato via rete con protocolli come Xml‐Rpc o Api Rest basate quindi su http. Come database embedded in un’applicazione ospite (di cui usa la Jvm). Oppure connesso a un servlet engine. In tutte queste modalità viene garantito l’accesso multiutente e si possono, se necessario, utilizzare i thread. Ci sono varie soluzioni libere che permettono di memorizzare documenti XML, ma eXist ha i suoi punti di forza nel supporto a XQuery e a XUpdate, nonché funzionalità di auto indicizzazione ed estensioni per ricerche full‐ text. Capitolo 4 Architettura dell’applicazione In questo capitolo viene analizzata l’architettura generale dell’applicazione e spiegate le scelte di progettazione. Per la base di questo lavoro si è partiti dall’idea che utilizzando una qualsiasi tecnologia da cui poi si potessero ottenere dei risultati in formato XML sarebbe stato possibile uno strumento con certe caratteristiche: • Estendibile, per poter definire nuove metriche. • Modificabile, per poter modificare le metriche già implementate. • Trasparente, per avere a disposizione l’implementazione delle metriche. • Indipendente, per poter misurare codice indipendentemente dagli strumenti di sviluppo utilizzati. • Interoperabile, per permettere all’applicazione di poter interagire con altre. Esistono svariati strumenti per analizzare il codice sorgente o il codice compilato ed alcuni effettuano anche misurazioni automatiche del codice. Molti di questi strumenti forniscono un insieme completo di metriche predefinite e producono misure affidabili. Alcuni strumenti esistenti sono dotati di interfacce utente abbastanza sofisticate in grado di visualizzare il risultato delle misurazioni anche attraverso grafici di varie tipologie. Per esempio PMD () and CheckStyle (http://checkstyle.sourceforge.net/) sono buoni esempi di strumenti che analizzano il codice sorgente Java. FindBugs (http://findbugs.sourceforge.net/) e JLint (http://jlint.sour ceforge.net/) analizzano il bytecode per effettuare analisi di tipo statico. Tuttavia spesso non sono presenti alcune funzionalità che riteniamo importanti, come la possibilità di definire nuove metriche, modificare quelle predefinite, ottenere una rappresentazione XML dei risultati delle misurazioni, ricavare metriche complesse e misurare le variazioni tra due diverse versioni . 4.1 Architettura La realizzazione dell’applicazione è partita dalla progettazione della sua architettura che rappresenta una fase cruciale al fine di sviluppare un tool flessibile, modulare e semplice. L’architettura di MACXIM è suddivisa in due sottoparti fondamentali. Una adibita all’estrazione delle informazioni e l’altra con il compito di effettuare le misurazioni sul progetto preso in analisi. In mezzo a questi due moduli è presente il database eXist. In questo database viene salvata la rappresentazione XML del progetto che costituisce il risultato del primo modulo. Il secondo modulo invece ha il compito di analizzare questa rappresentazione e fornire i risultati delle analisi attraverso una interfaccia. Lo scopo primario della scomposizione del sistema in sottosistemi è la realizzazione di più componenti distinti che risultano meno complessi rispetto alla realizzazione di un sistema unitario. Ridurre la complessità di realizzazione non è però l’unico scopo della fase di realizzazione dell’architettura. Un altro fine è quello di migliorare le caratteristiche di qualità del sistema e nello specifico i seguenti aspetti: Modificabilità. È possibile circoscrivere le modifiche da apportare al sistema alle sole componenti ove si desiderano apportare dei cambiamenti. Questa caratteristica permetterà di introdurre nuovi tipi di metriche o modificare quelle esistenti senza dover implementare la parte di estrazione dei dati sui quali queste vengono effettuate. Interoperabilità. Non solo si può migrare l’applicazione su una piattaforma distinta, ma le singole componenti dell’applicazione possono essere distribuite su varie piattaforme, in modo trasparente allo sviluppatore. Così facendo, ad esempio, si può applicare il sottosistema adibito al calcolo delle metriche a qualsiasi base di dati che contenga informazioni strutturate secondo le specifiche necessarie all’analisi. Favorire l’interoperabilità significa quindi fornire anche l’infrastruttura di comunicazione usata dalle varie componenti. Riuso di componenti già esistenti. L’idea che il software possa essere scomposto in componenti, e che per la sua realizzazione si possano usare componenti precostituiti è stata presentata da Douglas McIlroy’s [4]. La prima implementazione di questa idea fu l’uso di componenti prefabbricati nel sistema operativo Unix. Un altro esempio è .NET che include un insieme di componenti base riutilizzabili che forniscono varie funzionalità (gestione della rete, sicurezza, etc.). Riuso di componenti realizzati in precedenti progetti. Il riuso di componenti esistenti mira a sfruttare in un nuovo progetto, e quindi in un nuovo contesto, un componente già realizzato, senza modificarlo. Si può anche progettare un componente in previsione di un suo riuso futuro. Poiché i componenti non possono essere modificati ma possono essere estesi, se si progetta al fine di un riuso conviene definire componenti generici. Questo permetterà la facilitazione dello sviluppo futuro del progetto o il riuso di alcuni componenti di MACXIM in altri progetti. Prestazioni. L’architettura permette di valutare il carico di ogni componente, il volume di comunicazione tra componenti e il numero di possibili accessi alla base di dati xml. Sicurezza. L’architettura permette un controllo sulle comunicazioni tra le parti e l’identificazione delle parti vulnerabili. Verifica. La definizione dell’architettura del sistema consente in fase di verifica di seguire un approccio incrementale: verificare prima i singoli componenti, quindi insiemi sempre più ampi di componenti, per giungere in modo incrementale alla verifica dell’intero sistema. 4.2 MACXIM: descrizione generale Con lo strumento proposto, MACXIM, si vuole fornire la possibilità di ottenere delle metriche partendo da progetti Java. In generale, [Figura 8], nel database xml utilizzato per salvare i dati, eXist, vengono salvati i progetti open source Java opportunamente rappresentati da relativi file xml, i quali contengono tutte le informazioni inerenti al progetto. Tramite delle query al database sarà poi possibile ottenere le misurazioni desiderate. Figura 8 Rappresentazione generale di MACXIM. 4.2.1 Rappresentazione XML di sorgenti Java Per estrarre informazioni da codice sorgente Java si è deciso di utilizzare il framework Abstract Syntax Tree (AST) che permette di ispezionare il codice Java come se fosse una struttura ad albero. Questo ne permette un’ analisi più approfondita, ma è un aspetto trattato meglio nel quinto capitolo, quando si parlerà dell’estrazione delle informazioni a partire da un file sorgente Java. Si è scelto di definire un formato di rappresentazione xml. Questo formato viene così creato a partire dalla struttura ad albero ottenuta tramite AST. In Figura 9 è riportato lo schema generale del processo di trasformazione in formato xml del progetto e del suo salvataggio. Figura 9 Processo di inserimento di sorgenti Java nel database. Il tool si preoccupa di trasformare in maniera automatica il progetto in una sua corrispondente rappresentazione xml, estraendo dalla rappresentazione fornita da AST solo le informazioni necessarie al fine di poter eseguire successivamente delle metriche. 4.2.2 Rappresentazione XML delle metriche e dei risultati Di seguito viene illustrato il processo che a partire dall’esecuzione di misurazioni su un progetto fornisce i risultati delle metriche. La misurazione avviene tramite interrogazione al database eXist mediante query scritte in linguaggio XQuery. Tali query sono rappresentate nella base di dati in uno specifico formato XML. Come schematizzato in Figura 10, l’utente può eseguire le query di interesse sul progetto. La query inviata a MACXIM viene rappresentata tramite un modello XML in modo da poter essere eseguita sul progetto da misurare. Vengono estratti i risultati e questi vengono opportunamente rappresentati per facilitarne la consultazione e visualizzazione da parte dell’utente. Infine i risultati delle interrogazioni possono essere memorizzati all’interno del database, anch’essi in file XML in un formato appositamente definito. Figura 10 Processo di interrogazione. 4.3 Flusso delle informazioni: dall’estrazione ai risultati Fino ad ora è stato illustrato come è composto MACXIM e come le informazioni vengono rappresentate ed elaborate. Ma queste singole parti devono comunicare tra di loro per poter arrivare, a partire da un file sorgente, a mostrare i risultati finali. Il flusso delle informazioni parte da un progetto java, il quale viene fornito dall’utente. Una volta creati i file rappresentanti il progetto, questi vengono salvati all’interno di un database. A questo punto le informazioni sono fruibili attraverso le interrogazioni al database. Tramite opportune query è così possibile ottenere le informazioni di interesse. Come schematizzato in Figura 11, l’utente può eseguire le query di interesse su un progetto. Se il progetto non è ancora stato salvato all’interno del database questo deve essere fornito dall’utente. La query inviata a MACXIM viene rappresentata tramite un modello XML in modo da poter essere eseguita sul progetto da misurare. Vengono estratti i risultati e questi vengono opportunamente rappresentati per facilitarne la consultazione e visualizzazione da parte dell’utente. A questo punto l’utente può decidere se effettuare una nuova interrogazione, in questo caso il processo ricomincia dall’inizio. Fondamentalmente seguendo il flusso delle informazioni la struttura di MACXIM può essere suddivisa in due moduli principali. Uno con il compito di analizzare il codice sorgente per crearne una rappresentazione contenente le informazioni per eseguire delle metriche. L’altra con lo scopo di effettuare le misurazioni tramite delle query. A completamento e integrazione di queste fasi è stata realizzata un’interfaccia per la fruizione dei servizi. 11 Flusso di informazioni. 4.3.1 Estrazione delle informazioni Questo modulo è preposto all’estrazione delle informazioni dal codice sorgente. Operazione possibile grazie all’utilizzo dell’Abstract Syntax Tree. Dalla rappresentazione fornita dall’AST vengono poi estratte tutte le informazioni che riguardano il progetto, quali nomi delle classi, dipendenze dei metodi, dei costruttori. Queste informazioni vengono poi salvate all’interno di un file XML il quale a sua volta viene memorizzato all’interno del database eXist. 4.3.2 Misurazione del software Questo modulo permette l’analisi del codice attraverso l’implementazione di semplici query xql effetuate sulla rappresentazione astratta del codice in formato xml. 4.3.2 Visualizzazione dei risultati Attraverso un’ apposita interfaccia è poi possibile eseguire le interrogazioni al database contenente la rappresentazione creata al passo precedente di un progetto Java. I risultati sono in formato XML, il che garantisce la portabilità dei risultati delle misurazioni. Capitolo 5 Modulo estrazione informazioni In questo capitolo si analizza come avviene l’estrazione, a partire da un file sorgente Java, delle informazioni necessarie per poter successivamente eseguire la misurazione delle metriche. In particolare come viene riprodotto un file sorgente mediante una rappresentazione Abstract Syntax Tree e come è possibile scorrere questa rappresentazione ed estrarre le informazioni di interesse utilizzando il modello Visitor. Per quanto riguarda i sorgenti Java sono disponibili diversi tool che ne forniscono rappresentazioni XML. Uno di questi è JavaML, ma il problema legato all’utilizzo di un tool di questo genere consiste nel rimanere legati ad uno strumento che non è detto si tenga sempre aggiornato alle specifiche di Java. Quando Java introduce nuove strutture o funzioni, si deve sperare che anche il tool rimanga costantemente aggiornato. In questa ottica, per lo sviluppo del progetto, si è deciso di appoggiarci ad un framework legato ad Eclipse, partendo dal presupposto che questa piattaforma fosse molto utilizzata e di conseguenza che si possa mantenere molto più frequentemente aggiornata. 5.1 Abstract Syntax Tree Ogni file sorgente Java è interamente rappresentato come un albero composto da nodi. In particolare l’albero è composto da tanti nodi, ognuno dei quali è un oggetto di tipo ASTNode. Esistono poi tante sottoclassi di questo nodo, ognuna delle quali è specializzata per rappresentare un elemento del linguaggio di programmazione Java. Alcuni di questi nodi sono quelli per la dichiarazione di metodi (MethodDeclaration), altri tipi di nodo sono quelli per la dichiarazione di variabili (VariableDeclarationFragment), poi ci sono i nodi che rappresentano gli assegnamenti, le eccezioni e così per tutti i possibili elementi rappresentabili in Java. Un nodo che è stato utilizzato molto per ottenere informazioni significative è il nodo SimpleName. SimpleName è una qualsiasi stringa del sorgente Java che non rappresenta una keyword, un Boolean literal (true o false), o un valore null. Per esempio in i = 6 + j i e j sono rappresentati da un SimpleName. Se invece consideriamo l’importazione di un package come "import java.io.BufferedOutputStream;", le parole “java”, “io” e “BufferedOutputStream” sono anch’esse mappate in un nodo SimpleName. 5.2 Abstract Syntax Tree: Parsing del codice Il codice sorgente può essere fornito sotto forma di file Java all'interno di un progetto Eclipse o direttamente come stream di dati attraverso l’utilizzo di un array char[]. Si è scelto di utilizzare la seconda scelta per cercare di rimanere distaccati, per quanto possibile, dall'ambiente Eclipse. A questo punto in MACXIM, a partire dall’input fornito, viene generata una rappresentazione del codice. Figura 12 ASTParser parser = ASTParser.newParser(AST.JLS3); parser.setKind(ASTParser.K_COMPILATION_UNIT); parser.setSource(sourceString.toCharArray()); CompilationUnit node = (CompilationUnit) parser.createAST(null); Come si vede in figura 12 per prima cosa viene creato l’oggetto ASTParser, specificando quale versione di supporto a Java si vuole utilizzare. In questo caso JL3 rappresenta l’ultima disponibile. L’altro passaggio importante è quello di fornire il file sorgente vero e proprio da analizzare, ed avviene tramite il metodo parser.setSource(char[] input). La nostra scelta implementativa è stata quella di fornire il sorgente in input in formato String di modo che potesse essere caricato all’interno dell’array char[]. Quest’ultimo rappresenta il formato su cui il framework lavora per costruire l’Abstract Syntax Tree. Al termine di questa procedura la rappresentazione ad albero è disponibile per la fruizione. E' importante sottolineare che ASTParser non è un compilatore. Però è in grado di marcare degli errori nel caso in cui nel codice sorgente qualcosa potrebbe violare l'integrità dell'albero. Ad esempio, se viene scritto “classs” invece di “class”, questo errore di battitura viola l'integrità della creazione del nodo TypeDeclaration, e così viene marcato un errore. Ma bisogna specificare che un albero valido non significa una corretta compilazione del codice sorgente. 5.2 Abstract Syntax Tree: Node Anche un semplice programma "Hello world" risulta essere rappresentato da un AST in una struttura abbastanza complessa. Consideriamo lo spezzone di codice riportato in Figura 13. Come è possibile ottenere il nodo MethodInvocation del metodo println(“Hello world”)? Figura 13 public class Hello{ … System.out.println("Hello!"); … } E' possibile esaminare tutti i nodi dell'albero generato alla ricerca del nodo di interesse, ma non è una soluzione molto conveniente. La soluzione migliore è utilizzare il modello Visitor Pattern, che descriveremo meglio dopo, il quale permette di interrogare ricorsivamente i figli di un nodo. La classe ASTVisitor dichiara i metodi preVisit (ASTNode nodo) e postVisit (ASTNode nodo). In ogni classe sottoclasse di ASTNode avremo due metodi, uno chiamato visit(), l'altro chiamato endVisit(). Lo scorrimento dell’albero avviene in questo modo. La sottoclasse di ASTVisitor è passata ad ogni nodo dell'AST. L'AST scorre ricorsivamente all'interno dell'albero, chiamando i metodi del visitor per ogni nodo AST, ad esempio considerando il nodo MethodInvocation, in questo ordine: 1. preVisit(ASTNode node) 2. visit(MethodInvocation node) 3. Ora il nodo figlio del nodo MethodInvocation è processato ricorsivamente se il metodo visit() restituisce il valore booleano true. 4. endVisit(MethodInvocation node) 5. postVisit(ASTNode node) Un esempio di implementazione del metodo visit() viene mostrato nel blocco di codice in Figura 14. Figura 14 public boolean visit(VariableDeclarationStatement node) { for (Iterator iter = node.fragments().iterator(); iter.hasNext();) { VariableDeclarationFragment fragment = (VariableDeclarationFragment) iter.next(); // salva le informazioni da qualche parte } return false; } Se dal metodo visit() viene restituito come valore false allora il sottoalbero del nodo visitato non viene considerato. Questo serve per evitare di analizzare i rami dell'albero che non contengono i nodi che non si vuole analizzare. I nodi di tipo Comment non sono mostrati nella struttura dell’albero perché non hanno nodi padre. In questo caso il metodo getParent() applicato ad un nodo commento restituisce null. E' invece possibile accedere a questi richiamando il metodo getCommentList() dal nodo radice della struttura ad albero. Per visitare i nodi Comment si è dunque dovuto chiamare questo metodo e visitare ogni nodo commento individualmente. 5.3 Ottenere informazioni da un nodo AST. Ogni sottoclasse di un ASTNode contiene informazioni specifiche per ogni elemento Java che rappresenta. Ad esempio un nodo di tipo MethodDeclaration conterrà informazioni quali il nome del metodo, il tipo di ritorno, i parametri. Vediamo più in dettaglio le proprietà di un metodo. La struttura ad albero qui sotto riportata, [Figura 15], è un esempio di rappresentazione AST. Figura 15 In particolare l’albero qui sopra riportato fa riferimento al seguente blocco di codice, [Figura 16]: Figura 16 public void start(BundleContext context) throws Exception { super.start(context); } MACXIM quindi in realtà non lavora direttamente sul sorgente, ma su una sua rappresentazione ad albero. Le informazioni di un nodo sono riferite come proprietà strutturali. Queste proprietà possono essere suddivise in tre differenti tipologie, [Figura 17]: proprietà che possiedono solamente valori semplici, proprietà che contengono un singolo nodo figlio di tipo AST node e proprietà che contengono una lista di figli di tipo AST node. Figura 17 SimplePropertyDescriptor. Il valore può essere di tipo String, un valore di tipo Integer o Boolean oppure una costante AST. ChildPropertyDescriptor. Il valore può essere un nodo, un'istanza della sottoclasse di un ASTNode. ChildListPropertyDescriptor. Il valore può essere una lista di nodi AST. Le informazioni che si ottengono dalla rappresentazione AST sono molte. L'accesso ai valori delle proprietà strutturali di un nodo può essere effettuato utilizzando metodi static o generic. Con i metodi static ogni nodo offre dei metodi per accedere alle sue proprietà: getName(), exceptions(), etc. Con i metodi generic si richiede un valore di una proprietà usando il metodo getStructuralProperty (StructuralPropertyDescriptor property). Ogni sottoclasse AST definisce un set di StructuralPropertyDescriptors, uno per ogni proprietà della struttura. Si può così accedere esplicitamente allo StructuralPropertyDescriptor direttamente dalla classe a cui appartiene. Ad esempio per ottenere il nome di un metodo tramite MethodDeclaration.NAME_PROPERTY. La lista di tutti gli StructuralPropertyDescriptors di un nodo può essere estratta chiamando il metodo structuralPropertiesForType() su qualsiasi istanza di un ASTNode. 5.4 Pattern Visitor In MACXIM è necessario eseguire svariate operazioni indipendenti e non relazionate tra loro sugli oggetti della struttura composta, cioè l’albero AST. Per questo motivo si è deciso di utilizzare un Pattern Visitor, il quale rappresenta un' operazione che deve essere eseguita su una struttura di oggetti, in questo caso una struttura ad albero. Questo tipo di design pattern permette così di separare l’algoritmo dalla struttura di oggetti composti a cui è applicato, in modo da poter aggiungere nuove operazioni e comportamenti senza dover modificare la struttura stessa. In questo modo vengono definite le operazioni da eseguire su un nodo dell’albero in modo separato ed univoco. Una volta che l'AST è stato creato, il modo più comune per attraversare o manipolare l'albero è tramite l'utilizzo di un Visitor. Come in ogni Pattern Visitor tradizionale, ogni tipo di nodo AST possiede un diverso metodo per la sua visita, in modo da poter implementare un Visitor che analizza soltanto alcuni tipi di espressioni o dichiarazioni. Fuori dai Visitor invece ogni nodo AST offre metodi di accesso in modo opportuno per ognuno dei suoi tipi di nodo figlio.Per esempio un nodo di tipo MethodDeclaration possiede i metodi getBody() e setBody() per accedere o modificare il blocco rappresentante il corpo del metodo. Ma non ci sono altri metodi per accedere ai nodi figli del nodo MethodDeclaration, tranne il metodo generico getParent() per accedere al nodo padre del nodo corrente. Riprendendo il listato della Figura 18, è ora possibile associare un visitor all’AST creato dall’ASTParser. Nella Figura 18 è riportato il listato che realizza un Pattern Visitor, che si chiama ASTPrinter, che viene associato all’AST creato dall’ASTParser. Figura 18 ASTParser parser = ASTParser.newParser(AST.JLS3); parser.setKind(ASTParser.K_COMPILATION_UNIT); parser.setSource(sourceString.toCharArray()); CompilationUnit node = (CompilationUnit) parser.createAST(null); ASTPrinter printer = new ASTPrinter(); node.accept(printer); class ASTPrinter extends ASTVisitor { StringBuffer buffer = new StringBuffer(); public void preVisit(ASTNode node) { //scrive il nome del nodo che sta per essere visitato printDepth(node); String name = node.getClass().getName(); name = name.substring(name.lastIndexOf('.')+1); buffer.append(name); buffer.append(" {\r\n"); } public void postVisit(ASTNode node) { //chiude la parentesi graffa per indicare la fine del nodo printDepth(node); buffer.append("}\r\n"); } void printDepth(ASTNode node) { //indenta correttamente la linea while (node != null) { node = node.getParent(); buffer.append(" "); } } } Il Visitor ASTPrinter sopra riportato serve per mostrare la metodologia con la quale in MACXIM viene visitato l’albero AST alla ricerca delle informazioni. Nel listato riportato in particolare vengono recuperati i nomi di tutti i nodi e viene creata la loro struttura attraverso l’utilizzo delle parentesi graffe. 5.5 Rappresentazione XML di un sorgente Java. A questo punto è stata spiegata tutta la metodologia di approccio usata in MACXIM per rappresentare un sorgente Java. Qui di seguito, [Figura 19], è così riportato uno spezzone di file XML che rappresenta una classe Java. I primi dati rappresentati sono quelle relativi alle informazioni generali della classe come le misurazioni delle righe di codice, i moduli importati nel file sorgente, i modificatori della classe ed altre ancora. Dopo le informazioni generali vengono rappresentate le variabili globali, specificando il loro tipo, il nome e soprattutto assegnandogli un “id” di riferimento. Questo “id” serve per identificare univocamente questa variabile. Similmente ma con l’aggiunta di anche altre informazioni, come la complessità ciclomatica, sono rappresentati i metodi del file sorgente rappresentato. Figura 19 <java‐source‐program> <java‐class‐file blankLinesNum="82" commentLinesNum="130" linesNum="558" name="C:/MACXIM/projectToAnalyze/main/Hangman.java" onlyBraceLinesNum="57" onlyCommentLinesNum="130"> <import module="java.awt.*"/> <import module="java.awt.event.*"/> <import module="java.applet.*"/> <import module="java.io.*"/> <import module="java.net.*"/> <class blankLinesNum="80" commentLinesNum="93" id="LHangman;" idkind="type" linesNum="514" name="Hangman" onlyBraceLinesNum="57" onlyCommentLinesNum="93"> <modifiers> <modifier name="public"/> </modifiers> <superclass idkind="type" idref="Ljava/applet/Applet;" name="java.applet.Applet"/> <implement idkind="type" idref="Ljava/lang/Runnable;" interface="Runnable"/> <implement idkind="type" idref="Ljava/awt/event/MouseListener;" interface="MouseListener"/> <implement idkind="type" idref="Ljava/awt/event/KeyListener;" interface="KeyListener"/> <field id="LHangman;maxTries" idkind="field" name="maxTries"> <modifiers> <modifier name="final"/> </modifiers> <type name="int" primitive="true"/> </field> <field id="LHangman;maxWordLen" idkind="field" name="maxWordLen"> <modifiers> <modifier name="final"/> </modifiers> <type name="int" primitive="true"/> </field> <field id="LHangman;danceImageOffsets" idkind="field" name="danceImageOffsets[]"> <modifiers> <modifier name="private"/> </modifiers> <type name="int" primitive="true"/> </field> <method blankLinesNum="10" commentLinesNum="4" cyclomaticComplexity="3" id="LHangman;init()V" idkind="method" linesNum="42" messagesNum="14" name="init" onlyBraceLinesNum="3" onlyCommentLinesNum="4" returnNum="0" statementsNum="23"> <modifiers> <modifier name="public"/> </modifiers> <type name="void" primitive="true"/> <formal‐arguments/> <dependences> <dependence idkind="field" idref="LHangman;tracker"/> <dependence idkind="field" idref="LHangman;danceImages"/> <dependence idkind="type" idref="Ljava/awt/Image;"/> <dependence idkind="field" idref="LHangman;DANCECLASS"/> <dependence idkind="field" idref="LHangman;hangImages"/> <dependence idkind="field" idref="LHangman;maxTries"/> <dependence idkind="field" idref="LHangman;wrongLetters"/> <dependence idkind="field" idref="LHangman;secretWord"/> <dependence idkind="field" idref="LHangman;word"/> <dependence idkind="field" idref="LHangman;wordFont"/> </dependences> </method> <method blankLinesNum="9" commentLinesNum="7" cyclomaticComplexity="7" id="LHangman;paint(Ljava/awt/Graphics;)V" idkind="method" linesNum="65" messagesNum="21" name="paint" onlyBraceLinesNum="7" onlyCommentLinesNum="7" returnNum="0" statementsNum="36"> <modifiers> <modifier name="public"/> </modifiers> <type name="void" primitive="true"/> <formal‐arguments> <formal‐argument id="LHangman;arg222" idkind="formal" name="g"> <type idkind="type" idref="Ljava/awt/Graphics;" name="Graphics"/> </formal‐argument> </formal‐arguments> <dependences> <dependence idkind="type" idref="Ljava/awt/Font;"/> <dependence idkind="type" idref="Ljava/awt/FontMetrics;"/> <dependence idkind="formal" idref="LHangman;arg222"/> <dependence idkind="field" idref="Ljava/awt/Font;PLAIN"/> <dependence idkind="field" idref="Ljava/awt/Color;red"/> <dependence idkind="field" idref="LHangman;hangImages"/> </dependences> </method> 5.6 Inserimento delle informazioni nel database XML Le informazioni così estratte vengono salvate all’interno del database eXist sotto forma di file XML e sono pronte ad essere analizzate dal modulo successivo, quello incaricato di effettuare le misurazioni vere e proprie. 5.6.1 Struttura del database XML MACXIM si appoggia al database XML eXist, utilizzato per contenere le informazioni estratte da progetti Java e per l’esecuzione delle query che consentono di ricavare le metriche desiderate. Nel database è strutturato secondo delle cartelle per dividere e non mischiare le diverse risorse necessarie al funzionamento di MACXIM. Per questo motivo si è creata una cartella principale, /MACXIM, suddivisa a sua volta in sottocartelle contenenti le seguenti risorse: /db/MACXIM: è la cartella radice . /db/MACXIM/projects/java: contiene i documenti XML rappresentanti i progetti sui quali si desidera effettuare delle misurazioni. /db/MACXIM/modules/java: contiene i moduli relativi alle metriche ricavabili da progetti Java. /db/MACXIM/libraries/java: contiene le librerie relative alle metriche ricavabili da progetti Java. /db/MACXIM/results: contiene i documenti XML che rappresentano i risultati delle misurazioni. Capitolo 6 Modulo misurazione e visualizzazione In questo capitolo viene illustrato come è possibile effettuare le misurazioni delle metriche sulla rappresentazione XML di un progetto Java. MACXIM infatti comprende un’interfaccia utente che consente di interrogare il database al fine di ottenere delle metriche relative ai progetti presenti all’interno del database. In dettaglio poi verranno illustrate alcune metriche e il modo in cui sono implementate. Per poter effettuare le misurazioni sui progetti presenti all’interno del database XML occorre definire delle query in linguaggio XQuery che implementino le metriche desiderate. Ogni query deve poi essere rappresentata in un file XML e tale file dovrà essere inserito nel database XML, nell’apposita directory creata per contenere queste query. 6.1 Esecuzione delle misurazioni Le query che consentono di ricavare le metriche devono essere memorizzate in maniera opportuna nel database e potranno successivamente essere selezionate tramite l’apposita interfaccia web. Questa consentirà di eseguirle e ne permetterà la visualizzazione dei risultati. Una funzionalità aggiuntiva permette inoltre di salvare i risultati, sempre in formato XML. L’interfaccia per l’esecuzione delle misurazione è molto semplice ed intuitiva, [Figura 20]. È possibile caricare un nuovo progetto attraverso l’apposito modulo oppure effettuare delle misurazioni sui progetti precedentemente caricati ed elencati nel menu a sinistra sempre della figura 20. Figura 20 La tipologia di misurazione che si vuole effettuare è possibile sceglierla attraverso lo scorrimento di un menu a tendina. Inoltre viene sempre riportata come informazione il nome della classe o progetto su cui si stanno effettuando delle metriche. Infine è possibile anche decidere se salvare i risultati ottenuti. In questo caso è necessario specificare il nome del file, il quale sarà di tipo XML. Dopo aver scelto ed seguito la misurazione di interesse sono infine visualizzati i risultati, [Figura 21]. Figura 21 6.1 Metriche in MACXIM 6.1.1 Metriche di codice Le prime misurazioni che sono possibili effettuare sul codice riguardano la grandezza corporea del codice. L'indicatore che viene utilizzato per misurare le dimensioni di un programma è dato dal numero delle sue linee di codice ed è noto come Lines of Code (LOC). Questo tipo di misurazione permette di ricavare indicazioni sul possibile numero di errori presenti e il tempo necessario per il suo sviluppo (correlazione). È però un indicatore abbastanza criticabile in quanto di per se non fornisce molte informazioni su cui effettuare una valutazione. Così il numero di linee di codice deve essere integrato da altre informazioni per poter essere in grado di giungere ad una buona valutazione delle qualità del software. Il Lines of Code rappresenta una metrica di base per la stima del costo e del tempo di sviluppo di un progetto software ed è quindi molto importante quando combinata con altre misurazioni. Occorre però definire rigorosamente che cosa si intende per LOC perché questa misura rappresenta il numero di linee di codice, ma questo numero di linee può essere riferito non solo a tutto il codice sorgente. Può essere anche la misurazione del numero di funzione richiamate o di linee di commento. Quindi possiamo ricavare diverse metriche. Numero di linee. Conta le linee che costituiscono il corpo della funzione. Questo tipo di metrica include anche le linee non significative, come possono essere le linee vuote e le linee di solo commento. Numero di linee vuote. Conta le linee che non contengono nessun tipo di carattere. Questa misura indica che se il numero di linee vuote è troppo basso il codice potrebbe risultare poco leggibile e di conseguenza costoso da mantenere. Numero di linee con commenti. Conta le linee contenenti almeno un commento. È un indice che misura l’attenzione prestata dal programmatore per far sì che il codice sia comprensibile. Anche se in realtà codice per formato ed espressivo non dovrebbe avere bisogno di commenti. Numero di linee contenenti solo commenti. Conta le linee contenenti almeno un commento e nessuna istruzione. Numero di linee contenenti solo parentesi graffe. Conta le linee contenenti solo una o più parentesi graffe. Numero effettivo di linee. Calcola il numero di linee effettivo come il risultato della sottrazione dal numero totali di linee, del numero di linee vuote, del numero di linee contenenti solo commenti e del numero di linee contenenti solo parentesi graffe. Esclude insomma tutte le linee non significative. Numero di chiamate a metodo. Si contano le chiamate a metodi o a funzioni. Numero di return. Si contano le istruzioni return. Questa misura indica che un numero eccessivo di return potrebbe creare problemi in fase di testing. Ad esempio l’implementazione XQuery corrispondente alla misurazione del numero di linee con commenti per progetto è data dalla Figura 22: Figura 22 sum ( collection($project)/java‐source‐program/ java‐class‐file/@commentLinesNum ) Di seguito invece è riportata l’implementazione della query per misurare il numero di linee contenenti solo parentesi graffe per package, [Figura 23]: Figura 23 sum ( collection($project)/java‐source‐program/ java‐class‐file[package‐decl/@name=$packageId]/ @onlyCommentLinesNum ) 6.1.2 Complessità Ciclomatica Un’altra metrica molto importante e fondamentale è la Complessità Ciclomatica, che rappresenta un modello di metrica proposto da McCabe [5]. È una metrica strutturale relativa al flusso di controllo di un programma e rappresenta la sua complessità logica, cioè lo sforzo per realizzarlo e comprenderlo. A differenza delle metriche di codice, le quali forniscono indicazioni sulla leggibilità del codice, la Complessità Ciclomatica è una valutazione quantitativa della complessità logica di un programma. Stima in sostanza l’attitudine di un modulo a contenere errori. In modo più rigoroso il numero ciclomatico rappresenta il numero di cammini linearmente indipendenti cioè che introducono almeno un nuovo insieme di istruzioni o una nuova condizione rispetto ad un altro. La complessità ciclomatica conteggia ogni punto di decisione (if, while, for, oppure case statements) in un metodo. La definizione della complessità ciclomatica dovuta alla teoria dei grafi è data da: CC = E – N + P dove E rappresenta il numero degli archi di un grafo, N il numero dei nodi e P il numero dei componenti connessi. Un altro modo per calcolare la complessità ciclomatica è CC = numero dei punti di decisione + 1. Quest’ultima formula rappresenta il modo in cui è stato calcolato l’indice di complessità ciclomatica all’interno di MACXIM. La Figura 24, per esempio, mostra il metodo checkCC con complessità ciclomatica pari a 3 ottenuta da 2 punti di decisione (if e else) più 1. Figura 24 public int CheckCC(int x) { int value = 0; if (x == 0) { value = 100; } else { value = 10; } return value; } È utile conoscere questo parametro per il calcolo della complessità di una classe valutata in base alla complessità dei singoli metodi presenti in essa. Una complessità ciclomatica bassa è indice di un metodo di semplice comprensione, test e gestione. Inoltre una bassa complessità ciclomatica è indicatrice di buona comprensibilità del programma e della possibilità di modificarlo a basso rischio. È utile per misurare la complessità del codice durante la sua codifica per evitare la scrittura di blocchi che potrebbero più facilmente contenere errori. Inoltre facilita la fase di testing in quanto meno il codice è complesso e minore è il numero di test da effettuare per verificare la correttezza. Dunque c’è una correlazione tra la complessità ciclomatica di un programma e la frequenza di errori. 6.1.3 Altre metriche Le query che consentono di ricavare altre metriche devono comunque essere memorizzate in maniera opportuna nel database e potranno successivamente essere selezionate tramite un’interfaccia utente, la quale consente di eseguirle e visualizzarne i risultati, ed eventualmente memorizzarli. Non vengono riportate qui tutte le metriche memorizzante in quanto lo strumento è estensibile e si possono aggiungere e togliere metriche a seconda delle esigenze. Bisogna però sottolineare che la tecnologia utilizzata per effettuare le metriche è il linguaggio XQuery. Per esempio l’implementazione in linguaggio XQuery per effettuare la misurazione del numero di attributi pubblici è, [Figura 25]: Figura 25 count ( collection($project)/java‐source‐program/ java‐class‐file//class[@id=$classId]/field [modifiers/modifier/@name="public"] ) Un altro esempio è la query per misurare il numero di metodi pubblici, [Figura 26]: Figura 26 count ( collection($project)/java‐source‐program/ java‐class‐file//class[@id=$classId]/method [modifiers/modifier/@name="public"] ) L’implementazione della query per misurare il numero di metodi il cui tipo di ritorno è una classe, [Figura 27]: Figura 27 count ( for $parameter in collection($project)/java‐source‐program/ java‐class‐file//class[@id=$classId]/method, $class in collection($project)/*/java‐class‐file//class where $parameter/type/@idref=$class/@id return <ok/> ) Ma queste sono solamente alcune metriche e MACXIM inoltre offre la possibilità di inserirne di nuove. Capitolo 7 Conclusioni L’obbiettivo principale di questo lavoro era quello di ottenere uno strumento di misurazione per progetti Java. Oltre a questo obbiettivo si è pensato di realizzare uno strumento il più possibile modulare e riusabile, caratteristica questa ottenuta tramite la suddivisione di MACXIM in due moduli fondamentali, il modulo per l’estrazione e quello per l’interrogazione. La portabilità delle informazioni infine è garantita utilizzando come modello di rappresentazione il formato XML. Il risultato finale realizza queste aspettative e si presenta come un punto di partenza per poter effettuare delle misurazioni su progetti Java. Sia in fase di sviluppo, e quindi porsi come strumento di controllo del ciclo di sviluppo del software, sia come strumento di valutazione del codice interno di un prodotto finale. La possibilità di poter aggiungere nuove metriche inoltre può far crescere MACXIM e mantenerlo aggiornato. Uno dei più importanti sviluppi futuri sarà la possibilità di comparare le misure effettuate tra prodotti diversi. Un’altro possibile sviluppo sarà quello di staccare MACXIM dalla sua dipendenza con eXist per renderlo ancora più ad alto livello e riutilizzabile. Infine, MACXIM verrà distribuito come software Open Source. Bibliografia [1] T. De Marco, Controlling Software Projects, Management Measurement & Esitmation, Prentice Hall PTR. [2] Tratto da aicqtv: http://www.aicqtv.it/comitati/software/qualita_software.htm [3] The Java Language Environment ‐ A White Paper, James Gosling, Henry McGilton, Sun Microsystems Computer Company. [4] “Eclipse”, http://www.eclipse.org [5] “eXtensible Markup Language (XML)”, http://www.w3.org/XML [6] “XML Query Language (XQuery)”, http://www.w3.org/TR/xquery [7] “XML Syntax for XQuery (XQueryX)”, http://www.w3.org/TR/2003/WD‐xqueryx‐ 20031219 [8] “eXist, open‐source native XML database”, http://exist.sourceforge.net [9] P. Naur and B. Randell, "Software Engineering, Report ona conference sponsored by the NATO Science Committee, Garmisch, Germany, 7th to 11th October 1968". [10] T. McCabe, “A Complexity Measure”, IEEE Transactions on Software Engineering, vol. SE‐12, no. 1, December 1976.