I riassunti , gli appunti i testi contenuti nel nostro sito sono messi a disposizione gratuitamente con finalità illustrative didattiche, scientifiche, a carattere sociale, civile e culturale a tutti i possibili interessati secondo il concetto del fair use e con l' obiettivo del rispetto della direttiva europea 2001/29/CE e dell' art. 70 della legge 633/1941 sul diritto d'autore
Le informazioni di medicina e salute contenute nel sito sono di natura generale ed a scopo puramente divulgativo e per questo motivo non possono sostituire in alcun caso il consiglio di un medico (ovvero un soggetto abilitato legalmente alla professione).
INGEGNERIA DEL SOFTWARE II
INTRODUZIONE E RICHIAMI (parte 2)
L’ingegneria del software è un insieme di teorie, metodi e strumenti per sviluppare software di qualità in maniera professionale.
I costi del software spesso dominano i costi dei sistemi informatici. È più costoso manutenere il software piuttosto che svilupparlo. L’ingegneria del software ha come obiettivo riuscire a sviluppare software in maniera efficace e con costi contenuti.
Il software è un insieme di programmi, procedure, regole, e ogni altra documentazione relativa al funzionamento di un sistema di elaborazione dati. I prodotti software possono essere sviluppati o per un particolare cliente o per il mercato. Quindi esso può essere : generico(word, exel) oppure personalizzato secondo determinate specifiche del cliente. Il software può essere ottenuto sviluppandolo ex-novo, configurando sistemi software generici, o riusando software pre-esistente.
Il software è:
Il software deve essere di: qualità e facilmente modificabile. Il software non si consuma.
L’ingegneria del software è una disciplina ingegneristica che si occupa di tutti gli aspetti relativi allo sviluppo del software. Gli ingegneri del software dovrebbero adottare un approccio sistematico e organizzato per il loro lavoro, usando strumenti e tecniche appropriate variabili a seconda del problema da risolvere, dei vincoli di sviluppo, e delle risorse disponibili.
L’ingegneria del software ha l’obiettivo di produrre software affidabile, sicuro, usabile e manutenibile. Essa è particolarmente importante per sistemi da cui dipendono persone e processi di business, e che vengono usati per molti anni.
L’informatica si occupa delle teorie e dei metodi alla base dei sistemi software ed informatici, mentre, l’ingegneria del software si occupa degli aspetti pratici relativi alla produzione del software. Anche se le teorie ed i principi informatici sono fondamentali, spesso vengono trascurati dagli ingegneri del software per questioni di praticità.
L’ingegneria dei sistemi si occupa di tutti gli aspetti dello sviluppo ed evoluzione di un sistema informatico, dalla progettazione dell’hardware a quella dei processi fino all’ingegneria del software stessa. Gli ingegneri dei sistemi si occupano di definire le specifiche del sistema, la sua architettura generale, e di integrarne le varie parti. L’ingegneria del software è parte di questo processo e si preoccupa di sviluppare l’infrastruttura software, il controllo, le applicazioni ed il database.
Un processo software è un insieme di attività aventi per obiettivo lo sviluppo o l’evoluzione di un sistema software. Ogni processo software deve includere le seguenti attività:
Un modello di processo software è una descrizione semplificata del processo software osservato da un determinato punto di vista. Ci sono diverse possibili descrizioni:
Esistono poi, diversi modelli generici di processo: approccio a cascata (waterfall), sviluppo ciclico (interative development), ingegneria del software basa su componenti (Componet-based software engineering).
I costi dell’ingegneria del software sono per il 60% per le attività di sviluppo e il 40% per eseguire il testing. I costi variano in base al tipo di sistema sviluppato e ai requisiti di qualità richiesti quali prestazioni o l’affidabilità (es. un sistema real time è più costoso di uno basato sul web). La distribuzione dei costi dipende anche dal tipo di modello di sviluppo adottato.
I metodi di ingegneria del software sono degli approcci strutturati per sviluppare software di qualità, a costi contenuti. I metodi forniscono indicazioni relativamente a:
I CASE ( Computer-Aider Software Engineering) sono sistemi software usati per aiutare le attività di processi software (es. analisi, modellazione, debuging, testing).
I sistemi CASE sono spesso usati a supporto di specifici metodi, essi si dividono in:
Un software di qualità dovrebbe fornire le funzionalità e le prestazioni richieste essendo contemporaneamente:
L’ingegneria del software ha come obiettivo, nella produzione di software, che esso sia
L’ingegneria del software è un’attività che coinvolge uomini e macchine. L’anomalia in questo campo è rappresentata dall’enorme velocità di evoluzione dell’hardware, che rende il software obsoleto ancor prima di essere stato completato. Quindi le sfide nel campo dell’ingegneria del software sono legate alla produttività, all’affidabilità e alla semplicità.
Secondo Brooks non esiste nessuna pallottola d’argento che risolve tutti i problemi dell’ingegneria del software. Esso definisce due tipologie di difficoltà nel software:
Il Ciclo di vita del software è il periodo ti tempo che comincia quando un prodotto software è stato concepito e termina quando non è più utilizzabile per l’uso. Il ciclo di vita del software include il concetto di fasi in quanto lo si può suddividere in varie fasi.
Il ciclo di sviluppo del software è il periodo ti tempo che inizia con la decisione di sviluppare un prodotto software e termina quando il prodotto viene consegnato. Questo ciclo comprende anch’esso varie fasi.
Un processo è un insieme di attività concentrate nel tempo finalizzate alla realizzazione di un particolare output. Mentre, un processo software è un insieme strutturato di attività necessarie per lo sviluppo di un sistema software. Un processo software può essere organizzato usando i cicli di vita del software. I CVS definiscono la struttura di massima di un processo software, indicando le fasi in cui si articola e i criteri di successione. Si parla anche di modello di processo software il quale è una rappresentazione astratta di un processo. Esso fornisce una descrizione del processo da una particolare prospettiva. Abbiamo diversi modelli di processo software, ovvero:
Un processo software si compone delle seguenti attività:
L’attività del processo di progettazione si compone di: progetto dell’architettura, specifica astratta, progetto dell’interfaccia, progetto componenti, progetto delle strutture dati, progetto. degli algoritmi.
Nel processo di progettazione abbiamo vari approcci sistematici per produrre il progetto del software. Il progetto è in genere documentato da un insieme di modelli grafici (Object model, Sequence model, State transition model, structural model, data-flow model).
L’implementazione si compone di altri due processi quali la programmazione e il debugging. La prima serve a tradurre il progetto in un programma eseguibile, il secondo nella rimozione degli errori contenuti nel codice.
Tale attività si compone di una serie di testing: unit testing, testare ciascun componente, modeule testing, testa un insieme di componenti tra loro correlati, sub-system testing, testa un insieme di moduli, system testing, testing dell’intero sistema, acceptance testing, testing effettuato insieme al cliente per l’accettazione.
Vi sono vari modelli di processo software tra cui:
I vantaggi sono che le fasi da seguire sono ben definite, e gli output di ciascuna fase sono precisamente individuati. Gli svantaggi sono che: il modello assume che i requisiti siano stabili; e che il nuovo sistema software diventa installabile solo quanto è totalmente completato. Tale modello è applicabile quando fa parte di un progetto più vasto di ingegneria dei sistemi.
Anche lo sviluppo evolutivo esplorativo ha dei problemi, quali, la mancanza di visibilità del processo, i sistemi diventano spesso mal strutturati, e richiedono particolari skill (uso di linguaggi di prototipazione rapida). Tale modello è applicabile a sistemi interattivi di piccole e medie dimensioni (<500.000 LOC); per sviluppare alcune parti di sistemi di grandi dimensioni (interfaccia utente), utilizzando un approccio misto; per sistemi destinati a vita breve.
Per quanto riguarda lo sviluppo evolutivo basato su prototipi, esso è usato per superare i problemi relativi alla valutazione della fattibilità ed alla comprensione dei requisiti del committente. Si realizza una prima implementazione (prototipo) più o meno incompleta da considerare come una ‘prova’ con lo scopo di accertare la fattibilità del prodotto e calidari i requisiti. Dopo la fase di utilizzo del prototipo si passa alla produzione della versione definitiva del sistema software mediante un modello che, in generale, è di tipo waterfall.
Vi sono due tipi di approcci prototipali. Uno chiamato modello con sistema pilota orientato all’accertamento della fattibilità, con il quale si realizza un PILOT per stabilire se il problema sia risolubile attraverso una sua implementazione. Il pilota è esplorativo, non orientato alla produzione e fornisce feedback ed indicazioni per la definizione di un prodotto finale migliore e più utile.
L’altro chiamato modello con prototipazione. Il modello prototipale si compone di due tipi di prototipazioni:
Per quanto riguarda i processi di sviluppo iterativi, si basano sul concetto che i requisiti di un sistema cambiano sempre nel corso di un progetto, e sono pertanto inevitabili nuovi cicli di processo in cui si ritorna sulle fasi già condotte, soprattutto per grandi sistemi. I cicli di processo possono essere applicati ad ogni generico modello di processo. Abbiamo due possibili approcci. Di cui il primo è lo sviluppo e consegna incrementale, in cui si preferisce anziché consegnare il sistema tutto in una volta, il progetto lo si sviluppa e lo si consegna in modo incrementale, dove ogni incremento rilascia parte delle funzionalità richieste. Ai requisiti utente vengono associati livelli di priorità e quelli a priorità maggiore vengono rilasciati con i primi incrementi. Una volta partito lo sviluppo di un incremento, i relativi requisiti devono essere congelati, mentre i requisiti coinvolti in incrementi successivi possono continuare ad evolvere. I servizi comuni possono essere implementati all’inizio del processo, o quando una funzione è richiesta da una dato incremento.
I vantaggi di tale processo sono che i clienti non devono aspettare il sistema completo per la consegna, ma possono disporre al più presto dei requisiti più critici, attraverso i primi incrementi; i primi incrementi possono essere usati come prototipo per aiutare a definire i requisiti degli incrementi successivi; si riduce il rischio di un fallimento totale del progetto; i servizi a più alta priorità saranno anche testati più intensamente degli altri. I problemi sono che gli incrementi devono essere relativamente piccoli (<20.000 LOC) ma può essere difficile predisporre i requisiti in incrementi della dimensione giusta; le funzionalità comuni potrebbero non essere identificate abbastanza presto, giacchè bisogna prima attendere che gli incrementi siano completati per avere ben chiari tutti i requisiti.
L’extreme programming è un esempio di approccio allo sviluppo basato su sviluppo e consegna di piccolissimi incrementi di funzionalità. Si basa su un continuo miglioramento del codice, sul coinvolgimento dell’utente nel processo di sviluppo, e sulla programmazione a coppie.
Nello sviluppo a spirale il processo è rappresentato come una spirale, invece che una sequenza di attività con retroazioni. Ogni giro nella spirale rappresenta una fase del processo. Non prevede fasi prefissate a priori ma i cicli sono definiti in base al caso specifico. C’è una esplicita gestione dei rischi che vengono valutati e risolti durante tutto il processo.
I settori del modello a spirale sono: determinazione degli obiettivi, si definiscono gli obiettivi, vincoli e piano di gestione della fase; valutazione e riduzione del rischio, si analizzano i rischi della fase e si scelgono le attività necessarie a gestire i rischi; sviluppo e convalida, si sceglie un modello di sviluppo per il sistema tra i modelli generici; pianificazione, il progetto viene revisionato e si decide se continuare con un nuovo giro della spirale. Si pianifica tale giro.
Il RUP (Rational Unifield Process) è un moderno modello di processo software derivato da UML e dal relativo processo. Esso include tutte le caratteristiche dei modelli generici (sviluppo iterativo ed incrementale), ed si individuano 3 prospettive sul processo: 1) una prospettiva dinamica che mostra le fasi del modello al fluire del tempo; 2) una prospettiva statica che mostra le attività coinvolte; 3) una prospettiva pratica che suggerisce le buone regole da seguire.
Le fasi del RUP sono:
La prospettiva statica si concentra sui workflow. Il RUP prevede vari workflow (6 principale e 3 di supporto) essi sono:
mentre quelli di supporto sono:
Tutti i workflow del RUP possono essere eseguiti in qualunque iterazione del processo. Ovviamente nelle prime iterazione gli sforzi si concentreranno sui workflow di modellazione e dei requisiti, nelle successive sulla implementazione e sul test.
Le pratiche fondamentali del RUP sono:
Il RUP è un processo generico di sviluppo software, esso deve essere istanziato per ciascuna organizzazione e per ciascun progetto specifico, aggiungendo: standard, modelli di documento standard, strumenti… I suoi punti di forza consistono nella separazione di fasi e workflow, in quanto le fasi sono dinamiche e vanno pianificate, i workflow sono statici e sono attività tecniche condotte nelle varie fasi, il RUP comprende un vasto insieme di linee guida e template per operare con approccio OO e basato su componenti, e definisce in modo accurato: ruoli, attività, input e output delle varie attività.
Gli oneri relativi al RUP sono:
Spesso si rinuncia ad adottare il RUP proprio perché esso comporta un drastico cambiamento nel modo di lavorare delle persone, che potrebbero reagire (nel breve termine) diminuendo la loro produttività.
Per poter selezionare un modello di processo si dovrebbe seguire una determinata linea guida:
DOCUMENTAZIONE DEL SOFTWARE (parte 3)
La documentazione di un software è estremamente importante, quindi lo sforzo dedicato ad essa non deve essere mai considerata costosa e noiosa. Esistono tre tipi di standard per la documentazione:
Vi sono vari modelli di processi di documentazioni.
Oltre a tutta la documentazione a corredo di tutte le fasi alte del ciclo di vita del software, è fondamentale anche la documentazione interna del codice. Una corretta e completa documentazione del software facilità e supporta molte fasi di ciclo di vita, quali, il testing, l’’integrazioen, la manutenzione, il Riverse Engineering e Reengineering. L’utilizzo di standard di documentazione noti presenta diversi vantaggi, quali:
La documentazione interna al codice dovrebbe essere: Coerente, Consistente (niente ambiguità o contraddizioni), Conforme ad uno standard e Tracciabile, ovvero deve essere possibile poter collegare, nella maniera più rapida possibile, i concetti presenti nel codice con la loro documentazione e con i concetti ad esso associati.
Tra gli innumerevoli standard di documentazione esistenti, sono di interesse quello di Javadoc, ideato per Java, affiancato da un tool in grado di generare manualistica a partire dall’analisi della documentazione presente nel codice sorgente e generalizzabile a qualsiasi altro linguaggio, e quello di Doxygen.
Esistono strumenti in grado di valutare la completezza della documentazione come ad esempio Checkdoc della Sun. Il tool javadoc, richiamabile da linea di comendo, genera un insieme di file in puro HTML, altamente navibili. Non è prevista, invece, la generazione di un unico documento, che possa costituire lo scheletro di un manuale utente.
Doxygen è il sistema di documentazione utilizzabile per programmi scritti in C++, C, Java, ed altri. Esso genera documentazione in HTML e anche un manuale di riferimento completo in RTF, PostScript, PDF, etc.. direttamente a partire dalel informazioni reperibili nel codice sorgente. Doxygen estrae i commenti scritti in formato simile a quello di Javascript e inoltre la struttura dei costrutti principali ricavabili dall’analisi statica del codice, comprese associazioni, ereditarietà, … Doxygen genera automaticamente alcuni diagrammi, tra cui grafi delle dipendenze, diagrammi di ereditarietà, diagrammi di collaborazione ed altro, secondo il formato DOT.
Per naming guidlines si intende un insieme di regole da seguire nella scelta dei nomi di variabile. Si definiscono Pascal Cased i nomi che hanno le iniziali maiuscole (es. RegisterClientScriptCallBack). Sono Camel Cased i nomi che hanno l’iniziale della prima parola minuscola e le iniziali delle rimanenti parole maiuscole (es. httpContext).
Il Naming convention nelal quale ogni nome di variabile dipende dal suo tipo e dal suo scopo, deriva da una notazione ungherese. Si applica di solito ai nomi delle variabili. Ongi nome di variabile inizia con un prefisso costituito da una o più lettere minuscole in modo da formare un codice mnemonico per il tipo o lo scopo di questa variabile. Il primo carattere del nome assegnato è in maiuscolo per separarlo visivamente dal prefisso, coerentemente con lo stile CamelCase. È applicabile a qualsiasi linguaggio di programmazione.
Es. f sta per flag booleano quindi –fError, oppure ch sta per char quindi – chChoose, oppure ancora, str sta per stringa quindi – strText… etc…
La notazione ungherese ha dei limiti essa non è strettamente necessaria in nessun linguaggio compilato. Potrebbe essere utile al programmatore come elemento di tracciabilità della documentazione, ogni volta che viene utilizzata una variabile, il programmatore è consapevole del suo tipo, e nei linguaggi a oggetti si evitano errori semantici dovuti ad errata interpretazione del tipo in casi di polimorfismo. Inoltre aumenta la dimensione del codice e il tempo necessario a scriverlo e può portare a problemi di errata interpretazione degli acronimi dei tipi per le classi e i tipi definiti dall’utente. Attualmente vi sono dei dibattiti a riguardo.
Nel naming convention Microsoft tutti i tipi e membri pubblici devono essere Pascal Cased es.
class MyClass
public string ClassName
public string GetClassName ()
I parametri devono essere Camel Cased es:
public void LoadData (int objectId)…
Gli acronimi sono gestiti All Cased se di 2 caratteri, e Pascal Cased altrimenti. Es
Public int ID; ublic string Html;
Si devono usare i nomi al plurale per metodi e proprietà che ritornano collections o array. Es:
public MemberCollection GetMembers ();
pubblic Member GetMember ();
Si deve utilizzare il prefisso “Is”, “Has”, “Can” nel caso di metodi che restituiscono un boolean. Es
public boolean IsMemberOfTeam ();
public boolean HasRows ();
public boolean CanWrite ();
Non si deve usare prefissi per i nomi di classi. Es:
public class CPersona //errato
public class Persona //corretto
Si deve utilizzare un nome composto per le classi derivate, aggiungendo come suffisso il nome, o parte del nome, della classe base. Es:
public class PersoneCollection:
CollectionBase …
public class CustomEventArgs : EventArgs …
public class CustomException : Exception …
La scelta di un opportuna convenzione sui nomi aiuta notevolmente il manutentore che deve comprendere il codice al fine di poterlo modificare. Non esistono ancora convenzioni accettate universalmente. È possibile, fissata una convenzione, realizzare strumenti di refactioring in grado di: valutare la conformità e modificare il codice in modo da renderlo conforme.
QUALITA’ E METRICHE SOFTWARE (parte 4)
La qualità del software può essere definita come la conformità ai requisiti funzionali, agli standard di sviluppo e a caratteristiche implicite. I requisiti sono il fondamento su cui misurare la qualità. Gli standard di qualità definiscono i criteri da seguire nello sviluppo software. Esistono requisiti impliciti la cui assenza compromette la qualità del software. Vi sono alcune problematiche in quanto le specifiche software sono in genere incomplete e spesso inconsistenti, alcuni requisiti di qualità sono difficili da specificare in maniera non ambigua, può esistere contrapposizione fra i requisiti di qualità attesi dal cliente e quelli dello sviluppatore ed, infine alcuni requisiti di qualità sono difficili da valutare direttamente. Per quest’ultima problematica noi non possiamo valutare la qualità del software in assoluto, ma solo alcune sue manifestazioni. Ciò vuol dire che per la valutazione del software si ricorre alla valutazioni di attributi che hanno una certa relazione con la qualità. Ad esempio non possiamo misurare l’usabilità e la manutenibilità in assoluto, ma ci si deve riferire ad altri attributi misurabili correlati ad essi. Quindi si ha bisogno di modelli di qualità condivisi. La qualità di un prodotto software si caratterizza attraverso un insieme finito e definito di attributi ragionevolmente esaustivi e privi di reciproche sovrapposizioni. Il modello di qualità software è un insieme di attributi del software che fornisce uno schema di riferimento che va adeguato e tarato per la rappresentazione dei requisiti di qualità desiderati dal committente o posseduti dal software. I modelli di qualità del software pubblicati in letteratura sono tipicamente gerarchici, a n livelli. Il primo livello descrive un insieme di caratteristiche (dette proprietà) che rappresentano la qualità del software secondo diversi punti di vista. Le proprietà sono precisate attraverso degli attributi misurabili. Il grado di possesso che il software ha di questi attributi può essere valutato su una scala di riferimento, facendo ricorso ad opportune metriche ed a meccanismi di rating. I primi modelli di qualità del software sono stati sviluppati da McCall e da Boehm. Essi hanno un’architettura a più livelli. Ad esempio McCall prevede:
Il settore della revisione del prodotto, che interessa tutto ciò che emerge quando si modifica il software, i fattori relativi sono: manutenibilità, flessibilità e testabilità. Infine il settore della transizione del prodotto emerge quando si cambia piattaforma, e si compone dei seguenti attributi: portabilità, ricusabilità e interoperabilità.
La correttezza è definibile come il grado di adesione alle sue specifiche e agli standardi definiti sia nel processo produttivo che nel suo dominio di applicazione. Essa è in funzione della tracciabilità, il grado di reperibilità delle varie specifiche richieste dal software all’interno del codice; coerenza, si riferiscono all’uniformità delle tecniche utilizzate e delle notazioni adottate nei diversi componenti software, dai requisiti al codice; e completezza, la capacità del software di soddisfare tutti i requisiti per cui è stato sviluppato.
L’affidabilità è la capacità del software di eseguire le sue funzioni senza insucessi in uno specifiche periodo di tempo. Esso è in funzione della tolleranza all’errore, l’affidabilità dei risultati in presenza di anomalie; all’accuratezza, la precisione nei calcoli; e alla semplicità, se il software implementa le sue funzioni in maniera chiara e comprensibile.
L’efficienza è il livello di utilizzo di risorse da parte del software. Essa è in funzione dell’efficienza di esecuzione, ovvero il tempo impiegato per svolgere il compito richiesto; e dell’efficienza di memorizzazione, relativo alla quantità di spazio occupato in memoria dai dati.
L’integrità è il livello di capacità del software di operare senza insuccessi dovuti ad accessi non autorizzati a codice e/o dati in uno specificato periodo di tempo. Essa è funzione del controllo accessi, quegli attributi del software che si riferiscono al controllo degli accessi ai dati ed al software; e dalla revisione accessi ovvero quegli attributi del software che consentono la revisione degli accessi ai dati ed al software.
L’usabilità è lo sforzo necessario per usare il software. Essa è in funzione dell’operabilità, ovvero quegli attributi del software che si riferiscono alla semplicità delle operazioni e delle procedure necessarie al controllo dall’attivazione ed esecuzione del software; dell’addestramento quegli attributi che consentono la familiarizzazione; e della comunicabilità sono quegli attributi che supportano una agevole assimilazione degli inputs e degli outputs utili.
La manutenibilità è definita come lo sforzo necessario per trovare e correggere un errore all’interno del codice dopo il rilascio in esercizio al cliente. Essa è in funzione della coerenza, semplicità, concisione, la quantità di codice necessaria per adempiere ad una certa funzione; modularità, il grado di indipendenza dei vari moduli operanti all’interno del software; e l’auto documentazione ovvero la capacità del codice di spiegare le funzioni implementate.
La portabilità è la capacità di adattamento del software ad operare su nuovi ambienti di lavoro. Essa è funzione della modularità, auto documentazione, indipendenza dalla macchina (l’indipendenza del SW dalla piattaforma HW), indipendenza del software (indipendenza del SW dall’ambiente SW in cui opera).
La testabilità è la facilità con cui è possibile effettuare il testing sull’applicazione. Essa è funzione della semplicità, modularità, strumentazione (è la facilità con cui è possibile monitorare il funzionamento del software e quindi verificarne possibili errori), e dell’auto documentazione.
McCall fornì un quadro con un numero incredibile di metriche, associando ad ogni fattore di qualità una valutazione data da una funzione lineare delle metriche interessate:
F=a0+a1m1+…+ akmk
dove gli ai esprimono la rilevanza di ciascuna metrica ai fini della valutazione del fattore e ogni mi è normalizzato sull’intervallo [0,1].
Esistono sinergie e conflitti fra diversi fattori di qualità, da tenere in considerazione in sede di definizione dei requisiti di qualità e di sviluppo di un sistema software.
Un altro modello è quello di Boehm dove abbiamo 5 livelli, con 15 sottocaratteristiche misurabilie nel 4° livello.
I limiti dei modelli di McCall e Boehm sono che le caratteristiche sono in genere proprietà astratte misurabili solo attraverso indicatori e metriche. Non sempre l’andamento di queste grandezze è in correlazione perfettamente lineare con le caratteristiche che devono stimare. È difficile che le caratteristiche e sottocaratteristiche siano sempre prive di sovrapposizioni e manca in ogni caso il legame espliciti tra il modello qualitativo e “come” fare poi del buon software.
La difficoltà principale nell’adozione di un modello di qualità consiste nel riuscire a dare un valore a tutti gli attributi di qualità proposti, in funzione di attributi di qualità che siano direttamente misurabili. Affinché un modello possa essere affidabile e possa essere accettato, è auspicabile che esso provenga da un enti di standardizzazione.
Al momento lo standard utilizzato è quello ISO/IEC 2126, esso è suddiviso in 4 parti:
A determinare la qualità di un SW concorrono 3 punti di vista:
I tre punti di vista sulla qualità di influenzano a vicenda. Non può esservi qualità percepita positivamente dall’utente senza che vi sia una buona qualità intrinseca del codice e buone prestazioni.
La qualità del processo contribuisce a migliorare la qualità del prodotto, influenzando direttamente i valori degli attributi interni di qualità. Gli attributi di qualità interni influenzano insiemi di attributi di qualità esterni. Gli attributi di qualità esterni influenzano gli attributi di qualità in uso. In definitiva, migliorare la qualità del processo di sviluppo software e del prodotto software fa migliorare la qualità percepita dall’utilizzatore.
La qualità del software si determina progressivamente attraverso una sequenza logica di azioni, lungo il ciclo di vita.
Per descrivere le tre viste sulla qualità, lo standard propone due modelli, uno per la qualità interna ed esterna, ed uno per la qualità in uso. Entrambi i modelli definiscono la qualità in termini di un insieme di caratteristiche di primo livello a cui sono associati insiemi di sottocaratteristiche di secondo livello che meglio descrivono ciascuna caratteristica.
Funzionalità è la capacità del prodotto software di fornire funzioni che soddisfano esigenze stabilite ed implicite quando il software è usato sotto condizioni specifiche. Essa si compone: 1) appropriatezza, la capacità del prodotto software di fornire un appropriato insieme di funzioni all’utente per i compiti e gli obiettivi specificati; 2) accuratezza, la capacità del prodotto software di fornire i giusti o concordati risultati o effetti, con la precisione richiesta; 3) sicurezza, la capacità del prodotto software di proteggere informazioni e dati in modo che persone o sistemi non autorizzati non possano leggere o modificarli e che a persone o sistemi autorizzati non sia negato l’accesso ad essi; 4) conformità, la capacità del prodotto software di aderire a standard, convenzioni etc… relativamente alla funzionalità.
Affidabilità è la capacita del prodotto software di mantenere uno specifico livello di prestazioni quando usato sotto condizioni specificato. Essa si compone: 1) maturità, capacità di evitare malfunzionamenti; 2) tolleranza all’errore, capacità di mantenere uno specificato livello di prestazioni in caso di anomalie; 3) recuperabilità, capacità di ristabilire no specifico livello di prestazioni e di ripristinare i dati in caso di malfunzionamenti; 4) conformità, in relazione all’affidabilità.
Usabilità è la capacità del prodotto software di essere capito, appreso, usato e gradito all’utente. Esso si compone: 1) comprensibilità; 2) apprendibiltà, impararlo facilmente; 3) operabilità, possibilità di operare con esso e controllarlo; 4) attrattività; 5) conformità, relativamente all’usabilità.
Efficienza è la capacità del prodotto software di fornire appropriate prestazioni relativamente ala qualità delle risorse usate. Esso si compone: 1) comportamento rispetto al tempo (velocità); 2) utilizzo di risorse; 3) conformità relativamente all’efficienza.
Manutenibilità è la capacità del prodotto software di essere modificato. Le modifiche possono includere correzioni, miglioramenti, etc… Esso di compone: 1) analizzabilità capacità di essere diagnosticato per vari fini come l’identificazione di parti da modificare; 2) modificabilità capacità di permettere l’implementazione di una specifica modifica; 3) stabilità capacità di evitare effetti inaspettati derivanti da modifiche; 4) testabilità; 5) conformità relativamente alla manutenibilità.
Portabilità è la capacità del prodotto software di essere trasferito da un ambiente ad un altro. Esso si compone: 1) adattabilità; 2) installabilità; 3) coesistenza con altri SW; 4) sostituibilità usato al posto di un sw che ha gli stessi scopi; 5) conformità relativamente alla portabilità.
Il modello di qualità per la qualità in uso è rappresentata da 4 caratteristiche, che rappresentano il punto di vista dell’utente sulla qualità del software. Rappresenta la capacità del software di supportare specifici utenti a raggiungere determinati obiettivi, con efficacia (capacità di supportare un utente nel raggiungere i suoi obiettivi), produttività (capacità di supportare un utente nello spendere l’appropriata quantità di risorse in relazione all’efficacia dei risultati da raggiungere), soddisfazione e sicurezza personale (capacità di raggiungere accettabili livelli di rischio di danni), in determinati contesti d’uso.
Le metriche nello Standard ISO servono per valutare le caratteristiche di qualità. Lo standard fornisce 3 insiemi di metriche, rispettivamente metriche interne, applicabili ad un prodotto software non eseguibile; metriche esterne, misurano aspetti del software relativi al suo comportamento, osservabili testando il software in esecuzione; metrica di qualità in uso, misura fino a che punto un prodotto soddisfa i bisogni utente per raggiungere specifici scopi con efficacia, produttività, sicurezza e soddisfazione.
METRICHE SOFTWARE (parte 5)
La misurazione del software ha lo scopo di assegnare un valore ad un attributo caratterizzante un processo o prodotto software. Consente una comparazione obiettiva tra prodotti/processi e rende misurabili processi, risorse, prodotti rilevanti della Ingegneria del Sw. L’uso sistematico della misurazione del software non è ancora una pratica comune, infatti vi sono ancora pochi standard. Una metrica è una misura quantitativa del grado di possesso di uno specifico attributo da parte di un sistema, un componente, o un processo. La definizione di metrica include procedure e modalità di misurazione. La misurazione è il processo mediante il quale si assegnano numeri o simboli ad attributi di entità del mondo reale in modo tale da descriverle secondo regole chiaramente definite. L’entità rappresenta l’oggetto che si vuole sottoporre a misurazione. L’attributo dell’entità è l’aspetto di tale oggetto che interessa descrivere o rappresentare. Infine, l’insieme dei simboli o valori che si possono assegnare all’attributo costituisce la “scala dei valori”. Una misura è una funzione che mappa un insieme di oggetti in un altro insieme di oggetti. In conclusione la misurazione è l’attività generale, mentre una misura è l’effettiva assegnazione di valori. Spesso metrica e misura sono usati come sinonimi. Una misura è un’assegnazione di un numero ad un entità al fine di caratterizzarne uno specifico attributo. Una metrica caratterizza con valori attributi semplici, mentre una misura è una funzione di metriche che può essere usata per valutare o predire attributi più complessi.
Abbiamo diversi tipi di attributi:
Abbiamo due tipi di misure la misura diretta è la misura di un attributo che non dipende da quella di altri attributi, e la misura indiretta è la misura di un attributo che dipende da quella di almeno un altro attributo. Per quanto riguarda le metriche abbiamo quelle dirette ( ovvero tutto ciò per cui possiamo fare misure dirette) e quelle indirette ( tutto ciò per cui dobbiamo fare misure indirette – f(metriche)). Ad attributi semplici sono associate le metriche e ad attributi complessi sono associate le misure. Le entità di interesse per la misurazione sono:
Una scala di valori è l’insieme dei numeri/simboli da assegnare ad un attributo di un’entità e delle relazioni tra tali numeri/simboli. Esistono 5 tipologie di scala:
Le metriche software per essere efficaci devono osservare le seguenti caratteristiche: semplici e calcolabili; convincenti a livello empirico ed intuitivo; coerenti o obiettive; coerenti nell’uso di unità e dimensioni; indipendenti dal linguaggio di programmazione; devono essere un meccanismo efficace per un feedback sulla qualità. La metrica di un prodotto viene utilizzata solo se è intuitiva e facile da calcolare.
Esiste una tassonomia di metriche software relative al prodotto:
I dati relativi attraverso il confine e risiedono internamente all’esterno del confine dell’applicazione e sono mantenuti da un’altra applicazione. Un EIF è un ILF di un’altra applicazione.
Esistono diverse teorie per calcolare gli FP in funzione del conteggio degli elementi prima descritti. Il livello di complessità di ciascun elemento individuato per ognuna delle 5 precedenti categorie viene classificato in: basso (semplice), medio, alto (complesso). Ogni livello è associato un peso che varia da 3 a 15. Una prima stima dei FP è detta UFC (Unadjusted Fuction Point Cout), esso è corretto da un fattore moltiplicativo TFC compreso da 0.65 e 1.35 à (delivered function point) DFP = UFC * TFC
Il TFC ( fattore di complessità totale) è calcolato sulla base di 14 fattori di complessità valutati su una scala da 0 a 5 à TFC= 0.65 +0.01 * FCi
FP vengono utilizzati per stimare la dimensione finale del codice, per stimare lo sforzo (ore/uomo) di sviluppo, per valutare la completezza del testing. I vantaggi delle FP è che sono: indipendenti dal linguaggio; ottime indicazioni per applicazioni di elaborazione dati; basati su quei dati che hanno la maggior probabilità di essere noti all’inizio di un progetto. Gli svantaggi sono individuati dalla soggettività nell’assegnazione dei pesi, dai dati sul dominio delle informazioni possono essere difficili da reperire, dal fatto che nessuna valutazione della complessità dell’algoritmo, ed infine gli FP non hanno un diretto significato fisico.
Per quanto riguarda le metriche per la qualità delle specifiche sono stati proposti alcuni attributi di qualità dello SRS (completezza, coerenza, tracciabilità, etc.). Esse forniscono un metodo per valutare tali attributi come à nR requisiti, di cui nf funzionali e nnf non funzionali: nR = nf + nnf n
Una metrica proposta pe
Per quanto riguarda le metriche per il Design Object Oriented, esse hanno le seguenti peculiarità: localizzazione (il modo con cui le informazioni e le loro elaborazioni sono confinate in un sistema), incapsulamento, infomation hiding, inheritance (varie forme di influenza sul progetto) e abstraction.
Invece per quanto riguarda le metriche orientate alle classi essi si dividono in
Le metriche di design a livello dei componenti sono delle metriche che valutano caratteristiche interne dei moduli, quali: la coesione si basano sull’analisi delle funzioni e dei dati elaborati da un modulo, cercando di stabilire se il modulo svolge funzioni che operano sugli stessi dati oppure su dati disgiunti; accoppiamento tiene conto del flusso di dati e di controllo fra i moduli, dell’accoppiamento a variabili globali, e dell’accoppiamento ambientale fra moduli; e metriche di complessità che misurano in vario modo la complessità del CFG.
La metrica di McCabe è una metrica strutturale relativa al controllo flow di un programma G, è detta anche numero ciclomatico V(G). Essa rappresenta la complessità logica del programma e quindi lo sforzo per realizzarlo. Per programmi strutturati V(G) è uguale al numero di predicati (if-then-else, repeat, etc) aumentato di uno à V(G)= P +1
Rappresentando un programma con il controllo flow grapf, il numero ciclomatico V(G) può essere calcolato nel seguente modo à V(G) = E – N +2P dove E è il numero di archi, N il numero di nodi e P il numero di componenti connesse.
Un altro modo per calcolare V(G) è à V(G)= numero di aree chiuse del CFG +1
V(G) rappresenta il n° di cammini linearmente indipendenti del Controllo Flow Grapf nella teoria dei grafi V(G) rappresenta il numero di circuiti linearmente indipendenti che congiungono il nodo iniziale con quello finale. Un cammino si dice indipendente se introduce almeno un nuovo insieme di istruzioni o una nuova decisione, rispetto ad un altro. In un CFG un cammino è indipendente se attraversa almeno un arco non ancora percorso dagli altri già considerati. L’insieme di tutti i cammini linearmente indipendenti di un programma formano i cammini di base. Dato un programma, l’insieme dei cammini di base non è unico.
Lines of Code (LOC) è una metrica dimensionale. Misura la lunghezza di un programma con il numero di linee di codice. Esistono diverse possibili definizioni: linea di codice incluso i commenti, tutte le linee di codice senza i commenti, oppure solo istruzioni eseguibili e quelle dichiarative. La definizione più accettate è quella che una linea di codice è ogni linea di testo di un programma che non sia bianca. I commenti sono una parte importante del processo di sviluppo di un programma, e quindi ignorarle può essere deleterio. Alcune misure derivate dalle LOC come la densità di commento = CLOC(comment LOC)/LOC. I vantaggi sono che risultano molto semplici da calcolare automaticamente e molto intuitive. Gli svantaggi sono che essi sono dipendenti dal linguaggio di programmazione adottato, dallo stile di programmazione e diventano una misura inconsistente in presenza di generatori automatici di codice.
Le LOC sono utilizzate per misurare indirettamente altri attributi come: la probabilità di presenza di errori nel programma, il tempo per produrlo, la produttività di un programmatore, etc. . Nelle LOC bisognerebbe contare anche tutte le linee di codice non direttamente legate ai prodotti consegnati, ad esempio: codice per il testing e per i prototipi.
Metriche di Halstead su basate:
n1 = numero di operatori distinti che compaiono in un programma.
n2 = numero di operatori distinti che compaiono in un programma.
N1 = numero totale delle occorrenze di operatori.
N2 = numero totale delle occorrenze di operatori.
Si definiscono:
Il concetto di Volume è legato al concetto di informazione del programma e dovrebbe dipendere unicamente dall’algoritmo scelto, non dall’espressività del linguaggio di programmazione. Il volume è usato come una metrica dimensionale del software.
Altre metriche sono state derivate da Halstead: Volume potenziale
V*= (2+n2)log2(2+n2)
N1=n1= (nome funzione e parentesi); N2= n2 (tutti operandi distinti).
Livello di implementazione L= V*/V – maggiore è il volume minore è L; un algoritmo implementato con un linguaggio di programmazione con basso livello espressivo richiede un maggiore volume.
Difficoltà di implementare un algoritmo in un certo linguaggio di programmazione D=1/L.
Sforzo richiesto per comprendere una implementazione piuttosto che quello per produrlaà E = V*D = V/L = V2/V*
I meriti importanti dalle metriche Halstead sono quelli di aver dato modo di derivare una misura software a partire dalla teoria. I difetti però sono che essa utilizza delle misure “generali” e non orientate ad un particolare compito. Metriche basata sul codice e ideata quando i fondamenti dell’ingegneria del software non erano ancora pienamente apprezzati. Regole di conteggio non precisamente definite.
Le metriche per la manutenzione si occupano di prevedere la complessità del processo di manutenzione. Esse dipendono dal processo di manutenzione che si va a istanziare. Ad esempio l’Indice di maturità del Software à
SMI = [MT –(Fa+Fc+Fd)]/MT e
Dove MT è il numero di moduli nella versione corrente; Fa numero di moduli aggiunti; Fc numero di moduli modificati; Fd numero di moduli eliminati.
Al tendere di SMI a 1 il prodotto tende a stabilizzarsi. Il tempo medio per produrre una nuova versione del prodotto è correlato a SMI.
Per proporre delle metriche valide, bisogna trovare le relazioni tra entità e metriche. L’applicazione di un processo Goal – Question – Metric o GQM permette di effettuare un programma di raccolta dati. Occorre stabilire i Goal che si intendono perseguire con il programma di raccolta dati. Un goal si definisce individuando l’oggetto dello studio, le motivazioni per la misurazione, un modello di qualità di riferimento, un punto di vista per l’osservazione, l’ambiente a cui l’oggetto appartiene. Occorre poi stabilire le domande che permettono di valutare se e fino a che punto gli obiettivi vengono raggiunti. Infine occorre trovare delle metriche che consentono di dare risposta alle domande.
Si costruisce una matrice Goal-Question in cui per ogni posizione GiQj= 1 vuol dire che quella question soddisfa quel determinato goal. Si costruisce una matrice Question-Metrics in cui ad ogni posizione QiMj= 1 vuol dire che la i-esima metrica contribuisce a rispondere alla j-esima question. Si calcolano i valori delle metriche Mj. Si calcolano, indipendentemente i valori associati alle questions e da questi i valori associati ai goals.
I valori per le metriche QM e GQ sono ottenuti da questionari compilati dagli esperti dei processi aziendali e dai responsabili della qualità che utilizzano un insieme discreto di valori possibili. La difficoltà nell’esecuzione di un GQM sta nella ricerca di matrici che portino a risultati soddisfacenti. I valori possono essere aggiustati a seguito di osservazioni fatte a posteriori che vadano a correggere delle dipendenze tra question e goal che non siano soddisfacenti.
VERIFICA E VALIDAZIONE DEL SOFTWARE (parte 5bis)
La verifica e validazione punta a mostrare che il software è conforme alle sue specifiche (verifica) e soddisfa le aspettative del cliente (Validazione). Abbiamo due approcci complementari alla verifica un approccio sperimentale e uno analitico. Gli approcci per le attività di verifica e validazione sono: 1) analisi dinamica, processo di valutazione di un sistema software o di un suo componente basato sulla osservazione del suo comportamento in esecuzione (testing); 2) analisi statica è il processo di valutazione di un sistema o di un suo componente basato sulla sua forma, struttura, contenuto, senza che esso sia eseguito; 3) analisi formale uso di rigorose tecniche matematiche per l’analisi di algoritmi (usata per la verifica del codice e dei requisiti).
Il software testing è il processo di osservazione di un sistema sotto determinate condizioni. Un programma è esercitato da un caso di test. Un test è formato da un insieme di casi di test. L’esercitazione del test consiste nell’esecuzione del programma per tutti i casi di test. Il testing può avere diversi obiettivi, quali il dimostrare al cliente e allo sviluppatore che il software soddisfa i suoi requisiti e scoprire gli errori o difetti del software. Diamo alcune definizioni: un errore è un incomprensione umana nel tentativo di comprendere o risolvere un problema, o nell’uso di strumenti; un difetto è la manifestazione nel software di un errore umano, e causa del fallimento del sistema nell’eseguire la funzione richiesta; il malfunzionamento è l’incapacità del software di comportarsi secondo le aspettative, esso ha una natura dinamica in quanto può essere osservato solo mediante esecuzione.
Il test dei difetti ha lo scopo di scoprire i difetti di un programma osservando i malfunzionamenti. Un test dei difetti ha successo se porta il programma a comportarsi in maniera scorretta. Il testing può solo dimostrare la presenza dei difetti, ma non la loro assenza. Vi sono alcune problematiche inerenti a ciò, in quanto la correttezza di un programma è un problema indicibile. Non vi è alcuna garanzia che se alla n-esima prova un modulo od un sistema abbia risposto correttamente, altrettanto possa fare alla (n+1)-esima. Ancora vi è la impossibilità di produrre tutte le possibili configurazioni di valori in input in corrispondenza di tutti i possibili stati interni di un sistema software. In altri campo il testing è semplificato dall’esistenza di proprietà di continuità (se un ponte supporta 1000Kg allora supporterà qualsiasi peso inferiore). Nel campo del software si ha a che fare con sistemi discreti, per i quali piccole variazioni nei valori d’ingresso possono portare a risultati scorretti, ci vorrebbe solo un testing esaustivo che è un’idealità.
Un processo di testing non può dimostrare la correttezza, ma può consentire di acquistare fiducia nel software mostrando che esso è pronto per l’uso operativo.
Un testing deve essere: efficace(scoprire quanti più difetti possibilit), efficiente( trovare quanti più difetti utilizzando pochi casi di test) e ripetibile (non deve influenzare l’ambiente operativo).
Abbiamo diversi livelli di testing:
Testing dei requisiti non funzionali
Dopo che il sistema è stato completamente integrato, è possibile testarne le proprietà emergenti, come le prestazioni ed affidabilità. Il test di prestazione in genere riguardano il carico e si pianificano test in cui il carico viene incrementato progressivamente finché le prestazioni diventano inaccettabili. Il carico deve essere progettato in modo da rispecchiare le normali condizioni di utilizzo. Nello stress testing si sollecita il sistema con un carico superiore a quello massimo previsto, in questo modo si può testare il comportamento in caso di fallimento. L’eventuale fallimento non dovrebbe produrre effetti catastrofici. Lo stress testing è particolarmente rilevante per sistemi distribuiti che possono mostrare severe degradazioni delle prestazioni quando la rete è sommersa di richieste. Esistono poi altri tipo di testing come quello di compatibilità, di accessibilità, di sicurezza, di usabilità.
I Problemi chiave dei Processi di testing sono:
Le tecniche di Testing sono principalmente 2, ovvero il testing funzionale (black Box) e il testing strutturale (white Box). Il Black Box testing richiede l’analisi degli output generati dal sistema in risposta ad input definiti conoscendo i requisiti di sistema. Si basa sul principio della verificabilità dei requisiti il quale afferma che i requisiti dovrebbero essere testabili. Infatti il testing basato sui requisiti è una tecnica di convalida dove vengono progettati vari test per ogni requisito. Nel testing delle partizioni, i dati di input ed output possono essere in genere suddivisi in classi dove tutti i membri di una stessa classe sono in qualche modo correlati. Ognuna delle classi costituisce una classe di equivalenza ed il programma si comporterà nello stesso modo per ciascun membro della classe. I casi di test dovrebbero essere scelti all’interno di ciascuna partizione. Le partizioni sono identificate usando le specifiche del programma o altra documentazione. Una possibile suddivisione è quella in cui la classe di equivalenza rappresenta un insieme di stati validi o non validi per una condizione sulle variabili d’ingresso. Se la condizione sulle variabili d’ingresso specifica:
Per selezionare i casi di test dalle classi di equivalenza occorre che ogni classe di equivalenza deve essere coperta da almeno un caso di test, quindi un caso di test per ogni classe non valida e ciascun caso di test per le classi valide deve comprendere il maggior numero di classi valide ancora scoperte. Il problema è che a volte è necessario tenere conto delle pre-condizioni e post-condizioni di una funzione nella scelta delle classi di equivalenza. Poi, l’appartenenza ad una classe di equivalenza può dipendere dallo stato dell’applicazione. Vi è poi il testing basato su tabelle di decisione, le quali sono uno strumento per la specifica black-box di componenti in cui a diverse combinazioni degli ingressi corrispondono uscite/azioni diverse, le varie combinazioni possono essere rappresentate come espressioni booleane mutuamente esclusive e il risultato non deve dipendere da precedenti input o output. Per quanto riguarda la costruzione della Tabella di Decisione, le colonne rappresentano le combinazioni degli input a cui corrispondono le diverse azioni; le righe riportano i valori delle variabili di input (Sezioni Condizioni) e le azioni eseguibili (Sezione Azioni) e ogni distinta combinazione degli input viene chiamata Variante.
Nella tabella l’operatore logico fra le condizioni è di And. Per N condizioni abbiamo 2n varianti dette varianti implicite (non tutte significative). Il numero di varianti esplicite (significative) è in genere minore.
I criteri possibili di copertura della tabella sono: quello di coprire tutte le varianti esplicite o utilizzare un test case per ogni variante.
Il testing basato su grafi causa-effetto sono un modo alternativo per rappresentare le relazioni fra condizioni ed azioni di una tabella di decisione. Il grafo prevede un nodo per ogni causa e uno per ogni effetto. Alcuni effetti derivano da una singola causa, mentre altri effetti derivano da combinazioni fra cause esprimibili mediante espressioni booleane. Il vantaggio per tali grafi è rappresentata dalla loro intuitività, dalla convenienza nel sviluppare il grafo se non si ha a disposizione di una tabella di decisione, è possibile derivare una funzione booleana dal grafo causa-effetto, ed infine può essere usata per la verifica del comportamento del software. Lo svantaggio è che al crescere della complessità delle specifiche, il grafo può divenire ingestibile. La copertura di tutte le possibili combinazioni d’ingresso può diventare impraticabile, al crescere delle combinazioni. Una semplificazione può essere quella di partire dagli effetti e percorrere il grafo all’indietro cercando alcune combinazioni degli ingressi che rendono vero l’effetto considerato.
Macchine a Stati e state-base Testing
Una macchina a stati è un modello del comportamento dinamico di un sistema, indipendente dalla sua implementazione. Si basa sui seguenti elementi fondamentali quali lo stato, evento, azione, transizione e guardia (condizione booleana che si deve verificare affichè la transizione scatti).
Abbiamo diversi tipi di macchine a stati, abbiamo l’automa a stati finiti (FSM), la macchina di Mealy e di Moore, la statechart. Lo state transition diagram è una rappresentazione in forma di grafo di una Macchina a Stati. La state transitino table è una rappresentazione gabellare della macchina a stati. Le macchine a stati sono tipicamente modelli incompleti, in quanto solo stati, eventi e transizioni più importanti vengono rappresentate. Può essere deterministico o non deterministico (si innescano più transizioni), può avere vari stati finali, può avere vuoti, e può essere concorrente ovvero la macchina può essere in vari stati contemporaneamente. Il ruolo delle macchine a stati nel software testing supportano l’esecuzione di attività di model testing, dove un modello eseguibile del sistema viene eseguito o simulato con sequenze di eventi che costituiscono i casi di test, ancor prima dell’implementazione. Essi consentono di eseguire il testing dell’implementazione del sistema rispetto ad una sua specifica e supportano la generazione automatica di test cases a livello di codice. Per eseguire sia il Model Testing occorre usare delle Checklist per verificare che la macchina a stati sia completa e consistente. Occorre quindi che vi sia: uno stato iniziale, uno stato finale, non devono esserci stati equivalenti, ogni stato deve essere raggiungibile dallo stato iniziale, deve esserci almeno uno stato finale raggiungibile da ogni stato, ogni evento ed azione devono apparire in almeno una transizione. Un difetto sul controllo consente a sequenze scorrete di eventi di essere accettate, o produce sequenze scorrette di azioni di output. Nell’eseguire il testing basato su macchine a stati, occorre cercare di verificare la presenza dei seguenti tipi di difetto su controllo: transizioni mancanti, transizionio scorrette, eventi mancanti o scorretti, azioni mancanti o scorrette, uno strato extra mancante o corrotto, uno sneak path (evento accettato quando non dovrebbe), una trap door (accetta eventi non previsti). Le strategie usate per il progetto dei test nello state-based tesgint si basano sugli stessi concetti di copertura del white-box. ovvero test case = sequenza di eventi in input; tramite tale sequenza si pone l’obbiettivo di coprire tutti gli eventi (all-events coverete), tutti gli stati (all-states coverete) e tutte le azioni (all-actions coverete). Però tali criteri non definiscono una adeguata copertura in quanto si può riuscire ad esercitare tutti gli eventi, ma non visitare tutti gli stati etc… Vi sono, poi, altri criteri di copertura quale all-transitions (tutte le transizioni), all n-transition sequences (ogni trasizione deve essere esercitata almeno una volta da una sequenza di n eventi), all round-trip paths (ogni transizione che parte e termina nello stesso stato viene esercitata), exhaustive (ogni cammino sulla macchina a stati è esercitato almeno una volta). Lo state Based-Testing nasce per il testing di circuiti, poi fu adottato per il testing software, ed oggi è tipicamente usato per testare le unita di software OO e per le GUI e i Sistemi.
Il testing strutturale (white-box) è un testing strutturale, il quale utilizza la struttura interna del programma per ricavare i dati di test. Tramite tale testing si possono formulare criteri di copertura più precisi di quelli formulabili con il black-boc. I criteri di copertura sono fondati sull’adozione di metodi di copertura degli oggetti che compongono la struttura dei programmi, quali istruzioni, strutture di controllo, flusso di controllo, etc… i criteri di copertura si dividono in criteri di selezione (copertura dei comandi, decisioni, condizioni, decisioni e condizioni, cammini, cammini indipendenti) e di adeguatezza (n.ro di comandi eseguiti/n.ro di comandi percorribili; n.ro di archi percossi/n.ro di archi percorribili; n.ro cammini percorsi/n.ro cammini percorribili; n.ro cammini indipendenti percorsi/n.ro ciclomatico). Un modello di rappresentazione dei programmi è il Controllo-Flow Graph in cui il flusso di controllo di un programma P è dato da:
CFG(P)=<N,AC,nI,nF>
Dove <N,AC> è un grafo diretto con archi etichettati, {nI,nF} nodo iniziale e nodo finale, AC rappresenta la relazione flusso di controllo, N che può essere Ns o Np sono insiemi disgiunti di nodi di istruzione e nodi di predicato.
Il criterio di copertura dei comandi richiede che ogni nodo del CFG venga eseguito almeno una volta durante il testing, è un criteri di copertura debole, che non assicura la copertura dia del ramo true che false di una decision. Il criterio di copertura delle decisioni richiede che ciascun arco CFG sia attraversato almeno una volta, quindi ogni decisone è testata sia vera che falsa, il limite a tale criterio è legato alle decisioni in cui più condizioni sono valutate (con gli operatori di AND e di OR). Il criterio di copertura delle decisioni assicura che ciascuna condizione nei nodi decisione di un CFG deve essere valutata sia per valori true che false. Il criterio di copertura delle condizioni e delle decisione combina la copertura delle condizioni in modo da coprire anche tutte le decisioni. Prima di parlare dei criteri di copertura dei cammini occorre definire cosa sono i cammini linearmente indipendenti. Un cammino è un’esecuzione del modulo dal nodo iniziale del Cfg al nodo finale. Un cammino si dice indipendente se introduce almeno un nuovo insieme di istruzioni o una nuova condizione, in un Cfg un cammino è indipendente se attraversa almeno un arco non ancora percorso. L’insieme di tutti i cammini linearmente indipendenti di un programma forma i cammini di base; tutti gli altri cammini sono generati da una combinazione lineare di quelli di base. Dato un programma, l’insieme dei cammini di base non è unico. Il numero dei cammini linearmente indipendenti di un programma è pari al numero ciclomatico di McCabe ( V(G)=E-N+2=P+1=n.ro regioni chiuse in G +1). I test case esercitanti i cammini di base garantiscono l’esecuzione di ciascuna istruzione almeno una volta. Su tale concetto si basano i criteri di copertura dei cammini e quello dei cammini indipendenti. Vi sono poi le relazioni tra i criteri di copertura. La copertura delle decisioni implica la copertura dei nodi, la copertura delle condizioni nono sempre implica la copertura delle decisioni, la copertura dei cammini lineramente indipentendi implica la copertura dei nodi e la copertura delle decisioni, la copertura dei cammini è un test ideale ed implica tutti gli altri. I principali problemi riguardante tale criterio sta nel fatto che possibile riconoscere automaticamente quale cammino linearmente indipendente viene coperto dall’esecuzione di un dato test case, mentre risulta indicibile il problema di trovare un test case c ha va a coprire un dato cammino in quanto alcuni cammini possono risultare non percorribili. Infine, la copertura dei cammini linearmente indipendenti non garantisce da errori dovuti, ad esempio, al numero di cicli eseguiti, per i quali sarebbe necessaria la copertura di tutti i cammini, che però rappresenta il testing esaustivo.
Testing di sistemi OO. Le caratteristiche OO introducono nuovi concetti sul testing ovvero, ‘astrazione sui dati, l’ereditarietà, polimorfismo, binding automatico, etc. , i quali impattano concetti ed attività del testing. Occorrono quindi nuovi livelli di test e una nuova infrastruttura. Infine tale impatto genera nuove tecniche di generazione di casi di test e criteri di terminazione che tengano conto di: stato, polimorfismo, ereditarieà, genericità, eccezioni.
Nuovi livelli di test. I livelli di test tradizionali non si adattano al caso di linguaggi OO, una possibile suddivisione è rappresentata da: basic unit testing (singole operazioni di una classe), unit testing, integration testing. Ogni componente base è formato da una classe= struttura di dati + insieme di operazioni; gli oggetti sono istanze di classi e la verifica del risultato del test non è legata solo all’output, ma anche allo stato. La opacità dello stato rende più difficile la costruzione di infrastruttura e oracoli. Per quanto riguarda le tecniche di generazione di test abbiamo, tra cui Test ed Ereditarietà, in cui l’ereditarietà è una relazione fondamentale tra classi e nelle relazioni di ereditarietà alcune operazioni restano invariate nella sotto-classe, altre sono ridefinite, altre aggiunte. Resta il dubbio se ci si può fidare delle proprietà ereditate. A tal proposito è necessario identificare le proprietà che si devono ri-testare, può essere necessario verificare la compatibilità di comportamento tra metodi omonimi in una relazione classe-sottoclasse, si deve far attenzione al riuso del test, e a test specifici, infine l’ereditarietà produce una nuova forma di integrazione/interazione. I moduli generici sono presenti nella maggior parte dei linguaggi OO. Le classi parametriche devono essere instanziate per poter essere testate. Servono classi fidate da utilizzare come parametri, infine non esistono tecniche o approcci maturi in letteratura. Per quanto riguarda il polimorfismo e il binding dinamico c’è da dire che un riferimento può denotare oggetti appartenenti a diverse classi di una gerarchia di ereditarietà, ovvero il tipo dinamico e il tipo statico dell’oggetto possono essere differenti. Il test strutturale può diventare non praticabile. Il problema sta nel definire la copertura in un’invocazione su un oggetto polimorfico, poi, nel creare test per coprire tutte le possibile chiamate di un metodo in presenta di binding dinamico, infine, occorre gestire i parametri polimorfici. Ulteriori problematiche è la gestione delle eccezioni. Le eccezioni modificano il flusso di controllo senza la presenza di un esplicito costrutto di tipo test and brach, rendendo inefficace il CFG per modellare il flusso di controllo. È necessario introdurre ulteriori metodi per generare casi di test e ulteriori metriche di copertura per valutare l’effettiva copertura di tutte le eccezioni, abbiamo due tipologie di coperture quella ottimale e quella minima. Infine, un ulteriore problema è dato dalla concorrenza, e quindi dal non determinismo, in tal caso i casi di test composti solo da valori di input/output sono poco significativi, quindi occorre utilizzare casi di test composti da valori di input/output e da una sequenza di eventi di sincronizzazione (occorre forzare uno schedulet). Per il testing OO abbiamo diversi approcci, tra cui quello di Berard che dice che occorre:
Tipicamente i difetti riscontrati in un codice OO riguardano: interazioni scorrette tra metodi di superclassi e subclassi; mancata nuova specifica di un metodo ereditato in una subclasse, in una profonda gerarchia di ereditarietà; la subclasse eredita metodi non appropriati dalle superclassi; fallimenti in chiamate polimorfiche di una subclasse la cui interfaccia è conforme alle interfacce delle superclassi; mancata o scorretta inizializzazione della superclasse nelle subclassi; scorretto aggiornamento di variabili di istanza di una superclasse all’interno delle subclassi; polimorfismo a “spaghetti che produce perdita del controllo; subclassi violano il modello di stato o l’invariante della superclasse; istanziazioni di classi generiche con un tipo di parametro non testato; relazioni di controllo inter-modulo scorrette, a causa della delocalizzazione di funzionalità in classi piccole e incapsulate. Tra i metodi per il testing OO vi è lo scenario- based test design che è un testing in cui ci si concentra su ciò che fa l’utente, piuttosto che su ciò che fa il software. Esso richiede che vengano catturati i task eseguibili dall’utente per poi usarli insieme ai loro scenari alternativi come casi di test, in pratica si vanno a coprire con i casi di test tutte le varianti di comportamento di una caso d’uso. Si può passare dai casi d’uso ai test case mediante:
Un ulteriore metodo per il testing OO è il testing della classe e della gerarchia di classi. Una completa copertura di una classe richiede di testare tutte le operazioni di un oggetto, settare ed interrogare tutti gli attributi di un oggetto, ed esercitare l’oggetto in tutti i possibili stati. L’ereditarietà rende più difficile la scelta dei casi di test degli oggetti giacchè le informazioni da testare non sono localizzate. L’ereditarietà non permette di risparmiare sul testing delle classi derivate che, dovranno essere ri-testate comunque. Il testing white-box di una classe deve tenere in conto di tutti i possibili stati e cambiamenti di stato degli oggetti di quella classe, mediante l’uso di state chart per rappresentare tale evoluzione, l’uso di vari criteri di copertura, come la copertura degli stati, delle transizioni, dei cammini tra stato iniziale e finale di un oggetto… non basta solo valutare gli output restituiti dai metodi ma anche lo stato dell’oggetto dopo la chiamata ma alcuni attributi potrebbero essere privati o protetti. Poi abbiamo, il testing di weather station in cui occorre definite test case per tutti i metodi: reportWeather, calibrate, test, startup e shutdown. Usando un modello a stati, bisogna identificare le transizioni di stato da testare e le sequenze di eventi cha causano tali transizioni. Infine, vi è l’integration testing in cui gli oggetti collaborano nella realizzazione di funzionalità complesse. Occorre testare tali collaborazioni. La generazione dei casi di test può essere effettuata a partire dai diagrammi di interazione UML. Si rende opportuno la costruzione di diagrammi di interazione anche dal codice e verificare la corrispondenza con le specifiche. I principali problemi di tale testing è causato dall’ereditarietà e dal polimorfismo e binding dinamico. I diagrammi di interazione indicano possibili sequenze di messaggi. Da tali diagrammi si possono generare i casi test che dovrebbero indicare i casi frequenti e quelli particolare. Poi occorre selezionare alcuni di essi o in maniera immediata e quindi generando un test per ogni diagramma di interazione, oppure mediante una selezione più accurata, ovvero per ogni diagramma individuare possibili alternative e per ogni alternativa selezionare un ulteriore insieme di casi di test. L’automazione del testing OO (OOT) ha delle problematiche quali: la scarsa controllabilità, osservabilità e testabilità del sistema, a causa del numero elevato di piccoli oggetti incapsulati; difficoltà nell’analizzare la copertura white-box, a causa di un elevato numero di complesse dipendenze dinamiche; l’allocazione dinamica di oggetti crea delle dipendenze tra classi a tempo di esecuzione; difficoltà nel produrre drivers, stubs, e test suites che tengano conto della struttura di ereditarietà del sistema; possibilità di riusare i test case di superclassi nel testing di subclasse; e spesso il codice dell’applicazione non è completamente disponibile.
La documentazione del testing. Ogni caso di test indipendentemente dalla sua tipologia, dovrebbe essere descritto quando meno dai seguenti campi: numero identificativo, precondizioni, valori di input, valori di output attesi, postcondizione attese. All’atto dell’esecuzione del test, verranno aggiunti i seguenti campi: output riscontrati, postcondizioni riscontrate, esito (positivo c’è errore). Il documento relativo all’intero progetto ha la seguente struttura: specifica delle unità di test, caratteristiche da testare (prestazioni, vincoli), approccio (criterio di selezione dei test), prodotti del test (casi di test, rapporto finale, diario del testi…), schedulazione (quando effettuare il testing e lo sforzo per attività); allocazione del personale. I rapporti sul test sono composti da: diario del test, che descrive i dettagli del test per come si è svolto effettivamente; riepilogo del test (rivolto al management del progetto in cui sono riportati il n.ro totale di casi di test eseguiti, malfunzionamenti osservati, difetti scoperti); infine, il sommario dei malfunzionamenti, rivolto a chi deve effettuare il debuggin o la correzione.
SOFTWARE TESTING (parte 6)
L’automazione nel processo di testing è l’insieme delle tecniche e delle tecnologie che consentono di automatizzare alcune attività del processo di testing. Le aree di intervento sono: generazione dei casi test, preparazione ed esecuzione del test e valutazione dell’efficienza di test suite.
Generazione dei casi di test tali tecniche possono ridurre drasticamente i costi e i tempi legati alla fase di test design. Nella tecnica per la generazione automatica di casi di test per il testing black-box partendo dall’analisi delle sessioni utente, vengono installati strumenti che siano in grado di mantenere un log di tutte le interazioni che avvengono tra gli utenti dell’applicazione da testare e l’applicazione stessa, e a partire da tali dati vengono formalizzati casi di test che replichino le interazioni “catturate”. I problemi legati allo User-Session testing sta nel fatto che il numero di sessioni da prendere in considerazione al fine di poter avere un insieme significativo di casi di test può essere molto elevato. Il sistema di loggia deve essere in grado di monitorare il più possibile anche gli elementi legati all’ambiente di esecuzione. Per poter ottenere esecuzioni significative può essere necessario raccogliere sessioni per molto tempo. Infine vi è una grande difficoltà nel riprodurre le reali condizioni di utilizzo dell’applicazione prima del suo rilascio reale. Unìulteriore approccio è il testing mutazionale.
1) Il testing mutazionale è una tecnica per la generazione di casi di test. A partire da un sottoinsieme di casi di test, si applicano alcuni operatori mutazionali che vadano a modificare/incrociare i dati dei test case esistenti, in modo da ottenere nuovi test case. Si hanno due concetti distinti: analisi mutazionale e testing mutazionale. L’analisi mutazionale non è una tecnica di testing a tutti gli effetti ma un processo a supporto della valutazione dell’efficacia di test suite esistenti. Con il testing mutazionale si possono ottenere test suites più piccole, con maggiore copertura, con uno sforzo minore rispetto a quello della sessione utente. Bisogna però eliminare tutti i test cases che risultano inapplicabili. Questa tecnica è spesso utilizzate per il testing di interfacce o di protocolli. Ulteriori tecniche per la proposizione di casi di test si basano sulla valutazione analitica dei vincoli e dei valori “critici” dei dati in input, come il domain analysis, analisi dei valori limite. Una soluzione per l’automazione del testing black-box consiste nell’utilizzare appositi framework di supporto all’esecuzione dei casi di test. Tra questi framework sono molti noti quelli della famiglia XUnit (JUnit, CppUnit, csUnit…). Tali framework sono nati nell’ambito della eXtreme Programming per automattizare il testing di unità, ma possono essere generalizzati anche alle problematiche di testing black-box. unit testing basics. Il testing a livello di unità dei comportamenti di una classe dovrebbe essere progettato ed eseguito dallo sviluppatore della classe, contemporaneamente allo sviluppo stesso della classe. I vantaggi di tale metodo sono che lo sviluppatore conosce esattamente le responsabilità della classe cha ha sviluppato e i risultati che da essa si attende, e consce esattamente come si accede alla classe. Lo svantaggio è che lo sviluppatore tende a difendere il suo lavoro e quindi troverà meno errori di quanto possa fare un teste. Se le progettazione dei casi di test è un lavoro duro e difficile, l’esecuzione dei casi di test è un lavoro noioso e gramo. L’automatizzazione dell’esecuzione dei casi di test porta innumerevoli vantaggi: tempo risparmiato, affidabilità dei test e riuso dei test a seguito di modifiche nella classe. Nel caso di unit testing di una classe, una tecnica per l’esecuzione automatica del test richiede di scrivere in ogni classe un metodo main capace di testare i comportamenti della classe ma tale codice verrà distribuito anche nel prodotto finale appesantendolo e vi sono altri problemi. Per ovviare a ciò occorre cercare un approccio sistematico che separi il codice di test da quello della classe, che supporti la strutturazione dei casi di test in test suite e che fornisca un output separato dall’output dovuto all’esecuzione delal classe. Nel caso di testing black box, invetabilmente il codice di test sarà separato dal codice del programma da testare. Le classi di test si occuperanno di eseguire le operazioni rese accessibili dal sistema da testare. Nel caso di sistemi a componenti, si eseguiranno le operazioni nell’interfaccia dei componenti. Nel caso di sistemi a oggetti si eseguiranno i metodi pubblici. Nel caso di sistemi a linea di comando à i comandi accessibili variando i parametri passati. JUnit è un framework che permette la scrittura di test in maniera ripetibile. Plug-ins che supportano il processo di scrutta ed esecuzione dei test JUnit su classi Java sono previsti da alcuni ambienti di sviluppo (eclipse). Eclipse è dotato di un plug-in, di publico dominio, che supporta tutte le operazioni legate al testing di unità con JUnit. In particolare, esso fornisce dei wizard per: creare classi contenenti test cases, automatizzare l’esecuzione di tutti i test cases e organizzare i test cases in test suites. (slide per tutorial). I limiti di JUnit sono che se una classe è in associazione con altre e JUnite rileva l’errore, esso potrebbe dipendere dall’altra classe oppure dall’integrazione. JUnit è uno strumento che è in grado di risolvere solo le problematiche relative al testing di unità, e qualche utile indicazione rispetto al testing di integrazione. JUnit supporta unicamente il testing ma non il debugging.
La valutazione dell’efficienza di un Test suite misura il grado di copertura raggiungo dalla test suite, inoltre è una possibile sinergia fra white-box e black box testing in quanto, la valutazione della copertura con criteri white box è un metodo per valutare in maniera indiretta l’efficacia delle tecniche di generazione di test black box. è possibile sviluppare programmi che inseriscano automaticamente sonde all’interno di un programma in un determinato linguaggio di programmazione, allo scopo di valutare l’efficacia di un test suite rispetto a criteri di copertura strutturali. Il testing d’integrazione è applicato ad un aggregato di due o più unità di un sistema software, l’obiettivo è quello di rilevare errori nelle interazioni fra le unità e nelle funzioni che l’aggregato deve assolvere, non è compito dei programmatori che hanno prodotto le unità componeti, le unità da integrare sono selezionabili in base a criteri funzionali ricavabili dal progetto, e partendo da una architettura organizzata gerarchicamente, le integrazioni possono essere realizzate con approccio top-down o bottom-uo o misto. Per eseguire un test occorre costruire dei moduli guida (driver) e dei moduli fittizi (stub). Tramite un framework di testing automation con JUnit è possibile realizzare driver e stub necessari per testare un modulo non terminale. Un driver deve avere la visibilità dei componenti, in quando deve invocare l’unità sotto test inviandole opportuni valori relativi al test case. Uno stub è una funzione fittizia la cui correttezza è vera per ipotesi, esso viene invocato dall’unità sotto test, emulando il funzionamento della funzione chiamata rispetto al caso di test richiesto.
Il testing di sistemi interattivi viene condotto tipicamente in maniera black box. i casi di test sono progettati tenendo in conto le possibili interazioni che un utente può eseguire sull’interfaccia utente, è fondamentale la disponibilità di un modello descrittivo delle interazioni utente-macchina. Il modello più comune è il modello di Macchina a stati. Si suppone che l’interfaccia utente non esegua alcuna operazione se non in seguito a sollecitazioni da parte degli utenti. Dove lo stato dell’interfaccia utente viene modellato da uno stato di un automa, l’esecuzione di eventi sulla UI viene modellata come ingressi impulsivi che scatenano transizioni nell’automa e gli input immessi sono modellati come ingressi a livelli da cui dipende la transizione che si verifica. Per quanto riguarda il testing delle interfacce gli input dei casi di test devono essere indicati sotto forma di sequenze di valori di input ed eventi da eseguire. A seconda dei criteri di copertura si progetteranno diversi TC. Ad una sequenza di valori di input dovrebbe corrispondere una sequenza di stati visitati e di output riscontrati. Un problema può essere rappresentato dalla come ottenere un FSM che descriva adeguatametne l’UI e che possa essere usato per progettare i casi di test. Per tale problematica vi sono due possibilità: 1) FSM prodotto in fase di sviluppo dell’applicazione; 2) FSM ricostruito per Riverse Engineering a partire dalla UI effettivamente implementata. Nel caso di interfacce dinamicamente configurabili l’analisi statica non è sufficiente. Per poter eseguire automaticamente casi di test su interfacce utente sono necessari strumenti che consentano di emulare il comportamento dell’utente e strumenti che consentano di analizzare le interfacce restituite. Per esempio, il framework Selenium offre quattro modalità di utilizzo:
C’è un’alta probabilità che la rimozione di un difetto influisca sul resto del sistema, creando nuovi difetti (ripple effect). A tal proposito si applica il testing di regressione ad un intervento di manutenzione su di un software esistente, per il quale è stato formalizzato un piano di test. Occorre conoscere l’impatto della modifica sul sistema e quindi eseguire un’analisi di impatto. L’analisi di impatto è la disciplina che permette di conoscere, data una modifica, quali parti del software possono essere influenzate. Una tecnica semplice per valutazione dell’impatto è basata sul Grafo delle Dipendenze, che ha tanti nodi quanti sono i moduli, un arco per ogni associazione tra i moduli. Data una modifica su di un modulo m tutti i moduli m’ che da essi dipendono sono sicuramente impattati dalla modifica di m, e tutti i moduli m’’ che dipendono da uno qualunque dei moduli m’ saranno a loro volta impattati, e così via. I casi di test relativi ai moduli impattati devono essere rieseguiti, l’oracolo del testing di regressione è fornito dall’esito dei test che si otteneva prima di eseguire la modifica. Il testing di regressione si presta facilmente ad essere automatizzano, se non lo fosse il testing di regressione diventerebbe una pratica molto onerosa.
Il testing statico. Le tecniche di verifica e validazioni si ottengono eseguendo delle analisi. Esse sono: 1) analisi statica è un processo di valutazione di un sistema o di un suo componente basato sulla sua forma, struttura, contenuto, documentazione senza che esso sia eseguito; 2) analisi dinamica è un processo di valutazione di un sistema software o di un suo componente basato sulla osservazione del suo comportamento in esecuzione; 3) analisi formale fa uso di rigorose tecniche matematiche per l’analisi di algoritmi. Le principali tecniche di analisi statica sono:
Il problema di stabilire se esiste una soluzione per un sistema di disuguaglianze è indicibile. Un cammino è eseguibile se esiste un punto del dominio di ingresso che rende soddisfatta la sua path condition. La determinazione della feasibility o della infeasibility di un cammino è in decidibile. Se si riesce a dimostrare che ciascun predicato nella path condition è dipendente linearmente dalle variabili di ingresso, programmazione lineare. Dato un GFC(P), posto s=nI e reso aciclico tale grafo (collassando in un unico nodo le sue componenti) si ha che se px e py sono due nodi in un grafo aciclico.
Il nodo s domina tutti gli altri nodi: la relazione di dominanza è una relazione d’ordine parziale, è possibile provare che, eccetto s, ogni nodo ha un unico dominatore diretto.
Il debugging è un’attività di ricerca e correzione dei difetti che sono causa di malfunzionamenti. È l’attività consequenziale all’esecuzione di un test che ha avuto successo. Il debugging è ben lungi dall’essere stato formalizzato, metodologie e tecniche di debugging rappresentano soprattutto un elemento dell’esperienza del programmatore/tester. Le difficoltà del debugging possono essere: il sintomo e la causa possono essere lontani; il sintomo può scomparire solo temporaneamente; il sintomo può non essere causato da errori specifici, può dipendere da errori di temporizzazione e non di elaborazione; può essere difficile riprodurre le condizioni di partenza; il sintomo può essere intermittente. Per poter localizzare i difetti occorre ridurre la distanza tra difetto e malfunzionamento. Mantenendo un’immagine dello stato del processo in esecuzione in corrispondenza dell’esecuzione di specifiche istruzioni quali:
Abbiamo diverse metodologie per il Debugging:
Automatizzazione del debugging. Il debugging è un’attività estremamente intuitiva, che però deve essere operata nell’ambito dell’ambiente di sviluppo e di esecuzione del codice. Strumenti a supporto del debugging sono quindi convenientemente integrati nelle piattaforme di sviluppo, in modo da poter accedere ai dati del programma, anche durante la sua esecuzione, senza essere invasivi rispetto al codice. In assenza di ambienti di sviluppo, l’inserimento di codice di debugging invasivo rimane l’unica alternativa. Le funzionalità comuni di debugging sono: inserimento break point; esecuzione passo passo del codice, entrando o meno all’interno dei metodi chiamati; verifica di asserzioni, le asserzioni possono anche essere utilizzate come parametri dper break point condizionali; valutazione del valore delle variabili, mentre l’esecuzione è in stato di interruzione.
IL RIUSO (parte 7)
Nella maggior parte delle discipline ingegneristiche, i sistemi si progettano a partire da componenti che sono usati anche da altri sistemi. L’ingegnerai del software si è originariamente preoccupata soprattutto dello sviluppo di software ex-novo, ma oggi è ben noto che occorre adottare processi di sviluppo basati su un riuso sistematico del software per ottenere software migliore e per ottenere software più rapidamente ed economicamente. Il vantaggio del riuso del software può consentire di ridurre i costi di sviluppo/testint/manutenzione e produrre software che rifletta il livello di affidabilità del software riusati. Il riuso è possibile a diversi livello: riuso di intere applicazioni, riuso di componenti e riuso di oggetti e funzioni.
I benefici del riuso sono:
I problemi del riuso sono:
Ci sono diversi approcci per il riuso:
I fattori di cui tener conto nel pianificare il riuso sono: tempistica richiesta per lo sviluppo, durata prevista per la vita del software, Background (capacità ed esperienza del team di sviluppo), criticità del software e altri requisiti non funzionali, dominio di applicazione, piattaforma sulla quale eseguire il sistema. Il riuso di componenti già implementati obbliga ad ereditare le scelte di progetto e di sviluppo di chi ha realizzato i componenti, limitando le situazioni nelle quali il riuso è possibile. Ad un maggior livello di astrazione, è possibile invece riutilizzare “concetti”, ovvero scelte effettuate durante la specifica dei requisiti o la fase di progettazione. Il design pattern. Un pattern individua un’IDEA, uno schema generale e riusabile come uno schema di problema, uno schema di soluzione, etc… Rispetto ai componenti riusabili esso non è un oggetto fisico, e non può essere usato così come è stato definito, ma deve essere contestualizzato all’interno del particola problema applicativo. Due istanze di uno stesso pattern tipicamente sono diverse proprio per la contestualizzazione in domini differenti. Lo scopo dei pattern è quello di catturare l’esperienza e la saggezza degli esperti ed evitare di reinventare ogni volta le stesse cose. Ogni pattern descrive un problema specifico che ricorre più volte e descrive il nucleo della soluzione a quel problema, in modo da poter utilizzare tale soluzione un milione di volte, senza mai farlo allo stesso modo. Devono essere abbastanza astratti in modo da poter essere condivisi da progettisti con punti di vista diversi. Infine non devono essere complessi ne domain-specific, in quando non devono essere rivolti alla specifica applicazione ma riusabili in parti di applicazioni diverse. Un design pattern fornisce al progettista software una soluzione codificata e consolidata per un problema ricorrente, un’astrazione di gradualità e livello di astrazione più elevati di una classe, un supporto alla comunicazione delle caratteristiche del progetto, un modo per progettare software con caratteristiche predefinite e un supporto alla progettazione di sistemi complessi. Un design pattern ha le seguenti caratteristiche: Nomina, Astrae e Identifica. Questi sono aspetti chiave di una struttura comune di design che la rendono utile nel contesto del riuso in ambito OO. Un design pattern identifica le classi partecipanti, le associazioni ed i ruoli, le modalità di collaborazione tra le classi, e la distribuzione delle responsabilità nella soluzione del particolare problema di design considerato. Un pattern è formato da quattro elementi essenziali: il nome del pattern, il problema nel quali il pattern è applicabile, la soluzione che descrive in modo astratto come il pattern risolve il problema, e infine le conseguenze portate dall’applicazione del pattern.
Riuso basato su Generatori. Un generatore è un software che è in grado di generare software parametrizzato in base a delle specifiche fornite dall’utente. I generatori possono essere utilizzati nell’ambito di quei problemi per i quali esistono soluzioni ben consolidate che però dipendono notevolmente dai dati in ingresso. I dati in ingresso al generatore di programmi vanno a descrivere la conoscenza relativa al dominio per il quale debba essere utilizzato il programma da generare. Esistono diverti tipi di generatori di codice, come ad esempio i generatori di applicazioni per gestione i dati aziendali, parser e analizzatori lessicali per analisi di codice, generatori di codice basati su modelli (ad esempio DPAToolkit che genera uno scheletro di codice java/cpp che istanzia un design pattern, o Web Ratio che genera un’applicazione web a partire da un modello codificato di WEBml, che a sua volta estende modelli delle classi e modelli E-R). I vantaggi dei generatori sono i seguenti. Il riuso basato su generatori riduce notevolmente il costo di sviluppo e produce codice molto affidabile. Tramite generatori di codice si possono ottenere programmi più versatili e performanti di quanto si possa ottenere limitandosi a leggere direttametne dal database, a tempo di esecuzione, i dati relativi alla personalizzazione. Scrivere una descrizione di dominio per un utente programmatore è più semplice che sviluppare programmi da zero. Mentre gli svantaggi sono che la loro applicabilità si limita a poche tipologie di problemi, e spesso il linguaggio col quale descrivere il problema al generatore ha una semantica molto limitata.
Abbiamo una famiglia di standard di modellazione, basata su UML e OMG, pensati allo scopo di generare codice eseguibile a partire da modelli quale l’MDA (Model Driver Architectures) che si basa sulla separazione fra livelli di astrazione. MDA si basa sull’automazione della trasformazione fra modelli di diverso livello. Ad ogni modello previsto da MDA deve corrispondere una famiglia di strumenti che attuino le regole di traduzione previste per passare da modelli PIM (Platform Independent Moldel) verso modelli PSM (Platform Specific Model) e dal PSM verso il codice. I framework rappresentano modelli astratti di progetto di sotto-sistemi. Le applicazioni si costruiscono integrando e completando una serie di framework. Per esempio gli OO Framework sono composti di una collezione di classi astratte e concrete e di interfacce tra loro, inoltre un framework OO è una struttura generica per realizzare il software bisogna riempire le parti del progetto instanziando le classi astratte necessarie ed implementando il codice mancante. Si differenziano dai design patterns per il fatto di essere astrazioni di livello più alto, a livello architetturale anziché di design. I framework si classificano nel seguente modo:
I primi due tipi rientrano anche nella categoria di framework orizzontali che forniscono funzioni generali riusabili da molte applicazioni, mentre le applicazioni aziendali rientrano nei framework verticali che sono un po’ più completi, ma hanno sempre delle parti da riempire per adattarli alle specifiche applicazioni. I framework applicativi come MVC (Model-View Controller) sono usati per la progettazione di GUI. Spesso i framework sono istanziazioni di una serie di design pattern. L’utilizzo di un framework da parte di programmatori e progettisti comporta il raggiungimento di un notevole skill riguardante la conoscenza della struttura del framework e delle opportunità da esso messe a disposizione. Il riutilizzo di intere applicazioni consiste nel riutilizzare, previa riconfigurazione o personalizzazione, intere applicazioni. Le applicazioni possono essere riutilizzate direttamente o essere integrate fra loro come componenti indipendenti all’interno di più ampi sistemi. Esistono due approcci principali: integrazione di COTS e Sviluppo di prodotti. I COTS (Comercial-Off-The-Shelf) è un software commerciale che può essere usato dai suoi acquirenti senza modifiche (es. DBMS). Si tratta di applicazioni complete che spesso offrono un API per permettere ad altri componenti software di accedere alla proprie funzionalità. Nella pratica i COTS sono composti di un insieme di classi astratte di interfaccia visibile all’esterno e di un insieme di classi astratte e concrete non visibili. L’integrazione di COTS è una strategia abbastanza efficace per sistemi le cui funzionalità base siano abbastanza comune. Tramite COTS si velocizza il processo di sviluppo riducendo i costi di sviluppo e test. I fattori decisionali nella scelta dei COTS sono: le funzionalità più appropriate (come costo, completezza, affidabilità), come avviene lo scambio di dati (per esempio la conversione dei dati verso il formato richiesto dall’applicazione), quali caratteristiche del prodotto COTS vengono utilizzate (i prodotti COTS espongono una grande quantità di funzionalità, molte più di quante necessarie, quindi si rende opportuno negare l’accesso alle funzionalità non utilizzate, o scegliere COTS più ridotti). Per poter dialogare con applicazioni esistenti è necessario formulare richieste secondo il formato accettato dall’applicazione e interpretare le risposte ottenute nel formato prodotto dall’applicazione. Le possibili soluzionie al problema della realizzazione degli adattatori sono: per trasformare da/verso formati molto semplici conviene scrivere una classe adaptor; per trasformare tra documenti XML si può scrivere un documento XSLT dichiarando le regole di trasformazione tra formati; per interpretare/costruire documenti XML è possibile utilizzare librerie standard come SAX che forniscono un’API per l’accesso ai contenuti del documento XML; per interpretare formati divesi, non XML, un buon metodo consiste nella scrittura di un parser. I problemi dei COTS sono: la mancanza di controllo sulle funzionalità e sulle prestazioni ( in quanto il sistema è utilizzato a scatola chiusa); i problemi di interoperabilità tra sistemi COTS; nessun controllo sull’evoluzione del sistema; e supporto dei produttori COTS che tipicamente potrebbe terminare con l’acquisto del prodotto o limitandosi alle sole evoluzioni decise dal produttore. Le linee di prodotti software. Per linee di prodotti software si intendono famiglie di applicazioni con funzionalità generiche che si prestano ad essere configurate o adattate in modo da poter essere utilizzate in contesti specifici. Abbiamo diverse possibili specializzazioni:
La configurazione può avvenire in due momenti diversi: configurazione alla consegna che avviene per prodotto finito, senza modificarne internamente la struttura e il progetto, ma solo limitandone/personalizzando le funzionalità; e configurazione a tempo di progettazione che tramite l’adozione di patterns e framework generici, le richieste di personalizzazione vengono recepite in fase di progetto e influiscono direttamente sulla realizzazione del prodotto. Ad esempio i sistemi ERP (Enterprise Resource Planning) supportano comuni processi aziendali. Il processo di configurazione degli ERP si basa sull’adattamento di un core generico attraverso l’inclusione e la configurazione di moduli, e incorporando conoscenza su processi e regole aziendali del cliente specifico in un database di sistema. Essi sono molto usanti in grandi aziende, e costituiscono la forma di riuso più comune. Per mantenere l’adattabilità e la riconfigurabilità è utile adottare architetture modulari e stratificate, che permettono facilmente le necessarie modifiche. Inoltre, è importante separare le funzionalità generiche dai dati che si riferiscono alla personalizzazione, in modo da poter realizzare tale customizzazione senza generazione di codice. (slide esempio pag 24).
Component based software Engineering (CBSE) è approccio per lo sviluppo software basato sul riuso. Si è affermato a seguito del fallimento dello sviluppo OO ne supportare un effettivo riuso. I componenti sono più astratti delle classi di oggetti e possono essere considerati come fornitori di servizi stand-alone. Gli elementi essenziale del CBSE sono: componenti indipendenti, interamente specificati dalle proprie interfacce; standard di descrizione e realizzazione dei componenti, che semplificano l’integrazione tra componenti, anche sviluppati in vari linguaggi; 3) middleware che fornisce supporto per l’interoperabilità gestendo, le problematiche di concorrenza, protezione, gestione delle transizioni; un approccio di sviluppo che sia orientato verso la realizzazione di componenti riusabiliti. Oltre che sul riuso, CBSE si basa su ben noti principi di ingegneria del software: i componenti devono essere indipendenti in modo da non interferire tra loro; le implementazioni dei componenti sono nascoste; la comunicazione avviene attraverso ben precise interfacce; l’infrastruttura dei componenti fornisce una piattaforma che riduce i costi di sviluppo. I problemi riguardano: la fiducia nei componenti (in quanto il componente viene utilizzato a scatola chiusa); Certificazione dei componenti (per garantire la qualità dei componenti occorrerebbe un certificato di qualità); previsione delle proprietà emergenti ( i componenti potrebbero introdurre dei vincoli e dei problemi che non risultano prevedibili in fase di scelta del componente; compromesso tra i requisiti (non è sempre possibile scegliere componenti che risolvono esattamente i nostri problemi). Definizioni di componenti: un elemento software che si conforma a un modello di componente, che può essere consegnato indipendentemente e composto senza modifiche secondo uno standard di composizione. Un’altra definizione di componente: “un componente software è un’entità di composizione soltanto di interfacce definite per contratto e dipendenze esplicite di contesto. Un componente software può essere consegnato indipendentemente ed è oggetto a composizioni da terze parti”. Il componente è visto come fornitore di servizi. Ne consegue che i componenti dovrebbero essere entità indipendenti e direttamente eseguibili e i servizi offerti dai componenti sono disponibili attraverso un’interfaccia e tutte le interazioni col componente devono avvenire attraverso l’interfaccia. Le caratteristiche dei componenti sono:
I componenti sono definiti dalle loro interfacce, che forniscono una definiti i servizi che esso è in grafo di fornire agli utilizzatori o ad altri componenti e a loro volta tali componenti richiedono un’interfaccia con la quale si specifica quali debbano essere le interfacce di altri componenti dei quali ha bisogno per poter fornire i propri servizi.
Vi sono delle sostanziali differenze tra componenti ed oggetti. I componenti sono entità consegnabili, ovvero non devono essere compilati all’interno di un programma applicativo. I componenti non definiscono tipi, perché un componente è da vedersi come un’istanza, non come un tipo da istanziare. I componenti sono opachi, ovvero la loro implementazione interna non è visibile all’utilizzatore. I componenti sono indipendenti dal linguaggio. Infine, i componenti sono standardizzati, quindi la loro implementazione deve rispettare specifici modelli di componenti. I modelli di componenti sono degli standard che definiscono come possa essere implementato, documentato, distribuito un componente. Il modello di componente specifica in particolare quali elementi debbano essere definiti nella sua interfaccia e come debbano essere scritti.
Alcuni modelli possibili sono quelli proposti nell’ambito di infrastrutture di middleware come CORBA, COM+, EJB. Per la definizione delle interfaccie CORBA e COM+ prevedono appositi linguaggi per la descrizione delle interfacce, menter EJB usa Java. Per quanto riguarda la convenzione sui nomi, CORBA e COM+ hanno per ogni classe un identificatore a 128 bit che le rende uniche, mentre EJB utilizza una convensione basata sull’indirizzo web del produttore. Il packaging definisce come deve essere impacchettato il componente affichè possa essere usato dagli altri elementi del middleware. I modeli di componenti definiscono anche le caratteristiche del middleware che deve fornire il supporto per l’esecuzione dei componenti. L’implementazione di un middleware comprende: servizi di piattaforma, i quali consentono la comunicazione tra componenti conformi al modello; e da servizi orizzontali, indipendenti dall’applicazione e a disposizione di altri componenti. Per usare i servizi dell’infrastruttura, i componenti vengono consegnati in un contenitore predefinito e standardizzato.
Un altro problema riguarda il procurarsi i componenti riusabili. In attesa che si sviluppi un mercato aperto di componenti riusabili, attualmente questi si ottengono all’interno delle società a partire da software esistente. I componenti sviluppati per una specifica applicazione non sono automaticamente riusabili, ma devono essere generalizzati. Un componente riusabile deve avere alta probabilità di essere riusato, ed i costi per renderlo riusabile devono essere inferiori a quelli necessari per risvilupparlo. Un componente è più riusabile se è associato ad una stabile astrazione di dominio. Ad esempio, in un domino ospedaliero, le astrazioni stabili saranno: infermieri, pazienti, trattamenti, etc…
Le modifiche per rendere riusabile un componente devono conferire ad esso tutte le proprietà attese da un componente riusabile, ossia: deve nascondere la rappresentazione interna del proprio stato; deve essere indipendente da qualunque altro componente; e deve essere robusto rispetto a tutti i possibili utilizzi compatibili. Per poter rendere riusabili un componente occorre nascondere o eliminare i metodi specifici dell’applicazione, rendere i nomi più generali, aggiungere metodo per aumentare la copertura funzionale, rendere la gestione delle eccezione consistente con tutti gli utilizzi possibili, aggiungere un’interfaccia di configurazione e integrare i componente da cui si dipende. Per raggiungere maggiore ricusabilità, spesso l’interfaccia del componente viene resa più complessa. C’è un fondamentale treade-off tra usabilità e ricusabilità dei componenti. Un componente dall’interfaccia semplice non è in grado di modellare un concetto generale, ma è più facile renderlo riusabile. Mentre, un componente dall’interfaccia complessa può modellare un concetto generale, fornendo tutte le interfacce richieste alle diverse tipologie di utilizzatori, ma è molto difficile renderlo robusto e inoltre potrebbe essere necessario integrarlo con molti altri componenti. Quindi vi è la necessità di raggiungere un compromesso. Spesso si vuole estrarre componenti riusabili da sistemi esistenti (legacy system components). Per riutilizzare tali componenti è necessario sviluppare un componente wrapper che implementi le interfacce richieste e fornite dal componente riusabile. In generale, quest’opportunità è abbastanza costosa ma può rappresentare una via percorribile per le quali è richiesta una grande affidabilità. Produrre componenti riusabili è certamente più oneroso che produrre componenti che non lo siano, ma lo sforzo extra potrà essere ricompensato ogni volta che quel componente verrà riusato anziché riscritto. La vita dei componenti può essere parecchio più lunga della vita del sistema di cui fa originalmente parte.
Il processo CBSE. Quando si riusano componenti, è essenziale un compromesso fra requisiti ideali e servizi effettivamente forniti dai componenti disponibili. Di conseguenza, bisognerà: sviluppare una prima bozza dei requisiti, cercare i componenti e quindi modificare i requisiti in base alle funzionalità disponibili, cercare ancora eventuali altri componenti che soddisfano meglio i requisiti rivisti.
Per tutti i componenti creati bisogna valutare se coprano effettivamente tutti i requisiti richiesti. La specifica dei componenti deve essere tale da fornire le informazioni necessarie per poter costruire una test suite che sia in grado di verificarne tutte le funzionalità. L’utilizzo di un preciso formalismo di specifica può consentire l’utilizzo di strumenti che generino automaticamente casi di test. I componenti realizzati devono garantire i requisiti di qualità previsti, prima tra tutti quelli di sicurezza. I componenti più semplici risultano essere i più riusabili ma non riescono a risolvere problemi complessi. Nell’integrazione di componenti tramite composizione può essere necessario scrivere del glue code per rendere compatibili i formati degli input e degli output dei vari componenti, e coordinare i vari componenti. Però abbiamo la possibilità di insorgenza di incompatibilità tra le interfacce come ad esempio: incompatibilità tra i parametri delle operazioni (operazioni con lo stesso nome accettano un numero diverso di parametri), incompatibilità tra operazioni (le due interfacce prevedono nomi diversi per la stessa operazione), incompletezza delle operazioni (le operazioni con lo stesso nome svolgono in realtà l’una un sottoinsieme delle operazioni dell’altra. Tutti questi problemi di incompatibilità possono essere risolti dal glue code, in particolare dai cosiddetti adaptor. I componenti adattatori risolvono il problema della incompatibilità delle interfacce fra componenti. Gli adattatori possono essere diversi, a seconda del tipo di composizione.
L’object costraint language (OCL) è un linguaggio pensato per la definizione di vincoli previsti dai modelli UML. È basato sulle nozioni fondamentali di pre e post condizione, e si è rivelato utili anche per la descrizione delle condizioni di usabilità delle interfacce di un componente.
Le API appartengono alla famiglia dei componenti, che sono delle librerie di funzioni e oggetti istanziabili. A margine dello sviluppo di un componente del quale si vuole consentire il riuso, si forniscono un insieme di interfacce e classi astratte delle quali l’utente programmatore può vedere un’astrazione. Un esempio sono JAXP e Tockit.
SERVICE ENGINEERING (parte 8)
Le Architetture Orientate ai Servizi (SOA) sono un modo di progettare sisteme distribuiti i cui componenti sono costituiti da servizi indipendenti. I servizi sono componenti stand-alone, in grado di risolvere specifici problemi, che possono essere offerti da differenti fornitori ed essere eseguiti su computer diversi. Per rendere possibili tali attività, devono essere adottati protocolli standard per supportare: la comunicazione fra servizi e lo scambio di informazioni fra servizi. Rispetto ad una architettura a componenti, nelle SOA si prevede esplicitamente di automatizzare sia il processo di ricerca del servizio più adatto al sotto-problema da risolvere che il processo di composizione dei servizi trovati.
I service provider progettano e implementano servizi: per la loro specifica si utilizza un linguaggio chiamato WSDL. I provider inoltre pubblicano le informazioni relative ai servizi in un registro di accesso generale, usando uno standard di pubblicazione chiamato UDDI. Coloro che desiderano far uso di un servizio si chiamano service requestor, ma anche più semplicemente client del servizio: per esaminare la specifica dei servizi e localizzare i provider questi ultimi eseguono ricerche nel registro UDDI. Una volta fatto ciò possono legare la loro applicazione a uno specifico servizio e comunicare con esso per mezzo di un protocollo denominato SOAP.
I vantaggi nell’utilizzare un’architettura orientata ai servizi sono che:
I protocolli per i servizi web coprono tutti gli aspetti delle architetture orientate ai servizi, dai meccanismi base per lo scambio di informazioni (SOAP) ai linguaggi di programmazione (WS-BPEL). Questi standard sono tutti basati su XML.
Gli standard più importanti sono:
Gli approcci di ingegneria del software esistnti devono necessariamente cambiare per tener conto dell’approccio di sviluppo software service-oriented.Ci sono due temi fondamentali:
Servizi come componenti riusabili
Un servizio è: “Un componente software riusabile, debolmente accoppiato, che incapsula funzionalità discreta; può essere distribuito e vi si può accedere mediante la programmazione. Un servizio web è un servizio a cui si accede per mezzo di protocolli standard Internet e protocolli basati su XML.”
Le principali differenza con i componenti CBSE sono:
Fig 1 Processo di interazion sincrona
WSDL. Le interfacce dei servizi sono espresse in linguaggio WSDL. Una specifica WSDL definisce:
Service engineering
Service engineering è il processo di sviluppo di servizi riusabili per applicazioni service-oriented. Il servizio deve essere progettato come una astrazione riusabile nell’ambito di diversi sistemi. Esso richide:
Le fasi del processo di Service Engineering sono:
Un’altra classificazione distingue tra servizi orientati alle attività o alle entità.
Lo scopo dell’identificazione dei servizi candidati è di selezionare servizi logicamente coerenti, indipendenti e riusabili. Occorre innanzitutto compilare una lista di possibili candidati, quindi porsi una serie di domande che li riguardano per appurare se possono essere effettivamente utili. Alcune possibili domande sono:
Le fasi della progettazione dell’interfaccia sono:
I web service mostrano solamente l’interfaccia dei servizi forniti, mente non dovrebbero aver bisogno di altre risorse. Se il web service ha bisogno di dati persistenti, nel suo codice dovrà essere inglobato il codice per l’accesso alla risorsa. In questo modo il WS può operare da proxy nell’accesso al database. I web service possono mantenere lo stato unicamente sfruttando variabili di sessione e le altre possibilità messe a disposizione dai protocolli della famiglia http. I web service devono essere installati all’interno di un’application server. L’application server, analogamente ad un web server, ascolta su alcuni porti le richieste http in arrivo. Le richieste codificate con linguaggi come SOAP vengono processate; viene eseguito il Web Service richiesto con i parametri che è possibile estrarre dal messaggio SOAP di richiesta; e viene generato e inviato al sender un messaggio SOAP di risposta contenente tra l’altro i parametri risultato dell’eleborazione. L’application server rende accessibile anche il catalogo dei WSDL dei servizi messi a disposizione, esempi di application server è Apache Tomcat che è in realtà un’estensione del web server Apache.
UDDI è l’elemento fondamentale delle architetture SOA ed è la descrizione della semantica del servizio fornito che permetta, idealmente, la ricerca automatica di un servizio data la sua descrizione funzionale e, eventualmente, i requisiti di qualità richiesti o graditi. UDDI (dialetto XML) è il linguaggio più spesso utilizzato per fornire tale descrizione semantica. Un documento UDDI contiene:
Legaci system services. Un importante vantaggio nell’uso dei servizi è che essi danno la possibilità di accedere alle funzionalità di legaci systems. I legaci systems offrono funzionalità mature e stabili, il cui riuso può ridurre i costi di sviluppo dei servizi. Le applicazioni esterne potranno accedere a queste funzionalità attraverso l’interfaccia offerta dai servizi.
Sviluppo Software basato su servizi
L’idea è di comporre e configurare servizi esistenti per creare nuovi servizi composti ed applicazioni. La base per la composizione del servizio è spesso un workflow. I workflow sono sequenze logiche di attività che, insieme, modellano processi aziendali. Ad esempio, una composizione di servizi per prenotare un viaggio che coordina vari servizi di prenotazione volo, auto ed albergo.
Composizione di sevizi. Lo scenario prospettato dalle SOA è uno scenario nel quale:
Nel processo di composizione le difficoltà legate alla realizzazione di un workflow di servizi provenienti da fonti eterogenee sono comunque notevoli. In particolare bisogna tener conto di tutte le possibili eccezioni di ogni servizio e delle conseguenti azioni da intraprendere per recuperare dall’eccezione. Ad esempio, se la prenotazione del viaggio va a buon fine ma fallisce quella dell’albergo, la semantica del servizio potrebbe precedere la necessità di annullare la prenotazione del viaggio.
Per quanto riguarda la progettazione e l’implementazione dei workflow, sono stati proposti diversi linguaggi per la descrizione di workflow di servizi web:
Service testing. Il Testing ha lo scopo di trovare difetti e dimostrare che un sistema soddisfa i suoi requisiti funzionali e non-funzionali. Il testing dei servizi da parte del cliente del servizio è difficile in quanto i servizi sono “black-box”. Le tecniche di Testing basate sul codice sorgente non possono essere usate, mentre sono usabili tecniche black-box. I vari problemi relativi al Service Testing sono possono essere rappresentati da:
Migrazione di sistemi legacy verso SOA. Le tecnologie SOA sono particolarmente utili per la migrazione di funzionalità critiche di sistemi legacy. Spesso, infatti, le funzionalità presenti nei sistemi legacy risolvono problemi, particolarmente critici e sono da considerare molto affidabili, in quanto sono in opera da molti anni. Migrando tali funzionalità sotto forma di Web Services, si aprono scenari sia per l’adattamento a interfacce moderche, e sia alla composizione all’interno di architetture moderne.
Abbiamo diverse tipologie di paradigmi di interazione. Il modulo base di un sistema interattivo funziona in questo modo:
Mentre nei web service, un gruppo di utenti invocano un servizio implementato da un gruppo di provider, usando un messaggio di richiesta. Il provider processa le richieste e invia un messaggio di risposta contente i risultati.
Il Wrapper ha come obiettivo è guidare il system legacy durante l’esecuzione di ogni possibile scenario di interazione associato con un caso d’uso, fornendo il necessario flusso di dati e comandi. Il caso d’uso del sistema legacy wrapper è accessibile come servizio web.
I requisiti fondamentali per il Wrapper sono:
Per ottenere tali requisiti occorre eseguire un Reverse Engineering.
Un modello di interazione può essere il seguente.
Tale modello è un Automa a stati finiti FSA= (S,T,A,Sin,Sfin) dove:
Uno dei possibili problemi è quando si presenta un modello non deterministico, in cui da uno stato si può giungere con la stessa combinazione a due stati diversi.
Altri requisiti per il Wrapper è che esso deve conoscere la lista dei possibili stati che si possono raggiungere da un particolare stato, e infine il Wrapper deve essere in grado di individuare lo stato attuale, sulla base del risultato ottenuto.
La descrizione di Legacy Screen è necessaria per identificare lo Screen Templates. Esso è costituito da: etichette, campi di input e campi di output. Ogni campo ha una locazione nello Screen. Le location possono essere di due tipi: Fixed o Relative. Uno stato d’interazione è caratterizzato da un Screen template e da un set di azioni eseguite sul campo, causando la transizione verso un altro iteration state. Il terminale emulatore è responsabile della comunicazione tra il Wrapper e il sistema Legacy. Lo State idenfier è responsabile dell’identificazione dell’interation state raggiunto dal sistema Legacy. Esso fa corrispondere lo screen del corrente sistema legacy con lo screen templates associato, raggiunto dallo stato di interazione. Inoltre, lo state identifier localizza label, input ed output. Il motore Automatico ha il compito di interpretare gli FSA associati ai servizi offerti dal sistema legacy. (vedi slide 31-37 per meglio capire).
Per ottenere un caso d’uso di migrazione di un sistema legacy occorre seguire le seguenti fasi:
MIGLIORAMENTO DEI PROCESSI DI PRODUZIONE DEL SOFTWARE (parte 9)
Il processo software rappresenta l’insieme delle attività produttive che vengono realizzate durante tutte le fasi del Ciclo di Vita del Software esiste un legame empirico tra la qualità del processo software e la qualità dei prodotti software. Migliorare il processo software consente, tra l’altro, di migliorare la qualità dei prodotti software risultanti, nonché di ridurre costi e tempi. Esistono anche altri attributi di qualità di un processo che possono essere migliorati.
Come i prodotti, anche i processi hanno vari attributi quali:
Il miglioramento dei processi è un’attività ciclica. Tale ciclo si compone di tre fasi:
Sono quattro i fattori che influenzano la qualità dei prodotti software o di ogni altro prodotto intellettuale come libri o film, dove la qualità dipende dalla sua progettazione.
I quattro fattori sono: tecnologia di sviluppo utilizzata, qualità delle persone a cui ci lavorano, qualità del processo, infine costo, tempo e pianificazione.
Per sistemi di grandi dimensioni, la qualità del processo influenza sicuramente la qualità del prodotto. Per piccoli progetti, la capacità degli sviluppatori è invece il fattore determinante. La tecnologia utilizzate è fattore importante soprattutto nei piccoli progetti. In ogni caso, limitazioni sul tempo di realizzazione influiranno di sicuro negativamente sulla qualità del prodotto risultante.
I processi possono essere divisi in quattro classi:
Fase iniziale Scelta del processo. Il processo utilizzato dovrebbe dipendere dalla tipologia del prodotto da sviluppare. Per grandi sistemi, il management è di soluto il problema più significativo da risolvere, cosicché, un processo precisamente gestito è necessario. Per sistemi più piccoli, una gestione più informale è consentita e può far risparmiare tempo e risorse umane.
Non esiste un processo che sia applicabile uniformemente e senza adattamenti in ogni organizzazione. L’applicazione di un processo inappropriato può portare ad un aumento dei costi e ad un peggioramento della qualità del processo. Esistono dei tool a supporto dei processi software come ad esempio
Fase successiva Misurazione di processo. È auspicabile la raccolta di misure quantitative dei dati del processo, in quanto è molto difficile fare misure quanto non c’è chiarezza nei processi adottati. La definizione del processo è un prerequisito fondamentale per la sua misura. Misurare i processi consente di pianificare il loro miglioramento. Le misure sono però solo in grado di supportare il processo di miglioramento è comunque un processo organizzativo.
Vi sono diverse tipologie di misure di processo, quali: tempo impiegato, risorse richieste (es. sforzo giorni-uomo), numero di occorrenze di alcuni eventi (es. numero di difetti trovati). In questo caso è ancora possibile utilizzare un approccio GQM (Goal Question Metric).
Seconda fase Analisi e modellazione del processo. La prima condizione necessaria al miglioramento del processo è la sua conoscenza. L’analisi del processo consiste nella caratterizzazione del processo di sviluppo esistente in termini di attività svolte e attori coinvolti. Tutto ciò viene ottenuto tramite questionari/interviste/studio del flusso documentale del processo o osservazione esterna del processo. Mentre, la modellazione del processo è la realizzazione di documentazione di riferimento che stabilisca precisamente lo svolgimento del processo. Tale attività fa uso di strumenti grafici di modellazione come ad esempio i diagrammi di attività.
In un modello di processo dettagliato possono essere inclusi diversi elementi quali:
Come ultima fase del ciclo di miglioramento del processo abbiamo la modifica del processo. Per ottenere il miglioramento di un processo, può essere necessario modificarlo. Tra le possibili modifiche al processo possiamo avere:
Il cambiamento dovrebbe essere guidato da studi che ci permettono di prevedere il raggiungimento di benefici in termini di goals misurabili.
I cinque stadi chiave della procedura di modifica sono:
Valutazione della qualità dei processi. La qualità di un sistema SW è largamente determinata dalla qualità del processo usato per la sua produzione e manutenzione. È importante poter accertare la qualità di un processo, ciò implica la necessità di appropriati modelli di qualità di processo. Modelli di questo tipo sono detti modelli di accertamento e miglioramento. Essi consentono di valutare la capacità e la maturità di un processo di produzione e manutenzione del software e sono capaci di guidare il loro miglioramento.
Capability Maturità Model (CMM) è il meta-modello di riferimento utilizzato per valutare e migliorare la capability di un processo nell’eseguire tale disciplina. La Capability è l’adeguatezza di un processo rispetto alle attività produttive dell’azienda, mente, la Maturità è il grado di consolidamento di un processo all’interno dell’azienda. Il CMM è un meta-modello di processo che individua un insieme di funzionalità di ingegneria che dovrebbero essere presenti man mano che le organizzazioni raggiungono diversi livelli di capacità e maturità del processo. Per ottenere tali capacità, una organizzazione dovrebbe sviluppare un modello di processo conforme alle indicazioni del CMMI (CMM Integration). Il modello CMMI si compone di:
Le aree del processo CMMI sono:
(Slide pag 17)
La valutazione dei CMMI. Una organizzazione che intende adottare i CMMI viene valutata formalmente in ciascun area del processo sulla base di 6 livelli di capacità, verificando che raggiunga degli obiettivi associati a ciascun area. Col livello 0 incopleta, fino a livello 5 ottimizzata…
Esistono due modalità di applicazione del modello CMMI: Staget e Continuous. Nel primo caso si cerca di dare una valutazione generale della maturità di tutto il processo di sviluppo, secondo una scala composta di 5 livelli, per poter soddisfare un certo livello, l’organizzazione deve garantire il soddisfacimento di determinati obiettivi. Nel secondo caso si cerca di dare una valutazione (su 5 livelli) ad ogni aspetto del processo. La valutazione globale del processo è una combinazione delle valutazioni degli elementi.
CMM Staged viene dato un giudizio globale sulla maturità dell’intero processo di sviluppo software, secondo una scala a 5 valori che va da Initial (assenza di controllo di processo) fino a Optimising (processi di miglioramento del processo sono costantemente eseguiti). Lo Staged CMMI è paragonabile al CMM software. Ogni livello di maturità ha le proprie aree di processo e goals. Per raggiungere un dato livello di maturità occorre, in tutte le aree di processo ririchieste per quel livello, aver raggiunto almeno un certo livello di capacità.
Con il Continuous CMMI model la valutazione della maturità non avviene prendendo in considerazione l’intero processo nel suo complesso, ma cercando di dare un giudizio di maturità ad ogni sottoprocesso che è possibile individuare nell’ambito del processo complessivo. Si ottiene un valore di maturità per ogni area. Un valore complessivo può essere tratto da una media ponderata tra tutti questi valori. Uno dei vantaggi che si ha nell’utilizzare tale modello è nel fatto che è più semplice sapere come migliorare il processo al fine di acquisire un migliore livello CMMI.
MANUTENZIONE E REVERSE ENGINEERING
Nella pratica, qualsiasi software, dopo il rilascio della prima release, può aver bisogno di essere modificato. L’evoluzione di un software è inevitabile, ciò è dovuto a molteplici motivi quali ad esempio: nuovi requisiti possono emergere dopo il rilascio, il dominio del software può evolvere, errori possono essere individuati e devono essere corretti, nuove tecnologie hardware e software possono affermarsi nel frattempo, può essere necessario migliorare la qualità del software. Ogni volta che si afferma il bisogno di fare dei cambiamenti in un software, ci su pone di fronte alla scelta fra:
Tale scelta dovrà tener conto di fattori come gli enormi investimenti già fatti nello sviluppo del sistema software e l’evoluzione di sistemi esistenti consente di non perdere il residuo valore di mercato di tali sistemi.
La manutenzione del software è il processo di modifica di un prodotto software dopo il suo rilascio in esercizio. Gli interventi di manutenzione possono essere classificati nei seguenti modi:
Ciascuno di questi interventi richiede un determinato sforzo.
Anche lo standard IEEE propone una sua classificazione delle modifiche in: correttiva, adattiva, percettiva, preventiva e di emergenza. Parecchi ricercatori hanno proposto delle leggi relative all’evoluzione del software tra cui Lehman e Belady basandosi su studi empirici basati sull’osservazione dell’evoluzione di particolari SW.
I problemi della manutenzione risiedono in gran parte nella mancaza di controllo e disciplina nelle fasi di analisi e progetto del Ciclo di Vita del Software. Alcuni fattori tecnici sono:
Di solito lo sforzo legato al processo di manutenzione è nettamente maggiore di quello legato al processo di sviluppo. Tra le cause di ciò vi sono:
Manutenibilità. Il costo e l’efficacia degli interventi di manutenzione dipendono direttamente dal fattore di qualità denominato manutenibilità. Su un software sviluppato in maniera modulare e flessibile sono più semplici gli interventi di manutenzione evolutiva e correttiva. Per poter effettuare un intervento di manutenibilità è necessario essere in possesso di quante più documentazione possibile e bisogna comprendere appieno il funzionamento del software da modificare. Per poter prevedere la difficoltà degli interventi di manutenibilità sono state svolte parecchie ricerche per individuare fattori e metriche correlate allo sforzo di manutenzione. Tra i fattori che la determinano abbiamo:
Tra le metriche utilizzare abbiamo:
Il processo di manutenzione presentato può essere evitato e ridotto, ad esempio, in caso di manutenzione “d’urgenza”:
In questo caso si possono anticipare il più possibile le fasi di implementazione del cambiamento, ma la qualità complessiva del software si ridurrà. Eventualmente potrebbe essere utile un successivo intervento di aggiornamento della documentazione e/o di miglioramento della qualità del software (Reengineering).
La reingegnerizzazione (reengineering) di un sistema software è un’attività nella quale si vuole migliorare la qualità di un software esistente, allo scopo di rendere più semplici gli interventi di manutenzione. Essa può comprendere:
La ricodumentazione, implicando la comprensione del software da reeingegneerizzare, è una operazione preliminare necessaria alla ristrutturazione e alla riscrittura del software.
La restructuring o refactoring è un’attività che trasforma il codice esistente in codice equivalente dal punto di vista funzionale, ma migliorando dal punto di vista della sua qualità. Il reegineering è l’attività di ristrutturazione a livello della riorganizzazione dell’architettura modulare di un sistema; si parte da una certa organizzazione dei moduli del sistema e del relativo flusso dati, e se ne producono altre con migliori caratteristiche di qualità, come ad esempio, la riduzione dell’accoppiamento fra moduli, il controllo dell’uso di variabili globali e la riorganizzazione di data repository e così via.
L’ingegneria diretta inizia da una specifica del sistema e richiede la progettazione e l’implementazione di un nuovo sistema; la re-engegnerizzazione comincia con un sistema esistente e il processo di sviluppo per la sostituzione è basato sulla comprensione e trasformazione del sistema originale.
Le attività del processo di reengegnerizzazione sono:
I possibili approcci alla re-ingegnerizzazione sono molteplici e i costi si differiscono andando dalla soluzione più economica riguardante la traduzione del codice sorgente fino a giungere alla re-ingegnerizzazione come parte di migrazione architetturale che occupa la soluzione più onerosa.
Vi sono diversi fattori che influenzano i costi di re-ingegnerizzazione:
Evoluzione dei sistemi ereditati. Le organizzazione che hanno un budget limitato per la manutenzione e l’aggiornamento dei sistemi ereditati, devono decidere come ottenere il miglior profitto dai loro investimenti; questo significa fare una valutazione realistica dei sistemi ereditati e poi scegliere, tra le quattro opzioni più comuni, la strategia più adatta per farli evolvere. Ovvero tra:
Il reverse Engineering è un insieme di teorie, modelli, metodi, tecniche e tecnologie per il progetto e l’implementazione di processi di estrazione ed astrazione di informazioni da componenti di un sistema software esistente e la produzione di nuovi componenti ad un livello di astrazione maggiore e consistenti con quelli di partenza; e l’aggiunta ai componenti prodotti di conoscenza ed esperienza che non può essere ricostruita direttamente ed automaticamente dai componenti analizzati.
I problemi indicibili riguardanti il Reverse Engineering sono che non è possibile, a partir dal solo codice, astrarre il progetto da quale esso è stato prodotto (mentre non è in decidibile il problema di astrarre un progetto coerente con il codice); infine, non è possibile, a partire dal solo programma oggetto, astrarre il programma sorgente da quale esso è stato prodotto (mente non è in decidibile il problema di astrarre un programma sorgente che generi il dato programma oggetto).
In generale i problemi del Reverse Engineering sono che il processo di produzione del software è costellato di pozzi nei quali si perde parte della conoscenza: non tutta la conoscenza ed esperienza messa in campo dall’ingegnere del software in una fase di produzione viene in qualche forma rappresentata nello stesso prodotto in fase o in quello delle fasi successive. Questo comporta che ai problemi di indecidibilità si aggiungono quelli dovuti alla perdita di conoscenza che richiedono, per la realizzazione completa di un’astrazione, l’aggiunta di conoscenza ed esperienza da parte dell’ingegnere del software.
Gli scopi del Reverse Engineering sono:
Si distinguono due fasi nel Reverse Engineering:
Analisi statica VS Analisi dinamica. L’analisi statica deve essere effettuata necessariamente sul codice sorgente, quindi possibile solo per gli sviluppatori dell’applicazione. Inoltre occorre estrarre solo un sottosistema delle informazioni, ad esempio, nei software OO non può estrarre gli oggetti istanziati dinamicamente. Infine non a modificare il codice sorgente. Mente l’analisi dinamica può essere effettuata anche dagli utenti dell’applicazione. Potenzialmente l’analisi dinamica può estrarre tutte le interazioni che vengono in essere durante l’esecuzione del software. Inoltre essa necessita di sonde da inserire nel codice. Un’ulteriore fattore caratterizzante l’analisi dinamica è che essa non ha una terminazione.
Sistemi Legacy. I sistemi ereditati hanno vari difetti, in quanto un sistema legacy:
Il sistema legacy, però, obbliga il management a cercare soluzioni e strategie di manutenzione vincenti . Ci sono svariate alternative disponibili:
I sistemi legacy si possono dividere in quattro categorie:
L’assegnazione dei valori economici deve essere effettuata tenendo in considerazioni diversi punti di vista, e identificando gli stakeholder del sistema, e porre loro una serie di domande sul sistema.
Per valutare un sistema software da una prospettiva tecnica occorre considerare sia il sistema applicativo sia l’ambiente in cui il sistema opera: l’ambiente comprende l’hardware e tutto il software di supporto associato. I fattori da considerare nella valutazione ambientale son:
mentre per la valutazione delle applicazioni vengono utilizzati fattori quali:
Un processo decisionale va condotto in maniera strutturata, in quanto serve a stabilire l’obiettivo, determinare le alternative possibili, raffinare i criteri di selezione e stabilir le regole di decisione. Sono da considerare diversi punti di vista quali gli utenti, i manutentori, l’organizzazione proprietaria … esistono poi differenti rischi e benefici associati a ciascuna alternativa. Infine, esistono differenti fattori strategici che guidano la scelta.
Nel processo decisionale i passi fondamentali sono:
(vedere slide 28-29)
Esistono diversi modelli di processo per la manutenzione del software. I modelli di riparazione veloce (quick-fix model) hanno le seguenti caratteristiche:
I modelli di miglioramento iterativo (iterative-enhancement model) hanno le seguenti caratteristiche:
(slide 30)
STIMA DEI COSTI SFOTWARE E LA GESTIONE DEI RISCHI (parte 10)
I problemi fondamentali della stima sono il valutare quanto vale lo sforzo richiesto per completare un’attività, oppure, quanto tempo è necessario per completare l’attività, o ancora, quale è il costo totale di un’attività. La stima dei costi e la tempistica di un progetto sono in genere eseguite insieme. I fattori fondamentali dei costi sono:
Le stime servono a prevedere i costi di sviluppo di un sistema software. Il prezzo da pagare dovrebbe essere la somma del costo più il profitto, ma la relazione fra costi di sviluppo e prezzo richiesto al committente non è, in genere, semplice. Ci possono essere svariate considerazioni di tipo organizzativo, economico, politico ed aziendali che possono influire sul prezzo che verrà richiesto.
I fattori che determinano il prezzo di un software sono:
Per stimare i costi di sviluppo, può essere necessario conoscere la produttività degli ingegneri. La produttività è una misura della velocità con cui gli ingegneri producono software e la relativa documentazione. La misure per la produttività sono: Size related measurer basate su qualche output del processo, e Function-related measures basate su una stima della funzionalità del software rilasciato.
Ad esempio, alcune stime della produttività per sistemi Real-time embedded sono tipicamente 40 – 160 LOC/P-month, per una programmazione di sistema tra i 150 – 400 LOC/P-mounth e per applicazioni commerciali, intorno ai 200 – 900 LOC/P-mountf. La produttività è influenzata da vari fattori.
Tutte le misure di produttività basate sul rapporto volume/unità di tempo sono difettose perché considerano solo la quantità e non la qualità di ciò che si produce. La produttività potrebbe essere aumentata a discapito della qualità. La metrica di produttività più semplice (LOC/pm), usando le LOC, non è un indicatore valido in assoluto ma dipende da vari fattori. Es. più il linguaggio è di basso livello, più il programmatore risulta produttivo, oppure, più il programmatore è verboso, pià risulta produttivo…
Se si usano i FP, il conteggio di FP dipende da valutazioni soggettive sulla complessità.
Nelle tecniche per la stima dello sforzo non c’è un modo semplice ed efficace per fare stime accurate nei primi stadi del progetto. Le stime iniziali sono formulate in genere su requisiti definiti solo ad alto livello. Il software può dover funzionare su computer “sconosciti” o usando nuove tecnologie. Il personale assegnato al progetto può essere sconosciuto. Le tecnologie che cambiano continuamente comportano che le tradizionali tecniche di stima possono non funzionare più correttamente.
Abbiamo diverse tecniche di stima tradizionali dei costi:
Princing to win il progetto costerà quanto il committente ha intenzione di pagare, il vantaggio è che si ottiene il contratto, lo svantaggio è che la probabilità che il cliente ottenga ciò che richiede è piccola, in quanto i costi pagati non garantiscono tutto ciò che sarebbe richiesto.
Le tecniche viste fin ora possono essere usate sia in modo top-down che bottom-up. Top-down si parte dall’intero sistema e si analizza l’intera funzionalità e come questa sia via via ottenuta attraverso i vari sottosistemi. Bottom-up si parte dal livello dei singoli componenti e si stima lo sforzo di ciascuno di essi. Gli sforzi dei componenti si sommano per ottenere quello globale.
La stima Top-down è usabile anche senza alcuna conoscenza dell’architettura del sistema e dei suoi componenti. Essa considera anche i costi di integrazione, configuration management e di documentazione. Ma, può sottostimare i costi legati a problemi tecnici di basso livello.
La stima Bottom-up è usabile solo se l’architettura è nota ed i componenti ben identificati. Può essere un metodo di stima accurato se il sistema è stato progettato in dettaglio. Ma, può sottostimare il costo di attività a livello sistema quali l’integrazione e la documentazione.
Ogni metodo di stima ha i suoi vantaggi e svantaggi, per cui la stima dovrebbe basarsi su differenti metodi. Se i vari metodi non forniscono risultati comparabili, si deve concludere che non ci sono dati sufficienti per fare una stima valida. Occorrerà raccogliere ulteriori dati sul prodotto, sul processo o il team. La tecnica del Pricing to win è talora la sola tecnica applicabile in quanto ci si accorda su un costo di progetto in base ad una bozza di proposta e lo sviluppatore sarà vincolato da tale costo; e la specifica di dettaglio sarà negoziata successivamente o si userà un approccio di sviluppo evolutivo.
La stima per analogia si basa sul riuso delle esperienze accumulate in precedenti progetti, in quanto si valutano le analogie tra il nuovo progetto e progetti simili svolti in passato. Si applica quanto:
I requisiti per l’utilizzo di tale stima sono:
La stima per analogia viene eseguita valutando la quantità di lavoro (in mesi/uomo) e il tempo di sviluppo previsti. Vengono applicati dei coefficienti moltiplicativi al costo del progetto di riferimento che tengono conto degli scostamenti di determinate caratteristiche nel caso in esame.
I requisiti essenziali nella scelta del progetto di riferimento sono:
Le caratteristiche da considerare sono quelle che fanno riferimento direttamente al software.
Per quanto riguarda la modellazione algoritmica dei costi, il costo è stimato come una funzione matematica che tiene conto di attributi del prodotto, del processo e del progetto, stimati da project managers:
Effort= A * SizeB * M
Dove A è una costante dipendente dall’organizzazione; B tiene conto della non linearità tra lo sforzo e la dimensione indica che lo sforzo aumenta più che linearmente con le dimensioni; M è un moltiplicatore dipendente dal prodotto da realizzare, dal processo scelto e dalla tipologia delle persone coinvolte.
L’attributo di prodotto più usato per la stima è la dimensione del codice espressa in LOC o FP. I diversi modelli di stima algoritmica usano valori diversi per A, B e M. la stima dei fattori che contribuiscono a B e M è soggettiva.
Per usate tali modelli occorre stimare le dimensioni del software usando una possibile tecnica fra stima per analogia, traduzione dei FP in LOC, o usando giudizio di esperti.
È difficile stimare accuratamente le dimensioni quando si è nelle fasi iniziali dello sviluppo. Molti fattori influenzeranno la dimensione finale, come l’’uso di COTS e componenti, il programmino language, e il Distribution of system. La stima diventa più accurata col procedere del processo, man mano che le informazioni aumentano.
Il modello Costructive Cost Model (COCOMO) è un modello empirico basato su dati relativi ad esperienze di progetto proposto da Barry Boehm. Esso è un modello ben documentato ed indipendente dal venditore del software. Ha avuto una lunga storia di modifiche dalla versione iniziale (COCOMO-81) fino alla più redente (COCOMO 2). COCOMO 2 tiene conto di diversi approcci allo sviluppo software.
COCOMO 8 è un modello a te livelli (base, intermedio e dettaglio) in base al livello di accuratezza della stima proposta. Ogni livello propone stime diverse corrispondenti a tre diverse tipologie di progetti:
Per ognuno dei tre livelli sono definite le formule per la stima di sforzo e tempo di sviluppo secondo un dettaglio via via maggiore.
M= a * Sb (sforzo in man-mounth)
T= c * Md (tempo di sviluppo)
Dove S misurato in migliaia di DSI (Delivered Source Instructions) e “a, b, c, d” sono parametri dipendenti dal tipo di applicazione estratti da una tabella di valori.
Il COCOMO 81 proponeva un modello estremamente semplificato sia delle applicazioni che del ciclo di vita adottato. In pratica venivano utilizzati solo due parametri, ovvero DSI come metrica di prodotto e Tipologia come ulteriore metrica che tenesse in conto di tutti i fattori di processo, ma che può assumere solo tre valori possibili. A causa di queste limitazioni si sono introdotti modelli COCOMO più precisi fino ad arrivare al COCOMO 2.
COCOMO 2 comprende un insieme di modelli applicabili al variare delle modalità di realizzazione del software, che permettono stime più dettagliate e affidabili.
PM= (NAP * (1- %reuse/100))/PROD
Dove PM è lo sforzo in mesi-uomo, NAP il numero di Application Point, Prod la produttività.
Effort= A * SizeB * M
Dove M riflette vari aspetti quali à M=PERS*RCPX*RUSE*PDIF*PREX*FCIL*SCED
Ogni moltiplicatore è valutato su una scala da 1 a 6 e
Per codice generato automaticamente:
PMAuto= (ASLOC*AT/100)ATPROD
Dove ASLOC è il numero di linee di codice complessivo, AT è la percentuale di codice automaticamente generato, e ATPROD è la produttività degli ingegneri che si occupano dell’integrazione di codice.
Per codice riutilizzato e modificato:
ESLOC=ASLOC*(1-AT/100)*AAM
Dove ESLOC è il numero equivalente di righe di nuovo codice, ASLOC è il numero di linee di codice riutilizzato, e AAM è il coefficiente che indica la difficoltà di adattamento del codice.
Una dimensione del codice è stimata sulla base della somma di 3 componenti:
Il termine esponenziale B invece dipende da 5 fattori di scala valutati da 0 a 5. infine il coefficiente M dipende da 17 attributi.
Gestione dei Rischi. La gestione dei rischi consiste nella loro identificazione e nella proposta di soluzioni alternative che possono minimizzare gli effetti. Un rischio è definito come la probabilità che alcuni eventi avversi si verifichino durante la realizzazione di un processo software. Abbiamo tre diverse tipologie di rischi:
I rischi possono essere identificati in:
L’analisi del rischio esegue la valutazione della probabilità con la quale l’evento avverso si verifica e della serietà delle conseguenze connesse. La probabilità viene di solito stimata con una scala discreta che assume valori come (molto bassa, bassa, moderata, alta, molto alta). Gli effetti degli eventi avversi possono essere stimati anch’essi su scala discreta che assuma valori come (insignificante, serio, tollerabile, catastrofico).
La pianificazione dei rischi serve a:
Il monitoraggio del rischio serve a valutare dinamicamente, se i rischi diventano più o meno probabili e se i loro effetti cambiano di intensità. Vengono monitorati un insieme di indicatori di rischio, in maniera automatica o soggettiva. Ad ogni riunione del comitato che si occupa della gestione del progetto si dovrebbero riconsiderare i rischi effettivi, sulla base dei dati aggiornati provenienti dal monitoraggio.
ANTLR
ANTLR (Another Tool for Language Recognition) è un sofisticato generatore di parser che può essere utilizzato per implementare riconoscitori, compilatori, traduttori, strumenti di misura di metriche di prodotto per qualsiasi linguaggio, in particolare per i cosiddetti DSL (Domain-specific languages). Esempi classici di DSL sono i file dati, i file di configurazione, i protocolli di rete e molti altri linguaggi specifici di un dominio.
L’utilizzo di Antir non è consigliabile per quei linguaggi che sono dei dialetti di XML, in quanto, per essi, è più conveniente riutilizzare le librerie per il parsing XML.
Un traduttore lavora su di un documento in input, scritto in un certo linguaggio, e traduce ogni frase in un’altra nel linguaggio di output. Per realizzare questa trasformazione, esso esegue del codice sorgente realizzato ad hoc per il linguaggio da analizzare. Un traduttore dovrà quindi esibire comportamenti diversi per ognuno delle possibili tipologie di frasi trovate nel documento in input.
Il Lexer riconosce tokens (es keywords) dall’analisi del testo (analisi lessicale), il parser riconosce strutture formate da token (analisi sintattica). L’uscita più effciente da gestire è quella in forma AST (Abstract Syntax Tree).
ANTLR è in grado di generare automaticamente il codice sorgente dell’analizzatore lessicale e del parser sulla base delal descrizione della grammatica e delle elaborazioni conseguenti ai riconoscimenti dei token e delle strutture.
Gli automi deterministici a stati finiti (DFA) possono essere utilizzati come generatori di espressioni regolari. Un’espressione è regolare, se può essere associata ad un cammino sul DFA.
Le macchine a stati hanno un comportamento che non dipende dalla “memoria” della macchina. In pratica bisogna considerare stati diversi per ogni possibile valore della memoria, quindi il numero di stati andrebbe ad esplodere. Per questo motivo si preferiscono i Pushdown Automata che si tratta di Automi a Stati finiti dotati di memoria a stack. Un Pushdown Automata, essendo dotato di memoria, può essere visto come un insieme di submachine. Le transizioni tra uno stato e l’altro dipendono, oltre che dagli eventi che si verificano, anche dal valore di variabili memorizzate nello stack. Graficamente si indicano tramite Syntax Diagrams.
ANTLR supporta un riconoscimento delle frasi tipo Left Lookhead (LL). in pratica, data un’espressione come:
È possibile per il parser distinguere tra la prima e la seconda regola solo andando a vedere cosa accade 2 parole più avanti rispetto alla parola “int” (la presenza dell “=” anziché del “;”). In questo caso è necessario un parser LL (2). ANTL è in grado di generare parser che siano LL(*).
Gli obiettivi della traduzione sono quelli di costruire una grammatica che descriva la struttura sintattica delle espressioni e delle assegnazioni, e poi di scrivere del codice, contestualmente alal grammatica, che esegue le corrette azioni in conseguenza del riconoscimento delle espressioni della grammatica stessa. (slide 8-910).
AntlrWorks ha numerose funzioni che semplificano la realizzazione e il testing di grammatiche:
(slide 11-12-13).
GESTIONE DELLA CONFIGURAZIONE
La gestione della configurazione è un’attività fondamentale per ogni software. I sistemi software non vengono mai realizzati tutti una volta. Infatti abbiamo i cicli di vita evolutivi e i cicli di vita XP, nei quali si forza una integrazione della parti del software molto frequente, allo scopo di evidenziare al più presto eventuali problemi di integrazione. Dopo essere stati rilasciati, essi sono soggetti a numerosi interventi di manutenzione dovuti ad errori, cambiamento dei requisiti, estensioni, etc… Spesso uno stesso software necessita di essere rilasciato in diverse configurazioni:
Occorre innanzitutto eseguire una pianificazione in cui vengono fissati i seguenti punti:
Usualmente le richieste di cambiamento o change request form sono formalizzate attraverso la definizione di un apposito modulo di richiesta. Tra i campi sicuramente presenti ci sono:
Ogni versione si può identificare mediante:
Una release di un sistema è una sua versione che viene distribuita ai clienti. Essa comprende, tra l’altro:
Il processo di creazione e rilascio di una release deve quindi riuscire a gestire la generazione di tutti questi deliverables. In particolare, bisogna decidere se distribuire l’intero sistema oppure se distribuire unicamente delle patch di aggiornamento.
Gli strumenti CASE a supporto sono di due tipologie principali:
Il CVS (Courente Versioning System) è un sistema di controllo delle versioni di un progetto legato alla produzione e alla modifica di file. In pratica, permette a un gruppo di persone di lavorare simultaneamente sullo stesso gruppo di file, mantenendo il controllo dell’evoluzione delle modifiche che vengono apportate. Per attuare questo obiettivo, sistema CVS mantiene un deposito centrale dal quale i collaboratori di un progetto possono ottenere una copia di lavoro. I collaboratori modificano i file della loro copia di lavoro e sottopongono le loro modifiche al sistema CVS che le integra nel deposito. Il compito di un sistema CVS non si limita a questo, ad esempio, è sempre possibile ricostruire la storia delle modifiche apportate a un gruppo di file, oltre essere possibile ricostruire la storia delle modifiche apportate a un gruppo di file, oltre a essere anche possibile ottenere una copia che faccia riferimento a una versione passata di quel lavoro.
Il Modello Lock/Modify/Unlock in principio, era l’unico modello secondo il quale più programmatori accedevano in concorrenza ai diversi file di un progetto. Secondo questo modello un utente che vuole modificare un file del progetto, prima di tutto lo blocca (lock), impedendo a chiunque altro di modificarlo, dopodichè, quando ha terminato le modifiche lo sblocca (unlock). Questa strategia per quanto garantisca la massima sicurezza da problemi di manomissione contemporanea involontaria, non ottimizza nel modo migliore le operazioni.
Il Modello Copy/Modify/Merge è un’alternativa a quello LMU e prevede che :
Nel caso in cui due programmatori modificano lo stesso file, il sistema CVS può fondere (merge) le due versioni, sovrapponendo le modifiche, allorché si riferiscano a linee di codice diverse. Se invece ci sono modifiche alle stesse righe di codice si verifica un conflitto. La soluzione del conflitto è in questo caso demandata ai singoli programmatori, e la versione unificata che viene generata diventa la nuova versione di riferimento, oppure in alternativa, si potrebbe scegliere di mantenere entrambe le versioni come alternative, generando un branch.
Il sistema CVS è un software, presente per diversi sistemi operativi, che consente di gestire a linea di comando le principali operazioni previste dai modelli lock/modify/unlock e copy/modify/merge. Il lato server gestisce il repository, contenente sia tutti i file da gestire che tutte le informazioni sulle versioni. In alternativa il deposito potrebbe anche trovarsi sulla macchina client. Il lato client consente di effettuare tutte le operazioni riguardanti la copia locale del progetto.
Le operazioni dei CVS sono:
Ogni versione può essere annotata e ad essa possono essere aggiunte delle informazioni dette Tag. I tag sono particolarmente utili per distinguere tra loro le release di un software.
TortoiseCVS è un front-end client che rende l’uso di CVS più semplice, più intuitivo e più produttivo. Si inteffaccia direttamente con Windows Explorer. Uno dei maggiori vantaggi è quello di mostrare, per ogni comando dato da interfaccia, le corrispondenti operazioni a linea di comando effettuate. TortoiseCVS non include un CVS Server ma supporta la creazione di repository CVS locali. TortoiseCVS supporta anche una serie di operazioni di più alto livello:
Fonte: http://unina.stidue.net/Ingegneria%20del%20Software%202/Materiale/Dispensa%20Ingegneria%20del%20Software%20II.doc
Sito web da visitare: http://unina.stidue.net/
Autore del testo: non indicato nel documento di origine
Il testo è di proprietà dei rispettivi autori che ringraziamo per l'opportunità che ci danno di far conoscere gratuitamente i loro testi per finalità illustrative e didattiche. Se siete gli autori del testo e siete interessati a richiedere la rimozione del testo o l'inserimento di altre informazioni inviateci un e-mail dopo le opportune verifiche soddisferemo la vostra richiesta nel più breve tempo possibile.
I riassunti , gli appunti i testi contenuti nel nostro sito sono messi a disposizione gratuitamente con finalità illustrative didattiche, scientifiche, a carattere sociale, civile e culturale a tutti i possibili interessati secondo il concetto del fair use e con l' obiettivo del rispetto della direttiva europea 2001/29/CE e dell' art. 70 della legge 633/1941 sul diritto d'autore
Le informazioni di medicina e salute contenute nel sito sono di natura generale ed a scopo puramente divulgativo e per questo motivo non possono sostituire in alcun caso il consiglio di un medico (ovvero un soggetto abilitato legalmente alla professione).
"Ciò che sappiamo è una goccia, ciò che ignoriamo un oceano!" Isaac Newton. Essendo impossibile tenere a mente l'enorme quantità di informazioni, l'importante è sapere dove ritrovare l'informazione quando questa serve. U. Eco
www.riassuntini.com dove ritrovare l'informazione quando questa serve