Ingegneria del software

Ingegneria del software

 

 

 

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

INGEGNERIA DEL SOFTWARE

INTRODUZIONE E RICHIAMI

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 è:

  • intangibile , ovvero difficile da comprenderne la complessità, la qualità, lo sforzo necessario per lo sviluppo;
  • facile da riprodurre, in quanto i costi maggiori sono nel processo di progettazione e sviluppo, diversamente da altri prodotti industriali;
  • e richiede un grosso impegno intellettivo difficile da automatizzare.

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à:

  • Specifica, ovvero la definizione di ciò che il sistema dovrà fare e dei vincoli di progettazione;
  • Sviluppo, inteso come progettazione e programmazione;
  • Convalida, verifica che il software sia esattamente ciò che il cliente richiede;
  • Evoluzione, con cui avviene la modifica del software per adeguarlo a requisiti dell’utente e del mercato che cambiano.

Un modello di processo software è una descrizione semplificata del processo software osservato da un determinato punto di vista. Ci sono diverse possibili descrizioni:

  • workflow model, descrive la sequenza di attività;
  • data-flow model, evidenzia le trasformazioni dei dati operate dalle attività del processo;
  • role/action model, vengono modellati i ruoli delle persone coinvolte nel processo e le relative responsabilità.

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:

  • modelli di sistema, in cui troviamo la descrizione dei modelli grafici da sviluppare;
  • regole, ovvero i vincoli che i modelli devono rispettare;
  • raccomandazioni, ovvero i consigli che determinano la corretta procedura di progettazione;
  • guida ai processi, in cui si descrivono le attività di processo e della relativa organizzazione.

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:

  • Upper-CASE, i quali supportano le attività alte del processo, quali la specifica dei requisiti e la progettazione;
  • Lower-CASE, i quali supportano le attività successive, quali la programmazione, il debugging e il testing.

Un software di qualità dovrebbe fornire le funzionalità e le prestazioni richieste essendo contemporaneamente:

  • Manutenibile, in quanto il software deve essere in grado di evolvere per soddisfare le nuove richieste dei clienti;
  • Fidatezza, intesa come affidabilità, protezione e sicurezza;
  • Efficienza, in quanto il software non dovrebbe sprecare le risorse del sistema quali memoria e CPU;
  • Accettabilità, in quanto occorre che il software deve essere accettato dai suoi utenti e pertanto deve essere comprensibile, usabile e compatibile con altri sistemi.

L’ingegneria del software ha come obiettivo, nella produzione di software, che esso sia

    • eterogeneo;
    • che vengano rispettati i termini di consegna rispettando la qualità;
    • che sia dimostrata la fiducia mediante tecniche che dimostrino agli utenti l’affidabilità del sistema.

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:

  • Difficoltà accidentali: sono legate ad aspetti della produzione del software che generano la possibilità di commettere errori. Si risolvono mediante linguaggi ad alto livello, ambienti di sviluppo unificati…;
  • Difficoltà essenziali più difficili da rilevare, essi si classificano in difficoltà di:
    • Complessità, il software deve modellare e controlla re problemi complessi;
    • Conformità, la complessità del software deriva dalla sua necessità di sottostare ad un insieme di regole/interface poco chiare, perché dettate da persone diverse;
    • Variabilità, perché i requisiti del software variano molto velocemente, già al tempo stesso di sviluppo;
    • Invisibilità in quanto il software è invisibile e non visualizzabile: ciò rende difficile mantenere una vista complessiva.

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:

  • modello a cascata, prevede fasi ben distinti per la specifica dei requisiti e per lo sviluppo;
  • sviluppo evolutivo, specifica, sviluppo e validazione si accavallano;
  • ingegneria del software basata su componenti, il sistema viene assemblato a partire da componenti presistenti. Esistono però molte varianti di questi modelli.

Un processo software si compone delle seguenti attività:

  •  Specifica del Software, in cui vengono stabiliti quali servizi sono richiesti al software ed i vincoli sia per l’operatività che per lo sviluppo. Tale processo si compone dello studio dei Fattibilità, Raccolta ed Analisi dei requisiti, Specifica de requisiti, e Convalida dei requisiti.
  • Progettazione ed Implementazione, è il processo in cui si converte la specifica del sistema in un sistema eseguibile. La progettazione Software, ha l’obiettivo di progettare una struttura software che realizza le specifiche. L’implementazione ha il compito di tradurre la struttura in un codice eseguibile. La progettazione e l’implementazione sono attività correlate, non necessariamente sequenziali, ma possono intrecciarsi.

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.

  • Convalida del Software o processo di testing, tale processo punta a mostrare che il software è conforme alle sue specifiche e soddisfa le aspettative del cliente. Si compone di due azioni, ovvero della Verifica  e della Validazione.

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.

  • Evoluzione del software, in quanto il software è flessibile e può cambiare, man mano che i requisisti cambiano. È giusto considerare il processo di ingegneria del software come un processo evoluzionistico, in cui il software è continuamente modificato.

Vi sono vari modelli di processo software tra cui:

      • Il waterfall model che si compone di varie attività quali: analisi e definizione dei requisiti, progettazione del sistema e del software, implementazione e testing di unità, integrazione e testing di sistema, operatività e manutenzione.

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.

      • Lo sviluppo evolutivo si basa sull’idea di produrre una versione iniziale del software, esporla agli utenti e perfezionarla attraverso varie versioni. Vi sono due modelli fondamentali: sviluppo esplorativo, dove l’obiettivo è di lavorare col cliente per esaminare i requisiti iniziali e fali evolvere fino al sistema finale, si dovrebbe partire da pochi requisiti ben compresi e aggiungere nuove caratteristiche proposte dal cliente; prototipo usa e getta, in cui l’obiettivo è la comprensione dei requisiti del sistema, si parte da requisiti poco chiari e si realizzano prototipi per esplorare e requisiti e chiarirli.

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:

  • Mock-ups che riguarda la produzione completa dell’interfaccia utente
  • Breadboards che si focalizza sull’implementazione di sottosistemi di funzionalità critiche del Sistema Software, nel senso dei vincoli pesanti che sono posti nel funzionamento del SS, senza le interfacce utente. Produce feedbacks su come implementare la funzionalità.
  • Lo sviluppo basato su componenti si basa sul riuso sistematico, integrando componenti esistenti o interi sistemi commerciali. Le fasi di tale processo sono: specifica dei requisiti, analisi dei componenti, modifica dei requisiti, progettazione con riuso, sviluppo e integrazione. Questo approccio viene sempre più usato grazie all’affermarsi di appositi standard per la specifica di componenti.

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:

      • Avvio, in cui vengono stabiliti gli obiettivi di business (limiti, fattibilità) per il sistema;
      • Elaborazione, mira ad ottenere una comprensione del dominio del problema, e a stabilire una struttura architetturale ed il piano di progetto;
      • Costruzione, ovvero progettare, programmare e testare il sistema incrementalmente;
      • Transizione, che mira a trasferire il sistema nel suo ambiente operativo. Ogni fase può essere eseguita in modo ciclico, con risultati sviluppati in modo incrementale, e l’intero insieme delle fasi può essere eseguito in modo incrementale.

