[02] Modelli di ciclo di vita del software
In questa lezione vedremo i principali modelli di ciclo di vita del software, ovvero famiglie di processi di sviluppo che si distinguono per il modo in cui le fasi di produzione viste nella scorsa lezione vengono organizzate in un processo unitario. Ognuno di tali modelli avrà i propri pro e i propri contro, ed è bene da subito capire che non esiste il modello giusto per ogni situazione.
Modelli sequenziali
Il modo più semplice e immediato di organizzare le fasi del ciclo di vita di un software è sicuramente quello sequenziale: i vari passaggi vengono posti in un ordine prestabilito e vengono attraversati uno alla volta uno dopo l’altro. Da questa idea nascono i cosiddetti modelli sequenziali, di cui il più famoso è certamente il modello a cascata.
Modello a cascata
Caratteristiche e punti di forza
Nato negli anni ‘50 ma diventato famoso solo negli anni ‘70 grazie allo sviluppo di un grosso software per la difesa area chiamato SAGE (Semi-Automated Ground Environment), il modello a cascata organizza le fasi in una serie di step sequenziali: fatto uno si passa al successivo fino ad arrivare alla fine, come in una sorta di catena di montaggio. Viene infatti forzata una progressione lineare da una fase alla successiva; non è previsto in questo modello tornare indietro a uno step precedente.
Sebbene varino molto da processo a processo, la maggior parte dei processi che segue il modello a cascata include almeno le seguenti fasi organizzate in quest’ordine:
- Requisiti
- Progetto
- Codifica
- Testing
- Prodotto
Ognuno di tali step produce un output, detto semilavorato, che è dato come input allo step successivo. In virtù dell’affidamento su tali semilavorati intermedi il modello a cascata si dice document-based: tra una fase e l’altra si crea infatti un documento che è il mezzo di trasmissione dell’informazione. Questo aspetto permette una buona separazione dei compiti tra i vari dipendenti che lavorano al progetto: ognuno è infatti specializzato in una singola fase e una volta prodotto il documento utile ad avviare la fase successiva il suo coinvolgimento nel progetto non è più necessario ed esso può essere assegnato ad altri lavori.
La linearità del modello rende inoltre possibile pianificare i tempi accuratamente e monitorare semplicemente lo stato di avanzamento in ogni fase: è infatti sufficiente stimare la durata di ogni fase per ottenere una stima del tempo di completamento dell’intero progetto. Si tratta però di una stima a senso unico: una volta finita una fase non è possibile ridurre il tempo speso, e in caso di inconvenienti l’unica opzione è cercare di assorbire il ritardo.
Criticità
Sebbene il modello a cascata abbia il grande pregio di aver posto l’attenzione sulla comunicazione tra gli elementi del progetto in un momento storico in cui il modello di sviluppo più diffuso era di tipo code-and-fix, esso soffre di numerose criticità.
In primo luogo il modello non prevede una fase di manutenzione del software prodotto: esso assume di non dover apportare modifiche al progetto dopo averlo consegnato, e impedisce dunque di “tornare indietro” in alcun modo. Ovviamente questa assunzione è un’illusione smentita nella quasi totalità nei casi: qualunque software è destinato ad evolvere, e più un software viene usato più cambia. Una volta finito lo sviluppo ciò che si può fare è rilasciare al più piccole patch, che tuttavia non fanno altro che disallineare la documentazione prodotta precedentemente con il software reale.
Il modello soffre inoltre di una generale rigidità, che mal si sposa con la flessibilità naturalmente richiesta dall’ambiente di sviluppo software. In particolare, l’impossibilità di tornare indietro implica un congelamento dei sottoprodotti: una volta prodotto un semilavorato esso è fisso e non può essere modificato; questo è particolarmente critico per le stime e specifiche fatte durante le prime fasi, che sono fisiologicamente le più incerte.
Infine, il modello a cascata adotta un approccio volto alla monoliticità: tutta la pianificazione è orientata ad un singolo rilascio, e l’eventuale manutenzione può essere fatta solo sul codice. Inutile dire che si tratta di una visione fallace, in quanto come già detto più volte il software è destinato ad essere modificato e ad evolvere.
Who’s Afraid of The Big Bad Waterfall?
LIBRO: The Leprechauns of Software Engineeering di Laurent Bossavit.
In realtà, il modello a cascata non è mai stato veramente elogiato, ma è sempre stato utilizzato come paragone negativo per proporre altri modelli o variazioni. Nel corso del tempo la sua presentazione è stata erroneamente attribuita al paper “Managing the development of large software systems: concepts and techniques” di W.W. Royce, di cui veniva citata solo la prima figura: Royce stava a dire il vero presentando quel modello per descrivere la sua esperienza nello sviluppo software, per poi proporre altri concetti più moderni (come lo sviluppo incrementale) che non sono però mai stati colti dalla comunità scientifica.
Anche noi utilizziamo il modello a cascata solo come paragone negativo, e in generale nell’ambiente di sviluppo software esso non è più applicato alla lettera. Alcuni suoi aspetti si sono però mantenuti come linee guida generali (es. l’ordine delle fasi); è infatti bene chiarire subito che esistono due tipi di modelli:
- prescrittivi: forniscono delle indicazioni precise da seguire per svolgere un processo;
- descrittivi: colgono certi aspetti e caratteristiche di particolari processi esistenti, ma non obbligano a seguirli in modo rigoroso.
Tutti i modelli visti per ora ricadono perlopiù nell’ambito descrittivo, mentre i modelli AGILE che vedremo più avanti tendono ad essere più di tipo prescrittivo.
Riassunto pro e contro
Pro | Contro |
---|---|
|
|
Modello a V (denti di pesce cane)
Dal modello a cascata nascono poi numerose varianti che cercano di risolverne i vari problemi: tra queste spicca per rilevanza il modello a V, che introduce fondamentalmente una più estesa fase di testing.
Nonostante sia ancora un modello sequenziale come il modello a cascata, nel modello a V vengono infatti evidenziati nuovi legami tra le fasi di sviluppo, che corrispondono alle attività di verifica e convalida: alla fine di ogni fase si verifica che il semilavorato ottenuto rispetti la specifica espressa dalla fase precedente, e inoltre si richiede la convalida del fatto che esso sia in linea con i veri vincoli e necessità del cliente. Come si vede, questo modello pone l’accento sul rapporto con il cliente, che viene continuamente coinvolto con la richiesta di feedback su ciascun sottoprodotto generato.
Volendo formalizzare, le due nuove attività introdotte sono dunque:
- verifica (freccie grigie): controlla la correttezza rispetto alla descrizione formale delle specifiche;
- validazione (freccie bianche): controlla la compatabilità del sistema con le esigenze del cliente tramite feedback continuo.
Modelli iterativi
Osservando il modello a cascata e le sue varianti ci si è ben presto resi conto che la stringente sequenzialità delle fasi costituiva un grosso limite non conciliabile con la flessibilità richiesta dallo sviluppo software e con la naturale mutevolezza dei requisiti imposti dal cliente. Si inizia dunque a pensare di permettere agli sviluppatori di ripetere alcune fasi più di una volta, ciclando su di esse fino a ottenere un prodotto soddisfacente: nascono così i primi modelli interativi.
Modello a cascata con singola retroazione
Uno dei primi modelli iterativi è in realtà una variante del modello a cascata, in cui si permette di fare un’unico salto indietro; a parire da una fase si può cioè ritornare alla fase precedente: così, per esempio, si può iterare tra Codifica e Testing fino a consegnare il prodotto.
Anche in questo modello non si può però tornare indietro dalla consegna per eseguire attività di manutenzione; inoltre, l’introduzione di un’iterazione rende molto più difficile pianificare il lavoro e monitorarne l’avanzamento: si tratta questa di una caratteristica condivisa da molti modelli iterativi.
Modello a fontana
Nel 1993 nasce, in contrapposizione al modello a cascata, il cosiddetto modello a fontana, che amplia il concetto di iterazione permettendo in qualunque momento di tornare alla fase iniziale: se ci si accorge della presenza di errori si torna indietro all’inizio (software pool) e si ricontrollano tutte le fasi precedenti. Ovviamente questo non implica buttare tutto il lavoro già fatto, quanto piuttosto risolvere l’errore con un approccio che parta innanzitutto dalla modifica dei requisiti (se possibile), delle specifiche e solo dopo del codice, evitando di rattoppare solo quest’ultimo alla bell’e meglio come nel modello code-and-fix.
Il modello a fontana è inoltre il primo in cui sono previste delle azioni dopo la consegna; dopo l’ultima fase (programma in uso), infatti, si aprono ancora due strade: manutenzione ed evoluzione. La consegna del prodotto non è quindi più l’atto finale, ma solo un altro step del processo: ecco quindi che si aprono le porte ad una visione incrementale dello sviluppo software, che approfondiremo nel prossimo paragrafo.
Anche qui si perdono purtroppo le garanzie sui tempi di sviluppo: una volta ritornato all’inizio per sistemare un errore non è infatti affatto detto che riuscirò a ritornare alla fase da cui sono partito, ma potrei imbattermi in altri errori durante le fasi precedenti costringendomi a iterare su di esse più di una volta.
Modelli incrementali
Un modello incrementale è un particolare modello iterativo in cui nelle iterazioni è inclusa anche la consegna: questo permette di sviluppare il software a poco a poco, rilasciandone di volta in volta parti e componenti che costruiscano incrementalmente il programma finito.
Si noti la differenza tra incrementale e iterativo; si può parlare infatti di:
- implementazione iterativa: dopo aver raccolto le specifiche e aver progettato il sistema, iterativamente sviluppo i componenti, li integro nel prodotto finale, quindi consegno.
- sviluppo incrementale: l’iteratività interessa tutte le fasi, comprese quelle di specifiche e realizzazione.
Lo sviluppo incrementale riconosce la criticità della variabilità delle richieste e la integra nel processo. La manutenzione non è quindi più una particolarità ma è vista come normale e perfettamente integrata nel modello: in tal senso, la richiesta di una nuova feature o la correzione di un errore generano gli stessi step di sviluppo.
Modello prototipale
Un particolare modello incrementale è quello protitipale: in questo modello viene introdotto il concetto di protitipi usa e getta (throw away), interi programmi che vengono costruiti e poi vengono buttati via.
Lo scopo del prototipo non è consegnare un prodotto finito, ma ricevere feedback dal cliente per essere sicuri di aver compreso a pieno i suoi requisiti, oppure testare internamente un’idea o uno strumento. Per questo motivo tali prototipi vengono costruiti fregandosene di correttezza, pulizia del codice, leggibilità eccetera. I protitipi possono dunque essere:
- pubblici: per capire meglio i requisiti del cliente (vd. L3);
- privati: per esplorare nuovi strumenti, linguaggi, scelte per problemi difficili; inoltre, molto spesso una volta programmata una soluzione si capisce anche meglio il problema (“do it twice”).
La tentazione coi prototipi pubblici può essere quella di consegnarli come prodotto finito, ma c’è il rischio enorme di dover mantenere poi in futuro software non mantenibile, illeggibile e con altissimo debito tecnico.
La propotipizzazione riduce gli errori di analisi dei requisiti e di design, specialmente per le interfacce utente.
I problemi dei modelli incrementali
Come già detto nessun modello è perfetto, e anche i modelli incrementali soffrono di alcuni problemi.
Viene innanzitutto complicato il lavoro di planning: bisogna pianificare tutte le iterazioni e lo stato di avanzamento è meno visibile; inoltre, la ripetizione di alcune fasi richiede di avere sempre sul posto gli esperti in grado di eseguirle. Ad ogni iterazione, poi, dobbiamo rimettere mano a ciò che è stato fatto, in un processo che potrebbe non convergere mai a una versione finale.
Ma cosa è un’iterazione, e quanto dura? Tagliare verticalmente sulle funzionalità non è infatti facile, soprattutto considerando che quando si consegna il prodotto esso dev’essere funzionante con tutti i layer necessari ed essere al contempo pensato per poter crescere con successivi attaccamenti. Ci sono dunque diversi rischi:
- voler aggiungere troppe funzionalità nella prima iterazione;
- overhead dovuto a troppe iterazioni;
- avere un eccessivo overlapping tra le iterazioni: non si ha tempo di recepire il feedback dell’utente (es. Microsoft Office 2020 e 2019 vengono sviluppati contemporaneamente).
Pinball Life-Cycle
Il “modello meme” del Pinball Life-Cycle, creato da Ambler come critica ai modelli incrementali, estremizza queste problematiche: l’ordine in cui faccio le attività è casuale, incoltrollabile. Qualunque passo è possibile dopo qualunque altro, e non si possono imporre vincoli temporal: il processo è non misurabile.
Si tratta ovviamente di una visione eccessivamente pessimistica, ma spesso nelle aziende non specializzate l’iter di sviluppo assomiglia effettivamente a questo.
Modelli trasformazionali
Diametralmente opposti all’incubo del Pinball Life-Cycle troviamo i modelli trasformazionali: tali modelli pretendono infatti di controllare tutti i passi e i procedimenti in modo formale.
Partendo dai requisiti scritti in linguaggio informale, tali modelli procedono tramite una sequenza di passi di trasformazione dimostrabili tutti formalmente fino ad arrivare alla versione finale. Essi si basano infatti sull’idea che se le specifiche sono corrette e i passi di trasformazione sono dimostrati allora ottengo un programma corretto, ovvero di sicuro aderente alle specifiche di cui sopra. Inoltre, la presenza di una storia delle trasformazioni applicate permette un rudimentale versioning, con la possibilità di tornare indietro a uno stato precedente del progetto semplicemente annullando le ultime trasformazioni fatte.
Ad ogni passo si ottiene quindi un protitipo che differisce dal prodotto finale per efficienza e completezza, ma che è possibile trasformare in un altro più efficiente e corretto. Non si tratta tuttavia di un processo totalmente automatico, anzi: ad ogni passo di “ottimizzazione”, ovvero applicazione di una trasformazione, è richiesto l’intervento di un decisore umano che scelga che cosa ottimizzare.
Viene quindi introdotto il concetto di prova formale di correttezza delle trasformazioni applicate; per via di questo approccio molto matematico questo tipo di modelli è nella realtà applicato quasi solo negli ambienti di ricerca e produzione hardware.
Metamodello a spirale
Introduciamo ora un metamodello, ovvero un modello che ci permette di rappresentare e discutere di altri modelli (una sorta di framework).
Nel metamodello a spirale l’attenzione è posto sui rischi, ovvero sulla possibilità che qualcosa vada male (decisamente probabile nell’ambiente di sviluppo software). Per questo motivo il modello è di tipo incrementale e pone l’accento sul fatto che non abbia senso fare lo studio di fattibilità una sola volta, ma ad ogni iterazione serva una decisione. Le fasi generali sono dunque:
- Determinazione di obiettivi, alternative e vincoli
- Valutazione alternative, identificazione rischi (decido se ha senso andare avanti)
- Sviluppo e verifica
- Pianificazione della prossima iterazione
Nella figura il raggio della spirale indica i costi, che ad ogni iterazione aumentano fisiologicamente.
Variante “win-win”
Esiste una variante al modello a spirale che fa notare come i rischi ad ogni fase non sono solo rischi tecnologici ma anche contrattuali con il cliente. Ad ogni iterazione bisogna dunque trovare con esso un punto di equilibrio win-win in entrambi le parti “vincono” (o hanno l’illusione di aver vinto), così da far convergere tutti su un obiettivo comune.
Modello COTS (Component Off The Shelf)
Vediamo infine un modello che si concentra molto sulla riusabilità: si parte dalla disponibilità interna o sul mercato di moduli preesistenti sui quali basare il sistema, e che è dunque necessario solo integrare tra di loro.
Non si creda che si tratti di un approccio facile: questo modello di design necessita di far dialogare componenti che non necessariamente comunicano già nel modo voluto.
Si tratta tuttavia di un modello di sviluppo diverso perché richiede attività diverse. In particolare:
- Analisi dei requisiti
- Analisi dei componenti: prima di progettare considero la disponibilità di componenti che implementano una parte o tutte le funzionalità richieste;
- Modifica dei requisiti: stabilisco se il cliente è disposto ad accettare un cambiamento nei requisiti necessario per utilizzare un componente particolare;
- Progetto del sistema col riuso di componenti: occorre progettare il sistema per far interagire componenti che non necessariamente sono stati originariamente progettati per interagire;
- Sviluppo e integrazione;
- Verifica del sistema.
Metodologie Agili
Finora i modelli visti erano di tipo prettamente descrittivo; vediamo ora dei modelli più prescrittivi, che dicano cioè che cosa fare effettivamente durante lo sviluppo.
Le metodologie agili “nascono dal basso”, ovvero solitamente da chi sviluppa, per colmare un disagio prevalente nell’usare i metodi tradizionali. Per tale motivo, di tali metodologie esiste un…
Manifesto
Nelle parole di Fowler e i suoi collaboratori, per migliorare il modo in cui sviluppiamo il software dobbiamo dare più importanza ad alcuni valori rispetto agli altri:
- Gli individui e la collaborazione tra individui è più importante di processi e strumenti.
- Il software che funziona è più importante della documentazione ben fatta.
- La collaborazione con il cliente è più importante del contratto.
- Rispondere al cambiamento è più importante che seguire un piano.
LIBRO: Agile! di Bertrand Meyer
Come si vede, si tratta di un drastico cambio di rotta rispetto allo sviluppo tradizionale, che si evolve anche in un business model diverso: piuttosto che farsi pagare a programma finito, adesso gli sviluppatori vogliono farsi pagare a tempo di sviluppo, dando però la garanzia al cliente di lavorare durante tale periodo esclusivamente per lui e al massimo delle proprie capacità. Al rapporto confluttuale con il cliente, in cui ciascuno cerca di fregare l’altro, si sostituisce dunque una collaborazione più estesa in cui, come vedremo, anche il cliente diventa parte del team di sviluppo.
Vediamo dunque adesso alcune delle più famose metodologie agili, mettendone in evidenza gli aspetti peculiari.
Lean Software
Nato dal progetto di Lean Manufactioring della Toyota, ha l’obiettivo di ridurre gli sprechi, ovvero quei prodotti e sottoprodotti che non vengono consegnati al cliente (es. testing, prototipi…) e dunque non generano valore: essi possono essere ignorati.
Un’altra idea interessante è quella di posticipare il più possibile le scelte vincolanti per aiutare a risparmiare risorse: più possibilità mi lascio aperte, più mi sarà facile adattarmi (a patto però che l’adattamento sia veloce).
Kanban
L’obiettivo è qui invece di minimizzare il lavoro in corso (work in progress), ovvero concentrarsi in ogni momento su una sola cosa in modo da evitare i continui context switch che costituiscono una perdita di tempo. Le attività possono per esempio essere organizzate in una tabella con 5 colonne:
- backlog: richieste dal cliente
- da fare: attività da fare in questa iterazione
- in esecuzione
- in testing
- fatto
La tabella dà a colpo d’occhio informazioni sullo stato del progetto per tutti. Ogni card (storia) è assegnata a uno sviluppatore (o coppia nel pair programming), in modo che nella colonna in esecuzione vi sia una sola card per sviluppatore (o coppia); qualora il lavoro di un altro blocchi il mio lavoro in qualche modo è poi mia responsabilità aiutarlo per rimuovere il blocco.
Scrum
L’obiettivo è fissare i requisiti durante le iterazioni (brevi, da 2 a 4 settimane), in modo da permettere agli sviluppatori di lavorare in pace senza doversi adattare continuamente a nuove richieste. Solo al termine di ogni iterazione, infatti, si permette al cliente di rimettere in discussione i requisiti.
Crystal
Sebbene non sia molto apprezzata o usata, questa tecnica introduce l’interessante concetto di comunicazione osmotica. Nel modello a cascata la comunicazione è fatta tramite documenti rigidi, ed è settorializzata; in Crystal la conoscenza viene condivisa nel team tramite “osmosi”, in modo che tutti sappiano un po’ di tutto.
Questo rende il processo più robusto, perché l’assenza di una persona esperta in un campo non è più in grado di bloccare completamente i lavori. Il pair programming è in quest’ottica: tra i due componenti la conoscenza è condivisa; Crystal estende questo concetto all’intero team.
Si capisce però facilmente che questa tecnica funziona solo con team piccoli (max 8-10 persone), sebbene altre metodologie agili (SAFE) tentino di scalarla anche a team più massicci.
eXtreme Programming (XP)
Si tratta di una tecnica a cui dedicheremo una trattazione più approfondita nella prossima lezione. Per il momento accontentiamoci di enunciarne i due motti:
- incrementa quindi semplifica;
- sviluppo guidato dal test (test-first: prima testa poi sviluppa).