Affidabilità e Sicurezza dei Sistemi e del Software

Capitolo 3° - Metodologie per la realizzazione di software affidabile (parte 1a)

di A. Autino e V. de Val


 

1 - Introduzione

Individuate le peculiarità della progettazione software per quanto riguarda l’affidabilità, vediamo ora di analizzare il dettaglio le tecniche disponibili per lo sviluppo di un’applicazione software affidabile.

Premettiamo subito che le metodologie che stiamo per presentare non sono delle metodologie "speciali", da utilizzarsi esclusivamente quando si sviluppano applicazioni real-time che richiedono un elevato livello di affidabilità e sicurezza. Al contrario, molto di quanto scritto qui di seguito può essere classificato nell'ambito più generale di "metodologie per un corretto sviluppo di un progetto software", non strettamente real-time, né necessariamente ad alto contenuto affidabilistico. D'altro canto, se consideriamo che in prima approssimazione possiamo definire come affidabile un software che non contiene errori, è chiaro che l'obiettivo di evitare gli errori è un obiettivo comune a tutti gli sviluppatori software e non solo a quelli real-time (nel real-time, il problema è spesso legato ai danni che un eventuale errore può causare, e di conseguenza alla cura che si deve porre nel ridurre al minimo errori e loro conseguenze).

Introdurremo quindi una serie di argomenti relativi al ciclo di vita del software, alle metodologie per lo sviluppo del software, al controllo qualità. Tali argomenti, di valenza generale, verranno naturalmente visti con un'attenzione particolare alle problematiche real-time.

Oltre a questo, però, esistono anche tecniche decisamente "dedicate" per lo sviluppo di software altamente affidabile. In particolare, ci riferiamo alle tecniche usate per gestire e "recuperare" l'occorrenza di eventuali errori (dette "fault-tolerance"). Infatti, nelle applicazioni che possiamo definire standard, un eventuale errore si traduce quasi sempre in una notifica all'operatore (tramite messaggio, scrittura in un log file o altro) e nella terminazione del programma. Nelle applicazioni real-time, che controllano dispositivi fisici, è fortemente sconsigliato che un programma termini, lasciando completamente incontrollato il relativo dispositivo fisico. Al contrario, l'eventuale errore deve essere gestito e recuperato, riportando il sistema in uno stato controllato e sicuro.

(Per completezza, notiamo che anche altri settori dell'informatica, quali il gestionale, hanno esigenze di fault-tolerance. Basti pensare ad un'applicazione bancaria che gestisce una base dati, in cui non si può certo permettere che un errore corrompa la base dati. Le tecniche di fault-tolerance qui usate, tuttavia, sono spesso diverse e basate su meccanismi di protezione dei database: gestione transazioni, commit-rollback, logbook di database, ecc.)

I concetti sopra elencati verranno approfonditi in una serie di capitoli:

 

2 - Concetti Generali

Introduciamo innanzitutto una serie di termini base, che ci saranno utili nelle pagine successive.

Tutte le applicazioni software sono descritte da un insieme di specifiche (scritte in modo testuale oppure usando una metodologia più formale) che definiscono il comportamento richiesto all’applicazione. Ogni punto della specifica (che definisce quindi un aspetto puntuale del sistema) è detto requisito.

Possiamo definire l’affidabilità di un’applicazione software come segue [Randell 1978]:

L’affidabilità è una misura della capacità di un sistema di comportarsi come stabilito dalle sue specifiche.

E’ importante notare che le suddette specifiche, oltre ad essere complete, consistenti e non ambigue (qualità tutt’altro che scontate) devono anche definire i tempi di risposta del sistema ai vari eventi.

Introduciamo ora il concetto di failure (basato ancora su [Randell 1978]):

Si parla di failure quando il comportamento di un sistema differisce da quanto stabilito dalle sue specifiche.

(Nota: Per questa definizione, come pure per quelle che seguiranno, preferiamo utilizzare direttamente i termini inglesi, sia perché oramai largamente utilizzati, sia per evitare potenziali confusioni legati a termini simili, che in italiano rischiano di essere sinonimi.)