La prospettiva statica si concentra sui workflow. Il RUP prevede vari workflow (6 principale e 3 di supporto) essi sono:

  • Modellazione processi di business, utilizzando i business case;
  • Requisiti, vengono identificati gli attori che interagiscono col sistema e sviluppati casi d’uso per modellare i requisiti di sistema;
  • Analisi e Progettazione, viene creato e documentato un modello di progetto utilizzando modelli architetturali, dei componenti, degli oggetti e sequenziali;
  • Implementazione, i componenti del sistema sono implementati e strutturati nell’implementazione dei sottosistemi. La generazione automatica del codice a partire dai modelli di progetto accelera questo processo;
  • Test, è un processo iterativo eseguito in parallelo all’implementazione. Il testi di sistema segue il completamento di questa;
  • Rilascio, viene creata una release del prodotto, viene distribuita agli utenti e installata nelle loro postazioni di lavoro.

 mentre quelli di supporto sono:

  • Gestione della configurazione e delle modifiche, atto a gestire i cambiamenti al sistema;
  • Gestione del progetto, atto a gestire lo sviluppo del sistema
  • Ambiente, rende disponibile al team di sviluppatori del software gli strumenti adeguati.

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:

  • sviluppare il software iterativamente, ovvero pianificare gli incrementi in base alle priorità del cliente;
  • gestire i requisiti, ovvero documentare esplicitamente i requisiti ed i cambiamenti effetuati;
  • usare architetture basate su componenti, che consiste nel strutturare l’architettura con un approccio a componenti;
  • creare modelli visuali del software, ovvero usare modelli grafici UML per rappresentare viste statiche e dinamiche del sistema;
  • verificare la qualità del software, ovvero verificare l’aderenza a standard di qualità aziendali;
  • controllare le modifiche al software, ovvero gestire i cambiamenti e le configurazioni del software.

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:

  • l’impatto organizzativo, che può portare ad una riorganizzazione completa del lavoro;
  • l’impatto culturale, che può comportare una ridefinizione del modo di lavorare;
  • i suoi costi tecnologici, in quanto l’utilizzo del processo è agevolato dall’uso di strumenti specifici;
  • e infine, ai suoi costi di avvio, che interessano l’adattamento adel processo, la divulgazione e l’inquadramento dei processi esistenti.

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:

  • tolleranza del modello ai rischi che si potranno incontrare;
  • facilità di comunicazione tra sviluppatori e utilizzatori;
  • stabilità dei requisiti noti; probabilità di esistenza di requisiti non ancora noti;
  • importanza di rilasci ‘early’ di parziali funzionalità;
  • intrinseca complessità del problema e delle probabili soluzioni;
  • anticipata conoscenza di frequenza e dimensione di richieste di cambiamento;
  • maturità dell’applicazione;
  • disponibilità e priorità di fondi;
  • flessibilità dello scheduling e budget;
  • criticità del rispetto di scheduling e budget;
  • adeguatezza del processo ‘istituzionale’ e tool di sviluppo dello sviluppatore;
  • corrispondenza tra organizzazione del management e il modello da adottare.

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:

  • standard per il processo di documentazione, in cui vengono specificati il come i documenti vanno sviluppati, valicati, e manutenuti;
  • standard per i documenti, essi descrivono la struttura, il contenuto e l’editing del documento. Essi si dividono in vari standard tra cui: standard per l’identificazione di un documento, ovvero come i documenti sono univocamente identificati; 2) standard per la struttura di un documento, che identifica  la struttura standard per i documenti del progetto; 3) standard per la presentazione di un documento, il quale definisce i font, gli stili, l’uso dei loghi etc..; 4) standard per l’aggiornamento di documenti, essi definiscono il come le modifiche di una precedente versione si riflettono in un documento;
  • standard per lo scambio/condivisione di documenti, tali standard definiscono come i documenti vanno memorizzati e scambiati/condivisi tra differenti sistemi di documentazione. I documenti sono prodotti usando differenti sistemi di computers. Gli standard di inter-scambio consentono che documenti elettronici siano scambiati, inviati per e-mailt, etc… Essi necessitano di archiviazione. Attualmente XML si sta ergendo quale standard per lo scambio di documenti.

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 semplicità di scrittura della documentazione;
  • possibilità di valutare e verificare velocemente la completezza della documentazione;
  • possibilità di generare automaticamente manuali utente e diagrammi statici di dettaglio.

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:

  • fattori che descrivono il software da un punto di vista esterno (gli utenti);
  • criteri che descrivono gli elementi su cui agiscono gli sviluppatori per soddisfare i requisiti del cliente;
  • metriche che servono a controllare che i criteri sviluppati corrispondono ai fattori specificati. Tale modello si compone di 3 settori e 11 fattori. Abbiamo il settore dell’uso del prodotto, ovvero ciò che emerge quando si tiene il software in esercizio, e fanno parte fattori quali: correttezza, affidabilità, efficienza, integrità e sicurezza, eusabilità.

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:

  • quality model, un insieme di caratteristiche di qualità che possono essere in grado di descrivere i principali fattori di qualità di un prodotto software;
  • external metrics, è un insieme di metriche indirette attraverso le quali sia possibile valutare la conformità di un prodotto software al modello di qualità;
  • internal metrics, è un insieme di metriche direttamente misurabili che possono essere utilizzate allo scopo di valutare le External Metrics; ed infine 4) quality in use metrics, sono delle metriche rivolte alla valutazione del sottoinsieme di caratteristiche di qualità legate all’utente.

