2. Il software: natura e qualità
Obiettivo di ogni attività ingegneristica è costruire qualcosa, un artefatto' (utilizziamo il termine artefatto come traduzione dell'inglese artifact per indicare la specifica accezione di "elaborato intermedio", distinguendolo così da "elaborato", considerato come prodotto finale, consegnato al committente) o un prodotto; così l'ingegnere civile costruisce un ponte, l'ingegnere aerospaziale un aereo, l'ingegnere elettronico un circuito. Il prodotto dell'ingegneria del software è un sistema software: questo non è tangibile alla pari degli altri, ma è comunque un manufatto in grado di rispondere a una specifica funzione d'uso. La caratteristica che forse distingue maggiormente il software dagli altri prodotti è il fatto che sia "duttile": ossia, è possibile modificare il prodotto anziché modificare il progetto in maniera molto facile e questo lo rende sostanzialmente diverso dagli altri prodotti, come le automobili o i forni. Questa peculiarità del software è spesso male utilizzata. Quantunque sia possibile modificare un ponte o un aereo per soddisfare nuove necessità (per esempio, adeguare il ponte al crescente flusso di traffico o consentire a un aereo di trasportare più merci) questa modifica non viene mai intrapresa a cuor leggero, e certamente non viene tentata senza prima rielaborare il progetto e verificare l'impatto del cambiamento in maniera approfondita. Al contrario, agli ingegneri del software viene spesso richiesto di effettuare modifiche sostanziali. La duttilità del software porta a pensare che apportare modifiche sia banale, ma in pratica non è così. È facile cambiare il codice attraverso un editore di testi, ma non è facile fare in modo che il software soddisfi i fabbisogni per i quali era richiesto un cambiamento. È bene dunque che il software sia trattato alla pari degli altri prodotti ingegneristici: una modifica nel software deve essere vista come un cambiamento nel progetto piuttosto che nel codice. Una proprietà come la duttilità può essere vantaggiosamente sfruttata, ma occorre farlo con disciplina. Un'altra caratteristica del software è che la sua creazione è human intensive, ossia richiede un'elevata intensità di lavoro; richiede essenzialmente un'attività di "ingegneria" piuttosto che di "fabbricazione". Nella maggior parte delle altre discipline, il processo di fabbricazione determina il costo finale del prodotto; inoltre il processo di produzione deve essere gestito in maniera accurata, in modo che non vengano introdotti difetti indesiderati nel prodotto. Le stesse considerazioni si applicano ai prodotti hardware, mentre invece per il software la fabbricazione si riduce a un banale processo di duplicazione. Il processo di produzione del software consiste essenzialmente nel progetto e nell'implementazione, e non nella fabbricazione. Questo processo deve soddisfare opportuni criteri che assicurino la produzione di software di elevata qualità. E auspicabile che un prodotto soddisfi determinate necessità e rispetti standard di accettazione che prescrivono le qualità che deve possedere. Ad esempio, la funzionalità di un ponte è quella di rendere facile il collegamento da un posto a un altro; una delle qualità attese è che questo non crolli in presenza di vento molto forte o quando è attraversato da una fila di camion. Nelle discipline dell'ingegneria tradizionale, l'ingegnere dispone di strumenti per descrivere le qualità del prodotto in maniera distinta rispetto al progetto del prodotto stesso. Nell'ingegneria del software questa distinzione non è cosi chiara: le qualità di un prodotto software sono molte volte mescolate nelle specifiche, insieme alle qualità intrinseche del progetto. In questo capitolo esamineremo le qualità rilevanti nei prodotti software e nei processi di produzione del software. Queste qualità diventeranno i nostri obiettivi da perseguire nella pratica dell'ingegneria del software.
Classificazione delle qualità del software
Esistono molte qualità desiderabili per il software; alcune si applicano sia al prodotto che al processo utilizzato per il suo sviluppo. L'utente richiede che il prodotto software sia affidabile, efficiente e facile da usare. Il produttore del software desidera che sia verificabile, manutenibile, portabile ed estendibile. Il manager di un progetto software desidera che il processo di sviluppo sia produttivo, prevedibile e facile da controllare. In questo paragrafo analizzeremo due diverse classificazioni delle qualità relative al software: qualità interne e qualità esterne da un lato, e qualità di prodotto e qualità del processo dall'altro.
Qualità interne ed esterne
Possiamo dividere le qualità del software in due categorie: interne ed esterne. Le qualità esterne sono visibili agli utenti del sistema mentre le qualità interne riguardano gli sviluppatori. In generale gli utenti del software hanno interesse soltanto nelle qualità esterne, ma sono le qualità interne, che in larga misura hanno a che fare con la struttura del software, ad aiutare gli sviluppatori a raggiungere le qualità esterne. Per esempio la qualità interna di verificabilità è necessaria per ottenere la qualità esterna di affidabilità. In molti casi tuttavia le qualità sono tra di loro strettamente correlate e la distinzione tra qualità interne ed esterne non è così marcata.
Qualità del processo e qualità del prodotto
Per realizzare un prodotto software si utilizza un processo. E possibile attribuire alcune qualità a un processo, anche se le qualità del processo sono spesso correlate con quelle del prodotto. Per esempio, l'affidabilità di un prodotto aumenta se il processo relativo richiede un'accurata pianificazione dei dati di test prima che sia effettuata un'attività di progettazione e di sviluppo del sistema. Nell'affrontare le qualità del software è bene comunque cercare di distinguere tra qualità del processo e qualità del prodotto. II termine prodotto di solito si riferisce a quanto viene alla fine consegnato al committente. Quantunque questa sia una definizione accettabile dal punto di vista del committente, non è corretta per lo sviluppatore, in quanto questi, nel corso del processo di sviluppo, produce una serie di prodotti intermedi. Il prodotto effettivamente visibile al committente consiste nel codice eseguibile e nei manuali utente, ma lo sviluppatore produce un numero elevato di altri artifatti, quali i documenti di specifica dei requisiti e di progetto, i dati di test e così via. Noi useremo il termine artefatto per denotare questi prodotti intermedi e per distinguerli dal prodotto finale consegnato al committente. Questi prodotti intermedi sono spesso soggetti agli stessi requisiti di qualità del prodotto finale. Poiché esistono molti prodotti intermedi, è possibile che diversi sottoinsiemi di questi vengano poi resi disponibili a diversi committenti.
2.2 Principali qualità del software
In questa sezione presentiamo le più importanti qualità dei prodotti e dei processi software; ove appropriato, analizziamo una qualità con riferimento alle classificazioni che abbiamo descritto nel paragrafo precedente.
2.2.1 Correttezza, affidabilità e robustezza
I termini correttezza, affidabilità e robustezza sono strettamente correlati e insieme caratterizzano una qualità del software secondo la quale l'applicazione realizza la sua funzionalità attesa. Vediamo ora in dettaglio questi tre termini, analizzandone le loro mutue relazioni.
2.2.1.1 Correttezza
Un programma deve soddisfare la specifica dei suoi requisiti funzionali (functional requirements specification)-, esistono tuttavia altri requisiti, quelli di prestazioni e di scalabilità, i quali non fanno riferimento alle funzionalità del sistema; questi sono chiamati requisiti non funzionali (nonfunctional requirements). Un programma è funzionalmente corretto se si comporta secondo quanto stabilito dalle specifiche funzionali. Spesso si usa il termine "corretto" invece di "funzionalmente corretto", e analogamente, in questo contesto si usa il termine "specifiche" invece di "specifiche dei requisiti funzionali". Noi seguiremo questa convenzione quando il contesto è chiaro. La definizione di correttezza assume che le specifiche del sistema siano disponibili e che sia possibile determinare in maniera non ambigua se un programma soddisfi le specifiche. Tali specifiche raramente sono disponibili per la maggior parte dei sistemi software esistenti. Se una specifica esiste, questa è normalmente scritta in linguaggio non formale usando il linguaggio naturale; pertanto è probabile che contenga ambiguità. Tuttavia la definizione di correttezza è utile perché cattura un obiettivo desiderabile dei sistemi software. La correttezza è un proprietà matematica che stabilisce l'equivalenza tra il software e la sua specifica. Ovviamente, possiamo essere tanto più sistematici e precisi nel valutare la correttezza quanto più rigorosi siamo stati nello specificare i requisiti funzionali. Come vedremo nel Capitolo 6, si può valutare la correttezza di un programma mediante vari metodi, alcuni basati su un approccio sperimentale (ad esempio il testing), altri basati su un approccio analitico, come le ispezioni del codice o la verifica formale della sua correttezza. La correttezza può essere migliorata usando strumenti adeguati quali linguaggi di alto livello, in particolare quelli che supportano un'analisi statica approfondita dei programmi. Analogamente, la correttezza può essere migliorata usando ben noti algoritmi standard o librerie di moduli standard, piuttosto che inventarne ogni volta di nuovi. Infine, la correttezza può essere migliorata utilizzando metodologie e processi di provata efficacia.
2.2.1.2 Affidabilità
Informalmente, il software è considerato affidabile nella misura in cui l'utente può fare affidamento sulle sue funzionalità. La letteratura specializzata definisce l'affidabilità in termini statistici, ovvero come la probabilità che un software operi come atteso in un intervallo di tempo determinato. Approfondiremo questo approccio più avanti, per ora ci accontenteremo invece di una definizione informale. La correttezza è una qualità assoluta: una qualunque deviazione rispetto a quanto stabilito rende un sistema non corretto, indipendentemente dalla serietà delle conseguenze di tale deviazione. La nozione di affidabilità è invece relativa. Se la conseguenza di un errore software non è grave, un software non corretto può continuare a essere considerato affidabile. È comune attendersi che i prodotti dell'ingegneria siano affidabili: quelli non affidabili di solito scompaiono velocemente dal mercato. Sfortunatamente i prodotti software non hanno raggiunto questo invidiabile stato, e vengono spesso rilasciati insieme a una lista di malfunzionamenti noti; gli utenti del software accettano come ineluttabile il fatto che la versione 1 di un prodotto contenga errori (un prodotto del genere viene definito "bacato", in inglese buggy).
Figura 2.1 Relazione tra correttezza e affidabilità nel caso ideale.
Questo è uno dei sintomi più evidenti dell'immaturità dell'ingegneria del software rispetto agli altri campi dell'ingegneria. Nelle discipline classiche, un prodotto non viene rilasciato se può generare malfunzionamenti. Nessuno, infatti, accetterebbe di acquistare un automobile insieme a una lista di inconvenienti noti, o attraverserebbe un ponte che presenta un cartello di avvertimento sui pericoli del suo utilizzo. Gli errori di progettazione sono rari e qualora si manifestino vengono pubblicati sui giornali come fatti di cronaca. Il crollo di un ponte comporta inevitabilmente la denuncia dei progettisti. Al contrario, gli errori di progettazione del software vengono spesso trattati come inevitabili e, quando troviamo errori in un'applicazione, invece di essere sorpresi, in un certo senso li accettiamo con una sorta di rassegnazione. Mentre i manufatti convenzionali sono accompagnati da una garanzia che ne certifica l'assoluta affidabilità, di solito i prodotti software sono accompagnati da un avvertimento, nel quale il produttore afferma che non si ritiene responsabile degli errori presenti nel prodotto e dagli eventuali danni che possono provocare. L'ingegneria del software potrà davvero essere considerata una disciplina ingegneristica solo nel momento in cui si riuscirà a raggiungere con il software un'affidabilità paragonabile a quella raggiunta dagli altri prodotti dell'ingegneria. Nella Figura 2.1 è illustrata la relazione tra affidabilità e correttezza, nell'ipotesi che la specifica dei requisiti funzionali colga esattamente tutte le proprietà desiderabili di un'applicazione e non contenga invece, erroneamente, proprietà indesiderabili. La figura dimostra che l'insieme di tutti i programmi affidabili include l'insieme dei programmi corretti, ma non viceversa. Sfortunatamente, in pratica, la situazione è diversa: infatti la specifica è un modello di ciò che l'utente desidera, ma il modello non è necessariamente una descrizione accurata dei requisiti effettivi dell'utente. Ciò che il software può fare è soddisfare i requisiti specificati dal modello, non può invece assicurare l'accuratezza del modello. La Figura 2.1 rappresenta una situazione idealizzata nella quale si assume che i requisiti siano corretti, e cioè che siano una rappresentazione fedele di ciò che l'implementazione deve assicurare al fine di soddisfare i fabbisogni degli utenti previsti. Come tratteremo in modo approfondito nel Capitolo 7, si pongono frequentemente ostacoli insormontabili al raggiungimento di questo obiettivo; la conseguenza è che talvolta vi sono applicazioni corrette sviluppate sulla base di requisiti non corretti, sicché la correttezza del software non è sufficiente a garantire all'utente che il software si comporti come effettivamente desiderato.
2.2.1.3 Robustezza
Un programma è robusto se si comporta in modo accettabile anche in circostanze non previste nella specifica dei requisiti, per esempio quando vengono inseriti dati di input non corretti o in presenza di malfunzionamenti hardware (come la rottura di un disco). Un programma non è robusto se assume dati di ingresso perfetti e genera un errore non recuperabile durante l'esecuzione, qualora l'utente prema inavvertitamente un tasto sbagliato. Esso tuttavia può essere corretto, se la specifica dei requisiti non indica che cosa il programma dovrebbe fare in corrispondenza di un input scorretto. Naturalmente la robustezza è una qualità difficile da descrivere: se potessimo definire esattamente che cosa occorrerebbe fare per rendere un'applicazione robusta, saremmo sempre capaci di specificare, in maniera completa, il comportamento che ci attendiamo da un programma, e pertanto la robustezza diventerebbe equivalente alla correttezza o all'affidabilità nel senso della Figura 2.1. Ancora una volta, l'analogia con i ponti risulta istruttiva. Due ponti sullo stesso fiume sono entrambi corretti se soddisfano i requisiti di partenza; se invece durante un improvviso e inatteso terremoto solo uno dei due crolla, possiamo definire quello rimasto intatto più robusto dell'altro. Si noti che la lezione che si può trarre dal crollo di un ponte porterà a requisiti più completi per il futuro, stabilendo la resistenza ai terremoti come un requisito di correttezza. In altre parole, non appena il fenomeno studiato diventa meglio conosciuto, è possibile approssimare meglio il caso ideale mostrato nella Figura 2.1, dove le specifiche colgono i requisiti attesi in maniera esatta. La quantità di codice che affronta i requisiti di robustezza dipende dall'area applicativa; ad esempio, un sistema scritto per utenti inesperti di computer deve avere una maggiore predisposizione a reagire a input formattati in maniera scorretta, rispetto a un sistema embedded, che riceve i dati da sensori. Questo naturalmente non significa che i sistemi embedded non debbano essere robusti; al contrario, siccome spesso controllano dispositivi critici, richiedono di conseguenza un'elevata robustezza. In conclusione, possiamo dire che la robustezza e la correttezza sono caratteristiche strettamente correlate, senza che esista un linea divisoria netta tra le due. Se un requisito diventa parte della specifica, il suo soddisfacimento diventa un problema di correttezza; se invece lasciamo un requisito fuori dalla specifica, allora esso può diventare un problema di robustezza. La linea di demarcazione tra le due qualità è la specifica del sistema. Inoltre l'affidabilità ha un ruolo importante, in quanto non tutti i comportamenti scorretti implicano malfunzionamenti di eguale severità; vale a dire, alcuni comportamenti scorretti possono in pratica essere tollerati. Possiamo inoltre usare i termini correttezza, robustezza e affidabilità anche in relazione al processo di produzione del software. Un processo è robusto, per esempio, se può far fronte a cambiamenti inattesi relativi all'ambiente, come il rilascio di un nuovo sistema operativo o l'improvviso trasferimento di metà dei dipendenti dell'azienda a un'altra sede. Un processo è affidabile se porta comunque alla realizzazione di prodotti di elevata qualità. In molte discipline ingegneristiche una considerevole attività di ricerca è stata sviluppata per individuare processi affidabili.
2.2.2 Prestazioni
2.2.3 Usabilità
2.2.4 Verificabilità
2.2.5 Manutenibilità
Il termine "manutenzione del software" viene comunemente usato per indicare le modifiche effettuate su un software dopo il suo rilascio iniziale. Inizialmente, si riteneva che la manutenzione riguardasse esclusivamente l'eliminazione degli errori; tuttavia è stato dimostrato che la maggior parte del tempo per la manutenzione viene di fatto impiegato per migliorare il prodotto implementando caratteristiche che inizialmente non erano presenti nelle specifiche originali, o per correggere specifiche che erano state introdotte in maniera scorretta. In effetti, il termine "manutenzione" viene impiegato in modo improprio. Innanzitutto, per come viene oggi usato, denota un ampio spettro di attività che hanno tutte a che fare con la modifica di un frammento esistente di software al fine di migliorarlo. Il termine che probabilmente meglio esprime l'essenza di questo concetto è "evoluzione del software". Inoltre, in altri prodotti ingegneristici quali l'hardware dei computer, le automobili o gli elettrodomestici, il termine manutenzione si riferisce alle azioni svolte al fine di continuare a garantire il buon funzionamento del prodotto, malgrado il graduale deterioramento delle parti dovuto all'uso continuativo. Ad esempio, i sistemi di trasmissione devono essere lubrificati e i filtri dell'aria vanno periodicamente sostituiti a causa delle polveri che vi si depositano. L'uso del termine manutenzione nel caso del software fornisce un'impressione sbagliata, dal momento che il software non si deteriora. Purtroppo, però, il termine è usato in maniera così diffusa che ormai siamo obbligati a continuare a utilizzarlo. I fatti dimostrano che i costi della manutenzione superano il 60 per cento dei costi totali del software. Per analizzare i fattori che influenzano tali costi è comune classificare la manutenzione del software in tre categorie: correttiva, adattativa e perfettiva. La manutenzione correttiva riguarda la rimozione di errori residui presenti nel prodotto al momento del rilascio, come pure l'eliminazione di errori introdotti nel software durante l'attività di manutenzione stessa. Alla manutenzione correttiva può essere attribuito circa il 20 per cento dei costi totali di manutenzione. Le attività di manutenzione adattativa e perfettiva derivano dalle richieste di cambiamenti nel software e richiedono, da parte dell'applicazione, una capacità di evolvere come qualità fondamentale. Richiedono anche la capacità di anticipare i cambiamenti (definita nel Capitolo 3), quale principio generale che dovrebbe guidare l'ingegnere del software nello svolgimento delle attività progettuali. Alla manutenzione adattativa può essere attribuito circa il 20 per cento dei costi di manutenzione, mentre la manutenzione perfettiva assorbe dunque più del 50 per cento dei costi. La manutenzione adattativa riguarda le modifiche dell'applicazione in risposta a cambiamenti nell'ambiente come, per esempio, una nuova versione dell'hardware, del sistema operativo o del DBMS con il quale il software interagisce. In altri termini, nella manutenzione adattativa la necessità dei cambiamenti del software non è tanto da attribuire a problemi del software stesso, quali ad esempio errori residui o incapacità di fornire alcune funzioni richieste dall'utente, quanto a cambiamenti che avvengono nell'ambiente nel quale il software è inserito. Infine, la manutenzione perfettiva riguarda i cambiamenti nel software per migliorarne alcune qualità. In questo caso i cambiamenti sono dovuti alla necessità di modificare le funzioni offerte dall'applicazione, aggiungerne di nuove, migliorare le prestazioni, renderne più facile l'utilizzo, etc. Le richieste di manutenzione perfettiva possono provenire direttamente dall'ingegnere del software, con l'obiettivo di migliorarne la presenza sul mercato, oppure dal committente, al fine di rispondere a nuovi requisiti. Il termine legacy software (software ereditato) si riferisce al software che esiste in un'organizzazione da lungo tempo e che pertanto incorpora una parte notevole delle conoscenze dei processi dell'organizzazione. Pertanto tale software possiede per l'organizzazione un valore strategico, rappresenta investimenti passati e non può essere sostituito facilmente. D'altra parte, a causa della sua età, in genere questo software è scritto in linguaggi di programmazione ormai desueti e utilizza tecnologia obsoleta. Il legacy software è pertanto diffìcile da modificare e mantenere. Il legacy software rappresenta una sfida all'evoluzione del software. Le tecniche e le tecnologie di reverse engingeering (letteralmente, "ingegneria inversa") e reenginereering ("re-ingegnerizzazione") hanno l'obiettivo di aiutare a scoprire la struttura del legacy software per ristrutturarlo o, almeno, aiutare a migliorarlo. La manutenibiltà può essere vista come un insieme di due diverse qualità: la riparabilità e l'evolvibiltà. Il software è riparabile se consente facilmente di eliminare i difetti; è evolvibile se facilita i cambiamenti che gli permettono di soddisfare nuovi requisiti. La distinzione tra evolvibilità e riparabilità non è sempre chiara; ad esempio, se le specifiche dei requisiti sono vaghe, può non essere evidente se un'attività di modifica sia volta all'eliminazione di un difetto o al soddisfacimento di un nuovo requisito.
2.2.5.1 Riparabilità
Un sistema software è riparabile se i suoi difetti possono essere corretti con una quantità ragionevole di lavoro. In molti prodotti ingegneristici la riparabilità è un obiettivo progettuale fondamentale. Per esempio, un'automobile è costruita in modo che le parti maggiormente soggette a guasti o a usura siano agevolmente accessibili. Nell'ingegneria dell'hardware dei computer esiste una particolare specializzazione chiamata RAS (dalle iniziali di repairability, availability e serviceability) che affronta queste problematiche. In altri campi dell'ingegneria, quando il costo di un prodotto diminuisce e il prodotto diventa di uso quotidiano, assumendo lo stato di commodity, la necessità di riparabilità decresce: di fatto diventa più economico sostituire l'intero prodotto o gran parte di esso piuttosto che ripararlo. Ad esempio, nei primi apparecchi televisivi capitava di dover sostituire una singola valvola: oggi invece viene sostituita l'intera parte circuitale. Una tecnica comune per ottenere la riparabilità dei prodotti consiste nell'utilizzo di parti standard, facilmente sostituibili. A titolo di esempio, i personal computer inizialmente erano costruiti con componenti ad hoc e mediante meccanismi di interconnessione di tipo proprietario. Oggi, invece, sono costruiti con componenti standard e con sistemi di interconnessione {bus) standard. Questa standardizzazione ha portato a proliferare di produttori, ciascuno specializzato in singole parti. Attraverso la specializzazione, questi produttori hanno potuto dedicarsi al miglioramento dell'affidabilità di ciascuna parte, riducendone anche i costi. Di conseguenza, la produzione di un computer è diventata più veloce e meno costosa, e un guasto può essere riparato sostituendo la parte difettosa. Nel caso del software, le parti che costituiscono un'applicazione non si deteriorano. Pertanto, mentre l'uso di parti standard può ridurre il tempo e il costo di produzione, il concetto di parti riparabili non sembra applicarsi in maniera analoga al software. Il fatto di costruire un software a partire da componenti riduce solo il tempo di progettazione, in quanto il progettista si concentra sul combinare insieme componenti ben noti, piuttosto di dover partire da zero nel progetto. La riparabilità è influenzata anche dal numero di parti in un prodotto. Per esempio, è più difficile riparare un difetto nella carrozzeria di un'automobile costituita da un unico pezzo, di quanto non succeda per una carrozzeria costituita da numerosi componenti con forma regolare. In questo secondo caso è possibile sostituire un singolo pezzo molto più facilmente rispetto alla sostituzione dell'intera carrozzeria. Naturalmente, se la carrozzeria fosse costituita da un numero eccessivo di parti, richiederebbe molte connessioni tra questi, il che implicherebbe a sua volta una probabilità non trascurabile che le connessioni stesse debbano essere riparate. Una situazione analoga si applica al software: un prodotto software che consiste di moduli ben progettati è più facile da analizzare e riparare di un sistema monolitico. Il semplice aumento del numero dei moduli però non rende di per sé più riparabile un prodotto. E necessario scegliere la corretta struttura dei moduli, con le giuste interfacce che evitino l'insorgere di interconnessioni e interazioni troppo complesse tra i moduli. La giusta modularizzazione promuove la riparabilità, consentendo all'ingegnere di localizzare più facilmente gli errori. Nel Capitolo 4 esamineremo diverse tecniche di modularizzazione, tra le quali l'information hiding (incapsulamento di informazioni) e i tipi di dati astratti. La riparabilità può essere migliorata anche attraverso l'uso di strumenti adeguati; per esempio, l'utilizzo di un linguaggio di alto livello piuttosto del linguaggio assembler conduce a una migliore riparabilità. Strumenti come i debugger possono aiutare a isolare e riparare gli errori. Infine la riparabilità di un prodotto ne influenza l'affidabilità, e la necessità di riparabilità diminuisce con l'aumentare dell'affidabilità.
2.2.5.2 Evolvibilità
Come altri prodotti dell'ingegneria, i prodotti software sono modificati in continuazione al fine di fornire nuove funzioni o modificare quelle già esistenti. Il fatto che il software sia intrinsecamente duttile rende la modifiche molto facili da effettuare direttamente sull'implementazione. Esiste tuttavia una sostanziale differenza tra le modifiche nel campo del software e le modifiche negli altri prodotti ingegneristici. In quest'ultimo caso le modifiche iniziano al livello del progetto e solo successivamente si procede verso modifiche all'implementazione del prodotto. Ad esempio, se si decide di aggiungere un piano a un edificio, innanzitutto occorre eseguire uno studio di fattibilità per valutare se questa aggiunta possa effettivamente essere apportata in maniera sicura. Successivamente il progetto deve essere approvato, dopo aver verificato che non violi alcuno dei vincoli esistenti. Solo a questo punto può essere firmato un contratto per la costruzione del nuovo piano. Questo approccio sistematico di solito non è applicabile per il software. Anche nel caso di cambiamenti radicali a un'applicazione, spesso la fasi di studio di fattibilità e di analisi del progetto vengono saltate e si procede immediatamente a effettuare le modifiche sull'implementazione. Peggio ancora, una volta che i cambiamenti sono stati effettuati, questi non vengono documentati neppure a posteriori; vale a dire, le specifiche non vengono aggiornate in modo da riflettere i cambiamenti effettuati, e questo rende eventuali futuri cambiamenti ancora più difficili da apportare. Allo stesso tempo, occorre osservare che prodotti software di successo hanno una lunga vita. Il loro rilascio iniziale è il primo di una lunga serie di rilasci, ciascuno dei quali costituisce un passo nell'evoluzione del sistema. Se il software viene progettato avendo in mente la sua evoluzione e se ogni modifica viene progettata e poi messa in pratica attentamente, allora il software può effettivamente evolvere in maniera ordinata e controllata. Con il crescere del costo della produzione del software e della complessità delle applicazioni, l'evolvibilità assume ancora maggiore importanza. Uno dei motivi è la necessità di far fruttare gli investimenti fatti nel software, a fronte dei continui cambiamenti della tecnologia hardware. Ancora oggi alcuni dei primi grandi sistemi sviluppati negli anni Sessanta continuano a evolversi per sfruttare i vantaggi dei nuovi dispositivi hardware e delle tecnologie di rete. Per esempio, il sistema di prenotazione SABRE dell'American Airlines, sviluppato inizialmente negli anni Sessanta, è andato evolvendosi per decenni al fine di includere un insieme sempre più ricco di funzionalità. Nelle fasi iniziali, la maggior parte dei sistemi software è in grado di evolvere con facilità, ma purtroppo, dopo continui cambiamenti, si raggiunge uno stadio in cui ogni ulteriore modifica rischia di introdurre malfunzionamenti nelle caratteristiche esistenti. Inizialmente, l'evolvibilità viene raggiunta attraverso un'adeguata modularizzazione del sistema, ma i cambiamenti progressivi tendono a ridurre la modularità del sistema originario, tanto più quando le modifiche vengono effettuate senza un attento studio del progetto originario e senza che venga stilata una descrizione precisa dei cambiamenti, sia nei documenti di progetto che in quelli di specifica dei requisiti. E stato dimostrato da studi empirici sull'evoluzione di grandi sistemi software che la capacità di evolvere tende a diminuire con i rilasci successivi del prodotto. Ogni rilascio complica la struttura del software e rende le modifiche future sempre più difficili da effettuare. Al fine di superare questo problema occorre fare ogni sforzo perché sia il progetto iniziale che i successivi cambiamenti vengano effettuati prestando estrema attenzione al problema dell'evolvibilità. Questa è un'importante qualità del software per il suo impatto economico; molti dei principi presentati nel prossimo capitolo aiutano a ottenere una migliore evolvibilità. Nel Capitolo 4 illustreremo concetti quali le famiglie di programmi, le famiglie di prodotti e le architetture software, che hanno lo scopo di consentire una più facile evoluzione dei prodotti software, L'approccio delle famiglie di prodotti, spesso chiamate "linee di prodotti" {produci line), ha proprio l'obiettivo di trovare un approccio sistematico all'evolvibilità.
2.2.6 Riusabilità
2.2.7 Portabilità
2.2.8 Comprensibilità
2.2.9 Interoperabilità
2.2.10 Produttività
2.2.11 Tempestività
La tempestività è una qualità del processo che indica la capacità di rendere disponibile un prodotto al momento giusto. Storicamente, questa qualità è stata carente nei processi di sviluppo del software e ciò ha condotto alla cosiddetta "crisi del software" che, a sua volta, è stata responsabile della nascita dell'ingegneria del software come disciplina nell'ambito dell'informatica. Al giorno d'oggi, soprattutto a causa dei mercati sempre più competitivi, i progetti software affrontano sfide ancora più stringenti in termini di tempo di sviluppo dei prodotti. Il seguente esempio illustra come venivano gestite le difficoltà di consegna verso la fine degli anni Ottanta. Una società di software aveva promesso di rilasciare una prima versione del proprio compilatore per il linguaggio Ada entro una certa data. Alla scadenza prevista, i committenti che avevano effettuato l'ordine, anziché ricevere il prodotto, si videro recapitare una lettera, nella quale si annunciava la decisione di ritardare la consegna in quanto il prodotto era instabile e conteneva ancora molti difetti; la consegna sarebbe stata prorogata di tre mesi. Dopo quattro mesi il prodotto fu consegnato insieme a una lettera, nella quale si dichiarava che molti difetti erano stati corretti, ma molti altri erano ancora presenti. Tuttavia, il produttore aveva deciso che era meglio che i clienti ricevessero il compilatore Ada, nonostante contenesse ancora numerosi gravi difetti, in modo tale che potessero iniziare a sviluppare le applicazioni che utilizzavano il linguaggio Ada. Si era infatti valutato che i rischi connessi all'utilizzo di un prodotto prematuro fossero controbilanciati dal poter utilizzare il prodotto per lo sviluppo delle applicazioni nelle quali il committente era coinvolto. Il risultato fu comunque che il compilatore fu consegnato in ritardo e con difetti. Di per sé la tempestività non è sempre una qualità utile, anche se talvolta arrivare in ritardo alla consegna di un prodotto può precludere interessanti opportunità di mercato. Non ha molto senso consegnare alla data prevista un prodotto privo di altre qualità, quali l'affidabilità e le prestazioni. Tuttavia alcuni sostengono che la consegna tempestiva di una versione preliminare di un prodotto, ancorché instabile, favorisca la successiva accettazione della versione finale. Internet ha facilitato questo approccio, in quanto i produttori di software possono esporre versioni iniziali di prodotto attraverso i propri siti, e rilasciarne l'utilizzo agli utenti interessati, i quali possono fornire utili suggerimenti e critiche. La tempestività richiede un'attenta pianificazione temporale del processo, un'accurata stima delle attività e una specifica chiara degli obiettivi intermedi (milestone). Tutte le discipline ingegneristiche usano tecniche di gestione del progetto che favoriscono la tempestività. Esistono addirittura molti strumenti di gestione dei progetti, supportati dal computer, che facilitano queste attività. Le tecniche standard di gestione dei progetti sono talvolta difficili da applicare nell'ambito dell'ingegneria del software, a causa delle difficoltà intrinseche nella definizione dei requisiti e a causa della natura astratta del bene da produrre. Queste difficoltà, a loro volta, danno luogo a problemi nel misurare la quantità di lavoro necessaria per produrre un componente software, a problemi nel quantificare la produttività dei progettisti — o addirittura a individuare metriche affidabili per definire la produttività — e a problemi nella definizione di milestone precise e verificabili. Un'altra causa di difficoltà nel raggiungimento della tempestività nel processo software è costituita dalla continua evoluzione dei requisiti dell'utente.
Figura 2.2 Mancanza di tempestività del software.
La Figura 2.2 illustra graficamente l'andamento delle modifiche nei requisiti dell'utente rispetto a quanto effettivamente offerto dal sistema e indica come mai gran parte dei progetti di sviluppo del software falliscono. Il tempo t0
indica il momento in cui ci si rende conto della necessità di sviluppare un sistema software. A questo istante lo sviluppo inizia con una conoscenza incompleta degli effettivi requisiti dell'utente e pertanto, il prodotto iniziale consegnato al tempo t1
non solo non soddisfa i requisiti iniziali al tempo t0
, ma neppure quelli che si sono andati evolvendo dall'istante t0 all'istante t1
. Tra gli istanti t1
e t3
sul prodotto viene eseguita della manutenzione, al fine di approssimare meglio le richieste dell'utente. All'istante t2
possiamo immaginare che termini lo sviluppo di una nuova versione del sistema, che soddisfa i requisiti che l'utente aveva inizialmente, all'istante tv Per la ragione che abbiamo visto nel Paragrafo 2.2.5, al tempo t3
il costo di manutenzione è diventato così alto che lo sviluppatore del software decide di effettuare una riprogettazione radicale del sistema. La nuova versione diventa pertanto disponibile al tempo t4
, ma a questo punto la distanza del sistema rispetto alle necessità dell'utente è diventata ancora più grande di quanto lo fosse precedentemente. Al di fuori dell'ingegneria del software, un classico esempio delle difficoltà che si incontrano nella comprensione di requisiti complessi viene offerta dai sistemi avanzati di difesa. In parecchi casi descritti in letteratura, i sistemi diventano obsoleti prima ancora di essere consegnati, oppure non soddisfano i requisiti iniziali. Ovviamente, dopo dieci anni di sviluppo o più, è diffìcile decidere che cosa fare di un prodotto che non soddisfa i requisiti di dieci anni prima. Il problema chiave in questi casi è la formulazione precisa dei requisiti, in quanto questi dovrebbero riuscire a prefigurare il sistema più avanzato possibile, ovvero nel momento in cui sarà consegnato e non nel momento in cui i requisiti sono formulati. Una tecnica per ottenere la tempestività è attraverso la consegna incrementale del prodotto (incrementai delivery). Questa tecnica è illustrata attraverso questo ulteriore esempio di sviluppo di un compilatore Ada, eseguito da una società di software diversa da quella citata precedentemente. Questa società fu in grado di consegnare molto presto un compilatore che supportava un sottoinsieme estremamente ridotto del linguaggio Ada; sostanzialmente, si trattava di un sottoinsieme equivalente al Pascal, con in più il costrutto package. Il compilatore non supportava alcuna delle caratteristiche innovative del linguaggio, quali la programmazione concorrente e la gestione delle eccezioni. Il risultato fu però la consegna molto rapida di un prodotto affidabile. Gli utenti iniziarono a sperimentare con il nuovo linguaggio e la società che aveva il compito di sviluppare il compilatore riuscì gradualmente a comprendere e imparare a trattare le sottigliezze e le difficoltà delle nuove caratteristiche del linguaggio. Il compilatore per il linguaggio completo fu consegnato attraverso una serie di rilasci intermedi, che avvennero in un periodo di circa due anni. I rilasci incrementali consentono al prodotto di essere reso disponibile in tempi brevi e l'uso del prodotto contribuisce a raffinare i requisiti del prodotto stesso, in modo incrementale. Ovviamente, la possibilità di una consegna incrementale dipende dalla capacità di spezzare l'insieme delle funzionalità richieste in sottoinsiemi che possono essere sviluppati successivamente. Se non è possibile identificare questi sottoinsiemi, è impensabile che si possa adottare un processo che renda disponibile il prodotto in modo incrementale. Tuttavia, un processo non incrementale impedisce la produzione di sottoinsiemi, anche se questi sottoinsiemi sono identificabili. Pertanto, è la combinazione di un prodotto che può essere spezzato in sottoinsiemi e di un processo incrementale che ci consente uno sviluppo tempestivo. E ovvio che la consegna incrementale di sottoinsiemi di scarsa utilità, non ha alcun valore. La tempestività deve essere combinata con le altre qualità del software. Nel Capitolo 4 discuteremo altre tecniche che consentono di sviluppare sottoinsiemi di un prodotto e nel Capitolo 7 vedremo le tecniche per ottenere processi incrementali.
Last updated