Notare come la failure sia riferita al comportamento esteriore del sistema: una failure è causata da dei problemi interni all’applicazione, che alla fine si manifestano in comportamenti esteriori scorretti. Questi problemi interni sono chiamati fault.

Possiamo classificare i fault in funzione della loro origine ([Burns 1989]):

  1. Fault causati da errori nelle specifiche. Come analizzato nel capitolo precedente, questa è la caratteristica più specifica di un’applicazione software e si ritiene anche ([Levenson 1986]) che sia la causa principale delle failure software.

  2. Fault introdotti da errori nel disegno o nell’implementazione.

  3. Fault introdotti da guasti nei processori su cui viene eseguita l’applicazione software.

  4. Fault introdotti da guasti (transitori o permanenti) nel sistema di comunicazione.

Tutte le techniche affidabilistiche software ruotano attorno alla identificazione delle failures ed alla eliminazione dei Fault.

I metodi disponibili per minimizzare la presenza di Fault e delle loro conseguenze sono sostanzialmente due:

  1. Evitare il più possibile che dei Fault vengano introdotti nel sistema, o comunque eliminarli prima che il sistema venga messo in esercizio (fault prevention). Questi metodi si applicano principalmente alla risoluzione di Fault di tipo (a) e (b).

  2. Costruire il sistema in modo che sia capace di sopportare la presenza di Fault quando questi si verificheranno (fault tolerance). Questi metodi si applicano principalmente alla risoluzione di Fault di tipo (c) e (d).

Ovviamente i due approcci non sono esclusivi, ma complementari: si cercherà di eliminare il più possibile i Fault in fase di progettazione, ma si inseriranno anche dei meccanismi di protezione all’interno dell’applicazione che permettano di garantire un corretto funzionamento anche quando dei Fault (che non saremo riusciti ad eliminare in fase di progettazione) si manifesteranno.

 

3 - Fault Prevention

La prevenzione dei Fault è di solito divisa in due aspetti:

  1. Fault Avoidance: cercare di costruire un’applicazione con il minor numero possibile di errori. Questo punto riguarda principalmente la fase di definizione delle specifiche, la fase di progettazione e quella di codifica.

  2. Fault Removal: cercare di trovare e rimuovere il maggior numero possibile di errori. Questo punto riguarda invece tutta la fase di test del sistema.

In realtà, entrambi gli aspetti ruotano attorno allo stesso punto base: come costruire un’applicazione software in modo strutturato e sistematico, usando cioè un metodo di lavoro ben definito, che permetta ai progettisti di sviluppare il progetto in un modo razionale, organizzato e soprattutto completo. Quanto detto può sembrare scontato (tutte le applicazioni professionali, software e non, dovrebbero essere progettate in questo modo), ma purtroppo il software sfugge spesso a questo vincolo. Le cause di questo problema sono state analizzate nel Capitolo 2 di questa serie.

La disciplina dell'informatica che cerca di sopperire a questa lacuna e tenta di dare allo sviluppo software un approccio sistematico e razionale, tipico di tutte le altre discipline dell'ingegneria, si chiama appunto Software Engineering. Tale disciplina, come del resto tutte quelle legate all'informatica, è una disciplina giovane, in continua evoluzione, che non ha ancora raggiunto una maturità (in termini di metodologie e strumenti) tale da poter proporre un unico metodo, universalmente riconosciuto, per lo sviluppo strutturato del software. Posiamo però affermare che esiste oramai un'insieme di conoscenze maturato nel corso degli ultimi decenni che permette di affrontare lo sviluppo di una applicazione real-time in modo ragionevolmente organico e completo.

 

3.1. Il ciclo di vita del software

Solo l'applicazione sistematica di tali metodi può garantire lo sviluppo di un'applicazione con un numero minimo di Fault, e pertanto possiamo dire che tutte le tecniche di fault prevention si riassumono nell'adozione di una precisa metodologia di sviluppo del software o, come si dice in termini tecnici, nell'adozione di un preciso ciclo di vita del software, da seguire in modo sistematico.