A determinare la qualità di un SW concorrono 3 punti di vista:

  • Esterna esprime il comportamento dinamico del software, nell’ambiente d’uso. Tale qualità è rappresentata dalle prestazioni del prodotto e dalle funzionalità.
  • Interna (intrinseca) esprime la misura in cui il codice software possiede una serie di attributi statici, indipendentemente dall’ambiente di utilizzo e dall’utente. La qualità interna rappresenta le proprietà intrinseche del prodotto. Si realizza a partire dai requisiti di qualità dell’utente e dalle specifiche tecniche (quelle date dall’utente e tradotte dallo sviluppatore nell’architerttura SW, nel programma e nelle interfacce).
  • Percepita (in uso) esprime l’efficacia ed efficienza con cui il software serve le esigenze dell’utente, ed è correlata alla percezione diretta dell’utente. La qualità in uso riguarda il livello con cui il prodotto si dimostra utile all’utente nel suo effettivo contesto d’utilizzo, in particolare la capacità del prodotto di dare efficacia ed efficienza al lavoro dell’utente, a fronte di una sicurezza di utilizzo e di una soddisfazione nel far uso del prodotto.

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:

  • attributi esterni, sono attributi di un’entità che sono visibili e di interesse per l’utente del prodotto software indipendentemente dalla implementazione;
  • attributi interni, sono attributi di un’entità che sono visibili e di interesse del produttore, è dipendente dalla implementazione.

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:

  • prodotti, tutto ciò che  viene prodotto nel CVS;
  • processi, produzione specifiche, progettazione, codifica, testing, manutenzione, riuso …;
  • risorse, hardware, software, documentazione,….

 

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:

  • nominale, la realizzazione tra i valori consente una semplice classificazione degli oggetti della misurazione, ma non ci indica nulla sulla relazione fra di essi;
  • ordinale, si introduce una relazione d’ordine fra gli oggetti, per cui si è in grado di determinare le posizioni relative degli oggetti, però non si è in grado di quantificare la distanza fra gli oggetti;
  • intervalli, si è in grado di misurare la distanza fra gli oggetti del dominio, e non solo posizionarli tra di loro come con le scale ordinali;
  • ratio, introduce l’elemento zero che rappresenta l’assenza totale dell’attributo che si sta misurando nell’entità sottoposta a misurazione;
  • assoluta, esiste una strategia di conteggio per la quale possiamo assegnare ad ogni oggetto un numero univocamente determinato, la scala assoluta è usata per quegli attributi di un oggetto che richiedono un semplice conteggio di elementi.

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:

  • Metriche per il modello di analisi, consentono al management di monitorare e controllare costi, scheduling e qualità. Esse sono metriche basate sulle funzionalità Function Points (FP) e metriche per la qualità delle specifiche. La Fuction Point Analysis è una tecnica con un approccio indipendente dal linguaggio di programmazione per misurare le funzionalità del sistema. Essa può essere usata per: stimare il costo richiesto per programmare, prevedere il numero di errori che si rileveranno durante il testing, e prevedere il numero di componenti o di LOC del sistema. I FP sono una misura di funzionalità basata su entità logico-funzionali che l’utente facilmente comprende. Essi sono indipendenti dal linguaggio di programmazione. Un FP non è una singola caratteristica ma una combinazione di caratteristiche del sistema. I FP vengono derivati usando una relazione empirica che si basa su misure dirette nel dominio delle informazioni del software, e dalla valutazione della complessità del software. Le misure del dominio dell’informazione sono catturate con riferimento al confine del sistema software. Secondo il concetto di Boundary (confine) esiste una linea chiusa che definisce il confine del sistema software cui è rivolta la misura. Con riferimento al confine, sono misurate le seguenti caratteristiche:
  • external input (EI) rappresenta un tipo di input unico fornito da un utente o da un altro sistema, che verrà elaborato dall’applicazione. Questi input tipicamente devono aggiungere, cambiare o cancellare dati in un File Logico Interno (ILF). I dati possono essere informazioni di controllo o informazioni del dominio applicativo. Se i dati sono informazioni di controllo non devono aggiornare nessun ILF.
  • external output (EO) è un tipo di dati unico che viene creato nell’applicazione ed è destinato all’utente,o ad altre applicazioni. Esso consiste in report, schermate, file, messaggi d’errore. Può aggiornare un ILF, e tali reports sono creati a partire da uno o più ILF e EIF.
  • external inquiry (EQ) è definita come una coppia input/output unica, dove un input ondine produce la generazione di una risposta immediata del software, sotto forma di output ondine. Una EQ ricerca dati o informazioni di controllo per una risposta immediata. Il processo di input non deve aggiornare nessun ILF, mentre il processo di output non contiene dati “derivati”. Se c’è coinvolgimento di updating del file logico interno o una produzione di dati derivati o calcolati, deve essere considerato un input seguito da un output.
  • internal logical file è un raggruppamento unico di dati logicamente correlati identificabile dall’utente, usato ed aggiornato dall’applicazione. I dati relativi non attraversano il confine, ma risiedono internamente al confine dell’applicazione e sono gestiti attraverso gli external input.
  • extarnal interface file è un unico gruppo di dati logicamente correlati identificabile dall’utente che viene usato dall’applicazione.

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 per ognuno degli attributi di qualità dell’SRS ad esempio l’assenza di ambiguità.

  • Metriche di progetto architetturale si concentrano sulle caratteristiche dell’architettura del software, non tenendo conto della struttura interna dei singoli moduli. Si basano sull’analisi di modelli di progetto nei quali si evidenziano i moduli del sistema e i dati che vengono scambiati tra essi. Sono molto semplici da misurare, ma poco affidabili in quanto a legame con lo sforzo effettivo legato allo sviluppo del sistema. La complessità architetturale è vista come funzione del fan-in e fan-out dei moduli. La structural complexity = f(fan-out); la data complexity(mi) = f(delle variabili interne ed esterne, e dal fan-out); il system complexity = f( della strucutral e del data complexity).

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

    • WMC (Weighted Method per Class) è la somma peseta dei metodi di una classe. Il peso di un metodo è dato da un fattore di complessità a scelta. Al crescere del WMC aumenta la complessità della classe e diminuisce il riuso.
    • DIT o profondità dell’albero di ereditarietà di una classe è la distanza massima di un nodo dalla radice dell’albero rappresentante la struttura ereditaria. Maggiore è la profondità della classe nella gerarchia, maggiore è il numero di metodi che essa può ereditare, rendendo più complesso predire il suo comportamento e maggiore è il riuso dei metodi ereditati. Alberi di ereditarietà con elevata profondità possono aumentare la complessità del progetto.
    • NOC o numero di figli è il numero di sottoclassi, figlie di una super-classe. Al crescere del NOC, cresce il livello di riuso, ma tende a evaporare l’astrazione della classe madre e aumenta la possibilità che una classe figlia non sia membro appropriato della madre.
    • RFC o risposte per classe è l’insieme dei metodi che piossono essere potenzialmente eseguiti ad un messaggio ricevuto da un oggetto della classe. Esso è un indicatore del volume di interazione fra classi, a valori elevati aumenta la complessità progettuale della classe e cresce lo sforzo per il testing.
    • CBO o accoppiamento tra le classi rappresenta il numero di collaboratori di una classe, ovvero il numero di altre classi cui essa è accoppiata. L’accoppiamento può avvenire a seguito di lettura/modifica attributi, chiamata metodi, istanziazioni oggetti. Un eccessivo accoppiamento è negativo per la modularità e il riuso.
    • LCOM o mancanza di coesione nei metodi. Dove per coesione  di un metodo esprime la proprietà di un metodo di accedere in maniera esclusiva ad attributi della classe. La mancanza di coesione deriva dalla presenta di più metodi che accedono ad attributi comuni. Un LCOM elevato comporta un aumento della complessità del design della classe.

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.

  • Metriche per il codice sorgente si divide a sua volta in linee di codice e metriche di Halstead.

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:

  • Vocabolario: n=n1+n2
  • Lunghezza: N=N1+N2
  • Volume: V=N*log22

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.

  • Metriche per il testing si riferiscono al processo di testing. Per progettare ed eseguire l’attività di testing, possono essere utili le metriche del livello analisi, design e codice. Per prevedere l’impegno delle varie attività di testing si possono usare: metriche per la misura della funzionalità per pianificare il testing black-box, metriche per il design architetturale per stimare la complessità del testing di integrazione e la qualità di driver e stub da sviluppare, la complessità ciclomatica è utile per avere una misura della complessità del testing di unità. Inoltre è possibile relazionare sforzo e durata del testing, errori scoperti e numero di test case prodotti ai FP. Le metriche di Binder servono a valutare  la testabilità di sistemi OO, esse valutano: la mancanza di coesione nei metodi, percentuali di attributi pubblici e protetti, accesso pubblico ai dati, numero di classi radice, Fan-in, numero di figli e profondità dell’albero di ereditarietà.

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:

  • livello produttore il quale si compone di :
    • test dei componenti, ovvero il testing di singole unità di codice;
    • test di sistema sono testing di gruppi di componenti integrati per formare il sistema
    • test di integrazione richiede la costruzione del sistema a partire dai suoi componenti ed il testing del sistema risultante per scoprire componenti problemi che nascono dall’interazione fra i vari componenti, esse possono essere condotte o top-down o bottom-up o misto. In questo tipo di testing si costruiscono moduli guida che invocano l’unità sotto test, inviandole opportuni valori, e si realizzano moduli fittizi, i quali sono invocati dall’unità sotto test ed emulano il funzionamento della funzione chiamata rispetto al caso di test richiesto.
  • livello produttore-utente privilegiato si compone dell’Alpha testing (uso del sistema da parte di utenti reali ma nell’ambiente di produzione) e del Beta testing (uso del sistema in un ambiente reale). Essi sono tipicamente utilizzati dai produttori di package per mercato di massa.
  • livello utente che riguarda il testing di accettazione il quale è una più una dimostrazione che un test. Esso è effettuato sull’intero sistema sulla base di un piano e di procedure approvate dal cliente. L’obiettivo è quello di mettere il cliente, l’utente o altri a ciò preposti in condizione di decidere se accettare il prodotto. Occorre provare che il software fornisce tutte le funzionalità, le prestazioni, l’afficabilità, richieste. In genere è un testing black box basato solo sulle specifiche del software ma i tester non accedono al suo codice.

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:

  • la valutazione dei risultati del test è una condizione necessaria per effettuare un test, ovvero conoscere il comportamento atteso per poterlo confrontare con quello osservato. Viene utilizzato a tal scopo l’oracolo il quale conosce il comportamento atteso per ogni caso di prova. Esistono due tipi di Oracolo: Oracolo umano e Oracolo automatico.
  • la terminazione del testing, dal momento che il testing esaustivo e inraggiungibile, vi sono altri criteri per valutare quando il testing possa terminare: criterio temporale, criterio di costo, criterio di copertura (legato a un criterio di selezione del test), criterio statistico.
  • Problema della selezione dei casi test. Un test è costituito da un insieme di casi di test. L’esecuzione di un test consiste nell’esecuzione del programma per tutti i casi di test. Un test ha successo se rileva malfunzionamenti. Un test è ideale ed esaustivo se l’insuccesso del test implica la correttezza del programma, e contiene tutti i dati di ingresso al programma. Dato che è un’idealizzazione occorre selezionare casi di test che approssimano un test ideale. Il criterio di selezione dei casi di test specifica le condizioni che devono essere soddisfatte da un test e consente di selezionare più test per uno stesso programma. Un criterio di selezione di test C è affidabile se per ogni coppia di test selezionati da C, T1 e T2, se il test T1 ha successo, allora anche T2 ha successo e viceversa. Un criterio di selezione di test C è valido per un programma se, qualora il programma non è corretto, esiste almeno un test selezionato da C che ha successo. Se un criterio è affidabile e valido, allora qualsiasi test T generato da C per un programma non corretto avrà successo. (Esempio slide 21). Secondo Goodenough e Gerhart “il fallimento di un test T per un programma P, selezionato da un criterio C affidabile e valido, permette di dedurre la correttezza del programma P”. mentre  per Hownden “non esiste un algoritmo che, dato un programma P, generi un test ideale finito, e cioè un test definito da un criterio affidabile e valido”. Al di là di casi banali, non è possibile costruire un criterio di selezione generale di test valido e affidabile che non sia il test esaustivo. Quindi gli obiettivi pratici sono quelli di massimizzare il numero di malfunzionamenti scoperti e minimizzare il numero di casi di test.

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:

  • intervallo di valori, quindi una classe valida per valori interni all’intervallo, una non valida per valori inferiori e una non valida per valori superiori;
  • valore specificato, una classe valida per il valore specificato, una non valida per un valore inferiore e una non valida per un valore superiore;
