MACXIM: uno strumento per la misurazione di applicazioni Java.

Pagina creata da Alessio Bertolini
 
CONTINUA A LEGGERE
MACXIM: uno strumento per la misurazione di applicazioni Java.
MACXIM: uno
strumento per la
misurazione di
applicazioni Java.

Tesi di laurea di:             Relatore:
Vincenzo                      Prof. Vieri
Pandico                       Del Bianco
                            Correlatore:
Matricola:                  Prof. Sandro
612160                          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:                                                         Relatore:
Vincenzo                                                      Prof. Vieri Del Bianco
Pandico
                                                                      Correlatore:
Matricola:                                                   Prof. Sandro Morasca
612160                                                          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
  Codice Java                                                       bytecode

 Compilatore                  Network o file                        Verificatore
    Java                      system                                di bytecode

                                           Interprete              Generatore di
 Bytecode Java                                Java                    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.
Puoi anche leggere