Forniremo qui di seguito un'introduzione a tali tecniche, per quanto di interesse allo sviluppo di un'applicazione software real-time. La nostra presentazione sarà di carattere introduttivo, sia per la vastità dell'argomento (esiste una cospicua bibliografia a riguardo - si consiglia ad esempio [Dorfman 1997]), sia per la continua evoluzione della materia.

Possiamo dire che, indipendentemente dal tipo di applicazione da sviluppare, dai linguaggi di programmazione e dagli ambienti di sviluppo scelti, le fasi principali seguite in un qualsiasi progetto software sono le seguenti:

  1. Definizione dei requisiti: capire il più esattamente possibile che cosa l’applicazione deve fare.

  2. Disegno: identificare i vari moduli, le loro funzioni e le loro interazioni (il modo in cui i moduli vengono identificati e definiti varia pesantemente a seconda della metodologia scelta: top-down, a oggetti, ecc., ma in questo contesto tali differenze non sono significative).

  3. Implementazione dei singoli moduli (inclusiva della verifica di corretto funzionamento di ogni modulo).

  4. Integrazione dei moduli (inclusiva della verifica di corretta comunicazione tra i moduli).

  5. Verifica del corretto funzionamento (rispetto ai requisiti iniziali) dell’applicazione.

Le fasi sopra descritte, opportunamente collegate tra loro, costituiscono appunto il ciclo di vita del software. In realtà di cicli di vita non ne esiste uno solo, ma ne sono stati definiti diversi a seconda delle interazioni delle varie fasi tra loro. Il modello base, e quello anche più diffuso, è detto a cascata (in inglese waterfall) perché prevede di eseguire le varie fasi rigorosamente in sequenza. Altri modelli più recenti prevedono delle iterazioni tra le fasi di definizione dei requisiti e quella di disegno (modello a spirale), oppure lo sviluppo di prototipi, oppure ancora lo sviluppo incrementale (vedi [Dorfman 1997]). Per le applicazioni di nostro interesse (applicazioni embedded, real-time, con requisiti di affidabilità) il modello sicuramente più diffuso è quello a cascata.

La nostra esperienza ci porta ad osservare che, specialmente su grossi progetti, la progettazione affidabilistica, aldilà della buona volontà e della conoscenza teorica delle metodologie, è spesso completamente o in parte invalidata da alcune pratiche scorrette, che tendono a verificarsi, con la stessa puntualità di un alluvione in una zona già più volte disastrata:

Quanto sopra (o varianti ancora peggiori) non è evitabile senza che vi sia consapevolezza del problema, e senza che nel team di direzione del progetto siano presenti persone che hanno esperienza tecnologica e manageriale di progetti simili, e la sensibilità delle problematiche tipiche da affrontare. La gestione affidabilistica del progetto non va lasciata alla buona volontà dei singoli progettisti nè alla presenza fortuita di eventuali grilli parlanti. Si deve invece dare budget sin dall'inizio all'applicazione di metodologie adatte. Un notevole aiuto può venire da persone che sappiano sviluppare metodologie ad hoc, durante la progettazione. Le metodologie applicate devono essere d'aiuto, facilitare il lavoro dei progettisti, e non appesantirlo, come avviene con quasi tutte le metodologie dell'epoca industriale, basate sulla carta. Come vedremo meglio nel capitolo dedicato al Project Management affidabilistico dell'era elettronica, le metodologie di progettazione e di test possono e devono ormai basarsi su strumenti elettronici, che la tecnologia ci mette a disposizione in quantità. 