elemento di un insieme discreto, una classe valida per ogni elemento dell’insieme, una non valida per un elemento non appartenente;
  1.  
  2. valore booleano, una classe valida per il valore true e una non valida per il valore false.

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:

  1. associare ogni test alla classe da testare;
  2. specificare lo scopo del test;
  3. specificare una sequenza di passi di test, contenente l’elenco di stati per l’oggetto da testare, l’elenco di messaggi ed operazioni che saranno eseguite come conseguenza del test, l’elenco di eccezioni che potrebbero verificarsi durante il test, l’elenco di condizioni esterne, e infine, ulteriori informazioni per capire ed implementare il test.

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:

  1. l’identificazione delle variabili operazionali (esplicitando gli input e gli output sulle condizioni, stati del sistema, ed elementi di interfaccia);
  2. definizione del dominio delle variabili;
  3. sviluppando un modello relazionale sulle variabili, modellando le diverse risposte del sistema;
  4. sviluppo del test case per le variabili.

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:

  • Selenium IDE, si tratta di un’estensione di un browser che consente di catturare le interazione tra un utente e una applicazione web (capture), suggerisce asserzioni relative alla presenza di widget sull’interfaccia utente e per replicare l’esecuzione di casi di test mantenendo un log degli esisti del test (replay);
  • Selenium Core;
  • Selenium Remote Control;
  • Selium Grid.

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.

  • L’analisi mutazionale serve a valutare la capacità potenziale di una data test suite nella rilevazione dei difetti. Il primo passo consiste nell’immaginare quali possono essere i possibili errori. È necessario proporre un modello degli errori e dei corrispondenti operatori di mutazione. Un operatore di mutazione introduce in un programma un difetto trasformando il programma originale in un mutante. I difetti sono inseriti automaticamente nei programmi, ottenendo dei mutanti. Su ogni mutante generato si vanno ad eseguire i test case progettati per l’applicazione originale. L’oracolo per l’esecuzione di tali test è dato dall’output che veniva generato dal programma originale. Se l’esito del test è positivo allora si dice che il mutante è stato ucciso, altrimenti, il mutante non è stato rilevato dal test. Più mutanti sono uccisi, maggiore fiducia si può avere nella capacità della test suite di scoprire difetti. L’efficienza di un test suite si può misurare come à TER= #Killed Mutants / #Mutants. In conclusione una test suite che riesca a rivelare il maggior numero possibile di mutanti è da considerarsi più promettente nella rivelazione di potenziali difetti nell’applicazione. Il problema sta nella difficoltà nella modellazione dei difetti di un sistema software. Di solito vengono proposti degli operatori di mutazione basandosi sull’esperienza generica di chi propone il testing mutazionale riguardo le possibili cause di errore, e basandosi su di un’analisi statistica dei difetti rilevati in altri software. Gli operatori proposti sono di solito estremamente generici, per poter essere applicabili ad ogni software, indipendentemente dal linguaggio adottato e dalle caratteristiche della singola applicazione. Il numero di possibili difetti è estremamente elevato, già per un piccolo frammento di software il numero di mutanti generati è estremamente elevato. Altri problemi riguardanti l’analisi mutazionale è che non tutti i difetti di un sistema software sono legati ad errori nel codice sorgente, alcuni difetti portano ad errori sintattici e sono gia rivelati dal compilatore e non è possibile proporre operatori generali che riproducano gli errori semantici. I vantaggi, però, sono che tale analisi è una tecnica completamente automatica, che può essere eseguita in batch senza l’assistenza del tester. Si rivela utile come banco di prova per il confronto in ambiente sperimentale tra diverse test suite, per valutare quale sia in grado di rilevare più difetti.

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:

  • Analisi statica di compilazione. I compilatori effettuano una analisi statica del codice per verificare che un programma  soddisfi particolari caratteristiche di correttezza statica, per poter generare il codice oggetto. Le informazioni e le anomalie che può rilevare un compilatore dipendono dalle caratteristiche del linguaggio e dalle facility di cui esso dispone. Le tipiche anomali identificabili sono: nomi di indentificatori non dichiarati, incoerenza tra tipi di tai coinvolti in una istruzione, etc…
  • Code Reading è effettuata un’attenta lettura individuale del codice per individuare errori e/o discrepanze con il progetto. Il lettore effettua mentalmente una pseudo-esecuzione del codice e processi di astrazione che lo conducono a verificare la correttezza del codice rispetto alle specifiche e il rispetto di standard adottati. Tipici difetti identificabili: nomi di indentificatori errati, errato innesto di strutture di controllo, loop infiniti, inversione di predicati… L’efficacia di tale tecnica è limitata se chi la esegue è la stessa persona che ha scritto il codice.
  • Code Inspections. Le riunioni formali cui partecipa un gruppo di persone tra cui almeno una del gruppo di sviluppo, un moderatore ed altri esperti. Lo sviluppatore legge il codice ad alta voce, linea per linea, e i partecipanti fanno commenti e/o annotazioni. Tipicamente queste riunioni sono preannunciate ai partecipanti cui viene fornita la documentazione necessaria per la revisione. Questa è una tecnica che riesce ad individuare fra il 30% e il 70% degli errori nella logica del programma. L’obiettivo della riunione è scoprire difetti, non correggerli. Comunque, spesso l’analisi dei difetti effettuata viene discussa e vengono decise le eventuali azioni da intraprendere. Il codice è analizzato usando checklist dei tipici errori di programmazione quali: errori di data reference, data declaration, di calcolo, di confronto, etc… le checklist sono generiche ma possono essere adattate agli specifici linguaggi analizzati.
  • Walkthrough. L’analisi informale del codice svolta da vari partecipanti i quali operano come il computer, si scelgono alcuni casi di test e si simula l’esecuzione del codice a mano. L’organizzazione della riunione è simile a quella della tecnica delle Ispezioni: tra 3 e 5 partecipanti; riunioni brevi; attenzione sulla ricerca dei difetti, piuttosto che sulla correzione; attenzione a non criminalizzare il programmatore.
  • Control Flow Analysis. Il flusso di controllo è esaminato per verificarne la correttezza. Il codice è rappresentato tramite un grafo, il grafo del flusso di controllo, i cui nodi rappresentano statement del programma e gli archi il passaggio del flusso di controllo. Il grafo è esaminato per identificare ramificazioni del flusso di controllo e verificare ramificazioni del flusso di controllo e verificare l’esistenza di eventuali anomalie quali codice irraggiungibile e non strutturazione.
  • Data flow analysis – statica. L’analisi dell’evoluzione del valore delle variabili durante l’esecuzione di un programma, permettendo di rilevare anomalie. Intrinsecamente è dinamica, ma alcuni aspetti possono essere analizzati staticamente. L’analisi statica è legata alle operazioni eseguite su una variabile: definizione (alla variabile è assegnata un valore), uso (il valore della variabile è usato in un’espressione o un predicato), annullamento (al termine di un’istruzione il valore associato alla variabile non è più significativo). Es nell’espressione a:=b+c la variabile a è definita mentre b e c sono usate. La definizioni (d) di una variabile, così come un annullamento (a), cancella l’effetto di una precedente definizione della stessa variabile, ovvero ad essa è associato il nuovo valore derivante dalla nuova definizione. Una corretta sequenza di operazioni prevede che: l’uso (u) di una variabile deve essere sempre preceduto da una definizione della stessa variabile senza annullamenti intermedi. Una definizione di una variabile deve essere sempre seguita da un uso delal variabile, prima di un’altra definizione o di un annullamento della stessa variabile.
  • Esecuzione simbolica. Il programma non è eseguito con i valori effettivi ma con valori simbolici dei dati in input. L’esecuzione procede come una esecuzione normale ma non sono elaborati valori, bensì formule formate dai valori simbolici degli input. Gli output sono formule dei valori simbolici degli input. L’esecuzione simbolica anche di programmi di modeste dimensioni può risultare molto difficile. Ciò è dovuto all’esecuzione delle istruzione condizionali, ovvero deve essere valutato ciascun caso, in programmi con cicli ciò può portare a situazioni difficilmente gestibili. Nel caso di esecuzioni simboliche con condizioni alcuni statement sono eseguiti solo se gli input soddisfano determinate condizioni. Una path condition (PC), per un determinato statement, indica le condizioni che gli input devono soddisfatte affinché una esecuzione percorra un cammino lungo cui lo statement sia eseguito. Una pc è un’espressione booleana sugli input simbolici di un programma. All’inizio dell’esecuzione simbolica essa assume il valore vero. Per ogni condizione che si incontrerà lungo l’esecuzione, pc assumerà differenti valori a seconda dei differenti casi relativi ai diversi cammini dell’esecuzione. (slide pag 44). Ogni foglia dello execution tree rappresenta un cammino che sarà percorso per certi valori di input. Le Pc associate a due differenti foglie sono distinte, ciascuna foglia dello execution tree rappresenta un cammino che sarà percorso per la PC ad essa associata. Non esistono esecuzioni per cui sono vere contemporaneamente più PC. Feasible Path è un cammino per il quale esiste un insieme di dati di ingresso che soddisfa la path condition. Unfeasible Path è un cammino per il quale non esiste un insieme di dati di ingresso che soddisfa la path condition. Se l’output ad ogni foglia è corretto allora il programma è corretto. Il problema principale è il determinare quanti rami può avere un execution tree.

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:
  • watch point e variabili di watch, un watch è una semplice istruzione che inoltra il valore di una variabile verso un canale di output;
  • asserzioni, espressioni booleane dipendenti da uno o più valori di variabili legate allo stato dell’esecuzione.

 Abbiamo diverse metodologie per il Debugging:

  • Forza bruta è il modo più inefficiente per fare debugging. Vi sono diversi approcci possibili: usare uno storage dump; disseminare il codice di sonde per catturare quante più informazioni possibili e valutarle, in cerca di indizi; usare qualche strumento di debugging automatico. A tale metodologia si riccone solo quanto altre tecniche hanno fallito.
  • Debugging usando l’approccio Induttivo. Si procede dal particolare al generale. Il processo di ragionamento induttivo è basato sulla raccolta di dati ed indizi sul fallimento, formulazione e verifica di ipotesi sulle possibili cause, in modo iterativo.
  • Debugging usando l’approccio deduttivo. Con tale tecnica si procede dal generale al particolare. Si formulano varie ipotesi sulla causa dell’errore e si raccolgono dati per valicarle o scartarle.
  • Debugging per BackTracking si cerca di ripercorrere il codice “all’indietro” a partire dal punto dove si è verificato il malfunzionamento. Analogamente alla tecnica delle Path Condition, diventa via via più difficile procedere all’indietro all’allargarsi del campo di possibilità.

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:

  • maggiore affidabilità, in quanto il software riutilizzato dovrebbe essere più affidabile del nuovo software poiché i suoi errori di progettazione e implementazione sono già stati trovati e corretti;
  • rischio del processo ridotto, in quanto il costo del software esistente è già conosciuto, mente il costo di sviluppo è sempre una questione di giudizio;
  • uso efficace degli specialisti, che invece di fare lo stesso lavoro più e più volte, gli specialisti delle applicazioni possono sviluppare software riutilizzabile che racchiuda la loro conoscenza;
  • compatibilità con gli standard, in quanto alcuni standard possono essere implementati sotto forma di insieme di componenti standard riutilizzabili;
  • sviluppo accelerato, in quanto inserire un nuovo sistema nel mercato al più presto possibile è spesso più importante del costo generale di sviluppo e quindi riutilizzare il software può velocizzare la produzione del sistema.

