Nella parte precedente ci siamo occupati di definire le fondamenta di modello dei requisiti, riportando alcune definizioni formali ed evidenziando che il modello generale è composto da sotto-modelli, ciascuno per una specifica categoria di requisiti: funzionali, non-funzionali, data, e così via. In questo secondo articolo della serie, ci occupiamo di requisiti funzionali e della notazione dei casi d’uso.
Introduzione
L’obiettivo di questo articolo è presentare una veloce panoramica della notazione dei casi d’uso come base per la discussione del successivo articolo, in cui vedremo similitudini e differenze tra i casi d’uso e le user story. Quindi presentiamo le informazioni salienti, rimandando i lettori a leggere il libro “UML e ingegneria del Software: dalla teoria alla pratica” [1] per una trattazione dettagliata.
In questa miniserie non presentiamo le user story dal momento che sono state oggetto di una serie di articoli pubblicati di recente: in particolare si consiglia di leggere l’articolo [2].
Lo “zibaldone” delle specifiche tecniche
Non molto tempo fa i requisiti del sistema da implementare venivano documentati attraverso un unico documento, in genere di dimensioni notevoli, chiamato Specifiche Tecniche di Sistema, che finiva per includere tutto lo spettro dei requisiti: includeva la descrizione dei servizi e funzioni da implementare, le caratteristiche di qualità e altri aspetti non funzionali, i modelli dati, vari vincoli e spesso includeva anche considerazioni sull’architettura implementativa. Sebbene si sia trattato di una pratica molto diffusa per lungo tempo, con gli anni ci si è resi conto che era ben lungi dall’essere ottimale: era gravata da un insieme di problemi importanti, tra i quali i più rilevanti sono riportati di seguito.Anx
Anzitutto, non era adatto a un approccio “divide et impera”. Documenti di notevoli dimensioni, omnicomprensivi, non si si prestano al tipico approccio di gestione della complessità basato sulla suddivisione e sul disaccoppiamento. Risulta difficile per l’analista concentrarsi su uno specifico aspetto alla volta (per esempio l’analisi di un determinato servizio), demandando ad altri contesti l’analisi di altri elementi (per esempio l’analisi dei dati). Il risultato è che tali documenti tendono naturalmente a risultare astratti, inconsistenti e difficili da redigere.
Poi, un documento come questo riduce la possibilità di parallelizzare le attività: è difficile, spesso non fattibile, suddividere l’intero dominio di analisi tra vari analisti senza creare importanti inconsistenze. Per esempio, è meno immediato assegnare ai vari elementi del team un sottinsieme di servizi da analizzare e modellare in parallelo.
Oltre a questo, un documento unico è implicitamente poco preciso. Tale documento utilizza abbondantemente il linguaggio naturale che è ambiguo per definizione. È anche difficile da consultare e quindi da verificare e da manutenere. Il caso classico prevede che tali documenti diventino inaccurati e “obsoleti” già dopo il primo inevitabile giro di change requirement.
Va poi detto che offre un limitatissimo supporto alla fase di test. un calo di tensione si presta, inoltre, a supportare processi di sviluppo iterativi ed incrementali alla base delle moderne metodologie di sviluppo del software.
Infine, non supporta il team degli architetti nella definizione iniziale dell’architettura: bisogna avere il documento ultimato per poter definire l’architettura e, una volta definita, era complicato fare in modo che i requisiti venissero aggiornati per rifletterne la struttura. Anche qualora parti del documento siano rilasciate in anticipo, è comunque non immediato estrarre informazioni sui vari servizi, dati coinvolti, etc.
La maggior parte di questi problemi sono stati risolti grazie all’introduzione di approcci basati su notazioni come i casi d’uso e le user story. Ma prima di procedere oltre, riportiamo brevemente la storia dei casi d’uso.
Breve storia della notazione dei casi d’uso
L’idea inziale della metodologia dei casi d’uso risale al 1967, quando Ivar Jacobson, durante i lavori al progetto AXE presso Ericsson, cominciò ad utilizzare un nuovo approccio: “Quando ho utilizzato per la prima volta il termine [Use Case] nel 1986, si trattava dell’evoluzione del lavoro che andava avanti fin dal 1967.” [3].
Tuttavia, la vera svolta nella comunità Object Oriented avviene solo nel 1992, con la pubblicazione del libro “Object-Oriented Software Engineering: A Use Case Driven Approach”. Si tratta di un libro che ha contribuito all’evoluzione del pensiero OO a tal punto che Jacobson nel 1995 si unisce a Booch e Rambaugh per integrare la notazione dei casi d’uso allo standard UML (OMG).
Attualmente, dopo diversi anni della sua evoluzione, la notazione dei casi d’uso è verosimilmente una delle più utilizzate, tanto che per molti si tratta dello standard de facto per l’analisi dei requisiti funzionali. “La notazione dei casi d’uso è stata adottata universalmente per la specifica dei requisiti” [4]. Da notare che i casi d’uso non sono esclusivamente una notazione, ma sono il core di una completa metodologia: “I casi d’uso hanno inizio nella fase di analisi dei requisiti, sono tradotti in diagrammi di collaborazione durante la fase di analisi e disegno, e infine in casi di test durante la fase di test: questa è l’idea centrale dell’approccio use case driven.” [4].
I vantaggi della notazione dei casi d’uso
L’utilizzo della notazione dei casi d’uso permette di ottenere tutta una serie di vantaggi, alcuni dei quali, ad essere onesti, sono condivisi con la notazione delle user story… Ma di questo argomento ce ne occuperemo nel prossimo articolo della serie. I principali vantaggi del ricorso della notazione dei casi d’uso sono quelli che vedremo di seguito.
Semplice e immediata
Semplicità e immediatezza. Si tratta di una notazione intuitiva, attraente e molto potente che permette di descrivere il comportamento del sistema dal punto di vista dei suoi attori (approccio use case driven). Molti considerano questo vantaggio anche uno dei principali problemi della notazione [5]. In effetti la notazione molto seducente basata su persone stilizzate (UML Actor) ed ellissi possono dare l’errata impressione che si tratti di una notazione elementare il cui utilizzo non richieda studi o esperienza. Le persone esperte sanno benissimo che scrivere use case di qualità richiede studio e esperienza.
Adatta ai test
Elevato supporto per la fase di test. I casi d’uso possono essere trasformati meccanicamente in corrispondenti test case. Inoltre, strutturalmente i casi d’uso includono lo scenario principale (main) e gli scenari alternativi che descrivono la sequenza di eventi che permettono di raggiungere gli obiettivi quando tutto funziona bene, e un insieme di scenario di errore (exception scenario) che descrive la lista di potenziali problemi “business” che possono occorrere, compresa la loro relativa gestione. Questo fa sì che permettano di definire e verificare che il sistema sia corretto e robusto.
Tracciabilità dei requisiti
Supportano la totale tracciabilità sia dei requisiti iniziali (spesso consegnati in termini di una lista di feature) sia dei successivi modelli fino al codice (non è infrequente consultare una parte di codice che include reference agli use case implementati). La tracciabilità è lo strumento base per poter stimare in maniera accurata l’impatto di ogni cambiamento dei requisiti.
Efficienza migliorata
La notazione basata sui casi d’uso permette elevate efficienze. È infatti possibile raggruppare casi d’uso per aree funzionali e assegnare ciascuna di esse a un analista, con conseguente miglioramento dell’efficienza con cui ogni area funzionale viene analizzata.
Adatta ai processi iterativi e incrementali
Gli use case supportano intrinsecamente i processi di sviluppo del software iterativi e incrementali. Questo consente, in maniera relativamente immediata, di raggruppare l’insieme dei casi d’uso/scenario in opportune iterazioni/sprint.
Miglioramento della qualità dei requisiti
Permettono di produrre requisiti più semplici da consultare e da verificare e validare. Quindi, come logica conseguenza, permettono di produrre requisiti e sistemi di migliore qualità.
Supporto per tool di sviluppo
La notazione dei casi d’uso è ormai standardizzata in maniera tale da essere supportata all’interno dei più diffusi tool commerciali utilizzati nella progettazione e nello sviluppo.
Facciamo un po’ di chiarezza
Studiadno l’UML e la notazione dei casi d’uso si rischia facilmente di fare confusione… In effetti, i termini use case sono utilizzati per indicare diversi elementi. Cerchiamo di fare ordine.
4+1 views
Da quanto riportato su molti libri dedicati all’ingegneria del software, è possibile venire a conoscenza del modello delle 4+1 views (“quattro più una viste”, figura 1). Si tratta di un modello definito da Philippe Kruchten per descrivere l’architettura dei sistemi software, basati sull’utilizzo di viste multiple concorrenti [6].
Figura 1 – Diagramma delle 4 viste + 1, di Philippe Kruchten.
Come si vede, al centro compare la vista dei casi d’uso (use case view), che nei processi di sviluppo del software assume un ruolo di primaria importanza, sia perche’ definisce il comportamento del sistema da consegnare, sia perche’ le restanti viste si occupano di modellare quanto specificato in quella dei requisiti. Altro aspetto non di secondaria importanza, si tratta di una vista che spesso è sottoposta alla firma del cliente.
Questa vista è composta da diversi diagrammi dei casi d’uso e da altri elementi (figura 2). È opportuno chiarire fin da subito che la sola notazione dei diagrammi dei casi d’uso è insufficiente per modellare i requisiti funzionali del sistema. Si tratta di un ottimo formalismo per modellare le funzionalità del sistema, mutue dipendenze, dando particolare rilievo agli attori che interagiscono con esse. Però è necessario fornire maggiori informazioni circa le singole funzionalità, rappresentate graficamente attraverso elementi ellittici denominati use case.
Figura 2 – Illustrazione della vista dei casi d’uso.
I soli diagrammi dei casi d’uso sono in grado di fornire esclusivamente una overview dei requisti funzionali, forniscono diverse informazioni molto importanti utili in svariati contesti, ma nulla dicono sulla sequenza degli eventi, chi fa cosa, e così via. Per questo è necessario integrarli con i dettagli del comportamento. A tal fine è possibile ricorrere a diverse alternative. Si possono, per esempio, utilizzare gli altri diagrammi UML demandati a modellare aspetti comportamentali, come i sequence diagram o gli activity, oppure utilizzare appositi template (figura 3).
Figura 3 – Use case e use case specification.
Sebbene questa tematica sia affrontata nei paragrafi successivi, possiamo preannunciare fin da subito che i template forniscono tutta una serie di importanti vantaggi e pertanto risultano essere la soluzione più ampiamente utilizzata. Ogni use case viene quindi corredato dal corrispondente modello che riporta tutta una serie di importanti informazioni come: breve descrizione, pre- e post-condition, i vari scenari, e così via.
La notazione UML: gli attori
Un attore definisce un insieme coerente di ruoli che un “utente” di un caso d’uso recita quando interagisce con esso. Un attore non è necessariamente una persona: può essere un sistema automatizzato o un qualsiasi dispositivo fisico; in generale è un’entità esterna al sistema che interagisce con esso. Si tratta dell’idealizzazione di un persona, di un processo, o di qualsiasi cosa interagisca con il sistema inviando e/o ricevendo messaggi: scambiando informazioni.
Alcuni esempi di attori, come mostrato in figura 4, possono essere un amministratore, il cliente, uno studente universitario, un insegnante, un sensore di livello, un missile, un legacy system, un sito e-commerce, etc.
Figura 4 – Esempi di attori.
Nell’UML, gli attori prevedono una rappresentazione grafica standard data da un omino stilizzato (figura 3), tuttavia è possibile ricorrere a opportuni “stereotipi” (come quelli di figura 4) al fine di evidenziare le caratteristiche salienti dei vari attori e le loro funzionalità.
Per ciò che concerne le relazioni, un attore è connesso ai casi d’uso con i quali interagisce per mezzo di una relazione di associazione, e può prendere parte a relazioni di generalizzazione con altri attori in cui una descrizione astratta di un attore più essere condivisa e specializzata da una più specifica. Queste sono le uniche relazioni che possono coinvolgere gli attori. Per esempio, non è possibile collegare tra loro due attori per mezzo di un’associazione (il che ha senso dal momento in cui non può esserci un flusso dati senza un relativo servizio).
Un approccio spesso utilizzato per individuare gli attori consiste nel classificarli in primari e secondari. I primari sono quelli che utilizzano le funzioni proprie del sistema, e pertanto vengono anche definiti attivi; i secondari fruiscono di informazioni e sono detti anche passivi. È da tener presente che attori primari per uno use case possono essere secondari per un altro e viceversa. La differenza sostanziale tra i due tipi è che, mentre gli attori primari avviano delle funzionalità proprie del sistema, i secondari ricevono dei messaggi e non forniscono un vero e proprio stimolo. Tuttavia, si tratta di una categorizzazione superficiale che ha senso solo nel contesto di ogni singolo caso d’uso.
Relazione di generalizzazione tra attori
Nella realizzazione dei diagrammi dei casi d’uso può verificarsi il caso in cui più attori presentino diverse e significative similitudini, come per esempio interagiscano con un insieme di casi d’uso con le stesse modalità. In questi casi, come i princìpi del paradigma Object Oriented insegnano, è possibile dar luogo a un nuovo attore, magari astratto, che raccolga a fattor comune tali similitudini, esteso dagli altri per mezzo di apposite relazioni di generalizzazione. Chiaramente non sempre è necessario introdurre un nuovo attore; anzi, la maggior parte delle volte si verifica il caso più semplice di un attore che ne estende un altro: un attore “specializzato” eredita il comportamento del “genitore” e lo estende in qualche maniera. Coerentemente con la definizione di relazione di generalizzazione (o meglio con il relativo principio di sostituibilità di Barbara Liskov), un attore che ne specializza un altro può sempre essere utilizzato al posto di quello esteso. Nel diagramma di figura 5, l’attore Sales estende quello astratto Requester (il fatto che sia astratto si evidenzia dal nome scritto in corsivo), in quanto può partecipare a tutti i casi d’uso a cui partecipa quest’ultimo ed inoltre ce ne sono altri ad esso dedicati. A dire il vero, guardando il solo diagramma dei casi d’uso di figura 5, sembrerebbe che l’introduzione dall’attore astratto Requester non apporti nessun vantaggio. Tuttavia questo diagramma è stato preso da un contesto più complesso che ne giustificava appieno la presenza.
Figura 5 – Diagramma dei casi d’uso relativa all’area funzionale del Financial Instruments Sale.
Relazione di associazione tra attori e casi d’uso
Gli attori sono entità esterne al sistema che interagiscono con esso. Le interazioni si materializzano attraverso scambi di messaggi. Pertanto ogni attore è connesso con un insieme di casi d’uso che ne ricevono gli stimoli e che producono i dati richiesti dall’attore stesso. Il legame tra attore e use case è realizzato per mezzo di una relazione di associazione. La situazione più tipica è che un singolo attore è associato a molti casi d’uso. Graficamente le relazioni di associazione vengono visualizzate per mezzo di un segmento che unisce l’attore con il relativo caso d’uso. Nel diagramma di figura 5, l’attore Requester può gestire il proprio portfolio di strumenti finanziari (interagisce con lo use case “Manage Instrument Portfolio“), può visualizzare lo stream in tempo reali dei prezzi (scambia messaggi con lo use case “Show real-time stream quotes“), e così via. È evidente che le varie relazioni di associazione rappresentano dei flussi di dati.
La notazione UML: casi d’uso
Un caso d’uso (use case) rappresenta una funzionalità completa così come viene percepita da un attore. Si tratta di un costrutto utilizzato per definire il comportamento di un sistema o di un’altra entità semantica senza rivelarne la struttura interna. In termini più formali UML, un caso d’uso è un tipo di Classificatore, che rappresenta un’unità coerente di funzionalità fornita da una specifica entità (sistema, sottosistema, classe) e descritta sia dalla serie di messaggi scambiati tra l’entità stessa e un’altra interagente esterna (attore), sia dalla sequenza di azioni svolte. Ogni caso d’uso è pertanto definito da una sequenza di azioni (comportamento dinamico) necessarie per erogare un servizio che l’entità esegue interagendo con il relativo attore. I casi d’uso possono essere raggruppati in gruppi (package), che rappresentano le aree funzionali del sistema.
La notazione grafica dello UML prevede di modellare gli use case attraverso ellissi con il nome specificato all’interno, oppure di sotto. Essi possono essere connessi ad altri use case o agli attori (come visto poco sopra). I nomi dei casi d’uso, un elemento molto discusso dagli utenti/clienti, dovrebbero essere unici, sintetici e al tempo stesso capaci di definire completamente il concetto espresso: a tal fine è molto importante utilizzare verbi con significato preciso, piuttosto che verbi generici, come “fare”, “dare”, etc.
Gli use case possono essere “collegati” tra loro per mezzo di tre relazioni: generalizzazione, inclusione ed estensione.
Generalizzazione tra casi d’uso
Durante il disegno dei diagrammi dei casi d’uso si verifica spesso che diversi use case presentino importanti similitudini e condividano del comportamento (analogamente a quanto illustrato per gli attori). Come menzionato in precedenza, gli insegnamenti dell’OO prescrivono di raggruppare il comportamento comune in un apposito use case genitore (eventualmente astratto) e di estenderlo per mezzo di altri casi d’uso figli che lo specializzino per gli usi originariamente individuati. La proprietà a cui si fa riferimento è ovviamente l’eredità e la relazione dello UML è la generalizzazione.
Nel contesto dei casi d’uso, la generalizzazione (Generalization) è una relazione tassonomica tra uno use case, figlio, e un altro, chiamato genitore, che descrive le caratteristiche che il caso d’uso divide con altri use case che hanno lo stesso genitore. Un caso d’uso genitore può essere specializzato in uno o più figli che ne rappresentano forme più specifiche.
Come nel caso degli attori, la notazione grafica standard dell’UML per identificare la relazione di generalizzazione prevede una linea continua, terminata con un triangolo vuoto posto a mo’ di freccia in prossimità dello use case genitore. Da notare che la presenza di una relazione di generalizzazione implica che l’esecuzione di use case “fratelli” possa avvenire unicamente in alternativa. Per esempio, nel diagramma di figura 5, il caso d’uso astratto “Create a Request” (si vede che si tratta di un elemento astratto dal nome riportato in corsivo) include il comportamento comune dei tre casi d’uso specializzanti: “Create a trade oder request“, “Create a price order” e “Create a Request for quote“.
Crome illustrato di seguito, ad ogni caso d’suo viene associata una descrizione del flusso degli eventi che lo definisce. Le relazioni specificano in modo puntuale, come i flussi di eventi dei vari casi d’uso sono “eseguiti” (indipendentemente dalla modalità con cui i flussi sono descritti). Il diagramma di figura 6 mostra l’esecuzione di una generalizzazione. In particolare, è possibile notare che il caso d’uso base “Use Case A”, può prevedere diversi punti astratti combinati a generici passi di esecuzione. Uno use case specializzante può sia definire il comportamento per le sezioni astratte (utilizzo canonico), sia sovrascrivere quello sancito in passi generici, sia aggiungere altro comportamento dopo la fine del flusso di eventi del caso d’uso base. Chiaramente, nel caso in cui non definisca il comportamento di tutti i punti astratti dello “Use Case A” risulterebbe astratto a sua volta.
Figura 6 – Rappresentazione del flusso di eventi nel caso in cui “Use Case A” sia esteso da “Use Case B”.
Come si può notare, gli use case figli ereditano la sequenza comportamentale del genitore e vi aggiungono comportamento specifico. Differenti specializzazioni dello stesso genitore sono completamente indipendenti. Il comportamento specifico di un caso d’uso figlio può essere indicato inserendo opportune sezioni o sovrascrivendone altre, in modo del tutto analogo a quanto avviene con i metodi delle classi, nella sequenza di azioni ereditate dallo use case genitore. In questo contesto è consigliabile ricorrere con parsimonia al meccanismo di sovrascrittura onde non stravolgere gli intenti dello use case genitore. Quando il caso d’uso genitore è astratto, nella sua sequenza di azioni sono previste apposite sezioni lasciate volutamente incomplete e alle quali lo use case ereditante deve provvedere. La proprietà di sostituibilità propria della relazione di generalizzazione applicata ai casi d’uso implica che la sequenza di azioni dello use case figlio deve includere quella del genitore.
Relazione di inclusione tra use case
L’inclusione è una relazione tra uno use case base e uno incluso che specifica come il comportamento di quest’ultimo possa essere inserito in quello definito nello use case base, il quale, può avere visione dell’incluso e, tipicamente, dipende dagli effetti da esso generati durante la relativa esecuzione. In termini della sequenza delle azioni, che ciascun caso d’uso esegue per erogare un servizio, la relazione di include comporta l’esecuzione della sequenza di azioni dello use case incluso sotto il controllo e nella locazione specificata dello use case base, come mostrato nella figura 7. Il concetto di inclusione è per molti versi analogo a quello di invocazione di funzione: viene eseguita la sequenza di azioni dello use case base; quando viene raggiunto un punto di inclusione, il controllo viene passato al caso d’uso ivi indicato (incluso) e ne viene eseguita completamente la sequenza delle azioni, quindi il controllo ritorna allo use case base. Tuttavia, volendo essere più precisi bisognerebbe paragonare la relazione di inclusione al meccanismo di espansione delle macro presente in molti linguaggi di programmazione: si definisce una macro e la si copia (si espande il “codice”) in tutti i punti in cui ci si riferisce alla macro stessa.
Figura 7 – Rappresentazione del flusso di eventi nel caso in cui “Use Case A” includa “Use Case B”.
La relazione di inclusione è molto utile in quanto evita di dover ripetere più volte uno stesso flusso di sequenza. In particolare, per specificare nel flusso dello use case utilizzatore il punto in cui inserire quello dello use case incluso, è sufficiente premettere l’identificatore include seguito dal nome dello use case. Il caso d’uso incluso è a tutti gli effetti uno use case, e quindi può essere associato ad altri per mezzo di proprie relazioni di inclusione, estensione e così via.
Nella figura 5, il caso d’uso “Manage instrument portfolio” include il caso d’uso “Retrieve Instruments“, con ovvio significato. Da notare che la relazione di inclusione aiuta a modellare use case di dimensioni non eccessive e quindi contribuisce all’applicazione dell’approccio “divide et impera”. Tuttavia, bisogna stare attenti a non esagerare con questa relazione: la trappola in cui spesso si finisce è dar luogo a una decomposizione funzionale. Cosa sbagliatissima per molti motivi, tra i quali il fatto che la notazione dei casi d’uso non è lo strumento ideale per disegnare il sistema, che il disegno non deve esere fatto dal business analyst e che i casi d’uso devono essere compresi dagli utenti.
Relazione di estensione tra i casi d’uso
Una relazione di estensione (extend) è una relazione tra un caso d’uso estendente e uno base che specifica come il comportamento definito dal primo (l’estendente) debba essere inserito nel comportamento di quello base.
A questo punto la situazione potrebbe sembrare poco chiara: il comportamento dello use case estendente viene incorporato in quello base? In effetti è così. Il problema risiede unicamente nel nome scelto per tale relazione: extend. Considerato, erroneamente, come relazione di estensione “canonica”, il comportamento sarebbe assolutamente contrario alle convenzioni più classiche dell’OO; ma non bisogna farsi confondere dal nome e ricordare che per specificare relazioni di ereditarietà tra casi d’uso è prevista l’appropriata relazione di generalizzazione.
Si tenga comunque presente che la vista dei casi d’uso prevede come fruitori anche persone che, per definizione, hanno pochissima o nessuna esperienza del mondo OO, e la relazione di estensione così definita finisce probabilmente per risultar loro più naturale e comprensibile.
Una relazione di estensione contiene una lista dei nomi dei punti in cui lo use case base può e deve essere esteso (figura 8). Chiaramente ciascuno di essi deve essere esteso da opportuni segmenti presenti nel e nei casi d’uso estendenti. Un punto di estensione rappresenta una locazione o un insieme nello use case base, nel quale l’estensione può essere inserita.
Figura 8 – Rappresentazione del flusso di eventi nel caso in cui “Use Case A” è esteso da “Use Case B”.
Come evidenziato nella figura 8, lo use case base A prevede due punti di estensione, mentre il caso d’uso estendente ne fornisce l’apposita definizione. Il punto esclamativo serve a indicare che l’effettiva estensione avviene sotto il controllo di una condizione di guardia. Per questo motivo, spesso le relazioni di estensione sono utilizzate per modellare delle parti di use case che rappresentano delle azioni (business) facoltative, in modo da distinguerle esplicitamente dal flusso delle azioni non opzionali. In questo modo è possibile distinguere il comportamento opzionale da quello obbligatorio del sistema. Il funzionamento prevede che quando un’istanza dello use case base raggiunge una locazione referenziata da un punto di estensione, la condizione venga valutata: se l’esito è positivo (valore true) il flusso delle azioni specificate nel relativo caso d’uso estendente viene eseguito, altrimenti si procede oltre nell’esecuzione del flusso delle azioni dello use case base.
L’assenza di una condizione corrisponde a un valore sempre true. Nel caso in cui la relazione di estensione preveda diversi punti di estensione, la condizione viene verificata solo la prima volta, ossia prima dell’esecuzione del primo frammento. Chiaramente uno stesso use case può essere esteso da diversi altri, così come un caso d’uso può estenderne molteplici.
Quando si ricorre ad una relazione di estensione è possibile immaginare lo use case base come un framework modulare in cui le estensioni possono essere inserite, modificandone implicitamente il comportamento, senza che esso ne abbia visione.
Nella figura 5, lo use case “Take Request For quote” estende lo use case “Publish request“. Il significato è che mentre l’attore riceve le richieste pubblicate, può decidere di prenderne una specifica all’intento di diventarne l’onwer (in modo che gli altri colleghi non facciano lo stesso) per fornire una proposta di prezzo (quote).
La specifica del comportamento
I diagrammi dei casi d’uso, come visto, permettono di illustrare le singole funzionalità del sistema, conferendo particolare risalto alla percezione che ne hanno gli utenti o meglio gli attori. Tuttavia, forniscono esclusivamente una comoda panoramica, e da soli non sono assolutamente in grado di catturare tutte le varie informazioni necessarie per definire in dettaglio e correttamente i vari servizi. In particolare, non posseggono alcune elemento che li rende in grado di rilevare il comportamento dinamico; è inoltre necessario ricorrere a diversi artefici per tentare di evidenziare eventuali condizioni anomale; non è possibile specificare le azioni da intraprendere per la relativa gestione, e così via. Quindi, vanno assolutamente integrati con soluzioni in grado di specificare il comportamento.
Flussi di azione
Una prima soluzione a questi inconvenienti è l’utilizzo della tecnica dei flussi di azione, così come prescritto dalle iniziali direttive dei Tres Amigos. Tuttavia, nelle pratica quotidiana questo approccio non risulta sempre adeguato e privo di lacune; per esempio non vengono menzionate pre- e postcondizioni, probabilmente non viene conferita abbastanza enfasi agli attori coinvolti nel caso d’uso, non vengono specificate le garanzie, non ci sono riferimenti alle business rules, e così via.
Altri diagrammi UML: sequenza e attività
Un’altra alternativa potrebbe essere quella di ricorrere ad altri diagrammi messi a disposizione dallo UML per la modellazione del comportamento dinamico del sistema. Per esempio si potrebbe ricorrere ai diagrammi di sequenza o quelli di attività. L’utilizzo dei diagrammi succitati però non solo non risolve tutte le lacune evidenziate per i flussi di azione, ma anzi aggiunge altri inconvenienti, quali per esempio l’aumento significativo del tempo necessario per realizzare i diagrammi stessi e l’estrema difficoltà di manutenzione. Cerchiamo di capire questo punto. Uno use case si compone solitamente di uno scenario principale (main scenario), di diversi scenari alternativi (alternative scenario) e di alcuni scenari di errore (exception scenario). Ora, la loro modellazione per mezzo di diagrammi, verosimilmente, richiederebbe di disegnare un modello per ogni scenario, il che già di per se’ richiede un certo investimento temporale. Inoltre, un cambiamento, magari nello scenario principale, genererebbe la necessità di modificare tutti i rimanenti. Quindi, sebbene l’utilizzo di notazioni grafiche permetta di raggiungere molti vantaggi (immediatezza, possibilità di memorizzazione, etc.), in questo caso gli inconvenienti sono talmente importanti che rendono possibile utilizzare questa tecnica solo per usi limitati.
Template
L’esperienza quotidiana suggerisce che, molto spesso, lo strumento più opportuno da utilizzarsi per dettagliare il comportamento dinamico dei casi d’uso rimane il buon vecchio template: semplice, veloce da realizzare e da far comprendere ai clienti, agevole da manutenere e così via. Chiaramente, la soluzione ottimale consiste nell’utilizzare tool implementati per questo fino che poi permettono di rappresentare la descrizione del caso d’uso secondo i vari formati, come per esempio template, sequence diagram o activity diagram.
Il template
In questo articolo proponiamo il template presentato nel libro [1] e riportato qui di seguito.
Template 1 – Presentazione della struttura del template.
In prima analisi, il modello (template) può essere scomposto in quattro componenti: l’intestazione, le informazioni generali, gli scenari (che a loro volta si dividono in: principali, alternativi e di errore) e la sezione per eventuali annotazioni aggiuntive.
Intestazione
Nell’intestazione è necessario riportare un codice simbolico univoco da utilizzarsi per riferirsi al caso d’uso, una descrizione breve (il nome), la data dell’ultima modifica e la versione. Il codice si rivela particolarmente utile sia per evidenti questioni di catalogazione e facilità di reperimento, sia nell’utilizzo di tool per la produzione di diagrammi UML: è automatico associare al nome del diagramma il codice del caso d’uso.
Informazioni generali
Qui si trovano svariate informazioni importanti, quali:
- la priorità (priority), così come definita dal cliente (la quale non è necessariamente la priorità finale nel progetto);
- gli attori, suddivisi in primari e secondari, e ognuno corredato da una breve descrizione del suo interesse nel partecipare allo specifico caso d’uso;
- le precondizioni, vale a dire cosa deve accadere nel sistema affinche’ il caso d’uso possa essere eseguito; per esempio, il sistema ha un nuovo evento che deve essere elaborato, oppure l’utente è stato autorizzato ad eseguire il servizio, oppure i risultati del calcolo del rischio sono disponibili;
- le postcondizioni in caso di successo ossia cosa accade al sistema qualora lo use case termini con successo; alcuni esempi sono che il sistema consumi il nuovo evento e i risultati dell’elaborazione siano resti persistenti nel sistema, che il sistema transiti nel nuovo stato, e così via;
- le postcondizioni in caso di fallimento ossia cosa accade al sistema qualora lo use case termini con un insuccesso, in altre parole, qualora si verifichi un’eccezione (rigorosamente business) non gestibile; per esempio, il messaggio non viene consumato e il sistema non cambia di stato, l’utente non viene autenticato e quindi non può eseguire il servizio richiesto, e così via;
- il trigger, ossia l’evento che genera l’avvio dello use case; alcuni esempi di trigger sono, ad esempio, che l’utente richiede esplicitamente di eseguire un servizio, o che il sistema riceve un messaggio, che lo scheduler esterno comunica uno stimolo temporale, e via dicendo.
Scenari
La sezione successiva del modello è dedicato agli scenari. Tipicamente se ne distinguono tre tipologie (figura 9), descritte di seguito.
- Scenario principale di successo (main success scenario) è lo scenario che produce il servizio richiesto dall’attore primario nel caso in cui tutto funzioni correttamente: dati di ingresso validi, nessuna eccezione scaturisce durante lo svolgimento, etc. e che quindi termina con successo; lo use case genera le post-condizioni di successo.
- Scenari alternativi (alternative scenario): si tratta di flussi di azioni che rappresentano diramazioni dello scenario principale la cui funzionalità però non sia la gestione di condizioni di errore non gestibili. Si è ancora in un flusso di successo, ma l’esecuzione del servizio richiede un percorso diverso. In sostanza è una forma più elegante per evitare diramazioni nel flusso principale (il famoso costrutto if – then – else) che comunque restano legittime;
- Scenari di fallimento o di errore (failure scenario): gli scenari che specificano le azioni da intraprendere nel caso in cui l’esecuzione delle azioni degli scenari principale o alternativi sia impossibilitata dall’insorgere di condizioni di errore. Si tratta di illustrare il comportamento da eseguire in modo del tutto simile a quello utilizzato dai linguaggi di programmazione per specificare la gestione delle eccezioni.
Da tener presente che, ogni qualvolta in un passo dei flussi precedenti si menziona un verbo del tipo “verifica”, “controlla”, “valida”, etc. verosimilmente esiste un flusso alternativo o di errore associato con la verifica negativa del test.
Figura 9 – Schematizzazione di un caso d’uso.
Cosa succede “nel” template
La struttura utilizzata nel template oggetto di studio prevede di specificare inizialmente la sequenza di azioni da compiere nel caso in cui tutto funzioni correttamente, dall’avvio del caso d’uso al suo compimento, per poi fornire le condizioni anomale che possono intervenire e le azioni da intraprendere. Vi è una stretta similitudine con la pratica quotidiana. Si consideri la situazione in cui quando è necessario illustrare a un’altra persona un qualche sistema o una disposizione, si inizia fornendo le istruzioni sulla sequenza “corretta” e poi si spiega come gestire eventuali casi eccezionali che potrebbero intervenire: “è necessario fare questo, quello e quell’altro ma, nel caso in cui si verifichi quest’altro ancora, allora è necessario comportarsi in tale maniera ecc.”. Sicuramente, c’è una qualche somiglianza con le istruzioni per fare la spesa che, da bambini, si ricevevano dalla mamma: “compra il guanciale, la cipolla e un kg di pomodori rossi; se non ci sono i pomodori freschi, allora compra due barattoli di pomodori pelati”.
Spesso, piuttosto che “nominare” i flussi alternativi e di errore con numeri ordinali indicanti la sequenza temporale di apparizione, si preferisce attribuire una descrizione che precisi la condizione che ne genera l’esecuzione. Chiaramente ciò non avrebbe alcun senso per ciò che concerne il flusso principale (il nome sarebbe sempre lo stesso!). Il vantaggio offerto da tale tecnica è legato alla maggiore comprensibilità della descrizione del caso d’uso, alla minimizzazione del lavoro richiesto da eventuali aggiornamenti (per esempio, dovendo inserire un nuovo flusso o eliminarne un altro non sarebbe necessario numerare nuovamente i successivi), e all’utilizzo più proficuo di strumenti da utilizzarsi per la catalogazione e gestione dei requisiti (l’elenco dei flussi mostrerebbe una descrizione autoesplicativa piuttosto che un anonimo numero).
Nella stesura degli scenari probabilmente può risultare utile strutturarli come una successione di azioni o “transazioni” limitate. Molto importante è evidenziare chiaramente quale entità (attore, sistema) svolge ciascuna azione. Molto spesso si utilizzano particolari versioni di template nei quali la sezione dedicata agli scenari viene suddivisa orizzontalmente in un numero di colonne equivalenti alle entità che prendono parte al caso d’uso. Ciò permette di riportare in ciascuna colonna unicamente le azioni eseguite dall’entità di appartenenza.
Scenari di fallimento
Nella definizione di un caso d’uso, oltre a quello principale è necessario riportare tutta la sequenza di casi di errore o comportamento anomalo che potrebbero verificarsi, con le relative azioni da compiere. Esistono diverse modalità per specificare gli scenari di fallimento. La più classica è quella che utilizza uno stile del tutto simile al codice scritto con linguaggi di programmazione che non supportano la gestione delle eccezioni (C like): dopo ogni azione che può causare un errore, viene eseguito un test esplicito e quindi vengono dettagliate le azioni da compiere nel relativo blocco. Pertanto il tutto viene specificato nella stessa sequenza. Una tecnica alternativa, più elegante, prevede invece di utilizzare uno stile simile alla stesura del codice utilizzando il meccanismo delle eccezioni: nelle apposite sezioni vengono riportate “le eccezioni” che si intende gestire e le relative operazioni da compiere. Pertanto per ogni azione che può generare anomalie sono presenti tante sezioni “alternative”, una per ciascuna tipologia di anomalia, riportanti sia la dichiarazione dell’anomalia stessa sia le azioni da compiere. Questa organizzazione è particolarmente utile anche per l’attività di pianificazione del processo di sviluppo del software: le varie versioni (build) possono essere “schedulate” considerando solo specifici flussi di determinati casi d’uso, rimandando a versioni future quelli tralasciati.
L’illustrazione degli scenari alternativi effettuata attraverso opportuni template evidenzia il grande vantaggio offerto da questo formalismo rispetto a quello grafico. In quest’ultimo è di solito necessario disegnare un nuovo diagramma per ogni alternativa, il che richiede una quantità decisamente superiore di tempo, con maggiore dispendio di energie per la realizzazione, e rende difficoltosa la gestione delle modifiche.
Un esempio di template
Dall’analisi della specifica dello use case riportato di seguito (template 2) è possibile evidenziare l’applicazione di tutta una serie di best practices, tra le quali le più importanti sono:
· tutte le azioni sono espresse con frasi chiare e concise (“Completes the initial requested details”, “Verifies that the data inserted is correct”);
- è sempre chiaro quale entità stia eseguendo l’azione; User fa questo, System fa quello, etc.;
- non ci sono dettagli relativi alla GUI; lo use case non è il formalismo ideale per disegnare interfacce utenti e quindi occorre evitare descrizioni del tipo “l’utente seleziona la drop list con l’elenco delle Valute, poi preme il pulsante di quotazione”, “l’utente apre il menu del cambio e seleziona la valuta GBP” mentre è scelta migliore demandare il disegno della GUI ad apposito formalismo, e utilizzare frasi del tipo “l’utente seleziona una valuta”, “l’utente richiede di eseguire il cambio, etc.”;
- ogni scenario termina chiaramente con successo (Terminate the use case successfully) o fallimento (Terminate the use case unsuccessfully);
- lo use case non include le business rules, ma le referenzia;
- lo use case ha una dimensione contenuta; ovviamente la dimensione ha un suo impatto, e va da se’ che use case di molteplici pagine sono difficili da manipolare.
Template 2 – Use case specification: Create Trade Order
Conclusioni
In questo articolo abbiamo presentato un breve sunto della notazione dei casi d’uso. L’obiettivo non è presentare tutte le informazioni relative alla notazione, bensì fornire al lettore la base teorica necessaria per poter poi disquisire liberamente il nodo centrale della serie: use case e user story.
Abbiamo avviato l’articolo presentando l’approccio tradizionale basato su un documento, chiamato Technical Specification, tipicamente di grandi dimensioni, onnicomprensivo che include vari tipi di requisito: funzionali, non funzionali, GUI, business rules, data model e spesso anche soluzioni. Questo approccio, sebbene molto semplice in linea di principio, presenta tutta una serie di problemi, tra cui i più rilevanti sono lo scarso supporto ad approcci “divide et impera”, ridotte potenzialità di parallelizzazione delle attività di analisi e sviluppo, un elevato grado di ambiguità, una consultazione faticosa, e quindi difficile verifica e manutenzione, limitatissimo supporto alla fase di test, scarso supporto a processi di sviluppo iterativi ed incrementali, inadeguatezza alla definizione iniziale dell’architettura. Questi problemi spesso si riflettono in un incremento dei rischi e del tempo e budget richiesti al progetto.
Diverse notazioni sono state ideate per cercare di ovviare a questi limiti; una di queste è basata sulla notazione dei casi d’uso. Questa, sebbene non sia più una novità da oltre un decennio e per molti è lo standard de facto per la modellazione dei requisiti non funzionali; ciò nonostante è ancora frutto di molta confusione e utilizzi problematici. Non è per esempio chiaro a tutti che la notazione dei diagrammi dei casi d’uso permette solo di coprire una parte dei requisiti funzionali. Questi diagrammi permettono di illustrare le singole funzionalità del sistema, conferendo particolare risalto alla percezione che ne hanno gli utenti o meglio gli attori. Tuttavia, forniscono esclusivamente una panoramica statica, e da soli non sono assolutamente in grado di catturare le varie informazioni necessarie per definire in dettaglio i vari servizi.
Occorre pertanto integrare questi diagrammi con la descrizione comportamentale. Anche ricorrere ad altri diagrammi messi a disposizione dallo UML per la modellazione del comportamento dinamico del sistema ha i suoi problemi. I principali sono l’aumento significativo del tempo necessario per realizzare i diagrammi stessi e l’estrema difficoltà di manutenzione.
Uno dei principali vantaggi della notazione dei casi d’uso risiede nella semplicità, immediatezza e potenza della notazione. Si tratta di una notazione intuitiva, attraente e molto potente che permette di descrivere il comportamento del sistema dal punto di vista dei suoi attori. Queste caratteristiche hanno spesso generato degli effetti collaterali disastrosi: la notazione basata su persone stilizzate e ellissi, cioè “disegnini”, ha spesso dato l’errata impressione che si tratti di una notazione elementare il cui utilizzo non richieda studi o esperienza. Ma non è così e le persone esperte sanno benissimo che scrivere use case di qualità richiede studio e molta esperienza. Casi d’uso di scarsa qualità possono avere degli effetti dannosi sul progetto.
Per finire, quando si scrivono dei casi d’uso è necessario seguire tutta una serie di regole base, le più importanti delle quali sono che tutti i passi devono essere espressi attraverso frasi chiare e concise (non più di una riga!); che deve essere sempre chiarissimo quale entità stia eseguendo l’azione (attore o sistema); che non bisogna inquinare lo use case con descrizioni dettagliate della GUI; che ogni scenario deve terminare con una chiarissima dichiarazione di successo o fallimento; che lo use case non è il luogo giusto per includere business rule che invece vanno referenziate; che ogni use case deve avere una dimensione gestibile.
Nel prossimo articolo affronteremo il paragone fra casi d’uso e storie utente.
Riferimenti
[1] Luca Vetti Tagliati, “UML e ingegneria del software: dalla teoria alla pratica”, Tecniche Nuove, 2003
[2] Giovanni Puliti, “Guida galattica per scrummers – V parte: Le storie utente. Concetti fondamentali”, MokaByte 192, febbraio 2014
https://www.mokabyte.it/cms/article.run?articleId=LOS-RCD-DS5-T5D_7f000001_10364476_1d974629
[3] Ivar Jacobson, “Use Cases: Yesterday, Today, and Tomorrow”
http://www.jaczone.com/papers/use_cases-2002-11-26.pdf
[4] Ivar Jacobson, “Use Cases and Aspects: Working Seamlessly, Together”
http://www.jot.fm/issues/issue_2003_07/column1.pdf
[5] Susan Lilly “Use cases Pitfall: Top 10 Problems from Real Projects Using Use cases”, Technology of Object-Oriented Languages and Systems, 1999
http://www.ddj.com/architect/184414560
[6] Philippe Kruchten, “Architectural Blueprints. The “4+1″ View Model of Software Architecture”, IEEE Software 12 (6), 1995, pp. 42-50.
Luca Vetti Tagliati ha conseguito il PhD presso il Birkbeck College (University of London) e, negli anni, ha applicato la progettazione/programmazione OO, Component Based e SOA in diversi settori che variano dal mondo delle smart card a quello del trading bancario, prevalentemente in ambienti Java EE. Dopo aver lavorato a lungo nella City di Londra per le più importanti banche di investimento (Goldman Sachs, Lehman, Deutsche Bank, UBS, HSBC) ricopre attualmente l'incarico di Senior Lead Global Architect per importanti gruppi bancari nel sud della Svizzera. Ha collaborato attivamente con John Daniels (coautore del libro "UML components") e Frank Armour (coautore del libro "Advanced Use Case Modeling"). È autore del libro "UML e ingegneria del software", pubblicato nel 2003 da HOPS/Tecniche Nuove, di "Java Best Practice: i migliori consigli per scrivere codice di qualità", pubblicato nel 2008 dal medesimo editore, e dell'ebook "Verso Java 8. Appunti per lo sviluppatore in Java SE 7" pubblicato da MokaByte e scaricabile dal nostro sito. È stato invitato a Los Angeles e Boston per presentare i suoi paper in convegni inerenti Software Engineering & Architecture. Nel 2015 ha ricevuto il prestigioso ICMG Award.