Usare un approccio sistematico nello sviluppo di un’applicazione software, significa definire con precisione il suo ciclo di vita, il significato di ogni singola fase, la relazione tra le varie fasi. E' importante definire, per ogni fase:

  1. Quali sono le informazioni in ingresso necessarie e/o disponibili. Qual'è il grado di integrazione ed armonizzazione tra i design input disponibili. Quali azioni sono eventualmente necessarie per chiarire, integrare ed armonizzare i design input.

  2. Quali sono le competenze necessarie. Quali competenze mancanti si devono eventualmente ricercare. 

  3. Che cosa si deve fare. Quali eventuali nuove attività si devono schedulare e quali attività già schedulate devono eventualmente essere ripesate e rischedulate.

  4. Quali sono le metodologie e gli strumenti metodologici necessari.

  5. Che cosa si deve produrre al termine della fase.

  6. Come si verifica con precisione che quanto è stato prodotto sia corretto.

In particolare, gli ultimi due punti sottolineano la necessità di avere una uscita ben definita e verificata da ogni fase, che è il punto cardine attorno cui ruota la progettazione sistematica del software, e che la distingue da un approccio che potremmo chiamare "artigianale".

Tutte le tecniche di fault prevention si innestano nelle varie fasi del ciclo di vita e hanno lo scopo di garantire che ogni fase sia stato fatta in modo completo ed esaustivo: le tecniche di fault avoidance si focalizzano sulle prime fasi (definizione di requisiti e disegno), le fasi di fault removal si focalizzano invece sulle ultime (fondamentalmente sul testing).

Chiarito questo, passeremo ora ad analizzare più da vicino le varie fasi. Il nostro scopo non sarà quello di definire nel dettaglio gli obbiettivi, le attività ed i risultati di ogni singola fase: questo richiederebbe degli articoli a parte (se non un libro). Assumiamo al contrario che i lettori abbiamo una conoscenza generale di queste attività, e ci focalizzeremo quindi su quegli aspetti di ogni singola attività che sono più importanti per la produzione di un software affidabile.

Grande attenzione si deve focalizzare sulla prima delle fasi -- la definizione dei requisiti -- una fase di grande criticità per lo sviluppo di un software affidabile:

  1. Come già riportato, gli errori in fase di specifica che sono la causa principale delle failure software ([Levenson 1986]).

  2. L'importanza di questa fase viene molto spesso sottostimata, soprattutto dagli sviluppatori software, i quali non vedono l'ora di passare dalla carta al codice. Al contrario, ogni errore fatto in questa fase (requisito sbagliato o dimenticato) ha un costo enorme nel prosieguo del progetto. (A puro titolo di esempio, quante volte è capitato di non analizzare in fase di definizione dei requisiti le problematiche di diagnostica, salvo poi scoprire a sistema finito che quei requisiti dimenticati erano in realtà indispensabili, ma che oramai è impossibile introdurli, a meno di rifare il progetto?)

  3. Senza una corretta fase di definizione dei requisiti, tutto il resto del ciclo di vita del software viene a cadere. Ad esempio, senza avere definito bene i requisiti è impossibile organizzare una fase di test efficiente.

 

3.2 - Definizione dei requisiti

Scopo di questa fase è definire che cosa deve fare il software. Non si tratta solo di definirlo dal punto di vista dell'utente (che a volte ha già fatto ciò tramite un documento chiamata appunto "Requisiti Utente"), quanto di farlo dal punto di vista dello sviluppatore: bisogna cioè costruire un modello logico dell'applicazione, completo e coerente.

Questa è la fase che genera i Fault più insidiosi, perché una volta introdotti sono praticamente impossibili da rimuovere. Infatti, se nella fase di definizione dei requisiti si definisce un comportamento scorretto dell’applicativo, l’applicazione verrà progettata secondo quel requisito e neanche i tests (che verificano che il software si comporti in accordo con i suoi requisiti) potranno identificare il problema (Ciò non è del tutto esatto. Infatti accade a volte che nella fase di test si simuli una certa condizione operativa e, mentre si esegue il test, ci si accorga che il comportamento dell’applicazione è inconsistente. In altre parole, vedendo il software in azione, si riflette sui requisiti e ci si accorge che essi erano sbagliati o perlomeno insufficienti - caso, quest’ultimo, più probabile.)