I problemi del riuso sono:

  • aumento dei costi di manutenzione, in quanto se non è disponibile il codice sorgente di un sistema software o di un componente riutilizzato i costi di manutenzione crescono perché aumenta l’incompatibilità degli elementi riutilizzati con le modifiche al sistema;
  • mancanza di supporto agli strumenti, in quanto gli strumenti CASE non supportano lo sviluppo con riutilizzo, quindi può essere difficile integrare tali strumenti con un sistema di libreria di componenti;
  • sindrome del “non inventato qui” a causa del quali alcuni ingegneri del software preferiscono riscrivere i componenti poiché credono di poterli migliorare, questo ha in parte a che fare con la fiducia e in parte con il fatto che la scrittura di un software originale viene considerata una sfida maggiore del riutilizzo di software altrui;
  • creazione e manutenzione di una libreria dei componenti, in quanto popolare una libreria di componenti riutilizzabili e assicurasi che gli sviluppatori del software possano utilizzarla, può essere costoso;
  • ricerca, comprensione e adattamento dei componenti riutilizzabili, in quando i componenti software devono essere trovati in una libreria, compresi e a volte adattati per lavorare in un nuovo ambiente.

Ci sono diversi approcci per il riuso:

  • design pattern, in cui le astrazioni generiche che avvengono tra le applicazioni sono rappresentate come schemi di progettazione che mostrano gli oggetti e le interazioni astratte e concrete;
  • sviluppo basato su componenti, i sistemi vengono sviluppati integrando componenti che si conformano a standard di modellazione dei componenti;
  • application framework, collezioni di classi astratte e concrete che possono essere adattate ed estese per creare sistemi applicativi;
  • wrapping di sistemi ereditati, i sistemi ereditati possono essere “incapsulati” definendo un insieme di interfacce attraverso le quali si fornisce l’accesso ai sistemi ereditati;
  • sistemi orientati ai servizi, è un approccio in cui i sistemi vengono sviluppati collegando servizi condivisi che possono essere forniti esternamente;
  • linee di prodotti applicativi, un tipo di applicazione viene generalizzato attorno a un’architettura comune così da poter essere adattata a diversi clienti;
  • integrazione COTS, i sistemi vengono sviluppati integrando sistemi applicativi esistenti;
  • applicazioni verticali configurabili, in cui viene progettato un sistema generico in modo che si possa configurare secondo le specifiche necessità dei clienti del sistema;
  • librerie di programmi, si utilizzano librerie di classi e di funzioni che implementano astrazioni usate comunemente e che sono disponibili per il riutilizzo;
  • generatori di programmi, un sistema generatore integra la conoscenza di un particolare tipo di applicazione e può generare sistemi o frammenti di sistema in tale dominio;
  • sviluppo di software orientato agli aspetti, è un approccio in cui i componenti condivisi sono intessuti in un’applicazione in diversi punti quando il programma viene complilato.

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:

  • Framework per infrastruttura di sistema, che supportano sviluppo di infrastrutture come comunicazioni, interfacce utente, comunicazioni;
  • Integrazione di middleware, composti da standard e classi di oggetti per lo scambio di informazioni tra oggetti;
  • Applicazioni aziendali, integrano la conoscenza per specifici domini di applicazione e supportano lo sviluppo di nuovo applicazioni.

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:

  • specializzazione della piattaforma, in cui possono essere necessarie diverse versioni per diversi sistemi operativi o hw, senza modificare le funzionalità;
  • specializzazione dell’ambiente, in cui possono essere necessarie diverse versioni per venire incontro alle diverse funzionalità e modalità di funzionamento dei dispositivi periferici del sistema;
  • specializzazione funzionale, con cui possono essere necessarie diverse versioni personalizzate in base alle esigenze dei diversi clienti cui sono destinate;
  • specializzazione di processo, in cui possono essere necessarie diverse versioni per supportare i diversi processi di business da eseguire.

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:

  • standardizzato, ovvero la standardizzazione dei componenti implica che un componente utilizzato iin un processo CBSE deve conformarsi a un modello di componente standardizzato;
  • indipendente, ovvero un componente dovrebbe essere indipendente, dovrebbe essere possibile comporlo e consegnarlo senza dover utilizzare altri componenti specifici;
  • componibile perché un componente sia componibile tutte le interazione esterne devono avvenire attraverso interfacce pubbliche;
  • conseguibile, per essere conseguibile, un componente deve essere autonomo e capace di operare come entità indipendente su una piattaforma che implmenti il suo modello;
  • documento, i componenti devono essere documentati internamente in modo che i potenziali utenti possano decidere se il componente soddisfa o no le loro necessità.

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 servizi possono essere sviluppati localmente, o essere prodotti da fornitori esterni;
  • per accedere ai servizi è necessario conoscere unicamente la loro interfaccia, l’accesso ai servizi è possibile attraverso un linguaggio standard, indipendentemente dal linguaggio con cui i servizi sono stati implementati;
  • è possibile recuperare funzionalità di sistemi esistenti;
  • aziende o altre organizzazioni possono cooperare utilizzando funzioni di business altrui e avvalendosi di un meccanismo di scambio dati semplificato à support al collobarative Computing.

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:

  • SOAP è un protocollo per lo scambio di messaggio, tipicamente implementato su http o https;
  • WSDL (Web Service Definition Language) è un protocollo per lo scambio di informazioni descriventi l’interfaccia del Web Service e il collegamento con il servizio vero e proprio;
  • UDDI (Universal Description, Discovery, and Integration) definisce la semantica del servizio, in maniera tale da consentire la scoperta automatica dell’esistenza del servizio;
  • WS-BPEL permette di definire la modalità di composizione di servizi.

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:

    • Service engineering, che tratta lo sviluppo di servizi dependable e riusabili, ovvero sviluppo di software per il riuso;
    • Sviluppo software basato su servizi che si occupa di sviluppare software fidato riusando servizi software, ovvero sviluppo di software con il riuso.

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:

  • per i servizi non è definita l’interfaccia “richiede” , ma solo “fornisce” in quanto i servizi dovrebbero essere indipendenti e debolmente accoppiati.
  • L’interazione con i servizi è basata unicamente sullo scambio di messaggi. Lo scambio di messaggi è del tipo Request/Response, e i servizi possono essere visti come funzione remote e non come oggetti.

Fig 1 Processo di interazion sincrona
WSDL. Le interfacce dei servizi sono espresse in linguaggio WSDL. Una specifica WSDL definisce:

  • quali operazioni sono fornite dal servizio. Per ogni operazione è specificato il formato del messaggio di richiesta e il formato del messaggio di risposta.
  • Come accedere al servizio. Il Binding con cui si specifica come devono essere costruiti i messaggi di I/O ed il protocollo da seguire nello scambio di messaggi
  • Dove si trova il servizio, in termini di URI (Universal Resource Identifer). URI è l’indirizzo di una risorsa accessibile via Internet ed è una generalizzazione del concetto di indirizzo web.

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:

    • L’identificazione del servizio candidato;
    • La progettazione del servizio;
    • L’implementazione e consegna del servizio.

Le fasi del processo di Service Engineering sono:

  • identificazione dei servizi candicati. I servizi possono essere classificati in tre categorie fondamentali:
    • servizi di utilità che implementano funzionalità di carattere generale, applicabili a diversi problemi;
    • servizi di business, utili all’interno di una particolare tipologia di dominio aziendale;
    • servizi di coordinazione che supportano la composizione di servizi complessi (interagiscono con i servizi più generali, consentendo di ricavare astrazioni di domini).

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:

  • Il servizio è associato con una singola entità logica utilizzata in diversi processi di business?
  • L’attività in questione è normalmente portata avanti da persone diverse all’interno dell’organizzazione?
  • Il servizio è indipendente?
  • È necessario che il servizio mantenga aggiornato uno stato interno?
  • Il servizio potrà essere utilizzato da clienti esterni all’organizzazione?
  • Per quanto riguarda i requisiti non funzionali, è possibile che utenti diversi possano avere requisiti diversi?
  • Progettazione dell’interfaccia del servizio. Richiede la definizione delle operazioni associate al servizio e dei loro parametri. Il numero di messaggi scambiati per completare una richiesta di servizio dovrebbe essere minimo. I servizi devono essere senza stato: pertanto potrà essere necessario passare informazioni sullo stato per mezzo dei messaggi di input/output.

Le fasi della progettazione dell’interfaccia sono:

  • Logical inteface design, in questa fase si identificano i nomi delle operazioni associate al servizio, i parametri di input e output, e le eventuali eccezioni.
  • Message design, in cui avviene la progettazione dei messaggi, preferibilmente utilizzando prima linguaggi come UML piuttosto che direttamente XML.
  • WSDL description, in cui la specifica logica dell’interfaccia del servizio è tradotta in WSDL.
  • Implementazione e consegna del Servizio. Il servizio può essere implementato usando linguaggi standard di programmazione che forniscono librerie per lo sviluppo di servizi. Alternativamente, i servizi possono essere implementati usando componenti preesistenti o componenti ereditati, o definendo una composizione di servizi pre-esistenti con linguaggi di workflow. I servizi devono essere poi testati partizionando gli input, creando vari messaggi di input, e verificando che i messaggi di output prodotti siano corretti rispetto alla specifica. La consegna richiede che il servizio sia pubblicizzato usando UDDI e che venga installato su un web service. Molti server forniscono supporto per una semplice installazione del servizio.

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:

  • Dettagli sul fornitore del servizio
  • Descrizione delle funzionalità fornite
  • Posizione della descrizione WSDL dell’interfaccia.

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:

  • Si descrive un problema in un linguaggio formale;
  • Un motore di interpretazione scompone il problema in un workflow di sottoproblemi;
  • Vengono automaticamente ricercati e reperiti Web Service in grado di risolvere ogni sottoproblema;
  • Viene automaticamente composto il workflow in grado di risolvere il problema proposto.

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:

  • WS-BPEL è al momento il più comune. È anch’esso un dialetto XML.
  • Esistono componenti che vanno a estendere le funzionalità dell’application service, che agiscono da interpreti di documenti BPEL ed esecutori di Workflow.
  • Esistono inoltre strumenti come BPMN per la definizione visuale di workflow di web service.

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:

  • Eventuali modifiche ad un Servizio potrebbero invalidare i test già effettuati;
  • Se si utilizza in binding dinamico si dovrebbero rieseguire tutti i test progettati prima di utilizzare effettivamente il servizio;
  • Le caratteristiche di qualità del servizio, dipendono dal carico e non possono essere previste;
  • Se i servizi sono a pagamento, il loro utilizzo a scopo di test è costoso;
  • Può essere difficile esercitare le azioni per la gestione delle eccezioni in servizi esterni, giacchè esse possono dipendere da fallimenti di altri servizi che non possono essere simulati.

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:

  • Le richieste di sistema dell’utente vengono effettuate mediante l’immissione di dati e l’invio di comandi che interagiscono con l’interfaccia utente.
  • Il sistema risponde producendo una risposta sullo schermo, che contiene i valori di uscita e nuovi campi di input e pulsanti di comando.

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:

  • Il wrapper deve essere riusabile per poter essere utilizzato in diversi casi d’uso;
  • Il comportamento delle richieste wrapper per ogni caso d’uso nono sarà incorporato nel wrapper, ma sarà specificato separatamente per ciascun caso d’uso.
  • Un requisito di fondamentale importanza è di ottenere ad ogni caso d’uso un completo modello di interazione tra sistema legacy e utente.

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:

  • S è l’insieme degli stati di interazione;
  • A è l’insieme di Azioni eseguita dall’utente quando si giunge in uno stato di interazione;
  • T è l’insieme delle transizione tra stati;
  • Sin e Sfin sono gli stati iniziali e finali di interazione.

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:

  • Identificazione, ovvero eseguire il reverse engineering per il modello di interazione;
  • Design, ovvero definizione dell’FSA associato;
  • Implementazione, ossia la realizzazione della descrizione XML dell’FSA;
  • Sviluppo di Web Service;
  • Infine la Valutazione, ovvero il testing degli scenari dei casi d’uso di migrazione.

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:

  • La comprensibilità, ovvero fino a che punto il processo è definito esplicitamente e quanto è semplice comprendere la sua definizione;
  • La visibilità, evidenzia se le attività del processo arrivano a risultati chiari in modo che il progresso del processo sia visibile esternamente;
  • La sopportabilità ovvero, fino a che punto si possono usare strumenti CASE per supportare le attività del processo;
  • L’accettabilità, ciò riguarda se il processo definito è accettabile e usabile dagli ingegneri responsabili della produzione del prodotto software;
  • L’affidabilità, ovvero se il processo è stato progettato in modo che gli errori siano evitati o intrappolati prima che producano errori nel prodotto;
  • La robustezza che evidenzia il fatto che il processo può continuare anche dopo problemi inattesi;
  • La manutenibilità, ovvero se il processo può evolversi per riflettere i cambiamenti dei requisiti aziendali o i miglioramenti del processo individuati;
  • Infine la rapidità ossia quanto velocemente può essere completato il processo di consegna di un sistema data una specifica.

Il miglioramento dei processi è un’attività ciclica. Tale ciclo si compone di tre fasi:

  • Misurazione del processo, in cui vengono misurati attributi di qualità del processo;
  • Analisi del processo, dove il processo di sviluppo utilizzato è analizzato allo scopo di trovare debolezze e colli di bottiglia;
  • Infine, il cambiamento del processo, in cui vengono introdotti dei cambiamenti individuati in fase di analisi nel processo.

 

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:

  • Processi informali in cui nessun modello di processo è adottato. Alcuni strumenti come la gestione della configurazione potrebbero essere utilizzati, ma senza formalizzare la modalità d’utilizzo.
  • Processi gestiti in cui esiste un modello di processo definito, comprendente un insieme di procedure, tempistiche e vincoli.
  • Processi metodici in cui c’è il ricorso contemporaneo a modelli di processo sistematici, a metodi di sviluppo definiti, e a strumenti CASE a supporto dell’esecuzione.
  • Processi di miglioramento che oltre ad essere metodici, prevedono anche analisi quantitative volte al miglioramento del processo stesso.

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:

  • Attività. Un’attività ha obiettivo e condizioni di ingresso e uscita chiaramente definiti.
  • Processo. Un processo è un insieme coerente di attività il cui obiettivo viene solitamente convenuto all’interno di un’organizzazione.
  • Consegna. Una consegna è un output tangibile di un’attività previsto nel piano del progetto
  • Condizione. Una condizione può essere sia una precondizione che deve essere soddisfatta prima di iniziare un processo o un’attività, sia una postcondizione che deve essere soddisfatta dopo che un processo o un’attività sono stati completati.
  • Ruolo. Un ruolo è un’area di responsabilità limitata.
  • Eccezioni. Un’eccezione descrive come modificare il processo se accadono eventi inattesi o imprevisti.
  • Comunicazioni. La comunicazione è uno scambio di informazioni tra persone o tra persone e sistemi informatici di supporto.

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:

  • L’introduzione di nuove tecniche, metodologie, processi, procedure;
  • La modifica dell’ordine di esecuzione dei processi;
  • Introduzione di nuovi deliverables
  • L’introduzione di nuovi ruoli o responsabilità

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:

  • identificare i miglioramenti, ovvero definire le proposte in grado di rimuovere colli di bottiglia, debolezze del processo;
  • assegnazione priorità ai miglioramenti, ovvero stabilire le priorità delle modifiche in base ai costi, impatti, dipendenze…;
  • introduzione delle modifiche;
  • addestramento riguardo alle modifiche;
  • raffinamento delle modifiche.

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:

  • process area in cui sono state identificate 24 differenti aree di processo, organizzare in 4 gruppi e rilevanti al fine dell’individuazione della capability del processo e della possibilità di miglioramento;
  • goals rappresentano gli obiettivi organizzativi che si vorrebbero ottenere, intesi come stato desiderato da raggiungere per una organizzazione. Ogni process area ha i suoi obiettivi associati;
  • practices rappresentano metodologie e tecniche per ottenere un goal.

Le aree del processo CMMI sono:

    • Gestione del processoà definizione aziendale del processo; focalizzazione aziendale del processo; formazione aziendale; prestazioni aziendali del processo; innovazione e deployment aziendali.
    • Gestione del progetto à pianificazione del progetto; monitoraggio e controllo del progetto; gestione degli accordi con i fornitori; gestione integrata del progetto; gestione dei rischi; implementazione di team integrati; gestione quantitativa del progetto.
    • Ingegneria à gestione dei requisiti; sviluppo dei requisiti; soluzione tecnica; integrazione del prodotto; verifica; convalida.
    • Supporto à gestione della configurazione; gestione della qualità del processo e del prodotto; misurazione e analisi; analisi e risoluzione delle decisioni; ambiente di integrazione aziendale; analisi e risoluzione causale.