In questo articolo non entreremo nei dettagli di questa importante fase del ciclo di vita del software; ci limiteremo a sottolineare qui di seguito alcuni punti particolarmente importanti per l’affidabilità:

  1. Insistiamo sulla fondamentale importanza di questa fase, anche per quanto riguarda il problema dell’affidabilità. Più il software ha requisiti di affidabilità è più le specifiche devono essere complete, coerenti e non ambigue. Se ciò viene meno, tutte le altre tecniche affidabilistiche (fault removal e fault tolerance) saranno praticamente inutili!
  2. Se si vuole veramente preparare dei requisiti completi, coerenti e non ambigui, è praticamente indispensabile utilizzare una metodologia formale per descrivere il sistema. Una metodologia può essere definita come un formalismo (grafico o logico) per descrivere il sistema software, corredato da tutti i meccanismi pratici necessari per usare questo formalismo per la definizione di un’applicazione software. I formalismi più usati sono:
    1. Structured Analysis (SA): si tratta di una metodologia orientata al processo, in cui si descrive come il sistema trasforma gli input in output.
    2. SADT (Structured Analysis and Design Technique) e la Structured Analysis con le sue estensioni Real Time (SA/RT): rispetto ai puri modelli SA, aggiungono una vista sul controllo del sistema software (attivazione delle varie funzioni, sincronizzazione, ecc.) Si tratta di metodologie molto diffuse nel controllo di processo e, più in generale, nelle applicazioni embedded.
    3. Metodologie orientate ai dati, come l’Entity Relationship Diagrams (ERD): molto utili quando l’aspetto più importante dell’applicazione sono i dati, come nel caso di database o applicazioni che gestiscono transazioni. (In ogni applicazione software c’è sempre una parte di dati ed una parte di funzioni, che operano su di essi. I due aspetti non sono quindi esclusivi, ma complementari. Tuttavia, in alcuni casi, la parte più importante da definire è la parte funzionale (si pensi ad un sistema embedded che deve rispondere a degli eventi esterni generando degli opportuni output, che ha dei requisiti temporali nella generazione delle risposte, che deve attivare certe funzioni in certe ben precise condizioni operative). In altri casi, invece, è la corretta definizione dei dati attorno cui opererà il software (si pensi ad un database, in cui la parte più importante è la definizione dei vari tipi di dati su cui operare, e sui legami e vincoli tra questi, mentre le funzioni si possono quasi banalmente riassumere in inserisci, modifica e rimuovi).
    4. Metodologie orientate agli oggetti; esse racchiudono i dati e le funzioni che possono manipolarli dentro il concetto di oggetto, e descrivono un’applicazione come un insieme di oggetti che interagiscono tra loro. Sono le metodologie più recenti e che probabilemente soppianteranno tutte le precedenti, anche nel campo real-time.
    5. Metodi formali (quali Z): si tratta di metodi che impongono una definizione rigorosa, attraverso un formalismo algebrico, del software; costruendo una descrizione formale è possibile sviluppare concetti quali l’analisi "matematica" del software per studiarne la sua correttezza. Da un punto di vista pratico, si possono comunque classificare come metodologie orientate al processo.

    La scelta della metodologia dipende da vari fattori, quale il tipo di applicazione (i sistemi embedded e real-time sono tipicamente meglio descrivibili tramite metodologie funzionali) e la preparazione di chi deve stilare i requisiti. Infatti, usare una metodologia male produce effetti peggiori che usare una qualsiasi altra metodologia, che però si conosce. Quanto detto può sembrare una banalità, tuttavia questo è un caso che si presenta spesso. In particolare, capire di vedere sviluppatori (soprattutto i loro dirigenti) che pensano che la soluzione a tutti i loro problemi sia la scelta di una nuova, innovativa metodologia (attualmente, come già detto, va molto di moda l’Object Orientation) e ovviamente del tool che ne supporta l’utilizzo. Pertanto si affrettano ad acquistare il tool ed ad usarlo nel primo progetto da sviluppare (magari in un progetto che ha già dei problemi di tempi di consegna – tanto la nuova metodologia garantisce uno sviluppo del software in metà del tempo!). Chiunque sviluppi software da un po’ di anni avrà assistito a casi come questi e potrà testimoniarne il risultato! Quello che è veramente importante, più che la metodologia in sé, è il suo uso utilizzo in modo sistematico, competente e completo.

  3. Collegandoci al punto appena esposto, suggeriamo caldamente lo sviluppo di una "check list", per la definizione di tutte le attività, verifiche e risultati che si debbono produrre nella fase di definizione dei requisiti. Per quanto riguarda le problematiche di affidabilità e sicurezza, suggeriamo di inserire i seguenti punti nella check list generale:
      1. Sono stati definiti tutti gli stati operativi e tutte le transizioni di stato, incluso quelle alla partenza, alla terminazione, e a seguito di anomalie?
      2. Sono definite tutte le I/F esterne del software, sia con altri moduli software, che con l'hardware?
      3. E’ stata definita una politica della gestione uniforme degli errori?
      4. E' stata considerata la diagnostica dell'impianto?
      5. Sono state identificate le funzioni critiche dal punto di vista di affidabilità e sicurezza? (Questo a volte richiede un'interazione con la progettazione di sistema.)
  4. Un altro aspetto fondamentale, per garantire uno sviluppo sistematico dell’applicazione, è la chiara identificazione di ogni requisito identificato, al fine di poterlo gestire in modo corretto nelle fasi successive dello sviluppo:
    1. Ogni requisito deve essere identificato in modo univoco (numerato).
    2. Nella fase di disegno, bisogna assicurarsi che ogni requisito sia stato allocato ad uno o più moduli (in quest’ultimo caso, bisogna garantirsi che la suddivisione delle funzioni tra i vari moduli coinvolti garantisca effettivamente l’implementazione del requisito).
    3. Nella fase di test, bisogna identificare un test per ogni requisito. Va notato che, durante la stesura dei requisiti, è buona norma pensare già a come potranno essere verificati: questo permette di capire se il requisito che si sta scrivendo è sensato. Pensate a quei requisiti generali che spesso si trovano, come: "l’interfaccia uomo macchina dovrà essere user friendly", "il sistema dovrà essere robusto", "il sistema dovrà essere facilmente modificabile", tutti esempi di requisiti mal formulati e che risultano assolutamente non testabili, e quindi non verificabili.
  5. Tutto quanto fatto in questa fase deve infine passare al vaglio di una revisione formale (in inglese, review), in cui i requisiti identificati, opportunamente trascritti in un documento (Software Requirement Document), verranno analizzati nel dettaglio.
  6. Molto speso si pensa che la review sia un obbligo, richiesto solitamente dal cliente, dal quale non si può esimere, ma che in realtà "serve solo a fare carta". In realtà, la verità è esattamente l’opposto:

    1. Il Software Requirement Document non serve al cliente per capire come verrà fatto il software (tra l’altro, il Cliente, se non è egli stesso uno sviluppatore software, difficilmente potrà capire appieno il suo contenuto), ma serve allo sviluppatore per avere un modello logico completo e coerente del software che dovrà realizzare. E’ lo sviluppatore che si deve preoccupare che il Software Requirement Document contenga tutto quello che a lui serve! Anche da un punto di vista contrattuale, il Cliente di solito scrive che cosa vuole dal software in documenti differenti (User Requirements o System Specifications) ed è rispetto a quanto scritto in questi ultimi che il software verrà accettato, indipendentemente da quanto di giusto o sbagliato ci sia nel Software Requirement Document.
    2. La qualità non si deve fermare agli aspetti formali. Gli autori hanno vissuto in prima persona esperienze di progetti in cui il controllo qualità, benché applicato nelle varie fasi del progetto, seguendo regole ben precise, si è rivelato solo un appesantimento burocratico che non ha contribuito in alcun modo al miglioramento della qualità del software realizzato. Al contrario, essendo un'attività onerosa, ha provocato un assorbimento di risorse che potevano essere impiegate più proficuamente sul progetto! Tutto questo perché coloro che si occupavano di qualità avevano una competenza molto bassa sia della progettazione software che dell'applicazione specifica, e quindi non tenevano sotto controllo quelli che erano i veri punti critici della progettazione, mentre si "accanivano" su aspetti per loro importanti, ma in realtà irrilevanti al fine della corretta realizzazione dell'applicazione.

 

3.3 - Disegno

In questa fase si definisce l'architettura dell'applicazione: si identificano i vari moduli, si stabiliscono le interfacce tra essi, si definisce il comportamento di dettaglio di ognuno di essi.

Ci sono diverse metodologie di disegno di un’applicazione software: quella più recente e che promette di superare (o forse, inglobare) le altre è l’Object Oriented Design. Nella progettazione real-time è tuttavia ancora molto diffusa la più tradizionale progettazione funzionale. Tra l'altro, la scelta della metodologia per il disegno è fortemente influenzata dalla metodologia usata per la definizione dei requisiti. Infatti un disegno object oriented richiede una precedente analisi object oriented (in realtà, tra analisi e disegno vi è una transizione continua, non un salto).

Non è nostro obiettivo il descrivere una metodologia piuttosto che un'altra (rimandiamo come sempre all'abbondante bibliografia disponibile). Insistiamo invece sul fatto che l'utilizzo di una metodologia per il disegno è indispensabile per lo sviluppo di applicazioni di qualità (e quindi anche affidabili), e non è tanto il tipo di metodologia che fa, secondo noi, la differenza, quanto il modo in cui la si utilizza:

  1. Il team di sviluppo (dal programmatore al management) deve essere cosciente che la fase di disegno è fondamentale e non può essere saltata o fatta in modo approssimato. Deve essere chiaro che il tempo che si pensa di risparmiare saltando le attività di disegno si paga, con interessi salati, nelle fasi successive.

  2. La metodologia deve essere nota al team di sviluppo. Questo richiede fondamentalmente un investimento in tempo: parte usato per lo studio iniziale della metodologia da parte dei progettisti, parte a causa della minor efficienza durante l'applicazione della metodologia al primo progetto concreto. Proprio per questi motivi, si consiglia spesso di iniziare ad applicare una nuova metodologia ad un progetto non troppo grande e a basso rischio. Sappiamo bene però che i progetti di questo tipo sono quasi sempre una rarità.

  3. Il disegno serve ai progettisti, non a preparare dei documenti cartacei da fare vedere al cliente! Il tool CASE utilizzato deve permettere al progettista di concentrarsi sui problemi, di costruire un'architettura veramente valida e che poi tradurrà direttamente in codice. La produzione di documenti cartacei dovrebbe essere una cosa automatica, a partire dalla progettazione fatta con il tool. Tali documenti devono servire come documentazione per i progettisti, ed in pratica saranno comprensibili a chi ha conoscenza della metodologia. Ribadiamo ancora che se il risultato della fase di analisi è un pezzo di carta prodotto per far contento il cliente, ma poi i programmatori non lo usano (magari perché non dice niente di importante o peggio dice delle stupidaggini), allora si sono buttati via del tempo e dei soldi!

Fatte queste premesse generali che, per quanto possano sembrare banali, in realtà sono spesso disattese nella pratica e sono le cause principali della realizzazione di pessime (e quindi anche scarsamente affidabili) applicazioni software, vorremmo ora fornire alcuni spunti più specifici delle problematiche di affidabilità e sicurezza.

  1. Quando i requisiti di affidabilità sono particolarmente alti, è necessario disegnare l'applicazione in modo che sia in grado di sopportare eventuali Fault che accadessero durante la sua esecuzione, e sia in grado di riportarsi in uno stato di corretto funzionamento. Stiamo parlando delle tecniche di fault tolerance, introdotte all'inizio di questo capitolo. Tali tecniche, che verranno descritte più avanti, sono tutte tecniche di disegno, e pertanto è in questa fase del progetto che si deve decidere quali implementare, e si deve strutturare l'applicazione di conseguenza. Notare che non è possibile pensare di aggiungere la fault tolerance in un momento successivo (quando magari si è scoperto che l'applicazione non è affidabile).

  2. Un altro modo per costruire applicazioni robuste, e che secondo noi dovrebbe essere applicato sempre e comunque nei progetti real-time, è quello di suddividere l'applicazione in livelli logici. Ogni livello dovrebbe avere un ruolo ben preciso e dovrebbe essere ben separato dagli altri (poche interfacce e ben definite; nelle applicazioni distribuite, spesso i livelli diversi sono eseguiti su macchine diverse connesse in rete). Ogni livello dovrebbe:

    1. Preoccuparsi di proteggersi da eventuali problemi generati da altri livelli (evitare quello che si chiama fault propagation).
    2. Fornire una diagnostica propria.
    3. In caso di errore, portarsi in uno stato sicuro in modo autonomo.

    Una strutturazione del genere ha parecchi vantaggi (per esempio, la fase di test è enormemente facilitata), ma in particolare per quanto riguarda l'affidabilità essa è l'unico modo per garantire che i fault rimangano localizzati. Vedere anche, per un esempio, la pagina A&S_Esempio_1.

  3. Durante la fase di disegno bisogna anche decidere se certi costrutti di programmazione devono essere vietati oppure no. Come è noto, certi costrutti (utili per molti aspetti) possono creare dei fault molto gravi e difficilmente gestibili:
    1. Puntatori
    2. Allocazione dinamica della memoria
    3. Variabili globali
  4. Un'analisi simile deve essere fatta anche per il sistema operativo da utilizzare (ed il modo in cui lo si utilizzerà):

    1. Allocazione dinamica delle priorità ai processi, piuttosto che fissa.
    2. Swapping dei processi, piuttosto che forzatura in memoria fisica.
    3. Mantenimento di tutto il sistema operativo o selezione solamente delle funzionalità del kernel necessari alla nostra applicazione.

    Su questo punto (sistemi operativi) e quello precedente (linguaggi) torneremo in capitoli dedicati.

  5. Tracciabilità dei requisiti. Tutti i moduli software identificati durante la fase di disegno dovrebbero essere nati a seguito di uno o più requisiti dell'applicazione (contenuti nel Software Requirement Document). Se durante la progettazione di ogni modulo si definiscono quali sono i requisiti che esso copre, sarà poi possibili, alla fine della fase di disegno, costruire una matrice tra requisiti e moduli software che ci permetterà di verifcare che:
    1. Tutti i requisiti sono stati presi in considerazione.
    2. Non esistono moduli che non coprono alcun requisito (over-design).

 