(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:

  • L’evoluzione del software esistente;
  • La riscrittura da capo del software;
  • Il recupero del software esistente (reverse engineering) e la sua ristrutturazione (reeingineering).

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:

  1. manutenzione correttiva;
  2. manutenzione adattiva (adattare il SW ai cambiamenti dell’ambiente operativo);
  3. manutenzione percettiva;
  4. manutenzione preventiva.

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:

  • difficoltà nel comprendere un programma scritto da altri;
  • mancanza di documentazione completa/consistente;
  • software non progettato per modifiche future;
  • difficoltà nel tradurre una richiesta di modifica di funzionamento del sistema in una modifica del software;
  • valutazione dell’impatto di ciascuna modifica sull’intero sistema;
  • la necessità di ritestare il sistema dopo le modifiche;
  • la gestione della configurazione del software.

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:

    • Stabilità del team in quanto il team viene sciolto quando viene rilasciato il software, e quindi le persone che parteciparono a quel progetto possono essere diverse o essere rappresentati da una piccola parte.
    • Responsabilità contrattuale in quanto lo sviluppo e la manutenzione sono talvolta appaltati ad aziende diverse e quindi chi sviluppa il SW non vuole rendere la vita facile a chi dovrà provvedere alla manutenzione se non si tratta dell’azienda stessa di produzione.
    • Capacità dello staff in quando chi si occupa della manutenzione potrebbe essere meno competente di chi lo ha realizzato.
    • Età e struttura del programma, in quanto gli interventi di manutenzione tendono a far deteriorare la qualità del software e di conseguenza a rendere più difficili tutti gli interventi di manutenzione che vengono a essere necessari.

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:

      • Numero e complessità delle interfacce del sistema;
      • Numero dei requisiti di sistema intrinsecamente volatili;
      • Processi aziendali per i quali il sistema viene utilizzato;
      • Complessità dei moduli software da modificare;
      • Quantità di moduli impattati dagli interventi di manutenzione.

Tra le metriche utilizzare abbiamo:

  • Numero di richieste di manutenzione correttiva;
  • Tempo medio richiesto per l’analisi dell’impatto;
  • Tempo medio richiesto per implementare una richiesta di modifica;
  • Numero di richieste di modifica in attesa.

Il processo di manutenzione presentato può essere evitato e ridotto, ad esempio, in caso di manutenzione “d’urgenza”:

  • Se un difetto serio deve essere riparato;
  • Se modifiche dell’ambiente causano effetti collaterali imprevisti;
  • Se è necessario l’adeguamento urgente a seguito di un cambiamento delle regole di business.

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:

  • Ristrutturazione e riscrittura di parte del software senza modificare l’insieme di funzionalità che esso realizza;
  • Ridocumentazione e aggiornamento della documentazione.

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.

Figura 4 Ingegneria diretta

Figura 5 Re-ingegnerizzazione del software

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:

  • traduzione del codice sorgente in cui si converse il vecchio linguaggio di programmazione in una versione più moderna o in un linguaggio diverso;
  • Reverse engineering ove, il programma viene analizzato e vengono estratte informazioni utili a documentare la sua organizzazione e la sua funzionalità;
  • Perfezionamento della struttura del programma: la struttura di controllo viene analizzata e modificata per renderla più semplice da leggere e comprendere;
  • Modulizzazione del programma si raggruppano le parti correlate del programma e, dove appropriato, viene rimossa la ridondanza. In alcuni casi questo stadio può richiedere una trasformazione architetturale, come ad esempio, un sistema centralizzato ideato per un singolo computer viene modificato per essere eseguito su una piattaforma distribuita;
  • Re-ingegnerizzazione dei dati in cui si modificano i dati elaborati dal programma perché riflettano le modifiche apportate al programma stesso.

Figura 6 processo di re-engineering

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:

  • la qualità del software da re-ingegnerizzare: più è bassa la qualità del software e della documentazione associata, più sono alti i costi di re-ingegnerizzazione;
  • il supporto di strumenti disponibili per la re-ingegnerizzazione: normalmente non è economico re-ingegnerizzare un software se non si utilizzano strumenti CASE per automatizzare la maggior parte delle modifiche al programma;
  • le dimensioni della conversione dati richiesta: se la re-ingegnerizzazione richiede la conversione di un grande quantitativo di dati, i costi del processo aumentano significativamente;
  • la disponibilità di uno staff esperto: se non si può coinvolgere nel processo di re-ingegnerizzazione lo staff responsabile della manutenzione del sistema, i costi aumentano poiché coloro che dovranno re-ingegnerizzarlo impiegheranno molto tempo per comprendere il sistema.

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:

  1. cancellare completamente il sistema
  2. lasciare il sistema inalterato e continuare con la manutenzione regolare
  3. re-ingegnerizzzare il sistema per migliorarne la manutenibilità
  4. sostituire tutte le parti del sistema con un sistema nuovo.

 

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:

  • individauare i componenti del software e le relazioni esistenti tra essi;
  • creare rappresentazioni del prodotto ad un più alto livello di astrazione;
  • comprendere e descrivere le funzioni svolte dal prodotto stesso e le modalità con cui esse sono state implementate.

Si distinguono due fasi nel Reverse Engineering:

    • estrazione, in cui avviene l’analisi statica dl codice, allo scopo di produrre una documentazione consistente del codice analizzato, e vengono utilizzati quelli strumenti (o tool) in grado di estrarre informazioni da un codice sorgente qualsiasi, nota che sia la grammatica di programmazione.
    • astrazione, in cui si esaminano le informazioni estratte e si cercano di astrarre diagrammi ad un più alto livello di astrazione; i processi di astrazione non sono completamente automatizzabili poiché necessitano di conoscenza ed esperienza umana.

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:

  • è spesso vecchio;
  • è di grandi dimensioni;
  • è scritto in assembler o in un linguaggio di vecchia generazione;
  • è stato probabilmente sviluppato prima che si diffondessero i moderni principi dell’ingegneria del software;
  • la manutenzione è stata svolta in modo da seguire le modifiche nei requisiti, aumentanto così l’entropia del sistema;
  • realizza funzionalità cruciali e irrinunciabili per l’organizzazione;
  • contiene anni di esperienza accumulata nell’ambito del dominio specifico del problema.

Il sistema legacy, però, obbliga il management a cercare soluzioni e strategie di manutenzione vincenti . Ci sono svariate alternative disponibili:

  • eliminazione del sistema e sviluppo di un nuovo sistema;
  • recupero dei componenti più preziosi del sistema;
  • eliminazione dei componenti ormai inutili, del codice obsoleto e dei dati “morti”;
  • congelamento del sistema as is, e riutilizzo mediante tecniche di wrapping;
  • manutenzione ordinaria;
  • manutenzione preventiva;
  • migrazione …

I sistemi legacy si possono dividere in quattro categorie:

      • bassa qualità, basso valore economico, tali sistemi dovrebbero essere abbandonati;
      • bassa qualità, alto valore economico, tali sistemi realizza funzionali importanti ma è costoso mantenerlo. Dovrebbero essere reingegnerizzati in modo da rendere le future operazioni di manutenzione più agevoli ed efficaci;
      • alta qualità, alta valore economico, un tale sistema si può decidere sia di abbandonalro, sia rimpiazzarlo con COTS;
      • alta qualità, alta valore economico, su un tale sistema si eseguono le operazioni di manutenzione. In questo modo, però, la qualità diventerà via via più bassa, fino a finire nel secondo caso.

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:

  • stabilità del fornitore
  • tasso di fallimento
  • età
  • prestazioni
  • requisiti di supporto
  • costi di manutenzione
  • interoperabilità

mentre per la valutazione delle applicazioni vengono utilizzati fattori quali:

  • la comprensibilità
  • la documentazione
  • i dati
  • le prestazioni
  • il linguaggio di programmazione
  • la gestione della configurazione
  • i dati di test
  • la capacità del personale.

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:

  • goal definition, ovvero la definizione dell’obiettivo che si intende raggiungere per il sistema legacy;
  • gap analysis, dove il profilo di qualità atteso è confrontato con quello corrente;
  • porfolio analysis, in cui la macro-tipologia dell’intervento di manutenzione necessario è definita per ciascun componente software;
  • alternative definition, in tale passo le possibili strategie d’intervento sono definite;
  • infine, convension strategy definition, in cui le alternative sono opportunamente confrontate per stabilire la strategia di conversione da adottare.

(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:

  • le modifiche al codice avvengono in termini di patches (“pezze”)
  • risulta essere veloce ed economiche sul breve termine
  • si ha un alta degradazione della struttura
  • e la documentazione è modificata a posteriori.

I modelli di miglioramento iterativo (iterative-enhancement model) hanno le seguenti caratteristiche:

  • vi è una valutazione preventiva dell’impatto della modifica;
  • si decide se lavorare su componenti esistenti o sviluppare nuove componenti;
  • preserva la struttura
  • è lento e costoso sul breve termine
  • la documentazione è modificata in anticipo.

 

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:

  • costi dell’hardware e del software;
  • costi per viaggi e formazione;
  • costi dello sforzo agli stipendi degli ingegneri/programmatori coinvolti;
  • altri costi
    • come costi di rifornimento, riscaldamento, illuminazione
    • costi di tutto il personale a supporto, amministrazione, tecnici, personale delel pulizie, etc…
    • costi di rete e telecomunicazioni;
    • costi di biblioteche e altri servizi;
    • costi di previdenza sociale;
  • costi delle risorse “a controllo” di solito valgono almeno quanto i costi legati agli stipendi delle risorse addette direttamente alla produzione.

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:

  • opportunità di mercato. Un’organizzazione di sviluppo può fissare un prezzo basso perché vuole entrare in un nuovo segmento del mercato del software. Accettare un basso profitto su un progetto può fornire all’organizzazione l’opportunità di avere un profitto maggiore in futuro.
  • Incertezza della stima del costo. Se un’organizzazione non è sicura delel sue stime di costo potrebbe aumentar il proprio prezzo di un certo valore sopra il suo normale profitto.
  • Precarietà dei requisiti. Se è probabile che i requisiti cambiano, un’organizzazione può abbassare il suo prezzo per vincere un appalto. Dopo l’assegnazione dell’appalto possono essere richiesti prezzi più alti per le modifiche ai requisiti.
  • Salute finanziaria. Gli sviluppatori in difficoltà finanziaria possono abbassare il loro prezzo per ottenere un appalto. È meglio avere un profitto ridotto piuttosto che dichiarare fallimento.

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:

  • Prince to-win estimatine, in cui il costo viene stimato a quanto il cliente è disposto a spendere. La stima dipende dal budget del cliente più che dalle funzionalità del software.
  • Giudizio di esperti. Gli esperti sulle tecniche di sviluppo e sul dominio vengono consultati. Il costo del progetto viene stimato da ognuno, vengono quindi comparate e discusse le stime finché non viene trovato un accordo.
  • Legge di Parkinson dice “il lavoro si espande per occupare il tempo disponibile”. Quindi il costo viene determinato unicamente in base ai vincoli di tempo imposti e alle risorse disponibili.
  • Stima tramite analogia. Si cerca di stimare il costo trovando delle analogie tra il progetto da realizzare e progetti precedentemente realizzati.
  • Modellazione algoritmica in cui viene sviluppato un modello sulla base di informazioni storiche e del legame empiricamente trovato tra alcune metriche di prodotto e di processo e il costo del software.

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:

  • Non esiste una metodologia adottata per la stima dei costi;
  • È richiesta una rapida, semplice e ragionevole accurata macro-stima
  • Esistono informazioni di precedenti progetti ma non un data base storico dettagliato.

I requisiti per l’utilizzo di tale stima sono:

  • Dimensione e scopi del nuovo progetto devono essere simili a quelli del prodotto di riferimento;
  • Il metodo di lavoro del nuovo progetto deve essere simile a quello del progetto di riferimento;
  • La disponibilità di informazioni molto dettagliate p di persone che ricordano il lavoro in maniera accurata.

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:

  • Conoscenza di quali caratteristiche sono simili e quali no;
  • Conoscenza della qualità dei dati sullo sforzo e i costi di manodopera disponibili per il progetto di riferimento

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:

  • Semplice relativo ad applicazioni piccole, per le quali il team di sviluppo è pienamente consapevole delle problematiche da affrontare;
  • Moderato relativo ad applicazioni più complesse, nelle quali si possono incontrare problematiche rispetto alle quali l’esperienza del team è limitata;
  • Integrato relativo ad applicazioni complesse, anche con problematiche hardware e vincoli legati a norme.

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.

    • Modello di composizione delle applicazioni utile per la stima di sistemi ottenuti a partire dal riuso di componenti. È un modello utile per stimare i costi di realizzazione di un prototipo o di realizzazione di un software a partire da componenti esistenti.

PM= (NAP * (1- %reuse/100))/PROD
Dove PM è lo sforzo in mesi-uomo, NAP il numero di Application Point, Prod la produttività.

    • Modello di progettazione iniziale basata fondamentalmente sui Fuction Point, utile per ottenere stime in fase di analisi. Viene usato una volta che i requisiti utente sono stati definiti e la progettazione iniziata. È spesso utilizzato per confrontare diverse soluzioni progettuali possibili.

Effort= A * SizeB * M
Dove M riflette vari aspetti quali à M=PERS*RCPX*RUSE*PDIF*PREX*FCIL*SCED

      • PERS capacità del personale
      • RCPX complessità del prodotto
      • RUSE livello di riuso
      • PDIF difficoltà legate alal piattaforma utilizzata
      • PREX esperienza del personale
      • FCIL esperienza del personale
      • SCED tempistica

Ogni moltiplicatore è valutato su una scala da 1 a 6 e

    • Modello di riutilizzo utile per stimare il costo relativo all’integrazione di diversi componenti riusabili. Esso considera sia il riuso di codice in modalità black-box sia il riuso di codice che deve essere adattato per integrarlo con nuovo codice. Ci so nude versioni del modello di stima, ovvero, quello Black-box reuse (si calcola una stima dello sforzo) e il White-box reuse (si calcola una stima del numero equivalente di linee di nuovo codice).

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.

    • Modello di post-architettura utilizzabile a valle della definizione dell’architettura per ottenere stime più affidabili. Viene utilizzato quando è disponibile un progetto architetturale iniziale ed è conosciuta la struttura dei sottosistemi. Viene riutilizzata la stessa foruula del modello di composizione: Effort= A * SizeB * M. questa volta è possibile una migliore stima della Size.

Una dimensione del codice è stimata sulla base della somma di 3 componenti:

  • Numero di linee di codice da sviluppare da zero;
  • Stima di numero di linee di codice equivalente calcolata tramite il modello di riuso;
  • Stima del numero di linee di codice equivalente corrispondente alle righe automaticamente generate che devono essere adattate ai requisiti specifici dell’applicazione.

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:

  • Rischi per il progetto che comporta un allungamento dei tempi;
  • Rischi per il prodotto che comporta sia una perdita della qualità che una riduzione di funzionalità
  • Rischi di incremento dei costi.

I rischi possono essere identificati in:

  • Rischi tecnologici, che dipendono dalla tecnologia utilizzata e dalla possibile evoluzione/dismissione;
  • Rischi relativi alle persone, relativi all’abbandono di persone del team di progetto, e la difficoltosa integrazione di nuovo personale;
  • Rischi organizzativi, relativi all’ambiente di lavoro nel quale si sta realizzando il progettoM
  • Rischi strumentali, dipendenti dagli strumenti di sviluppo e di supporto utilizzati;
  • Rischi relativi ai requisiti, relativi alle variazioni dei requisiti del cliente;
  • Rischi di stima, riguardanti l’imprecisione delle stime sulle quali viene dimensionato il processo di sviluppo.

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:

  • Individuare le strategie per evitare i rischi, cercando di ridurre la probabilità connessa al rischio;
  • Individuare le strategie per minimizzare i rischi, cercando di ridurre l’impatto che eventualmente avrebbe l’evento avverso sul progetto;
  • Selezionare opportuni piani precauzionali, cercando di prevedere una soluzione alternativa di compromesso in anticipo rispetto all’effettivo verificarsi dell’evento avverso.

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).

  • La prima fase della traduzione è l’analisi lessicale, che opera direttamente sullo strema in ingresso, riconoscendo tokens a partire dall’analisi dei caratteri dello strema di input.
    • La seconda fase è il parsine propriamente detto, che elabora i tokens restituiti dal lexer, riconosce la loro organizzazione sintattica ed esegue delle elaborazioni conseguenti al loro riconoscimento.

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.

  • Quindi l’analizzatore lessicale organizza in token lo strema di input.
  • Il parser legge questa sequenza di token e cerca di riconoscere le frasi e la loro struttura.
  • Il più semplice traduttore si limita a proporre in output le frasi riconosciute, senza ulteriori frasi.
  • I traduttori più complessi costruiscono strutture organizzative come gli AST, per consentire un’agevole interpretazione ad altri software da posizionare più a valle.

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:

  • Visualizzazione/modifica Syntax Diagram
  • Generazione ed editino di Parser e Lexer;
  • Debugging delle grammatiche, con visualizzazione grafica dellAST trovato.

(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:

  • Versioni complete e versioni ridotte;
  • Versioni in diverse lingue;
  • Aggiornamento di versioni;
  • Versioni diverse per diverse configurazioni hardware.

Occorre innanzitutto eseguire una pianificazione in cui vengono fissati i seguenti punti:

  • Cosa deve essere gestito
  • Chi è il responsabile della procedura di gestione
  • Quali sono le politiche di gestione della configurazione
  • Quale struttura del database di configurazione bisogna usare.

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:

  • Modifica proposta;
  • Proponente;
  • Motivo del cambiamento;
  • Priorità o urgenza;
  • Impatto del cambiamento;
  • Costo.

Ogni versione si può identificare mediante:

  • Numerazione (#versione.#modifica.#variazione);
  • Identificazione basata su attributi, in cui le modifiche vengono etichettate con attributi, in modo da poter caratterizzare anche l’impatto della modifica oltre che l’ordine delle versioni.
  • Identificazione orientata alla modifica, in cui le modifiche al sistema vengono etichettate in base alle modifiche sui singoli componenti.

Una release di un sistema è una sua versione che viene distribuita ai clienti. Essa comprende, tra l’altro:

  • Codice eseguibile;
  • File di configurazione;
  • Programma di installazione;
  • Documentazione elettronica e cartacea;
  • Imballaggio e pubblicità;

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:

  • Workbench aperti. Si tratta di strumenti stand-alone che si occupano di uno degli aspetti della gestione delle versioni e delle release. Spesso si tratta di strumenti open source.
  • Workbench integrati. Si tratta di strumenti che vanno a integrarsi con gli ambienti di sviluppo in modo da supportare la gestione di versioni e release contestualmente allo sviluppo.

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 :

  • Lo sviluppatore A scarica una copia del progetto dal server CVS (repository);
  • Applica liberamente tutte le modifiche. Nel frattempo altri programmatori (B) potrebbero fare lo stesso;
  • Al termine del suo lavoro il programmatore A aggiorna il progetto sul server CVS (commit);
  • Altri programmatori potrebbero richiedere aggiornamenti della loro working copy (update) al repository o generare delle ulteriori versioni (commit).

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 persona coinvolta nel progetto, ha una copia locale dei file (sandbox);
  • Chi avvia il progetto crea per la prima volta il repository indicando anceh quale directory dovranno essere gestire;
  • Successivamente in qualsiasi collaboratore può aggiungere nuovi file/directory al CVS;
  • Un collaboratore che voglia inserire nel CVS dovrà per prima cosa effettuare il Checkout per prelevare dal repository le versioni più recenti di ogni file.
  • Sui file presenti nella propria sandbox si possono effettuare le seguenti operazioni:
  • Checkout che preleva una copia aggiornata dal repository
  • Edit richiede il permesso di scrivere sul file locale;
  • Commit rende pubbliche a tutti le proprie modifiche.
  • Gestione dei conflitti se due utenti vanno a modificare in concorrenza lo stesso file, e il primo di essi effettua il commit, verrà impedito al secondo di fare lo stesso;
  • Gestione branch genera un ramo alternativo nella storia del file diversa numerazione ad esempio dopo 1.2 ci sarà la 1.2.1
  • Fusione tra versioni diverse
  • Eliminazione copia locale
  • Eliminazione originale da operare direttamente sul repository.

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:

  • Gestione dei conflitti;
  • Cronologia delle versioni;
  • Grafo delel versioni
  • Creazione di patch;
  • Scelta delle politiche di accesso;
  • Operazione in batch

 

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.

 

Ingegneria del software

 

 

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

 

"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

 

Argomenti

Termini d' uso, cookies e privacy

Contatti

Cerca nel sito

 

 

Ingegneria del software