3.4 - Codifica

Questo è il momento dei programmatori, in cui tutto quanto è stato pensato, scritto, disegnato in schemi si traduce in codice.

Per quanto la si possa ritenere la fase più importante della creazione di un'applicazione software, in realtà questa è forse quella meno critica (anche se probabilmente la più divertente per noi pigiatori di tasti). Quanto detto è ancora più valido se parliamo di affidabilità e sicurezza. Infatti in questa fase gli errori introdotti sono sempre facilmente rilevabili dai test (soprattutto se si ha avuto l'accortezza di utilizzare un linguaggio di programmazione ed un sistema operativo adatto) ed in ogni caso sono ben circoscritti.

Non ci sembra quindi il caso di dilungarci sull'argomento. Torneremo comunque su di esso in un capitolo successivo, dove analizzeremo gli strumenti a disposizione per lo sviluppo di un software affidabile: linguaggi e sistemi operativi.

 

4 - Conclusioni prima parte

Abbiamo visto fin qui quali sono gli strumenti generali per lo sviluppo di un software affidabile (fault prevention e fault tolerance) e abbiamo analizzato in dettaglio le fasi di definizione e progetto del software (che abbiamo identificato anche con il termine di fault avoidance).

Il prossimo capitolo si occuperà della fase di test (che abbiamo anche chiamato fault removal). Vedremo come tale fase sia strettamente legata alla precedente e quanto sia importante per la corretta realizzazione del software.


AA e VdV - A&S Capitolo 3  - TDF 2/2000 - 30/04/2000

Torna alla Home Page