UML e Ingegneria Del Software, Dalla Teoria Alla Pratica (Luca Vetti Tagliati)(Giugno 2003) Hops

January 31, 2017 | Author: gaistefano | Category: N/A
Share Embed Donate


Short Description

Download UML e Ingegneria Del Software, Dalla Teoria Alla Pratica (Luca Vetti Tagliati)(Giugno 2003) Hops...

Description

A Francesco e Anna, miei dolci genitori: a voi cui tutto devo. Silenzio assordante… Eco smarrita… Frastuono di preghiera inconsolata sussurrata nella notte.

Introduzione Nemo propheta in patria

Prefazione Probabilmente gran parte dei lettori di questo libro conosceranno svariati trattati relativi alla programmazione Object Oriented, magari in Java, e avranno partecipato alla costruzione di sistemi informatici più o meno complessi. Altrettanto probabilmente, una buona percentuale avrà constatato che non sempre il modo di produrre sistemi software segue un approccio, per così dire, ingegneristico — basato su un processo formale — e che spesso il linguaggio di modellazione unificato (UML, Unified Modeling Language) recita un ruolo troppo marginale. Il problema è che, sebbene lo UML sia un linguaggio — magari di modellazione ma pur sempre un linguaggio — non sempre è chiaro come, quando e perché utilizzarlo. Ebbene, il presente libro si prefigge l’obiettivo di fornire al lettore tutte le informazioni necessarie per utilizzare lo UML nel contesto dei processi di sviluppo del software, utilizzando un approccio operativo che però non trascuri l’imprescindibile teoria. Il libro è stato scritto riducendo al minimo i prerequisiti, e conferendo particolare importanza agli esempi, specialmente quelli tratti da progetti reali, nella convinzione che un particolare legame con la realtà quotidiana dell’ambiente lavorativo sia il mezzo più idoneo per focalizzare rapidamente i concetti esposti… nonché per procurare all’autore vitto e alloggio a spese dello Stato. Ciò nondimeno si è cercato di non trascurare il pubblico più esigente, inserendo nel libro diversi paragrafi di livello tecnico superiore. Si tratta tuttavia di sezioni dedicate a lettori che desiderano l’approfondimento, ben circoscritte e assolutamente opzionali, quindi non indispensabili alla comprensione di quanto

2

Introduzione

esposto. Il libro pertanto si presta ad essere fruito secondo diversi livelli di difficoltà in base a necessità e obiettivi del lettore. Questo libro conferisce particolare enfasi a due grandi tematiche portanti: analisi dei requisiti utente e analisi e disegno del sistema. Quindi gli strumenti dello UML analizzati più approfonditamente sono sia quelli utilizzati durante le relative fasi del processo di sviluppo del software sia quelli utilizzati più frequentemente nel ciclo di vita del software: anche in questo contesto — come rimarcato dallo stesso James Rumbaugh — vale la famosa equazione dell’80/20: “l’80% della modellazione di un sistema, tipicamente, necessita del 20% dei concetti dello UML”. Ma quali sono i motivi che hanno spinto a scrivere un altro libro dedicato allo UML? Le risposte potrebbero essere due e molto semplici: • perché è in lingua italiana (o almeno in un suo surrogato); • perché i libri dei Tres Amigos (James Rumbaugh, Ivar Jacobson, Grady Booch) pur essendo uno straordinario ausilio — guai a non averne una copia, anche se mai aperta, poiché si rischierebbe di suscitare giudizi di scarsa professionalità — forse risentono del limite di essere poco accessibili a un pubblico non espertissimo di progettazione Object Oriented. L’idea di fondo del libro è fornire un diverso livello di astrazione: i libri dei Tres Amigos offrono un livello molto elevato — quasi filosofico — mentre il presente vuole essere decisamente vicino alla pratica nel tentativo di fornire un supporto concreto a tutti coloro che utilizzano, o vorrebbero utilizzare, lo UML quotidianamente per costruire sistemi che funzionano. A tal fine sono stati utilizzati moltissimi esempi che, sebbene finiscano per rendere i capitoli decisamente corposi, sono comunque sezioni opzionali benché costellate di utili riflessioni. La scrittura del libro è iniziata quasi congiuntamente alla pubblicazione della versione 1.2 dello UML e abbraccia un periodo di due anni di lavoro “matto e disperatissimo”. I contenuti sono costantemente revisionati e aggiornati sia al fine di renderlo via via conforme alle nuove specifiche, sia per integrare esempi sempre più interessanti attinti dall’esperienza lavorativa. Nella sua versione attuale, la variante dello UML di riferimento è la 1.4, sebbene l’autore stia controllando da vicino i lavori relativi alla versione 2. Tra le righe del libro fa capolino l’(auto)ironia dell’autore, coadiuvata all’occorrenza da quella dei collaboratori: ciò sia al fine di rendere l’argomento meno gravoso, sia per evidenziare con spirito grottesco le distorsioni riscontrate nel modo di produrre il software in molte realtà organizzative. Troppo spesso i vari manager sono sfrenatamente concentrati a consegnare i sistemi senza curare minimamente la crescita professionale del proprio personale e quindi, in ultima analisi, la qualità dei sistemi prodotti. Non è il software che rifiuta l’ingegnerizzazione, ma i tecnici che rimangono intimoriti dalla progettazione:

UML e ingegneria del software: dalla teoria alla pratica

3

troppo spesso si ha la sensazione che la produzione del software sia ancora concepita come un’arte piuttosto che come una scienza.

A chi è rivolto Questo libro prevede un pubblico di potenziali lettori piuttosto vasto: tutti coloro che hanno a che fare con progetti basati sul paradigma Object Oriented (o Component Based). In particolare, potrebbe risultare vantaggioso per figure professionali che vanno dai tester ai programmatori, dagli architetti ai capi progetto, dai business analyst a, perché no, persone con ruoli a carattere più commerciale. Gli architetti costituiscono sicuramente il pubblico ideale: loro, in effetti, dovrebbero essere in grado di utilizzare proficuamente il linguaggio durante tutta la fase di sviluppo del software, dall’analisi dei requisiti, al disegno del sistema, ai test di accettazione. I vantaggi che potrebbero ricavarne i programmatori scaturiscono essenzialmente dalla capacità di leggere i diagrammi forniti dagli architetti, per tradurli in codice e infine aggiornarli per far sì che il disegno finale corrisponda alla reale implementezione del sistema. Non è peccato mortale se l’implementazione varia, entro certi limiti, dal modello di disegno sia perché non è opportuno scendere eccessivamente in dettaglio nel disegno del sistema, sia perché non è sempre possibile prevedere tutto fin dall’inizio, inclusi malfunzionamenti di software fornito da terze parti. Nel mondo ideale la codifica dovrebbe essere immediata e senza troppe variazioni: nella realtà non è sempre così. Variazioni in corso d’opera sono normali fintantoché non stravolgano il disegno stesso. Sempre i programmatori, durante la codifica del disegno, dovrebbero avere bene in mente i casi d’uso oggetto dell’implementazione: essi descrivono la sequenza di azioni da svolgere nel caso in cui tutto funzioni correttamente e per gestire eventuali anomalie che potrebbero verificarsi. Per ciò che concerne i capi progetto, in molti casi essi potrebbero aumentare la qualità del proprio lavoro utilizzando i diagrammi dei casi d’uso (use cases diagram) e più in generale la relativa vista (use cases view, vista dei casi d’uso), sia al fine di coadiuvare il team nella “cattura” dei requisiti utente nel miglior modo possibile, sia per poter monitorare adeguatamente lo stato di avanzamento del progetto, sia per riuscire ad avere una più intima conoscenza di cosa si sta costruendo. Il cliente alla fine verifica che il sistema realizzi correttamente quanto sancito nella use case view presente nel documento di analisi dei requisiti. I team leader, tra le altre attività, potrebbero trarre enorme beneficio dall’utilizzo dello UML per assolvere alle tipiche funzioni di coordinamento, supporto e addestramento del team e per coadiuvare i capi progetti nell’analisi della tempificazione al fine di renderla quanto più reale possibile… La tempificazione non è esclusivo esercizio di fantasia: tempificare con successo non significa giocare al lotto con Microsoft Project. I tester dovrebbero essere in grado di validare, o, se si preferisce, di individuare malfunzionamenti eseguendo i famosi test a scatola nera (black box tests) per verificare che il sistema effettivamente aderisca alle specifiche catturate sempre dalla use case view.

4

Introduzione

Per terminare, i business analyst, nel loro lavoro di analisi dell’organizzazione del cliente e del relativo business, potrebbero utilizzare i diagrammi dello UML, per realizzare il famoso documento dei requisiti utente, modellando una prima versione del modello dei casi d’uso. Ciò apporterebbe due validi risultati: semplificazione del lavoro dei tecnici responsabili delle fasi seguenti e accrescimento del livello di professionalità e della qualità della documentazione prodotta.

Consigli per la lettura Visto il grande impegno profuso nel redigere il presente libro, verrebbe voglia di consigliarne una lettura basata sul seguente algoritmo: Iterator pagineLibro = libroUML.iterator(); while ( pagineLibro.hasNext() ) { utente.read( pagineLibro.next() ) }

Chissà che ciò possa essere realmente utile e possa fornire validi spunti soprattutto a coloro che non hanno molta esperienza dello UML e dei processi di sviluppo. Si è comunque consapevoli di quanto poco e prezioso sia il tempo: si è cercato di soddisfare le esigenze anche di coloro che sono desiderosi di arrivare direttamente al nocciolo degli argomenti senza perdere troppo tempo in tematiche ritenute meno interessanti. A tal fine il libro è stato strutturato in modo da favorirne una fruizione diversificata dipendente dalle necessità dei singoli lettori. In particolare vi sono sezioni di elevata complessità dedicate ai lettori più esigenti, che però possono essere assolutamente ignorate senza per questo precludersi la possibilità di comprendere quanto riportato negli altri paragrafi. Altre parti invece sono dedicate a considerazioni relative al processo di sviluppo: di nuovo, i lettori che volessero concentrare l’attenzione unicamente sullo UML possono semplicemente sorvolare tali paragrafi. Infine, vi sono interi paragrafi dedicati allo stile da utilizzarsi per disegnare i vari diagrammi. L’obiettivo è pertanto migliorare la forma senza entrare nel merito della semantica, quantunque le due caratteristiche non possano essere trattate in maniera completamente separata. Sebbene non siano di importanza fondamentale, questi paragrafi non sono neanche originati da un vezzo estetico. Si tratta di una serie regole che permettono di rendere i diagrammi più eleganti, armoniosi, semplici, in poche parole più accattivanti. Ciò è molto utile perché rende i diagrammi più facilmente comprensibili, memorizzabili, concorre a ridurre le barriere psicologiche che spesso si innalzano nella mente dei fruitori dei diagrammi, ecc. I primi due capitoli (UML: che cosa è, che cosa non è e UML: struttura, organizzazione, utilizzo), considerata la genericità degli argomenti affrontati, possono risultare dispersivi

UML e ingegneria del software: dalla teoria alla pratica

5

e a tratti tediosi. Sebbene non siano di importanza fondamentale, risultano utili per chiarire fin da subito cosa sia lo UML e il suo rapporto con le tecnologie attigue. A partire dal capitolo relativo ai casi d’uso, tutto diviene più operativo e quindi scorrevole: si entra nel vivo dello UML! Infine è presente un numero decisamente elevato di esempi attinti dal mondo del lavoro; tutti coloro certi di aver capito possono placidamente concentrarsi solo su alcuni di essi ignorando i restanti.

Struttura Questo libro è stato strutturato in capitoli autonomi, in ognuno dei quali viene esaminato approfonditamente uno specifico argomento, cercando di limitare al massimo i rimandi ai capitoli successivi: non è quindi necessario rincorrere gli argomenti per tutto il testo. Si è tentato di ricostruire una sorta d’unità d’azione: Milan Kundera non approverebbe. Ciò se da un lato offre evidenti vantaggi, dall’altro genera la necessità di introdurre inevitabili ridondanze…

Capitolo 1 – UML: che cosa è, che cosa non è Obiettivo del primo capitolo è di presentare lo UML inquadrandolo nel suo contesto, chiarendo fin dal principio il rapporto esistente con le altre tecniche/metodologie che lo circondano. Il capitolo si apre con una breve panoramica storica dello Unified Modeling Language e delle motivazioni che hanno spinto i Tres Amigos ad affrontare lo straordinario progetto di un linguaggio di modellazione unificato. Si prosegue con una descrizione di cosa sia e cosa non sia lo Unified Modeling Language, della sua architettura e di quali siano gli obiettivi. Successivamente viene affrontato il tema dei più diffusi processi di sviluppo del software e, in particolare, The Unified Software Development Process, Use Case Driven Object Modeling with UML, il Rational Unified Process (RUP), l’eXtreme Programming (XP) e l’Agile Modeling (AM).

Capitolo 2 – UML: struttura, organizzazione, utilizzo Il secondo capitolo è interamente dedicato a una panoramica dello UML: vengono presentate le viste, i diagrammi e i meccanismi di estensione forniti dallo UML per aumentarne semantica e sintassi. Sono inoltre introdotti i profili, ossia collezioni di meccanismi di estensione predefiniti da utilizzarsi durante la modellazione di sistemi che utilizzano architetture standard quali ad esempio CORBA ed EJB (Appendice C) o per compiti specifici come la modellazione dell’area business.

6

Introduzione

L’obiettivo è fornire un’idea, quanto più esauriente possibile, di cosa sia il linguaggio senza aver la pretesa che il lettore comprenda fin da subito tutti i concetti introdotti, per i quali sono presenti appositi capitoli di dettaglio. Importante è iniziare a comprendere che lo UML fornisce tutta una serie di “utensili”, ciascuno dei quali risulta appropriato a scopi ben precisi e del tutto inadeguato ad altri: una vera e propria cassetta degli attrezzi. La descrizione dei vari manufatti che si prestano a essere realizzati per mezzo dello UML avviene nel contesto di un generico processo di riferimento.

Capitolo 3 – Introduzione agli Use Case Nel terzo capitolo si focalizza l’attenzione sulla teoria degli use case e sul relativo impiego rispettando rigorosamente le direttive dello UML. Nel capitolo seguente invece, ci si discosterà sensibilmente dalle direttive standard, al fine di illustrare un utilizzo avanzato e maggiormente operativo dei casi d’uso. Dopo una breve disquisizione su cosa si intenda con i termini “requisiti utente”, si procede con l’introduzione della use cases view (vista dei casi d’uso), entrando finalmente nel vivo dello UML. Della vista dei casi d’uso sono descritte le due componenti: statica e dinamica. Per ciò che riguarda la proiezione statica, sono descritti in dettaglio i diagrammi dei casi d’uso correlati da elementi e relazioni. Per ciò che concerne la proiezione dinamica (ossia gli scenari), viene illustrata la versione standard basata sui flussi degli eventi e sui diagrammi dello UML atti a modellare comportamenti dinamici (diagrammi di sequenza, collaborazione, attività, ecc.).

Capitolo 4 – Modellazione avanzata degli Use Case Nel quarto capitolo viene illustrata una tecnica di utilizzo operativo per la cattura e la documentazione degli scenario dei casi d’uso che a volte si discosta sensibilmente dalle direttive standard. Modellare sistemi reali mette in luce una serie di problemi e lacune di cui si tratta raramente nei libri e nelle specifiche ufficiali dello UML. Si tratta di un capitolo di stampo decisamente pratico corredato da un notevole numero di esempi di casi d’uso pseudoreali. Sebbene non sia indispensabile esaminarli attentamente tutti, ognuno di essi è stato selezionato in quanto fornisce una serie di spunti molto interessanti.

Capitolo 5 – Completamento dell’analisi dei requisiti Il quinto capitolo verte sue due tematiche portanti: la presentazione di una tecnica dimostratasi particolarmente valida nell’arduo compito dell’analisi dei requisiti utente e l’illustrazione dei manufatti (artifact) che completano il modello dell’analisi dei requisiti.

UML e ingegneria del software: dalla teoria alla pratica

7

Il problema che tenta di risolvere la tecnica di analisi dei requisiti esposta è trovare una piattaforma di dialogo comune e costruttiva tra personale tecnico e clienti, in altre parole trovare un efficace raccordo tra i due diversi linguaggi tecnici: quello dell’area business e quello informatico. A tal fine viene sfruttato il formalismo degli activity diagram (diagramma delle attività). Il modello dei casi d’uso, sebbene costituisca un artifact di importanza centrale nel contesto dei processi di sviluppo del software, da solo non è assolutamente in grado di fornire sufficienti informazioni per il pieno svolgimento delle restanti fasi del ciclo di vita del sistema. La completa realizzazione della vista dei requisiti del sistema prevede la produzione di ulteriori manufatti complementari. In particolare, nel capitolo sono presentati artifact di notevole importanza, come il dettaglio delle interfacce, i test case, il documento dei requisiti non funzionali e le famosissime business rules.

Capitolo 6 — Object Oriented in un chicco di grano Il sesto capitolo è dedicato alla presentazione dei princìpi fondamentali dell’Object Oriented. L’obiettivo è introdurre le nozioni utilizzate nei capitoli successivi, senza aver la presunzione di trattare in maniera approfondita un argomento così importante e vasto. Il capitolo non poteva che iniziare con la definizione formale di oggetto e classe e delle relative relazioni, prosegue con l’illustrazione degli elementi essenziali di cui è composto un oggetto (identità, stato e interfaccia) e quindi presenta le tre leggi fondamentali dell’Object Oriented (ereditarietà, incapsulamento e polimorfismo). In questa sede si coglie l’occasione per riportare alcuni errori tipicamente commessi da tecnici alle prime armi. Questa prima sezione termina con l’introduzione di due princìpi fondamentale del disegno dei sistemi, note con i nomi di “massima coesione” e “minimo accoppiamento”. La parte conclusiva del capitolo è dedicata al formalismo dell’Abstract Data Type (tipo di dato astratto) e al Design By Contract (disegno per contratto).

Capitolo 7 — Gli oggetti: una questione di classe Il settimo capitolo è focalizzato sull’illustrazione del formalismo dei diagrammi delle classi, probabilmente uno dei più noti e affascinanti tra quelli definiti dallo UML. Nei processi di sviluppo del software, questo formalismo si presta a rappresentare formalmente molteplici artifact presenti in diversi stadi del processo. Nelle primissime fasi lo si adotta per rappresentare i modelli del dominio e di business, nello stadio di analisi è utilizzato per dar luogo all’omonimo modello, nella fase di disegno è impiegato per progettare e documentare l’organizzazione in codice del sistema e così via. Dopo aver illustrato come rappresentare in UML i concetti basilari di classi, interfacce, ecc., si passa all’illustrazione delle diverse tipologie di relazioni. La modellazione di un sistema non richiede esclusivamente l’identificazione delle classi di cui è composto, ma anche dei

8

Introduzione

legami che le interconnettono. Si prosegue pertanto passando in rassegna le relazioni con cui è possibile collegare le classi tra loro. La sezione successiva del capitolo illustra il formalismo dei diagrammi degli oggetti (object diagram). Questi rappresentano una variante dei diagrammi delle classi, o meglio ne sono istanze e ne condividono gran parte della notazione. Rappresentano la famosa istantanea del sistema eseguita in un preciso istante di tempo di un’ipotetica esecuzione.

Capitolo 8 – Le classi nei processi Il capitolo ottavo è dedicato all’illustrazione dell’utilizzo del formalismo dei diagrammi delle classi nel contesto di un processo di sviluppo del software. In particolare si presentano le diverse versioni di modelli a “oggetti”, le loro proprietà, un nutrito insieme di esempi e consigli, eventuali “tranelli” in cui è possibile cadere, il rapporto di ciascuno di essi con i restanti, ecc. Si tratta di uno di quei capitoli che dovrebbe fornire risposte esaurienti agli interrogativi più frequentemente posti all’autore del libro. Il primo modello ad oggetti presentato è quello relativo al dominio del problema (Domain Object Model). In particolare si presenta un processo pratico atto a semplificare l’individuazione delle classi appartenenti al modello e a inserirle in un contesto strutturato. Del processo viene fornito un esempio di applicazione, nel quale sono evidenziati errori ricorrenti e vengono dati utili consigli. Poiché è sempre meno frequente realizzare sistemi partendo dal nulla, mentre è ormai prassi reingegnerizzare sistemi esistenti, si presenta una tecnica atta a realizzare prime versioni del modello a oggetti del dominio partendo dall’analisi di una base di dati del sistema. Il modello ad oggetti presentato successivamente è quello relativo all’area business (Business Object Model). Si tratta di una versione molto simile a quello del dominio, in cui compaiono elementi aggiuntivi (work unit, unità di lavoro e worker, lavoratori). Pertanto si evidenziano le similitudini e le differenze con quello del dominio e si dà quindi una serie di consigli relativi a quale usare, quando, e così via. Teoricamente, in un processo di sviluppo andrebbero realizzati entrambi; nella pratica, per via di una serie di problemi relativi a tempo, budget, personale, ecc. è normale trascurarne uno per concentrasi sull’altro. Il passo successivo consiste nel presentare il modello a oggetti di analisi (Analysis Model). Anche per questo modello viene presentata una tecnica utile per la sua realizzazione, una serie di consigli, nonché i vari tranelli in cui si può correre il rischio di imbattersi. Nei progetti reali non è sempre possibile realizzare il modello di analisi, per via dei soliti problemi (mancanza di tempo, personale, ecc.), pertanto si fornisce una serie di consigli su come ridurre il rischio generato dalla mancata realizzazione, su quando sia possibile trascurarlo, su quando invece è opportuno realizzarlo, ecc. Prima di proseguire nella presentazione del modello successivo, viene presentato un formalismo di importanza storica per il processo di realizzazione dei vari modelli a oggetti che tuttora conserva delle aree di applicazioni, ossia il metodo delle CRC cards.

UML e ingegneria del software: dalla teoria alla pratica

9

L’ultimo modello a oggetti presentato, probabilmente il più noto, è quello di disegno (Design Object Model). Di questo dovrebbero esistere almeno due versioni: quello di specifica e quello di implementazione. Il primo dovrebbe essere realizzato prima dell’inizio della codifica, mentre il secondo è il risultato dell’evoluzione che il disegno subisce durante l’implementazione (variazioni in corso d’opera). Come al solito si riportano immancabili esempi, consigli su come realizzarlo ecc. Il capitolo si conclude cercando di rispondere alla fatidica domanda: “Quando un modello a oggetti di disegno si può considerare ben costruito?”.

Capitolo 9 – I diagrammi di interazione Le parti che costituiscono un qualsivoglia sistema non sono assemblate per persistere semplicemente in una posizione stabile, bensì interagiscono tra loro scambiandosi messaggi al fine di raggiungere uno scopo ben preciso. Pertanto la descrizione di ogni comportamento necessita di essere modellata sia attraverso una prospettiva che mostri la struttura statica degli elementi cooperanti, sia mediante un’altra che illustri il relativo comportamento dinamico. Obiettivo del presente capitolo e di quello successivo è illustrare i formalismi forniti dallo UML per modellare comportamenti dinamici. In particolare nel capitolo nono l’attenzione è focalizzata sui diagrammi di interazione (interaction diagram). Con questo nome si indicano i diagrammi di sequenza (sequence diagram) e quelli di collaborazione (collaboration diagram). Quantunque permettano di mostrare lo stesso contenuto informativo, si diversificano per l’aspetto dell’interazione cui è attribuita maggiore importanza: i diagrammi di sequenza enfatizzano lo scambio dei messaggi nel tempo, mentre i diagrammi di collaborazione attribuiscono maggiore importanza all’organizzazione degli “oggetti” coinvolti nell’interazione modellata. L’utilizzo di questi diagrammi comprende quasi tutte le fasi dei processi di sviluppo del software: si va dall’analisi dei requisiti in cui possono essere utilizzati per illustrare gli scenari dei casi d’uso (soprattutto i diagrammi di sequenza), alla fase di analisi e disegno in cui mostrano le dinamiche interne del sistema. Si tratta di diagrammi ampiamente utilizzati nei capitoli precedenti e che, nel capitolo nono, trovano un’illustrazione completa. Di questi due diagrammi è presentato in modo approfondito il formalismo, consigli su come e quando utilizzarli, ecc. Il tutto sempre coadiuvato da un consistente insieme di esempi nonché dalle sezioni dedicate ai consigli relativi allo stile.

Capitolo 10 – Le attività di stato In questo capitolo viene conclusa l’illustrazione dei diagrammi dello UML destinati a modellare comportamenti dinamici. In particolare sono illustrati i diagrammi degli stati (state chart diagram) e quelli di attività (activity diagram). Anche in questo caso si tratta di

10

Introduzione

diagrammi ampiamente utilizzati nel corso dei capitoli precedenti che però nel capitolo decimo sono affrontati in maniera organica. I diagrammi degli stati descrivono il ciclo di vita di automi a stati finiti. Più precisamente, possibili sequenze di stati e azioni attraverso le quali istanze di opportuni elementi possono procedere durante il relativo ciclo di vita. Tale evoluzione è l’effetto delle reazioni innescate in tali istanze al verificarsi di opportuni eventi discreti. I diagrammi di attività (discendenti dei celebri diagrammi di flusso, flowchart diagram) sono una variante di quelli degli stati, ove gli stati rappresentano l’esecuzione di azioni o sottoattività e le transizioni sono innescate dal completamento di tali azioni o sottoattività. Questi diagrammi possono prevedere un considerevole utilizzo nelle fasi di analisi dei requisiti, che, tipicamente, si affievolisce nelle restanti fasi del processo di sviluppo del software. I diagrammi delle attività trovano grande applicazione nella fase di analisi dei requisiti dove sono impiegati per mostrare gli scenari dei casi d’uso. Nelle restanti fasi tendono a essere utilizzati qualora vi sia la necessità di mostrare comportamenti concorrenti e la precisa allocazione delle responsabilità. Sempre i diagrammi delle attività recitano un ruolo di primissima importanza ogniqualvolta sia necessario illustrare graficamente processi e sottoprocessi. I diagrammi degli stati sono utilizzati indistintamente in tutte le fasi del processo di sviluppo del software qualora vi sia la necessità di illustrare il ciclo di vita di “oggetti” (anche composti) che possono evolvere attraverso un insieme finito di stati. Di questi due diagrammi è illustrato approfonditamente il formalismo, consigli su come e quando utilizzarli, ecc. Il tutto sempre coadiuvato da un consistente insieme di esempi nonché dalle sezioni dedicate ai consigli relativi allo stile grafico.

Capitolo 11 – Anche gli aspetti “fisici” sono importanti Il libro si conclude con la presentazione dei diagrammi forniti dallo UML per modellare aspetti fisici del sistema: ossia i diagrammi di implementazione (implementation diagram). Con questo termine ci si riferisce ai diagrammi dei componenti (component diagram) e a quelli di dispiegamento (deployment diagram). I diagrammi dei componenti mostrano appunto la struttura dei componenti di cui è composto il sistema, inclusi gli elementi che li specificano (classificatori) e i manufatti che li implementano. I diagrammi di dispiegamento mostrano la struttura dei nodi che costituiscono l’architettura fisica del sistema e nei quali vengono installati i componenti. Questi diagrammi si prestano anche a utilizzi più estesi. Nella modellazione dell’area business, per esempio, i componenti mostrano procedure di business e manufatti, mentre i nodi rappresentano organizzazioni e risorse del business. Sebbene costituiscano il modello fisico, la loro produzione inizia già dalle primissime fasi dell’analisi dei requisiti, poiché considerazioni di carattere architetturale forniscono importantissimi feedback allo studio di fattibilità dei requisiti.

UML e ingegneria del software: dalla teoria alla pratica

11

Di questi diagrammi, in particolare di quello dei componenti, sono mostrate importanti lacune evidenziate (quasi completamente riassorbite dalla versione 2 di UML), è illustrato ampiamente il formalismo, sono forniti consigli su come e quando utilizzarli, un consistente insieme di esempi, vengono proposte indicazioni relative allo stile grafico, ecc.

Appendice A – UML e i linguaggi di programmazione non OO Sebbene UML sia un linguaggio di modellazione ideato per la progettazione di sistemi OO e component-based, si presta a essere utilizzato per la produzione di sistemi che prevedano linguaggi di programmazione non basati su tali paradigmi. Ciò è vero però utilizzando opportune cautele. Nell’Appendice A sono illustrati utili criteri, idee e problematiche relative all’utilizzo dello UML con linguaggi di programmazione non OO.

Appendice B – UML e la modellazione di basi di dati non OO Obiettivo dell’appendice B è illustrare una sorta di profilo utilizzabile per modellare basi di dati fondati su paradigmi non OO. In particolare l’attenzione è focalizzata sulle basi di dati relazionali, sebbene i concetti esposti si prestino a essere estesi anche a basi di dati fondati su diversi paradigmi.

Appendice C – Il profilo EJB L’appendice C è dedicata ad una breve illustrazione di un profilo (non standard) utilizzabile per la modellazione di sistemi component based fondati sull’architettura Sun EJB. Brevemente, un profilo è una particolare estensione dello UML, o meglio una collezione di estensioni predefinite, le quali nascono dall’esigenza di standardizzare l’utilizzo dello UML per domini o scopi o tecnologie ampiamente diffuse.

Appendice D – Glossario Un utile glossario di riferimento costituisce l’Appendice D.

Appendice E – Bibliografia ragionata Un elenco di libri, riviste e siti web di riferimento.

Ringraziamenti Come ogni libro che si rispetti anche qui è presente l’immancabile e doverosa sezione dedicata ai ringraziamenti. In primo luogo la personale riconoscenza dell’autore va al fantastico team che lo ha assistito e coadiuvato durante tutto il ciclo di sviluppo del libro. In particolare si ringraziano gli amici Roberto Virgili, (www.RobertoVirgili.com, team leader architetto nonché sviluppatore di sistemi distribuiti, vero e proprio formatore di menti il quale, tra l’altro, ha sostenuto per primo l’idea del libro: i lettori sanno quindi a chi rivolgersi per le eventuali ritorsioni), e ad Antonio Rotondi (Antonio.Rotondi @Artech.freeserve.co.uk, team leader, architetto di sistemi distribuiti e infrastrutture di sicurezza che negli ultimi anni ha lavorato intensamente allo sviluppo di sistemi per la gestione di smart card). Si tratta di due tecnici informatici di profonda levatura ed esperienza, veri e propri profeti dell’arte di realizzare sistemi informatici che funzionano. Ringraziamenti aggiuntivi spettano a Gianluca Pignalberi, (Gianluca.Pignalberi @caspur.it, attualmente applicativo presso il CASPUR, Consorzio Interuniversitario per le Applicazioni di Supercalcolo Per Università e Ricerca, esperto di metodi di Intelligenza Artificiale applicati all’elaborazione delle immagini e di Linguistica Computazionale) sempre prodigo di consigli e preziosi suggerimenti, specie relativi alla sfera delle strutture dati e dei linguaggi formali. Un particolare ringraziamento va poi a Francesco Saliola che ha curato la redazione e l’impaginazione del libro. Ulteriori ringraziamenti spettano a Giovanni Puliti (Java enthusiast, esperto di Java e tecnologie annesse, scrittore, articolista e direttore della rivista web www.MokaByte.it) e a Claudio Bergamini (amministratore di profonda conoscenza tecnica e rara lungimiranza., www.imolainfo.it) che hanno creduto nel libro e si sono adoperati proficuamente affinché il progetto potesse vedere la luce di Internet. Altri ringraziamenti doverosi spettano ai familiari dell’autore e a tutti i veri amici, presenti nei tanti momenti di bisogno. La profonda riconoscenza e il ringraziamento dell’autore spettano poi a tutti coloro che accoglieranno l’invito di leggere il libro e a fornire le proprie riflessioni, al fine di arricchire il presente lavoro rendendolo sempre più vicino alle tipiche esigenze dei progetti reali. Dulcis in fundo — forse — un ringraziamento “particolare”, molto particolare, va ai sedicenti tecnici, i vari “gatto” e “volpe” onnipresenti in tutte le organizzazioni. A tutti coloro che, chiusi nella loro arroganza, si sentono perpetuamente più furbi, “patrigni” dispensatori di consigli sempre troppo gratuiti. Agli adepti delle ditte a “conduzione familiare”, che obbligano giovani professionisti a combattere contro muri di gomma, a lavorare sempre di più e a dare costantemente il meglio di sé fino alla nausea per dimostrare che la qualità non alberga esclusivamente nei libri di teoria… Ammesso che queste persone siano in grado di capirla!

UML e ingegneria del software: dalla teoria alla pratica

13

Scuse Le personali scuse dell’autore vanno ai teorizzatori dell’informatica, agli autori dei vari testi sacri, e in primo luogo ai Tres Amigos. Il loro contributo alla semplificazione del processo di sviluppo dei sistemi informatici è così evidente da non richiedere assolutamente commenti. Si tratta di vere oasi di formazione intellettuale nel deserto degli “Azzeccagarbugli”, di fari che illuminano la rotta di tutti coloro per i quali l’informatica è una scienza… Ciò nonostante si è ritenuto opportuno non prendersi troppo sul serio, permettendosi ironie anche nei confronti dei mostri sacri — non solo nel senso etimologico del termine — dell’informatica… D’altronde i cimiteri sono gremiti di lapidi che declamano: “Qui giace una persona insostituibile”. Le scuse personali dell’autore sono rivolte ai lettori più attenti, se, talune volte, l’impeto di voler illustrare concetti complessi nel modo più semplice possibile possa aver travolto la trattazione erudita e portato a “frasi fuorvianti”. Ciò nonostante si è deciso si perseverare diabolicamente in tale direzione confidenti che “quando il saggio indica la luna solo lo stolto fissa il dito”. Le scuse finali sono relative alla voluminosità del libro… Attualizzando una frase che Voltaire usava scrivere nelle lettere alla sua amica: “… vi porgo le mie scuse per la voluminosità della lettera, ma proprio non ho avuto tempo per scriverne una concisa”.

Breve biografia dell’autore Luca Vetti Tagliati è nato a Roma il 03/12/1972 (ottima annata per il vino). Ha da sempre studiato IT: quando all’asilo bucava i fogli di carta con la matita, manifestava i primi sintomi da programmatore di schede perforate. Ha iniziato a lavorare professionalmente nel 1991 occupandosi di sistemi firmware ed assembler per microprocessori della famiglia iAPXx286. Nel 1994/95 ha intrapreso il lungo cammino nel mondo Object Oriented grazie al meraviglioso linguaggio C++. Nel 1996 si è trovato catapultato nel mondo Java. Negli ultimi anni ha applicato la progettazione/programmazione Component Based in settori che variano dal mondo delle smart cards (collaborazione con la Datacard, www.datacard.com) a quello del trading bancario (www.hsbc.com). Attualmente lavora come consulente presso la sede londinese della banca HSBC con funzioni di chief designer, team leader e membro del gruppo PEG (Processing Engineering Group) istituito per stabilire un processo di sviluppo standard da applicarsi per lo sviluppo di sistemi software. Il PEG si avvale della collaborazione di persone del calibro di John Daniels ([BIB15]) e Frank Armour ([BIB14]). È rintracciabile — per ingiurie ed eventuali apprezzamenti — presso l’indirizzo [email protected].

14

Introduzione

Convenzioni grafiche Il carattere senza grazie, accoppiato a un’opportuna icona, è utilizzato per evidenziare parti di testo meritevoli di particolare attenzione oppure dedicati esclusivamente a un pubblico esperto.

Il carattere piccolo indica parti di testo che riportano considerazioni non fondamentali: nonostante le informazioni contenute in tali parti di testo possano risultare interessanti per alcuni lettori, si tratta di nozioni che non sono indispensabili e che possono essere tranquillamente tralasciate senza pregiudicare la comprensione del testo restante.

L’icona della lampadina, congiuntamente al carattere senza grazie viene utilizzata per mettere in luce un determinato concetto chiave, un consiglio pratico o un altro argomento che meriti particolare attenzione.

L’icona del pericolo generico viene utilizzata per evidenziare sezioni, parti o paragrafi aventi una difficoltà intrinseca elevata e dedicati a un pubblico esperto. Si tratta comunque di paragrafi completamente opzionali la cui lettura e comprensione non è assolutamente indispensabile per l’apprendimento di quanto riportato nei paragrafi e capitoli successivi. Ad ogni modo, non scarseggiano mai ampie descrizioni testuali atte a rendere accessibili a tutti anche le trattazioni più complesse.

L’icona degli ingranaggi è stata utilizzata per evidenziare sezioni, parti o paragrafi dedicati ai processi di sviluppo del software. Tutti coloro allettati unicamente dal linguaggio dello UML e quindi interessati fino ad un certo punto ai processi di sviluppo, possono tranquillamente disinteressarsi di questi paragrafi.

L’icona della tavolozza dei colori evidenzia paragrafi contenenti linee guida per il miglioramento dell’eleganza e della comprensibilità dei diagrammi. L’obiettivo è pertanto migliorare lo stile senza entrare nel merito della semantica — in altre parole si focalizza l’attenzione sullo strato di presentazione — sebbene poi le due caratteristiche non possano essere trattate in maniera completamente disgiunta.

Capitolo

1

UML: che cosa è, che cosa non è Beati monoculi in regno caecorum

Introduzione Obiettivo del presente capitolo è fornire una panoramica generale sullo UML: il capitolo pertanto risulta essere decisamente corposo, quasi onnicomprensivo e non sempre di facile comprensione. Il fattore positivo è che si può procedere tranquillamente nella lettura del testo pur non comprendendo appieno quanto riportato. Il consiglio per tutti coloro che si accostano allo UML per la prima volta è di leggerlo comunque, magari molto rapidamente, una prima volta, con l’intento di tornare a rileggerlo in un secondo momento, quando si sarà in possesso di una maggiore familiarità con il linguaggio stesso. Allora si potranno apprezzare maggiormente gli argomenti illustrati e si potrà comprendere pienamente il rapporto che intercorre tra lo UML e le tecnologie correlate. L’obiettivo del capitolo è chiarire sin da subito, e in modo inequivocabile, che cosa sia e che cosa non sia lo UML e, quindi, quali siano le relative aree di competenza e gli obiettivi. A tal fine vengono presentate anche altre tecnologie strettamente correlate con lo UML, nella convinzione che ciò, aiutando a definire più distintamente i confini dello UML, concorra a fornire una visione più completa e precisa dello stesso. Si è ritenuto altresì utile fornire un conciso quadro “storico” della situazione presente prima e durante l’evoluzione dello UML, al fine di evidenziare le motivazioni teoriche che hanno spinto i cosiddetti Tres Amigos (Grady Booch, James Rumbaugh, Ivar Jacobson) ad affrontare il superbo progetto del linguaggio unificato. Sebbene sia opinione comune di molti tecnici che lo UML rappresenti “semplicemente” una notazione standard per la creazione di modelli Object Oriented di sistemi, in realtà è molto di più. Esso possiede una definizione rigorosa di metamodello, istanza del meta-metamodello noto con la sigla MOF (Model-Object Facility). Si tratta di concetti

2

Capitolo 1. UML: che cosa

è, che cosa non è

molto eleganti e affascinanti, a elevato livello di astrazione (la relativa descrizione è riportata nell’apposita appendice). Tali concetti meriterebbero, in effetti, una trattazione più rigorosa (le specifiche ufficiali constano di oltre 500 pagine) rispetto a quella riportata, ma ovviamente — e fortunatamente — ciò esula dagli obiettivi del presente libro. Il problema che si pone a questo punto è che il metamodello dello UML è descritto per mezzo dello UML stesso: un’istanza che definisce la classe (ciò rievoca il ricordo del compilatore del linguaggio C scritto in linguaggio C). Si tratta indubbiamente di una scelta molto elegante, ma che, come incoveniente, può creare seri problemi a tutti coloro che affrontano lo UML per la prima volta: è come andare a lezione di Inglese senza conoscerne una parola e assistere a spiegazioni esclusivamente in lingua impartite da un insegnante di madrelingua che non faccia altro che parlare e parlare. La trattazione più approfondita è riportata nell’appendice A: è stata inserita in questo contesto solo al fine di rendere i lettori consapevoli delle specifiche formali, nonostante la relativa illustrazione venga ripresa e argomentata nel corso di tutto il libro. Se i vari concetti dovessero risultare troppo enigmatici non c’è da preoccuparsi più di tanto: si può utilizzare tranquillamente lo UML senza averne capito il metamodello (si può tranquillamente progammare in C senza sapere come sia stato realizzato il relativo compilatore). I paragrafi successivi a quelli relativi al metamodello sono dedicati alla trattazione dei metodi di sviluppo (i famosi processi) sia da un punto di vista generale — illustrazione delle principali “dottrine” (Use case Driven, Architecture Centric e Iterative and Incremental) —, sia da un punto di vista particolare — presentazione dei processi più diffusi (The Unified Software Development Process, frutto del lavoro dei Tres Amigos, Use case Driven Object Modeling with UML, OPEN Process patrocinato dall’OPEN Consultorium e Object-Orient Software Process). Per questioni di completezza viene fornita una brevissima introduzione dell’eXtreme Programming (XP) con tutte le relative perplessità generate e la relativa evoluzione fornita dall’eXtreme Modelling o Agile Modeling, in cui, fortunatamente, l’attenzione viene nuovamente spostata sulla fase di modellazione. Il capitolo termina con l’immancabile parte dedicata ai tool disponibili in commercio, con una brevissima illustrazione, quanto più oggettiva possibile, dei relativi pregi e difetti.

La modellazione Ogni qualvolta, in una disciplina dell’ingegneria, vi sia la necessità di realizzare un “manufatto”, indipendentemente dalla dimensione e dal settore di interesse (una casa, un grattacielo, un particolare meccanismo, un ponte, un dipartimento di un’azienda, e così via) si procede cercando di realizzarne un modello. L’obiettivo è produrre, in tempi relativamente brevi e soprattutto a costi contenuti, una versione razionalizzata e semplificata del sistema reale che, tuttavia, consenta di evidenziarne l’aspetto finale e di studiarne prestazioni, affidabilità, comportamento, ecc…

UML dalla teoria alla pratica

3

I modelli, importantissimi in tutti i processi di sviluppo, trovano la loro più completa legittimazione nell’ambito di sistemi di dimensioni medie e grandi. In tali circostanze la complessità di questi sistemi nella loro interezza ne rende molto difficoltosa la comprensione. È quindi maggiormente avvertita la necessità di avvalersi di modelli in grado di descrivere, in maniera semplificata, sistemi comunque complessi. Si vedrà come lo UML, grazie alla sua organizzazione in “viste” (Views), risponda alla logica necessità della mente umana di concentrarsi, in ogni istante, su un numero limitato di aspetti del sistema ritenuti importanti per la particolare fase del processo di sviluppo, rimandando a momenti successivi l’analisi degli aspetti rilevanti per le altre viste. Oltre ai motivi già citati, i modelli sono basilari poiché, avvalendosi anche del feedback fornito dai committenti, permettono di definire i requisiti del sistema in maniera chiara e, con la cooperazione del proprio gruppo, di razionalizzarne il processo di sviluppo. I vantaggi che se ne ricavano sono diversi: adeguate analisi dei tempi, migliori stime dei costi, piani più precisi di allocazione delle risorse, distribuzioni più affidabili del carico di lavoro, e così via. Si riesce quindi a risparmiare tempo e denaro, a ridurre i fattori di rischio presenti in ogni progetto, a studiare la risposta del sistema a particolari sollecitazioni, e via di seguito. Nonostante il grande valore apportato all’ingengeria del software dall’utilizzo della modellazione, troppo spesso in molte organizzazioni, questa meravigliosa tecnica rimane ancora una chimera. A nessuno appartenente al settore dell’ingegneria edile verrebbe in mente di costruire interamente un grattacielo, per poi studiarne la risposta a sollecitazioni di carattere sismico. Il buon senso —risorsa purtroppo sempre rara — suggerisce di procedere con la realizzazione di una versione miniaturizzata sulla quale condurre tutti gli studi del caso. Non necessariamente un processo di modellazione genera un oggetto tangibile: talvolta si tenta di rappresentare un complesso reale per mezzo di eleganti sistemi di disequazioni e quindi l’intero modello si risolve in una raffinata astrazione. Tutto ciò che è fin troppo scontato in molte discipline dell’ingegneria non lo è nel settore dell’informatica. In molte organizzazioni — e qui non si pensi a un limite riscontrabile solo nelle piccole realtà — la produzione del software è ancora un’attività, per così dire, “artigianale” in cui il relativo processo di sviluppo del software prevede tre fasi: analisi dei requisiti, implementazione e test… Forse. Si provi a immaginare che cosa potrebbe accadere se si avviasse la progettazione di un ponte a partire da specifiche sommarie, magari comunicate verbalmente o, peggio ancora, se si partisse subito a costruirlo materialmente, magari affidandosi all’esperienza di qualche costruttore. Inconcepibile eh? Tutto ciò, benché possa sembrare “fuori dal mondo”, molto spesso è prassi comune nel mondo dell’ingengeria informatica. Malgrado siano stati versati fiumi d’inchiostro sull’argomento, e svariati gruppi di ricerca lavorino a tempo pieno per la definizione di nuovi standard di analisi, in molte organizzazioni, soprattutto italiane, anche di “comprovato” prestigio, tale attività è ancora considerata prettamente accademica, vezzo di giovani neolaureati perché, in ultima analisi, è di

4

Capitolo 1. UML: che cosa

è, che cosa non è

scarsa utilità nel mondo reale, dove l’obiettivo è produrre codice e l’esperienza è in grado di sopperire a tutto. L’inevitabile risultato è che spesso si procede cominciando paradossalmente da una delle ultime fasi del processo di ingegnerizzazione del software, ossia dalla stesura del codice (paragonabile alla costruzione del ponte di cui si parlava nell’esempio). Si inizia concentrandosi prematuramente sul modo in cui redigere il codice ed, eventualmente, alla fine si scrivono “due righe di analisi”, magari demandando il tedioso incarico all’ultimo arrivato nel team, al baldo giovane di turno. I rischi che si corrono sono noti a tutti e possono generare delle “veniali” anomalie note in letteratura informatica come “crisi del software”. In molte occasioni — l’autore potrebbe citarne diverse, ma preferirebbe evitare vitto e alloggio a carico dello Stato… — può accadere che, a progetto “ultimato e pronto per la consegna”, ci si accorga dell’errata architettura. Non è da escludere, nel peggiore dei casi, che, per via di uno sviluppo interamente incentrato sulla fase di codifica, e quindi privo della visione globale che solo un buon processo di modellazione può apportare, sia praticamente impossibile integrare i moduli prodotti in parallelo o che sia necessario costruire estemporanei moduli di interfacciamento o, ancora, che il sistema preveda percorsi così tortuosi da renderlo praticamente inutilizzabile. L’unico vantaggio, in questo modo di procedere è che si placano, almeno sul momento, gli stati d’ansia di taluni manager, i quali diventano improvvisamente irrequieti quando il team non genera codice, anche se nel frattempo si stanno affrontando delicate e impegnative fasi di analisi e/o disegno. L’alibi che viene rispolverato è lo scarso tempo a disposizione, dimenticando quanto incida il processo di manutenzione sull’intero ciclo di vita del software: il famoso “risparmio della massaia”. A onor del vero, gli eventuali fallimenti non sono sempre imputabili a tecnici/commerciali che “vendono” l’impossibile, di manager informaticamente poco lungimiranti o di capi-progetto “invisibili” (detti in gergo ghost) che un giorno sono a favore di una soluzione e il giorno dopo di quella opposta… Maschere e pugnali! Molto spesso — è qui doveroso procedere con un po’ di sana autocritica — gli stessi progettisti si sentono più a loro agio quando si deve affrontare un problema di programmazione, piuttosto che quando si deve affrontare la modellazione di un sistema. Probabilmente non suonerà nuova la frase “Che noia! Non ne posso più! Ma quando si comincia a scrivere del codice?”. Probabilmente si tratta anche dell’eredità di una dannosa abitudine: all’inizio i sistemi non erano così complessi, le metodologie di progettazione erano quasi inesistenti, i problemi e gli imprevisti dell’implementazione erano di quantità e entità tutto sommato limitate, tali da rendere quasi giustificabile la contrazione del processo di modellazione. Eppure da allora alcune cose dovrebbero essere cambiate… E pensare che, da oltre un decennio, in ambito accademico si afferma che “la programmazione è un incidente — anche molto piacevole se si vuole — dell’informatica”.

5

UML dalla teoria alla pratica

Figura 1.1 — La vignetta dell’altalena Quello che voleva l'utente...

Quello che ha capito il business analyst...

Quello che ha progettato l'architetto...

Quello che hanno realizzato i programmatori...

Come hanno installato gli installatori...

Quello di cui aveva realmente bisogno l'utente...

Nella realtà, troppo spesso si liquidano prematuramente le fasi di analisi e disegno del software per tuffarsi a testa bassa nella programmazione. Come nella celebre “vignetta dell’altalena”, tutti sono felici e contenti, almeno fino al momento di appenderla al ramo dell’albero. La temporanea soddisfazione viene vinta dalla triste constatazione che, a causa del modo superficiale in cui si è operato, è necessario operare un taglio ortogonale alla base del tronco dell’albero per poter ospitare il seggiolino. In altre realtà, può succedere di realizzare un’analisi adeguata, di produrre modelli sofisticati ad elevato livello di astrazione da fornire al team di “sviluppatori object disoriented”. Una situazione del genere potrebbe ingenerare qualche problema, magari per spiegare che con la frase “l’overloading dei metodi” non si intende sovraccaricare il processo di sviluppo di metodologie.

6

Capitolo 1. UML: che cosa

è, che cosa non è

Questo per evidenziare che gli strumenti da utilizzare in fase di analisi, purtroppo, possono anche dipendere, in gran misura, dal team con cui si lavora. Provate a fornire, sempre allo stesso team di cui sopra, un “class diagram” (diagramma delle classi)… Non si otterranno grossi risultati, neanche con l’esempio dell’entomologo (qualcuno ricorda la catalogazione degli insetti un tempo tanto cara a taluni manuali?). Premesso ciò, va comunque ribadito che il processo di analisi e modellazione è un’attività estremamente creativa, strettamente dipendente dalle capacità, dalla cultura e dall’esperienza dei singoli: insomma un vero e proprio bene soggettivo. Il bello — si fa per dire — è che non esiste un manuale delle soluzioni corrette con il quale confrontarsi alla fine del proprio studio: solo il tempo fornirà i verdetti. A tutti coloro per i quali tale introduzione, e in generale la domanda “why do we model?”, è più che scontata, felicitazioni di cuore! Significa che si trovano a lavorare presso la APBDM (leggasi Azienda Più Bella Del Mondo). Purtroppo le realtà lavorative non sono sempre così, e la dice lunga il fatto che anche i fondatori dell’UML abbiano deciso di dedicare all’argomento un intero paragrafo sia del libro [BIB01], sia delle specifiche ufficiali dello UML. Per terminare, sintetizzando al massimo il concetto si può dire che si modella essenzialmente per due motivi: • aumentare la propria comprensione sia di un problema sia di eventuali soluzioni ipotizzate; • comunicare.

Qualità di un modello Illustrato il concetto di modello — cui si ricorrerà ampiamente in tutto il libro — si ritiene opportuno evidenziarne brevemente le qualità peculiari utili nel contesto dello sviluppo di sistemi software. Le proprietà che deve possedere un modello dovrebbero essere tenute a mente e guidare la fase di progettazione del sistema, al fine di produrne versioni di migliore qualità. Un modello dovrebbe essere: • accurato: deve descrivere il sistema correttamente, completamente e senza ambiguità; • consistente: le diverse viste devono completarsi vicendevolmente per formare un insieme coerente (la completezza di un’erma bifronte…); • semplice: deve poter essere compreso, senza troppi problemi, da persone estranee al processo di modellazione;

UML dalla teoria alla pratica

7

• manutenibile: la variazione dello stesso deve essere la più semplice possibile.

Nascita e sviluppo di UML La babele dei metodi Uno degli obiettivi che hanno caratterizzato il lavoro dei Tres Amigos è stato realizzare un linguaggio di modellazione che unificasse e incorporasse le caratteristiche migliori dei linguaggi esistenti intorno agli anni Novanta. In particolare, come citato esplicitamente nel documento delle specifiche [BIB07], lo UML è scaturito principalmente dalla fusione dei concetti presenti nei metodi di Grady Booch, OMT (Object Modeling Technique di cui Rumbaugh era uno dei principali fautori) e OOSE (Object Oriented Software Engineering /Objectory di cui Ivar Jacobson era uno dei più importanti promotori). Agli inizi degli anni Novanta, ossia poco prima dell’avvio dei lavori per lo UML, i suddetti metodi erano probabilmente quelli più apprezzati, a livello mondiale, dalla comunità Object Oriented. Brevemente, il metodo di Booch, diventato famoso nel settore con il nome di “metodo delle nuvolette”, definisce una notazione in cui il sistema viene analizzato e suddiviso in un insieme di viste, ciascuna costituita dai diagrammi di modello. Il metodo non si limita a un linguaggio di modellazione, ma contiene anche un processo, in base al quale, il sistema viene studiato attraverso micro- e macroviste di sviluppo, secondo un classico schema incrementale e iterativo. Il metodo di Booch, a detta degli esperti, sembrerebbe molto efficace soprattutto in fase di disegno e di codifica, mentre presenterebbe qualche lacuna nelle varie fasi di analisi. L’OMT è un linguaggio di modellazione, sviluppato presso la General Electric, rilevatosi particolarmente efficace nella fase di esplorazione dei requisiti. In maniera speculare al metodo precedente, quest’ultimo sembrerebbe essere carente nella fase della formulazione delle soluzioni, mentre risulterebbe particolarmente accurato nell’esplorazione delle specifiche nel dominio del problema. L’OMT prevede che il sistema venga descritto attraverso un preciso numero di modelli che si completano vicendevolmente. Grazie alla sua accuratezza nella fase di analisi dei requisiti, il modello fornisce un valido ausilio anche alla fase di test di sistema. Infine i metodi OOSE e Objectory (in gran parte dovuti al lavoro di Jacobson) si basano, quasi interamente, sugli Use Case (casi d’uso). Poiché gli Use Case permettono di definire i requisiti iniziali del sistema così come vengono percepiti da un attore esterno allo stesso, i metodi OOSE e Objectory si sono dimostrati particolarmente efficaci nello spazio dell’analisi nel dominio del problema. Anche questi metodi forniscono una serie di informazioni e linee guida su come passare dall’analisi dei requisiti al disegno del sistema. Altri metodi degni di essere menzionati sono Fusion (elaborato presso i laboratori della Hewlett-Packard) e il metodo Coad/Yourdon (noto anche come OOA/OOD – Object

8

Capitolo 1. UML: che cosa

è, che cosa non è

Oriented Analisys / Object Oriented Design) che vanta il primato di essere stato uno dei primi metodi di analisi e disegno di sistemi Object Oriented. Negli anni intercorsi tra il 1989 ed il 1994, oltre ai metodi appena citati, considerati tra i più importanti, se ne contarono addirittura una cinquantina. Questi andarono ad alimentare quella che fu definita “guerra dei metodi”: la famosa “Babele” tra i modelli software con la conseguente incomunicabilità. Ovviamente ciascuno di questi presentava punti di forza, lacune, un proprio formalismo, una nomenclatura proprietaria e un peculiare processo di sviluppo. Tutto ciò finiva per minare alla base lo sviluppo di processi di progettazione più accurati e rendeva difficoltosa la realizzazione di opportuni tool di supporto. Altri inconvenienti, sempre frutto dell’incomunicabilità dei metodi, erano legati all’impossibilità di far circolare informazioni tra team di aziende diverse, alla difficoltà di rendere rapidamente produttive nuove risorse allocate al progetto e così via. In ultima analisi si finiva per fornire un pretesto ai vari team “analisi-disegno repellenti”.

Le motivazioni Il presente libro viene pubblicato dopo più di sette anni da quando Grady Booch e James Rumbaugh iniziarono i lavori alla Rational Software Corporation (1994). Probabilmente si è trattato della tecnologia giusta al momento giusto (ciò potrebbe far impallidire anche il buon Murphy), visto il grande successo riscosso e le prestigiose collaborazioni che hanno contributo alla sua realizzazione. Tra i partner si contano alcune delle aziende più importanti nel settore della Computer Science, tra le quali IBM, Oracle, Microsoft, Sun Microsystem, Texas Instruments, MCI Systemhouse, Hewlett-Packard, e così via. Il clamoroso successo riscosso è molto probabilmente dovuto alla necessità, avvertita da tutta la comunità Object Oriented, di disporre di un unico linguaggio di modellazione che mettesse finalmente termine alla proliferazione degli anni precedenti. La “guerra dei metodi” ha finito per costituire un serio limite all’evoluzione di una rigorosa metodologia di sviluppo del software. Le aziende che producevano tool di supporto al processo di sviluppo del software, i famosi CASE (Computer Aided Software Engineering), avevano notevoli problemi nello scommettere, sul linguaggio da adottare. Si trattava di affidare la politica di sviluppo dell’azienda al successo di un metodo: vera e propria roulette russa. L’idea di produrre plug-in per il supporto degli altri linguaggi risultava uno sforzo troppo impegnativo e costoso, vista la significativa disomogeneità degli stessi. Dal canto loro le aziende di Information Tecnhnology non riuscivano a orientarsi bene nello scegliere un metodo da utilizzare come standard interno. L’investimento per la produzione di processi proprietari e per l’addestramento del personale risultava così elevato, che lo spettro di una scelta sbagliata rendeva di fatto molto difficile qualsiasi decisione e quindi finiva per produrre una certa immobilità. Le grandi aziende finivano per crearsi un proprio processo proprietario, basato sul bagaglio delle esperienze accumulate, e quindi

UML dalla teoria alla pratica

9

assolutamente non supportato. Nelle imprese medio-piccole, molto spesso, tutto era demandato alla capacità e alla saggezza dei singoli architetti. Il risultato era una totale incomunicabilità tra i vari team di sviluppo e una discrepanza di metodi presenti in progetti sviluppati in tempi diversi e sotto la direzione di persone diverse. La confusione imperante generava diversi problemi anche ai singoli tecnici, desiderosi di utilizzare un linguaggio di modellazione che agevolasse il lavoro di tutti i giorni e che fosse supportato da tool commerciali. Tali situazioni prepararono il terreno ideale alla sollecita accettazione del progetto di unificazione dei metodi. Non a caso, il progetto avviato alla Rational Software Comporation suscitò l’immediato consenso di tutta la comunità OO. Altro fattore non trascurabile è che i fautori (i Tres Amigos) del progetto dello UML erano padri dei metodi più conosciuti ed utilizzati. Chiaramente l’accettazione e il supporto del progetto da parte di altri metodologi non coinvolti nel lavoro fu tutt’altro che scontato: ma questo è un altro discorso. Se a tutto ciò si aggiunge il fatto che il linguaggio è nonproprietario si può ben capire che la sua affermazione era garantita sin dall’inizio della sua invenzione. Si tratta infatti di un linguaggio completamente aperto, tanto che le aziende sono incoraggiate a integrarlo nei propri metodi di sviluppo del software e che le imprese produttrici di CASE possono liberamente produrre software di supporto allo UML.

La genesi Originariamente il lavoro fu iniziato dai Dos Amigos Grady Booch e James Rumbaugh, con l’intento di produrre un nuovo metodo, detto “metodo unificato” che raccogliesse il meglio dei metodi Booch e OMT-2, del quale Rumbaugh era stato uno dei principali promotori. Nel 1995 si unì a loro Ivar Jacobson, fautore del metodo denominato OOSE (Object Oriented Software Engineering): il terzetto si era così costituito. L’azienda che promuove il progetto è la Rational Software Corporation che, dal canto suo, provvede anche ad assorbire la Objective Systems, azienda svedese che aveva sviluppato e distribuito il software Objectory. A questo punto il quadro era completo e lo standard in lavorazione fu ribattezzato Unified Modeling Language. La prima versione, la celebre 1.0, fu disponibile nel gennaio 1997. Lo UML è uno strumento di analisi molto versatile, nato per risolvere le problematiche connesse con la progettazione Object Oriented del software, ma che ben si adatta a essere utilizzato negli ambienti più disparati. Esso è stato, per esempio, utilizzato alla Cadence per la produzione di un dispositivo di compressione vocale operante a livello di porta fisica. Ancora, una delle aziende fornitrici della US Navy ha utilizzato lo UML come linguaggio di progettazione di un sistema d’arma di nuova generazione. Un’azienda sanitaria si è avvalsa dello UML nella realizzazione di un modello per il trattamento dei pazienti, e così via. Visto l’enorme successo riscosso nell’applicazione industriale e nel mondo accademico e considerato il relativo riconoscimento a livello di standard (UML 1.0 è stato proposto

10

Capitolo 1. UML: che cosa

è, che cosa non è

Figura 1.2 — Diagramma dei componenti dell’evoluzione dello UML. Nel diagramma sono riportati due stereotipi, etichettati rispettivamente con i termini inglesi refine e document. Per ora basti sapere che uno stereotipo è una specializzazione di un elemento standard UML. In particolare refine è una versione della relazione di dipendenza il cui significato è piuttosto intuitivo. In generale l’asserzione che un oggetto B dipende da un oggetto A (visualizzata per mezzo di una freccia tratteggiata in direzione dell’oggetto A), indica che una variazione all’oggetto indipendente (A) produce la necessità di revisionare ed eventualmente aggiornare l’oggetto dipendente (B). Lo stereotipo document rappresenta una particolare versione dell’elemento package, che, in questa fase, può essere considerato come un contenitore di oggetti. La similitudine con le cartelle del file system è piuttosto immediata. Una directory (o cartella che dir si voglia) è in grado di contenere eventuali altre cartelle e file delle più svariate tipologie (audio, filmati, grafici, testo, e così via).

2002 - 2004 (Pianificate revisioni significative)

«document»

UML 2.0 «refine»

Q3 2001 (Pianificate minori revisioni)

«document»

UML 1.4 «refine»

Q3 1999

«document»

UML 1.3 «refine»

Q2 1998 «document»

UML 1.2

Q3 1997 OMG acquisice lo UML

Nessuna modifica significativa

«refine» «document»

UML 1.1

Unificazione dei precedenti modelli quali Booch, OMG, Objectory

all’Object Managment Group nel gennaio 1997), gli stessi ideatori, i Tres Amigos, dichiararono ormai conclusa la loro esperienza in questo ambito tanto da dedicarsi a nuove sfide… Allo stato attuale lo sviluppo dell’UML è affidato a una task force appartenente

11

UML dalla teoria alla pratica

all’OMG, la famosa RTF (Revision Task Force… però che fantasia), diretta da Chris Kobyrn. Obiettivo di tale gruppo è accogliere e analizzare suggerimenti provenienti dalle industrie, correggere inevitabili imperfezioni (bugs), colmare eventuali lacune e così via. Attualmente è disponibile la versione 1.4 (risultato dell’attività della seconda RTF) e si sta lavorando, alla versioni 2.0 come mostrato nel paragrafo seguente. L’evoluzione dello UML è mostrata in fig. 1.2, attraverso il diagramma dei componenti, uno degli strumenti messi a disposizione dal linguaggio.

UML 2.0 A questo punto viene da interrogarsi legittimamente su come stiano procedendo i lavori per la versione 2.0 (etichettata spaventosamente come “major revision”), che cosa sia lecito attendersi per i legami con la serie 1.x dello UML, e così via. Figura 1.3 — Decomposizione delle attività dello UML 2.0 in sottogruppi. Le linee culminanti con un diamante rappresentano relazioni di composizione dette anche whole-part (tutto-parte). In particolare, nel contesto oggetto di studio, indicano che la versione UML 2.0 è costituita dalla documentazione prodotta dalla RTF Infrastructure, Superstructure e OCL.

«document»

UML 2.0

«document»

Pianificata per Q4 2002

UML 2.0 Superstructure

«document»

«document»

UML 2.0 Infrastructure

UML 2.0 OCL

«document»

UML 1.4

Pianificata per 2004

12

Capitolo 1. UML: che cosa

è, che cosa non è

Molto probabilmente uno degli impegni più importanti consiste nel proseguire nella direzione, già intrapresa con la versione 1.4, dello studio delle necessità dei sistemi component-based. Ciò al fine di adeguare sempre più lo UML allo stato dell’arte della tecnologia e quindi di fornire soluzioni concrete alle esigenze dei tecnici coinvolti nella realizzazione di sistemi basati sui componenti. Si consideri il diagramma riportato nella figura precedente. Come annunciato in precedenza dallo OMG, la RTF per la versione 2.0 è stata suddivisa (questa volta ufficialmente) in tre parti.

Infrastruttura (Infrastructure RFP, OMG document ad/00-09-01) Come suggerisce il nome, obiettivo di questa RTF consiste nel curare miglioramenti relativi alla struttura base del metamodello UML, riguardanti il core, i meccanismi di estensione, le facility del linguaggio ecc. In particolare i relativi obiettivi sono di allineare l’architettura dello UML agli altri standard gestiti dall’OMG (quali per esempio il MOF, Meta Object Facility, Meta-data Interchange XMI), ristrutturare il linguaggio al fine di renderlo più comprensibile ed estenderlo mantenendo però la semantica e la notazione attuale. (Per ulteriori informazioni consultare l’appendice A).

Superstruttura (Superstructure RFP, OMG document ad/00-09-02) Obiettivo di questa RTF è elaborare proposte atte a incorporare le best practices in aree particolarmente sensibili, come lo sviluppo di sistemi basati sui componenti, i modelli architetturali, quelli eseguibili e dell’area business. In particolare è necessario studiare ulteriormente le soluzioni per: • la gestione degli eventi nei diagrammi delle attività; • migliorare l’illustrazione dell’incapsulamento con particolare riferimento ai diagrammi di stato e di sequenza; • ridefinire la semantica di relazioni come la generalizzazione, associazione, ecc. • il supporto dello sviluppo component-based di sistemi software; • il supporto per architetture a tempo di esecuzione, favorendone la descrizione del comportamento dinamico e l’eventuale rappresentazione gerarchica.

Object Constraint Language (OMG document ad/00-09-03) Il nome di questa RTF evidenzia chiaramente quale sia la relativa area di competenza: l’OCL. Gli obiettivi sono di presentare proposte atte ad aumentare il livello di specializzazione dell’OCL per l’utilizzo UML. Ciò dovrebbe semplificare il lavoro degli utenti, favorendo l’aumento di formalità dei vari modelli prodotti.

UML dalla teoria alla pratica

13

Da diverso tempo è in corso un dibattito all’interno dell’OMG stesso, teso a investigare la necessità di introdurre un’ulteriore Task Force con l’obiettivo di studiare l’area relativa allo scambio di modelli UML prodotti da tool diversi. Il nome di tale gruppo dovrebbe essere Diagram Interchange RFP. Sebbene in tale direzione ci sia già stato un impegno iniziale della seconda RTF, il relativo interesse è stato focalizzato principalmente sulla semantica e non sulla problematica specifica dello scambio concreto di diagrammi. Come ben noto a tutti i tecnici informatici, l’aumento del parallelismo nello svolgimento di attività presenta evidenti vantaggi — riduzione dei tempi grazie al lavoro in parallelo, maggiore specificità delle tematiche affrontate, ecc. — a fronte dei quali però, sono presenti tutta una serie di problematiche. In questo caso sono relative essenzialmente all’incremento del lavoro necessario sia per l’integrazione delle proposte avanzate dai singoli gruppi, sia per il mantenimento della coerenza tra le varie parti costituenti le specifiche finali. Pertanto, ulteriore preoccupazione della RTF per la revisione 2.0 dello UML è controllare lo sviluppo parallelo delle varie task force al fine di far sì che le varie componenti lavorino nella stessa direzione e producano la major revision preservando la coerenza e le strutture delle precedenti versioni mostratesi particolarmente efficaci.

Obiettivi dello UML Vengono di seguito descritti gli obiettivi che i Tres Amigos si sono prefissi di raggiungere attraverso lo sviluppo dello UML. La relativa descrizione è stata tratta direttamente dal documento ufficiale delle specifiche [BIB07]. Scopi principali dello UML sono: • Fornire agli utenti un linguaggio di modellazione visuale pronto ad essere utilizzato per sviluppare e scambiare modelli espressivi. Chiaramente, risulta molto importante che un linguaggio di analisi e disegno Object Oriented standard (OA&D Object Analysis & Design) fornisca tutti i concetti necessari alla sua diretta utilizzazione nelle fondamentali attività del processo di produzione di un modello software. Lo UML ha consolidato un insieme nucleare di concetti di modellazione, necessari in molte applicazioni reali, presenti in molti metodi di progrettazione e tool di modellazione disponibili sul mercato. • Fornire meccanismi di estensione e specializzazione al fine di accrescere i concetti presenti nel nucleo. Uno dei punti fermi che ha guidato il lavoro dei Tres Amigos è stato realizzare un linguaggio di modellazione quanto più generale possibile, in modo da non relegarne l’utilizzo a un dominio specifico. In prima analisi, si sarebbe potuto raggiungere tale obiettivo corredando lo UML di un numero di elementi molto elevato, in modo tale da fornire gli strumenti specificamente necessari a tutte le esigenze dei vari ambienti e delle varie architetture. Si sarebbe potuto, ad esem-

14

Capitolo 1. UML: che cosa

è, che cosa non è

pio, includere nello UML tutta una serie di elementi specifici per la modellazione di sistemi CORBA, EJB, ecc. Tutto ciò però avrebbe sicuramente portato a un conflitto con un’altra esigenza, altrettanto importante di quella dell’applicazione dell’UML in campi specifici: mantenere il linguaggio il più semplice possibile, al fine di massimizzarne l’accettazione da parte della comunità Object Oriented. La soluzione a queste esigenze contrastanti è stato realizzare un nucleo del linguaggio basilare, valido per ogni ambiente, corredato da una serie di strumenti che consentano di aggiungere nuova semantica e sintassi al linguaggio stesso per utilizzi specifici dello UML. Il risultato è che i concetti fondamentali del linguaggio vengono utilizzati così come sono, senza ulteriori estensioni, per la maggior parte del sistema: vige, anche in questo contesto, la famosa legge empirica dell’80/20: l’80% del progetto utilizza il 20% dei concetti dello UML. Per quanto riguarda la restante parte e per progetti molto specifici è possibile sia aggiungere nuovi concetti e notazioni, al fine di modellare opportunamente le aree non coperte dal nucleo dello UML, sia specializzare quelle già esistenti per particolari domini dell’applicazione. • Supportare specifiche che risultino indipendenti da un particolare linguaggio di programmazione o processo di sviluppo. Lo UML è stato concepito con l’obiettivo di supportare plausibilmente tutti i linguaggi di programmazione. Quindi è stato reso idoneo all’utilizzo nei più svariati ambienti produttivi, che ricorrono a una vasta gamma di tecnologie, e inoltre si è fatto in modo di preservare la sua adattabilità a sviluppi futuri. Chiaramente lo UML risulta particolarmente indirizzato a linguaggi di programmazione Object Oriented. Pertanto parte della sua efficacia viene attenuata da linguaggi che non realizzano tale paradigma. Come solitamente succede, molto dipende dall’utilizzatore. Per esempio si può programmare in C seguendo il paradigma Object Oriented, al limite aiutandosi con un sistema evoluto di macro (le prime versioni di compilatori C++ altro non erano che preprocessori del linguaggio C, C-front). Si è altresì cercato di rendere lo UML idoneo a essere utilizzato con molteplici metodi di sviluppo: può essere proficuamente adoperato per esprimere i “prodotti” generati dalle varie fasi di cui ogni processo è composto. • Fornire le basi formali per comprendere il linguaggio di modellazione. Un linguaggio di modellazione deve necessariamente essere preciso e al contempo offrire una complessità contenuta. I formalismi non dovrebbero ricorrere all’utilizzo di nozioni matematiche di basso livello e distanti dal dominio del modello. Tali insiemi di nozioni teoriche e di definizioni operative risulterebbero essere una forma diversa di programmazione e implementazione, non idonea a tutte le fasi del processo di sviluppo del software. Lo UML fornisce una definizione formale del del modello, utilizzando un metamodello espresso attraverso il diagramma delle classi dello stes-

UML dalla teoria alla pratica

15

so UML. Esso presenta pertanto un adeguato formalismo e una complessità molto contenuta. • Incoraggiare la crescita del mercato dei tool Object Oriented. L’esistenza di un linguaggio di modellazione standard doveva — e così è stato — eliminare tutti quei problemi analizzati nel paragrafo dedicato alle motivazioni dello UML: difficoltà per le aziende di selezionare standard da adottare, frustrazione da parte dei tecnici di fronte alla proliferazione dei metodi, difficoltà di circolazione dei vari modelli prodotti, impedimenti per le aziende creatrici di CASE di individuare metodi da automatizzare, ecc. Pertanto, una volta rimossi i problemi alla base dello sviluppo formale dell’OO, venne incrementata la crescita del mercato dei prodotti commerciali per il supporto dello UML. Si trattò di un vero e proprio “toccasana” per tutta la comunità Object Oriented. • Supportare concetti di sviluppo di alto livello come componenti, collaborazioni, framework e pattern. Ulteriore obiettivo dello UML fu la chiara definizione dei concetti succitati poiché ciò è essenziale per trarre i massimi benefici dal paradigma Object Oriented e in particolare da quello della riusabilità. • Integrare le best practices. Un’altra motivazione alla base dello UML è stata integrare le pratiche mostratesi vincenti nella realizzazione di progetti reali. Ciò è risultato particolarmente utile al fine di offrire sin da subito un linguaggio maturo che inglobasse il meglio delle tecniche di comprovata validità.

Che cosa è lo UML Secondo le specifiche [BIB07] lo Unified Modeling Language è un linguaggio per specificare, costruire, visualizzare e documentare manufatti sia di sistemi software, sia di processi produttivi e altri sistemi non strettamente software. UML rappresenta una collezione di best practices di ingegneria, dimostratesi vincenti nella modellazione di vasti e complessi sistemi. Lo UML permette di visualizzare, per mezzo di un formalismo rigoroso, “manufatti” dell’ingegneria, consentendo di illustrare idee, decisioni prese, e soluzioni adottate. Tale linguaggio favorisce, inoltre, la divulgazione delle informazioni, in quanto standard internazionale non legato alle singole imprese. In teoria, un qualunque tecnico, di qualsivoglia nazionalità, dipendente della più ignota delle software house, con un minimo di conoscenza dell’UML dovrebbe essere in grado di leggere il modello del progetto e di comprenderne ogni dettaglio senza troppa fatica e, soprattutto, senza le ambiguità tipiche del linguaggio naturale. Come al solito qualche problema può sempre emergere, ma si tratterebbe comunque di problemi di poca entità. Un conto è non comprendere qualche

16

Capitolo 1. UML: che cosa

è, che cosa non è

dettaglio, un altro è non comprendere assolutamente cosa voleva realizzare l’autore; è situazione tipica di alcuni progetti ribattezzati “temini di scuola superiore”: si tratta di documenti tipicamente corposi — quando non si è sicuri è meglio scrivere tanto — in cui il 99% del contenuto è relativo alla descrizione, assolutamente non organica, di nozioni teoriche copiate da appositi libri e il restante 1% a embrioni di soluzioni. I vantaggi che derivano dal poter disporre di un modello del sistema sono notevoli e fin troppo evidenti. Basti pensare alla non indifferente semplificazione del processo di manutenzione che, da solo, tipicamente incide più del 50% nel ciclo di vita dei sistemi software ben progettati; alla possibilità di allocare risorse aggiuntive in corso d’opera, riducendo il rischio che ciò diventi controproducente (anche si tratta spesso di un finto rimedio a cui si ricorre in prima istanza: sarebbe un po’ come pensare che, poiché una donna impiega nove mesi per dare alla luce un bambino, se si “allocassero” tre donne, ecco che lo stesso figlio potrebbe essere “disponibile” in tre…), e così via. Disporre di un linguaggio per descrivere un sistema costringe il progettista stesso ad analizzare, con maggior minuzia, aspetti del sistema, anche di un certo rilievo, i quali, viceversa, potrebbero incautamente venir trascurati da un’analisi non molto rigorosa. Per ciò che concerne l’utilizzo dello UML per “specificare”, bisogna tener presente che in questo contesto l’espressione si riferisce alla possibilità di realizzare modelli completi, precisi e non ambigui. Lo UML dispone di tutti i meccanismi necessari per la specifica di qualsiasi dettaglio ritenuto rilevante in ogni fase del ciclo di vita del software e quindi, in ultima analisi, per produrre modelli accurati. Lo UML, permette di realizzare modelli che si prestano ad essere implementati con diversi linguaggi di programmazione, sebbene risulti particolarmente efficace per la progettazione di sistemi Object Oriented. In effetti è possibile realizzare un mapping esplicito tra un modello UML e un linguaggio di programmazione. Chiaramente, tale legame risulta più immediato per i linguaggi fortemente basati sul paradigma Object Oriented, quali C++, Java, Small-Talk, Ada, ecc. Sul mercato sono presenti diversi tool, in grado di generare codice a partire dal relativo modello, sia interattivamente durante la fase di disegno, sia su richiesta. L’esistenza di queste funzionalità, sebbene ancora non del tutto mature, dovrebbe far capire che l’implementazione è veramente un dettaglio del disegno, specie con linguaggi come Java. La generazione automatica di una prima implementazione del sistema risulta particolarmente utile quando il modello deve essere realizzato parallelamente (cioè sempre!), poiché fornisce, ai diversi sviluppatori, lo scheletro — eventualmente con qualche metodo implementato — delle classi fondamentali del sistema che dovrebbero presentare un’interfaccia stabile e ben definita. Sebbene alcuni tools tentino, in alcuni casi particolari, di dettagliare determinati metodi con il codice appropriato, è probabilmente ancora un po’ prematuro azzardarsi a utilizzare appieno tale funzionalità, a meno che non si tratti di meri metodi get / set di proprietà.

UML dalla teoria alla pratica

17

Il mapping tra modello e linguaggio di programmazione permette anche la realizzazione di funzioni di reverse engineering: fornendo a un opportuno tool i codici sorgenti o, talune volte anche quelli compilati, questo è in grado di ricostruire a ritroso il modello fino, ovviamente, alla fase di disegno. Purtroppo non si è ancora riusciti a realizzare un tool in grado di ricostruire i requisiti del cliente: un vero peccato! Il processo diretto (engineering) e quello inverso (reverse engineering) determinano quello che in gergo viene definito round-trip engineering. Nel mondo ideale, la funzione di reverse non dovrebbe mai venir utilizzata… In quello reale è invece molto apprezzata, e tutto dipende dall’uso che se ne fa. In fase di disegno, probabilmente non è opportuno disegnare tutto dettagliatamente; verosimilmente è opportuno lasciare qualche margine ai programmatori (tutto in funzione delle loro capacità). Sono ritenute assolutamente naturali e accettabili modifiche del modello in fase di codifica, fintantoché queste non stravolgano il modello stesso. Durante la fase di codifica può anche accadere di accorgersi che una data libreria non funziona come dovrebbe, o che c’è qualche lacuna nel modello, o che risulta opportuno cambiare qualche strategia al fine di ottenere un codice più efficiente… Tutto ciò è normalissimo. Tuttavia, nell’eventualità che le modifiche generino uno stravolgimento del modello, piuttosto che procedere nell’implementazione sarebbe forse opportuno introdurre un’opportuna iterazione della fase di disegno e successiva codifica. Per ciò che concerne l’utilizzo dello UML per la fase di documentazione, la tentazione di considerare il tutto fin troppo ovvio è forte; ma la famosa vocina dell’esperienza si fa sentire. Un progetto, per quanto ben congegnato, potrebbe perdere gran parte del suo fascino se poco documentato, o addirittura potrebbe finire per non essere compreso e in futuro non venire correttamente aggiornato. Per terminare, lo UML fornisce sia dei meccanismi molto formali, sia del testo libero da aggiungere, ogni qual volta lo si ritenga necessario, a parti ritenute poco chiare o particolarmente complesse, al fine di aumentarne il livello di dettaglio.

Che cosa non è lo UML Riportata la definizione formale del linguaggio, si ritiene opportuno ribadirne il concetto da un altro punto di vista: che cosa lo UML non è. Potrebbe sembrare ridondante e invece probabilmente è il caso del melius abundare quam deficere: c’è sempre il “distratto” di turno. In primo luogo, sebbene ciò dispiaccia a molte persone, lo UML non è un linguaggio di programmazione visuale, anche se questa sarà probabilmente la sua naturale evoluzione). In ogni modo, chi ritiene che la modellazione sia inutile in quanto coincidente con la codifica sappia che lo UML non gli sarà di particolare aiuto. Il linguaggio di modellazione definisce un metamodello semantico ma non un tool di interfacciamento o di memorizzazione, e nemmeno un modello in grado di essere eseguito.

18

Capitolo 1. UML: che cosa

è, che cosa non è

La documentazione dello UML include molti suggerimenti utili alle aziende che si occupano di realizzare dei tool di supporto, ma non stabilisce ogni necessario particolare. Per esempio, non vengono presi in considerazione dettagli quali la selezione dei colori da utilizzare nella costruzione di diagrammi, la navigazione all’interno del modello, le modalità con cui memorizzare le informazioni, e così via. Infine lo UML non è un processo di sviluppo, sebbene alcuni tecnici tendano a confondere i ruoli: lo UML è “solo” un linguaggio di modellazione e, in quanto tale, si presta a rappresentare i prodotti generati nelle varie fasi di cui un processo è composto. Pertanto lo UML, contrariamente ai processi, non fornisce alcuna direttiva su come fare evolvere il progetto, né attraverso quali fasi farlo transitare, né tantomeno su quali siano i manufatti da produrre, o su chi ne sia responsabile ecc.

Metamodello e meta-metamodello Definizioni di metamodello e meta-metamodello Contrariamente a convinzioni comuni in molti tecnici, lo Unified Modeling Language, non è unicamente una notazione standard per la descrizione di modelli Object Oriented di sistemi software; si tratta bensì di un metamodello definito rigorosamente che, a sua volta, è istanza di un meta-metamodello definito altrettanto formalmente. Si provvederà ora a illustrare brevemente i succitati concetti e le inerenti relazioni, senza avere la pretesa di fornirne una descrizione dettagliata, per la quale si rimanda sia all’apposita Appendice A, sia a testi specializzati e in particolare al documento di specifica dell’OMG. Ancora una volta si tratta di una materia così vasta che, probabilmente, meriterebbe un libro a sé stante. Ma l’argomento ha un’importanza e un fascino così elevato che sarebbe stato un vero peccato escluderlo dalla trattazione del presente libro. Un metamodello è un modello a sua volta istanza di un meta-metamodello, fruibile per esprimere una sua istanza di modello: l’UML (metamodello) permette di realizzare diversi modelli Object Oriented (modelli definiti dall’utente). Il metamodello dello UML definisce la struttura dei modelli UML. Un metamodello non fornisce alcuna regola su come esprimere concetti del mondo OO, quali ad esempio classi, interfacce, relazioni e così via, ma esso rende possibile avere diverse notazioni che si conformano al metamodello stesso. Per esempio, in UML una classe viene rappresentata da un rettangolo suddiviso in tre sezioni. Un’altra notazione (in modo molto originale ma poco appropriato) potrebbe stabilire di rappresentare il concetto di classe per mezzo di un apposito pseudo-codice e di visualizzare le relazioni graficamente. Entrambe le notazioni, se definite rigorosamente, rappresentano istanze del metamodello, e sono quindi valide a tutti gli effetti. Un metamodello può specificare che una relazione di associazione deve avere due o più terminazioni di associazione, ma non prescrive assolutamente che essa vada rappresentata per mezzo di una linea che connette due classi.

UML dalla teoria alla pratica

19

Così come avviene nella relazione che lega le classi agli oggetti — una volta definita una classe si possono avere svariate istanze della stessa (oggetti) — analogamente è possibile progettare un numero infinito di varianti dello UML (istanze del metamodello). Al fine di evitare un’ennesima proliferazione di dialetti — sembrerebbe che non appena si lasci un minimo spazio di azione, i generatori naturali di entropia tendano a prendere il sopravvento — lo OMG ha anche definito la notazione standard conforme al metamodello, comunemente denominata UML. A dire il vero le cose non sono proprio andate così; si è piuttosto utilizzata una metodologia Bottom-up: partendo da un oggetto concreto (lo UML) e procedendo per astrazioni successive, è stata definita la classe (il metamodello). Nelle sue prime apparizioni ufficiali lo UML era composto semplicemente da una notazione grafica, fruibile per rappresentare modelli di sistemi Object Oriented. Quando poi è giunto il momento della sottomissione allo OMG, per il riconoscimento ufficiale di standard (gennaio 1997), si è reso necessario conferirgli una veste più formale. Così a partire dalla versione 1.1 lo UML definisce rigorosamente un metamodello e la notazione atta alla formulazione di modelli Object Oriented conformi a esso. Fin qui quasi tutto chiaro… Forse. Volendo però procedere oltre, i concetti diventano un po’ più articolati. In effetti, così come in molti linguaggi di programmazione Object Oriented esiste il concetto di metaclasse (classe le cui istanze sono costituite da altre classi), anche il metamodello è un’istanza di un’entità di livello di astrazione superiore: il meta-metamodello. Un meta-metamodello è un modello che definisce un linguaggio per esprimere un metamodello. Chiaro no? La relazione tra il meta-metamodello e il metamodello è paragonabile a quella esistente tra il metamodello e il modello. Lo UML è definito in termini di un meta-metamodello denominato MOF: Meta Object Facility. Nella fig. 1.4 viene illustrato graficamente quanto emerso fino a questo punto: relazioni esistenti tra il meta-metamodello, il metamodello e il modello dell’utente. Scorrendo il diagramma dall’alto verso il basso si assiste a una graduale diminuzione del livello di astrazione: se si fosse deciso di visualizzare un ulteriore livello, si sarebbero trovate entità istanze della classe del modello dell’utente: oggetti. Se per esempio si realizzasse un prototipo di un sistema di commercio elettronico (i famosi siti .com), a livello del modello realizzato dall’architetto, si troverebbero classi, opportunamente relazionate tra loro, del tipo: Categoria, SottoCategoria (probabilmente l’organizzazione in Categorie si presta ad essere rappresentata per mezzo del pattern Composite), Prodotto, Utente, Profilo, e così via. Se poi si volesse visualizzare l’ulteriore livello, l’istanza dell’user model, bisognerebbe inserire oggetti, istanze di tali classi, come per esempio il Prodotto di codice X, avente prezzo Y, della categoria Z, e così via. Per avere un’idea più chiara, si consultino la tab. 1.1 e il diagramma riportato nella fig. 1.5. Come si può notare, nel modello di analisi compare una classe denominata Ordine appartenente al dominio del problema. La classe è un’istanza della metaclasse, presente nel package UML metamodello e contiene attributi ed operazioni che, a loro volta, sono

20

Capitolo 1. UML: che cosa

è, che cosa non è

Figura 1.4 — Meta-metamodello, metamodello e modello UML.

«document»

MOF - meta-metamodel

Specifica meta-meta classi per il metamodello.

«instanceOf»

«document»

MOF - meta-metamodel

Specifica meta classi per il metamodello. Esempio: Classe

«instanceOf»

«document»

MOF - meta-metamodel

Specifica classi per il Modello UML dell'architetto.

istanze rispettivamente delle metaclassi Attributi e Operazioni. Se anche in questo caso si fosse deciso di visualizzare un ulteriore livello si sarebbero trovate le istanze delle classe Ordine, ossia oggetti del tipo: 00000312 : Ordine.

Linguaggi e processi A questo punto è opportuno chiarire la relazione che lega i linguaggi di modellazione, con particolare riferimento allo UML, ai metodi di sviluppo del software: spesso si tende a confonderne le relative funzioni. Lo UML è un linguaggio di progettazione e, come tale, è “solo” parte di metodi più generali per lo sviluppo del software: lo UML è un formalismo utilizzato dai processi per realizzare, organizzare, documentare i prodotti generati dalle fasi di cui il processo si compone. Un metodo, tra i vari princìpi, è formato dalle direttive che indicano al progettista cosa fare, quando farlo, dove e perché. Un linguaggio invece è carente di questo aspetto.

21

UML dalla teoria alla pratica

Tabella 1.1 — La scala di astrazioni: dal meta-metamodello agli oggetti utente.

livello

M3

M2

M1

M0

descrizione

elementi MetaClassi MetaAttributi MetaAssociazioni Mof::Classi Mof::Attributi Mof::Associazioni

MOF Core

Definizione degli oggetti utilizzati per specificare metamodelli.

Metamodello

Metamodello definito in termini degli elementi appartenenti al core del MOF.

UML: Classi, Attributi, AssociazioniData Warehousing: Base di Dati, Tabelle, ennuple,…

Modello

Il modello è la descrizione delle informazioni del dominio del problema, effettuata attraverso lo UML. A dir il vero è molto di più…

UML classi: Cliente, Item, Ordine, …UML associazioni: Prodotti ordinati(Business Entities and processes)

Oggetti utente

Istanze degli elementi del modello.

Cliente #00DNLMDRCliente #01BRZRIOItem #00HSFRNTItem #01HITECHOrdine #0000003Associazione tra Ordine #0000233 e Item #00HSFRNT

Va da sé che i processi svolgono un ruolo assolutamente fondamentale nella realizzazione di progetti di successo, e non solo in campo informatico. Talvolta, nonostante tutti i migliori prerequisiti, si assiste al fallimento di un progetto perché lo sviluppo non è stato guidato da un processo ben definito o perché non è stato ben gestito: spesso i processi costituiscono la differenza tra progetti iperproduttivi e progetti fallimentari.

22

Capitolo 1. UML: che cosa

è, che cosa non è

Le varie ipotesi di processi di produzione del software, in ultima analisi, rappresentano tentativi di conciliare la produzione della “testa” (analisi e progetto) con quella delle “mani” (codifica e test). Il tempo, la quarta dimensione in cui siamo imprigionati, chiaramente, svolge un ruolo importante nella produzione di qualsiasi oggetto o servizio, ma scorre in modo diverso a seconda dell’attività nella quale viene impegnato. Non esiste il processo universalmente valido o che risulti ottimale per ogni progetto. Alcune volte è necessario conferire particolare attenzione ai requisiti del cliente, altre all’architettura da utilizzare, altre volte ancora è necessario tenere sotto controllo i rischi più rilevanti del progetto e così via. Il processo, per sua natura, deve essere rapportato all’organizzazione, alla relativa cultura tecnologica, al particolare dominio del problema, alle capacità del team, e così via. Figura 1.5 — Esempio delle relazioni ai vari gradi di astrazione tra i modelli UML.

«metamodel»

UML metamodel Class

Association

«metamodel»

UML metamodel Instance of MetaClass

Instance of MetaAssociation

Instance of MetaClass

IDL for UML IDL for Class

IDL for Class-Association

IDL for Association

UML dalla teoria alla pratica

23

Un linguaggio, è uno strumento — nel caso dello UML è un insieme di strumenti — da utilizzarsi per realizzare, descrivere e documentare i modelli prodotti nelle varie attività di cui si compone un modello. Un linguaggio è costituito da una sintassi, una semantica e dei paradigmi. Nel linguaggio naturale la sintassi è costituita sia dalle norme che regolano il coordinamento delle parole (in UML, i simboli dei diagrammi) nelle proposizioni sia da quelle che specificano come organizzare tali proposizioni all’interno di un periodo (in UML, come combinare i simboli, quali possono essere associati e quali no, come, ecc.). La semantica si occupa del significato delle parole (simboli), considerate sia singolarmente, sia nel contesto nel quale vengono utilizzate (significato dei simboli in un particolare diagramma). Infine, un particolare paradigma potrebbe contenere le direttive atte a conferire determinate forme, variabili a seconda dei casi, aggiungendo quindi chiarezza e precisione ad alcuni elementi della lingua (in UML, come realizzare modelli chiari, leggibili, eleganti, ecc.). In quanto linguaggio di modellazione, lo UML è quindi indipendente dal processo che si utilizza per lo sviluppo del software. I processi più popolari nelle aziende di produzione del software tipicamente si fondano su uno dei seguenti aspetti: Use Case Driven (il processo guidato dalla vista dei casi d’uso, ossia dai requisiti utente), lo Architecture Centric (incentrato sull’architettura), lo Iterative and Incremental (iterativo e incrementale). Come spesso accade nel mondo dell’informatica, è difficile trovare un metodo da applicare alla pratica così come previsto dalla teoria. Molto più frequentemente il processo è un ibrido generato dalla fusione di caratteristiche presenti anche in altri metodi. Per esempio, non è infrequente il ricorso a un processo basato sui casi d’uso, che preveda il raggiungimento del modello finale attraverso un processo iterativo e incrementale e che, a ogni iterazione, sviluppi il relativo contenuto semantico. La Rational, visto il grande successo riscosso con lo UML, ha sostenuto il progetto volto a diffondere la relativa standardizzazione anche al mondo dei processi (consultare il paragrafo dedicato allo Unified Software Development Process). Si è tentato di applicare la strategia dimostratasi vincente con lo UML al settore dei processi, affidando l’incarico alle stesse persone: i Tres Amigos. Il risultato è stato denominato USDP (Unified Software Development Process) [BIB08] poi confluito nel RUP (Rational Unified Process). In questo caso, nonostante l’enorme successo riscontrato, l’accettazione dell’USDP come standard è decisamente più difficoltosa, per via di tutta una serie di problemi, non ultimi i retaggi culturali radicati nelle aziende, l’utilizzo storico di standard interni e internazionali e così via. Come già asserito, l’applicazione di processi dipende molto dalle caratteristiche peculiari delle organizzazioni: quelli che possono risultare vincenti in alcune potrebbero rivelarsi assolutamente fallimentari in altre. Probabilmente la necessità di disporre di un processo unificato non era e non è così avvertita contrariamente a quanto si è verificato con il linguaggio. I tre processi elencati precedentemente si differenziano essenzialmente per via del particolare aspetto, ritenuto fondamentale per l’intero ciclo di vita. Il primo (Use Case Driven)

24

Capitolo 1. UML: che cosa

è, che cosa non è

probabilmente il più popolare, come suggerito dal suo stesso nome basa tutta la progettazione sugli use case, utilizzati per stabilire il comportamento del sistema, per verificarne e validarne l’architettura, per eseguire la fase di test, ecc. Il secondo invece (Architecture Centric), pone in primo piano la progettazione dell’architettura del sistema, considerata come il manufatto primario per la concettualizzazione, la costruzione, e la gestione dell’evoluzione del sistema in costruzione. Infine l’ultimo (Iterative and Incremental) è basato sull’iterazione incrementale del modello. Un processo di sviluppo iterativo comporta la realizzazione di versioni successive del sistema, e un processo incrementale richiede l’apporto di continui arricchimenti dello stesso per ottenere le relative nuove versioni. La fusione delle due modalità operative genera un’altra metodologia definita risk-driven in quanto ogni nuova versione del sistema è incentrata sulla risoluzione del fattore di rischio ritenuto più pericoloso nella precedente versione.

Il famoso gap: mind the gap Nel libro Use Case Driven Object Modeling with UML [BIB05] viene proposta un’immagine, simile a quella in fig. 1.6, che di per sé chiarisce, più di molte parole, articoli e libri, uno dei principali problemi della progettazione: il salto del gap, il passaggio dalle specifiche del cliente alla realizzazione del modello di disengo del sistema (questi argomenti vengono trattati in maniera approfondita nel relativo capitolo). Alcune persone, confortate anche dall’impostazione di taluni tool CASE, confidano sul fatto che il problema sia passare dal disegno alla codifica: niente di meno vero! Tale passaggio è abbastanza diretto a patto di non avere i famosi programmatori “object disoriented”. Il problema reale è esprimere in termini di componenti software sia il dominio del problema sia l’infrastruttura necessaria per sostenere il tutto. Per quanto concerne Figura 1.6 — Il gap della progettazione del software.

?

UML dalla teoria alla pratica

25

l’infrastruttura, le nuove architetture (come per esempio J2EE), cercano di semplifcare sempre di più il problema della realizzazione dell’infrastruttura). Chi scrive ha avuto modo di constatare quanto sia gravoso superare il famoso gap: sperimentandolo in prima persona attraverso la realizzazione di progetti Object Oriented di grandi dimensioni, attraverso il preziosissimo feedback maturato nell’ambito della collaborazione con la rivista web MokaByte e, infine, nell’ambito dell’insegnamento di UML. Il problema tipo riscontrato è che i neofiti, pur avendo compreso come funzioni lo UML, eventualmente con qualche lacuna, non lo sanno poi applicare alla pratica. Come si può ben intuire, ancora una volta il problema è strettamente connesso all’esperienza e al processo utilizzato, piuttosto che al linguaggio di per sé. Un buon supporto è rappresentato dai vari libri dedicati ai Design Pattern e ai processi. È il caso di sottolineare che, così come conoscere un linguaggio non significa necessariamente essere bravi programmatori, conoscere lo UML non significa assolutamente essere in grado di produrre buoni disegni né tantomeno essere bravi architetti. Un valido aiuto viene fornito dal processo il quale deve: 1. provvedere le linee guida e il relativo ordine delle attività che il team deve svolgere; 2. specificare quali manufatti debbano essere prodotti in ogni fase; 3. fornire direttive allo sviluppo sia dei singoli programmatori sia del team; 4. offrire dei criteri per controllare e misurare i prodotti e le attività del progetto. La trattazione di tale argomento necessiterebbe da solo di un libro a sé stante: come si suol dire, esula dai fini del presente libro. Ciò nonostante nei seguenti paragrafi si tenterà di affrontarlo, seppur molto parzialmente, consigliando lo studio dei libri [BIB05] e [BIB06] a tutti coloro che desiderassero approfondire l’argomento.

Processi Use Case Driven I sistemi software, in primo luogo, dovrebbero essere realizzati per soddisfare specifiche esigenze dell’utente anche se, a volte, specie nel Belpaese, il fine è decisamente meno nobile… Logica conseguenza è che, per produrre un sistema il quale effettivamente realizzi gli obiettivi per il quale è stato finanziato, è indispensabile chiarire la prospettiva del cliente, le relative necessità, le aspirazioni più o meno recondite, ecc.: tutto ciò che comunemente viene indicato con “specifiche utente”. Occorre inoltre tenere bene a mente queste ultime durante tutto il ciclo di vita del software: l’utente non è sempre l’idiota che inserisce i dati nelle caselle di testo e successivamente preme il tasto OK. I team tecnici, tipicamente,

26

Capitolo 1. UML: che cosa

è, che cosa non è

tendono a non provare sufficiente rispetto per le esigenze degli utenti finali. Discorso a parte andrebbe fatto per taluni committenti che rivendono i sistemi limitandosi ad aggiungere un paio di zeri prima della virgola nella fattura: ci sono 99 possibilità su 100 che raccontino… inesattezze. Come si vedrà nel capitolo dedicato ai casi d’uso, non sempre è così semplice tenere conto di tutte le specifiche utente, per una serie di motivi: difficoltà di stabilire una piattaforma di intesa comune, background e prospettive diverse, esigenze contrastanti, ecc. Focalizzare, fin dalle primissime fasi del ciclo di vita del software l’aderenza dei vari modelli alle richieste del cliente, continuando a monitorarle, può risultare una mossa vincente. È da tener presente che l’utente del sistema (attore) non è sempre un operatore umano: può essere un altro sistema o un qualsiasi dispositivo fisico. In generale si tratta di un’entità esterna al sistema che interagisce con esso. Se, per esempio, si volesse realizzare un sistema di elaborazione automatica e smistamento di documentazione in formato digitale, il quale, a partire da modelli di documenti riesca a completarne il contenuto, richiedendo ai vari enti le informazioni mancanti (dati anagrafici, penali, pensionistici, ecc.), i vari attori del sistema sarebbero, oltre che diversi operatori, altri enti in grado di produrre, completare, ricevere e inoltrare file di documentazione. Se si volesse poi rendere possibile memorizzare tali documenti, corredati da opportune firme digitali in apposite smart card di proprietà degli utenti, anche queste risulterebbero attori. Tipicamente un sistema, a fronte di un’iterazione avviata da un utente, risponde eseguendo una sequenza di azioni in grado di fornire all’attore stesso i risultati desiderati: produzione del documento, completamento dello stesso con l’aggiunta dei dati richiesti, inoltro verso opportuno destinatario, e così via. Tali interazioni costituiscono quello che viene denominato “caso d’uso” (Use Case), si tratta cioè di una parte delle funzionalità del sistema che fornisce all’utente determinati valori. La sommatoria di tutti i casi d’uso costituisce la proiezione statica dello Use Case Model, il quale, oltre a specificare quali siano le funzionalità che il sistema deve realizzare — e fin qui non ci sono grosse novità rispetto ai metodi tradizionali, eccezion fatta per il formalismo grafico —, mette in rilievo gli attori coinvolti e le relative interazioni con il sistema. Quindi, ancora una volta, gli Use Case risultano particolarmente focalizzati sul ruolo degli attori. La centralità degli attori dovrebbe spingere il progettista a pensare il sistema non solo in termini delle funzionalità da realizzare, ma anche in termini del valore da fornire agli utenti. Dunque, i casi d’uso non vengono utilizzati unicamente come formalismo per la specifica formale dei requisiti utente, ma come valido supporto alle altri fasi, ossia disegno, implementazione e test. In altre parole essi guidano l’intero processo di sviluppo. Logica conseguenza è che i prodotti generati nelle varie fasi sono modelli che realizzano, in qualche misura, i casi d’uso. I programmatori dovrebbero continuamente rivedere il codice, al fine di verificarne l’aderenza con quanto sancito nei casi d’uso codificati, mentre i tester dovrebbero prova-

UML dalla teoria alla pratica

27

re le funzionalità del codice, per accertarsi che il sistema realizzi effettivamente quanto sancito nelle specifiche. Sintetizzando, l’intero ciclo di vita ruota attorno alla Use Case View, vale a dire che i casi d’uso vengono progettati, soprassiedono la fase di disegno, vengono implementati e infine, nei test di sistema, si verifica che il sistema realizzi quanto stabilito. Nel paragrafo dedicato al processo ICONIX, viene fornita un’istanza di processo Use Case Driven.

Processi Architecture Centric Il ruolo dell’architettura di un sistema software può essere, per molti versi, ricondotto a quello svolto dall’architettura nelle costruzioni civili. L’edificio viene studiato da parte del team di progettisti da diversi punti di vista: struttura, sicurezza, servizi, tubature, riscaldamenti, e così via, in modo che se ne abbia una visione completa prima di avviare la costruzione effettiva: perla di saggezza! Allo stesso modo l’architettura di un sistema software è descritta per mezzo di diverse viste prima che venga iniziata la codifica del sistema. I relativi concetti comprendono gli aspetti più significativi della proiezione statica e dinamica del sistema. L’architettura, in ultima analisi, dovrebbe venire progettata con l’intento di fornire l’infrastruttura necessaria per il soddisfacimento dei requisiti utente e, come tale, dovrebbe essere funzionale alla Use Case View. L’architettura, chiaramente, è influenzata da molti altri fattori tra i quali: la piattaforma sulla quale il sistema dovrà funzionare (architettura di rete, sistemi operativi presenti, gestori di base di dati, modalità di comunicazioni tra sistemi esistenti, …); riusabilità di determinati componenti (framework, interfacce utente, …), considerazioni di “dispiegamento” (adattamento dei componenti sull’architettura fisica), presenza di legacy system, requisiti non funzionali (prestazioni, robustezza, efficiente utilizzo delle risorse), e così via. L’architettura è la vista del disegno nella sua globalità, grazie alla quale si evidenziano gli aspetti di maggiore importanza, mentre gli altri dettagli vengono lasciati appositamente fuori. La valutazione di quali aspetti siano più interessanti e quali, invece, lo siano meno è un metro soggettivo e come tale dipende dall’esperienza dei singoli. Comunque ciascun metodo di sviluppo offre linee guida come comprensibilità, flessibilità, capacità di adattamento a futuri cambiamenti, riusabilità, e così via.

Processi iterativi e incrementali Lo sviluppo di un sistema software è un processo che può richiedere mesi o anni, mentre la relativa manutenzione, se il software si rivela di successo, può richiedere un ordine di grandezza superiore. Queste considerazioni di carattere empirico hanno finito inevitabilmente per fornire un prezioso feedback ai processi di sviluppo: l’intero processo viene infatti suddiviso in

28

Capitolo 1. UML: che cosa

è, che cosa non è

Figura 1.7 — Schematizzazione di un processo software.

Processo

diversi miniprogetti, ognuno dei quali è un’interazione che produce un incremento significativo del progetto globale. Le varie iterazioni non dovrebbero essere frutto del caso o dell’umore degli architetti, dovrebbero bensì essere debitamente pianificate e controllate, vale a dire che dovrebbero essere veri e propri miniprogetti completi. La scelta di cosa realizzare a ogni iterazione dovrebbe dipendere da due fattori: • selezione del gruppo di use case in grado di estendere ulteriormente le funzionalità del sistema, considerando il punto di evoluzione raggiunto — o da raggiungere — nell’iterazione precedente; • rischi più pressanti. Giacché ogni iterazione è un miniprogetto, questa dovrebbe includere tutte le fasi relative: analisi, disegno, implementazione e test. Non sempre un’iterazione produce un aumento quantitativo del modello; ma spesso tale aumento si può risolvere nella sostituzione di parti di disegno/implementazione con altre più dettagliate e/o più sofisticate. Infatti può accadere di realizzare velocemente parti del disegno, e relativa implementazione, senza curarne troppo la struttura interna, rimandando quindi la relativa reingegnerizzazione a ulteriori iterazioni. Ciò si rivela particolarmente utile quando tali sottosistemi forniscono l’infrastruttura o le funzionalità utilizzate da altri. Può infatti risultare conveniente realizzare prime versioni, seppur grezze, di un particolare sottosistema, al fine di dare modo alle altre parti dipendenti di poter evolvere liberamente. L’intento è quello di reingengerizzare tali versioni prototipali in un secondo momento, disponendo di maggior calma e migliori informazioni (magari applicando opportuni criteri, come quelli specificati nel libro Refactoring di Fowler). Ancora una volta è possibile ricorrere a tale approccio attraverso l’utilizzo intelligente delle interfacce. Si potrebbe, per esempio, progettare e realizzare una prima versione del sistema di sicurezza — magari in grado di fornire risposte costanti — senza che esso realizzi alcun meccanismo specifico, solo per fornire agli altri sottosistemi la possibilità di poter eseguire dei test.

UML dalla teoria alla pratica

29

Nella realizzazione dei casi d’uso ritenuti fondamentali per l’iterazione in atto (Use Case Driven), è necessario utilizzare come guida anche l’architettura stabilita (Architecture Centric), realizzando pertanto il disegno nei termini di componenti compatibili con essa. Al termine di ciascuna iterazione è indispensabile verificare che essa abbia effettivamente raggiunto gli scopi prefissati, ossia che la nuova versione del sistema realizzi correttamente i casi d’uso selezionati. In caso affermativo si può procedere con l’iterazione successiva, altrimenti è necessario introdurre una fase di revisione, nella quale, può rendersi necessaria anche una completa revisione dell’approccio utilizzato in precedenza. Per avere migliori possibilità di riuscita, un progetto dovrebbe seguire l’iter pianificato, pur con minime deviazioni dovute a imprevisti, i quali dovrebbero comunque sempre essere tenuti in considerazione all’atto della pianificazione del processo stesso. I vantaggi nell’utilizzo di metodi di sviluppo iterativo e incrementale sono: • riduzione dei costi dovuti a errori o imprevisti generatisi durante una singola iterazione. Se per qualche motivo si rende necessaria l’introduzione di una nuova iterazione, l’organizzazione impegnata nello sviluppo viene penalizzata “solo” per il relativo contesto senza però intaccare l’intero prodotto. • riduzione del rischio di non produrre il sistema nei tempi previsti. Identificare fin dall’inizio i rischi maggiori e assegnare un congruo periodo di tempo per la realizzazione della relativa soluzione, dovrebbe consentire alle varie persone coinvolte di trattare il rischio con la dovuta calma, evitando paradossalmente di aggravare la situazione per via di eventuali stati d’ansia dovuti a potenziali ritardi. Seguendo i metodi tradizionali è comune osservare come nel momento in cui un un problema viene evidenziato si generi l’affanno di risolverlo, temendo di produrre un ritardo nei tempi di consegna. Come direbbe Murphy: “se esiste la possibilità che una cosa vada male, sicuramente andrà male!”. • riduzione del tempo necessario per rilasciare il sistema globale. Ciò dovrebbe essere il risultato di un lavoro più razionale e dominato da obiettivi più immediati e ben riconoscibili. • maggiore flessibilità in caso di modifiche dei requisiti utente. Nella pratica si verifica che molto raramente si riescano a identificare sin da subito e correttamente le specifiche del cliente, anche per via di sue inequivocabili responsabilità. Paradossalmente la comprensione di esse, nella loro globalità, si raggiunge a progetto ultimato. La suddivisione in iterazioni e la realizzazione di insiemi ben identificati di casi d’uso, rende più facile la possibilità di verificare il sistema in maniera incrementale e di evidenziarne eventuali anomalie.

30

Capitolo 1. UML: che cosa

è, che cosa non è

The Unified Software Development Process Nei prossimi paragrafi viene presentato, sin troppo brevemente, il processo di sviluppo del software, ideato dai Tres Amigos presso la Rational e denominato “processo unificato di sviluppo software” (Unified Software Development Process). Quanto riportato è tratto dal libro [BIB08] al quale si rimanda per una trattazione più approfondita dell’argomento così indispensabile per la produzione di sistemi software di elevata qualità. L’utilizzo consapevole di un buon processo può, da solo, produrre la differenza tra un progetto fallimentare e uno altamente produttivo, sebbene in molte organizzazioni la produzione del software sia ancora basata su processi ideati ai tempi delle schede perforate oppure standardizzati da personale commerciale o addirittura inesistenti o demandata alla responsabilità e all’esperienza dei singoli (leggasi inesistente). Chiaramente si tratta di una condizione necessaria, ma non sufficiente! Un processo di sviluppo software è costituito dal complesso di attività necessarie per trasformare i requisiti utente in un sistema software. Lo Unified Software Development Process, in realtà, non è semplicemente un processo bensì un framework di un processo generico. Esso si presta a essere proficuamente utilizzato per classi piuttosto estese di sistemi software, differenti aree di applicazioni, diverse tipologie di organizzazioni, svariati livelli di competenza e progetti di varie dimensioni. Una delle linee-guida del processo unificato è di essere component-based: il sistema da realizzare viene basato su componenti e interconnesioni fra i componenti stessi, fondate su opportune e ben definite interfacce. In questo contesto, per “componente” si intende una parte fisica e sostituibile di un sistema che si conforma e fornisce la realizzazione di un insieme ben definito di interfacce. Queste ultime sono collezioni di operazioni, utilizzate per specificare un servizio offerto da una classe o da un componente. Vista l’importanza e l’eleganza di questi concetti e la loro centralità nella produzione di sistemi Object Oriented, si è deciso di trattare tali argomenti in dettaglio nel capitolo

Figura 1.8 — Esempio di suddivisione del sistema in sottosistemi. «subsystem»

«framework» Services

UserInterface

SystemCore

ResultListener

«subsystem» SecurityServices

SecuritySystem

NotificationListener RemoteServices «subsystem»

«subsystem»

ClientBusinessLogic

RemoteSystem

UML dalla teoria alla pratica

31

appropriato, ossia quello dedicato ai diagrammi delle classi (Capitolo 7). Per ora basti sapere che tale approccio si basa sulla suddivisione del sistema globale in diversi sottositemi, comunicanti per mezzo di interfacce che, in questo contesto, assumono la valenza di un contratto. Un sottosistema si impegna a fornire i servizi esposti nelle relative interfacce, mentre un altro le utilizza impegnandosi a rispettarne le condizioni. Il processo unificato, ovviamente, utilizza lo UML per produrre e documentare i manufatti prodotti in ciascuna delle fasi di cui si compone. Altra caratteristica peculiare, offerta dal processo, è la fusione razionale dei tre principali metodi esistenti: Use Case Driven, Architecture Centric e Iterative and Incremental. Forse ciò non solo lo rende unico ma, verosimilmente anche raro da utilizzare nella sua versione originale. Se da una parte, infatti, l’insufficienza cronica di tempo non può e non deve giustificare la produzione di sistemi software non guidati da un processo e tantomeno può giustificarne la produzione incentrata sulla sola fase di codifica, dall’altra bisogna ammettere che il problema tempo esiste ed è molto sentito. Forse, se è possibile avanzare una critica al processo unificato, si può dire che esso necessita di una notevole quantità di tempo, non sempre disponibile, per poter essere attuato così come proposto.

Integrazione tra Use Case Driven e Architecture Centric Ogni prodotto possiede delle funzionalità e una forma fisica. Il problema sta nel trovare il giusto equilibrio per riuscire a generare un valido prodotto. Nel caso di sistemi software le funzionalità vengono modellate per mezzo della vista dei casi d’uso mentre la forma è rappresentata dall’architettura. Tra le due viste esiste una forte interdipendenza: il sistema che realizza la Use Case View deve conformarsi all’architettura fisica, la quale, a sua volta, deve fornire lo spazio necessario al soddisfacimento dei requisiti utente, sia quelli attuali, sia quelli futuri… Le due viste dovrebbero evolvere parallelamente attraverso una successione di sincronizazzioni. L’architetto, nel conferire la forma al sistema (progettazione dell’architettura), deve prestare attenzione sia a fornire un appropriato ambiente per la versione iniziale, sia a consentire a eventuali versioni future di adattarsi senza grandi problemi. Per individuare la forma migliore, è necessario essere in grado di comprendere le funzionalità che il sistema dovrà fornire; chiaramente non è necessario conoscerle perfettamente tutte. In particolare l’architetto dovrebbe: • generare un disegno iniziale di architettura che non sia specificamente connessa ai requisiti utente (use case independent), sebbene ne debba possedere una certa conoscenza. Ciò è conseguibile iniziando a tener presenti i vincoli dettati dalla piattaforma, dal network che si utilizzerà, e così via. Tipicamente si ragiona in termini di pattern architetturali o più semplicemente si cerca di ricondursi ad architetture — o opportuni segmenti di esse — dimostratesi efficaci in precedenti progetti.

32

Capitolo 1. UML: che cosa

è, che cosa non è

• a questo punto è necessario lavorare con forte aderenza ai casi d’uso. Vengono selezionati quelli ritenuti più importanti o significativi e, di ciascuno di essi, vengono forniti i dettagli in termini di sottosistemi, classi e componenti. • non appena i casi d’uso sono stati ben specificati e presentano una certa stabilità, si procede con una verifica dell’architettura che a sua volta potrebbe fornire nuovi elementi per migliorare i casi d’uso. Tale processo viene iterato fin quando anche l’architettura viene considerata stabile.

Ciclo di vita del processo Lo Unified Software Development Process integra i tre principali processi di sviluppo tra cui l’Iterative and Incremental; pertanto la realizzazione del sistema avviene attraverso cicli di iterazioni controllate, ognuna delle quali termina con la produzione di una nuova versione del sistema. Ogni ciclo è costituito da quattro fasi: iniziale, elaborazione, costruzione e transizione, ognuna delle quali è, a sua volta, suddivisa in iterazioni. Risultato di ogni ciclo è la produzione di una nuova versione del sistema virtualmente pronta per la consegna. Grosso vantaggio di questo criterio è che anche il processo di verifica è iterativo e incrementale: ogni volta che una nuova versione è disponibile viene sottoposta a relativa fase di test. Ciò dovrebbe favorire la produzione di sistemi di migliore qualità. Nel mondo dell’ideale, ogni versione dovrebbe consistere in codice incapsulato in opportuni componenti eseguibili, corredati da manuali, documentazione e così via. Sebbene dal punto di vista del cliente i componenti eseguibili siano i manufatti più importanti, essi da soli non sono sufficienti. Ciò è dovuto al fatto che l’ambiente tipicamente è destinato a variare: espansione dei sistemi hardware, aggiornamenti di sistemi operativi e database manager, ecc. Gli stessi requisiti cliente diventano più chiari e precisi con il procedere nel ciclo di vita del software e pertanto sono soggetti anch’essi ad aggiornamenti. Non a caso un’idea che spesso si ingenera nei team di sviluppo a consegna avvenuta, è di essere in grado di poter rifare l’intero sistema più adeguatamente e in minor tempo. I prodotti che devono accompagnare la versione finale sono: • il modello dei casi d’uso, con evidenziate tutte le relazioni tra i diversi use case e i relativi attori e gli scenari; • il modello di analisi che dovrebbe conseguire, principalmente, due obiettivi: a. definire i casi d’uso in maggior dettaglio; b. orientare un iniziale disegno del comportamento del sistema per mezzo dell’individuazione degli oggetti basilari che lo caratterizzano;

33

UML dalla teoria alla pratica

• il modello di disegno che definisce: a. la struttura statica del sistema in termini di sottosistemi, classi, interfacce; b. la realizzazione dei casi d’uso per mezzo di collaborazioni attraverso i sottosistemi, le classi , ecc. evidenziati nella proiezione statica al punto precedente; • il modello di implementazione che include i componenti (raggruppamento di codice) e il mapping degli stessi con le relative classi; • il modello di dispiegamento (deployment) che definisce come i componenti software vadano allocati sui nodi fisici (computer, server, reti, ecc.) previsti dall’architettura hardware; • il modello di test il quale definisce i casi di test (Test Case) che verificano gli Use Case; • la rappresentazione dell’architettura. La totalità di questi manufatti rappresenta il sistema nel suo insieme. Ogni ciclo necessità di un’appropriata porzione di tempo che viene suddivisa in quattro fasi che sono: iniziale, elaborazione, costruzione e transizione. Ciascuna fase può a sua

Figura 1.9 — I modelli previsti dallo Unified Process.

specificato da

Modello casi d'uso realizzato da dispiegato come da

Modello di analisi

implementato da

Modello di disegno

verificato da

Modello di dispiegamento

Modello di implementazione

x

OK

x OK

x

OK

Modello di test

34

Capitolo 1. UML: che cosa

è, che cosa non è

volta essere ulteriormente suddivisa e termina con il raggiungimento del milestone pianificato: realizzazione dei manufatti programmati. I milestones sono importanti per diverse ragioni. In primo luogo permettono ai manager di prendere cruciali decisioni prima di considerare conclusa una fase e passare alla successiva. Successivamente risultano utili anche per gli sviluppatori in quanto permettono di monitorare la progressione delle attività assegnate. Infine, tenendo traccia del tempo speso e delle problematiche emerse in ciascuna fase, è possibile raccogliere tutta una serie di informazioni da utilizzarsi nella pianificazione dei progetti futuri. Nella fase iniziale di ogni ciclo (detta inception) si ipotizzano diverse soluzioni e l’idea migliore viene sviluppata in una visione del prodotto della quale vengono forniti gli scenari di utilizzo. Obiettivo della fase di inception è essenzialmente fornire risposte adeguate agli interrogativi riportati di seguito: • quali sono le funzioni basilari che il sistema deve fornire per ciascuno dei principali attori? • qual è il modello di massima dell’architettura necessaria al sistema? • qual è il piano e quanto dovrebbe costare lo sviluppo del prodotto? Il primo quesito trova risposta in un modello semplificato degli Use Case che ne contempli i più critici. Per ciò che concerne il secondo punto va tenuto presente che nella prima fase l’architettura è poco più di un’ipotesi da sviluppare in dettaglio nelle fasi successive, pertanto è sufficiente che contenga i principali sottosistemi. Molto importante invece è riuscire ad identificare prima possibile i rischi principali, assegnare loro la giusta priorità ed elaborare la pianificazione della fase in dettaglio. La seconda fase, nota come elaborativa (elaboration), prevede la specifica di dettaglio di tutti i casi d’uso e il disegno dell’architettura la quale riveste un ruolo di primaria importanza e viene espressa per mezzo delle viste di tutti i modelli del sistema, i quali, globalmente, costituiscono il sistema stesso. Ciò equivale a dire che esistono viste architetturali del modello: dei casi d’uso, di analisi, di disegno, di implementazione e di deployment. La vista del modello di implementazione include componenti per dimostrare che l’architettura è eseguibile. In questa fase i casi d’uso più critici, identificati nella fase precedente, vengono realizzati. Risultato di questa fase è l’Architectural Baseline, ovvero un insieme rivisto e approvato di manufatti che: • rappresentano una base stabilita per futuri sviluppi ed evoluzioni; • possono essere modificati solo attraverso una procedura formale di configurazione o di cambio di gestione.

UML dalla teoria alla pratica

35

Al termine della fase elaborativa il capo progetto si trova nelle condizioni di poter pianificare le attività e di stimare le risorse necessarie per completare l’intero progetto. I pericoli di questa fase possono essere adeguatamente sintetizzati dalle seguenti inquietudini: i casi d’uso, l’architettura e i vari piani sono sufficientemente stabili? I rischi sono abbastanza sotto controllo perché lo sviluppo dell’intero progetto in contratto sia in condizione di procedere? Terminata la seconda fase si passa a quella di costruzione (construction), in cui, come è lecito aspettarsi, il prodotto viene costruito. Nel corso dell’evoluzione del prodotto l’architettura di base cresce all’aumentare dei dettagli del sistema e il prodotto evolve fino ad essere pronto per la trasformazione per gli utenti finali. Durante questa fase le risorse precedentemente ipotizzate vengono impiegate. L’architettura del sistema assume una sua stabilità anche grazie al lavoro degli sviluppatori che in corso d’opera ne verificano la validità ed eventualmente ne suggeriscono opportune varianti. Terminata la fase di costruzione il prodotto contiene tutti i casi d’uso concordati per la versione generata dalla fase in corso. Come pratica insegna, raramente si tratterà di una versione senza difetti: questi sono oggetto di indagine nella fase successiva. Il milestone della fase di costruzione si raggiunge quando si è certi che il prodotto risolva sufficientemente le necessità di qualche utente tanto da poterne consegnare la versione per il test. L’ultima fase, di transizione (transition) comprende il periodo necessario al prodotto per divenire una versione beta. Un ristretto numero di utenti prova la versione e compila un documento con l’elenco di difetti e deficienze. Questo documento viene quindi fornito a un gruppo di sviluppatori che ricontrolla il prodotto e provvede a risolvere i problemi esposti. A questo punto il prodotto è pronto a essere somministrato a un gruppo di prova più ampio. La fase di transizione dovrebbe prevedere attività quali la manutenzione del prodotto per accogliere eventuali suggerimenti, l’ addestramento del personale, la fornitura di assistenza, e così via. I suggerimenti ricevuti, tipicamente, vengono suddivisi in due categorie: quelli urgenti che quindi necessitano di essere risolti il prima possibile e quelli da demandare alle fasi successive (i famosi must, should, could).

RUP: Rational Unified Process La Rational ha presentato un processo di sviluppo, denominato RUP (Rational Unified Process) che, lentamente, ambisce a divenire lo standard di mercato per la produzione del software. Il principale fautore è Philippe Kruchten, Lead Architect della Rational. La prima versione fu rilasciata a dicembre 1998 frutto, verosimilmente, della rielaborazione di quello presentato dai Tres Amigos (illustrato nel paragrafo precedente) e del processo Objectory (frutto della medesima organizzazione di Jacobson). Al momento in cui viene scritto il presente libro è disponibile la versione 5.5.

36

Capitolo 1. UML: che cosa

è, che cosa non è

Brevemente i vantaggi offerti dal RUP sono: • è basato su princìpi di ingegneria del software dimostratisi efficaci: approccio Iterative and Incremental, Use Case Driven, Architecture Centric; • si tratta di un processo proprietario e verosimilmente leader del mercato, pertanto è frutto di continue evoluzioni grazie agli investimenti della Rational; • prevede una serie di meccanismi, come per esempio i prototipi funzionanti da realizzare alla fine di ogni interazione del ciclo, i punti di decisione (go/no go) che permettono di stabilire, alla fine di ogni fase, se abortire il processo: chiaramente se il processo è destinato a fallire è meglio rendersene conto e quindi abortirlo il prima possibile; • include un sistema semplificato di agevolazione dell’utilizzo grazie alla descrizione basata su tecnologie web. Gran parte della descrizione del RUP è stata riportata nella sezione dedicata ai tool di sviluppo, in quanto si tratta dell’unico processo dotato di apposito software. Gli svantaggi sono: • è molto focalizzato nel processo di sviluppo e trascura altri flussi importanti nello sviluppo di sistemi software (come spiegato di seguito); • essere iterativo e incrementale è un vantaggio ma uno svantaggio al tempo stesso: richiede tutta una serie di attività supplementari dovute alle varie iterazioni; • non si integra perfettamente in organizzazioni, quali le software house, che, tipicamente, realizzano più progetti contemporaneamente; • essendo un prodotto proprietario e guidato dalle esigenze di mercato, fa sì che aspetti meno pressanti vengano trascurati (tools per il disegno dell’interfaccia utente, per la modellazione dei dati, ecc.). Del RUP è disponibile una versione “migliorata” elaborata presso la Ronin International Inc. il cui fautore principale è Scott Ambler — autore, tra l’altro, di testi come Building Object Applications that Work (1998) e Process Patterns (1999) — che ne è il presidente. Tale versione, denominata EUP (Enhanced Unified Process, processo unificato miglioraro) è animata dall’obiettivo di perfezionare il RUP aggiungendo flussi mancanti ed espandendo opportunamente, qualora necessario, quelli esistenti. Per esempio è presente una quinta fase (oltre Inception, Elaboration, Construction e Transiction) denominata Production

UML dalla teoria alla pratica

37

(produzione). Si tratta di un’unica iterazione atta a gestire le attività necessarie per mantenere il sistema operativo fino a quando sia disponibile una nuova versione o, addirittura, il sistema venga rimpiazzato da uno completamente nuovo. Un’altra innovazione è data dalla presenza di un workflow denominato Operations and Supports che, come indicato dal nome, è designato a gestire operazioni di sviluppo e piani di supporto. Per esempio classiche attività di supporto necessarie a sistema rilasciato sono le attività di backup, l’esecuzione di specifici lavori batch, ecc.

ICONIX: un processo Use Case Driven In questo paragrafo viene introdotto brevemente il processo di sviluppo Use Case Driven denominato ICONIX Unified Object Modeling. Per uno studio più completo, si rimanda il lettore a [BIB05] e [BIB06]. I processi di sviluppo del software basati sulla vista dei casi d’uso sono probabilmente i più popolari nelle aziende di informatica. In ultima analisi, sono quelli utilizzati più o meno formalmente da sempre: si tratta di conferire la massima importanza ai requisiti funzionali di un sistema e ciò da sempre ha costituito un criterio guida nello sviluppo del software. Poiché il ciclo di vita viene completamente basato sulla vista dei casi d’uso, logica conseguenza è che la use case view assume un’importanza e una criticità eccessive: un errore nella formulazione dei requisiti potrebbe determinare il fallimento dell’intero processo. Si ricordi che la difficoltà di comunicazione tra clienti e personale tecnico e la laboriosità nel tentare di definire una piattaforma comune hanno generato l’insuccesso di molti progetti. Spesso accade che certi team siano riusciti a realizzare progetti fantastici con tecnologie ultramoderne ma con l’unico neo, affatto trascurabile, di non risolvere minimamente i problemi del cliente. Per ridimensionare il più possibile tale rischio, il processo viene frequentemente associato ad altri e spesso si utilizza un approccio incrementale e iterativo: lo sviluppo richiede diverse iterazioni del modello del dominio al fine di identificare ed analizzare completamente i vari casi d’uso. Altre iterazioni avvengono durante l’intero ciclo di vita del software tra le viste di cui si compone. Lo stesso modello statico viene definito incrementalmente a seguito delle successive iterazioni attraverso il modello dinamico. Rappresentando le fondamenta del processo di sviluppo, la Use Case View necessita di un’eccezionale attenzione; in particolare è necessario stabilire con assoluta precisione ed esattezza: • chi sono gli attori del sistema e quali interazioni esercitano con il sistema (Use Case View); • quali sono gli oggetti del dominio del problema (l’ambiente che si sta automatizzando) e quali relazioni esistono tra di essi (diagramma concettuale delle classi);

38

Capitolo 1. UML: che cosa

è, che cosa non è

• quali oggetti sono coinvolti in ogni caso d’uso; • come gli oggetti collaborano all’interno di uno Use Case (diagrammi di interazione e collaborazione); • come gestire le problematiche di controllo del mondo reale (diagrammi di stato); • come costruire fisicamente il sistema (diagramma di dettaglio delle classi); Come si vedrà in seguito, lo UML fornisce tutti i formalismi necessari per modellare quanto sancito nei punti precedenti nel contesto di un’unica notazione. Il processo Use Case Driven come ulteriore vantaggio offre un elevato livello di “tracciabilità”. Con ciò si intende dire che durante tutto il ciclo di vita è sempre possibile risalire, più o meno direttamente, ai requisiti del cliente. Ciò permette di realizzare il disegno del sistema senza venire meno alle necessità dell’utente e in più è sempre possibile seguire come gli oggetti evolvono dai casi d’uso all’analisi fino al disegno. Il processo di sviluppo guidato dai casi d’uso proposto nel libro Use Case Driven Object Modeling with UML (cfr. [BIB05]) prevede quattro “pietre miliari” (milestones): 1. riesame dei requisiti utente; 2. revisione del disegno preliminare; 3. verifica del disegno di dettaglio; 4. consegna. Durante la prima fase è necessario identificare gli oggetti del dominio del problema e le relazioni che intercorrono tra di essi. Tale primissimo processo dovrebbe concludersi con la realizzazione di diagrammi delle classi di alto livello. Laddove i tempi e la complessità del sistema lo permettano, un forte valore aggiunto potrebbe venire dalla realizzazione di un rapidissimo prototipo. La riesamina dello stesso alla presenza del cliente fornisce di solito un’ottima piattaforma di argomentazione. Ulteriore vantaggio è che si riesce a placare temporaneamente il cliente facendolo trastullare con il nuovo giocattolo… A dire il vero i prototipi possono risultare anche rischiosissimi: può capitare che, da qualche capo progetto avventuriero, esso venga fatto passare come prodotto ultimato, per la gioia di tutto il team di sviluppo; credete che sia solo un rischio e che non sia già capitato? Oppure — il che sostanzialmente si riconduce al caso precedente — può succedere che il cliente sviluppi l’idea che il passaggio dalla fase prototipale a quella definitiva avvenga con accelerazione infinita, ossia tempo zero.

UML dalla teoria alla pratica

39

Può accadere che alcune menti semplici pensino che per edificare la propria casetta ci vogliano pochi giorni o settimane, dal momento che, per immaginarsela impiegano pochi minuti. Poi discutono con architetti e muratori e tornano rapidamente sulla terra: si rassegnano ad aspettare il tempo necessario. Nel software, tanto per cambiare, le cose funzionano in modo leggermente diverso: le menti semplici immaginano, per esempio, complicati sistemi di e-commerce e credono di averli già tra le mani, poi discutono con chi il software lo deve realizzare e, strano ma vero, restano comunque della propria opinione. A questo proposito si ritiene opportuno tributare una meritatissima lode a uno dei padri dell’Object Oriented: l’eccelso Bjarne Stroustrup. Nel suo libro C++, capitolo “Progetto e sviluppo”, nel contesto “Sperimentazione e analisi” egli esprime un concetto molto interessante: afferma che all’inizio di un progetto importante è semplicemente impossibile ipotizzare con successo i dettagli della sua realizzazione; pertanto occorre sperimentare per acquisire quell’esperienza che, riguardo alla problematica, ancora non si possiede. Il prototipo è uno dei principali strumenti di sperimentazione — sicuramente uno dei più utili — ma ha il terribile difetto di somigliare troppo alla soluzione finale. Le menti semplici, già messe a dura prova dalla discrepanza tempi mentali / tempi fisici, non riescono a distinguere il dito dalla luna: ma se un cliente può essere opportunamente edotto riguardo la reale natura di ciò che vede, nulla si può fare quando l’assurdità si annida nella testa dei cosiddetti coordinatori, manager o capi progetto. In tal caso, l’unico consiglio da dare ai malcapitati sviluppatori coinvolti è quello di abbandonare la nave al più presto, prima che l’infausta marea li travolga. Al riguardo, è possibile citare Fowler: “Se non puoi cambiare la tua azienda, cambia azienda”. Sempre durante la prima fase è essenziale identificare i casi d’uso utilizzando i relativi diagrammi o appositi schemi e organizzarli in gruppi. Risultato di ciò è la produzione di primi diagrammi dei componenti. Per terminare la prima fase e quindi raggiungere il milestone della riesamina dei requisiti del cliente, è necessario allocare i requisiti funzionali agli oggetti del dominio del problema e ai relativi casi d’uso. La prima attività del secondo milestone prevede la redazione della descrizione di dettaglio dei casi d’uso emersi nella fase precedente. In particolare, per ogni caso d’uso, è necessario descrivere lo scenario relativo al caso ideale, quello in cui tutto funziona bene (mainstream) e non si verifica alcuna anomalia, e quelli relativi ai casi meno frequenti e alla gestione delle situazioni di errore che possono verificarsi. Durante questa fase si procede verso l’interno del sistema. A questo punto è necessario eseguire la famosa analisi di robustezza. Per ogni caso d’uso è necessario individuare tutti gli oggetti che permettono di realizzare i vari scenari e aggiornare il diagramma delle classi del dominio del problema con quelle nuove, gli attributi e i metodi, a mano a mano che vengono individuati. Durante questa fase si procede dall’interno del sistema verso il suo esterno utilizzando un approccio Top Down: si aumenta il livello di dettaglio per mezzo di iterazioni successive. La seconda pietra miliare

40

Capitolo 1. UML: che cosa

è, che cosa non è

viene raggiunta con l’aggiornamento del diagramma delle classi al fine di adeguarlo ai concetti emersi nelle attività della seconda fase. La terza fase prevede l’allocazione del comportamento. Per ogni caso d’uso si identificano i messaggi scambiati dai vari oggetti e i metodi da invocare. A tal fine risulta conveniente utilizzare i diagrammi di interazione Sequence o Collaboration. Si tratta di diagrammi equivalenti; quello che cambia è l’aspetto al quale si conferisce maggior risalto: l’iterazione temporale nel primo, la collaborazione tra oggetti nel secondo. Nel tracciare i diagrammi, è necessario continuare ad aggiornare il class diagram a mano a mano che vengono introdotti nuovi concetti. Terminata anche questa fase occorre completare il modello statico aggiungendo le informazioni dettagliate di disegno, eventualmente ricorrendo ai design patterns. Per raggiungere anche questa milestone bisogna procedere con la verifica del disegno: è indispensabile accertarsi che soddisfi i requisiti precedentemente identificati. Giunti qui, non resta che concentrarsi sull’ultima fase: il delivery. In primo luogo è necessario produrre i diagrammi di implementazione: componenti e dispiegamento. Quindi si passa alla codifica che dovrebbe risultare abbastanza automatica. Si eseguono i test di unità e di integrazione. Si esegue il famoso test di accettazione: utilizzando i casi d’uso e agendo sul sistema con una strategia a scatola nera, si verifica che il sistema faccia effettivamente quello per cui è stato finanziato. Terminata tale fase il progetto è concluso e quindi si passa alla relativa manutenzione che, nel caso in cui il sistema si riveli di successo, occuperà circa il 50% dell’intero ciclo di vita.

XP (eXtreme Programming): tutto e il contrario di tutto Viene qui presentato un particolare processo di sviluppo del software, di recente formulazione e in continua evoluzione, incentrato sulla fase di codifica: il teatro dell’assurdo. Si tratta di camminare sulla lama del rasoio. La comunità informatica — e lo stesso team che ha collaborato alla realizzazione del presente libro — è percorsa da vivaci dibattiti tra sostenitori dell’XP e coloro che invece lo considerano come un vero e proprio anti-processo. Onde evitare di essere tacciato di ignavia, l’autore dichiara onestamente di aver sempre osservato l’XP con non poche riserve mentali, riserve poi attenuate dall’aumento di formalità apportato grazie alle collaborazioni di personaggi del calibro di Martin Fowler e Erich Gamma (uno della “combriccola dei quattro”). Prima di fornire qualche dettaglio sull’eXtreme Programming (XP), si consiglia a tutti i lettori che stessero casomai leggendo in piedi questo libro di sedersi comodamente e di rilassarsi: in questo paragrafo vengono azzerati diversi sforzi prodotti finora nel tentativo di conferire il giusto rilievo alla fasi di analisi e disegno. Il problema principale è che di questo metodo si tende a fare abuso con estrema facilità. Esso finisce involontariamente per fornire giustificazioni a tutta una serie di gruppi “modello-repellenti”. Spesso, collaborando con varie aziende e implorando i diversi team di

UML dalla teoria alla pratica

41

poter prendere visione dei modelli prodotti, ci si sente rispondere che non sono stati realizzati in quanto il sistema è stato sviluppato con un approccio di tipo XP e che, eventualmente, alcuni diagrammi delle classi verranno prodotti per mezzo di reverse engineering… Come se questi fossero gli unici “manufatti” necessari. Team “furbetti” di questo tipo trascurano qualche particolare: a differenza dei processi “model free” (presenti in molte aziende), l’XP attribuisce elevata importanza agli unit test (test di unità): prima di scrivere il codice effettivo è assolutamente necessaria la scrittura dei relativi casi di test (quindi, se si utilizzasse veramente un approccio di tipo XP, come minimo dovrebbe essere disponibile una serie di package di test). La realizzazione di test di unità equivale a dire che il comportamento viene analizzato e “modellato” a priori però sempre attraverso codice: il che non è assolutamente vietato, ma forse non privo di tutta una serie di svantaggi. Per esempio si ritiene complicato sottoporre il modello ad altre persone, magari non esperte del particolare linguaggio, al fine di ricevere qualche considerazione; risulta più complicato considerare i modelli stessi come risorse preziose per il futuro: si pensi a Use Case o modelli di analisi e disegno presenti in organizzazioni bancarie che rappresentano veri e propri investimenti indipendenti dal linguaggio utilizzato per l’implementazione, di un valore elevatissimo per futuri sviluppi e reingegnerizzazioni. Chiaramente non si può accusare uno strumento di cattivo funzionamento se lo si utilizza per fini non contemplati; come dire che non si può attribuire all’Aspirina alcuna colpa se la si utilizza per curare gastriti: del resto, non si tratta della cura per tutte le malattie; sicuramente è efficace per tutta una serie di patologie ma in altri casi non solo non produce alcun effetto, ma rischia anzi di aggravare la situazione. Con ciò si intende dire che ogni prodotto, e quindi anche i processi, deve essere utilizzato per i fini per il quale è stato ideato: l’XP può dare buoni risultati in contesti ben definiti: progetti di dimensioni medio-piccole, team di buona qualità, persone in grado di capire i limiti e le virtù del processo stesso, e così via. Si provi invece a pensare di utilizzare l’XP per sviluppare un complesso sistema bancario… L’idea di fondo che legittima l’intero processo è l’insufficienza cronica di tempo tipica dei progetti: invece di non seguire assolutamente un processo, il che rischierebbe di produrre risultati assolutamente non desiderabili, probabilmente è più opportuno seguirne uno “intuitivo”, leggero, pragmatico e quindi molto operativo. Ciò può risultare particolarmente utile lavorando a progetti con cicli di consegna molto compressi e con fattori di rischio tecnologico molto elevati: indagini e sperimentazioni condotte codificando possono rivelarsi ottimi espedienti. L’autore ritiene preferibile, anche in casi estremi, applicare versioni light di processi come lo RUP; ma va riconosciuto che l’XP può risultare un ottimo strumento a supporto di persone intelligenti, preparate e frustrate dai soliti manager sempre convinti di saper far girare correttamente gli ingranaggi del sistema di produzione del software, magari perché riescono a consegnare qualcosa .exe ai vari clienti.

42

Capitolo 1. UML: che cosa

è, che cosa non è

Uno dei punti deboli dell’XP è che il team deve prevedere persone di talento, cooperative, dotate di buon affiatamento, coordinate da un Team Leader dotato di esperienza e capacità personali non del tutto comuni. Per le persone meno brave del team si prospettano due alternative: elevare rapidamente la propria esperienza o uscirne fuori a gambe levate. Un altro punto da menzionare è che l’XP non porta a produrre tutta una serie di modelli estremamente importanti per l’organizzazione in cui il sistema funzionerà. Infatti, sebbene l’obiettivo del ciclo di vita del software sia produrre un sistema “eseguibile”, i processi del tipo RUP permettono di realizzare, oltre al sistema stesso, tutta una serie di manufatti di estremo interesse, come per esempio il modello del business. Esso può essere utilizzato per aggiornare il sistema, per future reingegnerizzazioni, per insegnare a nuovi dipendenti l’area di business, ecc. Per capirne l’importanza, basti pensare che mentre la tecnologia tende a rinnovarsi molto rapidamente, non altrettanto vale per il business, e quindi un buon modello può risultare valido per decine di anni. Nel processo XP, per così dire code centric, tutte le fasi del ciclo di vita del software vengono compresse a favore dell’attività di codifica: l’evoluzione delle API del sistema si acquisisce leggendo direttamente il codice, il comportamento di oggetti complessi viene definito per mezzo della codifica di appositi casi di test, gli inconvenienti vengono mostrati attraverso i problemi emersi dall’esecuzione dei casi di test, e così via. In tale ottica tutto il processo di sviluppo si risolve in una continua reingegnerizzazione del software e quindi ecco che le tecniche di refactoring illustrate da Fowler (Refactoring pubblicato dalla solita Addison Wesley) risultano un validissimo strumento di lavoro. Questo approccio può risultare particolarmente utile nel realizzare opportuni FrameWork: la collezione di classi via via prodotte viene immediatamente verificata ed eventuali lacune vengono alla luce rapidamente. Il processo non rifiuta completamente la fase di disegno, ma ne prevede l’utilizzo in casi estremi, quando non sia possibile codificare: in tutti gli altri casi l’attività di implementazione ha la precedenza. Da quanto emerso risulta chiaro che un processo code centric è applicabile quando si dispone di team di dimensioni medio-piccole formati da personale particolarmente esperto e il progetto da affrontare è anch’esso di dimensioni piuttosto contenute. Si tratta comunque di processi in grado di rendere felici taluni manager i quali ogni settimana, o poco più, si vedono consegnare nuove masse di codice. XP viene presentato come un processo leggero, efficiente, a basso rischio, flessibile, scientifico con un approccio divertente allo sviluppo del software. Quest’ultima qualità è sicuramente la più allettante: a tutti i tecnici piace divertirsi con nuovi giocattoli. Certamente è un metodo coraggioso — e per questo merita di essere considerato con il dovuto rispetto — che fa e farà discutere. L’autore nutre diversi dubbi sulla relativa efficacia nello sviluppo parallelo. Anche trovandosi nel caso più ideale possibile (requisiti utente ben chiari, team di elevata qualità tecnica, ecc.) il dubbio è che lo sviluppo parallelo del software possa risultare piuttosto

UML dalla teoria alla pratica

43

macchinoso in quanto, evidentemente, nessun elemento del team può sapere in anticipo cosa produrranno gli altri: non c’è il modello di disegno. Ciò, in ultima analisi, dovrebbe rendere difficile l’integrazione dei vari sottositemi prodotti da elementi diversi del team. Chiaramente il processo richiede personale intelligente in grado di capire che le aree di collegamento tra i vari sottosistemi andrebbero realizzate per prime e non prevede che il personale debba chiudersi in una stanza fino a sviluppo terminato; però la macchinosità del processo potrebbe risultare notevole. Ulteriore considerazione è che si tratta di un processo di produzione un po’ “cieco”: non si tenta in alcun modo di prevedere e affrontare i problemi che si potrebbero incontrare. Come dire, si comincia a costruire la strada, prima di edificarne un tratto si producono i vari test, ogni elemento del personale ne costruisce un pezzo, magari qualche kilometro più a est o più ad ovest, magari comunicando frequentemente per informarsi vicendevolmente circa il punto geografico in cui ognuno sta lavorando, e così via… Però come si dovrebbe procedere se poi alla fine ci si dovesse accorgere di trovarsi di fronte a una montagna o a un altro elemento geografico che complicano il raccordo delle strade?

XM (eXtreme Modeling): un buon compromesso Il processo Agile Modeling si deve allo studio di Scott W. Ambler e la seguente illustrazione deriva dal sito ufficiale del processo (www.extreme-modeling.com). L’eXtreme Modeling è un processo che tenta di riutilizzare quanto di positivo emerso grazie all’eXtreme Programming, riportando finalmente l’attenzione dalla fase di programmazione a quella di modellazione: un metodo decisamente più vicino alla forma mentis che sta alla base del presente libro… Sarà per la presenza della magica parolina modeling nel nome? Sia chiaro che non qui non si ripudia assolutamente il mio amore per la programmazione che tante gioie ha dato e continua a darmi: chi scrive è anzi convinto che spesso qualche sperimentazione programmativa (specie quando si gioca con nuovi “giocattoli”) prima di procedere con l’attività di modellazione possa far risparmiare molto tempo. Ciò di cui invece si dubita è dell’approccio diametralmente opposto: ricorrere alla modellazione solo nei casi in cui non sia possibile codificare come l’XP suggerisce. Recentemente il nome di eXtreme Modeling è stato variato in Agile Modeling in quanto ritenuto più rispondente ai propositi del processo: • definire come mettere in pratica una collezione di valori, princìpi e pratiche per la modellazione del software applicabile in progetti per lo sviluppo di sistemi software in modo efficace e leggero. Il segreto non sono le tecniche di modellazione in sé stesse quanto piuttosto il modo in cui metterle in pratica; • determinare come il processo possa essere utilizzato agevolmente ed efficacemente in progetti di sviluppo di sistemi software utilizzando anche criteri provenienti dall’XP;

44

Capitolo 1. UML: che cosa

è, che cosa non è

• determinare come il processo possa essere applicato seguendo le direttive e i modelli previsti dello Unified Process. I valori alla base del’AM sono: comunicazione, semplicità, riscontri, coraggio e umiltà. Chiaramente una delle precondizioni irrinunciabili per un progetto di successo è la comunicazione efficace tra i vari personaggi coinvolti nello stesso: dai clienti ai Business Analyst ai programmatori ai tester e così via; e qui ci sarebbe molto da attingere dall’esperienza personale di chi scrive, specie dopo l’espatrio. Una delle proprietà che aiutano a conseguire questo risultato è sicuramente la capacità di riuscire a individuare la soluzione più semplice in grado di soddisfare completamente le proprie necessità. Molto importante è ricevere continui riscontri durante il proprio lavoro i quali, a seconda delle fasi possono venire dai clienti, dai Business Analyst, dagli Architect ecc. È necessario poi avere il coraggio di prendere delle decisioni e cercare di mantenerle fino in fondo: questa frase dovrebbe essere stampata a lettere cubitali e affissa nella stanza di taluni manager che all’inizio si dicono disposti ad adattarsi ai tempi necessari per produrre modelli di qualità salvo poi cambiare improvvisamente idea non vedendo scrivere codice. Ovviamente, nel caso in cui una decisione risulti errata, bisognerebbe avere altrettanto coraggio di cambiare rotta prima che sia troppo tardi. Infine è necessario avere l’umiltà di riconoscere che non si può sapere tutto e che anche altri elementi del proprio team possono apportare del valore aggiuntivo all’intero progetto: e ci si rende conto che questo è il più difficile dei valori a cui credere. Tra i princìpi da applicare, quello in cui più credo afferma che un modello può avere più valore di 1024 linee di codice; e personalmente l’autore avrebbe inserito un ordine di grandezza superiore… Realizzare un paio di modelli — magari anche su un foglio di carta o alla lavagna — può aiutare a pensare e a investigare in merito a determinate soluzioni prima di realizzarle e magari a rendersi conto che non erano le migliori o addirittura che non erano praticabili. Lo sviluppo guidato da uno scopo ben preciso e già verificato è il modo migliore per evitare di sprecare tempo ed energie. Ovviamente non si tratta solo di applicare il modello più adatto ma anche di realizzarne diverse viste al fine di catturare i vari aspetti di sistemi complessi. Ogni qualvolta sia necessario sviluppare un sistema in team o interagire con sistemi esterni al proprio può risultare molto conveniente realizzare un modello che funga da contratto specie se il sistema esterno gestisce delle risorse necessarie al progetto. Sembrerebbe scontato, ma è opportuno asserire che per modellare è necessario conoscere sia i modelli che gli strumenti. In genere non esiste quello ideale, tutto dipende dagli obiettivi. Talune volte sono sufficienti un pennarello e una lavagna o una matita e un foglio di carta, altre volte è preferibile avere un dispositivo che proietti quanto è oggetto dello studio su uno schermo o in realtime in un sito remoto del mondo. Alcune volte sono sufficienti pochi minuti per realizzare un modello sufficiente; altre volte ci vogliono settimane. Importante è cercare di riutilizzare manufatti esistenti, patterns ecc.

UML dalla teoria alla pratica

45

In primo luogo è bene chiarire gli obiettivi fondamentali dell’attività di modellazione: • aumentare la propria conoscenza del sistema esplorando soluzioni; • comunicare. Disegnare diagrammi aiuta a formalizzare e a discutere eventuali problemi, magari con persone dislocate all’altro capo del mondo. Si capisce come risulti assolutamente necessario cercare di realizzare modelli più semplici possibile, magari concentrandosi su singole parti del sistema. Molto spesso è importante realizzare modelli collettivamente con il proprio team al fine di rendere tutti consapevoli delle problematiche e delle collaborazioni necessarie tra le varie componenti del sistema. Fortemente consigliato è utilizzare pattern ogni qualvolta risulti possibile: si tratta di soluzioni eleganti, ben collaudate, facili da comunicare, e così via. Altra pratica fortemente consigliata è rendere disponibili a tutto il personale coinvolto nel progetto i modelli prodotti. Ciò aiuta ad aumentare la comunicazione e a ricevere preziosi feedback. Ciò è ottenibile sia preparando un opportuno sito Intranet/Internet sia, eventualmente, utilizzando un’apposita bacheca in cui affiggere le riproduzioni cartacee dei vari modelli prodotti. Sebbene la collezione di valori, princìpi e pratiche proposte — quelle riportate sono solo una parte — derivi dal buon senso e dall’esperienza di ogni sviluppatore di sistemi, la relativa catalogazione ed esposizione in un quadro organico è sicuramente un lavoro di grande interesse che finisce per fornire la mancante formalità al processo dell’eXtreme Programming.

I restanti processi Un altro processo degno di considerazione è l’OPEN Process. Il nome del processo già di per sé dovrebbe fornire diverse indicazioni circa i relativi obiettivi e gli artefici dello stesso. In effetti, si tratta di un processo ideato presso l’“OPEN Consortium” (consorzio aperto), che raggruppa un insieme di persone e di organizzazioni animate dall’obiettivo comune di accrescere e migliorare l’utilizzo della tecnologia Object Oriented. Dati i presupposti, è evidente che i destinatari principali dell’Open Process sono le organizzazioni interessate nello sviluppo di sistemi Object Oriented o Component Based. L’Open Process è indipendente dai particolari linguaggi di modellazione, e pertanto qualsiasi linguaggio in grado di rappresentare modelli Object Oriented è idoneo, quantunque alla fine gli unici utilizzabili siano lo UML e l’OML (Object Modelling Language). L’OML è un linguaggio di modellazione ideato anch’esso dall’OPEN Consortium, che a lungo si è opposto allo UML nella corsa alla conquista della standardizzazione. Chiaramente si trattava di una competizione dall’esito scontato in quanto opponeva un linguag-

46

Capitolo 1. UML: che cosa

è, che cosa non è

gio (lo UML) fortemente sostenuto dagli investimenti di un’azienda che su di esso ha basato il proprio successo, all’altro (lo OML) affidato alla buona volontà delle persone che hanno collaborato al progetto. Il ciclo di vita del sistema software è denominato “contract driven”, in quanto ciascuna attività è vincolata a contratti dichiarati che quindi, finiscono per rendere il processo guidato dalle responsabilità (responsability-driven). Uno dei pregi del progetto è la relativa capacità di adattarsi ad organizzazioni che, generalmente, sviluppano più progetti contemporaneamente: esempio tipico sono le software house. Il processo, in prima analisi, può essere scisso in due grosse categorie di attività: quelle da svolgersi per il singolo progetto e quelle trasversali, ossia comuni a tutti i progetti. Le attività appartenenti alla seconda categoria supportano la “gestione del programma” (Programme Management), dove per programma si intende un insieme di progetti e/o versioni. Come gli altri processi anche l’Open si presta a essere adattato alle esigenze delle varie organizzazioni e progetti. I punti di forza di questo processo sono: • copre quasi tutti gli aspetti della produzione ingegneristica del software; • è uno standard aperto a cui partecipano liberamente le persone di esperienza e capacità elogiate; A dir il vero il secondo punto è un vantaggio/svantaggio. È un pro in quanto permette al processo di evolvere indipendentemente dalle strategie di mercato, e quindi per esempio gli permette di prevedere strumenti per problemi considerati di minore importanza (disegno della GUI per esempio); però al tempo stesso è un punto a sfavore in quanto, non essendo sostenuto da un’azienda investitrice, verosimilmente è destinato a condividere la sorte dell’OML. Un ultimo processo da menzionare è Object-Oriented Software Process (OOSP, Processo di sviluppo software object oriented). Verosimilmente l’iniziale elaborazione è attribuibile a James Coplien (A Generative Development-Process Pattern Language e Pattern Languages of Program Design) sebbene i recenti miglioramenti e successi siano stati congegnati da Scott Ambler (Process Patterns e More Process Patterns). Il processo OOSP è basato sul concetto di pattern di processi: ossia soluzioni dimostratesi valide nella soluzione di problemi tipici presenti nello sviluppo di sistemi software. Ciascuno di essi è costituito da una collezione di tecniche, azioni e attività che permettono di risolvere problemi specifici del processo software prendendo in giusta considerazione fattori ed eventuali vincoli presenti. Tali processi operano su tre diversi livelli di astrazione: fase (phase), stadio (stage) e attività (task). L’OOPS è basato, essenzialmente, su quattro fasi:

UML dalla teoria alla pratica

47

1. iniziale (initiate) composta dagli stadi: giustificazione (justify), definizione e validazione dei requisiti iniziali (define and validate initial requirements), definizione iniziale dei documenti di gestione (define initial management documents) e definizione infrastruttura (define infrastructure); 2. costruzione (construct) formata dagli stadi: modellazione (model), verifiche in piccolo (test in the small), generalizzazione (generalize) e programmazione (program); 3. consegna (deliver) costituita dagli stadi: verifiche in larga scala (test in large), rielaborazione (rework), rilascio (release) e assestamento (assest); 4. mantenimento e supporto (maintain and support) che come sancito dal nome è modellata dagli stadi di supporto e identificazione dei difetti e migliorie (indetify defects and enhancements). Anche l’OOSP è un processo iterativo, in cui le iterazioni avvengono nel contesto delle fasi, che vengono eseguite in serie. Come l’open process, l’OOSP prevede una serie di attività da svolgersi attraverso i progetti, ossia a livello di “programma”. Queste attività prevedono controllo qualità, gestione del progetto, formazione del personale, gestione dei rischi, gestione del personale, ecc. Chiaramente molte attività richiedono di essere svolte sia nell’ambito del singolo progetto sia in quello, più generale, di tutti i progetti attivi presso l’organizzazione. Per esempio è importante effettuare la valutazione (e gestione) dei fattori di rischio dei progetti sia nel dominio dei singoli, sia nella loro totalità: un insieme di progetti relativamente rischiosi potrebbe generare un rischio molto elevato a livello di organizzazione. I vantaggi del processo sono: 1. operare a livello di singolo progetto e a quello di portafoglio di progetti; 2. considerare tutti i processi necessari per lo sviluppo, la consegna e il mantenimento in esercizio dei sistemi prodotti; 3. includere tecniche dimostratesi valide nella realizzazione dei processi e nella segnalazione di condizioni che potrebbero generare effetti indesiderati.

I tool Tool UML L’intento predominante che ha guidato tutto il lungo processo di stesura del presente libro è stato quello di tentare di suscitare, nel maggior numero possibile di lettori non

48

Capitolo 1. UML: che cosa

è, che cosa non è

ancora espertissimi dello UML, un qualche interesse nel disegnare modelli di sistemi Object Oriented. Sia chiaro che l’importante è progettare un sistema, usando poi un qualsivoglia formalismo, ma visto e considerato che esiste uno standard universalmente accettato, elegante, ben provato, perché non utilizzarlo? A questo punto il passo successivo consiste nel reperire un tool commerciale da utilizzarsi come ausilio nella modellazione di sistemi software… Fino a qualche tempo fa non c’erano molte alternative: nelle varie aziende di informatica era possibile trovare quasi esclusivamente Rational Rose o TogetherJ.

TogetherJ TogetherJ, sebbene sia un ottimo prodotto può correre il rischio di generare delle perplessità. Il motivo risiede proprio e paradossalmente in quella che dovrebbe esserne una peculiarità: la fusione del processo di disegno con quello di implementazione. Ciò, considerando il programmatore che si nasconde dentro ogni buon disegnatore, finisce per essere una tentazione troppo forte, quasi irresistibile, ad abbandonare prematuramente la fase di disegno per tuffarsi in quella di codifica. Chiaramente si tratta di uno strumento e quindi, se l’utilizzatore lo utilizza in modo errato — la classica zappa sul piede — non si può attribuire la colpa all’applicativo; ma è anche vero che se lo stesso non fa altro che esibire la magnifica mela “tentatrice” è chiaro che prima o poi venga la voglia di morderla… In ogni modo si tratta di un tool di comprovato successo, particolarmente apprezzato per la relativa cura conferita all’aspetto grafico e ai dettagli. Anche la funzione di reverse engineering (o meglio di sincronizzazione in tempo reale tra disegno e codifica) risulta ben congegnata. Probabilmente si tratta di un ottimo strumento per lo sviluppo di sistemi component-based. Qualora si disponga di un buon team di sviluppatori e si sia realizzato almeno il disegno delle interfacce relative ai vari componenti si potrebbe assegnarne l’implementazione di ciascuno di essi a uno sviluppatore, provvedendo un minimo di disegno e affidandosi alla relativa buona volontà ed esperienza. In un contesto di questo tipo, TogetherJ rappresenterebbe effettivamente uno strumento imbattibile. Il passaggio dal modello di disegno alla relativa traduzione in codice non è stato mai — o quasi mai, dipende dai programmatori a disposizione — un problema del processo di sviluppo del software; anzi il tutto è quasi immediato specie con linguaggi di programmazione come Java. La vera fase critica è invece passare dalla vista dei casi d’uso a quella di disegno. Un altro piccolo problema è che il tool è molto Java oriented e ciò lo porta, talune volte, lontano dalle specifiche formali dello UML. Attualmente la TogetherSoft distribuisce un’ottima evoluzione del prodotto denominata Together Controlo Center.

Rational Rose Un discorso a parte va fatto per il software della Rational: è, o comunque è stato, il punto di riferimento degli altri, realizzati a sua immagine e somiglianza. Si tratta indub-

UML dalla teoria alla pratica

49

biamente di un altro livello, anche dal punto di vista economico (tipicamente, costa un’ordine di grandezza in più rispetto agli altri): la relativa esosità, non sempre giustificata, forse ne costituisce la seccatura principale. Probabilmente per sistemi di una certa dimensione esistono poche alternative. La cosa meno comprensibile è che, nonostante Rational Rose sia fornito dall’azienda che ha prodotto le prime versioni dello UML, non sembra aderire sempre alle relative specifiche. Attualmente è disponibile l’evoluzione denominata XDE (eXtended Development Environment, ambiente di sviluppo esteso), nella quale si è cercato di muoversi vero le fasi più di dettaglio del processo di sviluppo del software.

MagicDraw Un altro tool che recentemente ha iniziato a prendere piede nelle varie organizzazioni è MagicDrawUML. Si tratta probabilmente del software che —al momento in cui viene redatto il presente libro (2002) — aderisce maggiormente alle specifiche standard dello UML. La quasi totalità degli esempi forniti i questo libro sono stati realizzati con la versione 3.6, proprio per fornire esempi più standard possibile. È molto accurato, ben congegnato e non trascura alcun diagramma o meccanismo dello UML. L’unico problema è che, verosimilmente, è tanta e tale la frenesia di aderire alle nuove specifiche dello UML e di rilasciare nuove versioni sempre più vicine alle direttive, che esse non sempre sono esenti da un certo numero di bugs. Ciononostante la versione 4 sembrerebbe finalmente aver raggiunto un buon livello di maturità.

Argo UML Un altro tool che merita particolare menzione è Argo UML (www.ArgoUML.org), frutto di un progetto realizzato presso l’Università della California (Computer Science University of California, Irvine). Si tratta veramente di un tool accattivante e ben studiato, soprattutto dal punto di vista dell’interfaccia utente. Propone soluzioni decisamente innovative, accattivanti, completamente diverse rispetto all’approccio classico su cui sono basati gli altri software. Altro grosso vantaggio è che si tratta di un sistema open source e quindi i sorgenti sono di pubblico dominio. Eventualmente è possibile customizzarlo alle proprie esigenze modificandone direttamente il codice. Qualche pecca è imputabile alla non sempre perfetta aderenza alla direttive standard dello UML e alla latenza nell’incorporamento dei cambiamenti di specifiche dovuti alle varie versioni dello UML: si tratta pur sempre di un prodotto gratuito! Questo ne costituisce il punto di forza e la debolezza al tempo stesso: trattandosi di un prodotto open source, non esiste un’azienda che ne faccia un prodotto di punta e quindi: non viene effettuata alcuna operazione di marketing, non vi sono dipendenti (stipendiati) per curare il supporto della clientela, lo sviluppo di nuove funzionalità, il miglioramento ecc. Ciò, d’altro canto, offre altri vantaggi relativi soprattutto al fatto che il prodotto non è vincolato alle leggi di mercato. Per esempio vengono realizzate funzionalità relative ad aree che dal punto di vista del mercato risultano di minore importanza per via del numero di clienti che potrebbero esservi inte-

50

Capitolo 1. UML: che cosa

è, che cosa non è

ressati, il ciclo di vita del prodotto è basato su criteri canonici della progettazione, è possibile avvalersi della collaborazione di personale veramente esperto dislocato nelle più svariate parti del mondo, ecc. A integrazione e modifica di quanto appena riportato, va considerato però che recentemente la software house Gentleware distribuisce una versione di ArgoUML, battezzata Poseidon. Ovviamente esistono altri tool di supporto allo UML e probabilmente qualcuno di questi è veramente formidabile: purtroppo chi scrive ha lavorato a progetti reali solo con quelli appena citati; ma del resto la professione dell’autore non è lo UML tool tester…

Tool per i processi Mentre è possibile reperire svariati tool di supporto dello Unified Modeling Language, lo stesso non si può dire per i processi di sviluppo del software. Nel momento in cui viene redatto il presente libro, l’autore ha avuto la possibilità di utilizzare — sarebbe più opportuno dire “consultare” — un solo prodotto: il software di supporto al processo RUP (Rational Unified Process, Processo Unificato della Rational) fornito, tanto per cambiare, dalla Rational. Per fare chiarezza il RUP è un processo formale scaturito dalla revisione del processo elaborato dai Tres Amigos e da quello denominato Objectory. In questo paragrafo, quando si parla di RUP si fa riferimento al pacchetto software che lo accompagna. Non si tratta di un applicativo vero e proprio bensì di una base di informazioni messe a disposizione dell’utente: una sorta di sito web dello Unified Software Development Process, corredato da tutta una serie di consigli, linee guida, best practices, e così via. Non a caso il pacchetto è corredato da una copia del celebre libro dei Tres Amigos (The Unified Software Development Process). Per il software di supporto al processo di sviluppo che tutti sognano, probabilmente bisogna attendere ancora un po’… Aspirazione di RUP è fornire una valida guida allo sviluppo di sistemi Object Oriented in tutte le relative fasi del ciclo di vita del software. A detta della casa produttrice, il Rational Unified Process è un software di supporto al processo di sviluppo, fornito di un sistema di conoscenza fruibile con tecnologia web based (si tratta di un vero e proprio sito web), in grado di incrementare il livello di produttività dei team e quindi ridurre i tempi necessari per la consegna del software grazie alle linee guida fornite: dalle best practices, ai templates, a un tool in grado di fornire “suggerimenti” in tutte le attività del ciclo di vita del software e così via. Il RUP è basato essenzialmente sui tre concetti: workers (lavoratori), artifacts (manufatti) e activities (attività) illustrate da appositi workflow. Un worker definisce il comportamento e le responsabilità di un individuo o di un team in termini del ruolo ricoperto e quindi le relative attività possono essere svolte da un

UML dalla teoria alla pratica

51

singolo individuo o da un team. I lavoratori vengono suddivisi in categorie quali: analisti, sviluppatori, tester, manager, ecc. che a loro volta vengono suddivisi in ruoli. Tipicamente la categoria degli sviluppatori include architetti, disegnatori, programmatori e integratori di sistemi. Ai vari lavoratori vengono affidate diverse attività: unità di lavoro che i lavoratori devono espletare nel contesto del progetto. La granularità delle attività può spaziare dall’ordine delle ore a quello dei giorni: un’eccessiva minuziosità richiede una lunga e laboriosa programmazione che spesso può risolversi in un inutile spreco di energie, mentre una programmazione superficiale può condurre a una errata stima dei tempi. Come al solito si tratta di individuare il giusto mezzo. Ciascuna attività è decomposta in tre fasi: thinking (elaborazione mentale), performing (esecuzione) e reviewing (revisione) i cui obiettivi sono ben comprensibili dai nomi stessi. Il sistema fornisce tutta una serie di linee guida, consigli e tecniche pratiche al fine di aiutare i lavoratori a conseguire le relative attività. Le azioni da svolgere per implementare le varie attività vengono descritte in termini dei prodotti di input e di quelli da produrre in output. Per esempio, le linee guida fornite dal sistema (RUP) a un programmatore che deve assolvere l’attività di implementazione di un componente, consistono nell’eseguire le seguenti attività: • codificare le operazioni; • implementare gli stati; • utilizzare la delegazione al fine di riutilizzare il codice; • realizzare le associazioni; • codificare gli attributi; • fornire un opportuno feedback al disegno del modello; • valutare il codice. Al fine di espletare le attività assegnategli, ciascun lavoratore necessita di informazioni. Per esempio, per produrre un componente, uno sviluppatore necessita di un disegno con le specifiche. La realizzazione di un’attività produce un qualche tipo di risultato, come le classi prodotte dallo sviluppatore. Le informazioni di input e i risultati prodotti vengono comunemente definiti manufatti. Alcuni esempi di manufatti sono: il modello dei casi d’uso, il modello di analisi, il modello di disegno, il modello di implementazione, il piano dei test, il glossario, il documento di analisi dei rischi, il documento architetturale e così via.

52

Capitolo 1. UML: che cosa

è, che cosa non è

Storicamente il ciclo di vita del software è organizzato a cascata: il progetto passa attraverso una successione di fasi (workflow) a partire dall’analisi del dominio del problema procedendo via via per le fasi di analisi, disegno, implementazione, test e consegna. Nella pratica, i workflow si sono dimostrati non ottimali in quanto eventuali lacune o problemi presenti in una fase emergono tipicamente solo alla fine, durante i test. La sistemazione di tali anomalie può richiedere costosissime e non sempre risolutive iterazioni del workflow stesso, magari perché per colmare una lacuna bisognerebbe scardinare le fondamenta del modello di disegno. I processi più recenti, come illustrato precedentemente, prevedono un approccio iterativo e incrementale: ogni iterazione perfeziona ed espande il sistema che via via assume una forma sempre più completa. In sostanza ogni iterazione prevede lo svolgimento dell’intero workflow. Le prime iterazioni sono decisamente incentrate sull’analisi del dominio del problema e sulla cattura dei requisiti utente, mentre le ultime risultano molto implementative. Man mano che il progetto matura si assiste a una graduale diminuzione delle attività di analisi e progetto a favore dell’espansione delle attività di sviluppo e test. Dal punto di vista della gestione, il progetto può essere suddiviso nelle quattro fasi esaminate nel paragrafo dello Unified Software Developmente Process: inception, elaboration, construction e trasition. Ogni workflow è costituito dalle seguenti parti: • introduzione: illustra gli obiettivi del workflow stesso e le relative relazioni con gli altri; • spiegazione dei concetti necessari per comprendere il workflow; • descrizione di dettaglio del workflow; • overview delle attività: viene fornita la lista delle attività da svolgere corredata dai lavoratori coinvolti; • overview dei manufatti: elenco dei manufatti da produrre corredato dall’attribuzione delle responsabilità; • overview delle linee guida: spiegazioni e consigli su come produrre ed utilizzare i manufatti coinvolti nel processo in corso. Pertanto ogni iterazione del workflow descrive lavoratori, manufatti e attività coinvolte nell’iterazione del ciclo di vita del software. Il sistema prevede diverse sezioni, tra le quali le classiche best practices, white papers — chissà come mai il nome di questa sezione

UML dalla teoria alla pratica

53

finisce per rievocare inesorabilmente memorie delle straordinarie interpretazioni di Totò —, forms, tool mentor, … La sezione dedicata ai modelli (forms) è particolarmente utile in quanto, fornendo templates in molteplici formati per tutta una serie di attività, permette di risparmiare molto tempo. La sezione denominata tool mentor fornisce una serie di consigli e istruzioni su come utilizzare al meglio il software Rational Rose durante tutta la fase di sviluppo del sistema. Ovviamente non sono presenti consigli su come realizzare i vari modelli o come organizzare viste, classi, ecc. ma unicamente quali funzioni utilizzare, dove muovere il mouse e quale pulsante premere. In conclusione si può dire che questo sistema rappresenta una valida risorsa per tutti coloro che desiderano sviluppare seriamente sistemi software Object Oriented. In ultima analisi, si tratta di una fruizione ipertestuale della rivisitazione del libro dei Tres Amigos corredata di qualche riferimento al software Rose.

Ricapitolando… Il presente capitolo è stato dedicato all’illustrazione di che cosa sia e che cosa non sia lo UML e di quali siano le relative aree di applicabilità. L’intento è stato quello di chiarire il prima possibile quali siano gli obiettivi, le aree di interesse e i confini dello UML: nella comunità Object Oriented non sono infrequenti opinioni poco chiare che conferiscono allo UML funzionalità tipiche dei processi di sviluppo del software o che, in maniera diametralmente opposta, lo riducono a semplice linguaggio per la rappresentazione dell’organizzazione statica del software. Secondo le specifiche formali, lo UML è un linguaggio per specificare, costruire, visualizzare e documentare manufatti sia di sistemi software, sia di processi produttivi e altri sistemi non strettamente software. L’UML rappresenta una collezione di best practices di ingegneria dimostratesi vincenti nella modellazione di vasti e complessi sistemi. Il capitolo, considerati anche gli obiettivi, è decisamente voluminoso e non sempre di facile fruizione. Il lato positivo è che si può tranquillamente proseguire nella lettura del testo anche se non lo si è compreso completamente; in altre parole: non è necessariamente propedeutico per la comprensione dei restanti capitoli. Frasi del genere proferite dai docenti all’Università facevano guizzare nella mente degli studenti l’implicazione “quindi è possibile saltare il capitolo!”. In questo caso si consiglia comunque di leggerlo, magari molto rapidamente, e di ritornare a leggerlo in un secondo momento quando si avrà maggiore padronanza dello UML: ciò permetterà di apprezzare maggiormente gli argomenti riportati. Il capitolo si apre con una digressione sul concetto di modello e sulle proprietà a cui si è interessati nel contesto della progettazione di sistemi software, ossia: accuratezza, consistenza, semplicità e manutenibilità. Si illustra il grande vantaggio di ricorrere ai modelli: essendo rappresentazioni puntuali ma semplificate di sistemi reali, permettono comun-

54

Capitolo 1. UML: che cosa

è, che cosa non è

que di studiarne le proprietà di interesse ma, in quanto rappresentazioni, possono essere realizzati molto rapidamente e soprattutto a costi contenuti. Inoltre forniscono una base formale di argomentazione con i clienti. Sebbene i modelli offrano vantaggi indiscutibili, a tutt’oggi, in molte organizzazioni informatiche semplicemente se ne dimentica l’importanza per concentrarsi prematuramente sull’implementazione del sistema. Negli anni che hanno preceduto la presentazione dello UML, la comunità Object Oriented, limitatamente alla sfera dei linguaggi di progettazione, viveva un momento di smarrimento: agli inizi degli anni Novanta si contavano una cinquantina di diversi linguaggi che finivano per alimentare la cosiddetta “guerra dei metodi”. Ciò creava problemi a tutti gli attori operanti nel mondo dell’Object Oriented — dai singoli tecnici, ai clienti, dalle aziende produttrici di prodotti CASE, alle varie consulting, e così via — e finiva per costituire un serio limite al fiorente sviluppo delle tecnologia stessa. Lo UML è nato dallo straordinario lavoro di Grady Booch, James Rumbaugh e Ivar Jacobson, presto divenuti famosi nella comunità informatica internazionale con il nomignolo di Tres Amigos. Inizialmente, l’impegno profuso è stato indirizzato essenzialmente nel tentativo di realizzare un linguaggio di progettazione che unificasse, come suggerito dal nome stesso, quanto di buono era presente nei metodi esistenti o almeno in quelli di maggior successo. In particolare, lo UML è scaturito, principalmente, dalla fusione dei concetti presenti nei metodi di Grady Booch, OMT (Object Modeling Techique di cui Rumbaugh era uno dei principali fautori) e OOSE (Object Oriented Software Engineering /Objectory di cui Ivar Jacobson era uno dei promotori). Lo UML è uno dei pochi esempi di standard nati nel mondo dell’industria (è stato interamente finanziato dalla Rational Software Comporation) per poi approdare ai riconoscimenti accademici: nel 1997 è stato ratificato come standard e posto sotto il controllo dell’OMG (Object Management Group). Contrariamente a convinzioni comuni in molti tecnici, lo Unified Modeling Language, non è unicamente una notazione standard per la descrizione di modelli Object Oriented di sistemi software; si tratta bensì di un metamodello definito rigorosamente che a sua volta è istanza di un meta-metamodello definito altrettanto formalmente. Nelle specifiche formali, la proiezione statica dello UML è specificata dai diagrammi delle classi: una delle tipologie di diagrammi previste dal linguaggio stesso. In altre parole, lo UML è definito per mezzo di sé stesso, o meglio la relativa definizione formale utilizza il linguaggio stesso. In alcuni gruppi di lavoro vi è una certa confusione sul rapporto che lega i linguaggi di progettazione ai processi di sviluppo del software: lo UML è un linguaggio di progettazione e, come tale, è “solo” parte di metodi più generali per lo sviluppo del software. Lo UML è un formalismo utilizzato dai processi per realizzare, organizzare, documentare, … i prodotti generati dalle fasi di cui il processo si compone. Un metodo, tra i vari princìpi, è formato dalle direttive che indicano al progettista cosa fare, quando farlo, dove e perché: un linguaggio invece è carente di ciò. I processi di sviluppo più celebri, tipicamente, sono fondati su un insieme ben definito di “filosofie” quali: Use Case Driven, Architecture Centric e Iterative and Incremental.

UML dalla teoria alla pratica

55

La prima, come suggerito dal nome, è completamente imperniata sui casi d’uso, ossia sulle funzionalità del sistema così come vengono percepite da entità esterne al sistema stesso. Focalizzare fin dalle primissime fasi del ciclo di vita del software e continuare a monitorare l’aderenza dei vari modelli alle richieste del cliente può evitare tutti quei problemi derivanti da un’inefficace dialogo tra il gruppo di tecnici e i clienti. Si tratta della strategia decisamente più ricorrente e, più o meno consciamente, utilizzata da sempre: i sistemi software in primo luogo dovrebbero essere realizzati per soddisfare specifiche esigenze dell’utente. I processi Architecture Centric tentano di creare il sistema partendo dalla progettazione dell’architettura del sistema, la quale viene posta al centro dell’intero processo di sviluppo. Molto importante quindi è stabilire il prima possibile un’architettura ben definita e quindi un embrione di prototipo per poterla valutare. Chiaramente la versione finale del sistema e dell’architettura stessa viene raggiunta attraverso una serie di iterazioni, ma le variazioni dovrebbero essere confinate a rifiniture e non rappresentare stravolgimenti della versione base. Da tenere presente che nella pratica non esistono processi unicamente basati sull’architettura di un sistema: in genere sono ibridi che ospitano anche le direttive derivanti da dottrine Architecture Centric. Gli approcci Iterative and Incremental prevedono di produrre il sistema finale attraverso una serie di successive versioni generate alla fine di opportune iterazioni. Ciò equivale a dire che il progetto si risolve in una successione di miniprogetti completi ognuno con opportune e prestabilite versioni del sistema. I vantaggi derivanti dall’utilizzo di metodi di sviluppo iterativo e incrementali sono riduzione dei costi dovuti a errori o imprevisti generatisi durante una singola iterazione, riduzione del rischio di non produrre il sistema nei tempi previsti, riduzione del tempo necessario per rilasciare il sistema globale, maggiore flessibilità in caso di modifiche dei requisiti utente. I processi di sviluppo del software più interessanti sono molto probabilmente: The Unified Software Development Process (processo unificato di sviluppo software) e ICONIX Use Case Driven. Tipicamente i processi non possono essere utilizzati nella loro forma originale: è necessario eseguirne determinate personalizzazioni al fine di adattarli alla realtà organizzativa, al team che si ha a disposizione, alla natura del progetto, e così via. Il paragone più immediato è con gli abiti: una volta acquistati è necessario effettuarvi dei ritocchi per adeguarli alla propria struttura, stile, ecc. Lo Unified Software Development Process progettato dai Tres Amigos presso la Rational in realtà non è “semplicemente” un processo bensì un framework di un processo generico; si presta a essere utilizzato proficuamente per classi piuttosto estese di sistemi software, differenti aree di applicazioni, diverse tipologie di organizzazioni, svariati livelli di competenza e per progetti di varie dimensioni. Caratteristica peculiare offerta dal processo è la razionale fusione dei tre principali metodi esistenti: Use Case Driven, Architecture Centric e Iterative and Incremental. Forse ciò non solo lo rende unico ma, verosimilmente, anche raro da utilizzare nella sua versione originale: richiede un notevole investimento in termini di tempo.

56

Capitolo 1. UML: che cosa

è, che cosa non è

Il processo presentato dalla ICONIX è anch’esso basato sulla vista dei casi d’uso e utilizza un approccio iterativo e incrementale, però risulta essere decisamente più flessibile, “leggero” e facile da applicare… Magari un po’ meno formale di quello presentato dai Tres Amigos. Per terminare, vengono presentati i prodotti software utilizzabili come supporto al ciclo di vita del software, sia quelli di ausilio alla progettazione del sistema (supporto dello UML) sia prodotti di ausilio al processo di sviluppo. Nel primo gruppo si trovano software quali Rational Rose, MagicDrawUML, TogetherJ, Argo UML mentre come unico esemplare di software di supporto al processo di sviluppo va menzionato RUP (Rational Unified Process).

Capitolo

2

UML: struttura, organizzazione, utilizzo L’esperienza insegna che la capacità di comunicare il comportamento e il disegno di un sistema, prima della relativa implementazione, è cruciale per la buona riuscita dello sviluppo. WALKER ROYCE

Introduzione Scopo del presente capitolo è fornire una panoramica generale dello UML corredata da opportuni esempi. In particolare viene esposta l’organizzazione in viste e vengono presentati molto brevemente i diagrammi di cui ciascuna di essa è composta. Particolare attenzione è stata attribuita ai metodi forniti dal linguaggio UML per estenderne sintassi e semantica. Sebbene esuli decisamente dagli obiettivi del presente libro illustrare dettagliatamente i processi di sviluppo, durante la stesura del presente capitolo ci si è resi conto che un’illustrazione decontestualizzata da un processo di sviluppo avrebbe probabilmente finito per apportare un beneficio relativo al lettore. Pertanto, senza avere la pretesa di scrivere un libro sui processi di sviluppo, si è ritenuto indispensabile prenderne in considerazione uno di riferimento al fine di illustrare più concretamente l’utilizzo dello UML per la modellazione dei relativi manufatti. Tutti coloro che già conoscono lo UML verosimilmente potrebbero venir tentati di procedere direttamente alla lettura del capitolo successivo e potrebbe forse trattarsi di una buona idea. Nonostante ciò, nel presente capitolo sono inserite alcune riflessioni relative ai processi di sviluppo, ai meccanismi di estensione e ai profili che potrebbe valer la pena leggere, magari sull’autobus o in metropolitana Per tutti i meno esperti il consiglio è di non ostinarsi e perder tempo nel tentar di capire da subito tutti i meccanismi presenti in ciascun diagramma: in questo capitolo hanno

2

Capitolo 2. UML: struttura, organizzazione, utilizzo

valenza unicamente introduttiva. L’importante è cominciare a crearsi un’idea delle potenzialità dello UML e prendere familiarità con i vari strumenti messi a disposizione e le relative aree di utilizzo: per ciascuno dei concetti introdotti è presente una trattazione precisa ed esaustiva nel capitolo dedicato all’argomento. Si è colta l’occasione per proporre un primo esempio di progetto completo: un sistema Internet/Intranet per l’acquisto di biglietti del teatro.

La struttura Lo UML presenta una tipica struttura a strati; procedendo dall’esterno verso l’interno, essa è costituita da: 1. viste 2. diagrammi 3. elementi del modello Le viste mostrano i diversi aspetti di un sistema per mezzo di un insieme di diagrammi. Si tratta di astrazioni, ognuna delle quali analizza il sistema da modellare secondo un’ottica diversa e ben precisa (funzionale, non funzionale, organizzativa, ecc.), la cui totalità fornisce il quadro d’insieme. I diagrammi permettono di descrivere graficamente le viste logiche. Lo UML prevede ben nove tipi di diagrammi differenti, ognuno dei quali è particolarmente appropriato a essere utilizzato in particolari viste. Per ciò che concerne gli elementi del modello, essi sono i concetti che permettono di realizzare i vari diagrammi. Alcuni esempi di elementi sono: attori, classi, packages, oggetti, e così via. Durante la fase di definizione dei vari elementi, per i Tres Amigos fu necessario affrontare il problema di stabilire quanti e quali elementi dovessero essere inglobati nel linguaggio e se era il caso di iniziare a catalogare tutti quelli immaginabili. Sebbene da un lato ciò sarebbe stato utile perché ogni applicazione o tecnologia avrebbe avuto il proprio set di simboli, dall’altro il prezzo da pagare sarebbe stato troppo elevato: linguaggio rigido, eccessivamente complesso e comunque carente: si trova sempre un progettista Tizio che abbia la necessità di esprimere qualche concetto e non trova gli strumenti opportuni per farlo. Addirittura si possono incontrare presunti tecnici, “guerrafondai” non paghi della pace raggiunta dopo decenni di guerra dei “metodi di analisi”, che tentano ancora di riaccendere qualche focolaio inventando propri formalismi. Il buon senso, ovviamente portò a preferire un altro approccio: si decise di definire e standardizzare un certo numero di elementi base invarianti (core, nucleo), ritenuti fonda-

UML e ingegneria del software: dalla teoria alla pratica

3

mentali, e di fornire un insieme di altri meccanismi atti a estendere la semantica del linguaggio (stereotypes) per aggiungere documentazione, note, vincoli, ecc. I vantaggi offerti da tale soluzione furono essenzialmente: mantenimento della “semplicità” e, contestualmente, conferimento del dinamismo e della flessibilità necessari per rendere lo UML in grado di adattarsi ai più svariati settori. Come però spesso accade, attraverso riscontri ottenuti dall’applicazione dello UML in progetti reali, fu possibile realizzare che, probabilmente, la soluzione migliore era una via di mezzo: era necessario standardizzare collezioni predefinite di estensioni denominate profili. Quindi a partire dal nucleo base, utilizzando opportunamente i meccanismi di estensione dello UML, — nel momento in cui viene scritto il libro — si stanno definendo una serie di “plug-in” di elementi standardizzati (appunto i profili) dimostratisi particolarmente utili nell’applicazione dello UML in progetti che sfruttano le architetture ricorrenti (EJB, CORBA, ecc.). È possibile pensare ai profili come alle librerie utilizzabili nella stesura del codice. Sebbene ognuno possa definirsi e riscrivere librerie proprie per risolvere particolari problemi, ne esistono tutta una serie predefinite (gratuite), ben testate e universalmente accettate… Eventualmente nessuno vieta di estenderle, ma davvero conviene reinventare la ruota? Il rischio a cui si andava incontro per via della mancanza di tale standardizzazione era una ennesima proliferazione di linguaggi — o meglio dialetti — che avrebbero finito per ridurre molti dei vantaggi offerti dallo UML.

Le viste In prima analisi, lo UML è organizzato in viste, e in particolare è costituito dalle viste Use Case, Design, Implementation, Component e Deployment come illustrato in fig. 2.1. In breve, la prima vista, Use Case View (vista dei casi d’uso), è utilizzata per analizzare i requisiti utente: specifica le funzionalità del sistema come vengono percepite dalle entità esterne al sistema stesso dette Attori. Dunque, si tratta di una vista ad alto livello di astrazione, e di importanza fondamentale sia perché, nella maggioranza dei processi, guida lo sviluppo delle rimanenti, sia perché è il manufatto principale utilizzato per ottenere riscontri dal cliente, sia perché stabilisce le funzionalità che il sistema dovrà realizzare: quelle per le quali il cliente paga. Obiettivo di questo livello di analisi è studiare il sistema considerandolo come una scatola nera: è necessario concentrarsi sul cosa il sistema deve fare astraendosi il più possibile dal come: è necessario individuare tutti gli attori, i casi d’uso e le relative associazioni. La vista dei casi d’uso è costituita da due proiezioni: quella statica catturata dai diagrammi dei casi d’uso e quella dinamica rappresentante le interazioni tra gli attori e il sistema.

4

Capitolo 2. UML: struttura, organizzazione, utilizzo

Figura 2.1 — Diagramma delle viste dello UML. assemblaggio del sisterma gestione della configurazione

vocabolario funzionalità

DESIGN VIEW comportamento

COMPONENT VIEW

IMPLEMENTATION VIEW USE CASE VIEW DEPLOYMENT VIEW

prestazioni scalabilità throughput

topologia del sistema distribuzione consegna installazione

Importante è dettagliare i requisiti del cliente, carpirne i desideri più o meno inconsci, cercare di prevederne i possibili sviluppi futuri, ecc. La Design View (vista di disegno, talune volte indicata come Logical View, vista logica), specularmente alla precedente, descrive come le funzionalità del sistema debbano essere realizzate, in altre parole si analizza il sistema dall’interno (scatola trasparente). Anche questa vista è composta sia dalla struttura statica (diagramma delle classi e diagramma degli oggetti), sia dalla collaborazione dinamica dovuta alle interazioni tra gli oggetti che lo costituiscono (diagrammi di comportamento del sistema). La Implementation View (vista implementativa, detta anche Component View, vista dei componenti) è la descrizione di come il codice (classi per i linguaggi object oriented) debba essere accomunato in opportuni moduli (package) evidenziandone le reciproche dipendenze. La Process View (vista dei processi, detta anche Concurrency View, vista della concorrenza), rientra nell’analisi degli aspetti non funzionali del sistema e consiste nell’individuare i processi e i processori. Ciò sia al fine di dar luogo a un utilizzo efficiente delle risorse, sia per poter stabilire l’esecuzione parallela degli oggetti (almeno quelli più importanti), sia per gestire correttamente eventuali eventi asincroni, e così via. La Deployment View (vista di “dispiegamento”), mostra l’architettura fisica del sistema e fornisce l’allocazione delle componenti software nella struttura stessa.

I diagrammi Lo UML definisce diversi diagrammi (consultare fig. 2.2).

5

UML e ingegneria del software: dalla teoria alla pratica

Figura 2.2 — Diagrammi dello UML.

Use Case (Casi D'uso)

Class Diagram (Diagrammi delle Classi)

Object Diagram (Diagrammi degli Oggetti)

Collaboration Diagram (Diagramma di Collaborazione)

Sequence Diagram (Diagramma di Sequenza)

Activity Diagram (Diagramma di Attivita')

Statechart Diagram (Diagramma degli Stati)

Component Diagram (Diagramma dei Componenti)

Deployment Diagram (Diagramma di Dispiegamento)

Diagrammi strutturali (logico): • Use Case Diagram (diagramma dei casi d’uso, da non confondersi con la relativa vista) • Class Diagram (diagramma delle classi) • Object Diagram (diagramma degli oggetti) Diagrammi di comportamento: • Statechart Diagram (diagramma degli stati)

6

Capitolo 2. UML: struttura, organizzazione, utilizzo

• Activity Diagram (diagramma delle attività) Diagrammi di interazione: • Sequence Diagram (diagramma di sequenza) • Collaboration Diagram (diagramma di collaborazione) Diagrammi di implementazione: • Component Diagram (diagramma dei componenti) • Deployment Diagram (diagramma di “dispiegamento”)

Qualche lacuna... Una prima lacuna individuata dall’autore utilizzando lo UML riguarda il disegno della base dati. Nel mondo dell’ideale non dovrebbero esserci troppi problemi: sia l’architettura software, sia il database dovrebbero essere Object Oriented, quindi mapping uno–a– uno o quasi. In queste circostanze il disegno della base dati deriva direttamente dal modello ad oggetti del dominio e quindi si presta a essere descritto attraverso i diagrammi delle classi. Nella realtà però, i database relazionali sono ancora la stragrande maggioranza per tutta una serie di motivi: molto spesso si ha a che fare con sistemi legacy che — nei casi migliori — utilizzano database relazionali; non sempre i database OO sembrerebbero offrire performance paragonabili ai rispettivi database relazionali; esiste ancora una certa ignoranza e quindi timore nei confronti dei database Object Oriented, e così via. Comunque sia, il risultato è che i database relazionali sono molto ricorrenti nei progetti reali, e da qui nasce la lacuna dello UML di non prevedere apposito formalismo per la descrizione del disegno logico e fisico della base di dati. Si spera che questa lacuna venga colmata con la versione 2 dello UML; ma nel frattempo cosa fare? Un consiglio potrebbe essere di cercare di adattare la metodologia EntityRelationship. Questo consiglio rientra nella categoria di quelli che suggeriscono al progettista di adoperare i modelli nella maniera più confacente alle proprie necessità. L’idea scaturisce dalla constatazione che, in ultima analisi, i diagrammi delle classi possono essere considerati un’evoluzione dei diagrammi E-R. Nella metodologia EntityRelationship, partendo dai relativi diagrammi si arriva fino al disegno della base di dati, disegno che poi si ottimizza in funzione delle operazioni da compiervi e del relativo carico. Pertanto, attraverso una serie di passaggi successivi, si può giungere fino alla rappresentazione dello schema finale partendo dalla rappresentazione Object Oriented del do-

7

UML e ingegneria del software: dalla teoria alla pratica

minio del problema.1 Per far ciò è necessario applicare tutta una serie di regole ben fissate che però esulano dalla presente trattazione. Sintetizzando, trasportare un diagramma delle classi nella relativa versione E-R dovrebbe essere un lavoro abbastanza immediato e, fatto ciò, si tratterebbe di proseguire applicando placidamente le direttive della metodologia Entity-Relationship. Un’altra tecnica consiste nell’elaborare particolari versioni del formalismo dei Class Diagram stesso. Sebbene ciò possa offrire diversi vantaggi, quali per esempio utilizzare uno stesso tool UML e quindi uno stesso repository anche per questa tipologia di manufatti, d’altro canto porta a generare diagrammi delle classi a dir poco artificiosi. Nel mondo dei database relazionali le relazioni si ottengono per mezzo di relazioni “implicite” realizzate per mezzo di codici. Si consideri la fig. 2.3. Volendo per esempio rappresentare che un record di una tabella A è associato con n record di una tabella B, si potrebbe ricorrere a una relazione di dipendenza tra le classi rappresentanti le due tabelle, esportando esplicitamente l’attributo chiave della tabella A nella tabella B come chiave

Figura 2.3 — Esempio di utilizzo dei diagrammi delle classi per mostrare la struttura logica di un database relazionale. L’esempio di figura vuole avere unicamente valenza dimostratrice. Nella realtà, probabilmente sarebbe stato più opportuno mostrare una relazione tra le entità Squadra e Persona e quindi evidenziare opportune generalizzazioni di quest’ultima (per esempio Uomo, Donna) e un’ulteriore associazione con un’altra entità denominata Funzione. Una persona può recitare più ruoli: Calciatore, Allenatore, ecc.

idSquadra nome ...

è costituita

SQUADRA

Squadra «primaryKey»idSquadra : String name : String ...

1

(1,1)

(1,n)

CALCIATORE

idCalciatore cognome nome ...

Calciatore «primaryKey»idCalciatore : String «foreignKey»idSquadra : String cognome : String nome : String ...

La versione finale della rappresentazione Object Oriented del dominio del problema tipicamente viene realizzata

nel modello di analisi attraverso opportune “varianti” dei diagrammi delle classi. La caratteristica di questi ultimi è visualizzare unicamente le entità effettivamente presenti nella realtà oggetto di studio, corredate dai relativi attributi,

8

Capitolo 2. UML: struttura, organizzazione, utilizzo

“straniera”. Eventualmente si potrebbero utilizzare stereotipi del tipo e . Probabilmente l’autore verrà accusato di essere affetto da un virus di stranezza, o di realizzare software dell’età della pietra, eppure, nel 99% dei sistemi realizzati erano presenti uno o più moduli dedicati all’interfaccia utente. I sistemi, incredibilmente, erogano servizi fruibili dagli utenti e, “stranamente”, l’interazione avviene attraverso opportuni layer di interfacciamento. Si tratta dello strato più esterno: quello che però l’utente vede ed è in grado di valutare, tanto che spesso il giudizio che un utente si costruisce di un sistema è basato principalmente sulla relativa interfaccia utente, sulla sua accuratezza, sulla semplicità di utilizzo, ecc. Ora viene ancora da farsi la solita domanda: perché lo UML non prevede formalismo dedicato alla descrizione dell’interfaccia l’utente? Eppure il relativo studio, qualora necessario, rappresenta uno dei manufatti di una certa importanza (si pensi a sistemi Internet di commercio elettronico) da produrre e sottoporre al vaglio dell’utente. Probabilmente esisteranno altri strani fattori traversali che rendono difficile l’individuazione di uno standard. A questo punto si ritiene divertente riportare una serie di aneddoti relativi al rapporto tra l’autore e la produzione di interfacce utente: Errare humanum est, perseverare diabolicum. Nel primo progetto londinese l’autore temeva che gli inevitabili problemi di abilità linguistiche avrebbero potuto creare notevoli problemi; la situazione era più drammatica del solito: oltre al problema di parlare un linguaggio differente dal committente per via delle diverse competenze (cliente esperto di business con scarso bagaglio tecnico), era proprio la lingua a essere effettivamente diversa! Che fare? Ecco la brillante — si fa per dire — idea: realizzare rapidamente e nottetempo un prototipo con uno strumento quale ad esempio Borland Delphi o MS Visual Basic, da utilizzarsi come piattaforma di dialogo. All’inizio sembrava che l’idea fosse veramente buona. Il prototipo, vera primadonna, era al centro di tutti i meeting e forniva un valido ausilio anche al cliente per capire di cosa avesse effettivamente bisogno: si richiedeva di aggiungere informazioni supplementari, di modificarne altre, di variare le dimensioni di alcuni campi, di modificare le business rules ecc. Tutto bene fino al momento in cui il prototipo fu giudicato sufficientemente “rispondente”. A questo punto ci si accorse che nella mente del cliente si era drammaticamente instaurata la malsana idea secondo la quale il sistema reale era quasi pronto: bastava “riciclare” e sistemare quanto presente nel prototipo per poi consegnare. In altre parole il cliente si aspettava di avere la versione finale del sistema nell’arco di poco più di un mese! A quel punto la catastrofe si era consumata: ci vollero non pochi meeting per far comprendere ciò che in altri ambienti dell’ingegneria è abbastanza ovvio: un prototipo non è un sistema reale… Chi potrebbe pretendere di camminare sul plastico di un ponte o di un centro commerciale? Chissà come mai la solita vocina dice che ci sono manager i quali farebbero anche questo. Secondo progetto londinese: memori di quanto accaduto con il progetto precedente, questa volta si tentò con MS PowerPoint: caspita, tutti sanno che si tratta di un prodotto

UML e ingegneria del software: dalla teoria alla pratica

9

per le presentazioni e non per lo sviluppo del software… l’autore comincia a odiare la vocina che gli urla dentro. Se da un lato non si corse quindi il rischio di ingenerare nell’utente la spaventevole idea che il prototipo fosse quasi il sistema finale, dall’altro non si riuscì — a differenza del caso precedente — a evidenziare le varie procedure del business, la navigazione della GUI, i vari dettagli e così via. In altre parole l’utilità della presentazione realizzata con MS PowerPoint fu molto relativa, a dir poco. Terzo tentativo… Il problema era il seguente: il prototipo realizzato in Delphi o MS Visual Basic risultava molto allettante, ma nessuno, proprio nessuno, voleva più correre il rischio di ingenerare nel cliente strane idee… Cosa fare? La soluzione venne all’autore vedendo un negozio di giocattoli: acquistare un bel modellino in scala del famoso London Bridge. A questo punto si realizzò l’ennesimo prototipo in Delphi, ma questa volta l’autore portò con sé nei vari meeting il ponte giocattolo non perdendo nessuna occasione per indicarlo al fine di evidenziare l’oggetto delle varie analisi: un prototipo, solo un prototipo, nient’altro che un prototipo.

Utilizzo dello UML nei processi di sviluppo Sebbene esuli decisamente dagli obiettivi del presente libro trattare una materia così complessa e fondamentale come quella dei processi di sviluppo — ci vorrebbe almeno un libro solo per questo argomento — si è ritenuto altresì indispensabile prendere in considerazione un processo di riferimento al fine di illustrare in maniera più concreta l’utilizzo dello UML. Spesso alcuni tecnici tendono a confondere lo UML con il processo di sviluppo: come ripetuto più volte lo UML è “solo” un linguaggio di modellazione e come tale si presta a rappresentare i prodotti generati nelle varie fasi di cui un processo è composto. Pertanto lo UML, al contrario dei processi, non fornisce alcuna direttiva (cosa fare, quali attività svolgere, quando, chi ne detiene la responsabilità e così via) su come fare evolvere il progetto attraverso le varie fasi (si veda il Capitolo 1) così come non specifica quali sono i manufatti da produrre, chi ne è responsabile ecc. Nel momento in cui viene scritto il presente libro, non esiste un processo standard universalmente accettato2 né tantomeno esiste un accordo sulle varie fasi e sui relativi nomi. Addirittura non esiste una visione completamente concordante tra il libro dei Tres Amigos The Unified Software Development Process ([BIB08]) e il processo proposto dalla Rational, il RUP (Rational Unified Process), che ne rappresenta una rielaborazione.

2

Sembrerebbe che il processo proposto dalla Rational (RUP) stia di fatto prendendo sempre più piede nelle varie

aziende con non poche difficoltà non sempre ingiustificate.

10

Capitolo 2. UML: struttura, organizzazione, utilizzo

Lavorando come liberi professionisti può anche accadere di dover inventare nuovi nomi per indicare alcune fasi e/o modelli (come per esempio “Detailed Design Model”, modello di disegno di dettaglio), al fine di evitare con il rifacimento del lavoro di urtare la suscettibilità dei colleghi… Come dire che se un manufatto presenta seri problemi, però è stato realizzato da una persona importante, magari dal Chief Architect, allora è meglio escogitare un nuovo nome per il rifacimento piuttosto che pestare i piedi di qualche collega. I processi devono essere sia adattati alle singole organizzazioni in funzione di molti parametri, come per esempio le dimensioni, lo skill tecnico, le diverse figure professionali disponibili e così via, sia dimensionati rispetto alle caratteristiche dei progetti: probabilmente il processo dimostratosi efficace per la costruzione di un grattacielo potrebbe risultare non altrettanto efficace per realizzare una copertura per le macchine. Sebbene per decenni i vari “lavoratori” siano stati considerati attori, componenti sostituibili del sistema di sviluppo, solo recentemente si è cominciato a comprendere come in realtà le cose non siano così. Come dire: un processo di sviluppo del software per essere prevedibile necessita di componenti a comportamento altrettanto prevedibile. A. Cockburn addirittura si esprime in termini di processi incentrati sulle persone (people-centric). In queste situazioni di mancanza di standard, gli autori professionali suggeriscono al lettore di accendere il proprio cervello — switch on the brain… ancora la vocina che si fa sentire! — e di cercare di prendere tutto ciò che viene percepito positivamente e confacente al proprio contesto, eventualmente arrangiandolo in funzione delle proprie esigenze, conferendo eventualmente un’importanza relativa a ciò che viene ritenuto meno importante. Lo stesso processo proposto dai Tres Amigos, applicato fedelmente, probabilmente richiederebbe il triplo del tempo e delle risorse effettivamente necessarie. In tal caso gli stati d’ansia dei vari manager potrebbero risultare giustificabili. Visti gli obiettivi del libro, il processo illustrato di seguito viene presentato focalizzando l’attenzione sui prodotti generati piuttosto che sul processo stesso. In ogni modo, cercando di interpolare i vari processi, con particolare riferimento al processo proposto dai soliti Tres Amigos, una schematizzazione abbastanza plausibile potrebbe essere quella in fig. 2.4. Come emerge chiaramente dal diagramma in fig. 2.4, diversi modelli prevedono due proiezioni: una statica ed una dinamica. Per ciò che concerne la prima, a seconda della fase in cui si trova, è possibile impiegare le seguenti tipologie di diagrammi: casi d’uso, classi, oggetti, componenti, dispiegamento e così via. Per ciò che concerne la proiezione dinamica, è possibile descriverla (sempre in funzione della fase del processo oggetto di studio) attraverso il linguaggio naturale (più opportuno nelle primissime fasi), con appositi modelli e/o schede, mediante gli appositi diagrammi dello UML (sequenza, di collaborazione, delle attività e degli stati). L’evoluzione logica del progetto avviene secondo un approccio classico denominato a cascata: si passa da una fase a quella successiva e i manufatti prodotti in una fase fornisco-

11

UML e ingegneria del software: dalla teoria alla pratica

no l’input delle seguenti. Qualora si evidenzino delle lacune (una fase è stata conclusa prematuramente), è necessario ripercorrere il processo a ritroso fino a raggiungere la fase Figura 2.4 — Utilizzo dei diagrammi UML per produrre i modelli di cui un processo è composto. I modelli evidenziati in figura non corrispondono necessariamente a una successione temporale del processo di sviluppo: molte fasi, che in prima analisi potrebbero considerarsi terminali o in stretta successione, vengono avviate il prima possibile al fine di massimizzare il parallelismo (si veda fig. 2.5). Ciò è utile non solo per logiche legate all’ottimizzazione dell’utilizzo delle risorse, ma anche per aumentare la qualità: ricevere, prima possibile, il feedback da diverse prospettive del modello. Per esempio, le primissime versioni del modello dell’architettura, tipicamente, sono progettate parallelamente alla fase di analisi dei requisiti, al fine di bilanciare i diagrammi dei casi d’uso nel contesto dell’architettura. Si tenga presente che obiettivo del processo presentato è semplicemente avere un riferimento atto a illustrare l’impiego dello UML nell’ambito di un processo organico di sviluppo. Il primo diagramma mostrato nel modello di analisi rappresenta una versione del diagramma delle classi che utilizza particolari stereotipi. Modello Business

Modello dei Requisiti

Modello di Analisi

Modello dei Disegno

Modello Fisico

Proiezione statica

Proiezione statica

Proiezione statica

Modello di dispiegamento

Proiezione dinamica

Proiezione dinamica

Modello di Test

Proiezione dinamica

Modello di dispiegamento

x

OK

x

OK

x

OK

Modello Implementativo

12

Capitolo 2. UML: struttura, organizzazione, utilizzo

Figura 2.5 — Ciclo di vita di un processo di sviluppo del software. Il diagramma in figura è una rielaborazione del processo denominato Enhanced Unified Process (EUP) proposto dalla Ronin Internation Inc. Organizzazione temporale Fasi

Main Process Workflow

Inception Elaboration

Construction

Transition

Production

Business modelling

Organizzazione flussi attività

Requirements Analysis Analysis and Design Implemetation Test Deployment Operations and Supports

Support Process Workflow Configuration and Changes Management Project Management Environment Infrastructures management iterazioni/e 1a 2a preliminari Iter. Iter.

i-ma i-ma+1 i-ma+2 n-ma Iter. Iter. Iter. Iter.

n-ma+1 Iter.

Iterazioni

che permette di colmarla. Chiaramente gli effetti generati dalle correzioni sono proporzionali al numero di fasi a ritroso in cui è necessario risalire: più si risale e maggiore è l’onere necessario per aggiornare i manufatti prodotti. Qualora si utilizzi un approccio iterativo e incrementale, l’intero processo viene suddiviso in sottoprogetti e quindi l’evoluzione avviene sulla base di opportune iterazioni. In ciascun sottoprogetto vengono presi in considerazione un certo numero di casi d’uso o relative frazioni. Man mano che si procede verso fasi più tecniche del processo, risulta fortemente consigliabile cercare di utilizzare modelli sempre più formali, come per esempio i diagrammi dello UML. Per esempio mentre nel modello dei requisiti è del tutto legittimo ricorrere a template per descrivere gli scenari dei casi d’uso, lo sarebbe di meno nel modello di disegno ove è consigliabile utilizzare i diagrammi dello UML dedicati alla descrizione del comportamento dinamico (per esempio Sequence, Collaboration, Activity, ecc.). Gli stessi diagrammi dovrebbero possedere un livello di astrazione decrescente (aumento del livello di dettaglio) procedendo nelle fasi più “interne” dello sviluppo del siste-

UML e ingegneria del software: dalla teoria alla pratica

13

ma: un diagramma di sequenza utilizzato nel modello dei requisiti dovrebbe possedere un livello di astrazione molto elevato, mentre utilizzato nel modello di disegno dovrebbe essere molto dettagliato. Lo UML può essere efficacemente paragonato all’insieme di attrezzi presente, per esempio, in una falegnameria. Ci sono tutta una serie di utensili (i diagrammi) ognuno dei quali risulta particolarmente indicato per compiti specifici, mentre del tutto inadeguato per altri. Volendo avvitare una vite verosimilmente il cacciavite risulta lo strumento più consono, mentre per conficcare un chiodo in una superficie, probabilmente, lo sarebbe di meno (può capitare però che determinati capi progetto non abbiano alcun problema a richiedere di infilare chiodi utilizzando i cacciaviti). Sebbene sia possibile individuare processi più formali o più affini alle necessità delle singole organizzazioni e ai progetti, la schematizzazione proposta in figura dovrebbe risultare piuttosto credibile; eventualmente si potrebbero escogitare nomi diversi, ulteriori manufatti da produrre e così via. Brevemente (ogni fase verrà ripresa nei capitoli successivi) i vari modelli da produrre come risultato delle varie fasi di un processo sono quelli riportati di seguito.

Modello Business Viene generato come risultato della fase di modellazione della realtà oggetto di studio (Business Modeling). L’obiettivo è capire cosa bisognerà realizzare (requisiti funzionali e non), quale contesto bisognerà automatizzare (studiarne struttura e dinamiche e, a tal fine, spesso si utilizzano i famosi Domain e/o Business Object Model), comprendere l’organigramma dell’organizzazione del cliente, valutare l’ordine di grandezza del progetto, individuare possibili ritorni dell’investimento per il cliente, eventuali debolezze, potenziali miglioramenti, iniziare a redigere un glossario della nomenclatura tecnica al fine di assicurarsi che, nelle fasi successive, il team di sviluppo parli lo stesso linguaggio del cliente, e così via. È necessario effettuare il famoso studio di fattibilità, redigere una prima versione dei famosi requisiti del cliente sia attraverso la versione iniziale del modello dei casi d’uso (denominato appunto business) sia per mezzo di documenti elettronici (lista dei potenziali requisiti), ecc. Molto spesso viene avviata anche la produzione del documento dell’architettura software del sistema (SAD, Software Architect Document) aggiornato durante tutto il ciclo di vita del sistema. In questo contesto è di fondamentale importanza riuscire a instaurare una corretta piattaforma di intesa con il cliente: si tratta di una fase non eccessivamente formale in cui tutte le convenzioni utilizzate risultano ben accette purché concorrano effettivamente ad instaurare un dialogo costruttivo con il cliente. Nel mondo ideale gran parte di questo modello potrebbe essere realizzato attraverso i vari diagrammi messi a disposizione dallo UML… Però quanti manager (anche di società informatiche) sono in grado di capire modelli formulati attraverso lo UML? L’interrogazione retorica potrebbe essere posta anche in altri termini: quanti dei vostri manager sono in grado di comprendere modelli espressi per mezzo dello UML? Si provi allora a immaginare la

14

Capitolo 2. UML: struttura, organizzazione, utilizzo

risposta pensando ai clienti i quali, per definizione, possiedono limitate conoscenze informatiche. In molti processi non è prevista la presente fase: si parte direttamente con il modello dei requisiti. In questo contesto si è deciso invece di riportarla sia perché si fa sempre in tempo a eliminarla dal proprio processo, sia perché, tipicamente, prima di avviare il processo di sviluppo vero e proprio è necessario superare una prima attività relativa allo studio di fattibilità, al fine di accertarsi che esista una piattaforma d’intesa economica con il cliente: chissà come mai gli studi di fattibilità non producono mai un esito negativo. Nella peggiore delle ipotesi è sempre possibile aggiustare l’esito con qualche donazione (gli orologi vanno per la maggiore). Nella realtà gli studi di fattibilità dovrebbero venir eseguiti da particolari dipartimenti interni all’azienda committente o da terze parti…

Modello dei requisiti Questo modello viene prodotto a seguito della fase comunemente detta analisi dei requisiti (Requirements Analysis). Scopo di questa fase è produrre una versione più tecnica dei requisiti del cliente evidenziati nella fase precedente. Tipicamente, il modello dei casi d’uso elaborato in questa fase (prodotto da System Analyst e Use Case Specifier) è ancora utilizzato come strumento di dialogo con il cliente: gli Use Case prodotti risultano ancora parlare un linguaggio assolutamente compatibile con il cliente, sebbene il modello venga inserito nel contesto definito dall’architettura (ci si riferisce ad esso con i termini di System Use Case Model). In questa fase è importante far confluire eventuali vincoli presenti e i cosiddetti requisiti non funzionali del sistema (performance, affidabilità, disponibilità, scalabilità, e così via), sebbene alcuni di essi si prestino ad essere inclusi direttamente nei vari Use Case: vengono raggruppati in appositi documenti (SRS, Supplementary Requirements Specifications, specifica dei requisiti supplementari). Altri prodotti particolarmente importanti da produrre sono: stima dei costi e dei tempi di sviluppo dell’intero sistema, assegnazione delle priorità (e quindi suddivisione del processo in iterazioni), primissime versioni/idee relative alla GUI (Graphical User Inetrface), rielaborazioni dei vari Object Model, del SAD, e così via.

Modello di analisi Come è lecito attendersi, questo modello viene prodotto come risultato della fase di analisi i cui obiettivi sono: 1. produrre una versione dettagliata e molto tecnica della Use Case View attraverso opportuni diagrammi delle classi, accogliendo direttive provenienti dalle versioni disponibili del disegno dell’architettura del sistema, che a loro volta dipendono da questo modello. Si assiste alla graduale perdita di generalità dei modelli e si passa a un linguaggio più vicino agli sviluppatori e quindi dotato di una notazione più formale. L’obiettivo del modello varia: a questo punto il fruitore principale è il

UML e ingegneria del software: dalla teoria alla pratica

15

proprio team tecnico e non più il cliente. Si realizza, pertanto, una primissima versione del modello di disegno, generalmente, costituita dalle entità effettivamente presenti nel dominio, interfacce e processi di controllo. In questa fase, tipicamente, non si ha interesse a introdurre ottimizzazioni o razionalizzazioni: l’obiettivo è quello di rappresentare formalmente i requisiti del sistema. 2. analizzare dettagliatamente le business rules da implementare. 3. qualora necessario, si revisiona il prototipo (o comunque una descrizione) dell’interfaccia utente, il SAD, ecc. In molti processi (come il RUP per esempio) la fase di analisi viene aggregata con la fase di disegno.

Modello di disegno Anche in questo caso il modello di disegno è il prodotto della omonima fase, in cui ci si occupa di plasmare il sistema, trasformare i vari requisiti (funzionali e non funzionali) forniti nel modello di analisi in un modello direttamente traducibile in codice. A tal fine è necessario costruire l’infrastruttura intorno ai diagrammi delle classi prodotti nella precedente fase. Sempre in questo stadio, è importante realizzare un modello completo dell’interfaccia utente e della relativa navigabilità. In sintesi, gli obiettivi della fase di disegno sono: 1. acquisire una profonda comprensione di problematiche relative ai requisiti nonfunzionali e ai vincoli dovuti ai linguaggi di programmazione, al riutilizzo di eventuali componenti, ai sistemi operativi coinvolti, a problematiche di distribuzione del carico di lavoro e alla conseguente possibilità di svolgere operazioni in parallelo, al sistema di gestione della base dati, alle tecnologie da utilizzare per l’interfaccia con l’utente, a quelle da utilizzarsi per la gestione delle transazioni, e così via. 2. realizzare un opportuno modello completo in grado da fornire tutte le direttive necessarie per l’implementazione del sistema. 3. stabilire il piano per la decomposizione intelligente dell’implementazione del sistema, al fine di massimizzarne l’evoluzione parallela dei vari sottosistemi. 4. disegnare quanto prima possibile le interfacce esistenti tra le varie componenti del sistema.

16

Capitolo 2. UML: struttura, organizzazione, utilizzo

5. disegnare opportuni modelli al fine di investigare sulle possibili soluzioni attuabili. In genere non è necessario eccedere nel disegno del sistema dettagliando tutte le classi di un package e/o tutti i metodi. Qualora il disegno sia sufficiente per la relativa implementazione e non sia fonte di ambiguità, è del tutto accettabile procedere alla codifica senza sprecare tempo. Chiaramente il livello di dettaglio a cui giungere è inversamente proporzionale al livello tecnico del proprio team di codificatori: al diminuire del secondo deve aumentare il livello di dettaglio; quando poi lo skill tende a zero, allora conviene implementare in proprio e inventarsi esercizi di stile da assegnare al proprio team. Poiché il modello di disegno è molto vicino all’implementazione del sistema, tipicamente risulta soggetto a correzioni, rifiniture e aumento del livello di dettaglio provenienti sia dall’implementazione del modello stesso sia da eventuale refactoring. Il reverse engineering, posto in questi termini, è del tutto accettabile. Può capitare però di chiedere ad alcuni tecnici di visionare il modello di disegno del loro sistema e sentirsi rispondere che verrà prodotto alla fine dell’implementazione attraverso, appunto, reverse engineering. Il roundtrip (si modella, si implementa e quindi si ritocca il modello) è del tutto accettabile (d’altronde solo il codice è allineato con l’implementazione del sistema), mentre il reverse engineering da solo, nella mente dell’autore, è percepito come cosa da evitarsi e spesso del tutto inutile. Si pensi al caso in cui si utilizzino tecniche di reflection nel codice Java. Tra le varie caratteristiche, le classi di questo package permettono di creare oggetti specificando la classe di appartenenza per mezzo del nome impostato in una stringa. In altre parole si chiede di creare un oggetto non attraverso il classico costruttore new, ma specificando il nome (stringa) della classe di appartenenza come parametro di un opportuno metodo. In tal caso i vari tool non sono ancora in grado di ricostruire le dipendenze tra oggetti di questo tipo, e quindi il reverse engineering non mostrerebbe alcun legame tra le classi che incorporano tecniche di reflection e quelle specificate nelle relative stringhe.

Modello fisico Il modello fisico, a sua volta, è composto essenzialmente da due modelli: Deployment e Component. Non si tratta quindi del prodotto di una fase ben specifica, bensì dei risultati di rielaborazioni effettuate in diverse fasi. Il modello dei componenti mostra le dipendenze tra i vari componenti software che costituiscono il sistema. Come tale la versione finale di questo modello dovrebbe essere il risultato della fase di disegno. Non è infrequente il caso in cui però se ne realizzino versioni preliminari in fasi precedenti con l’obiettivo di chiarire i servizi esposti dai vari sottosistemi e quindi di consentire l’evoluzione parallela dei sottosistemi. Il modello di dispiegamento (Deployment) descrive la distribuzione fisica del sistema in termini di come le funzionalità sono ripartite sui nodi dell’architettura fisica del sistema. Si tratta quindi di un modello assolutamente indispensabile per le attività di disegno ed implementazione del sistema. In genere le primissime versioni del modello di dispiegamento vengono create nella fase di elaborazione dei requisiti per consen-

UML e ingegneria del software: dalla teoria alla pratica

17

tire di contestualizzare e bilanciare tra loro i vari modelli: gli Use Case perdono di genericità e incorporano riferimenti architetturali. Il disegno dell’architettura fisica dovrebbe essere realizzato in funzione del modello dei casi d’uso, ossia dei servizi che il sistema dovrà fornire calati nel contesto dei requisiti non funzionali. In realtà, si cerca di partire da soluzioni architetturali mostratesi efficaci in precedenti progetti (pattern architetturali) eventualmente revisionati in funzione di alcune particolari necessità.

Modello di test Nella produzione di sistemi, con particolare riferimento a quelli di dimensioni medie e grandi, è opportuno effettuare test in tutte le fasi. Eventuali errori o lacune vanno eliminate prima possibile — sarebbe ancor più opportuno prevenire — al fine di neutralizzare l’effetto delle relative ripercussioni sul sistema. Se si sta costruendo un ponte è evidente che è opportuno verificare le varie colonne sia dopo la relativa progettazione sia subito dopo la costruzione. Non si vuole di certo correre il rischio di riscontrare manchevolezze dopo avervi posato la sezione relativa alla strada. Quindi è opportuno effettuare test prima di dichiarare conclusa ciascuna fase. In questo contesto si fa riferimento a uno specifico modello atto a verificare la rispondenza di ogni versione del sistema frutto di un’appropriata iterazione. Il modello di test dovrebbe essere generato non appena disponibile una nuova versione stabile dei casi d’uso. Pertanto dovrebbe essere elaborato o dopo il modello dei requisiti o dopo quello di analisi. Chiaramente è opportuno verificare tutti i manufatti; però meritano particolare attenzione quelli che in ultima analisi verranno eseguiti: classi, package, componenti, sottosistemi, script e così via. Nel realizzare il modello di test è necessario: 1. pianificare i test richiesti a seguito di ciascuna iterazione. In questo contesto si parla di test di integrazione (Integration Test) e test di sistema (System Test). I primi vengono eseguiti ogni qualvolta si integrano diverse parti del sistema al fine di verificarne la corretta “collaborazione”. I secondi si effettuano alla fine dell’iterazione e servono per accettarsi che le funzionalità oggetto della iterazione siano rispondenti agli Use Case/scenari presi in considerazione dalla stessa. 2. disegnare e realizzare i test attraverso i Test Cases che specificano cosa verificare e le Test Procedure che illustrano come eseguire i test. Quando possibile, sarebbe opportuno creare componenti eseguibili (test component) in grado di effettuare i test automaticamente. 3. Integrare i test necessari al termine dell’attuale iterazione con quelli eventualmente prodotti per le iterazioni precedenti. Il modello di test quindi specifica come eseguire i vari test (che ovviamente vanno eseguiti nella relativa fase) e come tenere traccia dei risultati e del comportamento. L’obietti-

18

Capitolo 2. UML: struttura, organizzazione, utilizzo

vo è essere in grado, in caso di malfunzionamenti, di fornire opportuno feedback per la rigenerazione dell’errore.

Modello implementativo Questo modello è frutto della fase di implementazione il cui obiettivo è tradurre in codice i manufatti prodotti nella fase di disegno e quindi realizzare il sistema in termini di componenti: codice sorgente, file XML, file di configurazione, scripts, ecc. In particolare è necessario: • pianificare l’integrazione necessaria tra i sottosistemi prodotti durante le varie iterazioni (nel caso classico in cui il processo utilizzi un approccio iterativo e incrementale); • distribuire il sistema come prescritto dal Deployment Diagram (mapping dei componenti eseguibili sui nodi del sistema); • codificare il modello di disegno in classi. A seconda del livello di completezza del disegno, tipicamente accade che l’implementazione richieda un aumento del dettaglio del disegno catturato attraverso il reverse engineering; • realizzare dei package, atti a eseguire gli Unit Test, ossia a verificare il corretto funzionamento di sottosistemi prima di renderli disponibili per l’integrazione. Dalla descrizione del processo sono stati trascurati volutamente tutti i manufatti — tra l’altro importantissimi — relativi alla pianificazione del progetto, in altre parole quelli più a carattere manageriale. Si tratta di una sorta di processo parallelo presente in tutte le fasi del ciclo di vita del software: pianificazione della gestione dei requisiti, assegnazione delle priorità agli Use Case con conseguente pianificazione delle iterazioni, analisi dei rischi — anche questa concorre alla pianificazione delle iterazioni —, continua elaborazione e monitoraggio del SDP (Software Development Plan, piano di sviluppo del software) così via. Chiaramente la buona riuscita di un progetto dipende molto dalla sua corretta pianificazione, anche se taluni manager credono che ciò sia il risultato della padronanza del tool Microsoft Project: che cosa importano i vari numeri (giorni/uomo) o la pianificazione delle attività? L’importante è disporre di un bel diagramma da appendere al muro… Altri manufatti da produrre nelle varie fasi sono i documenti con le linee guida (Guidelines) con indicazioni sul modo di realizzare i vari manufatti stessi. In altre parole si tratta di prodotti funzionali al processo, da riutilizzarsi anche per eventuali progetti futuri. Per esempio è necessario realizzare le guidelines relative agli Use Case (quali stereotipi utilizzare, come descrivere i vari scenari, ecc.). Ogni progetto ha delle proprie problematiche specifiche e quindi si vuole che i vari manufatti alla fine di ogni fase risultino consistenti.

19

UML e ingegneria del software: dalla teoria alla pratica

La critica che più di frequente viene avanzata ai processi formali è di essere eccessivamente burocratici e Document-Oriented, tanto da essere addirittura definiti monumentali (Jim Highsmith). Come suggerisce lo stesso M. Fowler, spesso è necessario “mettere a dieta i processi”, al fine di evitare la produzione di un enorme quantitativo di documentazione che, oltre a richiedere molto tempo per la realizzazione e l’aggiornamento, finisce, paradossalmente, per scoraggiare i tecnici più volenterosi. Come alternativa si assiste a tutto un fiorire di processi per così dire leggeri (Lightweight), più pragmatici, comunque migliori di “metodologie” completamente imperniate sulla “codifica e correzione”. Processi basati su tutta una serie di decisioni a breve o brevissimo termine, validissimi per progetti di piccole dimensioni, che però, applicati in sistemi di dimensioni maggiori, sono in grado di generale sistemi caotici e chiusi. In tali sistemi l’inserimento di nuove funzionalità implica acrobazie e pene indescrivibili nonché il terrore dell’intero team di sviluppo. In fig. 2.6 viene riportato un altro diagramma che visualizza le dipendenze tra i vari manufatti prodotti durante un processo. Ancora una volta non sono stati presentati tutti i manufatti, ma solo quelli ritenuti più interessanti e confacenti ai fini della trattazione (il diagramma è già abbastanza complicato così). Alcuni sono stati condensati in uno stesso package e altri sono stati estrapolati in quanto ritenuti interessanti da visualizzare: si ricor-

Figura 2.6 — Diagramma delle classi dei manufatti prodotti dal processo. (Si tratta di una rielaborazione Object Oriented di quello fornito da Scott Ambler nel libro Process Pattern ([BIB12]).

«GUI» Diagramma di navigazione

«GUI» «refine»

Prototipo GUI (alpha ver)

«GUI»

«deployment diag.»

«class diagram»

«activity diagram»

Prototipo GUI (beta ver)

Deployment

Def. interfacce esterne

Comportamento concorrente

«sequence diagram»

«use case»

«refine»

«use case»

«class diagram»

Modello u.c. requisiti

Modello di analisi

Comportamento dinamico «refine»

Modello di disegno

«componet diag.» Modello dei componenti

Modello del database

Modello implementativo

«class diagram»

«r efi ne »

Modello u.c. business

«collaboration diag.» Collaborazione tra oggetti

«doc»

«class diagram»

«class diagram»

Glossario

Modello ad ogg. business

Modello ad ogg. dominio «statechart diagram» Descrizione stati

«doc» Requisiti non funzionali

«sorgenti» Vincoli

Business rules

Test model «refine»

20

Capitolo 2. UML: struttura, organizzazione, utilizzo

Figura 2.7 — Diagramma delle attività del processo.

Acquisizione ed analisi documentazione

Analisi dominio del problema

Studio requisiti funzionali e non

Analisi vincoli

Studio: ROI, fattibilita', organigramma, ecc.

Stesura glossario

Studio business

Revisione modello business [modello non soddisfacente] [modello soddisfacente] Analisi business rules

Modellazione use case requisiti

Revisione vincoli

Assegnazione priorita'

Def. prima versione interfacce Produz. modello ad oggetti del dominio

Prima definizione test case

Analisi dei requisiti

Prototipazione GUI versione alpha

Indagine soluzioni architetturali

Revisione modello dei requisiti [modello non soddisfacente] [modello soddisfacente] Realizzazione modello di analisi

Affinamento soluzioni architetturali

Aggiornamento modello concettuale

Revisione test case

Revisione / aggiornamento GUI

Revisione business rules

Analisi

Revisione manufatti di analisi [manufatti non soddisfacenti] [manufatti soddisfacenti] Disegno sistema

Revisione architettura

Definizione interfacce

Definizione diag. componenti

Pianificazione integrazione sottosistemi

Disegno

Revisione manufatti di disegno [manufatti non soddisfacenti] [manufatti soddisfacenti] Implementazione sottosistemi

Codifica test

Integrazione sottosistemi

Test sottosistemi

Test di integrazione

Aggiornamento modello di disegno

Verifica versione del sistema [versione del sistema non soddisfacente] [versione del sistema soddisfacente]

Test

UML e ingegneria del software: dalla teoria alla pratica

21

di che l’obiettivo è evidenziare i manufatti realizzabili per mezzo dei meccanismi forniti dallo UML In questo contesto la relazione di dipendenza (freccia tratteggiata) che unisce i vari package ha un significato piuttosto intuitivo: un aggiornamento dell’entità indipendente genera necessariamente modifiche su quella dipendente. Per esempio se si apportano delle modifiche al modello di analisi, verosimilmente è necessario rivedere il modello di disegno. Non sempre la relazione di dipendenza ha un legame così forte. Per esempio la variazione del modello di analisi non sempre genera modifiche in quello della base di dati, però, tipicamente, ne richiede almeno la revisione. I vari package (in questo contesto sono collezioni di manufatti) assumono un colore che ne indica, per così dire, la connotazione UML: quelli evidenziati con un giallo intenso possono essere specificati interamente attraverso i meccanismi dello UML, quelli in giallo tenue possono esserlo solo parzialmente, mentre per quelli bianchi è necessario ricorrere a formalismi diversi, quali per esempio i classici documenti. Per esempio il modello del database, nel caso in cui si consideri un database Object Oriented potrebbe essere specificato attraverso diagrammi delle classi e degli oggetti e quindi per mezzo dello UML, mentre negli altri casi è necessario realizzare un mapping tra tali diagrammi e altri più vicini alla natura del database manager con tecniche non presenti nello UML. I vari package del diagramma tengono conto della proprietà transitiva della dipendenza. Per esempio poiché il modello del database dipende dal modello di disegno che a sua volta dipende dal modello concettuale, è evidente che anche il modello del database dipenda da quello concettuale. Ciononostante, alcune volte, si è deciso di evidenziare tale dipendenza perché, teoricamente, diversi modelli (in questo caso disegno e database) dovrebbero venir sviluppati separatamente e in parallelo per poi essere riallineati. In fig. 2.7 viene riportata un’altra proiezione del processo di sviluppo focalizzata questa volta sulle attività da svolgere. Il formalismo utilizzato è quello degli Activity Diagram (diagrammi di attività). L’interpretazione è abbastanza intuitiva: i rettangoli arrotondati indicano attività da svolgere, i rombi evidenziano la divisione del flusso mentre le linee orizzontali doppie indicano che le attività incluse tra due successive possono essere eseguite parallelamente. Il diagramma si configura ancora come una overview: ogni attività dovrebbe essere illustrata per mezzo di un altro Activity Diagram di dettaglio. Ovviamente l’obiettivo è quello di fornire al lettore un’idea delle varie attività da svolgere nelle varie fasi, senza avere assolutamente la pretesa di essere esaustivi. Nella rappresentazione del processo non si è data alcuna enfasi all’approccio iterativo e incrementale solo al fine di evitare di complicare maggiormente il diagramma. Tipicamente le prime due o tre iterazioni (in funzione delle dimensioni del progetto) sono imperniate sulle prime due fasi: analisi del business e dei requisiti.

22

Capitolo 2. UML: struttura, organizzazione, utilizzo

Le restanti iterazioni invece sono focalizzate sulla realizzazioni di versioni successive del sistema e quindi hanno come dominio le fasi di analisi, disegno, implementazione e test.

Sistemi di sistemi: il progetto che non ti aspetti Per definizione tutti i progetti sono unici. Anche qualora si lavori nel settore dell’ecommerce, dove i pattern utilizzati sono sempre gli stessi, è prassi che ogni progetto abbia Business Rules del tutto esclusive, integrazioni specifiche, architetture di riferimento particolari e così via. Capita sempre insomma di dover affrontare problematiche particolari non presenti in nessun testo: a questa legge empirica non si sottraggono certo i processi di sviluppo. Si prendano in considerazione grandi sistemi quali per esempio quelli delle banche. In questi contesti probabilmente è più opportuno affrontare il processo di sviluppo pensando il progetto in termini di sistemi di sistemi: c’è una serie di sistemi (front office, gestione conti correnti, sistema di banking on-line, treasury, gestione delle eccezioni, e così via) e il sistema globale con funzioni di collante (fig. 2.8). La realizzazione del sistema finale di solito non prevede la realizzazione ex-novo di tutti i sistemi: sarebbe una follia. Verosimilmente, per la maggior parte di essi risulta sufficiente realizzare appositi strati di wrapping: business logic antica in una facciata moderna. In contesti di questo tipo probabilmente utilizzare un processo “tradizionale” potrebbe causare diversi problemi. Il fatto è che ormai i progetti non sono più come quelli di un ventennio fa in cui c’era tutto da fare e quindi era possibile dividere nettamente le varie aree: nelle organizzazioni Figura 2.8 — Schematizzazione di un sistema di sistemi.

A

B

C

Sistema "globale"

C

D

UML e ingegneria del software: dalla teoria alla pratica

23

moderne i vari impiegati crescono con l’infrastruttura informatica fusa nel loro business; essa diventa parte integrante anche della loro forma mentis. Pertanto, affrontare il processo realizzando dapprima il modello dei casi d’uso a livello business, poi requirements, il modello di analisi, ecc. potrebbe risultare alquanto complicato. Per esempio i clienti — in questo contesto gli esperti dell’area business della banca — sono generalmente abituati a pensare alle varie funzionalità in termini di collaborazione tra i sistemi informatici presenti; sanno esattamente, per esempio, quando termina il workflow a carico del front office e quando inizia quello relativo al back office; conoscono di quali altri sistemi si avvale la collaborazione (revaluation, messaggistica, market rates, ecc.) e così via. In queste situazioni, per quale motivo forzare il cliente di astrarsi dalla suddivisione del sistema in sottosistemi? Anzi, magari fossero tutti così i clienti. Cercare di applicare il processo in modo “accademico” potrebbe addirittura portare a realizzare modelli artificiosi e innaturali per gli stessi clienti. Altri problemi potrebbero venire dal tempo e dall’aumento del fattore di rischio dovuto all’affrontare globalmente anche solo la modellazione dei casi d’uso di un sistema di dimensioni così elevate. Una buona idea potrebbe essere quella di sfruttare la percezione della scomposizione architetturale del sistema intrinseca nella concezione del cliente e quindi affrontare n+1 progetti con n uguale al numero dei sottosistemi esistenti e il fattore aggiuntivo dovuto al sistema globale di raccordo (l’integrazione). Il rischio più preoccupante in cui si potrebbe incorrere con approcci di questo tipo, è realizzare un sistema in cui le varie parti non risultino facilmente integrabili tra loro. Ricorrendo però a particolari accorgimenti è possibile gestire questo rischio corrispondente alla mancata integrazione. Alcuni suggerimenti potrebbero essere: • realizzare la versione business considerando globalmente i vari processi — dall’inizio alla fine — senza preoccuparsi dell’organizzazione in sottosistemi, ossia focalizzando l’attenzione sulla sequenza delle attività da svolgere per erogare il servizio, senza però entrare nel dettaglio dell’allocazione delle varie attività ai sottosistemi; • pianificare per tempo un “processo” parallelo per lo studio delle aree in comune tra sottosistemi (integrazione); • definire, prima possibile, le interfacce tra i sottosistemi, e così via. L’approccio descritto risulterebbe particolarmente valido in tutti quei contesti in cui il cliente possiede una chiara comprensione della suddivisione del flusso tra i vari sistemi ed è in grado di specificare le responsabilità e le funzionalità di ciascuno di essi.

24

Capitolo 2. UML: struttura, organizzazione, utilizzo

In ultima analisi, dopo la definizione del modello del business, sarebbe necessario applicare, per ogni sottosistema, lo stesso processo di sviluppo. Ognuno di essi sarebbe caratterizzato dal focalizzare l’attenzione su uno specifico sottosistema e dal considerare i restanti come entità esterne (ossia attori) con cui scambiare informazioni. Decomponendo il modello generale dei casi d’uso, si verifica che determinati servizi, per essere realizzati, necessitano di attraversare più sottosistemi. Nei punti di attraversamento risiedono le varie interfacce. L’individuazione delle stesse è semplificata dalla successiva versione dei casi d’uso: focalizzando l’attenzione su di un sottosistema, quelli restanti diventano attori per lo stesso. L’associazione tra un “attore sottosistema” e quello oggetto di studio avviene rispettando una specifica interfaccia.

In queste tipologie di progetto, potrebbe risultare vantaggioso adeguare parte dell’organigramma alla struttura del sistema. Per esempio, potrebbe essere una buona idea suddividere il personale in tanti team di sviluppo quanti sono i diversi sottosistemi. Prevedere un architetto leader e un analista leader per ciascuno di essi. Ciò al fine di decentrare le varie responsabilità, aumentare la consistenza, creare aree di competenza, disporre eventualmente di team ridotti (costituiti per esempio da tutti i vari architetti leader) atti a studiare problemi relativi a più sistemi (come per esempio lo scambio di messaggi), a evidenziare e utilizzare componenti comuni e così via.

Questo approccio, che dovrebbe risultare naturale in progetti in cui sia preesistente un’organizzazione in sottosistemi, potrebbe altresì essere utilizzato comunque proficuamente anche in casi in cui il sistema da sviluppare risulti oggettivamente grande. Situazioni di questo tipo sono facilmente riscontrabili dalle continue lamentele del team di sviluppo considerato in senso generale: architetti, programmatori, ecc. Le frasi tipiche sono: “il modello è troppo astratto”, “servono maggiori dettagli”, e così via. Il che non significa, necessariamente, che il modello non sia stato realizzato correttamente, anzi… Evidentemente però è necessario produrre un’altra versione del modello, che potrebbe ricollegarsi al caso del “sistema di sistemi”. Volendo procedere con questa tecnica, un’attività importante è quella di individuare correttamente i vari sottosistemi. Nel caso “canonico”, ciò non era necessario semplicemente perché ci si trovava di fronte a una situazione predefinita, da tenere in conto. Ciò risulterebbe del tutto coerente con le direttive dei processi di sviluppo: tipicamente la versione requirements del modello dei casi d’uso è detta system proprio perché si pone il modello del business nel contesto del sistema da realizzare e si accettano eventuali feed back provenienti dal disegno di architettura. Di solito gli architetti esperti non hanno enormi difficoltà nel decomporre il sistema generale in sottosistemi. In ogni modo la tec-

UML e ingegneria del software: dalla teoria alla pratica

25

nica generalmente utilizzata è, a tutti gli effetti, basata sulle direttive Object Oriented: si cerca di costruire sottosistemi intorno a “gruppi di dati” relazionati tenendo conto anche delle relative funzioni. Per questo fine il Domain Object Model assume un ruolo molto importante: attraverso l’ispezione visiva permette di raggruppare “oggetti” strettamente relazionati tra loro e relativamente disaccoppiati dai restanti. Per esempio, se si disponesse di un modello di dominio di un sistema di investimenti bancari, si potrebbe evincere l’esistenza di un gruppo di oggetti strettamente legati ai dati provenienti dalla quotazioni di mercato, di un altro relativo a gruppi di dati (relativamente) statici, di un gruppo relativo alla gestione dei trade, di un altro afferente alla gestione della messaggistica e così via. Chiaramente ciò non significa assolutamente che i vari “mondi” non siano relazionati tra loro. Una buona verifica consiste nel “bilanciare” le ripartizioni in sottosistemi in funzione dei servizi da erogare. Da tener presente che, una volta decomposto il sistema, la fornitura di diversi servizi richiede in genere l’interazione dei sottosistemi attraverso opportune interfacce. Il bilanciamento dalla prospettiva delle funzioni, permette di verificare che un sottosistema soddisfi i criteri base dell’ingegneria dei sistemi: • forte coesione interna; • minimo accoppiamento tra sottosistemi; • comunicazione ridotta al minimo tra sottositemi; • servizi logicamente accomunabili; e così via.

Use Case Diagram I diagrammi dei casi d’uso mostrano un insieme di entità esterne al sistema, dette attori, associati con le funzionalità, dette a loro volta Use Case (casi d’uso), che il sistema dovrà realizzare. L’interazione tra gli attori e i casi d’uso viene espressa per mezzo di una sequenza di messaggi scambiati tra gli attori e il sistema. L’obiettivo dei diagrammi dei casi d’uso è definire un comportamento coerente senza rivelare la struttura interna del sistema. Nel contesto dello UML, un attore è una qualsiasi entità esterna al sistema che interagisce con esso: può essere un operatore umano, un dispositivo fisico qualsiasi, un sensore, un legacy system, e così via. All’interno dei diagrammi degli use case diagram sono illustrate un insieme di funzionalità, gli use case appunto, che il sistema dovrà fornire. Si faccia attenzione a non fare

26

Capitolo 2. UML: struttura, organizzazione, utilizzo

confusione: sia i diagrammi, sia le relative funzionalità vengono chiamati Use Case. Questi elementi possono essere connessi tra loro per mezzo di una serie di relazioni quali generalizzazione, inclusione ed estensione. I diagrammi dei casi d’uso costituiscono il fondamento della Use Case View (ancora una volta use case) e in particolare ne rappresentano la proiezione statica. Tipicamente la vista dei casi d’uso è composta da un insieme di diagrammi use case, i quali sono indipendenti gli uni dagli altri sebbene condividano alcuni elementi. Per illustrare i vari diagrammi forniti dallo UML si è deciso di prendere in considerazione un unico esempio: il sito Internet/Intranet con funzioni di commercio elettronico di un teatro; sito che l’autore avrebbe tanto voluto utilizzare. In questo caso si tratta di un sistema frutto della fantasia e quindi potrebbero evidenziarsi lacune e incongruenze tipiche di sistemi non realizzati: solo il passaggio attraverso le varie fasi del processo permette di effettuare revisioni formali e quindi di evidenziare le immancabili lacune e incoerenze. Il primo diagramma che viene visualizzato è una sorta di indice, o meglio di ricapitolazione delle varie funzioni fornite dal sistema: per ogni caso d’uso visualizzato è lecito attendersi il relativo diagramma di dettaglio.

Sebbene di solito non sia buona pratica procedere per raffinamenti successivi e mostrare diagrammi via via di maggiore dettaglio — si rischia di disegnare il sistema in fasi troppo premature e con strumenti sbagliati: non si devono applicare metodologie quali il Data Flow Diagram — è buona pratica visualizzare il diagramma iniziale di overview.

In esso compaiono Use Case con funzioni di riepilogo e quindi non completamente coerenti con quelli di dettaglio: ciò è legittimo considerando il relativo fine di overview. Per esempio viene mostrato che l’attore Credit Card Authority, è associato allo Use Case vendita biglietti , mentre nella realtà ne è previsto uno appropriato: ottenimento autorizzazione. Analizzando il diagramma dei casi d’uso mostrati in fig. 2.9, si può notare che, in primo luogo, gli attori possono essere suddivisi in due macrocategorie: quelli umani (Addetto allo sportello e Cliente) e quelli non umani (Credit Card Authority e Sistema di back office). La suddivisione degli attori umani del sistema in clienti e addetti allo sportello è dovuta, come si vedrà meglio nel diagramma di deployment, alle modalità con cui sarà possibile fruire del sistema: attraverso qualsiasi stazione remota Internet e per mezzo di apposite postazioni ubicate nelle varie biglietterie.

27

UML e ingegneria del software: dalla teoria alla pratica

Figura 2.9 — Diagramma dei casi d’uso: overview delle funzioni del sistema.

Consultazione calendario spettacoli

Vendita biglietti

Utente Validazione utente

Credit Card Authority

Registrazione cliente

Addetto Sportello

Cliente

Aggiorna profilo cliente

Invio dati biglietto

Invio nuovo calendario

Aggiornamento dati calendario

Sistema di back-office

Produzione dati statistici

Le direttive Object Oriented consigliano, qualora possibile, di inserire attori, eventualmente astratti, atti a raggruppare il comportamento comune di altri attori

In questo contesto ciò si traduce con l’aggiunta di un attore astratto, denominato genericamente Utente, specializzato dai due attori concreti: Addetto allo sportello e Cliente. Dall’analisi degli attori non umani, si può notare che: • l’acquisto on-line di biglietti è fruibile solo ai possessori di carta di credito e le relative transazioni sono subordinate all’autorizzazione fornita da apposito sistema: Credit Card Authority;

28

Capitolo 2. UML: struttura, organizzazione, utilizzo

• il sistema rappresenta una sorta di front office, mentre tutte le attività di contabilità, amministrative, ecc. vengono effettuate da apposito sistema: Sistema di back office (BCS). I due sistemi dialogano tra loro attraverso invio di opportuni messaggi. Per esempio il BCS invia messaggi contenenti aggiornamenti relativi ai calendari degli spettacoli e riceve quelli relativi ai biglietti venduti. In fig. 2.10 viene riportato lo Use Case Diagram relativo alla vendita dei biglietti teatrali.

Una delle leggi non scritte, ma applicate nella pratica da tutti gli utilizzatori dello UML è nota come “legge del 7”. Si tratta del consiglio, proveniente direttamente da studi di psicologia realtivi alla capacità di concentrazione e comprensione, che propone di limitare il numero massimo degli elementi presenti in ogni diagramma a 7 unità.

In questo caso non è stata rispettata per semplici motivi legati alla necessità di presentare un unico diagramma e di presentarlo in un contesto organico. Sebbene gli Use Case Diagram verranno presentati in dettaglio nei prossimi due capitoli, ne viene comunque fornita la chiave di lettura. Le frecce tratteggiate che uniscono i vari casi d’uso rappresentano relazioni di include ed extend. Si tratta di relazioni molto dibattute che verranno argomentate dettagliatamente nell’apposita sede. Per ora basti considerare che un’inclusione ha un significato del tutto equivalente a una invocazione di procedura effettuata in un programma scritto in un qualsiasi linguaggio di programmazione. Ad un certo punto dell’esecuzione di un caso d’uso compare la richiesta di inclusione di un altro incluso, quindi il flusso passa a questo ultimo che viene eseguito completamente e quindi il controllo ritorna allo Use Case chiamante. Per esempio la relazione di inclusione che lega il caso d’uso seleziona posto, a quello di aggiorna mappa disponibilità, sancisce che la funzione di selezione del posto, durante la propria esecuzione, eseguirà un aggiornamento della mappa delle disponibilità dei posti per il determinato spettacolo prescelto. La relazione di extend è molto simile ma più potente: è possibile associarvi un’espressione booleana che deve essere vera per abilitare l’esecuzione dello Use Case: in altre parole l’esecuzione di un Use Case estendente è subordinata al soddisfacimento di una condizione di guardia. Nel caso in cui tale espressione non sia presente (indipendentemente dal fatto che sia visualizzata o meno), ciò equivale a una condizione sempre verificata. Tipicamente, le relazioni di extend vengono utilizzate per distinguere il comportamento opzionale da quello obbligatorio visualizzato per mezzo della relazione di include. Per esempio lo use case ottenimento autorizzazione, nel caso in cui l’autorizzazione venga rifiutata, prevede l’esecuzione della funzione di annullamento della vendita.

29

UML e ingegneria del software: dalla teoria alla pratica

Figura 2.10 — Diagramma dei casi d’uso: dettaglio Use Case vendita biglietti.

consegna diretta

spedizione Sistema di back office

login erogazione biglietto «extend» [validazione non ancora avvenuta]

Addetto sportello

«extend»

notifica gravi anomalie cash

«extend»

vendita biglietti Utente

«extend»

pagamento

«extend»

carta di credito

«extend» «extend»

selezione spettacolo Cliente

annulla vendita selezione posto

«extend» [transazione annullata] «include»

«include» «include»

ottenimento autorizzazione

aggiorna mappa disponibilita'

Credict card authority

Anche se la direzione della freccia della relazione di dipendenza potrebbe risultare innaturale è invece corretta: la relazione di extend prevede che la funzione utilizzata (come per esempio annulla vendita) venga incorporata nell’esecuzione del caso d’uso “invocante” (ottenimento autorizzazione). Di seguito viene riportata la descrizione dell’intero diagramma dei casi d’uso. Per esercizio, si consiglia al lettore, di provare a descriverlo per poi verificare la propria descrizione con quanto riportato di seguito. In primo luogo il caso d’uso di vendita biglietti prevede l’autenticazione dell’utente (login). Si tratta di un extend — e quindi di un comportamento opzionale — in quanto è necessario eseguire il caso d’uso solo se l’utente non sia già stato precedentemente riconosciuto nell’arco della stessa sessione. Poiché è presente lo Use Case di validazione utente, tutti gli altri diventano opzionali: se il riconoscimento fallisce, l’esecuzione termina, e quindi tutte le altre funzioni non devono essere eseguite. Per questioni di leggibilità molte delle condizioni presenti nelle relazioni di estensione non sono state riportate.

30

Capitolo 2. UML: struttura, organizzazione, utilizzo

Nel caso in cui tutto proceda bene, l’utente seleziona lo spettacolo desiderato e quindi i posti desiderati tra quelli disponibili. Come si può notare fin da questi primi scampoli, i diagrammi dei casi d’uso, da soli, non sono in grado di fornire alcuna indicazione circa la dinamica dell’esecuzione, l’ordine di esecuzione degli stessi casi d’uso, né tantomeno permettono di descrivere il comportamento che ciascuno di essi possiede. La selezione del posto include l’esecuzione della funzione di aggiornamento della mappa di disponibilità: i posti “prenotati” non devono ovviamente poter essere selezionati da altri utenti. Selezionati anche i posti, è necessario effettuare il fatidico pagamento. Nel caso la fruizione del sistema avvenga da uno sportello vendita biglietti, il pagamento può avvenire o tramite contante o per mezzo di carta di credito, in tutti gli altri casi (da casa o attraverso apposito chiosco) solo per mezzo di carta di credito. Nel caso in cui il cliente opti per un pagamento tramite carta di credito è necessario richiedere autorizzazione a un apposito sistema esterno, indicato genericamente con il nome di Credit Card Authority. Nel caso in cui l’autorizzazione venga negata, è necessario effettuare una sorta di roll-back: la vendita viene annullata e quindi i posti precedentemente prenotati vengono nuovamente resi disponibili.3 Effettuato con successo anche il pagamento, è possibile effettuare l’erogazione del biglietto. Nel caso dello sportello è possibile fornirlo direttamente al cliente, eventualmente con opportune stampe su biglietti di formato predefinito (come avviene nelle agenzie di viaggi quando si acquista un biglietto aereo). Nel caso in cui il cliente stia fruendo del sistema attraverso le altre modalità, potrà decidere se richiedere il recapito presso l’indirizzo impostato o ritirarlo in uno degli uffici oppure direttamente al teatro. Nel caso in cui si verifichino dei problemi gravi è prevista immediata notifica al sistema di back office. Si suppone che questo preveda opportuni meccanismi (workflow) in grado di tenere traccia dell’accaduto e di avvisare il personale umano ritenuto più adatto a risolvere il problema occorso. I diagrammi dei casi d’uso illustrano la proiezione statica del sistema; quella dinamica è invece affidata ad altri strumenti (come si vedrà nei capitoli successivi dedicati agli Use Case), come per esempio i diagrammi di interazione, di attività, i flussi di attività od opportuni moduli.

3

Nel diagramma di figura lo Use Case ottenimento autorizzazione è collegato all’attore Credit Card

Authority per mezzo di un’associazione senza “verso” (entrambi gli oggetti sono navigabili). Sebbene ciò non costituisca una grave mancanza — e in questo contesto si è fatto ricorso a tale espediente al fine di contenere il livello di complessità del grafico — non costituisce nemmeno un buon disegno: in genere si preferisce dettagliare tale associazione per mezzo di più connessioni dotate di verso. In effetti gli Use Case prevedono unicamente scambi di messaggi per così dire “asincroni”. È consigliabile dettagliare le associazioni con gli attori per una serie di motivi: evidenziare la direzione dell’interazione (chi fornisce informazioni) e quindi rendere i diagrammi più chiari; evidenziare gli attori primari da quelli secondari, evidenziare ulteriori Use Case e in particolare i messaggi che li avviano, e così via.

31

UML e ingegneria del software: dalla teoria alla pratica

Class Diagram Il diagramma delle classi è probabilmente una delle tipologie di diagramma più note e affascinanti dello UML; se non altro è anche quella che l’autore adora maggiormente. L’utilizzo più importante è legato alla realizzazione del modello di disegno, il quale, una volta completato, rappresenta graficamente l’implementazione del sistema eseguibile. Coloro che provengono dal mondo dell’analisi strutturata potranno notare la stretta somiglianza con i diagrammi Entità-Relazioni, di cui i Class Diagram rappresentano l’evoluzione Object Oriented (vi è il fattore comportamentale). Ovviamente il passo non è diretto: nella catena evolutiva dei diagrammi delle classi sono presenti diversi significativi anelli, quali per esempio il formalismo OMT e quello di Booch apprezzabile nel libro capostipite dei Design Pattern ([BIB04]). Obiettivo principale dei diagrammi delle classi è visualizzare la proiezione statica del sistema: sono utilizzati per realizzare il modello ad oggetti del dominio e quello del business. Qualora si utilizzino Database Management System Object Oriented, i diagrammi delle classi ne rappresentano la struttura (schema logico) utilizzata per la persistenza del sistema (negli altri cassi è necessario realizzare un mapping); inoltre supportano i diagrammi degli oggetti, del deployment (dispiegamento), gli Interaction ecc. Come suggerito dal nome, tali diagrammi sono costituiti da un insieme di classi, interfacce, collaborazioni e relazioni tra tali elementi. Esistono diverse tipologie di relazioni con le quali è possibile associare classi, come, la dipendenza, l’associazione, la specializzazione, il raggruppamento in package e così via (fig. 2.11). Il diagramma riportato nella figura rappresenta una possibile organizzazione in package di un’opportuna sezione del modello ad oggetti del dominio. Probabilmente il livello di Figura 2.11 — Organizzazione in package del modello del dominio.

StrutturaTeatro

InfoSpettacoli

ListiniPrezzi

Rappresentazioni

Biglietti

32

Capitolo 2. UML: struttura, organizzazione, utilizzo

Figura 2.12 — Frammento del Domain Model del sistema teatrale. «composite»

Piantina

Settore

ListinoPrezzi

getZona()

e' relativo

1

0..*

getPoltrone() getSettori()

getZoneTariffarie() getTariffeZonaDescr()

possiede

e' suddiviso

nome : String descrizione : String

codice : String descrizione : String

1

Teatro

ApplTipologie

Zona

TemplateFasce

SottoSettore

denominazione : String ragioneSociale :String getCalendario() getCalendari() 1

1..*

a ott ad

e' inerente

prevede

1..*

utilizza e' relativa

TemplateTariffe

codice : String descrizione : String

getSpettacolo() getSpettacoli()

...

getTipologieTariffe() 0..*

utilizza

1..*

Spettacolo codice : String titolo :String

1

InfoSpettacolo

1..*

numero : int 1..*

colleziona 1..*

Tariffa importo : float prevista : boolean

getRappresentazione() 1 1

e' provvisto

1..*

Posto

FasciaTariffaria

codice : String descrizione : String

1

raggruppa

applica

codice : String descrizione :String

formato

1

1..*

1

0..*

CalendarioSpettacoli

1..*

presenta

Biglietto

TipologiaTariffa

codice : String stato getStato()

descrizione : String

Rappresentazione 1..* data : Date orario : Time getBiglietto() getListinoPrezzi()

dispone 1..*

granularità è eccessivo, ossia alcuni package raggruppano non più di un paio di classi e potrebbero essere tranquillamente inglobati in altri. In questo contesto si è comunque deciso di mostrarli per poter meglio illustrare i formalismi dello UML. Questo approccio è stato utilizzato in tutto il libro: elevata granularità a fini espositivi. Se, per ragioni di riusabilità del codice, è opportuno seguire il principio di disaccoppiamento al livello delle classi, lo è ancora di più a livello dei package. Probabilmente è più importante poter riutilizzare interi package piuttosto che singole classi. Da notare che spesso non si parla più di riusabilità del codice, bensì di sostituibilità di opportuni “componenti” al fine di rendere più semplice far “rincorrere” al sistema l’evoluzione del business del cliente. La mutua dipendenza esistente tra i package Biglietti e Rappresentazione non è di certo un buon disegno e pertanto, nella fase di disegno, andrebbe disaccoppiata per mezzo dell’introduzione di opportune interfacce. Le razionalizzazioni dovrebbero essere introdotte con attenzione nei modelli relativi al dominio del problema il cui scopo principale è rappresentare appunto il dominio del problema e cercare di ottenere riscontri dal

33

UML e ingegneria del software: dalla teoria alla pratica

cliente su quanto modellato. Le relazioni tra package mostrate in figura (frecce tratteggiate) illustrano le relative dipendenze: se un package A dipende da uno B, ciò implica che modifiche a quest’ultimo package (indipendente) si ripercuotono sul primo (dipendente). Per esempio una revisione dell’organizzazione interna del package dei ListiniPrezzi verosimilmente richiede una verifica di quello dei Biglietti. Nel diagramma di fig. 2.12 viene proposto un frammento di quello che potrebbe essere il Domain Object Model (modello ad oggetti del dominio). Questa particolare tipologia di modello ha alcune caratteristiche peculiari: • sono presenti unicamente entità effettivamente esistenti nel mondo reale; • nelle varie classi vengono riportati principalmente gli attributi ritenuti più significativi, mentre l’interesse per i metodi è decisamente più marginale. Il diagramma rappresentato risulta piuttosto interessante in quanto permette di illustrare buona parte del formalismo previsto dallo UML per i disegnare i diagrammi delle classi. Si proceda con l’analisi del diagramma a partire dalla classe Teatro, e in particolare si inizi con il considerare la relativa rappresentazione strutturale. Un Teatro dispone di una Piantina la cui unica istanza è utilizzata come raggruppamento, ossia come punto

Figura 2.13 — Frammento del diagramma delle classi relativo all’utilizzo del Design Pattern Composite. «composite»

Settore nome : String descrizione : String

e' suddiviso 0..*

getPoltrone() getSettori()

Zona

SottoSettore

34

Capitolo 2. UML: struttura, organizzazione, utilizzo

di partenza della struttura. Quest’ultima è rappresentata attraverso un celebre Design Pattern denominato Composite (fig. 2.13). Brevemente, si tratta di una soluzione che permette di modellare complessi grafi ad albero (intrinsecamente ricorsivi) in cui compaiono relazioni gerarchiche “tutto–parte”. Nel contesto oggetto di studio la foglia dell’albero (nodo senza figli, elemento “parte” della relazione gerarchica) è rappresentato dalla Zona, mentre i nodi con figli sono rappresentati dai SottoSettori. Quindi, in accordo al diagramma, la Zona è l’elemento più semplice della struttura, quello che raggruppa direttamente i posti. Il Composite in questione permette di asserire, per esempio, che la struttura del teatro è costituita da livelli (istanze della classe SottoSettore) che a loro volta sono suddivisi in altri SottoSettori (centrale, centrale-est, est, centrale-ovest e ovest) suddivisi per Zone (palchetto centrale, palchetto centrale di destra, palchetto centrale di sinistra, ecc.). Si tratta ovviamente di un caso e nulla vieta di disporre di diverse configurazioni, come per esempio che il teatro possa essere diviso in Livelli e Platea (primo livello dell’albero) e quindi in Zone. In un successivo paragrafo viene fornita una visualizzazione di quanto esposto attraverso l’utilizzo dei diagrammi degli oggetti (fig. 2.17). Come si può notare, l’utilizzo del pattern Composite ha permesso di risolvere elegantemente ed efficacemente una struttura altresì complessa, con poco sforzo intellettivo: è sufficiente ricercare nel relativo catalogo una soluzione generale al proprio problema e quindi adattarla alle proprie necessità. L’organizzazione gerarchica, e in particolare il raggruppamento dei Posti per Zone, è particolarmente importante in quanto permette di assegnare opportunamente le Tariffe: è lecito attendersi che un palco centrale abbia una tariffa leggermente diversa da un posto nel loggione. Dall’analisi del diagramma in figura, si può notare che spesso le classi vengono interconnesse da una particolare associazione, detta aggregazione, visualizzata per mezzo di un rombo vuoto posto ad una estremità. La relazione di aggregazione specifica una relazione binaria, detta whole-part (tutto–parte) tra un elemento detto aggregato (il “tutto”) e uno detto costituente (la “parte”). Nella rappresentazione grafica il rombo viene collocato nell’estremità relativa all’entità “tutto”. La peculiarità rispetto a una relazione di associazione semplice è la semantica: l’aggregazione sancisce una relazione decisamente più forte tra le parti associate. Tornando alla classe Teatro , essa può essere collegata a n istanze della classe CalendariSpettacoli: si supponga che lo stesso teatro possa essere utilizzato per diverse tipologie di spettacoli: rappresentazioni teatrali, concerti, show ecc., oppure che gli stessi possano essere divisi per stagioni: il calendario invernale, quello estivo e così via. Ogni calendario è ovviamente costituito da un insieme di Spettacoli (La Tosca, La Traviata, La Locandiera, il concerto dei Vaia Con Dios, lo show Barracuda di D. Luttazzi…) le cui informazioni salienti sono riportate in un’apposita classe denominata

35

UML e ingegneria del software: dalla teoria alla pratica

Figura 2.14 — Frammento del diagramma della classi relativo a Spettacoli e Rappresentazioni.

Piantina getZona() 1 possiede 1

Teatro denominazione : String ragioneSociale :String getCalendario() getCalendari() 1 prevede 0..*

CalendarioSpettacoli codice : String descrizione :String getSpettacolo() getSpettacoli() 1 formato 1..*

Spettacolo codice : String titolo :String getRappresentazione() 1 1

InfoSpettacolo

Rappresentazione presenta 1

data : Date 1..* orario : Time getBiglietto() getListinoPrezzi()

36

Capitolo 2. UML: struttura, organizzazione, utilizzo

InfoSpettacolo. Da notare che volendo descrivere propriamente queste informazioni

sarebbe stato necessario realizzare un apposito modello: per questo nel diagramma dei package è mostrato un apposito package denominato InfoSpettacoli. In questo caso si è deciso di trascurarlo per non complicare ulteriormente la spiegazione.

Figura 2.15 — Frammento del diagramma della classi relativo ai listini prezzi.

Zona

1 raggruppa 1..*

Posto numero : int

Biglietto

TipologiaTariffa

codice : String stato getStato()

descrizione : String

Rappresentazione data : Date orario : Time getBiglietto() getListinoPrezzi()

dispone 0..*

1..*

37

UML e ingegneria del software: dalla teoria alla pratica

Figura 2.16 — Frammento del diagramma delle classi relativo ai biglietti e alle relative tariffe.

Zona

1 raggruppa 1..*

Posto

1..*

numero : int

Biglietto

TipologiaTariffa

codice : String stato getStato()

descrizione : String

Rappresentazione data : Date orario : Time getBiglietto() getListinoPrezzi()

dispone 0..*

In prima analisi si potrebbe distinguere la tipologia di spettacolo (rappresentazione teatrale, concerto, show, ecc.) verosimilmente attraverso una relazione di generalizzazione con tante specializzazioni quante sono le tipologie. Poi si sarebbero dovute formalizzare informazioni come: autori o compositori, direttori o registi o coreografi, gruppo teatrale o orchestra sinfonica o corpo di ballo, ecc. Tipicamente, per ogni Spettacolo possono essere previste diverse Rappresentazioni.

38

Capitolo 2. UML: struttura, organizzazione, utilizzo

Figura 2.17 — Esempio di diagrammi degli oggetti: frammento di un’ipotetica struttura di un teatro.

:Piantina

Platea :SottoSettore

PrimeFile :SottoSettore PrimaFila :Zona SecondaFila :Zona RestantiPrimeFile :Zona FileCentrali :SottoSettore ZonaCA :Zona ZonaCB :Zona FileFondo :SottoSettore ZonaFondo :Zona Piano1 :SottoSettore

. . . Loggione :SottoSettore Centrale :Zona CntOvst :Zona CntEst :Zona Ovst :Zona Est :Zona

A questo punto si giunge alle informazioni relative ai listini prezzi: un Teatro ne può prevedere diversi che possono differenziarsi sia per questioni lucrative, sia per struttura. L’intento principale seguito per la modellazione di questa sezione è stato realizzare un modello abbastanza generale da potersi applicare al maggior numero di casi possibili — si ricordi che si sta realizzando un prodotto — senza però complicare eccessivamente la struttura.

39

UML e ingegneria del software: dalla teoria alla pratica

Un altro elemento preso in esame è l’effettiva analisi della realtà — quella dei teatri londinesi — dove in particolare si può notare che la struttura dei tariffari tende a rimanere fissa (tariffa extra lusso da applicarsi a palchetti centrali e primissime file della platea, tariffa normale per la restante parte della platea, tariffa scontata per il loggione, ecc.) così come gli stessi prezzi tendono a prevedere poche alternative (tariffa di lusso equivalente a 150 sterline per spettacoli più importanti, 100 per quelli medi, 75 per quelli meno blasonati o in periodi dell’anno meno richiesti, ecc.). Se la seguente trattazione dovesse risultare oscura, è possibile far riferimento ai paragrafi successivi e alla fig. 2.18), in cui viene riportato un esempio di listino. In prima analisi un listino prezzi può essere suddiviso in diverse fasce tariffarie (per esempio: Imperiale, Lusso, Normale, Economica, ecc.). Onde evitare di produrre un insieme di oggetti slegati e di dover ogni volta ridefinire tale struttura si è introdotta la classe TemplateFasce. La considerazione è che i diversi listini non dovrebbero differire gli uni dagli altri di molto; pertanto, in una situazione a regime, ci si dovrebbe ridurre a riutilizzare due o tre modelli. La classe TemplateFasce può essere associata a diverse fasce tariffarie. L’utente, per ogni listino da inserire, potrebbe selezionare un template tra quelli esistenti oppure realizzarne uno nuovo, selezionando le fasce tariffarie di interesse. Figura 2.18 — Esempio di diagrammi degli oggetti: illustrazione dei listini prezzi.

AS :ListinoPrezzi

BS :ListinoPrezzi Standard :TemplateFasce

Lusso :FasciaTariffaria

Ordinaria :FasciaTariffaria

Super :FasciaTariffaria

ASL :ApplTipologie

BSL :ApplTipologie

ASS :ApplTipologie

TLusso :TemplateTariffe

Scontata :TipologiaTariffa

Economica :FasciaTariffaria

TSuper :TemplateTariffe

Anziani :TipologiaTariffa

Piena :TipologiaTariffa

L60 :Tariffa

L80 :Tariffa

L50 :Tariffa

L40 :Tariffa

L90 :Tariffa

L22 :Tariffa

L95 :Tariffa

40

Capitolo 2. UML: struttura, organizzazione, utilizzo

L’importanza delle fasce risiede nella loro associazione (n a n) con le zone: in particolare ogni zona, nel contesto di uno stesso listino, viene associata a una FasciaTariffaria, mentre, in generale, può essere associata a diverse: una per ogni listino appunto. Questo è il meccanismo alla base del tariffario: una zona applica una specifica fascia tariffaria, la quale, a sua volta, può essere associata a diverse zone. Pertanto, dopo aver dato luogo alle varie associazioni (Zona–FasciaTariffaria) e aver quotato le varie fasce, anche il listino risulta quotato, ossia si sono quotate tutte le zone e quindi i posti in esse presenti. Da notare come il vincolo molto importante per cui una zona può prevedere una sola FasciaTariffaria all’interno di uno stesso listino non si desume dalla lettura del diagramma; sarebbe pertanto necessario riportarlo esplicitamente, per mezzo dell’OCL (Object Constraint Language). La suddivisione del listino in fasce tariffarie non risulta ancora sufficiente: è necessario tenere in considerazione le varie tariffe applicabili: tariffa piena, scontata, anziani, studenti ecc. Nuovamente queste tipologie vengono raggruppate in opportuni template, definiti dall’associazione della classe TemplateTariffe con la classe TipologiaTariffa. Analogamente al caso precedente, si suppone che, se l’utente prevede un certo numero di tipologie, tenderà a riapplicare le stesse con qualche variazione eventualmente evidenziabile per mezzo di ulteriori template. Quindi, una volta definite tre o quattro versioni del template tariffe, non si dovrebbe aver più bisogno di definirne altre. I vari oggetti TemplateTariffe dipendono sia dal listino di riferimento, sia dalla fascia tariffaria, o meglio ogni listino deve prevedere, per ogni FasciaTariffaria, un’apposita istanza della classe TemplateTariffa da associarvi. A tal fine è stata introdotta la classe ApplTipologie.4 Sebbene non esista nella struttura del listino un’associazione diretta tra le classi TemplateFasce e TemplateTariffa — è necessario definire tale legame in funzione

4

Nel disegno la classe ApplTipologie non è associata direttamente a un’altra, bensì a una relazione tra due classi

e pertanto è un esempio di Association Class (classe associazione). Da un punto di vista implementativo, chiaramente, non è possibile associare una classe a una relazione. Si tenga presente che, in ultima analisi, un’associazione è l’esportazione e/o l’importanzione di un indirizzo di memoria di un oggetto (pardon… di un reference) in un particolare attributo di un altro oggetto. La sua realizzazione, tipicamente, prevede che la classe associazione contenga i riferimenti a entrambe le classi che darebbero vita all’associazione da cui dipende. Nel caso oggetto di studio ApplTipologie, dovrebbe memorizzare i reference degli oggetti istanza delle classi ListinoPrezzi e FasciaTariffaria. La domanda potrebbe essere la seguente: “perché allora non modellare la classe associazione direttamente con due associazioni alle classi da cui dipende?”. La risposta è semplice e risiede nella semantica: la classe associazione sancisce che, se l’oggetto istanza di una delle due classi a cui è associata l’istanza della classe associazione viene distrutto — o deferenziata in caso si consideri Java — anche la classe associazione deve essere distrutta di conseguenza. Con due associazioni a due classi non si evincerebbe questa regola a meno di non inserire esplicitamente appositi vincoli. Questi argomenti verranno debitamente trattati nel capitolo relativo ai Class Diagram.

UML e ingegneria del software: dalla teoria alla pratica

41

del listino che si sta considerando — è stata definita un’associazione di default tra le due. Ciò semplifica l’inserimento di nuovi listini: dovendone introdurre uno nuovo, per ciò che concerne la struttura, l’interazione con l’utente potrebbe essere limitata alla conferma della struttura di default proposta. L’attribuzione delle tariffe vere e proprie dipende da tre fattori: il listino prezzi di riferimento (per esempio listino rappresentazioni più importanti), la fascia tariffaria (per esempio economica), la tipologia di tariffa (per esempio tariffa studenti). Poiché la classe ApplTipologie già dipende dal listino e dalla relativa suddivisione in fasce tariffarie, è sufficiente asserire che gli oggetti Tariffa dipendono dall’associazione delle istanze della classe ApplTipologie con quelle della classe TipologiaTariffa. Così si è eliminata al necessità di dover ricorrere a una relazione tra quattro classi. Nella classe Tariffa è stato introdotto l’attributo prevista il quale con il suo valore permette di discriminare i casi in cui una particolare tipologia deve essere applicata o meno. L’idea alla base è di evitare di dover ridefinire tutta una struttura solo perché una particolare Tipologia in un particolare contesto non debba essere applicata. Per ciò che riguarda la classe Biglietti, si può notare che questi appartengono a un’opportuna MappaDisponibilità, associata a una specifica Rappresentazione e, una volta acquistati, fanno riferimento a una particolare TipologiaTariffa. Per esempio il biglietto è stato acquistato da uno studente e quindi la tariffa applicata è la relativa. In questo contesto i Biglietti esistono indipendentemente dal fatto che siano stati acquistati o meno (non a caso esiste l’attributo Stato), il che dovrebbe essere abbastanza rispondente alla realtà. La relazione con la TipologiaTariffa viene instaurata all’atto della vendita. Naturalmente quando si acquista un biglietto lo si fa in riferimento a una particolare Rappresentazione, e per un posto appartenente a una ben specifica Zona. Ecco ancora una volta che la classe Biglietto rappresenta una classe associazione e dipende appunto dal legame tra la classe Rappresentazione e quella Posto. Quindi, ricapitolando, per ogni Posto e per ogni Rappresentazione è prevista un’apposita istanza della classe Biglietto che, una volta acquistato, ha una specifica Tariffa applicata.

Object Diagram I diagrammi degli oggetti rappresentano una variante dei diagrammi delle classi, tanto che anche la notazione utilizzata è pressoché equivalente con le sole differenze che i nomi degli oggetti vengono sottolineati e le relazioni vengono dettagliate. Gli Objects Diagrams mostrano un numero di oggetti istanze delle classi con i relativi legami riportati in modo esplicito. Per esempio, se nel diagramma delle classi una classe A è associata con n istanze della classe B, nel diagramma degli oggetti vengono visualizzati un opportuno oggetto istanza della classe A ed esplicitamente tutti i legami che lo connettono con gli altrettanti oggetti istanza della classe B.

42

Capitolo 2. UML: struttura, organizzazione, utilizzo

Anche questa tipologia di diagramma si occupa della proiezione statica del sistema e mostra un ipotetico esempio di un diagramma delle classi. Si tratta della famosa diapositiva “scattata” a un istante di tempo preciso, riportante un ipotetico stato di esecuzione evidenziato dagli oggetti presenti in memoria e dal relativo stato.

Pertanto mentre un diagramma delle classi è sempre valido, un diagramma degli oggetti rappresenta una possibile istantanea del sistema valida in un istante di tempo ben preciso. I diagrammi degli oggetti vengono utilizzati essenzialmente per dettagliare le relazioni presenti in diagrammi delle classi ritenute poco chiare o particolarmente complicate: ciò permette di verificare la correttezza logica dei diagrammi delle classi realizzati. Qualora non si riescano a concretizzare formalmente determinate relazioni o non si abbia la certezza che quanto modellato sia effettivamente ciò che si desiderava, è consigliabile realizzare un paio di diagrammi degli oggetti al fine di visualizzare come, in una situazione a regime, i vari oggetti siano relazionati gli uni agli altri.

L’utilizzo dei diagrammi degli oggetti nel corso di una modellazione è tipicamente limitato ma non per questo non importante: talune volte da solo è più esplicativo e preciso di molte linee di commento di un Class Diagram. Il problema? La maggior parte dei tool commerciali (al momento in cui viene scritto il presente libro) sembrerebbe sottovalutarne l’importanza. Per realizzare i diagrammi degli oggetti bisogna ricorrere sempre a qualche artificio, come simularlo attraverso un Class Diagram o un Collaboration. Tipicamente la soluzione migliore è realizzare i diagrammi degli oggetti per mezzo dei diagrammi di collaborazione. Il diagramma riportato nella fig. 2.17 mostra una possibile organizzazione della struttura di un teatro. In una situazione reale, questa dovrebbe essere definita, attraverso un opportuno tool, all’atto della configurazione del sistema e memorizzata in un database. Il sistema all’atto dell’avvio dovrebbe provvedere a caricarla in memoria. Il diagramma visualizza una ipotetica struttura di un teatro attraverso un opportuno grafo ad albero e quindi dimostra l’efficacia del pattern Composite. L’oggetto radice dell’intero albero è un’istanza della classe Piantina, alla quale si è ritenuto non necessario attribuire un nome in quanto ne esiste una sola istanza e quindi non si corre il rischio di confusione. I nodi di primo livello sono Platea, Piano1, Piano2, e così via fino a Loggione. Si tratta chiaramente di istanze della classe SottoSettore, specializzazione di Settore. Per questione di rappresentazione grafica, non viene data la descrizione di tutti gli elementi. La Platea, a sua volta, viene suddivisa in altri settori: PrimeFile, FileCentrali e FilediFondo ancora istanze della classe SottoSettore.

UML e ingegneria del software: dalla teoria alla pratica

43

Il settore PrimeFile , infine, è suddiviso in PrimaFila , SecondaFila e RestantiPrimeFile istanze della classe Zona che a sua volta è una specializzazione del Settore. Per il Loggione invece è stata decisa un’organizzazione diversa: si passa dal Settore direttamente alle Zone senza SottoSettori intermedi. Il diagramma degli oggetti proposto in fig. 2.18, mostra una porzione di due ipotetitici listini prezzi applicabili al Teatro, denominati rispettivamente Alta Stagione (AS) e Bassa Stagione (BS). La notazione grafica utilizzata, o meglio quella relativa ai colori, prevede: • testo e bordi rossi per gli elementi dipendenti dal listino AS; • testo e bordi blu per gli elementi dipendenti dal listino BS; • testo e bordi neri per gli elementi indipendenti dai listini. Come si può notare, entrambi applicano lo stesso template previsto per la suddivisione in fasce tariffarie, denominato Standard e composto dagli oggetti istanza della classe FasciaTariffaria: Super, Lusso, Ordinaria e Scontata. A ciascuno di questi oggetti si può associare un particolare oggetto TemplateTariffe in funzione anche del listino che si prende in considerazione. A tal fine è prevista la classe (associazione) ApplTipologie che appunto associa un listino a un opportuno TemplateTariffe. Nel caso in esame, entrambi i listini applicano, per le fasce tariffarie Super e Lusso, gli stessi template, rispettivamente TLusso e TSuper. La prima prevede l’applicazione delle tipologie di tariffe Piena, Scontata e Anziani, mentre la seconda solamente Piena.

Interaction Diagrams I diagrammi di sequenza e collaborazione — detti anche di interazione in quanto mostrano le interazioni tra oggetti che costituiscono il sistema e/o con attori esterni allo stesso — vengono utilizzati per modellare il comportamento dinamico del sistema. I due diagrammi risultano molto simili e si può passare agevolmente dall’una all’altra rappresentazione (isomorfi). Alcuni tool, tra cui Rational Rose, permettono, dato un Sequence Diagram, di generare l’equivalente Collaboration. Sequence e Collaboration si differenziano per via dell’aspetto dell’interazione a cui conferiscono maggior rilievo: i diagrammi di sequenza focalizzano l’attenzione sull’ordine temporale dello scambio di messaggi, i diagrammi di collaborazione mettono in risalto l’organizzazione degli oggetti che si scambiano i messaggi. Possono essere utilizzati con diversi livelli di astrazione in funzione dell’utilizzo che se ne vuole fare. Li si utilizza nella Use Cases View per modellarne la proiezione dinamica, ossia per fornire l’illustrazione grafica degli scenari (esempi di utilizzo dei casi d’uso). In tale conte-

44

Capitolo 2. UML: struttura, organizzazione, utilizzo

sto il livello di astrazione deve essere necessariamente elevato: ciò non significa che si ignorano i dettagli del funzionamento del processo. Semplicemente l’astrazione è relativa alla mancanza di particolari a carattere più “implementativo”, come per esempio i metodi di una classe da invocare. In questo contesto i diagrammi di sequenza risultano particolarmente utili, mentre quelli di collaborazione lo sono molto meno: verrebbero visualizzati due/tre oggetti con molte connessioni. I diagrammi di interazione vengono utilizzati anche in fase di modellazione (livello di astrazione molto basso) per documentare l’utilizzo di classi, per illustrare come funzionalità complesse siano realizzate per mezzo dell’interazione (scambio di messaggi) tra più oggetti, e così via. In fig. 2.19 viene riportato il Sequence Diagram che illustra l’interazione dinamica tra un cliente e il distributore automatico. Il grande vantaggio offerto, come si riscontra facilmente, è legato alla semplicità di lettura e comprensione; pertanto il diagramma di sequenza si presta a essere utilizzato per illustrare dei comportamenti da sottoporre all’attenzione del cliente. In tal caso i diagrammi devono possedere un elevato livello di astrazione. Come si vedrà meglio nel prossimo capitolo, lo scenario illustrato viene comunemente denominato main success scenario (scenario principale di successo) in quanto illustra un’interazione in cui tutto — magicamente — “funziona bene” e non si verificano errori o eccezioni di sorta. Per esempio le varie verifiche di disponibilità non danno mai esito negativo, così come la richiesta di autorizzazione della transazione viene sempre accordata ecc. Per la visualizzazione delle anomalie e la relativa gestione, a meno di casi semplici, si preferisce realizzare altri diagrammi. L’obiettivo è evitare di realizzare diagrammi confusi e quindi meno leggibili: ciò ne invaliderebbe la peculiarità. Però, se da un lato disporre di una serie di diagrammi permette di mantenere gli stessi semplici e lineari, dall’altro crea problemi nella manutenzione: spesso una modifica al flusso descritto nel main scenario implica l’aggiornamento di tutti i diagrammi. Questo è uno dei motivi per cui spesso gli scenari vengono descritti attraverso opportuni moduli di testo. Nel caso dei diagrammi di sequenza, nella prima riga vengono riportati i gli oggetti che partecipano all’interazione. Da ciascuno di essi parte una linea tratteggiata verticale che rappresenta il trascorrere del tempo, mentre le varie frecce rappresentano lo scambio esplicito dei messaggi. Come si vedrà nell’apposito capitolo esistono diverse tipologie di messaggio. Una prima obiezione che si potrebbe fare è la seguente : “Come è possibile realizzare diagrammi di interazione, i cui elementi principali sono gli oggetti (istanze di classi) nella vista dei casi d’uso? In altre parole, se concetti come classi e oggetti sono oggetto di studio

45

UML e ingegneria del software: dalla teoria alla pratica

Figura 2.19 — Sequence diagram.

:Sistema :Credit Card Authority

:Utente 1: Esegue funzione acquisto biglietti

2: Reperisce lista calendari spettacoli

3: Visualizza lista calendari spettacoli 4: Seleziona calendario spettacolo desiderato

5: Reperisce elenco spettacoli

6: Visualizza elenco spettacoli 7: Seleziona spettacolo desiderato 8: Reperisce inform azioni spettacolo 9: Visualizza inform azioni spettacolo 10: Conferma seleziona spettacolo

11: Reperisce elenco rappresentazioni

12: Visualizza elenco rappresentazioni 13: Seleziona rappresentazione desiderata

14:Genera m appa disponibilita' con prezzario

15: Visualizza m appa 16: Seleziona settori desiderati

17:Determ ina disponibilita' settori

18: Visualizza m odulo dettaglio settori, disponibilita', tariffe 19: Seleziona posti desiderati

20: Verifica disponibilita'

21: Visualizza somm ario posti/tariffe 22: Seleziona tariffa e conferm a 23: Visualizza m odulo richiesta dati transazione

L'utente seleziona la tariffa in quanto potrebbe appartenere ad una delle categorie per le quali sono previste tariffe speciali (studenti, anziani, vip, ecc.)

24: Imposta dati carta di credito 25: Verifica correttezza dati impostati

BUSINESS RULE: la prenotazione fisica avviene solo dopo aver validato i dati relativi alla transazione (prima di richiedere l'autorizzazione della transazione)

26: Riserva biglietti

27: Richiede autorizzazione transazione

29: Autorizza transazione 30: Visualizza m odulo richiesta istruzioni di recapito 31: Imposta dati istruzioni di recapito

33: Com unica conferm a transazione

32: Mem orizza dati

28: Verifica transazione

46

Capitolo 2. UML: struttura, organizzazione, utilizzo

di fasi successive del processo di sviluppo del software (analisi, disegno), come è possibile prevederli già in durante l’analisi dei requisiti utente?”. Le risposte possono essere diverse. Una prima soluzione consiste nell’indicare globalmente il sistema attraverso un unico oggetto (come fatto nel diagramma in figura). Un’altra alternativa è utilizzare la propria esperienza cominciando a dare una “sbirciatina” all’interno del sistema individuando i primi macrosottosistemi. Un’ultima alternativa consiste nel cominciare a realizzare un diagramma delle classi concettuale, ad elevato livello di astrazione, fin dalle primissime fasi del processo di sviluppo. Nella fase di disegno poi si provvederà a rendere tale diagramma sempre più concreto fino a farlo diventare la rappresentazione del codice del sistema. Il diagramma di fig. 2.20 rappresenta un esempio di utilizzo del sequence diagram nel modello del disegno: mostra come gli oggetti istanze delle classi collaborino tra loro per realizzare un determinato servizio, in questo caso la verifica della disponibilità di un posto per una specifica rappresentazione. Nel diagramma si è considerato che, nel modello di disegno, esiste una classe, denominata TicketingServer, in grado di “nascondere” i dettagli della navigazione delle varie classi del modello. Si tratta, nuovamente, di un Design Pattern denominato Façade. In sostanza, ogniqualvolta diverse classi necessitino di navigare attraverso altre classi di uno o più package, al fine di evitare un’eccessiva complicazione delle varie relazioni, si preferisce inserire un’apposita classe “schermante” in grado Figura 2.20 — Sequence Diagram a livello del modello di disegno. GenericClient

:TicketingServer

1: getDisponibilita()

:Teatro

2: getTeatro()

3: getCalendario()

4: getSpettacolo()

5: getRappresentazione()

6: getBiglietto()

7: getStato()

:CalendarioSpettacoli

:Spettacolo

:Rappresentazione

:Biglietto

47

UML e ingegneria del software: dalla teoria alla pratica

Figura 2.21 — Collaboration Diagram della funzione getDisponibilità.

GenericClient

:Teatro 1. getDisponibilita() 2. getTeatro()

3. getCalendario()

:TicketingServer 7. getStato()

4. getSpettacolo()

:Biglietto

:CalendarioSpettacoli 6. getBiglietto()

:Rappresentazione

5. getRappresentazione()

:Spettacolo

di fornire i vari servizi, nascondendo i vari dettagli alle diverse classi “client”. Tra l’altro ciò è particolarmente utile in quanto, qualora i dettagli della navigazione dovessero variare, è sufficiente modificare un’unica classe. Da notare che l’oggetto che usufruisce del servizio, GenericClient, essendo esterno al sottosistema considerato poteva essere indicato come un attore. Ancora una volta il diagramma è piuttosto semplice da leggere: le uniche differenze sono costituite dalle frecce piene che rappresentano delle invocazioni sincrone, tipiche invocazioni di metodi di classi. Si consideri il diagramma delle classi di fig. 2.12. In primo luogo si suppone che la classe TicketingServer sia associata alla classe Teatro, il cui reperimento del riferimento avviene invocando un proprio metodo. Nel caso ci si trovi in una gestione multiteatro sarebbe necessario specificare il codice del teatro desiderato. A questo punto, tramite codice del calendario degli spettacoli desiderato, si reperisce la relativa istanza dalla quale, sempre tramite codice, è possibile selezionare l’istanza dello Spettacolo richiesto. A questo punto, tramite la data, è possibile selezionare la Rappresentazione, da cui, per mezzo del numero del posto, è possibile reperire il relativo biglietto e quindi verificarne la disponibilità.

48

Capitolo 2. UML: struttura, organizzazione, utilizzo

Riassumendo, il metodo getDisponibilità() dovrebbe prevedere i seguenti parametri: eventualmente codice teatro, codice calendario spettacoli, codice spettacolo, data rappresentazione e numero del posto. La presenza dei vari codici non dovrebbe preoccupare più di tanto: verrebbero “nascosti” dall’apposita interfaccia utente. Il Collaboration Diagram riportato in fig. 2.21 illustra la stessa funzionalità del precedente Sequence Diagram (getDisponibilità; è possibile passare dall’uno all’altro molto agevolmente), solo che in questo caso l’aspetto a cui viene conferita maggiore enfasi è la sequenzialità dei messaggi nel contesto dell’organizzazione strutturale degli oggetti. Per questa caratteristica, e per la capacità di mostrare senza grandi problemi molti oggetti nell’ambito di uno stesso diagramma senza disordinare il diagramma, i Collaboration Diagram vengono preferiti in fase di disegno. Il diagramma di collaborazione presentato in fig. 2.22 non presenta grosse novità. L’unico elemento da evidenziare è che il metodo dovrebbe ritornare, nel caso di successo, un array di oggetti istanze di una classe che si potrebbe indicare con il nome TariffeDescritte. Essa dovrebbe possedere come attributi sia il codice e la descrizione della tipologia della tariffa (presente nella classe FasciaTariffaria), sia la tariffa stessa (attributo della classe Tariffa).

Figura 2.22 — Collaboration Diagram della funzione getTariffe().

GenericClient

:Teatro 1. getTariffeZona()

3. getCalendario()

2. getTeatro()

4. getSpettacolo() 8. getListino()

:TicketingServer

9. getTariffeZonaDescr()

6. getPiantina()

:CalendarioSpettacoli

3. getRappresentazione()

7. getZona()

:ListinoPrezzi

:Piantina

:Spettacolo

49

UML e ingegneria del software: dalla teoria alla pratica

Figura 2.23 — Statechart Diagram: ciclo di vita di un biglietto.

Annullato

tempo limite scaduto

Disponibile do / verifica data e ora

[timer = timeout]

autorizazzione pagamento negata

prenotazione

annulla acquisto

Prenotato entry / reset(Timer) do / inc(Timer) transazione acquisto confermata

Acquistato

Statechart Diagrams I diagrammi di stato essenzialmente descrivono automi a stati finiti e pertanto sono costituiti da un insieme di stati, transazioni tra di essi, eventi e attività. Ogni stato rappresenta un periodo di tempo ben delimitato della vita di un oggetto durante il quale l’oggetto stesso soddisfa precise condizioni. I diagrammi degli stati pertanto, modellando la possibile storia della vita di un oggetto, vengono utilizzati principalmente come completamento della descrizione delle classi: concorrono a modellarne il comportamento dinamico. Nulla vieta di utilizzarli anche nelle primissime fasi del processo di sviluppo del software al fine di documentare l’evoluzione attraverso i relativi stati di macrooggetti, con l’accorgimento di mantenere elevato il livello di astrazione. Chiaramente ha senso utilizzare i diagrammi degli stati per illustrare in dettaglio il comportamento delle sole classi i cui oggetti possono transitare per ben definito insieme di stati. Per esempio se un particolare oggetto è un biglietto per un preciso spettacolo, esso può trovarsi nello stato Disponibile (non è stato ancora acquistato o prenotato e lo spettacolo non è andato in scena), Prenotato (il biglietto è stato riservato ed il tempo a dispo-

50

Capitolo 2. UML: struttura, organizzazione, utilizzo

sizione per acquistarlo non è scaduto), Acquistato (l’importo del biglietto è stato versato) e Annullato (il tempo limite per l’acquisto è trascorso). Come si può notare esistono due tipi di transazioni: • provocate dall’esterno (per esempio il Credit Card Authority autorizza la transazione di acquisto e quindi provoca la transizione del biglietto nello stato Acquistato); • che scaturiscono internamente (scade il tempo a disposizione per poter acquistare il biglietto, e lo stesso transita nello stato di Annullato). Il diagramma di fig. 2.23 risulta piuttosto comprensibile: unica nota è che nei vari stati possono essere descritte esplicitamente attività da compiersi all’atto dell’entrata nello stato (entry), durante la permanenza (do) ed in uscita (exit). Per esempio si potrebbe avere la necessità di notificare l’uscita di un oggetto da un ben preciso stato. In quel caso si potrebbe associare la comunicazione di notifica all’uscita dallo stato (exit). Chiaramente i diagrammi degli stati prevedono altri elementi che verranno descritti in dettaglio nel relativo capitolo.

Activity Diagram Gli Activity Diagram (diagrammi delle attività) mostrano l’evoluzione di un flusso di attività, ognuna delle quali è definita come un’esecuzione continua non atomica all’interno di uno stato: un diagramma di attività mostra una procedura o un workflow. Si tratta di una variante dei diagrammi di stato (mostrati nel paragrafo precedente) in cui ogni stato rappresenta l’esecuzione di un’opportuna attività e la transizione da uno stato a quello successivo è generata dal completamento dell’attività stessa (trigger interno). Gli stati evidenziati negli Activity Diagram sono essenzialmente di due tipologie: Activity State (stati di attività) e Action State (stati di azione). I primi consistono in stati che eseguono una computazione la quale, una volta ultimata, genera la transizione allo stato successivo; uno stato di azione, invece, è uno stato atomico (ossia non può essere interrotto).

I diagrammi delle attività possono essere considerati una versione rivista e aggiornata dei paleolitici Flow Chart: per questo risultano particolarmente familiari a un vastissimo numero di persone anche con limitato skill tecnico. Logica conseguenza è che risultano un valido ausilio per documentare funzionalità da sottoporre al vaglio di personale non tecnico: clienti e utenti.

51

UML e ingegneria del software: dalla teoria alla pratica

Figura 2.24 — Activity Diagram acquisto biglietti. Utente

Sistema

Credit Card Authority

Selezione servizio acquisto biglietti Genera modulo "elenco calendari spettacoli" Selezione calendario spettacoli Genera modulo "elenco spettacoli calendario" Seleziona spettacolo Genera modulo "elenco rappresentazioni" Seleziona rappresentazione Genera mappa "disponibilità posti" Seleziona settori Genera dettaglio settori selezionati

P oic he' uno dei crite ri utilizzati per la selezione d el s e t t o re è i l re l a t iv o importo, la mappa dovrebbe riflettere le fasce tariffarie.

Seleziona posti nel dettaglio settori Riverifica disponibilità posti [posti non piu' disponibili]

Aggiunge notifica non disponibilità [posti ancora disponibili]

Determina importo transazione Genera modulo richiesta accredito Imposta dati accredito Verifica formale dati [dati non validi]

Aggiunge notifica dati errati

[dati validi]

Prenota biglietti [posti non più disponibili]

Aggiunge notifica non disponibilità Richiesta autorizzazione Ricezione richiesta

Attesa esito valutazione

[time-out]

Verifica formale richiesta Notifica esito valutazione

Ricezione richiesta

Aggiunge notifica autorizzazione fallita

Valutazione responso [responso negativo]

Annulla prenotazione [responso positivo]

Valuta tempo a disposizione [tempo insufficiente per consegna] [responso positivo]

Genera modulo richiesta indirizzo consegna Imposta indirizzo consegna Genera pagina riassuntiva

52

Capitolo 2. UML: struttura, organizzazione, utilizzo

Tipicamente li si utilizza nella fase di disegno, in particolare quando alcune decisioni implementative — tipo quali attività assegnare agli oggetti istanza di una classe — risultano difficili da prendere. Infatti i diagrammi di attività permettono di enfatizzare la sequenzialità e la concorrenza degli step di cui si compone una particolare procedura.

Component diagrams I Component Diagram mostrano la struttura fisica del codice in termini di componenti e di reciproche dipendenze. Insieme ai Deployment Diagram costituiscono la vista fisica del sistema e vengono comunemente indicati collettivamente con il nome generico di diagrammi di implementazione. In particolare, i diagrammi dei componenti illustrano la proiezione statica dell’implementazione del sistema e pertanto, sono strettamente connessi ai diagrammi delle classi. Ciascun componente rappresenta una parte del sistema, modulare, sostituibile5, deployable, che incapsula implementazione ed espone un insieme di interfacce. Un componente è tipicamente costituito da un insieme di elementi (interfacce, classi, ecc.) che risiedono nel componente stesso. Un certo numero di questi ne definisce esplicitamente le interfacce esterne, ossia la definizione dei servizi esposti e quindi forniti dal componente. Un’area dello UML giudicata particolarmente carente dai vari esperti di sistemi Component-Based, che ha suscitato e continua a suscitare non poche critiche, è proprio quella relativa ai componenti. La definizione attuale fornita dallo UML è effettivamente un po’ restrittiva; ciò però è abbastanza comprensibile se si pensa che la tecnologia dei sistemi Component-Based è relativamente recente e, verosimilmente, nel momento in cui viene scritto il libro, ci si trova ancora all’inizio della relativa curva di apprendimento. L’approccio basato sui componenti è la logica evoluzione della filosofia Object Oriented ed è destinata ad avere un impatto profondo su disegno e implementazione dei sistemi. Probabilmente lo stesso UML finirà — magari non a brevissimo termine — per essere considerato un linguaggio di modellazione Component-Based piuttosto che semplicemente Object Oriented. Con la versione 1.4 molto è stato fatto nella direzione dello studio di soluzioni atte a colmare le lacune evidenziate dallo UML nella modellazione di sistemi basati sui componenti. Ciononostante, ancora diverse carenze risultano irrisolte. Per esempio sarebbe opportuno definire profili da utilizzarsi per la modellazione di sistemi basati sulle architetture Component-Based più famose (EJB, COM+ e CCM), proporre tecniche per modellare il

5

Recenti studi hanno evidenziato come la volatilità dei requisiti, tipica nel 90% dei sistemi, ha finito per minimizzare

il concetto di riusabilità, una volta baluardo dell’Object Oriented. Allo stato attuale si preferisce parlare di sostituibilità dei componenti (in senso generale). La variazione dei requisiti può essere neutralizzata aggiornando o sostituendo uno o più componenti senza dover modificare altre parti del sistema non affette dalla variazione stessa.

53

UML e ingegneria del software: dalla teoria alla pratica

Figura 2.25 — Component Diagram del distributore automatico. «servlet»

ControllerWEB

«EJB»

ServizioDIPrenotazione

BusinessLogic

DatabaseServices «jsp» DatabaseLayer View

contesto dell’esecuzione dei componenti (Containers EJB) e relative comunicazioni, individuare tecniche per modellare l’assemblaggio dei sistemi Component-Based e così via. Nella fig. 2.25 è illustrato il diagramma dei componenti (concettuale) del sistema di ticketing del teatro. Si tratta di una primissima versione da raffinare durante la fase di disegno e da revisionare durante il processo di implementazione. Probabilmente l’esempio è artificioso e i componenti vengono assemblati per tecnologia, il che non sempre rappresenta l’idea migliore. In ogni modo viene presentata una schematizzazione di una versione del Design Pattern MVC (Model-View-Control, modello vista e controllo): lo stesso utilizzato dalle Swing Java, adattato al Web. (Per maggori informazioni è possibile consultare il documento Sun’s Blueprints dor J2EE). In particolare la o le Servlet dovrebbero incaricarsi essenzialmente di fornire dei servizi comuni come autenticazione, login, gestione degli errori, ecc. e di effettuare opportune re-direzioni delle richieste dell’utente (il vero e proprio Controller). Le richieste del client (browser Internet) dovrebbero giungere all’opportuna Servlet, la quale dovrebbe “girare” la richiesta alla classe appropriata (magari servizio EJB) atta alla relativa gestione: agisce in qualche modo da notificatore eventi (Events Dispatcher). Le Servlet si dovrebbero anche incaricare di istanziare opportuni JavaBean da trasmettere (in qualche modo) al client insieme alla pagina HTML generata dall’appropriata JSP (pagine HTML con integrato del codice Java). Chiaramente la Business Logic del sistema dovrebbe essere fornita dagli Enterprise Java Bean: le Servlet dovrebbero utilizzare anche questi elementi per inserire dati in appo-

54

Capitolo 2. UML: struttura, organizzazione, utilizzo

siti JavaBeans. Nel diagramma è presentata una generica macrointerfaccia esposta dagli EJB; ovviamente si tratta di una rappresentazione simbolica: in realtà bisognerebbe evidenziare tutta una miriade di interfacce implementate dai relativi EJB. Per terminare è mostrato un component definito Database Layer, in quanto si assume che la persistenza non venga gestita da Entity Beans bensì da Session. Nella fig. 2.26 è riportato un ulteriore esempio di Component Diagram. Brevemente, il diagramma mostra tre componenti: Prenotazione, ListinoPrezzi e DisponibilitaPosti. Verosimilmente, compito del primo componente è fornire una serie di servizi quali prenotazione di poltrone situate in specifici settori del teatro per determinate rappresentazioni di spettacoli, reperimento di informazioni relative al listino prezzi, verifica disponibilità, ecc. L’esempio, sebbene non completo — probabilmente sarebbe stato opportuno mostrare un maggior numero di componenti opportunamente organizzati — è stato proposto al fine di presentare alcuni elementi dello UML introdotti con la versione 1.4. Questi elementi risultano particolarmente utili per la progettazione e descrizione della struttura dei componenti. In particolare gli stereotipi (in questo contesto si tratta di classi con particolare significato) e , da utilizzarsi in coppia, permettono di distinguere classi core (fondamentali o centrali per il flusso di controllo) da altre di secondaria importanza (sempre dal punto di vista logico). Figura 2.26 — Esempio di specifica di un componente. «EJBEntity»

ListinoPrezzi

ListinoPrezziHome

«auxiliary» «focus»

ListinoPrezziPK

ListinoPrezziHome

ListinoPrezzi «auxiliary»

ListinoPrezzi

ListinoPrezziInfo «jarFile»

ListinoPrezzi

ListinoPrezziJar

DisponibilitaPostiHome

«EJBSessionBean»

Prenotazione

«EJBEntity» DisponibilitaPosti

DisponibilitaPosti

UML e ingegneria del software: dalla teoria alla pratica

55

Nel diagramma in fig. 2.26 viene conferito particolare risalto alla struttura del componente ListinoPrezzi, costituito dalle classi ListinoPrezzi, ListinoPrezziInfo, ListinoPrezziPK. L’acronimo PK riportato come suffisso all’ultima classe indica un oggetto che ingloba la chiave primaria (PK, Primary Key). Sarebbe stato opportuno considerare un nome diverso, soprattutto al fine di non creare confusione con altre tecnologie, come per esempio quella dei database relazionali, ma questa tecnica permette di specificare l’identificatore univoco dei componenti entity EJB. Da quanto descritto, si comprende come il componente generale sia realizzato attraverso l’utilizzo di due classi ausiliarie. Gli stereotipi e risultano particolarmente utili per cominciare a progettare e descrivere componenti già nelle fasi di analisi e disegno direttamente nei diagrammi delle classi. Infine lo stereotipo permette di distinguere componenti eseguibili (EJB entity bean, EJB session bean, COM object) dai manufatti che li implementano (JAR file, DLLs ).

Deployment Diagram I diagrammi di dispiegamento mostrano l’architettura hardware e software del sistema: ne vengono illustrati gli elementi fisici (computer, reti, dispositivi fisici, …) opportunamente interconnessi e i moduli software allocati su ciascuno di essi. In sintesi viene mostrato il dispiegamento del sistema a tempo di esecuzione sulla relativa architettura fisica, in termini di componenti e relative allocazioni nelle istanze dei nodi. Dall’analisi di fig. 2.27 è possibile individuare una serie di nodi, rappresentati attraverso parallelepipedi, collegati tra loro per mezzo di opportune connessioni (associazioni). I nodi sono elementi fisici esistenti a tempo di esecuzione che rappresentano risorse computazionali, mentre i collegamenti mostrano le relative connessioni fisiche. I nodi vengono suddivisi in processori (Processor) e dispositivi (Device): la differenza risiede nel fatto che i primi dispongono di capacità elaborative (possono eseguire dei componenti), mentre i secondi no e, tipicamente, vengono utilizzati per rappresentare elementi di interfacciamento con il mondo esterno. I nodi sono entità simili alle classi (ereditano dallo stesso genitore: classificatore), per cui è possibile utilizzarne le stesse potenzialità: ruoli, molteplicità, vincoli, ecc. Poiché diagrammi con soli nodi possono risultare decisamente anonimi e poco esplicativi, è possibile utilizzare delle rappresentazioni alternative utilizzandone “istanze” (TelefonoCellulareWAP, TouchScreen, ecc.) : un concetto del tutto simile a quello dei diagrammi degli oggetti. In tali varianti, è possibile corredare i nodi con un nome, indicarne i processi allocati, e così via. Nel diagramma in fig. 2.27 viene mostrata una prima versione del Deployment Diagram. Nelle versioni più dettagliate sarebbe corretto riportare anche i propri moduli: per esem-

56

Capitolo 2. UML: struttura, organizzazione, utilizzo

Figura 2.27 — Esempio di diagramma di dispiegamento.

:TelefonoCellulareW AP

er nt «I

WAP Internet browser

W eb Server

ne t»

InternetClient : PC

« In

te r

net

«L

AN

»

Web Server

»

Internet browser W eb Server

e er n «Int TouchScreen : PC

Internet browser

e nt «I

«LAN»



» et rn

Web Server

«L

AN

» W eb Server

Web Server Sportello : PC

Internet browser

pio tutte le pagine HTML, JSP (Java Server Pages), le Servlet e i JavaBeans andrebbero installate nel Web Server, il core del sistema; i servizi EJB e il layer di interfacciamento al database invece andrebbero ubicati nell’Application Server e così via.

Meccanismi generali I successivi paragrafi illustrano i meccanismi generali, in quanto utilizzabili in qualsiasi vista e diagramma, forniti dallo UML per estenderne sintassi e semantica. Sebbene gli elementi del nucleo dello UML permettano di formalizzare molti aspetti di un sistema, è impossibile pensare che da soli siano sufficienti per illustrarne tutti i dettagli. Si sarebbe potuto tentare di colmare tale lacuna per esempio inserendo numerosi elementi specializzati, ma il costo da pagare sarebbe stato quello di rendere il linguaggio inutilmente complesso e rigido e poi ci sarebbe sempre stato il famoso caso non contemplato (Murphy insegna…).

57

UML e ingegneria del software: dalla teoria alla pratica

Per evitare ciò, lo UML fornisce sia meccanismi generali utilizzabili per aggiungere informazioni supplementari difficilmente standardizzabili, sia veri e propri meccanismi di estensione. L’esempio più classico, già incontrato nel corso di alcuni dei diagrammi presentati nei paragrafi precedenti, è quello delle note (notes): informazioni supplementari redatte con un formalismo che può variare, a completa discrezione dell’architetto, dal linguaggio di programmazione, allo pseudocodice al testo in linguaggio naturale.

Naturalmente la selezione del livello di astrazione da utilizzare deve essere subordinata al miglioramento della leggibilità del diagramma. Poiché la leggibilità dipende anche dal fruitore, ne segue che il formalismo da utilizzarsi dipende dal destinatario principale del diagramma. Per esempio un’annotazione in codice avrebbe poco significato nella Use Case View, dove il pubblico dei fruitori prevede anche i clienti che, per definizione, non hanno conoscenze tecniche, mentre dovrebbe risultare del tutto naturale in un Class Diagram, i cui fruitori sono tecnici qualificati.

Figura 2.28 — Utilizzo delle note in uno Use Case Diagram.

login

reperimento dati cliente «include»

«extend» Il caricamento avviene con le stesse modalita' con cui si associa un attachment ad una e-Mail in un server di posta elettronica con interfaccia webbased.

upload ordine Cliente

«extend»

verifica ordine

«extend»

invia ordine espresso Legacy System Gli ordini es pres si vanno spediti a l Legacy System appena disponibili, gli a ltri in m od alita' b a tc h s ec o nd o scadenze prestabilite.

58

Capitolo 2. UML: struttura, organizzazione, utilizzo

Graficamente le note vengono rappresentate per mezzo di un foglio di carta stilizzato associato, per mezzo di una linea tratteggiata, all’elemento che si vuole dettagliare. Nella fig. 2.28 le note sono state attaccate a due casi d’uso: Upload ordini e Invia ordine espresso, rispettivamente per specificare la modalità utilizzata per trasmettere un file da un browser client a un Web server e per evidenziare la differenza esistente tra gli ordini comuni e quelli espressi da trasmettere al legacy system appena disponibili. In questo caso le note contengono semplice testo in linguaggio naturale coerentemente con la vista di appartenenza e quindi con i relativi fruitori. Si noti il ricorso al colore grigio chiaro sia per il testo, sia per il disegno della sagoma delle note. Esso è utilizzato al fine di non appesantire il diagramma e di non distogliere l’attenzione dagli elementi più importanti. Un altro meccanismo generale è costituito dai cosiddetti ornamenti (adornments), che permettono di aggiungere semantica al linguaggio. Si tratta di elementi grafici atti a fornire al lettore una cognizione diretta di aspetti particolarmente importanti di specifici elementi. Con riferimento a fig 2.29, si può notare che il nome delle classi è stato scritto in corsivo per indicare che si tratta di classi astratte. Nella relazione (associazione) che lega l’OggettoOsservato all’OggettoOsservatore è presente un altro esempio di ornamento: la molteplicità. In una relazione tra classi, le molteplicità sono i numeri (o simboli) che specificano, per entrambe le classi dell’associazione, quante istanze possono essere coinvolte nella relazione con l’altra classe. Per esempio nel diagramma di fig. 2.29, un O g g e t t o O s s e r v a t o può disporre di diversi osservatori, così come OggettoOsservatore può osservare diversi oggetti. Un altro esempio di ornamento sono i simboli utilizzati per mostrare la visibilità di metodi ed attributi delle classi (+ per elementi pubblici, - per privati, # per protetti, ecc). Da notare che nella versione 1.4 dello UML è stata aggiunta un’ulteriore visibilità: quella a livello di package (il friendly del C++) indicata con il simbolo tilde (~ package). Ulteriore esempio di meccanismo generale è rappresentato dalle specificazioni (specifications): elementi di testo che aggiungono sintassi e semantica agli elementi dello UML. Un esempio di specificazione è fornito dall’indicazione dell’elenco degli attributi e Figura 2.29 — Utilizzo delle note per illustrare le operazioni eseguite da un metodo. osservatori

OggettoOsservatore ... +richiestaAggiornamento() ...

*

OggettoOsservato

*

while(osservatori.hasElement()) {

... +aggiungiAscoltatore() +leggiStato() +notificaEvento +rimuoviAscoltatore() +rimuoviAscoltatori() ...

osservatore = osservatori.next(); osservatore.richiestaAggiornamento(); }

59

UML e ingegneria del software: dalla teoria alla pratica

Figura 2.30 — Distinzione tra classe e relative istanze (oggetti).

Spettacolo

: Spettacolo

Barracuda : Spettacolo

Barracuda

- codice : String - titolo : String ... + getRappresentazione() ...

dei metodi (inclusa la firma completa) presenti nell’icona delle classi. Tipicamente per conferire ai vari diagrammi un livello migliore di chiarezza, vengono visualizzati solo determinati sottoinsiemi degli attributi e metodi dei vari elementi, chiaramente quelli ritenuti più utili nel contesto oggetto di studio. Ciò implica che per una stessa classe, in diversi diagrammi, possano essere visualizzati sottoinsiemi diversi di metodi e attributi. Ultimo esempio di meccanismo generale è costituito dalle divisioni comuni (common divisions): nella modellazione di sistemi OO, ogni elemento reale può essere diviso, almeno, in due diverse tipologie. Il primo esempio è costituito dalle classi e dagli oggetti: una classe è un’astrazione; un oggetto ne è il corpo, ossia una concreta manifestazione della classe. Su questi argomenti si tornerà in dettaglio nel capitolo dedicato ai diagrammi delle classi e degli oggetti, per ora è sufficiente sapere che lo UML fornisce i meccanismi per mostrare la dicotomia presente in molti degli elementi (Use Case e istanze di Use Case, componenti e istanze di componenti, e così via). In fig. 2.30 sono rappresentate la classe Spettacolo e tre sue istanze: nella prima viene indicata unicamente la classe di appartenenza (Spettacolo appunto), la seconda evidenzia anche il nome (Barracuda :Spettacolo), mentre la terza solo il nome. Un altro esempio di divisione comune è fornito dalle interfacce e dalle relative implementazioni: un’interfaccia dichiara un contratto e una sua implementazione rappresenta una concreta realizzazione del contratto stesso, e quindi è responsabile della fornitura dei servizi in esso promessi.

Meccanismi di estensione I meccanismi di estensione (Extensibility Mechanisms) consentono di aumentare sintassi e semantica dello UML al fine di permetterne il proficuo utilizzo in aree molto specifiche migliorando la leggibilità dei diagrammi prodotti e, al contempo, contenendo il livello di complessità del linguaggio stesso.

60

Capitolo 2. UML: struttura, organizzazione, utilizzo

Figura 2.31 — Esempio di Interfaccia e relativa implementazione.

PrenotazioneBigliettiImp PrenotazioneBiglietti

In particolare i meccanismi di estensione forniti dallo UML sono: • Stereotypes (stereotipi); • Tagged Values (valori etichettati); • Constraints (vincoli). Gli stereotipi permettono di estendere il vocabolario dello UML attraverso l’introduzione di nuovi elementi, definiti dall’utente, specifici per il problema oggetto di studio. Chiaramente la definizione degli stereotipi deve seguire regole ben precise: ciascuno di essi deve essere la specializzazione di un elemento presente nello UML (metamodello). In ultima analisi uno stereotipo è un nuova classe che viene (virtualmente) aggiunta al metamodello in fase di progettazione. Chiaramente non è assolutamente necessario andare a modificare il metamodello: è sufficiente che il nuovo stereotipo rispetti le regole previste. Spesso uno stereotipo viene introdotto unicamente per disporre di icone più accattivanti (per esempio se un attore è un legacy system, invece di mostrare l’uomo stilizzato potrebbe risultare più chiaro mostrare un’icona con riportato un vecchio mainframe) o per evidenziare un’etichetta autoesplicativa (come per esempio nel caso di focus e auxiliary). Altre volte li si utilizza per aggiungere insiemi predefiniti di Tagged Value (associazione tra un attributo e il relativo valore) e vincoli validi per tutte le istanze dello stereotipo stesso. Ovviamente i vincoli, il nome e le altre caratteristiche aggiunte per mezzo di uno stereotipo, non devono entrare in conflitto con la definizione dell’elemento base (metaclasse o stereotipo) che si intende specializzare. Un esempio di quanto detto è rappresentato dallo stereotipo (fig. 2.32) dell’elemento classe (volendo essere formali si tratta di una specializzazione della metaclasse del metamodello denominata Classe). Esso si presta a essere utilizzato per

61

UML e ingegneria del software: dalla teoria alla pratica

inserire informazioni aggiuntive alle classi le cui istanze necessitino di essere memorizzate in forma permanente dal sistema. La presenza della etichetta riportante con lo stereotipo (), già da sola aiuta ad aumentare la chiarezza del modello. Infatti permette di distinguere immediatamente le classi (istanze dello stereotipo) i cui oggetti necessitano di essere memorizzati in modo permanente da quelle che invece non ne hanno bisogno. Questo stereotipo presenta tutta una serie di elementi aggiuntivi, quali: • Tag: storageMode i cui elementi sono di tipo enumerato e possono assumere uno dei seguenti valori: table, file, object;

Figura 2.32 — Definizione ed utilizzo di uno stereotipo (Persistent). Formalmente in UML uno stereotipo è rappresentato attraverso un’apposita (meta)Classe il cui stereotipo è appunto. Tale classe è la client di una relazione di dipendenza (a sua volta stereotipizzata con il termine ) che la vincola all’elemento del metamodello che intende estendere. Nell’esempio di figura l’elemento che si intende estendere è la metaclasse Class (Classe). I Tagged Value del nuovo elemento sono rappresentati per mezzo di appositi attributi, talune volte stereotipizzati come .

«metaclass» Class «stereotype» «Persistent»

«stereotype» Persistent tags table : String database : Database isContainer : boolean constraints {table.size exists(Customer.oclIsKindOf(Company)) implies BankAccount.holders->size = 1

In particolare la prima riga afferma che se nella collezione degli intestatari (holders) del conto corrente è presente una società, allora tale collezione prevede un solo elemento: la società stessa. Il secondo vincolo risulta decisamente più interessante. Afferma che se uno degli intestatari di un conto corrente è un minorenne, allora lo stesso conto corrente deve necessariamente essere intestato anche a un’altra persona, questa volta maggiorenne. BankAccount.holders->exists( elem|elem.oclIsKindOf(Person) & elem.oclAsType(Person).age() < 18) implies BankAccount.holders->exists( elem|elem.oclIsKindOf(Person) & elem.oclAsType(Person).age()>= 18)

Si consideri ora il diagramma delle classi riportato in fig. 2.37. Si tratta di una porzione di un diagramma proveniente direttamente dal mondo degli investimenti bancari (Treasury System). Diagrammi del tipo di quello in figura servono a rappresentare le entità dell’area

69

UML e ingegneria del software: dalla teoria alla pratica

Figura 2.36 — Esempio di diagramma delle classi atto a rappresentare la relazione tra clienti di una banca e conti correnti.

- BankAccount.holders-> exists(Customer.oclIsKindOf(Company)) implies BankAccount.holders -> size = 1 - BankAccount.holders-> exists( elem | elem.oclIsKindOf(Person) & elem.oclAsType(Person).age() < 18) implies BankAccount.holders-> exists( elem | elem.oclIsKindOf(Person) & elem.oclAsType(Person).age() >= 18)

BankAccount - balance - accountNumber ...

Customer - holders 1..*

1..*

+getHolders() +getMovements() ...

- customerId - address ... +getId() ...

Customer

Person

- customerId - address ...

- title - dateOfBirth - name ...

+getId() ...

+getAge() ...

oggetto di studio (business) e tipicamente vengono indicati come modelli a oggetti del dominio (DOM, Domain Object Model). Il relativo obiettivo, come illustrato nel capitolo dei diagrammi delle classi, è mostrare le varie entità e le relative associazioni presenti nel mondo concettuale che il sistema software deve automatizzare (area business o dominio del problema). Si tratta a tutti gli effetti di versioni Object Oriented dei famosi diagrammi entità-relazioni (Entity-Relationship Diagram). I DOM sono caratterizzati dal fatto che specificano nelle varie classi i soli attributi ritenuti significativi per l’utente, corredandoli solo sporadicamente con qualche metodo ritenuto particolarmente significativo per l’area

70

Capitolo 2. UML: struttura, organizzazione, utilizzo

di business oggetto di studio. Visto e considerato il dominio del problema si è lasciato il diagramma in lingua inglese… A Cesare quel che è di Cesare. Senza entrare troppo nei dettagli — in fondo l’obiettivo è quello di fornire un esempio di utilizzo dell’OCL — il diagramma può essere spiegato come segue. In primo luogo nel mondo degli investimenti esistono diverse entità (agenti, broker, dipartimenti di banche, clienti, ecc.) aventi ruoli diversi ma comunque accomunabili sia perché condividono un certo sottoinsieme di comportamento sia per evitare di avere miriadi di oggetti simili sparsi per il sistema. Queste entità vengono rappresentate dalla classe denominata genericamente Party. Da tener presente che il raggruppamento delle relative istanze è necessario anche perché ciascuna di esse può recitare diversi ruoli: l’associazione plays che connette la classe Party a quella PartyRole prevede cardinalità (1, n). Per ogni ruolo che una classe Party recita è necessario disporre delle informazioni inerenti i relativi recapiti (Contact): indirizzo, telefono, fax, e-mail, indirizzo per la messaggistica interbancaria, ecc. Un’entità Party poi è localizzata in una specifica città (City) e può essere strutturata secondo un’apposita organizzazione gerarchica: ogni oggetto Party può essere associato a un altro per mezzo dell’associazione parent. Una stessa banca per esempio può essere suddivisa in dipartimenti, ognuno dei quali organizzato altri dipartimenti e così via.

Figura 2.37 — Diagramma concettuale relativo all’entità party e relative associazioni. self.books->notEmpty() implies self.roles->exists(PartyRole.oclIsKindOf(ProcessingOrg))

self.counterparty.roles->exists(PartyRole.oclIsKindOf(Counterparty))

-parent 0..1

City

located

1

0..n

Party

-counterparty 1

0..n 1

-roles

Contact

Agent

Broker

1

-books

1..n

0..n

PartyRole

CalculationAgent

Counterparty

Trade 0..n

IPA

Investor

Issuer

Book

Trustee

ProcessingOrg

UML e ingegneria del software: dalla teoria alla pratica

71

A questo punto si considerino le informazioni più direttamente legate agli investimenti. Ciascuno di essi (Trade ) deve possedere almeno due entità: sorgente (chi vende) e destinatario (chi acquista). Nel caso più generale possono essere coinvolte molte più entità: agenti, intermediari ecc. Questi però vengono specificati attraverso altre entità (non riportate nel diagramma) denominate “istruzioni per l’erogazione delle liquidazioni” (Standing Settlement Instructions), che si applicano agli investimenti in funzione dell’ennupla (sorgente, destinatario, prodotto finanziario, moneta, ecc.). Ogni trade appartiene a un solo book che tra l’altro permette di individuare il dipartimento che ha stipulato l’investimento. A questo punto entrano in gioco i vincoli. Il primo (quello più a sinistra) sancisce che solamente Party che svolgono anche funzioni di Processing Organization possono amministrare dei book. self.books->notEmpty() implies self.roles->exists(PartyRole.oclIsKindOf(ProcessingOrg))

Precisamente, il vincolo sancisce che se un Party gestisce almeno un book ne segue che nella lista dei ruoli recitati dal Party stesso deve necessariamente comparire quello di Processing Organization. Il secondo vincolo invece è associato alla classe Trade. Sancisce che un trade può prevedere come controparte unicamente un’istanza della classe Party che recita appunto il ruolo di controparte: self.counterparty.roles->exists(PartyRole.oclIsKindOf(Counterparty))

Formalmente, nella lista dei ruoli recitati dalla classe Party collegata al trade per mezzo dell’associazione counterparty deve essere necessariamente presente il ruolo di Counterparty. Quindi un’istanza di Trade rappresenta un deal (contratto, affare) stipulato tra uno specifico dipartimento della banca (ProcessingOrg), quello che amministra il Book a cui il Trade è associato, e una controparte. Ultimo esempio di meccanismo di estensione è costituito dai Profili (illustrati anche nei paragrafi seguenti) che con la versione 1.4 sono entrati a far parte integrante del linguaggio. Attualmente ne sono stati formalmente incorporati due: il profilo per i Software Development Processes (processi di sviluppo del software) e quello per il Business Modeling (modellazione dell’area business). Un profilo è uno stereotipo di un package contenente un insieme di elementi del modello opportunamente adattati a uno specifico dominio o scopo. Tipicamente lo si ottiene estendendo il metamodello tramite l’uso adeguato di meccanismi di estensione forniti dallo UML (stereotipi, definizioni di Tagged Value, vincoli e icone). In tali casi si parla di profili “leggeri” (lightweight), in quanto ottenuti per mezzo dei meccanismi di estensione propri del linguaggio, in contrasto con quelli “pesanti” (heavyweight) ottenibili per mezzo dei meccanismi di estensione definiti dalle specifiche del MOF.

72

Capitolo 2. UML: struttura, organizzazione, utilizzo

Tabella 2.3 — Stereotipi presenti nel profilo dei Software Development Processes. NOME STEREOTIPO

UseCaseModel AnalysisModel DesignModel ImplementationModel UseCaseSystem AnalysisSystem DesignSystem ImplementationSystem AnalysisPackage DesignSubsystem ImplementationSubsystem UseCasePackage AnalysisServicePackage DesignServiceSubsystem Boundary Entità Control Communicate Subscribe

ELEMENTO BASE

Model* Model Model Model Package Package Subsystem** Subsystem Package Subsystem Subsystem Package Package Subsystem Class Class Class Association Association

* Il modello cattura una particolare vista di un sistema fisico, in altre parole, ne è una astrazione realizzata per scopi ben precisi. In particolare lo scopo permette di determinare quali elementi sia interessante riportare e quali invece possano essere trascurati. È possibile definire diversi modelli per uno stesso sistema, dove ciascuno ne rappresenta una vista definita da propri obiettivi e dal relativo livello di astrazione (per esempio modello di analisi, modello di disegno, modello di implementazione, e così via). Tipicamente diversi modelli risultano tra loro complementari e vengono definiti dalle prospettive (Viewpoints, punti di vista) dei diversi fruitori del sistema. Poiché nel metamodello la metaclasse modello — che scioglilingua! — eredita da Package , ne consegue che rappresenta un raggruppamento di elementi opportunamente organizzati secondo strutture gerarchiche ( P a c k a g e eredita dal N a m e s p a c e e dal

GeneralizableElement). * * Un sottosistema (Subsytem) è un raggruppamento di elementi che rappresentano un’unità, aggregabile dal punto di vista del comportamento, del sistema fisico. I sottosistemi sono in grado di esporre opportune interfacce e dispongono di operazioni. Gli elementi che costituiscono i sottosistemi vengono divisi in due categorie: elementi di specifica e quelli di realizzazione. Nel metamodello l’elemento Subsystem presenta una ereditarietà multipla: eredita sia dal Package sia dal Classifier. Quindi oltre alle caratteristiche proprie del Package, esso possiede tutta una serie di features quali operazioni, associazioni, ecc.

UML e ingegneria del software: dalla teoria alla pratica

73

La definizione formale di un profilo, per essere consistente all’approccio utilizzato nel documento delle specifiche ufficiali, deve prevedere le seguenti sezioni: 1. insieme delle estensioni standard che definiscono il profilo stesso, ottenuto attraverso l’opportuna applicazione dei meccanismi di estensione dello UML al sottoinsieme di interesse del metamodello; 2. definizione della relativa semantica descritta attraverso il linguaggio naturale (americano, ovviamente); 3. insieme delle regole ben formate (well-formed rules), ossia insieme di vincoli, espressi in linguaggio naturale, e qualora possibile per mezzo dell’OCL, appartenenti agli elementi introdotti con il profilo; 4. elementi comuni del modello (common model elements), ossia istanze predefinite di elementi del metamodello UML. La definizione di tali elementi può far uso delle estensioni standard definite nel profilo stesso secondo gli eventuali vincoli in esso presenti; 5. eventuali estensioni al MOF (Meta Object Facility). Questa sezione, qualora presente, determina la transazione del profilo dalla caratterizzazione “leggero” a quella “pesante”. Si ottiene definendo nuove meta-classi da incorporare nella definizione formale del MOF. Chiaramente, si tratta di estensioni delicate a cui ricorrere solo dopo aver appurato l’inadeguatezza ai fini richiesti dei meccanismi standard. È possibile pensare ai profili come a librerie di elementi o a plug-in da installare, contenenti insiemi predefiniti di elementi ottenuti dall’estensione di quelli base, da utilizzarsi per modellare specifiche aree business o per determinati scopi. Nella fig. 2.38 vengono riportati alcuni esempi di elementi presenti nel profilo dei Software Development Processes. Questi sono solo alcuni esempi di stereotipi presenti nel profilo: la lista completa è presente nella tab. 2.3. Per quanto riguarda gli stereotipi delle classi riportati in figura, non si tratta di elementi nuovi; li si può trovare, tra l’altro nel libro dei Tres Amigos dedicato al processo di sviluppo del software ([BIB08]). Essi si prestano a essere utilizzati nel modello di analisi con i seguenti significati:

Il significato di questo stereotipo è abbastanza intuitivo: gli oggetti entità contengono informazioni che necessitano di essere memorizzate in forma permanente. Quindi, tipicamente, rappresentano entità che esistono (o traspirano) nel mondo concettuale oggetto di

74

Capitolo 2. UML: struttura, organizzazione, utilizzo

Figura 2.38 — Stereotipi di classi definiti nel profilo dei Software Development Proecesses. «control»

SessionTracker SessionTracker «boundary»

TicketReservation TicketReservation

«entity»

ShowRepresentation ShowRepresentation

studio: si tratta prevalentemente, di raffinamenti delle classi presenti nel modello del dominio. Le istanze dello stereotipo sono oggetti passivi (per esempio non iniziano una interazione per loro iniziativa) e tipicamente partecipano nella realizzazione di molti Use Case e sopravvivono alle singole interazioni.

Le istanze di questo stereotipo gestiscono interazioni tra collezioni di oggetti. Tipicamente hanno comportamento specifico per un singolo Use Case e i relativi oggetti non sopravvivono a una specifica interazione a cui prendono parte. Queste classi sono anche utilizzate per rappresentare complessi algoritmi di calcolo, regole appartenenti all’area business, ecc.

Le istanze di questa classe “vivono” nel confine interno del sistema, e il relativo compito, tipicamente, consiste nel gestire l’interazione tra attori esterni e altri oggetti boundary, entity e control interni al sistema stesso. Quindi si tratta di parti del sistema che dipendono dai relativi attori e in particolare catturano i requisiti per determinate interazioni.

I profili attesi Come visto in precedenza, i profili sono delle particolari estensioni dello UML, o meglio collezioni di estensioni predefinite, le quali nascono dall’esigenza di standardizzare

UML e ingegneria del software: dalla teoria alla pratica

75

l’utilizzo dello UML per domini o scopi o tecnologie ampiamente diffuse come per esempio le architetture CORBA, EJB e COM+. Lo UML di per sé è un metamodello e quindi una notazione a scopo generale (general purpose) particolarmente indicata per esprimere modelli ad oggetti. Uno degli intenti che fin dall’inizio hanno ispirato il lavoro dei Tres Amigos è stato di renderlo così generico da poter essere utilizzato proficuamente in un’estesa varietà di ambienti. Poiché però era impossibile riuscire a immaginare i requisiti di tutti i possibili ambienti fin dall’inizio, è stato deciso di dotare lo UML di meccanismi che ne consentissero l’estensione (consultare i paragrafi precedenti) garantendo così un efficace utilizzo in ogni ambiente specifico. Il problema emerso era che le estensioni da apportare allo UML al fine di adattarlo a tecnologie particolarmente ricorrenti in progetti Object Oriented erano completamente demandate ai progettisti. Il rischio, ancora una volta, era quello di una nuova proliferazione di “plug-in” non standardizzati e completamente disomogenei che chiaramente finivano per minare i grandi vantaggi offerti dallo UML, derivanti dall’elevato grado di standardizzazione. Per esempio, se si fosse chiesto ad n architetti (esperti) di progettare un sistema basato sulla tecnologia EJB attraverso lo UML, verosimilmente tutti avrebbero realizzato altrettanti modelli perfettamente validi e formali ma, probabilmente, ciascuno avrebbe formulato un proprio insieme di estensioni, diverso da quello ideato degli altri. Si correva pertanto il rischio di generare ancora una volta una miriade di dialetti. Un altro problema è legato, paradossalmente, a una delle caratteristiche più apprezzate del linguaggio: l’essere general purpose. Se da un lato è un vantaggio per i motivi più volte elencati, dall’altro la relativa flessibilità può creare non pochi problemi — e qui ce ne sarebbero di esempi da citare — soprattutto a un pubblico di utilizzatori UML non esperti. Molto spesso i progettisti vengono sopraffatti dalla flessibilità del linguaggio e preferirebbero disporre di elementi pronti la cui validità sia stata già collaudata da altri. Onde evitare tutto ciò, lo OMG (Object Management Group) ha deciso di lavorare alla standardizzazione di una serie di profili, tra i quali molto importanti sono quello CORBA e quello EJB per il quale, ovviamente, è stata richiesta l’approvazione della stessa Sun Microsytem. Si tratta di un passo indietro? Tanto valeva, allora, definire tutti gli elementi direttamente dall’inizio. In realtà le cose non stanno così. La struttura dello UML risulta paragonabile a quella di un qualsiasi linguaggio di programmazione. Esiste un core ben definito, basato su un numero di elementi relativamente limitato, e quindi più facilmente accessibile, utilizzabile per costruire tutte le funzionalità di cui si ha bisogno, e in più è possibile utilizzare tutta una serie di librerie predefinite, di provato successo, addirittura gratuite. L’architettura dello UML è un esempio di ingegneria del software, che possiede le caratteristiche più ambite: flessibilità, formalità, potenza, semplicità, ecc. I meccanismi di estensione, pertanto, rimangono strumenti comunque molto validi, la cui efficacia, tra l’altro, è stata provata durante il lavoro stesso di definizione di profili. Si tratta di un framework utilizzabile per estendere efficacemente il linguaggio per gli usi più svariati

76

Capitolo 2. UML: struttura, organizzazione, utilizzo

secondo direttive ben definite… E poi… nessuno vieta di definire ulteriori elementi non previsti dai profili. Di seguito si cercherà di illustrare il modo in cui utilizzare proficuamente i meccanismi di estensione forniti dallo UML e di rendere il lettore consapevole dell’esistenza di direttive standardizzate per la modellazione di sistemi basati su tali architetture. è lecito attendersi variazioni in fututo, visto che, al momento in cui si scrive, le specifiche per tali profili non sono ancora disponibili.

Profilo CORBA molto in generale In primo luogo, il profilo CORBA, sancisce come era logico attendersi che le relative interfacce siano modellate attraverso classi. Ovviamente gli attributi e i metodi di tali classi corrispondono, rispettivamente, ai metodi e alle operazioni delle interfacce (IDL). Figura 2.39 — Frammento di un layer di wrapper di un sistema per le prenotazioni dei biglietti per voli aerei.

Aeroporto - codiceIATA : string - nomeBreve : string - nome : string - zonaFusoOrario : string - tasseNaz : string - tasseIntrn : string - tasseIntrc : string ...

Citta

e' ubicato 1..*

1

Moneta

- codice : string - nome : string - sigla : string ...

- codice : string - nome : string - simbolo : string ...

...

... 1..*

1..* appartiene

...

za iliz ut

1

Nazione «IDL»

IAeroporto ... ...

- codice : string - nome : string - sigla : string ... ...

«IDL»

+getAeroporti(citta) +getAeroporto(codiceIATA) +getAeroporto(nomeAeroporto)

...

Continente

contiene 1..*

1

- codice : string - nome : string - simbolo : string ... ...

AeroportoFinder

IAeroportoFinder

1..*

77

UML e ingegneria del software: dalla teoria alla pratica

A questo punto lettori non esperti dell’architettura CORBA potrebbero avere qualche perplessità legata alla presenza di attributi nelle interfacce CORBA. È tutto corretto; si tratta di interfacce definite per mezzo di un apposito linguaggio denominato IDL (Interface Definition Language) e possono ospitare anche attributi. Prima di proseguire oltre si prenda in considerazione il diagramma delle classi illustrato in fig. 2.39: costituisce l’esempio di riferimento della spiegazione che segue. Il frammento riportato in figura mostra una porzione di un layer atto a incapsulare, per mezzo di opportuna architettura CORBA, un sistema legacy di una generica compagnia aerea. In particolare, la sezione mostrata focalizza l’attenzione sull’entità aeroporto e sulle classi CORBA che ne permettono l’esposizione. Il primo problema che si poneva era che in CORBA è possibile specificare che un attributo sia di sola lettura (read only), mentre in UML ciò non è possibile. Al più è possibile specificare che un attributo sia congelato (frozen), ma il significato non è esattamente lo stesso. Brevemente frozen indica che il valore di un attributo o di un’associazione non può essere modificato dopo che il relativo oggetto è stato creato e l’attributo/associazione frozen è stato/a inizializzato/a. Per risolvere questo primo inconveniente è possibile definire uno stereotipo che estende il costrutto UML di attributo in grado di indicarne appunto l’inibizione della scrittura (fig. 2.40). Nella classe visualizzata tutti gli attributi sono dichiarati di sola lettura in quanto utilizzata per comunicare alla classe client le informazioni relative agli aeroporti. L’aspetto inte-

«IDL» «IDL»

IAeroporto

IAeroporto

«ReadOnly» codiceIATA : string «ReadOnly» nomeBreve : string «ReadOnly» nome : string «ReadOnly» zonaFusoOrario : string «ReadOnly» codiceCitta : string «ReadOnly» nomeCitta : string «ReadOnly» nomeNazione : string «ReadOnly» continente : string «ReadOnly» tasseNaz : string «ReadOnly» tasseIntrn : string «ReadOnly» tasseIntrc : string ...

«ReadOnly» codiceIATA : string «ReadOnly» nomeBreve : string «ReadOnly» nome : string «ReadOnly» zonaFusoOrario : string «ReadOnly» codiceCitta : string «ReadOnly» nomeCitta : string «ReadOnly» nomeNazione : string «ReadOnly» continente : string «ReadOnly» tasseNaz : string «ReadOnly» tasseIntrn : string «ReadOnly» tasseIntrc : string ...

...

...

Figura 2.40 — Esempio dell’utilizzo dello stereotipo .

{IDLOrder = 1} {IDLOrder = 2} {IDLOrder = 3} {IDLOrder = 4} {IDLOrder = 5} {IDLOrder = 6} {IDLOrder = 7} {IDLOrder = 8} {IDLOrder = 9} {IDLOrder = 10} {IDLOrder = 11}

Figura 2.41 — Esempio di utilizzo del Tagged Value IDLOrder.

78

Capitolo 2. UML: struttura, organizzazione, utilizzo

ressante è che lo UML si limita a fornire il supporto alla specificazione degli stereotipi mentre non necessita assolutamente di conoscerne il significato, il quale, evidentemente, deve però essere noto ai fruitori. Ciò permette ai tool commerciali di abilitare l’utente a estendere sintassi e semantica dello UML senza dover necessariamente fornire il significato dei nuovi costrutti eventualmente introdotti. Un altro problema che si potrebbe incontrare nella definizione di un’interfaccia IDL è la necessità di specificare l’esatto ordine in cui gli attributi e le operazioni compaiono nelle interfacce, che non necessariamente deve coincidere con quello degli stessi elementi che compaiono nelle classi che rappresentano le interfacce. In questo caso uno stereotipo non sarebbe di grande aiuto e pertanto è stato necessario ricorrere a qualcosa di diverso come i valori etichettati (Tagged Value). In effetti il profilo CORBA prevede di utilizzare un Tagged Value il cui nome è IDLOrder in cui il valore associato, un numero naturale, specifica l’ordine dell’elemento al quale viene associato (fig. 2.41). Nuovamente, un tool commerciale per permettere all’utente di utilizzare il profilo CORBA deve unicamente supportare i concetti come stereotipi e Tagged Value senza dover assolutamente conoscere la semantica di quanto introdotto dall’utente. Un profilo è costituito sostanzialmente da un insieme di stereotipi e Tagged Value predefiniti. Nel caso del profilo CORBA, la definizione dello stereotipo readonly e del Tagged Value IDLOrder ha permesso di estendere virtualmente il metamodello UML senza introdurre esplicitamente nessun nuovo costrutto o concetto notazionale.

Profilo EJB molto in generale La formulazione del profilo EJB è il risultato della collaborazione tra Rational, Sun Microsystem e Object Management Group (Java Specification Request JSR-26). Attualmente è disponibile la versione draft (UML Profile for EJB, pubblicata nel maggio 2001), il cui principale fautore è Jack Greenfield coadiuvato da un team formato da James Abbott, Loïc Julien, David Frankel, Scott Rich e molti altri ancora. Sebbene di recente formulazione, si tratta di un profilo già obsoleto sia perché basato sulla versione 1.3 delle specifiche ufficiali dello UML, sia perché la versione dell’architettura EJB presa in considerazione è la 1.1. Ciò nonostante si tratta di un documento molto importante e ben congegnato, il cui adeguamento alle ultime versioni delle specifiche UML ed EJB non dovrebbe richiedere eccessivo lavoro. Obiettivo del profilo è definire un mapping standard tra lo UML e l’architettura Enterprise JavaBeans (EJB). Il raggiungimento di tale obiettivo comporta: • la definizione di un approccio standard per la modellazione di sistemi basati sull’architettura EJB e quindi fondati sul linguaggio di programmazione Java; • il supporto delle esigenze pratiche comunemente incontrate nel disegno di sistemi EJB-based;

UML e ingegneria del software: dalla teoria alla pratica

79

• la definizione di una rappresentazione completa, accurata e non ambigua dei manufatti previsti dall’architettura EJB corredati dalla relativa semantica limitatamente ai fini della modellazione; • il supporto della costruzione di modelli di “assemblati” EJB frutto della combinazione di package creati utilizzando tool forniti da diversi produttori; • la semplificazione del disegno di sistemi basati sull’architettura EJB, in modo tale che gli “sviluppatori” da un lato non siano costretti a definire per conto proprio tecniche per la modellazione di sistemi EJB e dall’altro possano confidare in tecniche di reverse engineering compatibili con quanto disegnato; • il supporto alle diverse fasi del ciclo di vita del software che implicano manufatti attinenti ai componenti EJB (modello di disegno, di sviluppo, deployment e runtime); • l’attuazione dell’interscambio di modelli di manufatti EJB prodotti per mezzo di tool forniti da diverse ditte produttrici; • la compatibilità con tutte le API e gli standard del linguaggio di programmazione Java; • la compatibilità con i profili UML per le analoghe tecnologie, come CORBA e il modello a componenti CORBA Per approfondimenti ulteriori sul profilo EJB si rimanda il lettore all’Appendice C dove è possibile trovare una trattazione più dettagliata di tale profilo con una breve introduzione sulla tecnologia a oggetti distribuiti Enterprise JavaBeans.

Ricapitolando… Il presente capitolo è stato dedicato alla disamina del linguaggio UML: una vera e propria overview. L’intento è di iniziare a mostrare le potenzialità e i meccanismi dello UML senza avere assolutamente la pretesa di essere esaustivi: per ogni argomento presentato esiste un apposito capitolo in cui i vari concetti vengono illustrati dettagliatamente. Procedendo dall’esterno verso l’interno, lo UML è composto da una serie di viste (Use Case, Design, Implementation, Component e Deployment) realizzate attraverso opportuni di diagrammi ognuno dei quali utilizza specifici elementi del modello. La prima vista, Use Case View (vista dei casi d’uso), è utilizzata per analizzare i requisiti utente; specifica le funzionalità del sistema come vengono percepite da entità esterne allo stesso, dette attori.

80

Capitolo 2. UML: struttura, organizzazione, utilizzo

La Design View (vista di disegno), descrive come le funzionalità del sistema devono essere realizzate; in altre parole il sistema viene analizzato dall’interno. La Implementation View (vista implementativa, detta anche Component View, vista dei componenti), è la descrizione del modo in cui il codice (classi per i linguaggi Object Oriented) viene aggregato in moduli (package) e di come questi dipendano gli uni dagli altri. La Process View (vista dei processi, detta anche Concurrency View, vista della concorrenza) rientra nell’analisi degli aspetti non funzionali del sistema, e consiste nell’individuare i processi e i processori. La Deployment View (vista di “dispiegamento”), mostra l’architettura fisica del sistema e fornisce l’allocazione delle componenti software nella struttura stessa. Per ciò che concerne i diagrammi, sono previste ben nove tipologie: Use Case, Class, Object, Sequence, Collaboration, StateChart, Activity, Component e Deployment. Utilizzando lo UML per rappresentare modelli di sistemi reali è possibile riscontrare due lacune: l’interfaccia utente e il disegno di database relazionali. Il problema è che non esistono formalismi per rappresentare due modelli decisamente importanti nella realizzazione di sistemi informatici come l’interfaccia utente e il disegno di database razionali. Per database Object Oriented è possibile utilizzare i diagrammi delle classi e quelli degli oggetti. Sebbene il presente testo non si occupi di trattare in dettaglio i processi di sviluppo, si è comunque preso in considerazione un processo di riferimento al fine di illustrare in maniera più concreta l’utilizzo dello UML. Spesso alcuni tecnici tendono a confondere lo UML con il processo di sviluppo: come ripetuto più volte lo UML è “solo” un linguaggio di modellazione e come tale si presta a rappresentare i prodotti generati nelle varie fasi di cui un processo è composto. Pertanto lo UML, al contrario dei processi, non fornisce alcuna direttiva su come fare evolvere il progetto attraverso le varie fasi così come non specifica quali sono i manufatti da produrre, chi ne è responsabile ecc. Il problema è che non esiste un processo standard universalmente accettato e tantomeno esiste un accordo sulle varie fasi e sui relativi nomi: ogni processo va adattato alle caratteristiche delle specifiche organizzazioni, ai progetti, e così via. Brevemente — ogni fase verrà ripresa nei capitoli successivi — i vari modelli da produrre sono: • Modello business. Viene generato come risultato della fase di modellazione della realtà oggetto di studio (business modeling). L’obiettivo è capire cosa bisognerà realizzare (requisiti funzionali e non), quale contesto bisognerà automatizzare (studiarne struttura e dinamiche; a tal fine, spesso, si utilizzano i famosi Domain e/o Business Model), comprendere l’organigramma dell’organizzazione del cliente, valutare l’ordine di grandezza del progetto, individuare possibili ritorni dell’investimento per il cliente, eventuali debolezze, potenziali miglioramenti, iniziare a redi-

UML e ingegneria del software: dalla teoria alla pratica

81

gere un glossario della nomenclatura tecnica al fine di assicurarsi che, nelle fasi successive, il team di sviluppo parli lo stesso linguaggio del cliente, e così via. • Modello dei requisiti. Questo modello viene prodotto a seguito della fase comunemente detta analisi dei requisiti (Requirements Analysis). Scopo di questa fase è produrre una versione più tecnica dei requisiti del cliente evidenziati nella fase precedente. • Modello di analisi. Questo modello viene prodotto come risultato della fase di analisi i cui obiettivi sono di produrre una versione dettagliata e molto tecnica della Use Case View accogliendo anche le direttive provenienti dalle prime versioni del disegno dell’architettura del sistema (che a loro volta dipendono da questo modello), realizzare un modello concettuale, analizzare dettagliatamente le business rules da implementare, revisionare il prototipo — o comunque una descrizione — dell’interfaccia utente. • Modello di disegno. Anche in questo caso il modello di disegno è il prodotto della omonima fase, in cui ci si occupa di plasmare il sistema, trasformare in un modello direttamente traducibile in codice i vari requisiti (funzionali e non funzionali) forniti nel modello dei casi d’uso di analisi. • Modello fisico. Il modello fisico, a sua volta, è composto essenzialmente da due modelli: Deployment e Component. Non si tratta quindi del prodotto di una fase ben specifica, bensì sono risultati di rielaborazioni effettuate in diverse fasi. Il modello dei componenti mostra le dipendenze tra i vari componenti software che costituiscono il sistema. Come tale, la versione finale di tale modello dovrebbe essere il risultato della fase di disegno. Il modello di dispiegamento (Deployment) descrive la distribuzione fisica del sistema in termini del modo in cui le funzionalità sono ripartite sui nodi dell’architettura fisica del sistema. Si tratta quindi di un modello assolutamente indispensabile per le attività di disegno e implementazione del sistema. • Modello di test. Nella produzione di sistemi, con particolare riferimento a quelli di medio/grandi dimensioni, è opportuno effettuare test in tutte le fasi. Eventuali errori o lacune vanno eliminate prima possibile al fine di neutralizzare l’effetto delle relative ripercussioni sul sistema. È opportuno effettuare test prima di dichiarare conclusa ciascuna fase. In questo contesto si fa riferimento a uno specifico modello atto a verificare la rispondenza di ogni versione del sistema frutto di un’appropriata iterazione. Il modello di test dovrebbe essere generato non appena disponibile una nuova versione stabile dei casi d’uso. Nel realizzare il modello di test è necessa-

82

Capitolo 2. UML: struttura, organizzazione, utilizzo

rio: pianificare i test richiesti a seguito di ciascuna iterazione, disegnare e realizzare i test attraverso i Test Cases che specificano cosa verificare, e le Test Procedure che illustrano come eseguire i test. Quando possibile, sarebbe opportuno creare componenti eseguibili (Test Component) in grado di effettuare i test automaticamente, integrare i test necessari al termine dell’iterazione in corso con quelli eventualmente prodotti per le iterazioni precedenti. • Modello implementativo. Questo modello è frutto della fase di implementazione il cui obiettivo è tradurre in codice i manufatti prodotti nella fase di disegno e quindi realizzare il sistema in termini di componenti: codice sorgente, file XML, file di configurazione, scripts, ecc. I diagrammi dei casi d’uso mostrano un insieme di entità esterne al sistema, detti attori, associati con le funzionalità che il sistema stesso dovrà realizzare. Le funzionalità vengono espresse per mezzo di una sequenza di messaggi scambiati tra gli attori e il sistema. L’obiettivo dei casi d’uso è definire un comportamento coerente senza rivelare la struttura interna del sistema. I diagrammi delle classi, a disegno completato, forniscono una rappresentazione grafica dell’implementazione del sistema eseguibile e sono costituiti da un insieme di classi, interfacce, collaborazioni e relazioni tra tali elementi. I diagrammi degli oggetti rappresentano una variante dei diagrammi delle classi, tanto che anche la notazione utilizzata è pressoché equivalente, con le sole differenze che i nomi degli oggetti vengono sottolineati e le relazioni vengono dettagliate. Gli Objects Diagrams mostrano esplicitamente un numero di oggetti istanze delle classi e i relativi legami. I diagrammi di sequenza e collaborazione — detti anche di interazione in quanto mostrano le interazioni tra gli oggetti che costituiscono il sistema — vengono utilizzati per modellare il comportamento dinamico del sistema. I due diagrammi risultano molto simili: si può passare agevolmente dall’una all’atra rappresentazione (isomorfismo). Si differenziano per via dell’aspetto dell’interazione a cui conferiscono maggior rilievo: i diagrammi di sequenza focalizzano l’attenzione sull’ordine temporale dello scambio di messaggi, i diagrammi di collaborazione mettono in risalto l’organizzazione degli oggetti che si scambiano i messaggi. I diagrammi di stato essenzialmente descrivono automi a stati finiti, e pertanto sono costituiti da un insieme di stati, transazioni tra di essi, eventi e attività. Ogni stato rappresenta un periodo di tempo ben delimitato della vita di un oggetto durante il quale esso soddisfa precise condizioni. I diagrammi delle attività vengono utilizzati per illustrare il flusso di attività coinvolte in un particolare processo. Sono particolarmente utili per mostrare esecuzioni del comportamento del sistema di alto livello che non coinvolgano dettagli interni come lo scambio di messaggi catturati da altri diagrammi.

UML e ingegneria del software: dalla teoria alla pratica

83

I Components Diagram mostrano la struttura fisica del codice in termini di componenti e di reciproche dipendenze. Insieme ai Deployment Diagrams costituiscono la vista fisica del sistema. I diagrammi di dispiegamento (Deployment) mostrano l’architettura hardware e software del sistema: vengono illustrati gli elementi fisici (computer, reti, dispositivi fisici…) opportunamente interconnessi e i moduli software presenti su ciascuno di essi. In sintesi viene mostrato il dispiegamento del sistema a tempo di esecuzione sulla relativa architettura fisica, in termini di componenti e relative allocazioni nelle istanze dei nodi. Sebbene gli elementi del nucleo dello UML permettano di formalizzare molti aspetti di un sistema, è impossibile pensare che da soli siano sufficienti per illustrarne tutti i dettagli. Pertanto, al fine di mantenere il linguaggio semplice, flessibile e potente allo stesso tempo, lo UML è stato corredato sia di meccanismi generali utilizzabili per aggiungere informazioni supplementari difficilmente standardizzabili (per esempio le note), sia di veri e propri meccanismi di estensione (come per esempio gli stereotipi). I meccanismi generali forniti dallo UML sono: • Notes (note): informazioni supplementari, scritte con un formalismo che può variare dal linguaggio di programmazione, allo pseudocodice al testo in linguaggio naturale e pertanto difficilmente standardizzabili; • Adornments (ornamenti): elementi grafici che permettono di aggiungere semantica al linguaggio al fine di fornire al lettore una cognizione diretta di aspetti particolarmente importanti di specifici elementi (come per esempio le cardinalità); • Specifications (specificazioni): elementi di testo che aggiungono sintassi e semantica agli elementi dello UML, per esempio l’elenco degli attributi e metodi presenti appartenenti alle classi. • Common Divisions (divisioni comuni): nella modellazione di sistemi Object Oriented, ogni elemento reale può essere diviso almeno in due modi diversi: l’astrazione e il relativo corpo; si pensi al rapporto che esiste tra classi e oggetti; I meccanismi di estensioni forniti dallo UML sono: • Stereotypes (stereotipi): permettono di estendere il vocabolario dello UML aggiungendo nuovi elementi, specifici per il problema oggetto di studio, ottenuti estendendo opportunamente quelli esistenti; • Tagged Value (valori etichettati): estendono le proprietà degli elementi dello UML, aggiungendo nuove informazioni costituite dalla coppia nome–valore.

84

Capitolo 2. UML: struttura, organizzazione, utilizzo

• Constraints (vincoli). Durante il processo di disegno di sistemi che utilizzano particolari tecnologie piuttosto ricorrenti nei progetti, è necessario avvalersi di una serie di estensioni dello UML. Ciò al fine di renderlo in grado di rappresentare adeguatamente le caratteristiche peculiari di suddette tecnologie. Lo OMG ha deciso pertanto di standardizzare questi insiemi di regole, detti profili, come veri e propri plug-in dello UML. Particolarmente utili sono i profili CORBA ed EJB.

Capitolo

3

Introduzione agli Use Case Il problema di “questo particolare” progetto è che le specifiche continuano a variare! LVT “Un software veramente forte è in grado di cambiare i propri utenti” DONALT KNUTH

Introduzione Il presente capitolo è dedicato all’illustrazione dei concetti forniti dallo UML per supportare il delicato processo di analisi dei requisiti utente; l’attenzione sarà puntata sulla sua prima vista: la Use Cases View (Vista dei Casi d’Uso). In questa fase della modellazione del sistema è importante cercare di astrarsi il più possibile dall’implementazione dello stesso: bisogna concentrarsi su cosa il sistema dovrà fare e non sul come. Sebbene i casi d’uso siano utilizzati quasi esclusivamente per catturare i requisiti del cliente, essi si prestano a documentare funzionalità offerte da qualsiasi entità: sistema, sottosistema o classe. I casi d’uso sono modelli importantissimi nel contesto del ciclo di vita del software, sia perché sono quelli su cui il cliente appone la sua firma, sia perché stabiliscono in maniera inequivocabile — o dovrebbero farlo — quali funzioni il sistema dovrà fornire, le relative modalità di fruizione nonché il modo in cui trattare eventuali anomalie che potrebbero verificarsi. Costituiscono inoltre, già di per sé un prodotto di eccezionale importanza da consegnare al committente, in quanto, corredate da altri manufatti — il modello ad oggetti del dominio per esempio —, le viste dei casi d’uso condensano l’analisi del business dell’organizzazione. La tecnologia evolve molto rapidamente, mentre in genere l’evoluzione del business — si consideri per esempio un sistema bancario — è decisamente meno rapida. Pertanto i modelli dei casi d’uso si prestano a essere usate per molteplici funzioni:

2

Capitolo 3. Introduzione agli Use Case

implementazioni di nuove versioni del sistema, ottimizzazione dei processi interni, addestramento del personale, ecc. Come illustrato nel capitolo precedente, i processi di sviluppo prevedono diverse versioni di modelli dei casi d’uso: business e requirements (detta anche di sistema). Procedendo nelle varie fasi del processo si assiste a una graduale transizione sia del linguaggio, sia del livello di astrazione. Per ciò che concerne il primo, si assiste a un progressivo passaggio dal linguaggio del cliente a quello tecnico. Per quanto riguarda il livello di astrazione, si passa da un livello elevato a uno sempre più dettagliato fino a incorporare suggerimenti derivanti dall’architettura del sistema. I processi di sviluppo del software più popolari prevedono, tra l’altro, l’integrazione di approcci Use Case Driven. Ciò implica che la vista dei casi d’uso divenga il punto di riferimento per tutto il ciclo di sviluppo del sistema: dalla progettazione — in termini sia di disegno, sia di architettura hardware — allo sviluppo, ai test. La criticità della vista viene parzialmente attenuata dall’integrazione di altri approcci, quali, per esempio, Architecture Centric e Risk Driven. I modelli dei casi d’uso risultano costituiti da due proiezioni: statica e dinamica. La prima viene completamente catturata attraverso i diagrammi dei casi d’uso, mentre per la seconda sono disponibili diverse alternative che variano dal linguaggio naturale a opportuni diagrammi UML (sequence, activity, ecc.). La proiezione dinamica dovrebbe, illustrare sia le sequenze di attività da svolgere quanto tutto funziona correttamente (best scenario, scenario migliore), sia l’elenco completo degli inconvenienti che potrebbero insorgere, correlato dai provvedimenti che il sistema deve attuare per la relativa gestione. Quindi i programmatori, durante la fase di codifica, dovrebbero implementare il modello di disegno tenendo sotto controllo i casi d’uso relativi alla parte di sistema oggetto dello sviluppo. Da quanto detto, va da sé che errori o lacune nelle viste dei casi d’uso determinano problemi in tutte le restanti viste, fino a giungere, in situazioni estreme, all’invalidazione della qualità del sistema finale: magari il sistema è tecnicamente ben congegnato, realizzato rispettando tutti i canoni della produzione del software ma, semplicemente, non risolve i problemi del cliente. Non bisogna sempre pensare all’utente come l’“asino” di turno che inserisce i dati quando il cursore lampeggia in una casella. Al fine di minimizzare i rischi dovuti a possibili analisi dei requisiti approssimative o errate, si preferisce incorporare nel processo di sviluppo approcci di tipo incrementale e iterativo, tipicamente guidati dall’attenuazione dei fattori di rischio. Ciò permette di sottoporre al vaglio del cliente successive versioni del sistema per verificarne la rispondenza alle reali necessità ed eventualmente correggere tempestivamente la “rotta”. Lo stesso utente, potendo interagire con il sistema, o con una sua versione, riesce a chiarirsi le idee e a capire quanto i requisiti analizzati siano effettivamente rispondenti alle reali necessità: non a caso spesso si ricorre alla realizzazione di opportuni prototipi.

UML e ingegneria del software: dalla teoria alla pratica

3

La vista dei casi d’uso viene illustrata partendo dalla visione globale; ne viene illustrata la suddivisione in proiezione statica e dinamica, per poi scendere via via nei particolari, con un approccio tipicamente top down. In questa trattazione si è deciso di non attribuire eccessiva attenzione al flusso delle azioni (descrizione del comportamento dinamico degli use case) così come definito dai Tres Amigos in quanto, nella pratica, non è infrequente il ricorso a tecniche sensibilmente divergenti da quelle “standard” in grado di apportare ambìti benefici. Tali tecniche sono oggetto del capitolo successivo. Prima di entrare nel merito dell’oggetto di studio del capitolo, si è ritenuto opportuno presentare una breve digressione su cosa si intenda con la famosa — spesso misteriosa — definizione di requisiti utente e in cosa consista il relativo processo di analisi: tutti i tecnici ne hanno una percezione più o meno intuitiva, ma spesso ciò che è chiaro a livello intuitivo non è in definitiva facile da rendere a livello pratico. Nel corso del capitolo si utilizzano i termini use case e la relativa traduzione in lingua italiana casi d’uso con significato assolutamente equivalente.

I requisiti utente La realizzazione di un qualsiasi manufatto, e quindi anche di un nuovo sistema software, dovrebbe cominciare dalla raccolta dei relativi requisiti, la quale innesca il processo denominato “ciclo di vita del software”. Tipicamente la primissima fase è denominata analisi del business, in cui le informazioni raccolte e opportunamente elaborate (modello del business) costituiscono i prodotti di input della fase di analisi dei requisiti vera e propria. Visto lo stretto legame esistente tra le due fasi e le sovrapposizioni, non sempre è possibile effettuare una netta ripartizione tra le due. L’iter inizia tipicamente con il team degli esperti analisti, corredato da qualche commerciale, che si reca presso il cliente. A dire il vero, nelle organizzazioni più complesse il compito è interamente demandato ad appositi team composti quasi esclusivamente dai famosi Business Analyst: persone esperte dell’attività economica del cliente. Obiettivi della prima spedizione sono essenzialmente due: 1. capire (o carpire) le esigenze che determinano la necessità di un nuovo sistema informatico o dell’aggiornamento di quello esistente; 2. analizzare lo scenario del cliente con particolare attenzione alle risorse disponibili: umane, tecnologiche (parco macchine, connessioni, reti, software utilizzati…) e, perché no, anche di budget. È consigliabile iniziare con una serie di interviste ai manager, sia perché essi sono in grado di fornire una buona visione di insieme dell’organizzazione e della relativa attività economica, sia per evitare di urtarne — da subito — la suscettibilità.

4

Capitolo 3. Introduzione agli Use Case

Gli analisti più esperti fanno precedere a ogni intervista l’invio di un documento riportante i quesiti e gli argomenti di cui si intende discutere: proprio come taluni giornalisti politici italiani (trascurabili “pretese” dei cosiddetti politici). In questo contesto, tuttavia, si tratta di un buon espediente… sempre che i manager leggano il questionario.

Terminata la tornata iniziale, si redige un primo documento, corredato da un glossario, con la spiegazione degli acronimi e dei termini tecnici utilizzati (vocabolario del dominio del problema). Il passo successivo, purtroppo spesso trascurato, consiste nello scendere via via di livello nell’organigramma dell’organizzazione del cliente, fino a intervistare coloro che dovranno interagire direttamente con il sistema: i “poveri” utenti finali. Tipicamente, questi mancano di una visione d’insieme, ma in compenso conoscono ogni dettaglio sul modo in cui le procedure vengono realmente eseguite: dalla teoria alla pratica. Durante le interviste può sorgere qualche problema: alcuni dipendenti, sentendosi minacciati dall’introduzione di un nuovo sistema e magari indagati in qualche modo sulle relative mansioni, tendono a divenire improvvisamente reticenti ed enigmatici. Terminata anche questa fase, si redige una seconda versione del documento e… qui cominciano le dolenti note. Il problema che può emergere a questo punto è che le versioni fornite dai manager potrebbero risultare discordanti o addirittura contraddittorie rispetto a quelle fornite da coloro che eseguono fisicamente le varie procedure. A chi credere? Si tratta di affrontare l’eterno dilemma tra teoria e pratica. Cosa fare? Si reiterano le interviste, cercando di dettagliare maggiormente i punti controversi nel tentativo di rendere il tutto coerente, si riscrivono i vari documenti, ecc. Nei casi più estremi è necessario giungere a confronti all’americana — si mettono i manager faccia a faccia con i subalterni — nei quali, stranamente, le versioni dei manager tendono a prevalere. Si riorganizza di nuovo quello che ormai è diventato un enorme ammasso di appunti e si redige un ennesimo documento che man mano ha acquistato anch’esso di corposità. In questo documento sono condensati i famosi requisiti utente: le richieste legittime del cliente, le sue elucubrazioni mentali — in termini tecnici si possono trovare sinonimi più incisivi — le distorsioni, le aspettative, tanti omissis… Quando ci sono da esprimere pareri o suggerimenti, nessuno riesce a fare a meno di fornire la propria visione: ognuno vorrebbe dimensionare il nuovo sistema a propria immagine e somiglianza. Quando poi giunge il fatidico momento di firmare il documento delle specifiche, si assiste al gioco del fuggi fuggi meglio noto come secondo sport nazionale: lo “scaricabarile”. Infine, ottenuta la validazione dal cliente, non è infrequente un’attività di rielaborazione: si comincia a lavorare sul serio.

UML e ingegneria del software: dalla teoria alla pratica

5

Il primo embrione di analisi dei requisiti è tuttavia di grande interesse per i commerciali, in quanto dà un’idea del giro d’affari che ruota intorno al progetto: fa capire quanto ci si possa guadagnare…

Obiettivi dell’analisi dei requisiti utente Durante la conduzione del processo di analisi dei requisiti utente risulta importante tenere bene a mente quali siano gli obiettivi che si vuole raggiungere, ossia: • decidere e descrivere le specifiche, funzionali e non, concordate con il cliente le quali confluiscono nel contratto che l’azienda sviluppatrice sottoscrive con il cliente; • fornire una descrizione chiara e consistente delle funzionalità che il sistema dovrà realizzare: prodotto guida delle altri fasi del ciclo di vita del software; • generare un modello che consenta di eseguire il test del sistema; • iniziare a progettare l’interfaccia utente del sistema; • disporre, attraverso le varie versioni degli use case, di una traccia dell’evoluzione del sistema e/o dei requisiti utente (i quali, tipicamente, si “automodificano” a ogni ciclo lunare); • eseguire la stima dei tempi e quindi dei costi; • pianificare il processo di sviluppo del sistema (attribuzione delle priorità, definizione delle iterazioni, ecc.). Per poter realizzare la vista dei casi d’uso, è necessario definire precisamente il sistema (in termine dei suoi “confini”), identificare correttamente gli attori, definire le funzioni dei vari diagrammi, determinare le relazioni tra use case e validare il sistema stesso. Si tratta di un’attività che richiede un’elevata interazione con il cliente e con le persone che rappresentano gli attori del sistema stesso.

Sebbene ogni qualvolta venga proposto uno strumento dedicato all’analisi concettuale di un sistema si affermi che esso possa essere facilmente compreso da clienti con nessuna esperienza informatica (vedi diagrammi E-R e/o DFD), nella realtà ciò si verifica molto raramente: almeno è questa la personale esperienza dell’autore. Pertanto l’analisi dei requisiti deve essere comunque espres-

6

Capitolo 3. Introduzione agli Use Case sa anche per mezzo di strumenti non eccessivamente formali, quali per esempio opportuni template o addirittura il buon classico documento in linguaggio naturale. Inoltre è sempre consigliabile pianificare opportune sessioni di training relative al formalismo utilizzato (per esempio use case), allo scopo di fornire agli utenti/clienti i requisiti necessari per leggere e interpretare i vari modelli che dovranno validare.

Può succedere — ogni riferimento alla vita reale dell’autore è puramente casuale — di sviluppare elegantissimi diagrammi dei casi d’uso ben strutturati, colmi di relazioni e di generalizzazioni, giungere al momento di revisionarli con il team del cliente, e di vedere poi comparire progressivamente nei loro volti un certo sconforto di fronte al livello di astrazione prodotto.

È bene ricordare che, seppure anche la vista dei casi d’uso debba essere un modello Object Oriented a tutti gli effetti, è talvolta ahimè necessario sacrificare parzialmente la propria formalità al fine di favorire il dialogo con il cliente.

Da tener presente che lo UML (secondo quanto riportato nei libri dei Tres Amigos) prevede di descrivere il comportamento dinamico dei casi d’uso per mezzo del relativo flusso di eventi, il quale specifica quando uno use case inizia, quando termina, quando interagisce con un attore, quali oggetti vengono scambiati, ecc. Il ruolo dei vari diagrammi dei casi d’uso, per tutta una serie di motivazioni, dovrebbe, in qualche modo, venir dimensionato a rappresentarne una sorta di overview. La speranza è che l’utilizzo di un formalismo grafico renda il tutto più accattivante, immediato e comprensibile al cliente.

Chiaramente, attraverso una serie di grafici non è possibile una completa descrizione di tutti i requisiti del cliente. Il monito da tenere sempre a mente è che una deficienza grave nell’analisi dei requisiti può generare grandi problemi nel processo di sviluppo del software. Ciò però non deve neanche generare la paralisi del processo di sviluppo dovuta al timore di proseguire nelle fasi successive mentre i requisiti non sono ancora completi: in ultima analisi si utilizzano processi iterativi e incrementali anche per mitigare questi rischi. In ogni modo, la rappresentazione grafica è decisamente utile e molto accattivante, ma assolutamente non sufficiente.

UML e ingegneria del software: dalla teoria alla pratica

7

Un ultimo — solo in senso temporale di esposizione — aspetto da tenere bene a mente è che i requisiti utente variano: bisogna rassegnarsi all’idea che, per quanto meticolosi, esperti e scaltri si possa essere, è impossibile prevedere tutti i dettagli fin dall’inizio. Sebbene ciò possa apparire in contrasto con gli insegnamenti ricevuti nei banchi dell’università, la realtà è che l’evoluzione è insita nel destino dell’universo e che i sistemi informatici non esulano da tale regola. Il sogno di alcuni manager informatici consiste nel congelare le specifiche prima di proseguire nell’attività di codifica. Sebbene ciò possa sembrare logico, si corre “l’insignificante” rischio di realizzare un sistema, magari bellissimo, che però semplicemente non era quello desiderato dal cliente e che non è di alcun aiuto: con un esempio, l’utente ha bisogno di una scaletta per arrampicarsi sull’albero e gli viene fornito un ascensore adatto a un grattacielo.

Dunque, invece di investire un tempo eccessivo nell’analisi dei requisiti nel vano tentativo di studiare ogni dettaglio per realizzare un modello “perfetto” prima di procedere alla fase successiva, paralizzando tra l’altro l’intero processo, forse potrebbe essere opportuno cercare sì di fare del proprio meglio, ma comunque avanzare con un processo di sviluppo di tipo iterativo e incrementale. Ovviamente ciò non significa realizzare un prodotto di scarsa qualità… “tanto dovrà cambiare”. È necessario anticipare il problema progettando sistemi flessibili, cercando di circoscrivere le aree più a rischio di variazione, studiando opportuni piani per la gestione dei temutissimi “change requirements”. “Indipendentemente dalla fase del ciclo di vita in cui ci si trova, il sistema varierà e il desiderio di cambiare persisterà per tutto il ciclo di vita” E.H. BERSOFF

Diagramma di “diritti e doveri” del cliente Uno dei problemi storici nel mondo della progettazione dei sistemi informatici risiede nel difficile rapporto, in genere di carattere comunicativo, che spesso si instaura tra tecnici informatici e committenti che necessitano di sistemi informatici per migliorare la qualità e l’efficienza del proprio lavoro. Si tratta di problematiche antiche che rivivono sotto forme moderne. Materia certamente nota: la conoscono tutti fin dai primi corsi di programmazione, eppure si continua a perseverare nell’errore in modo decisamente diabolico (con accezione latina). Obiettivo del presente paragrafo, giocando un po’ con i diagrammi dello UML, è di illustrare quelli che dovrebbero essere diritti e doveri delle due parti in causa: team tecnici e utenti/clienti. Si è deciso di presentare l’argomento utilizzando un espe-

8

Capitolo 3. Introduzione agli Use Case

diente tecnico: interfacce e componenti nel tentativo di rendere più piacevole la trattazione. (Ci si augura che ciò non disturbi l’eterno sonno dell’eccelso Cesare Beccaria… A dire il vero ci sarebbero altri motivi ben più reali del presente paragrafo per turbare il riposo del sommo pensatore). Un’interfaccia (in termini di classi) è a tutti gli effetti un contratto tra classi client che utilizzano i servizi e la classe server che li fornisce. Nel contesto della presente trattazione, entrambi i componenti (team tecnico e cliente) espongono una propria interfaccia (si impegnano a erogare determinati servizi: quelli appunto definiti nella propria interfaccia) e utilizzano i servizi esposti dall’interfaccia dell’altro componente. Quindi entrambi presentano sia comportamento da client (utilizzano determinati servizi), sia da server (implementano i servizi dichiarati dalla propria interfaccia). Il componente Cliente, poiché è quello che alloca le risorse economiche, può pretendere di accettare la sottoscrizione da un componente che esponga l’interfaccia desiderata (offre precisi servizi). Quanto riportato nei prossimi paragrafi è una rivisitazione tecnica di vari libri e articoli. Il contributo maggiore comunque è dovuto, strano ma vero, alla rivista “Microsoft Press” e in particolare al Software Requirements di Karl Wiegers. Con riferimento alla fig. 3.1, di seguito viene presentata una disamina commentata dei vari metodi esposti nelle interfacce. La descrizione dei servizi esposti spesso si presta ad

Figura 3.1 — Diagramma dei componenti di “diritti e doveri dei clienti”.

DoveriCliente

Cliente

+clarifyRequirements( requirements ) +makeTimelyDecision( requirements ) +makeTimelyDecision( solutions ) +respectTimeAndCosts() +setRequirementsPriorities( requirements ) +reviewModel() +reviewPrototype() +notifyChangeRequirements() +respectSWDevelopmentsProcess()

TeamTecnico

DoveriTeamTecnico +setContextLanguage( businessLanguage ) +setBusinessRules( businessRules ) +setObjectives( objectives ) +getRequirementsModel() +getRequirementsModelExplanation() +getSolutionIdeas( requirements ) +getSolutionIdeas( newProblem ) +setClientRespect() +setProductEasyToUse( characteristics ) +changeRequirements( newRequirements ) +getChangeReqRealEstimation() +getQualitySystem()

UML e ingegneria del software: dalla teoria alla pratica

9

essere illustrata per mezzo della tecnica denominata design by contract (trattato in maggior dettaglio nel capitolo dedicato ai principi base dell’Object Oriented), e in particolare attraverso: 1. prerequisiti: quando un client generico utilizza un determinato servizio esposto da un server deve impegnarsi a rispettarne le relative eventuali precondizioni; 2. requisiti durante l’utilizzo: responsabilità dei server che espongono servizi è garantire il soddisfacimento di determinate condizioni durante la fruizione del servizio stesso; 3. post condizioni: sempre i server, per ogni servizio esposto, devono assicurare, al termine dell’erogazione dello stesso, il conseguimento dei risultati decretati nelle post condizioni, a patto chiaramente che i prerequisiti siano soddisfatti.

setContextLanguage(BusinessLanguage : Language)

Questo metodo indica che il componente TeamTecnico dovrebbe fornire un “servizio” che permetta di sincronizzare il proprio linguaggio con quello utilizzato dal cliente, in altre parole è diritto di questi ultimi attendersi che il team degli analisti parli il linguaggio “tecnico” utilizzato nel proprio ambiente di business. Ciò però comporta il dovere dei clienti di provvedere le risorse necessarie (in termini di personale, di strutture, di documentazione nonché di tempo) affinché il team tecnico possa raggiungere l’obiettivo di appropriarsi del linguaggio. Utilizzando il design by contract si può asserire: 1. i prerequisiti sono: il Cliente deve impegnarsi ad allocare le risorse necessarie per insegnare e verificare l’esatto apprendimento del linguaggio tecnico del business; 2. i requisiti durante l’utilizzo sono: il TeamTecnico garantisce di compiere ogni “sforzo” necessario per acquisire il linguaggio tecnico; 3. post condizioni sono: il team tecnico padroneggerà il linguaggio tecnico dell’area business nei limiti imposti dalle relative necessità e dal tempo a disposizione.

setBusinessRules(businessRules : businessRule[]) setObjectives(objectives : objective[])

10

Capitolo 3. Introduzione agli Use Case

In modo completamente analogo al caso precedente, è del tutto legittimo attendersi che il cliente pretenda che il team tecnico acquisisca le regole vigenti nel proprio business e, in qualche modo, ne condivida gli obiettivi finanziari e strategici: i sistemi informatici, essendo parte importante di quelli informativi, recitano un ruolo di primaria importanza nel conseguimento degli obiettivi strategici delle aziende. Basti pensare ad esempio a siti e-commerce o a sistemi per la gestione degli investimenti bancari (area treasury).

RequirementsModel : getRequirementsModel() ModelExplanation : getRequirementsModelExplanation()

Altro diritto/dovere del cliente è richiedere di visionare il modello dei requisiti e di ricevere tutte le spiegazioni del caso. Per quanto riguarda il primo metodo (getRequirementsModel), le precondizioni che deve rispettare il cliente sono di aver provveduto a invocare i metodi precedenti, ossia aver facilitato l’apprendimento del proprio linguaggio, aver illustrato in maniera precisa e puntuale le business rules, aver spiegato i propri obiettivi ecc., mentre le condizioni durante l’uso, garantite dal TeamTecnico, sono che il modello verrà prodotto in accordo alle informazioni fornite dal cliente e la post condizione è che il modello fornito sarà completo, rispondente alle esigenze del cliente e di elevata qualità tecnica. Per ciò che riguarda il secondo metodo (getRequirementsModelExplanation), le precondizioni sono che il cliente abbia eseguito il metodo precedente, ossia abbia ottenuto una istanza del RequirementsModel. Le condizioni durante l’utilizzo e le post condizioni sono piuttosto evidenti: il TeamTecnico deve adoperarsi per chiarire i dubbi espressi dal cliente e verificare se questi celino qualche requisito non detto e quindi fornire una spiegazione puntuale, precisa ed esauriente.

setClientRespect()

Questo metodo, in prima istanza, potrebbe sembrare piuttosto banale, e invece è proprio il caso di commentarlo. Spesso i team tecnici sembrano non nutrire il dovuto rispetto per “il cliente” (in questo contesto si fa riferimento all’utente finale). Troppo spesso si considerano i clienti come gli zucconi di turno agli ordini del cursore lampeggiante, i quali, per definizione, non riescono mai a impostare i dati in maniera corretta. Suggeriscono nulla uno dei primissimi assiomi della programmazione: “il cliente è uno sciocco” o i famosi “test dell’asino” da farsi per verificare se il sistema sia o meno a prova di utente? Talune volte sarebbe opportuno provare compassione (anche qui, accezione latina ovviamente) per la frustrazione spesso provata dai clienti che interagiscono con nuovi

UML e ingegneria del software: dalla teoria alla pratica

11

sistemi. Qualora tale frustrazione esista veramente, sarebbe il caso si interrogarsi se si è fatto del tutto per neutralizzarla o quantomeno minimizzarla. Naturalmente, se gli utenti/clienti forniscono indicazioni (requisiti) errati, difficilmente verificabili, c’è poco da fare (vige l’acronimo GIGO, Garbage In, Garbage Out ovvero “entra spazzatura, esce spazzatura”; a questo acronimo viene spesso preferita la più prosaica versione SISO la cui traduzione viene demandata alla fantasia del lettore…), ma se i tecnici si ingannano con un’idea preconcetta basata sulla propria, sopravvalutata esperienza e trascurano l’attività di convalida con gli stessi utenti, allora difficilmente il sistema risulterà soddisfare le reali necessità degli stessi. Per entrare nel giusto spirito si provi a immaginare di dover realizzare un sistema complesso utilizzando un framework esistente, magari fornito da terze parti, non intuitivo e con scarsa documentazione tecnica: a chi non è capitato?

getSolutionIdeas(newProblem : Problem) getSolutionIdeas(requirement : Requirement)

Questi metodi decretano che il team di sviluppo deve essere in grado di comprendere le reali necessità del cliente — eventualmente anche non dette o non pensate dal cliente ma comprese attraverso la sfera di cristallo chiamata esperienza — e provvedere idee e alternative relative sia ai requisiti specificati dal clienti, sia a dubbi e perplessità palesate. In altre parole è diritto del cliente ottenere proposte di soluzioni relative ai propri problemi e requisiti basate su esperienza e competenza del team tecnico. Qualora la famosa “esperienza” manchi, non è comunque la fine del mondo: è possibile sopperire con una buona dose di impegno e intelligenza; ma se mancano anche queste qualità… la situazione diviene davvero complicata.

setProductEasyToUse(characteristics : Characteristic[])

Visto e considerato che il cliente finanzia il sistema e sarà “lui” a utilizzarlo, dovrebbe essere del tutto comprensibile che si attenda di ricevere un prodotto il quale, ovviamente, assolva alle problematiche che ne hanno generato la necessità e che, al tempo stesso, sia semplice da utilizzarsi in funzione dei propri parametri/necessità. Talune volte i team tecnici costruiscono interfacce utente incomprensibili e disordinate o magari semplicemente ottimizzate in funzione della struttura del sistema o in base a quelle che erano considerate le esigenze dell’utente dal team tecnico. Si tende banalmente a dimenticare chi sarà il fruitore ultimo del sistema stesso. Non è infrequente anche imbattersi in situazioni in cui l’utente, per eseguire determinate funzionalità ricorrenti, sia costretto a eseguire tortuosi giri e incomprensibili procedu-

12

Capitolo 3. Introduzione agli Use Case

re, mentre sarebbe del tutto legittimo attendersi un utilizzo più lineare, eventualmente ottimizzato in base ai servizi richiesti più frequentemente. Nei casi peggiori, sempre molto frequenti, le complicatissime interfacce utente sono solo la punta dell’iceberg… Accade che l’intero modello del sistema sia basato su “intuizioni” tecniche, magari anche interessanti, ma lontanissime dalla realtà… Nella fase di consegna si assiste alle crisi isteriche dell’“intuitore”: “Ma come? Non si riesce a forzare la realtà alla struttura del sistema?”.

ChangeRequirements(newRequirements : Requirements[])

Questo è indubbiamente il metodo che incute più paura di tutti: basta il nome per generare tremori nel team tecnico. Sfortunatamente si tratta di una pretesa legittima del cliente: l’obiettivo comune delle parti dovrebbe essere quello di progettare un sistema di qualità che effettivamente soddisfi il cliente e non di “consegnare qualcosa di eseguibile”. In altri ambienti, per esempio nella costruzioni di edifici, risultano accettabili incursioni nel cantiere dei committenti con eventuali richieste di modifiche. Pertanto non c’è da stupirsi che, iniziando a vedere il sistema reale e cominciando a interagirvi il cliente possa chiederne dei cambiamenti, perché per esempio non era riuscito a capire in anticipo eventuali problematiche o semplicemente perché erano state fraintese. Come al solito è necessario un mea culpa: quando l’utente invoca il fastidiosissimo metodo changeRequirements, quanta colpa ha veramente il cliente? Spesso accade anche che il team tecnico, pur avvertendo difficoltà o incongruenze, semplicemente le ignori dimenticando che prima o poi il problema si ripresenterà, ma questa volta amplificato dal fattore tempo. Continuando con l’esempio dell’edificio, è un po’ come se la squadra che lo sta costruendo si rende conto che gli spazi lasciati per le tubature sono assolutamente insufficienti, ma prosegue ugualmente nella costruzione, dimenticando che prima o poi bisognerà demolire il tutto e ricostruire e questa volta con pochissimo tempo a disposizione. L’importante è fornire l’edificio per la data di presentazione prevista, e poi… Poi è sempre possibile inserire delle toppe.

Estimation : getChangeReqRealEstimation(newReq : Requirement)

Questo metodo è strettamente collegato a quello precedente. In caso di richiesta di cambiamenti dei requisiti, sarebbe naturale da parte del cliente ottenere una stima attendibile e onesta dei tempi necessari per soddisfare tali richieste. Nella realtà accade che le richieste ritenute più divertenti e meno ostiche tendano a ricevere una stima ottimistica, mentre altre, magari anche molto importanti, tendano a venir sovrastimate. A onor del vero va detto che, indubbiamente, uno dei principali moti-

UML e ingegneria del software: dalla teoria alla pratica

13

vi di sollecitudine dei team tecnici consiste nel giocare con nuovi giocattoli. Qualora venga fiutata la possibilità di poter cogliere l’occasione per utilizzare nuove tecnologie, le stime tendono improvvisamente a divenire molto ottimistiche, mentre il caso contrario implica stime funeste.

System : getQualitySystem().

Il commento di questo metodo viene deliberatamente lasciato al lettore. Esaurita l’analisi dei diritti del cliente, si passa ora a esaminarne i doveri… Alcuni di essi sono già stati espressi per mezzo di precondizioni dei metodi del TeamTecnico. In particolare è dovere del cliente insegnare il linguaggio tecnico, le regole dell’ambiente oggetto di analisi, cercare di spiegarsi nel modo più chiaro possibile, ecc. A tal fine il cliente deve allocare tutte le risorse necessarie, tra cui il tempo, per illustrare, spiegare, verificare e così via.

Clarification[] : clarifyRequirement(requirements : Requirement[])

Responsabilità dei clienti è fornire i requisiti al TeamTecnico, allocando il tempo necessario per chiarire quelli ritenuti meno evidenti. È importante che sia i requisiti, sia le relative spiegazioni siano precise e concise.

Decision[] : makeTimelyDecision(requirements : Requirement[]) Decision[] : makeTimelyDecision(solutions : Solution[])

Altra responsabilità dei cliente consiste nel prendere decisioni e nel farlo in tempi umani e non geologici. In generale le decisioni del cliente dovrebbero essere relative sia alla selezione delle ipotesi di soluzioni ritenute più valide tra quelle esposte dal TeamTecnico, sia relative a requisiti non chiari o di costoso ottenimento. In merito alla selezione delle varie alternative di soluzioni viene alla mente il film La vita è bella… “Cosa gradisce? Del maiale grasso grasso grasso con patate cotte in olio grasso grasso grasso oppure del salmone leggero leggero leggero con un’insalata leggerissima?”. Cosa sceglierà mai il cliente? La vocina presente nell’autore afferma che molti clienti sceglierebbero comunque il maiale grasso grasso grasso…

respectTimeAndCosts()

14

Capitolo 3. Introduzione agli Use Case

Questo metodo può essere visto come lo speculare del metodo setRespect(): così come il TeamTecnico deve rispettare il cliente, allo stesso modo, quest’ultimo deve rispettare la stima dei tempi e dei costi fornita dal TeamTecnico, e produrre ogni sforzo per limitare le proprie richieste di ridurre i tempi per la messa in opera del sistema. Quando le pressioni del cliente cominciano a essere eccessive, si vede la differenza tra capi progetto all’altezza del proprio ruolo e quelli che credono che il capo progetto è colui che inserisce una serie di numeri nelle caselline del progetto realizzato per mezzo di Microsoft Project. Un altro problema tipico, che si verifica di sovente, è relativo al fatto che clienti/utenti desidererebbero esporre molto rapidamente, si intende, le funzionalità richieste, o magari addirittura solamente nominarle, per poi vedersele perfettamente incorporate nel sistema nell’arco di qualche giorno. Come dire che i clienti tendono a non rispettare il processo di sviluppo del software adottato dal team tecnico. Da un lato si richiede elevata qualità e dall’altro si pretende di ottenerla subito.

setRequirementsPriorities(priorities : RequirementsPriority[])

Su questo argomento si ritornerà più in dettaglio nel paragrafo dedicato al processo di sviluppo. Per adesso basti pensare che è necessario che il cliente corredi i vari requisiti con le relative priorità. È importante però sottolineare che queste devono ritenersi a carattere prettamente indicativo, in quanto l’ordine con cui le varie funzionalità verranno realizzate deve dipendere dal piano di suddivisione del processo in iterazioni: è compito dell’architetto e del capo progetto decidere tale piano in funzione anche di altri parametri, quali per esempio la riduzione dei rischi.

reviewModel() reviewPrototype()

Altro compito molto importante dei clienti consiste nel revisionare i modelli prodotti dal TeamTecnico. Spesso si tratta di un’attività tediosa e non priva di elementi di frustrazione, però di importanza fondamentale per il processo di sviluppo del sistema. Una volta validati i vari modelli si prosegue con lo sviluppo del sistema ed eventuali errori nelle specifiche possono generare conseguenze notevoli. Chiaramente il colloquio tra il team tecnico e il cliente non si esaurisce qui: è sempre cosa buona e giusta chiedere ai clienti ulteriori chiarificazioni in caso di incertezze relative ai requisiti o proporre nuove idee. Ciò dovrebbe essere del tutto naturale: si assiste spesso a persone che volendo costruire la propria casa, cominciano non solo a visionare i vari progetti, ma anche a metterci le

UML e ingegneria del software: dalla teoria alla pratica

15

mani in prima persona. Chiaramente se dovesse accadere che, per esempio, la cucina risulti troppo piccola, sarebbe compito dell’architetto, in funzione della propria esperienza, evidenziare l’anomalia. Per ciò che concerne il metodo reviewPrototype(), non ci sono dubbi: il cliente adora a sua volta giocare con i nuovi giocattoli e quindi sicuramente spenderà il tempo necessario, se non di più, per analizzare i prototipi in tutti i suoi dettagli. I limiti dei prototipi sono stati evidenziati nel capitolo precedente, e li si può sintetizzare riproponendo il commento di Stroustrup: il loro difetto principale “è di somigliare terribilmente al sistema finale”.

notifyChangeRequirements()

Questo metodo evidenzia una responsabilità fondamentale del cliente: comunicare i cambiamenti di specifiche non appena avvertiti. Nel caso in cui, eseguendo un’incursione nella casa in costruzione, il cliente si accorga che la cucina è effettivamente troppo piccola, lo dovrebbe comunicare immediatamente senza attendere che i vari muri divisori siano edificati e che quindi una loro risistemazione diventi piuttosto gravosa se non impossibile.

respectSWdevelopmentProcess()

Spesso una delle manchevolezze dei clienti è di non rispettare il processo di sviluppo del software, nel senso che loro tendenza naturale è di desiderare la botte piena e la moglie ubriaca: sistema di qualità corredato dai vari modelli e realizzato in un paio di fasi lunari. I sistemi software, alla stregua degli altri prodotti ingegneristici, prevedono opportune fasi e diversi modelli da produrre, ognuno dei quali richiede un certo quantitativo di tempo. Spesso sono gli stessi clienti che richiedono di codificare direttamente per poter giocare il prima possibile con il sistema. In ogni modo, il sogno dei clienti/utenti è di comunicare le funzioni da realizzare, magari semplicemente citandone il nome, e poi iniziare a effettuare i vari test dopo qualche giorno. Spesso palesare l’inattuabilità di questo sogno diviene impossibile da parte di qualche commerciale di turno, ansioso forse di mascherare o farsi perdonare qualche marachella magari commessa in fase di fatturazione. La relativa decisione, a questo punto obbligatoria, di tenersi buono il cliente porta a promettere cose la cui portata esula assolutamente dalle reali possibilità… Iterato poi il comportamento per un almeno un paio di generazioni, si ottiene la mutazione genetica anche dei clienti. D’altronde si immagini di voler costruire la propria casa e per questo convocare due ditte edili per ottenere altrettanti preventivi. Si supponga ancora che l’addetto commer-

16

Capitolo 3. Introduzione agli Use Case

Figura 3.2 — Rappresentazione schematica della proiezione statica della use case view. STATIC PERSPECTIVE

USE CASE DIAGRAM

ACTOR

USE CASE

USE CASE VIEW

RELATIONSHIP

DINAMIC PERSPECTIVE

DESIGN VIEW

DOCUMENT

TEMPLATE

SEQUENCE DIAGRAM

COMPONENT VIEW ACTIVITY DIAGRAM

IMPLEMENTATION VIEW

DEPLOYMENT VIEW

ciale della prima vi dica: “Non c’è problema, in un paio di settimane inizieremo i lavori. La mia azienda dispone di squadre di muratori e carpentieri che sono degli artisti. Lavoreranno come forsennati per consegnarvi la casa dopo sei mesi. Il tutto per un costo di centomila euro, migliaio in più o in meno”. L’addetto commerciale della seconda ditta, invece, vi dirà: “In effetti è necessario capire che tipo di casa si vuole e quale sia conveniente costruire, verificare il terreno, esaminare il piano regolatore, realizzare qualche progetto di massima insieme. Solo allora sarà possibile una stima appropriata. In ogni modo lo studio iniziale di fattibilità le costerà sui cinquemila euro”. Ora, quanti di voi affiderebbero la costruzione della casa alla seconda azienda?

Use Cases View Le use case view (viste dei casi d’uso) nei processi di sviluppo del software assumono un ruolo di primaria importanza, sia perché vengono sottoposte alla firma del cliente — generalmente quella dei requisiti utente… e, per la firma, si consiglia di richiedere l’utilizzo del sangue come inchiostro —, sia perché le restanti viste si occupano di modellare

UML e ingegneria del software: dalla teoria alla pratica

17

quanto specificato in quella dei requisiti. La proiezione statica della vista dei casi d’uso si basa sugli omonimi diagrammi, frutto del lavoro svolto da Ivar Jacobson durante la sua collaborazione con la Ericsson nell’ambito dello sviluppo di un sistema denominato AXE. Visto il successo riscosso dai diagrammi e percepitene le relative potenzialità, i Tres Amigos hanno pensato bene di inglobarli nello UML. Gli use case diagram sono utilizzati per dettagliare le funzionalità del sistema, dando particolare rilievo agli attori che interagiscono con esse. Le singole funzionalità vengono rappresentate graficamente attraverso elementi ellittici denominati use case. A questo punto si corre il rischio di fare confusione. In effetti, la prima vista logica, i relativi diagrammi, o meglio quelli che si occupano della proiezione statica, e alcuni elementi di questi ultimi, quelli che rappresentano le funzioni del sistema, si chiamano tutti, grazie a un sovrumano lavoro di fantasia, use case. Per tentare di far chiarezza si consideri il diagramma riportato in fig. 3.2. La prima vista è quindi composta da più diagrammi, di cui quelli derogati a modellarne la proiezione statica sono gli use case diagram, i quali permettono di specificare le funzioni del sistema per mezzo di elementi grafici a forma di ellissi detti use case. La vista dei casi d’uso, come gran parte dei modelli, prevede due componenti: statica e dinamica, di cui solo la prima è rappresentabile per mezzo dei use case diagram. Quindi, al fine di colmare la lacuna e modellare anche la proiezione dinamica, fanno la loro apparizione gli interaction e activity diagram (diagrammi di interazione e diagrammi di attività), utilizzati per rappresentare particolari istanze dei casi d’uso dette scenari. Entrambi sono oggetto di studio di appositi capitoli; per ora basti sapere che servono per modellare aspetti dinamici del sistema. Nei paragrafi seguenti si evidenzia che il ricorso a tali diagrammi, sebbene permetta di illustrare il comportamento dinamico delle funzioni del sistema, può risultare decisamente laborioso e non sempre di facile lettura. Probabilmente opportuni template dei casi d’uso finiscono per essere lo strumento migliore: sono chiari, immediati e decisamente facili da aggiornare. Quando i diagrammi di interazione vengono utilizzati nella use cases view, ne realizzano gli scenario: versioni a elevato grado di astrazione dei flussi di azioni racchiusi nei casi d’uso. Tipicamente, degli stessi diagrammi vengono realizzate versioni di maggior dettaglio nella design view allo scopo di rappresentare il flusso dei messaggi che gli oggetti si scambiano per produrre un determinato risultato. Semplificando al massimo il concetto, è possibile affermare la seguente proporzione: gli scenari stanno agli use case come gli oggetti stanno alle classi. Talune volte può capitare di incontrare in opportune versioni degli use case view anche diagrammi delle classi: ciò è una buona norma a patto che il grado di astrazione degli stessi rimanga al livello concettuale e che le classi identificate siano quelle appartenenti al mondo reale e non all’infrastruttura del sistema (si parla di modello del dominio). L’obiettivo è quello di modellare e quindi studiare le relazioni esistenti tra le varie entità presenti nell’area di business del cliente (dominio del problema).

18

Capitolo 3. Introduzione agli Use Case

A lavoro concluso, i diagrammi dei casi d’uso devono rappresentare tutte le funzioni del sistema, a partire dal relativo avvio, o richiesta, che tipicamente viene generata da un attore del sistema, fino alla relativa conclusione. Le proprietà che tutti i modelli dovrebbero possedere (accuratezza, consistenza, semplicità e manutenibilità) in questo contesto assumono un’importanza imprescindibile. Gli attori, sono entità esterne al sistema che hanno interesse a interagire con esso. Pertanto, contrariamente a quanto lascerebbe pensare il nome, e a memorie derivanti da metodologie quali DFD (Data Flow Diagram, diagramma del flusso dei dati), gli attori sono sia persone fisiche, sia sistemi o dispositivi hardware che interagiscono con il sistema. Va sottolineato, per l’ennesima volta, che a questo livello di analisi, il sistema va visto come una scatola nera, pertanto si deve descrivere cosa fa il sistema senza specificare il come: ci si deve occupare dello spazio del problema e non di quello delle soluzioni. L’impegno è proprio nel cercare di isolarsi, di astrarsi il più possibile da dettagli realizzativi il cui studio è demandato alle apposite fasi.

Perché utilizzare i diagrammi dei casi d’uso?. L’interrogazione posta nel titolo potrebbe risultare piuttosto retorica e la risposta piuttosto convenzionale potrebbe essere: “perché fanno parte dello UML”. Allora si provi a porla in questi termini “perché i Tres Amigos hanno inserito i diagrammi dei casi d’uso nello UML? Quali sono i vantaggi?”. Molto brevemente le principali peculiarità dei casi d’uso possono ricondursi ai seguenti punti: • si tratta di uno strumento facilmente comprensibile dagli utenti e quindi appropriato per comunicare con essi e per ottenere le necessarie validazioni. • si prestano a essere utilizzati con diversi livelli di formalità. Generalmente, nella primissime fasi si è concentrati nel comprendere più intimamente le necessità dell’utente e quindi si conferisce minore importanza ai principi dell’Object Oriented, anche perché spesso non facilmente comprensibili dall’utente medio. Nelle fasi successive invece si è anche coinvolti nel processo di sviluppo del sistema e quindi il livello di formalità deve essere necessariamente elevato. • permettono di definire molto chiaramente i confini del sistema e di evidenziarne gli attori sia umani che non. • evidenziano il comportamento del sistema, permettendo di evidenziare eventuali incoerenze e lacune nell’analisi dei requisiti.

UML e ingegneria del software: dalla teoria alla pratica

19

• permettono di realizzare un’ottima documentazione. • i casi d’uso opportunamente strutturati si prestano a essere utilizzati come scenari di test (i test case).

Fruitori della use case view La vista dei casi d’uso è oggetto di interesse per un vasto insieme di figure professionali coinvolte nel ciclo di vita del sistema: dai clienti ai capi progetto, dagli architetti ai tester, dagli addetti al marketing ai commerciali e così via. I primi, in ordine cronologico, a fruire della vista dei casi d’uso sono i clienti, i capi progetto e i business analysts: le use case view specificano le funzionalità che il sistema dovrà realizzare e come queste verranno fruite. In quest’ottica le viste sono necessarie per riuscire a carpire i requisiti utente: reali necessità, sogni più o meno consci, eventuali reticenze… In molte organizzazioni sono previsti opportuni team che si occupano unicamente di analizzare l’attività economica del cliente, di capire i requisiti del sistema e di redigere il documento dei requisiti. Sono i “padroni” della use case view. Queste figure professionali vengono tipicamente denominate business analysts. La vista dei casi d’uso è necessaria poi al team degli sviluppatori (architetti, disegnatori, codificatori) poiché fornisce direttive, chiare e precise — un sogno — su come realizzare il modello. In questo contesto, con il termine di architetti si fa riferimento a quelli software, ossia a coloro che si occupano di disegnare il sistema in termini di classi. Ovviamente anche gli architetti hardware hanno interesse a fruire della vista degli use case perché fornisce le prime disposizioni su come organizzare il sistema in termini di dispositivi fisici (server, connessioni, proxy, ecc.). Come si avrà modo di vedere nel capitolo dedicato, nei processi di sviluppo use case driven e architecture centric esiste una mutua dipendenza tra la use case view e la vista fisica del sistema: le funzionalità da realizzare forniscono l’orientamento iniziale su come organizzare l’infrastruttura hardware così come quest’ultima influenza la modalità con la quale le diverse funzioni possono essere fruite. Da tener presente che la proiezione dinamica oltre a dettagliare la sequenza di azioni da svolgere, nel caso in cui tutto funzioni correttamente, dovrebbe riportare anche la casistica completa degli inconvenienti che potrebbero verificarsi durante l’esecuzione del caso d’uso e le relative contromisure che il sistema dovrebbe attuare come risposta. Pertanto durante la progettazione e l’implementazione del sistema, il team di sviluppatori dovrebbe aver ben presente i casi d’uso a cui si riferisce la parte del sistema oggetto di disegno o implementazione. Altra categoria di utilizzatori è costituita dalle persone che dovranno effettuare le verifiche finali (note anche come test di sistema): l’utilizzo di tale vista consente di verificare che il sistema prodotto realizzi pienamente ciò che è stato sancito con il cliente.

20

Capitolo 3. Introduzione agli Use Case

Sebbene in molte organizzazioni i test vengano eseguiti solo dagli sviluppatori e dall’organizzazione presente presso il committente, nelle organizzazioni più strutturate sono previsti appositi team (detti “martellatori” in quanto il loro obiettivo è prendere a martellate il sistema…) che si occupano della delicata fase di test. In generale, non è un buon principio far eseguire i test allo stesso personale che ha prodotto lo specifico artifact: i programmatori non dovrebbero essere i soli a verificare il proprio codice. Il problema è che chi realizza un “prodotto” tende, inconsciamente, a trascurare verifiche di situazioni critiche e meno chiare che, per questo, divengono più soggette a errori. A tal fine, chi effettua i test deve possedere una buona conoscenza dei requisiti utente e quindi della use case view. A dire il vero, solo per la fase di test, si dovrebbero realizzare apposite versioni delle use case view denominate test case. Dall’elenco dei fruitori si vogliono poi escludere i commerciali? Ma certo che no. Anzi, sarebbe auspicabile che taluni commerciali e addetti al marketing dessero un’occhiata a quanto prodotto, giusto quel tanto che basta per capire di cosa dovranno parlare nelle varie riunioni con i clienti ed evitare di basare i propri giudizi unicamente sulla scelta dei colori presenti nelle interfacce utente. Si potrebbe “addirittura” correre il rischio di evitare di vendere congelatori agli esquimesi ed impianti di riscaldamento a chi vive all’equatore. Oppure potrebbero evitarsi frasi del tipo “Perché ci vuole così tanto a realizzare un sistema?”. Ma chi glielo fa fare di stare a competere con questi tecnici paranoici e schizoidi? In fondo, esistono centinaia di lavori migliori di questo… Gestire un banco di frutta è difficile, le mele hanno la brutta abitudine di andare a male… Troppo complicato, è meglio il software, lì si può “buttare tutto in caciara” e un responsabile “esterno” si riesce sempre a trovarlo.

Processo di sviluppo, useless case e IDP Il processo di sviluppo del software è una delle risorse — come tale dovrebbe essere considerato — più importanti delle aziende produttrici di software: tra le varie caratteristiche, fornisce al progettista e ai vari elementi del team direttive precise su cosa fare, quando farlo, come, dove e perché. In sintesi la corretta adozione di un processo, da sola, può fare la differenza tra un progetto di buona riuscita e un insuccesso. Sebbene non esista il processo valido in assoluto o che risulta ottimale per ogni progetto, non sono infrequenti aziende software che si affidano al famoso processo di sviluppo denominato IDP (Irrational Development Process, processo di sviluppo irrazionale), tipicamente composto da tre fasi: 1. analisi dei requisiti;

UML e ingegneria del software: dalla teoria alla pratica

21

2. codifica; 3. test. A complicare ulteriormente la situazione spesso interviene un’analisi dei requisiti sommaria magari effettuata solo parzialmente (gli useless case, casi inutili) da migliorarsi durante il processo di codifica, o addirittura durante e/o dopo i test. L’imperativo dell’IDP, da trent’anni a questa parte, è sempre lo stesso: liquidare le fasi iniziali per dedicarsi alla codifica e solo alla codifica. Parlando seriamente, i processi di sviluppo razionali prevedono invece una successione — eventualmente iterata per uno sviluppo incrementale del sistema — decisamente più articolata (analisi business, requisiti utente, analisi, disegno, codifica, test). Nei processi iterativi e incrementali, le prime iterazioni (tipicamente due o tre) sono focalizzate quasi esclusivamente sull’analisi dei requisiti, attività che, naturalmente, dovrebbe tendere gradualmente a zero nelle successive iterazioni. Realizzare un buon modello dei casi d’uso non è assolutamente semplice, tuttavia esiste una serie di regole e accorgimenti che aiutano a prevenire la realizzazione dei succitati useless cases. Ciò che assolutamente non va dato per scontato è che gli utenti sappiano effettivamente quello di cui hanno bisogno: ciò si verifica raramente. Molto più frequentemente i clienti diventano pienamente consapevoli delle proprie necessità solo durante il processo di sviluppo o addirittura a sistema ultimato: per questo è cosa buona decomporre il processo in una successione ponderata di iterazioni che permettano di controllare i rischi. In altri casi è semplicemente pretestuoso pensare di riuscire a catturare le necessità di tutti gli utenti fin dall’inizio. Si provi a immaginare un nuovo sistema che non preveda simili nel suo genere o sistemi Internet destinati a un potenziale e vastissimo pubblico. Logica conseguenza di ciò è l’impossibilità sia di definire completamente i requisiti prima di poter passare alle fasi successive del processo di sviluppo, sia di poterli “congelare”. Questo concetto, particolarmente caro ai manager, purtroppo non è applicabile: il sistema è destinato a evolvere e i requisiti diventano più chiari con lo sviluppo del sistema. Ciò non significa assolutamente che le fasi di analisi e disegno siano inutili, o che sia sufficiente procedere con un’analisi grossolana dei requisiti utente, sebbene ciò farebbe piacere a molti tecnici. Compito del team tecnico, e in particolare degli analisti, è aiutare l’utente a comprendere più approfonditamente le proprie esigenze e a rifinire i requisiti. Questi obiettivi diventano raggiungibili realizzando opportuni modelli (use case, domain object model, ecc.). Onde evitare problemi di incomprensione e analisi dei requisiti completamente assurde, spesso intervengono nel processo particolari tipologie di utenti: i business analyst, persone esperte della realtà oggetto di studio con qualche background tecnico. Per la particolare tipologia delle loro mansioni, si tratta prevalentemente di consulenti.

22

Capitolo 3. Introduzione agli Use Case

Possono essere paragonati a sistemi di wrapper: da un lato parlano lo stesso linguaggio del cliente — regola basilare nell’analisi dei requisiti — e dall’altro riescono a interagire con sistemi decisamente più tecnici. Risulta importantissimo adattarsi il più possibile al linguaggio dei clienti, i quali devono essere in grado di leggere e comprendere quanto emerso dall’analisi dei requisiti al fine di essere in grado di fornire il relativo preziosissimo feedback e, dal punto di vista dei project manager, di firmare il documento delle specifiche.

Durante questa fase è fortemente consigliato redigere un glossario dei termini utilizzati soprattutto con riferimento all’area del cliente (business). Il glossario offre una serie di vantaggi e in particolare: 1. permette di verificare che si stia effettivamente utilizzando lo stesso gergo del cliente e che non ci siano equivoci relativi al significato dei termini; 2. offre un punto di riferimento per le fasi successive e in particolare rende più difficile attribuire i nomi alle classi con un criterio noto con il neolatinismo ad cacchium.

Il risultato dell’indagine dei business analyst consta, tipicamente, di un voluminoso documento, il quale, nella maggioranza dei casi, risulta stracarico di business rules (regole e procedure dell’ambiente oggetto di studio) ma povero — non potrebbe essere altrimenti — dal punto di vista dell’apporto tecnico, spesso non completamente consistente e non privo di lacune: è difficile riuscire a essere chiari e coerenti in documenti così voluminosi, spesso infarciti di singolarissimi e incomprensibili diagrammi utilizzanti formalismi creati all’uopo dagli autori del documento stesso. Nel mondo dei sogni, i business analyst dovrebbero essere in grado di produrre totalmente o parzialmente la use case view… Si tratta però del mondo dei sogni. Ciò nonostante, il documento fornito dai business analyst risulta preziosissimo per la successiva fase: realizzazione della vera e propria use case view. I “modellatori” — spesso, anche se non dovrebbe essere così, si tratta degli stessi architetti — dovrebbero razionalizzare e trasformare il precedente documento nel modello della vista dei casi d’uso, la quale, nella fase di analisi, deve risultare decisamente formale: si tratta comunque di un modello Object Oriented. Durante questo processo è possibile eliminare immancabili incoerenze presenti nel documento dei requisiti, colmare eventuali lacune, aumentare la componente tecnica e incorporare elementi provenienti dalle prime versioni del disegno dell’architettura. Indipendentemente dai nomi che si vogliono attribuire alle varie fasi, di solito si prevedono almeno due versioni di modelli dei casi d’uso:

23

UML e ingegneria del software: dalla teoria alla pratica

• il primo (tipicamente denominato business) più astratto caratterizzato da un livello tecnico informatico contenuto e da un linguaggio e un formalismo adeguati a quelli del cliente; • il secondo (generalmente denominato di sistema) decisamente più particolareggiato derivante sia dall’approfondimento del modello precedente, sia dall’assorbimento di direttive provenienti dal disegno dell’architettura tecnica. Nella seconda versione dei casi d’uso, generalmente, si cerca di far confluire direttive provenienti dai requisiti di tipo non funzionale, ossia requisiti che non dipendono direttamente dal cliente e dai servizi che si vogliono ottenere dal sistema, bensì da fattori di ordine più tecnico: performance, affidabilità, robustezza, flessibilità, estensibilità, ambiente, e così via. Per esempio, non è infrequente riportare nell’analisi delle singole funzionalità il lasso di tempo entro il quale il sistema dovrebbe essere in grado di erogare i servizi forniti. Quindi la versione del modello dei casi d’uso a livello di sistema deve incorporare vincoli e indicazioni provenienti dal disegno dell’architettura fisica. Il problema che potrebbe sorgere è che a sua volta l’architettura del sistema dovrebbe poter essere stabilita solo dopo che la use case view sia disponibile. Si corre pertanto il rischio di innescare un circolo vizioso. La soluzione che tipicamente viene adottata consiste nel realizzare prime versioni di casi d’uso delle funzionalità ritenute più importanti e quindi fornirle all’architetto del sistema. Quest’ultimo, analizzando la versione embrionale della use case view e cercando di riadattare precisi pattern architetturali, dovrebbe essere in grado di realizzare un disegno iniziale dell’architettura. Così facendo, lavorando per approssimazioni successive, si rimuove il rischio di generare un circolo vizioso. L’interazione tra le due viste continua fino al completamento di entrambe in cui il tutto deve necessariamente risultare coerente (fig. 3.3).

Figura 3.3 — Mutua dipendenza tra modello dei casi d’uso e quello dell’architettura fisica.

Modello use case

Architettura fisica

24

Capitolo 3. Introduzione agli Use Case

Tabella 3.1 — Modulo per la ricapitolazione dei requisiti frutto del brainstorming. Data: Versione: 0.00.000

CODICE: codice

Breve nome mnemonico

Descrizione:

Descrizione del requisito

Impatto sul sistema:

Aree interessate dalla funzionalità e conseguenze

Stima del rischio

basso/medio/elevato

Priorità cliente Durata

bassa/media/alta ordine di grandezza del tempo stimato per la realizzazione Personale figura tecnica richiesta

Costo stimato:

Iter:

Giorni uomo gg gg gg gg

questa valutazione deriva direttamente dal punto precedente Autore

Stato

Data

iniziali autore

proposta / respinta / approvata / da variare

data

Tabella 3.2 — Esempio di compilazione di un modulo di ricapitolazione. Data: 2/II/2000 Versione: 0.00.002

CODICE: IR_ATSND_0010

Autenticazione mittente / certezza integrità messaggio

Descrizione:

Nell’ambito della ricezione dei messaggi relativi alle quotazioni finali dell’attività borsistica, si esige la certezza dell’identità del mittente, la relativa autenticazione, e l’integrità del messaggio.

Impatto sul sistema

Sistema di sicurezza. (Verosimilmente è necessario memorizzare, in maniera centralizzata e crittografata, la “chiave pubblica” dei sistemi esterni abilitati a scambiare messaggi con il sistema e fornire appositi servizi che siano gli unici a poter accedere a tali informazioni). Sistema di interfacciamento con i sistemi esterni.

Stima del rischio

Elevato.

Priorità cliente

Media (Per le prime release del sistema non è indipensabile)

Durata:

27 giorni uomo. Personale Business Analyst Chief architect Architect Programmatore Tester 10 000,00 € Autore

Stato

Data

L.V.T. A.R:

Proposta Approvato

2/II/2000 10/III/2000

Costo stimato: Iter:

Giorni uomo 4 3 10 15 5

UML e ingegneria del software: dalla teoria alla pratica

25

Sebbene l’esperienza insegni che ogni sistema è unico, esiste tuttavia un certo numero di pattern accomunabili e quindi “riciclabili”: tutti i tecnici, a qualsiasi livello del processo di sviluppo, tendono a riutilizzare soluzioni dimostratesi efficaci in precedenti progetti. A questa legge empirica non si sottraggono i casi d’uso e tantomeno i modelli per l’architettura fisica. In altre parole, se i modelli vengono organizzati propriamente, è probabile che diverse parti risultino riutilizzabili in progetti futuri. Il processo di analisi dei requisiti richiede esperienza e competenza specifica sul business del cliente e sulla tecnologia di riferimento. È necessario capire di cosa abbia effettivamente bisogno il cliente: talune volte è necessario convincerlo delle relative necessità, imparare a leggere tra le righe a afferrare ciò che non viene detto — per esempio, razionalizzare un processo potrebbe nuocere a “società amiche” —, suggerirgli eventuali soluzioni — facendole magari passare per sue… — e così via. Una buona idea per cercare di affrontare nel miglior modo possibile l’analisi dei requisiti consiste nel procedere con un brainstorming iniziale allo scopo di redigere un documento con l’elenco di tutti i potenziali requisiti che passano per la mente dei vari personaggi coinvolti nel processo. Ovviamente tale lista andrebbe rielaborata al fine di catalogare tutte le idee emerse, valutarne la fattibilità, il costo orientativo, il rischio e così via (tab. 3.1). Stranamente, i requisiti più odiati dal personale tecnico riportano dei costi esorbitanti. Il risultato di questa sottofase dovrebbe consistere in un documento riportante: codice o un breve nome mnemonico, descrizione, stima del rischio e impatto nel sistema, valutazione del personale coinvolto e del tempo necessario (quindi costo), approvazione e priorità. La priorità dovrebbe essere assegnata dal business analyst in funzione delle direttive fornite dal cliente. Da tenere presente che queste dovrebbero avere un valore essenzialmente di tipo indicativo: non ci si può attendere che le funzionalità vengano completamente realizzate secondo l’ordine stabilito dal cliente. L’“ordine” finale è dato dal piano delle iterazioni nelle quali si intende scindere l’intero processo. È evidente che esso debba tener presente sia le richieste del cliente sia di altri fattori come neutralizzare o perlomeno minimizzare i fattori di rischio, delle dipendenze tra le varie parti componenti, e così via. Quindi la decisione finale delle priorità dovrebbe essere presa con la collaborazione del capo architetto. In genere si cerca di bilanciare le priorità definite dall’utente con quelle dovute da fattori tecnici. Tipicamente i processi di sviluppo più comuni consigliano di affrontare per primi gli use case (o solo alcuni scenari: magari quelli di successo) più complicati o comunque più rischiosi, come dire: “se deve andare tutto a rotoli è meglio accorgersene prima possibile”. Come si può ben notare, un documento riportante schede come quella precedente risulta essere un valido ausilio al project manager per poter effettuare la stima dei tempi e dei costi in maniera meno avventurosa. Nella tab. 3.2 è presentata la descrizione di un requisito utente che potrebbe scaturire nel contesto della realizzazione di un sottosistema bancario dedicato alla gestione degli

26

Capitolo 3. Introduzione agli Use Case

investimenti (trading system). In particolare, sistemi di questo tipo necessitano di ricevere e memorizzare, da appropriate fonti esterne, informazioni relative alle quotazioni di mercato di determinati insiemi di titoli, valute, azioni ecc. Le informazioni delle quotazioni sono necessarie sia in tempo reale, sia come resoconto di fine giornata “borsistica” al fine di essere in grado di compiere tutta una serie di processi di rivalutazione, analisi rischi, previsioni, ecc. Il requisito specificato nella tabella fa riferimento essenzialmente ai messaggi di fine giornata e in particolare si esige di avere l’assoluta certezza sia in merito all’indennità del mittente, sia all’integrità del messaggio. Si provi ad immaginare i risultati che potrebbero conseguire da un attacco hacker o semplicemente da un errore di trasmissione.

Use Cases Diagram Il sistema Sebbene possa sembrare un’attività piuttosto banale, il primo passo nella realizzazione dei modelli dei casi d’uso consiste nello stabilire con precisione il confine del sistema. Molto spesso, analizzando modelli dei casi d’uso, non si riesce a comprendere chiaramente quale ne sia il dominio: un sistema informativo, un sistema informatico, un sottosistema, un modulo e così via. Ovviamente tutto è valido a patto che si stabilisca con precisione l’oggetto di studio e si realizzi un modello coerente. Verosimilmente gli attori di un sistema informativo sono ben diversi da quelli di un componente: lo stesso livello di dettaglio dovrebbe risultare completamente differente. In un tipico processo di sviluppo del software esistono diversi modelli dei casi d’uso e, indipendentemente da quale sia quello oggetto di studio (business, requirements, ecc.), è importante che i confini siano ben definiti. Se per esempio l’oggetto di studio è il modello business dei casi d’uso, verosimilmente l’area di interesse è parte di una struttura economica più vasta, e quindi è evidente l’importanza di definire precisamente la porzione del business che il sistema finale dovrà risolvere. La corretta definizione dei confini del sistema riduce il rischio di commettere un errore spesso presente nei modelli dei casi d’uso: la modellazione delle entità esterne. Si tratta di un malinteso che comporta una serie di problemi: • rende difficile l’individuazione degli attori del sistema; • si corre il rischio di definire comportamenti non rispondenti alla realtà e/o requisiti non concretizzabili in quanto non sotto controllo; • si spreca tempo, ecc.

UML e ingegneria del software: dalla teoria alla pratica

27

Non sempre è immediato stabilire i confini del sistema; non sempre si riesce a determinare esattamente se talune attività sia più opportuno eseguirle manualmente o automatizzarle: quante volte capita di sentire un operatore esclamare che un’attività x prima dell’introduzione del computer veniva svolta in pochi secondi, mentre ora, con l’automazione, necessita di ore? Quante volte è colpa esclusivamente degli orizzonti limitati dell’operatore? La vocina interna direbbe quasi mai… Chissà come mai riflessioni di questo tipo sono particolarmente frequenti in istituti ben precisi. Un’altra considerazione da farsi è quanto grande si vuole realizzare il sistema nelle sue prime versioni. Una buona idea è quella di iniziare con l’identificazione delle funzionalità ritenute indispensabili: il “core” del nuovo sistema, rimandando a sviluppi futuri le restanti. Procedendo con approccio di questo tipo, è possibile concentrarsi su un insieme limitato di funzioni con la speranza di riuscire a realizzare una prima versione del sistema soddisfacente. Raggiunto l’obiettivo, e convinto il cliente della qualità del lavoro prodotto, è sempre possibile aggiungere altre funzioni ritenute di secondaria importanza (altro passaggio del confine, altro fiorino). Approcci iterativi e incrementali incontrano tipicamente il consenso del cliente: sebbene nel complesso si trovi a elargire una somma superiore, lo fa a rate e ci sono maggiori garanzie di successo del sistema. Graficamente il sistema dovrebbe essere evidenziato per mezzo di un rettangolo che ne contenga gli use case e ne lasci all’esterno gli attori: un vero e proprio confine. Per esigenze di “rendering” grafico e lacune di taluni tools, la delimitazione del sistema viene quasi sempre omessa.

Attori Definizione 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 fig. 3.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, ecc. Come si spiega più avanti, esiste per gli attori una rappresentazione grafica standard ma, come illustrato nel Capitolo 2, nulla vieta di ricorrere a opportuni stereotipi al fine di evidenziare le caratteristiche salienti dei vari attori e le relative funzionalità. Un errore tipico è che spesso si rappresentano persone o descrizioni di incarichi invece che attori veri e propri. Pertanto, nell’individuare gli attori del sistema è necessario

28

Capitolo 3. Introduzione agli Use Case

Figura 3.4 — Esempi di attori.

Amministratore

Cliente

Studente universitario

Insegnante

M

@ m Sensore di livello

Legacy system

Missile

Sito e-commerce

prestare attenzione al fatto che in particolari organizzazioni diversi utenti possono impersonare lo stesso attore “logico”, così come uno stesso utente può recitare il ruolo di più attori. Gli attori vengono rappresentati graficamente nello UML attraverso sagome di omini stilizzati (stick man), ribattezzati in gergo tecnico “scarabocchi” — o più teneramente “scarabocchietti” — con il relativo nome riportato alla base. Nella fig. 3.5 vengono riportati alcuni esempi di attori. Come evidenziato precedentemente, non necessariamente un attore deve essere una persona fisica: anche altri sistemi o dispositivi hardware possono assurgere al ruolo di attore. Se per esempio si dovesse modellare un sistema d’arma di artiglieria contraerei, i vari radar, la sezione lancio e, spesso, i missili stessi avrebbero tutta la dignità per assumere il ruolo di attori. Ancora, se si dovesse progettare un sistema di wrapper (layer di comunicazione) tra un sito per il commercio elettronico e il relativo Legacy System del cliente, entrambi i sistemi avrebbero tutta la dignità di essere considerati attori. Un sensore in grado di rilevare e comunicare al sistema il livello dell’acqua presente in una diga è anch’esso a tutti gli effetti un attore per il relativo sistema di monitoraggio e così via.

29

UML e ingegneria del software: dalla teoria alla pratica

Un errore tipico che si commette nel realizzare il modello dei casi d’uso e in particolare nell’individuare gli attori, è utilizzare nomi diversi per identificare lo stesso ruolo e quindi lo stesso attore. Questo problema è particolarmente frequente quando uno stesso modello dei casi sia realizzato da diversi team. Si assiste quindi al proliferare di sinonimi del tipo: “banconista”, “addetto allo sportello”, “sportellista”, ecc. Il problema può essere risolto aggiornando la lista degli attori utilizzati con relativa descrizione, magari inserendoli tutti in uno stesso package condiviso del proprio modello. Pertanto prima di inventare un nuovo attore sarebbe sufficiente effettuare una breve verifica tra quelli individuati.

Per ciò che concerne le relazioni, un attore viene connesso ai casi d’uso con i quali interagisce per mezzo di una relazione di associazione, e può prendere parte a relazioni di generalizzazione in cui una descrizione astratta di un attore più essere condivisa e specializzata da una più specifica. Ogni attore, durante la fase di disegno, deve in qualche modo essere rappresentato all’interno del sistema con cui interagisce: come si vedrà meglio nel capitolo dedicato ai class diagram: in ultima analisi un attore tende a essere rappresentato attraverso una particolare classe, la cui implementazione interna non è rilevante nel contesto dei casi d’uso. Gli attori comunicano con il sistema inviando o ricevendo messaggi: forniscono lo “stimolo” (trigger) agli use case; ciò equivale a dire che ogni caso d’uso deve essere avviato da un’esplicita richiesta di un attore. Una tecnica utilizzata per semplificare il processo di individuazione degli attori consiste nel classificarli in primari e secondari. I primari sono quelli che utilizzano le funzioni Figura 3.5 — Esempi di attori con rappresentazione classica UML.

Amministratore

Cliente

Studente Universitario

Insegnante

Sensore di Livello

Missile

Legacy System

Sito eCommerce

30

Capitolo 3. Introduzione agli Use Case

proprie del sistema (per un use case diagram sono quelli che lo avviano), e pertanto vengono anche definiti attivi; i secondi, tipicamente, fruiscono di informazioni o notifiche generate da use case eseguiti da altri attori. Per esempio un manager riceve la notifica che un proprio subalterno ha compilato il modulo per la richiesta delle ferie e pertanto dovrà eseguire la funzione per validare o rifiutare la richiesta stessa (da notare che per lo use case relativo a tale funzionalità, il manager assurgerebbe al ruolo di attore primario); oppure, in un sistema di protocollazione, i destinatari di un documento in input, ricevono la notifica di visionare lo stesso. Questi sono i cosiddetti attori secondari o passivi. È da tener presente che attori primari per uno use case possono essere sendondari 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. L’insieme completo degli attori presenti nei diagrammi dei casi d’uso evidenzia tutte le entità che necessitano di scambiare informazioni con il sistema stesso.

Relazione di generalizzazione tra attori Nella realizzazione di use case diagram 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à), un attore che ne specializza un altro può sempre essere utilizzato al posto di quello esteso (“genitore” o “padre”). Ciò è abbastanza chiaro: se per esempio in un team di sviluppo è necessario allocare un ulteriore analista programmatore Object Oriented e viene un dottore laureato in scienze tecniche proveniente da qualche famosa azienda di consulting e che vanta la partecipazione in mille ruoli di rilievo, magari anche in riviste di prestigio, il tutto dovrebbe funzionare benissimo: oltre a capacità analitiche e programmative, richieste all’attore base, dovrebbe possedere una serie di esperienze (comportamenti e attributi aggiuntivi, particolarmente appetibili). Chissà perché poi le cose nella realtà non sono mai così lineari… Si consideri il caso di un sistema con due attori: operatore e amministratore. L’amministratore risulta caratterizzato dallo svolgere tutte le funzioni dell’operatore e da dall’esse-

UML e ingegneria del software: dalla teoria alla pratica

31

Figura 3.6 — Esempio di relazione di generalizzazione tra attori. Gestione ordini

Venditore

Validazione ordini rischiosi

Supervisore

re gravato da un insieme di funzioni aggiuntive ritenute sensibili per il sistema. In questo caso, è immediato asserire che l’attore Amministratore eredita dall’attore Operatore. Si consideri l’esempio di fig. 3.6. Si tratta di un sistema automatizzato di vendita utilizzato, dal lato back office, essenzialmente da due attori: Venditore e Supervisore. Entrambi possono gestire un ordine, ma l’attore Supervisore estende il Venditore, in quanto ha la responsabilità di validare ordini che, in base a determinati fattori, vengono considerati rischiosi. La notazione grafica utilizzata in UML per rappresentare una relazione di generalizzazione è quella standard e prevede un segmento che unisce l’attore estendente a quello esteso; in prossimità di quest’ultimo viene riportato un triangolo vuoto a mo’ di freccia.

Molto spesso analizzando modelli use case è possibile notare una rete densissima di collegamenti tra attori e casi d’uso. Per esempio può capitare di osservare che una serie di use case risultino tutti connessi con un insieme ben definito di attori (fig. 3.7) Fitte reti di collegamento attori/casi d’uso possono costituire sintomi del fatto che non si sia proceduto ad una appropriata organizzazione gerarchica degli attori. Probabilmente è possibile ristrutturare i vari attori secondo un’opportuna relazione gerarchica. Ciò è ottenibile definendo un nuovo attore “padre” connesso con tutti i casi d’uso comuni agli altri e quindi specializzarlo con i vari attori, ognuno dei quali, eventualmente connesso ad altri casi d’uso di più specifica pertinenza (fig. 3.8).

Il diagramma mostrato rievoca nella mente dell’autore un “simpatico” aneddoto. Realizzata la primissima versione del modello dei casi d’uso di un sistema piuttosto complesso, bisognava definire chiaramente ruoli e responsabilità dei relativi attori umani. Trat-

32

Capitolo 3. Introduzione agli Use Case

Figura 3.7 — Esempio di mancata astrazione nell’identificazione degli attori.

Inserimento ordine

Venditore

Verifica stato ordine

Annullamento ordine

Supervisore

Generazione report

Figura 3.8 — Diagramma ristrutturato evidenziando la relazione di generalizzazione tra attori.

Inserimento ordine

Venditore

Verifica stato ordine

Annullamento ordine

Supervisore

Generazione report

33

UML e ingegneria del software: dalla teoria alla pratica

Figura 3.9 — Frammento di un’organizzazione gerarchica degli attori umani di un sistema.

Utente

. Utente Esterno

.

.

.

Utente Interno

. Addetto Gestione Clienti

.

.

.

Addetto Sicurezza

. Direttore Gestione Clienti

.

.

.

Direttore Sicurezza

Direttore Dipartimento

tandosi di un sistema completamente nuovo, l’attività risultava abbastanza importante: si trattava di definire il profilo delle figure professionali da reclutare oltre ai manager già assegnati per l’entrata in esercizio del sistema. La prima versione dell’organizzazione dei ruoli risultava simile a quella presentata in fig. 3.9. La logica alla base della struttura di figura era di astrarre ruoli con comportamento comune ossia raggruppare attori che interagivano con un insieme condiviso di casi d’uso. Per esempio, era importante disporre di un attore utente generico (Utente) per descrivere le funzioni che tutti gli attori utilizzavano (login, cambiamento di password, richiesta nuova password, ecc.). All’interno di ogni dipartimento era poi importante mostrare le differenze tra gli addetti e i manager in quanto questi, oltre a poter espletare tutte le

34

Capitolo 3. Introduzione agli Use Case

mansioni dei propri subalterni (ereditano dagli addetti del reparto), sono gravati da responsabilità supplementari tipiche del ruolo manageriale (ereditano dall’attore Direttore Dipartimento). Per esempio avviano il servizio di richiesta del profilo utente per i propri dipendenti, hanno l’obbligo di validarne i time sheet, ecc. Una volta sottoposto il diagramma all’attenzione dei “clienti” (manager), successe la rivoluzione… “Orrore!!! Come!? I manager nell’organigramma compaiono a un livello inferiore dei propri subalterni!?”. A questo punto il pasticcio era fatto. Tutte le spiegazioni tecniche non sortivano alcun effetto… Cosa fare? Prima idea: ribaltare il diagramma… Per questa soluzione era troppo tardi e comunque rimaneva il concetto poco gradito della doppia piramide. La soluzione fu la realizzazione di un diagramma illeggibile… Si decise di portare tutti i manager su una riga superiore come mostrato in fig. 3.10.

Figura 3.10 — Modifiche a un frammento dell’organizzazione gerarchica degli attori presentata nel diagramma precedente.

Direttore Dipartimento

. Direttore Sicurezza

Direttore Gestione Clienti

Utente

. Utente Esterno

.

.

.

Utente Interno

. Addetto Gestione Clienti

Addetto Sicurezza

.

.

.

.

.

.

35

UML e ingegneria del software: dalla teoria alla pratica

Figura 3.11 — Esempio di relazioni di associazione.

Reperimento dati cliente

Reperimento articoli «include»

«include»

Effettua ordine

Cliente

Controlla ordine

Venditore

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 (funzioni del sistema) 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. Nella fig. 3.11 viene riportato un semplice diagramma dei casi d’uso rappresentante una porzione di un sistema automatizzato di vendita online. Per ora si attribuisca alla relazione include (inclusione) un significato del tutto intuitivo: l’esecuzione della funzione Effettua ordine necessita dell’esecuzione delle funzioni Reperimento dati cliente e Reperimento articoli. Dunque, un cliente, previa opportuna identificazione, può compilare degli ordini selezionando gli articoli di interesse. L’attore Venditore ha il compito di visionare gli ordini e l’esito del controllo viene comunicato al cliente che ha emesso l’ordine oggetto di verifica.

Come identificare gli attori del sistema Identificare gli attori del sistema è un’attività molto importante: si identificano le entità interessate a usare e interagire con il sistema. Tipicamente si tratta di un’attività non smisuratamente complessa che l’esperienza tende a rendere ancora meno complicata. Tuttavia esistono casi particolari in cui anche il processo di individuazione degli attori può generare più di qualche incertezza.

36

Capitolo 3. Introduzione agli Use Case

In ultima analisi un attore è, almeno, una classe per il sistema e quindi si intuisce l’importanza sia dell’identificare i singoli attori, sia nell’assegnargli un nome corretto. Nel caso in cui l’attribuzione del nome possa causare qualche problema, ciò potrebbe essere un buon campanello di allarme che qualcosa non funzioni correttamente: si sono raggruppati più attori in uno solo (magari perché nella realtà di interesse diversi ruoli vengono fisicamente recitati da una sola persona) o, problema diametralmente opposto, lo stesso ruolo è diviso in più attori (caratteristica peculiare di particolari istituti). L’errore tipico che si commette nell’individuare gli utenti del sistema è che si prendono in considerazione unicamente gli operatori umani: i famosi signori con i manicotti seduti di fronte ai monitor. Spesso ci si dimentica che un attore è qualsiasi entità che ha interesse a interagire con il sistema: anche dispositivi fisici. Un altro errore ricorrente è quello di considerare il sistema da subito e unicamente con una prospettiva molto tecnica e non con una più vicina al business del cliente. In altre parole, spesso ci si dimentica dei veri utenti del sistema i quali, molto frequentemente, sono i clienti del committente stesso. Premesso ciò, rispondere alle domande riportate di seguito può aiutare ad identificare/verificare gli attori del sistema: • Chi sono gli utilizzatori delle principali funzioni del sistema? Ossia, chi sono gli attori principali? • Chi usufruirà giornalmente del supporto del sistema? • Chi si occuperà di mantenere, amministrare il sistema? • Esistono sistemi esterni (sia software, sia hardware) che necessitano di interagire con il sistema? Quali di essi iniziano il colloquio con il sistema e quali invece ricevono i risultati dell’esecuzione di opportuni processi? • Esiste qualche ulteriore entità che ha interesse a essere informata sui risultati prodotti da opportune elaborazioni del sistema?

Modello per la documentazione degli attori Spesso è conveniente, già durante i colloqui iniziali con gli utenti del sistema, cominciare a redigere una lista riportante gli aspiranti attori. Tale elenco è destinato a essere riesaminato e corretto man mano che si procede con le varie interviste e quindi, più in generale, con la comprensione dell’area di business che il sistema, in qualche percentuale, intende risolvere. Una volta terminata la serie delle interviste è necessario eseguire una fase di verifica della lista degli aspiranti attori al fine di individuarne eventuali “ridondanti”, “ambigui”, non ben definiti, mancanti e così via.

37

UML e ingegneria del software: dalla teoria alla pratica

Effettuata anche questa verifica la lista costituisce un ottimo prodotto di input per la costruzione dei vari diagrammi dei casi d’uso. Ciò, tra l’altro, dovrebbe favorire la realizzazione di un glossario, preciso circa gli attori del sistema. Al fine di supportare il processo di costruzione del modello dei casi d’uso, si ritiene utile, per ogni attore, compilare una scheda come quella riportata in tab. 3.3. Molto importante è definire le responsabilità di un attore anche se tipicamente si tende a specificarne le operazioni. Queste permettono di chiarire le regole del business vigenti nel sistema oggetto di studio. Talune volte, invece di specificare i casi d’uso in cui un attore è coinvolto, si preferisce descriverne le aspettative. Ciò risulta particolarmente utile quando ci si trova in una delle primissime fasi del ciclo di vita, quando i modelli dei casi d’uso non sono ancora definiti. Per ciò che concerne la frequenza dell’utilizzo dei casi d’uso, si tratta di un’informazione utilissima in molti contesti, quali ottimizzazione delle operazioni, pianificazione dei test, ecc.

I casi d’uso Definizione Uno use case (caso d’uso) 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, un caso d’uso è un tipo di Classificatore (UseCase eredita da Classifier) 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) che l’entità esegue interagendo con il relativo attore, necessarie per erogare un

Tabella 3.3 — Scheda per la definizione degli attori del sistema. Nome dell’attore:

Versione:

Data:

Responsabilità 1. 2. USE CASE CON CUI INTERAGISCE Nome use case

Primario

Frequenza

38

Capitolo 3. Introduzione agli Use Case

servizio. Tale sequenza può prevedere delle varianti come sequenze alternative, comportamento eccezionale, gestione degli errori, e così via. Gli use case specificano servizi che un’entità fornisce ai relativi utilizzatori: ognuno di questi servizi deve essere completo e avviato da un attore. Le implicazioni della proprietà di completezza sono essenzialmente due: 1. l’entità, dopo aver erogato il relativo servizio, deve transitare in uno stato tale che il servizio possa essere fruito nuovamente; 2. più casi d’uso che specificano la stessa entità non possono comunicare tra loro, perché ciascuno di essi deve, individualmente, descrivere completamente l’utilizzo dell’entità stessa. I casi d’uso possono essere raggruppati in gruppi (package) per questioni di comodità e facilità di reperimento. Sebbene gli use case siano impiegati quasi esclusivamente per specificare i requisiti esterni di un’entità, essi possono anche essere utilizzati per documentare le funzionalità offerte da un’altra entità (esistente) del sistema, anche se per tali fini sarebbe preferibile utilizzare altri diagrammi messi a disposizione dallo UML (diagrammi di sequenza, collaborazione, attività e di stato). Indirettamente ciascun caso d’uso stabilisce i requisiti che ogni entità esige dai relativi utilizzatori, come per esempio le modalità con cui essi devono interagire con l’entità stessa per fruire dei relativi servizi offerti. Nel caso che l’entità sia il sistema o un sottosistema, i relativi utenti sono gli attori; se invece si fa riferimento a sottosistemi e classi interne al sistema, tipicamente, gli “utenti” non vengono definiti. La notazione grafica dello UML prevede di rappresentare gli use case attraverso ellissi con all’interno specificato il nome. Essi possono essere connessi ad altri use case o agli attori. In tal caso la “connessione” prende il nome di “associazione” o “comunicazione di associazione”. Nell’individuazione degli use case, l’esperienza insegna che molto importante risulta la selezione del relativo nome. Si tratta dell’elemento più discusso dagli utenti/clienti. I nomi dovrebbero essere unici, sintetici e al tempo stesso capaci di definire completamente il relativo concetto. Ciò risulta particolarmente utile per i modelli a livello di dominio in cui il nome da solo dovrebbe essere in grado di giustificare il caso d’uso dal punto di vista del business. Oltre a facilitarne la lettura, nomi di questo tipo assicurano che il relativo use case rappresenti un reale requisito e non uno supposto o un’incomprensione. Probabilmente la sintassi ideale prevede una breve frase formata da un sostantivo indicante l’azione e da un sostantivo indicante l’oggetto della stessa (Inserimento ordine, Invio conferma, Annullamento ordine, Inserimento utente, Variazione profilo, Selezione domicilio, ecc.).

39

UML e ingegneria del software: dalla teoria alla pratica

Poiché un modello dei casi d’uso, in ultima analisi, rappresenta l’insieme dei servizi forniti agli attori del sistema, nella selezione dei nomi dei casi d’uso è opportuno mettersi nell’ottica degli attori.

Relazione di associazione Si faccia riferimento al paragrafo denominato “Relazione di associazione tra attori e casi d’uso”.

Relazione di generalizzazione Nel disegnare i diagrammi dei casi d’uso si verifica spesso che diversi use case presentino delle somiglianze e condividano del comportamento (analogamente a quanto illustrato per gli attori). Come in precedenza, gli insegnamenti dell’Object Oriented 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 un 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.

Figura 3.12 — Esempio di relazione di generalizzazione tra use case: Upload ordine.

Verifica ordine

Validazione utente «include»

«include»

Upload ordine Cliente

Legacy System

Upload ordine standard

Invio ordine prioritario «include»

Upload ordine prioritario

40

Capitolo 3. Introduzione agli Use Case

Un caso d’uso genitore può essere specializzato in uno o più figli che ne rappresentano forme più specifiche. Lo Use Case figlio eredita gli attributi, le operazioni, il comportamento del genitore, e pertanto può sovrascrivere (override) o aggiungere comportamento (attributi, operazioni, e così via). Come da standard UML, anche in questo contesto, la notazione grafica utilizzata per 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” può avvenire unicamente in alternativa. Si consideri l’esempio di generalizzazione illustrato nella fig. 3.12. Si tratta di uno use case che descrive la funzione che supporta l’invio (upload) automatico ordini, ad una determinata organizzazione commerciale, tramite browser Internet. Si immagini un sito per il commercio elettronico il quale, tra le varie funzionalità, permetta di ricevere ordini preparati dagli utenti secondo opportuni formati (ASCII, XML, ecc. ecc.). Questa funzionalità di upload potrebbe funzionare in modo del tutto equivalente al modo con cui i server di posta elettronica, fruibili via comune browser, consentono di allegare file (attachment) in una e-mail: si seleziona localmente il percorso del file da allegare e quindi si preme il tasto di upload. Si supponga che il sistema preveda due tipologie di ordini: normali e prioritari. La peculiarità dei secondi è di richiedere una gestione immediata: appena caricati vengono spediti al sistema di back office (Legacy System) che provvede a gestirli immediatamente, mentre i primi prevedono un iter rallentato dovuto sia a vincoli architetturali presenti nel sottosistema Internet sia in quello di back office operante presso l’organizzazione del cliente. Il diagramma in figura mostra il caso d’uso di Upload ordine. In particolare la presenza delle relazioni di generalizzazione implicano che è possibile eseguire uno solo di tali casi: l’esecuzione di un “fratello” esclude quella dei rimanenti. Quindi o si esegue l’upload di un ordine standard oppure si esegue quello di un ordine prioritario. Il diagramma in figura indica unicamente la proiezione statica della funzionalità: specifica che il caricamento di un ordine richiede la validazione dell’utente e la verifica dell’ordine stesso; evidenzia che l’utente può richiedere due tipologie diverse di servizio: standard e prioritario. Nel secondo caso è previsto l’immediato invio al sistema di legacy. Il diagramma evidentemente non specifica nulla circa la dinamica dell’operazione, non specifica altresì precondizioni e postcondizioni, non fornisce alcuna direttiva su come trattare i casi anomali: cosa fare se il formato dell’ordine risulta errato o se il prezzo di un prodotto presente nell’ordine non corrisponde a quello impostato nel sistema? E si potrebbe continuare. Per sopperire a tale lacuna è possibile specificare il flusso delle attività illustrato in fig. 3.13. Dall’analisi del diagramma è possibile notare che il caso d’uso base A, può prevede-

41

UML e ingegneria del software: dalla teoria alla pratica

Figura 3.13 — Flusso degli eventi di due casi d’uso uniti dalla relazione di generalizzazione.

Flusso degli eventi Use case A ..................... ....................... passo astratto ....................... .............. ........ ............. passo generico ....................... .............. ........ ............. fine use case

Use case A

Use case B

Flusso degli eventi Use case B definizione passo astratto ..................... ....................... fine segmento ridefinizione passo ....................... .............. ........ fine segmento definizione passo aggiuntivo ................... ........................ fine use case

re dei punti astratti mischiati a generici passi di esecuzione. Un 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. Per esempio, con riferimento allo use case di fig. 3.12, Upload ordine, un possibile flusso di eventi potrebbe essere quello riportato di seguito. Use case base Upload ordine: 1. include (Riconoscimento utente) 2. if riconoscimento fallito then 2.1.

if riconoscimento fallito per tre volte consecutive then

2.1.1. mostra messaggio. 2.1.2. termina lo use case con errore.

42

Capitolo 3. Introduzione agli Use Case 2.2.

else

2.2.1.mostra messaggio. 2.2.2.torna al punto 1. 3. impostazione file da parte dell’utente. 4. conferma caricamento file da parte dell’utente. 5. caricamento file nel sistema. 6. if errore nel caricamento then 6.1.

mostra opportuno messaggio.

6.2.

torna al punto 3.

7. memorizzazione temporanea del file (ordini da verificare) 8. include (Verifica ordine) 9. if verifica fallita then 9.1.

mostra opportuno messaggio.

9.2.

eliminazione dell’ordine.

9.2.

torna al punto 3.

10. memorizzazione permanente del file (ordine verificato) Use case Upload ordine standard: 10. memorizzazione permanente del file nella directory ordini standard. Use case Upload ordine prioritario: 10. memorizzazione permanente del file nella directory ordini prioritari. 11. include (Invio ordine prioritario).

Si può immaginare il sistema strutturato in modo tale da prevedere un servizio cadenzato che si occupi, a intervalli prestabiliti di tempo (una/due volta/e al giorno) di prelevare gli ordini standard per inviarli al legacy system (sistema automatizzato di back office presente presso l’organizzazione del cliente: la struttura che in ultima analisi fornisce i prodotti che vengono venduti tramite il sito di e-commerce).

Figura 3.14 — Invio ordini standard.

Invio ordini standard Timer

Venditore

UML e ingegneria del software: dalla teoria alla pratica

43

Lo use case di una funzionalità così concepita offre un interessante motivo di riflessione: l’avvio dello use case avverrebbe automaticamente (a istanti temporali predefiniti) senza lo stimolo di un vero attore esterno. A questo punto nasce un dilemma: il tempo può essere considerato un attore o no? Probabilmente si tratta più di uno sceneggiatore o di un regista. Applicando rigorosamente le direttive dello UML, il tempo non dovrebbe poter essere considerato un attore; come punto a favore si può invece asserire che si tratta di un’entità indubbiamente esterna al sistema e altrettanto sicuramente non controllabile dallo stesso. In altre parole l’attore Tempo non solo è un’assunzione decisamente accettabile ma contribuisce anche a rendere i diagrammi più leggibili… In ultima analisi gli stessi use case non dovrebbero venir sempre avviati da un attore?

Nella fig. 3.14 è stato riportato l’esempio del tempo come attore evidenziandolo per mezzo di apposito stereotipo al fine di far risaltare ulteriormente il concetto dell’avvio temporizzato. Per ciò che concerne invece gli ordini prioritari, la specifica del comportamento dinamico ne prevede l’immediato invio al legacy system. Come si vedrà successivamente, probabilmente il formalismo utilizzato per specificare il comportamento dinamico dei casi d’uso (così come proposto dai Tres Amigos) nella pratica non risulta essere il più efficace: forse sarebbe più opportuno utilizzare specifici template scegliendoli tra quelli proposti. Come si può notare gli use case figli ereditano la sequenza comportamentale del genitore e vi aggiungono comportamento specifico. Potenzialmente sia il caso d’uso genitore, a meno che non preveda opportune sezioni astratte, sia quello figlio sono istanziabili. Differenti specializzazioni dello stesso genitore sono completamente indipendenti contrariamente a quanto avviene con le relazioni di estensione (illustrato nell’apposito paragrafo) in cui i diversi use case estendenti modificano il comportamento dello stesso use case e possono essere invocati in sequenza. 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 relativa 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.

44

Capitolo 3. Introduzione agli Use Case

Figura 3.15 — Flusso degli esempi della relazione di inclusione.

Use case A

«include»

Flusso degli eventi Use case A ..................... ....................... ....................... include( Use case B) .............. ........ ............. ....................... .............. ........ ............. fine use case

Use case B

Flusso degli eventi Use case B ..................... ....................... ....................... .............. ........ ................... ........................ ........................ ........................ fine use case

Relazione di inclusione L’inclusione è una relazione tra un 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 fig. 3.15. 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. Volendo essere più precisi bisognerebbe paragonare la relazione di inclusione al meccanismo delle macro presente in molti linguaggi di programmazione: si definisce una macro e

45

UML e ingegneria del software: dalla teoria alla pratica

la si copia (si espande il “codice”) in tutti i punti in cui ci si riferisce alla macro stessa. Lo use case incluso può avere accesso agli attributi ed alle operazioni di quello base. La relazione di inclusione ha avuto una genesi piuttosto complicata; inizialmente si trattava di uno stereotipo della relazione di generalizzazione e, pertanto, veniva rappresentata per mezzo dello stesso simbolo grafico con affianco l’etichetta (nome dello stereotipo). Nella versione 1.3 dello UML, per fortuna, le cose sono un po’ cambiate e l’inclusione è stata più opportunamente convertita in una relazione a sé stante. La notazione grafica prevede una freccia tratteggiata con l’etichetta . Probabilmente era poco opportuno indicare una relazione di uso o di inclusione attraverso uno stereotipo della relazione di generalizzazione in quanto quest’ultima implica la proprietà di sostituibilità; nel contesto della relazione di inclusione indicava che lo use case “utilizzatore” poteva sempre essere sostituito al posto di quello utilizzato, il che evidentemente è lungi da rispondere a verità. Una relazione di inclusione indica che lo use case base (l’utilizzatore) incorpora esplicitamente il comportamento di un altro use case (l’utilizzato), il quale, tipicamente, non vive di vita propria, ma deve necessariamente essere istanziato come parte di un altro caso d’uso. Lo use case “utilizzato” ha visione completa del suo utilizzatore con il quale può scambiare parametri e comunicare risultati. 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. Chiaramente, poiché il caso d’uso incluso è a tutti gli effetti uno use case, può essere associato ad altri per mezzo di proprie relazioni di inclusione, estensione e così via. Si consideri l’esempio riportato in fig. 3.16. Si tratta di uno use case per la compilazione ordini. In particolare vi è l’attore Cliente che inserisce gli ordini nel sistema. Come si Figura 3.16 — Esempio di relazione di inclusione: use case per la compilazione di un ordine. Reperimento dati cliente

Reperimento articoli «include»

Compila ordine

Cliente

«include»

46

Capitolo 3. Introduzione agli Use Case

può evidenziare, la funzionalità Compila ordine prevede il Reperimento dei dati del cliente e la ricerca degli articoli (Reperimento articolo) che andranno a costituire l’ordine stesso. Nel redigere i diagrammi dei casi d’uso è necessario porre attenzione a non essere eccessivamente prescrittivi: ciò non sarebbe di alcun aiuto e, paradossalmente, finirebbe con il rendere il diagramma inutilmente complicato e con l’obbligare eccessivamente il team di sviluppo durante le fasi successive a rispettarne i dettagli; si ricordi che la use case view è anche un documento utilizzato nei test di accettazione. Per esempio nello use case di fig. 3.17 si sarebbero potute evidenziare ulteriori funzionalità come Verifica disponibilità articolo o Reperimento prezzo cliente generalizzazione della funzionalità di ricerca articolo Ricerca per codice, Ricerca per descrizione, Ricerca per categoria, ecc. Ciò sarebbe stato del tutto ingiustificato e avrebbe complicato un use case intrinsecamente semplice. L’inconveniente può non sembrare così serio nel diagramma di figura, ma si tenga presente che, in una situazione reale, esso sarebbe parte di un modello decisamente più complesso; è consigliabile, quindi, non perdere tempo nel dettagliare eccessivamente gli use case diagram: si corre unicamente il rischio di renderli inutilmente complessi: come si suol dire “ogni cosa a suo tempo”. Assumendo che l’utente sia stato precedentemente autenticato dal sistema e autorizzato a eseguire la funzione, il flusso di azione del diagramma (fig. 3.16) potrebbe assumere una forma del tipo: Use case base Compila Ordine: 1. include (Reperimento dati cliente) 2. if reperimento fallito then 2.1.

mostra messaggio

2.2.

termina use case.

3. include (Ricerca articolo) 4. if reperimento fallito then 4.1.

mostra messaggio

4.2.

torna al punto 3.

5. L’utente selezione operazione da eseguire sull’ordine 6. if ordine annullato then 6.1.

mostra messaggio

6.2.

termina use case.

7. if utente aggiorna quantità articolo then 7.1. il sistema ricalcala i totali. 7.2. il sistema aggiorna l’ordine. 7.3. torna al punto 5. 8. if utente elimina una riga then 8.1. il sistema ricalcala i totali.

47

UML e ingegneria del software: dalla teoria alla pratica

Figura 3.17 — Come complicare un “affare” semplice.

Reperimento cliente per denominazione

Reperimento cliente per codice

Verifica disponibilita' articolo

Reperimento dati cliente

«include»

«include»

Compila ordine

«include»

Cliente

«include»

Reperimento articolo

Reperimento articolo per codice

Reperimento prezzo cliente

Reperimento articolo per categoria

Reperimento articolo per descrizione

8.2. il sistema aggiorna l’ordine. 8.3. torna al punto 5. 9. if utente richiede aggiunta ulteriore articolo then 9.1. torna al punto 3. 10. if utente conferma ordine then 10.1. Memorizza ordine 11. Fine use case Use case Reperimento dati cliente: 1. acquisizione codice cliente. 2. reperimento dati cliente per codice. 3. if reperimento fallito then 3.1.

acquisizione denominazione cliente.

3.2.

reperimento dati cliente per denominazione.

48

Capitolo 3. Introduzione agli Use Case 3.3.

if reperimento fallito then

3.3.1.termina con errore. 4. restituisci dati reperiti. Use case Ricerca ordine: 1. acquisizione codice articolo. 2. reperimento articolo per codice 3. if reperimento fallito then 3.1.

acquisizione descrizione articolo

3.2.

reperimento dati articolo per descrizione

4. if reperimento fallito then 4.1.

termina con insuccesso

5. reperimento prezzo articolo da applicare al cliente 6. if reperimento fallito then 6.1.

termina con insuccesso

7. riporta dati reperiti

Volendo si sarebbe potuto rendere i flussi di azione decisamene più eleganti e complessi, ma proprio non è il caso; anzi è consigliabile impiegare meglio il tempo nel contesto dell’economia dell’intero progetto.

Relazione di estensione 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 i lettori non molto esperti della vista dei casi d’uso potrebbero essere scossi da più di qualche turbamento: 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’Object Oriented, 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 Object Oriented: se si è fortunati, tali individui appartengono unicamente alla categoria dei clienti, e la relazione di estensione così definita finisce probabilmente per risultar loro più naturale e comprensibile. Nel diagramma riportato in fig. 3.18 viene illustrata la differenza tra una relazione di inclusione e una di estensione. Si considerino due use case generici A e B, in cui il primo necessita in qualche modo del secondo. Nel caso in cui:

49

UML e ingegneria del software: dalla teoria alla pratica

Figura 3.18 — Differenza di notazione tra la relazione di inclusione e quella di estensione.

Verifica formato on-line

«include»

Visualizza eccezione

Verifica formato on-line

«extend»

Visualizza eccezione

• lo use case A include quello B, la freccia va dal primo al secondo; • lo use case B estende quello A, la freccia va riportata esattamente al contrario del caso precedente. Una relazione di estensione contiene una lista dei nomi dei punti in cui lo use case base può e deve essere esteso (consultare figura seguente). Chiaramente ciascuno di essi deve essere esteso da opportuni segmenti presenti nello 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. Come evidenziato nella fig. 3.19, lo use case base A prevede due punti di estensione, mentre il caso d’uso estendente ne fornisce l’apposita definizione. Il punto interrogativo 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 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 viene 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.

50

Capitolo 3. Introduzione agli Use Case

Figura 3.19 — Flusso degli eventi della relazione di estensione.

Use case A

«extend»

Flusso degli eventi Use case A ..................... ....................... dich. segmento di estensione i ....................... .............. ........ dich. segmento di estensione ii ....................... .............. ........ fine use case

? Flusso degli eventi Use case B def. segmento di estensione i ..................... ....................... fine segmento

Use case B

def. segmento di estensione ii ....................... .............. ........ fine segmento fine use case

Figura 3.20 — Prenotazione appelli di esami universitari: esempio di relazioni di estensione.

validazione utente «include» Prenota appello extensions points transazione abilitata verifica idoneita'

Studente Universitario

«extend»

(transazione abilitata) [studente abilitato]

Seleziona esame

«extend»

(verifica idoneita') [appello selezionato]

Verifica idoneita' studente

UML e ingegneria del software: dalla teoria alla pratica

51

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.

Spesso, analizzando i vari flussi degli eventi che descrivono il comportamento dinamico dei singoli casi d’uso, ci si imbatte in vere e proprie procedure scritte in linguaggio di programmazione (la patologia della “programmite” è sempre in agguato) infarcite di comandi for, while, if, ecc. Questa soluzione tipicamente non riscuote i consensi dei clienti i quali, per definizione, non sono tecnici informatici e tendono a sentirsi a disagio quando il formalismo tecnico prende il sopravvento. L’esperienza insegna che è preferibile cercare di utilizzare un linguaggio quanto più vicino possibile a quello dei clienti e rendere le varie descrizioni dei casi d’uso lineari. Per esempio, qualora uno use case sia molto esteso o troppo complicato, è possibile semplificarne la struttura inserendo alcuni flussi alternativi in appositi caso d’uso, connessi a quello oggetto di studio per mezzo di una relazione di estensione. Ovviamente, utilizzando questo espediente bisogna sempre fare attenzione a non eccedere nella granularità.

Il diagramma riportato in fig. 3.20 corrisponde a una funzione che permette a studenti universitari di prenotarsi per gli appelli delle sedute d’esame previste, magari comodamente seduti nella poltrona di casa, oppure interagendo con opportuni chioschi presenti presso le varie facoltà dell’università (!?). Come si può notare, il caso d’uso prenota appello prevede due punti di estensione denominati rispettivamente transazione abilitata e verifica idoneità. Il comportamento del primo punto viene definito dallo use case seleziona esame. Nella relativa relazione di estensione viene esplicitamente dichiarato il punto, o meglio, il segmento che il caso d’uso estendente specifica nello use case base: transazione abilitata. Sempre nella relazione di estensione è definita la condizione che deve essere soddisfatta affinché il flusso del caso d’uso possa essere eseguito, ossia: lo studente deve essere stato preventivamente riconosciuto e quindi abilitato dal sistema. Un discorso completamente analogo vale per il caso d’uso verifica idoneità studente il cui obiettivo è quello di verificare che l’utente sia in possesso di tutti i requisiti (di tipo sia amministrativo, sia di curriculum) per potersi prenotare per la sessione di esami prescelta. In questo caso la condizione da soddisfare è che lo studente

52

Capitolo 3. Introduzione agli Use Case

abbia precedentemente selezionato l’appello di interesse e il punto in cui viene esteso il caso d’uso base è verifica idoneità.

Ancora una volta, probabilmente non è il caso di sprecare energie andando a definire nei diagrammi i punti in cui ogni caso d’uso estendente estende quello base: è possibile fare ciò nel flusso di azioni. Probabilmente, non solo si perde molto tempo, ma si finisce anche per complicare inutilmente i diagrammi.

Il flusso di azioni dello use case di fig. 3.20 potrebbe assumere una forma del tipo: Use case base Prenota appello: 1. include (validazione studente) 2. if studente non riconosciuto then 2.1.

if fallita autenticazione studente per tre volte consecutive then

2.1.1.lock the user. 2.1.2.termina con insuccesso. 2.2.

mostra messaggio utente.

2.2.

torna al punto 1.

3. reperimento dati studente (facoltà, anno di corso, ecc.) 4. if dati studente non reperiti then 4.1.

mostra messaggio

4.2.

termina sessione

5. 8. acquisisci appello selezionato dallo studente 9. 10. fine Extension use case Seleziona esame: definzione segmento 1. acquisisci anno di corso relativo all’esame di interesse per lo studente 4. if nessun dato impostato then 5. termina sessione con insuccesso 2. visualizza elenco degli esami relativi all’anno impostato 3. acquisisci esame selezionato 4. if nessun esame selezionato then 5. termina sessione con insuccesso 6. restituisci esame selezionato

UML e ingegneria del software: dalla teoria alla pratica

53

7. mostra appelli previsti per l’esame selezionato 8. if nessun appello previsto then 8.1.

mostra messaggio

8.2.

torna al punto 1.

Extension use case Verifica idoneità studente: definzione segmento 1. verifica idoneità amministrativa. 2. if anomalie amministrative riscontrate then 2.1.

visualizza messaggio

2.2.

termina sessione

3. verifica propedeuticità esame selezionato 4. if riscontro propedeuticità fallito then 4.1.

visualizza messaggio

4.2.

termina sessione

5. restituisci esito verifica positivo

Lo Use Case estendente può avere accesso e modificare gli attributi definiti nello use case base mentre quest’ultimo non ha visione dei casi d’uso estendenti e quindi non può averne accesso ai relativi attributi/operazioni. Con riferimento al caso presentato, lo use case seleziona esame, per poter visualizzare l’elenco degli esami di interesse dello studente per l’anno specificato, deve essere in grado di accedere ad informazioni presenti nello use case base come la facoltà a cui lo studente risulta iscritto. 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. La differenza tra la relazione di generalizzazione e quella di estensione dovrebbe essere fin troppo chiara ma si ribadisce — repetita juvant — che nel primo caso il comportamento specializzato è presente nelle istanze che generalizzano lo use case mentre nel secondo caso è riportato direttamente nello use case esteso. Come già menzionato, la relazione di estensione viene rappresentata graficamente per mezzo di una freccia tratteggiata corredata dall’etichetta . È sempre consigliabile organizzare i diagrammi dei casi d’uso estraendo il comportamento comune (utilizzo delle relazioni di inclusione) e distinguendo le varianti (relazione di estensione). Inoltre è opportuno ricordare che uno degli obiettivi dei modelli è rappresentare completamente il sistema evitando però di appesantirne inutilmente i diagrammi.

Inclusione o estensione? Nel processo di progettazione degli use case diagram, è prassi comune strutturare i casi d’uso associandoli tra loro per mezzo di relazioni di inclusione ed estensione. Ciò

54

Capitolo 3. Introduzione agli Use Case

permette di realizzare diagrammi più eleganti, chiari e quindi, in ultima analisi, più facilmente comprensibili. Il ricorso alle relazioni testé enunciate equivale ad affermare che la sequenza delle azioni di un use case necessita, rispettivamente, dell’esecuzione di quella espressa in eventuali use case inclusi e che la stessa viene variata da eventuali casi d’uso estendenti. Non è infrequente il caso in cui non si sappia bene quale sia la tipologia di relazione più appropriata: “Quale stereotipo utilizzare: include o extend?”.

Prima di procedere in questa disquisizione si ritiene opportuno rimarcare due concetti molto importanti: 1. il tempo è una risorsa di massima importanza da utilizzarsi con oculatezza (questo termine racchiude una sua diabolicità): quando non esistono motivazioni lampanti per preferire l’una all’altra relazione, probabilmente non vale la pena dedicarsi a elucubrazioni mentali. Spesso si impiega molto tempo cercando di capire quale sia la relazione più appropriata per un particolare contesto: si ricordi che ancora non è stato istituito il premio per il diagramma dei casi d’uso più elegante; 2. non si può pretendere di esprimere i requisiti del sistema unicamente per mezzo del formalismo grafico; probabilmente è più opportuno relegarlo a una sorta di overview (questo argomento viene trattato approfonditamente nel capitolo successivo). Ancora una volta è meglio operare economie di tempo anche perché le fasi successive del ciclo di vita del software, e in particolare il disegno, lasciano ampio margine per avvalersi di tutta la propria cultura e skill.

Premesso ciò, la prima macrodifferenza tra le due relazioni risiede nell’opzionalità: se l’esecuzione del flusso di azioni di uno use case “invocato” deve avvenire solo al verificarsi di particolari condizioni, ossia se rappresenta una variante del flusso di azioni, allora non vi è dubbio: bisogna ricorrere alla relazione di estensione. Per esempio nello use case di fig. 3.20 lo studente viene abilitato a selezionare l’esame di interesse e quindi a procedere nell’iter solo se preventivamente riconosciuto e quindi autorizzato dal sistema. Probabilmente, l’opzionalità dell’esecuzione è il concetto più importante a cui bisogna prestare particolare attenzione nel selezionare la relazione più opportuna da utilizzarsi: le altre differenze potrebbero rivelarsi piuttosto marginali. Per tutti gli altri casi di incertezza di seguito viene riportato un elenco di riflessioni che potrebbero rivelarsi utili nella selezione della relazione più adeguata. Una prima considerazione da farsi è relativa all’integrazione: se un caso d’uso viene invocato solo da un altro, probabilmente è opportuno utilizzare il costrutto di estensio-

UML e ingegneria del software: dalla teoria alla pratica

55

ne, se invece è possibile riutilizzarlo nel flusso di azioni di svariati casi d’uso invocanti, allora potrebbe essere preferibile ricorrere a una relazione di inclusione. La relazione di inclusione ricorda molto un’invocazione di procedura (o metodo) e pertanto, nel caso in cui uno use case incluso necessiti di attributi, questi dovrebbero essere forniti dallo use case base. Nel caso di use case estendenti, data la loro natura, è del tutto legittimo che essi accedano agli attributi dello use case base. Un’altra argomentazione che potrebbe risultare utile è relativa alla conoscenza che un caso d’uso ha dell’altro utilizzato. Nel caso della relazione di inclusione, lo use case che include ha conoscenza di quest’ultimo, in modo del tutto analogo a quanto avviene nella fase di codifica: un metodo conosce un altro invocato. Nel caso invece della relazione di estensione, lo use case esteso contiene il flusso primario degli eventi e non ha conoscenza diretta di eventuali altri casi d’uso estendenti (ovviamente predispone esplicitamente i punti il cui comportamento deve essere esteso). Ciò implica che lo use case base o esteso deve essere ben formato anche senza la presenza di use case estendenti. Per molti tecnici lo use case estendente è paragonabile al comportamento di un editor che aggiorna solo particolari parti di un testo, sia correggendo, sia inserendo nuovi paragrafi. Per concludere l’argomento, si vuole sottolineare ancora che ogni qualvolta non sussistano evidenti motivazioni per propendere per l’una o l’altra relazione è consigliabile non perdere troppo tempo; l’unica cosa che è sempre opportuno tenere a mente è che uno degli obiettivi primari è rendere il tutto il più semplice possibile: i diagrammi dei casi d’uso dovrebbero risultare facilmente fruibili anche da personale con scarsa conoscenza di informatica.

Relazioni precede e invoca Analizzando molti use case diagram prodotti in diverse aziende è possibile imbattersi in molteplici stereotipizzazioni delle relazioni base o addirittura in alcune completamente nuove non previste nel metamodello. In particolare nel libro Use Case Driven Object Modeling with UML (vedi Bibliografia) ne vengono proposte due interessanti: invokes (invoca) e precedes (precede), che tra l’altro non sono neanche stereotipi. Circa l’utilizzo di nuovi elementi esistono diverse correnti di pensiero tutte più o meno plausibili. Un concetto da tenere bene a mente è che ogni qualvolta si ricorre all’utilizzo di stereotipi non standard è sempre necessario pensare che, se da un lato è vero che si tratta di strumenti messi a disposizione dello UML e quindi da utilizzarsi, dall’altro stereotipizzazioni importanti e non standard rendono i modelli meno fruibili, il che evidentemente non è esattamente un obiettivo dello UML. Il tutto viene poi amplificato quando ci si riferisce a elementi del tutto nuovi. Quindi il ricorso agli stereotipi va sempre ponderato. Chiaramente non si fa riferimento a stereotipi semplici come l’icona di Exception per le relative classi, o particolari versioni di attori raffigurati per mezzo di sagome più specifiche.

56

Capitolo 3. Introduzione agli Use Case

Il consiglio allora può essere di utilizzare opportuni stereotipi (non quelli banali) ogni qualvolta sia veramente necessario e concorrano effettivamente ad aumentare la qualità e leggibilità del modello, in tutti gli altri casi, probabilmente è consigliabile forzarsi a utilizzare elementi standard. Per ciò che concerne invece elementi non standard che non siano frutto di stereotipizzazioni, il consiglio non può che essere di utilizzarli solo in casi di estrema necessità.

Premesso ciò, l’idea alla base della relazione di invokes è molto semplice: un caso d’uso può invocarne un altro, analogamente a quanto avviene con le funzioni. Un vantaggio nel ricorrere a relazioni di questo tipo è che permettono di focalizzare l’attenzione su ciò che è racchiuso in un caso d’uso (in termini di sequenza di funzioni). Probabilmente spesso si viene distratti dal come si arrivi a un caso d’uso (precondizioni) e da cosa accade quando si lascia lo stesso (postcondizioni), senza dare il giusto rilievo allo use case stesso. Per quanto concerne la relazione precedes (precede) essa semplicemente evidenzia che la sequenza di operazioni eseguite da un caso d’uso deve essere preceduta dall’esecuzione di quella presente in un altro. Probabilmente il motivo che rende particolarmente vantaggiose queste relazioni è che spesso si cerca di modellare attraverso i casi d’uso anche aspetti che esulano dal relativo campo di pertinenza (dinamici) nell’errata convinzione che gli use case diagram da soli siano sufficienti a catturare tutti i requisiti funzionali. Come riportato successivamente è necessario calarli nella giusta dimensione di overview grafico: in questo contesto il buon testo è ancora lo strumento migliore. Probabilmente il diagramma in fig. 3.21 non dovrebbe essere sottoposto al vaglio di un’analisi eccessivamente critica: in fin dei conti si tratta di un esempio e pertanto dovrebbe essere considerato come tale: nessuno vuole commettere l’errore dello stolto che guarda il dito del saggio che indica la luna. Comunque, poiché il fine del presente libro è sicuramente didattico forse qualche considerazione è lecita. Leggendo il diagramma si corre il rischio di capire che il mediatore (Assistente Trader) nell’inserire un nuovo ordine nel sistema debba prima impostare il codice per la transazione di vendita (Imposta trade di vendita), poi quello per la transazione di acquisto (Imposta trade di acquisto) e infine definire in dettaglio l’investimento (Definisce investimento). Evidentemente questa sarebbe una conclusione errata, in quanto, nell’esempio — non si tratta di uno scambio come nella compravendita di valuta Foreign eXchange — un singolo trade può essere o di acquisto o di vendita ma non entrambi. Il problema è che le relazioni di precedes sembrerebbero non possedere alcuna semantica che ne indichi l’esecuzione alternativa che, invece, è presente nelle relazioni di generalizzazione. Pertanto, probabilmente una versione più chiara del diagramma potrebbe essere quella riportata nella fig. 3.22.

57

UML e ingegneria del software: dalla teoria alla pratica

Figura 3.21 — Esempio di un frammento di un trading system. Relazioni di precedes e invokes.

Imposta trade di vendita «precedes»

Validazione utente «precedes» «invokes»

Imposta trade di acquisto

Verifica idoneita' studente Assistente Tader

Esegue perfezionamento trade

Figura 3.22 — Versione dello use case “inserisce nuovo ordine” con la relazione di generalizzazione.

Definisce investimento «include»

Inserisce nuovo ordine

Assistente Trader

Ordine di acquisto

Ordine di vendita

58

Capitolo 3. Introduzione agli Use Case

In questo caso è evidentissimo che un ordine può essere o di vendita o di acquisto ma non di entrambe le tipologie.

Come identificare i casi d’uso Il processo di identificazione dei casi d’uso, sebbene possa sembrare un’attività di livello di complessità contenuto, nel contesto del processo di sviluppo del software riveste un’importanza notevole; basti pensare che il cliente alla fine pretende che il sistema realizzi correttamente tutte le funzionalità descritte nella vista dei casi d’uso. Come illustrato in precedenza, gli attori sono persone o sistemi interessati al comportamento dei casi d’uso: ne avviano l’esecuzione e vi scambiano messaggi. Quindi, poiché le funzioni che il sistema deve fornire sono intimamente connesse con gli attori che interagiscono con i casi d’uso, molto probabilmente è possibile semplificare il processo facendo precedere l’attività di identificazione degli attori. Un primo problema che si pone è il livello di dettaglio da raggiungere. Spesso l’individuazione e la descrizione dei casi d’uso viene affrontata con una metodologia di tipo top down iterata fino a raggiungere un’eccessiva granularità. Si ricordi che ciò non è sempre auspicabile perché si investe male il tempo a disposizione, si imprigiona prematuramente il processo di sviluppo, e così via. Probabilmente è preferibile tendere costantemente alla semplicità e alla chiarezza dei diagrammi. Il consiglio pertanto non può che essere di mostrare unicamente gli use case veramente importanti per il corrente livello di astrazione e per il sottosistema oggetto di studio. Osservando il sistema secondo la prospettiva di cui dispongono gli attori, è possibile individuare le funzioni che lo stesso deve implementare rispondendo ai seguenti interrogativi: • Quali funzioni sono richieste al sistema da ciascun attore? Quali azioni esegue l’attore con particolare riferimento al sistema informatico? • Quali sono le informazioni elaborate dal sistema oggetto di interesse dell’attore? Che tipo di interazione esiste tra l’attore e le informazioni stesse? Le produce, scrive, legge, modifica? • L’attore ha necessità di inviare o di ricevere notifiche dal sistema? Quali sono le funzionalità necessarie per ricevere, e/o produrre gli eventi relativi l’attore? • Il lavoro degli attori, specie quello più ricorrente, può essere semplificato o reso più efficiente? • Di quali input e output ha bisogno il sistema? Quale è il relativo flusso?

UML e ingegneria del software: dalla teoria alla pratica

59

Una volta esaurito il precedente questionario è possibile verificare quanto emerso ponendosi questa volta nell’ottica del sistema: • Di quali input e output il sistema ha bisogno? Da dove provengono questi input e dove sono destinati gli output? • Nel caso che già esista un sistema, quali sono i suoi problemi maggiori nell'implementazione esistente? (Questo quesito potrebbe considerarsi piuttosto marginale: le lamentele sono le prime cose comunicate dagli utenti quando si inizia la serie di interviste). Un approccio differente, che di solito riscuote molto successo, consiste nel cominciare a individuare le funzioni che il sistema dovrà realizzare e poi gli attori che vi interagiscono, utilizzando il formalismo degli activity diagram. Questi offrono il grande vantaggio di somigliare moltissimo ai progenitori flow chart e quindi di essere familiari a persone con pochissima esperienza tecnica: chi non ha mai realizzato un flow chart? Pertanto si può richiedere agli utenti del sistema di redigere prime versioni di tali diagrammi e da questi poi ricavare i casi d’uso. Chiaramente non è pensabile che le versioni prodotte dagli utenti siano quelle definitive, ma comunque esistono due vantaggi: • si individua un formalismo di dialogo tra cliente e team tecnici; • forniscono un ottimo inizio. In particolare si potrà riscontrare che una o più attività (a seconda del livello di dettaglio dell’activity diagram e dello use case da produrre) confluirà in uno o più casi d’uso. Il lato negativo è che tipicamente si assisterà a tutto un fiorire di activity diagram molto simili tra loro; come dire: il dono della generalizzazione tipicamente non alberga presso i clienti. Questa tecnica verrà descritta più in dettaglio nel Capitolo 4.

Organizzazione dei casi d’uso I modelli dei casi d’uso di sistemi non semplici sono generalmente costituiti da svariate decine di diagrammi e da centinaia di singoli casi d’uso. A questo punto si pone il problema di come strutturare il modello in package. Il consiglio migliore probabilmente è di organizzare il tutto nel modo più naturale possibile. Una buona tecnica è realizzare un primo package nel quale inserire tutti gli attori (magari ordinati per nome al fine di semplificarne l’individuazione), seguiti da tanti altri package ognuno dedicato a un diagramma. In questi casi si parla di business functional packages. Nel caso in cui però non sia ben chiaro come organizzare i diagrammi, la linea guida da seguire è di utilizzare il vecchio criterio della coesione funzionale: ogni diagramma do-

60

Capitolo 3. Introduzione agli Use Case

vrebbe modellare completamente uno specifico servizio, ossia l’obiettivo fondamentale che l’efferente attore si aspetta di raggiungere dalla relativa fruizione. Dovendo modellare le funzioni, o più in generale, il valore offerto dal sistema (o dal business) ai relativi attori, in linea di massima è opportuno realizzare un diagramma per ogni singolo servizio erogato al relativo attore. Qualora poi una funzione risulti particolarmente complessa, probabilmente è opportuno verificare la possibilità di rappresentarla attraverso diversi diagrammi. Spesso risulta molto conveniente definire un diagramma supplementare, inserito nell’apposito package, riportante una sorta di overview — un indice — dell’intero modello. Sebbene non sia pratica consigliata procedere con esercizi di decomposizione funzionale, realizzare un diagramma di tipo top-level risulta molto utile. In questo diagramma dovrebbero comparire tutti gli attori del sistema connessi con i casi d’uso ritenuti più significativi. Alcuni tecnici consigliano di realizzare diverse versioni di questo tipo, una per ogni attore. L’autore considera questo esercizio un sovraccarico di lavoro (overhead) che tipicamente provvede un valore aggiunto piuttosto limitato. Il problema non risiede tanto nella realizzazione quanto nella manutenzione dei vari diagrammi.

La classe negli use case Sebbene il gioco di parole utilizzato nel titolo del presente paragrafo possa far pensare a chissà quali diagrammi, l’obiettivo è fornire una serie di consigli operativi, tratti dall’esperienza, atti ad accrescere il livello qualitativo dei grafici dei casi d’uso. Il relativo formalismo, sebbene risulti piuttosto intuitivo, viene in ogni caso avvertito come estraneo dal cliente. Pertanto è consigliabile sia pianificare delle sessioni di addestramento del personale non tecnico coinvolto nell’attività di revisione del modello dei casi d’uso — si tratta comunque di un formalismo relativamente recente — sia ricorrere a una serie di espedienti al fine di rendere i diagrammi più ordinati e accattivanti: ciò se non elimina le iniziali idiosincrasie, sicuramente non le aumenta.

L’eleganza è uno stato mentale e come tale lo si applica indistintamente all’ambiente che ci circonda. Esaminare un diagramma ordinato, possibilmente senza linee che si incrociano, con le varie sagome ben allineate e con colori scelti accuratamente sicuramente sortisce un effetto positivo sul cliente. Sebbene l’abito non faccia il monaco, la prima cosa che si percepisce è proprio l’abito. In ultima analisi, si tratta di stabilire delle convenzioni e, limitatamente a questo caso, ogni team può decidere liberamente di utilizzare quelle ritenute più opportune; la parola chiave però è coerenza. Una volta scelte delle convenzioni, diventa necessario rispettarle. Riconoscere rapidamente pattern ripetitivi, infatti, ben si adatta alla naturale tendenza

UML e ingegneria del software: dalla teoria alla pratica

61

della mente umana. Tra l’altro, sempre da studi di ricercatori in psicologia eseguiti intorno agli anni Settanta, sembrerebbe che sia sette il numero “magico” di elementi di cui un diagramma dovrebbe essere composto, per risultare più comprensibile alla mente umana. Pertanto, qualora possibile, risulta preferibile ridurre a sette il numero di elementi presenti in uno stesso diagramma. L’autore viola sistematicamente questa regola. Un altro fattore di psicologia della percezione da tenere sempre presente è il valore delle simmetrie (qualche esempio? La splendida Piazza San Giovanni a Roma). Qualora possibile è consigliabile organizzare il diagramma cercando di enfatizzare forme simmetriche. Ad onor del vero esiste una gara (non ufficiale) tra gli architetti il cui obiettivo è realizzare diagrammi le cui forme ricordino oggetti reali. Molto quotate sono forme ad albero e a missile. Altro elemento da considerare è che, sebbene l’ordine con cui i vari casi d’uso compaiono nei diagrammi non abbia alcuna relazione con l’ordine con il quale gli stessi dovranno essere “eseguiti” per realizzare le funzionalità che descrivono, viene abbastanza spontaneo leggere i diagrammi rispettando l’ordine di apparizione degli stessi. Dunque, disegnando i casi d’uso, è consigliabile tenere a mente che il lettore sarà portato, in qualche modo, a rispettare la sequenza con cui i singoli use case sono collocati nei relativi diagrammi. Qualora possibile è opportuno posizionare i casi d’uso in base al relativo ordine di utilizzo. Rispettare tale orientamento potrebbe risultare in contrapposizione con la necessità di realizzare i famosi diagrammi ordinati; in caso di conflitti, è probabilmente più opportuno assegnare la priorità all’armonia: l’ordine di esecuzione viene evidenziato nei flussi di azione, nei template e nei diagrammi dinamici. Un altro fattore da considerare è la dimensione degli elementi grafici. Anche se questa non dovrebbe fornire informazioni supplementari, come priorità, importanza, ecc. è ovvio che l’attenzione del lettore tenderà a essere rapita dagli elementi di dimensioni superiori. Pertanto, se per le caratteristiche di un particolare tool (p.e.: iscrizione del nome del caso d’uso all’interno dell’ellisse, come da definizione standard) è necessario visualizzare elementi di dimensioni diverse, si consiglia di utilizzare tale inconveniente a proprio vantaggio, per esempio ingrandendo artificiosamente l’elemento a cui si vuole attribuire maggiore enfasi e/o importanza.

Per ciò che concerne l’attività di selezione dei nomi è consigliabile seguire delle convenzioni, semantiche e stilistiche. Per quanto riguarda le prime, è preferibile iniziare la descrizione con dei verbi e porsi nell’ottica degli attori: i nomi devono descrivere gli obiettivi che gli utenti intendono conseguire interagendo con il sistema e non ciò che il sistema stesso esegue. Questo sia perché i fruitori principali dei modelli dei casi d’uso sono gli utenti, sia perché obiettivo del formalismo stesso è conferire particolare attenzione alla prospettiva che gli attori hanno del sistema.

62

Capitolo 3. Introduzione agli Use Case

Figura 3.23 — Convenzione del posizionamento degli stereotipi a “destra” rispetto al verso delle frecce.

Figura 3.24 — Diagramma illustrante le convenzioni citate.

Reperimento DVD «include»

Prenota DVD «include»

«extend»

Validazione utente Cliente

«include»

Aggiorna disponibilita' «extend»

Annulla prenotazione «extend»

Addebita penale

Business rule: L'addebito della penale avviene solo in caso di recidivita' (2 ricorrenze nell'arco dello stesso mese)

UML e ingegneria del software: dalla teoria alla pratica

63

Quindi se una funzionalità consiste, ad esempio, nel “caricare” un file nel sistema, è più opportuno “battezzarla” “Invia file” piuttosto che “Ricevi file”. Per ciò che concerne la forma, la praticità suggerirebbe di riportare tutto in minuscolo, mentre dal punto di vista dello stile, probabilmente è più elegante riportare solo la prima lettera in maiuscolo. Per ciò che attiene alle direttive da utilizzarsi per gli attori è consigliabile utilizzare le convenzioni de facto esistenti per le classi (un attore in ultima analisi può essere considerato come una classe particolare): nomi con le prime lettere maiuscole e ricordanti il ruolo recitato dall’attore e non le singole istanze. Per quanto concerne il ricorso ai colori è opportuno evidenziare che, se utilizzati correttamente, concorrono ad aumentare il fascino del diagramma, mentre se usati male o in eccesso tendono a tramutarsi nella classica zappa sui piedi. Colori troppo forti e diagrammi eccessivamente sfarzosi sono decisamente fuori luogo: Carnevale, sebbene sia un periodo piacevole, ha una collocazione temporale piuttosto definita nell’arco dell’anno. In primo luogo è possibile utilizzare i colori al fine di attribuire la giusta enfasi ai vari oggetti presenti nei diagrammi. Per esempio, i campi note, pur contenendo importanti informazioni, raramente risultano vitali e quindi è consigliabile attenuarne l’impatto. Ciò si può ottenere utilizzando, per esempio, un colore grigio chiaro per la sagoma del foglio, per il font e per la linea tratteggiata che li connette all’elemento relativo. La precedente convenzione non vale nel caso si tratti di un’annotazione temporanea, riportante un dubbio o comunque un’argomentazione transitoria da discutere con il cliente. Per quanto riguarda gli stereotipi associati alle relazioni di estensione e di inclusione, è consigliabile riportarli con un font di dimensione abbastanza ridotta. Poiché poi le relazioni di estensioni tendono a essere utilizzate per rappresentare comportamenti opzionali, al contrario delle relazioni di inclusione, che invece sono comportamenti “obbligatori”, risulta molto elegante evidenziare le prime con una gradazione scura di grigio e le seconde con un colore nero. Il tocco finale è dato dalla posizione in cui riportare le etichette degli stereotipi: non bisogna lasciare nulla al caso, anzi è opportuno trasmettere al cliente la sensazione che tutto sia stato oggetto di lungo e accurato studio. Il consiglio è quello di scegliere una convenzione (come per esempio quella in fig. 3.23, in cui le etichette vengono riportate a destra rispetto il verso della freccia), stamparla e quindi osservarla ogni qualvolta sia necessario posizionare uno stereotipo. Un esempio di utilizzo di quanto appena detto è riscontrabile in fig. 3.24.

Alcuni esempi Esempio 1: sistema d’arma A conclusione della trattazione sui diagrammi dei casi d’uso, ossia della proiezione statica della use case view, si fornisce un esempio, forse un po’ artificioso, ma sicuramente originale, relativo a una versione molto semplificata di un sistema d’arma contraerei.

64

Capitolo 3. Introduzione agli Use Case

Lungi dall’idea di voler fornire una descrizione esaustiva di un sistema così complesso, — altro che il famoso vitto ed alloggio a carico dello Stato… — si è deciso comunque di proporre questo esempio essenzialmente per due motivi: 1. mostrare la peculiarità dello UML di poter essere utilizzato proficuamente in ambienti tra i più diversificati; 2. fornire un esempio accattivante ed originale non ancora usurato dall’inflazione della manualistica. Da una prima analisi del diagramma in fig. 3.25 si può notare che esistono due tipologie di attori: 1. operatori umani: Comandante e Operatore radar (nei sistemi reali esistono diversi altri attori, ad esempio altri operatori radar, ognuno addetto a interagire con uno specifico dispositivo); 2. dispositivi fisici: Sistema di lancio e Radar, che prevede le specializzazioni in: scoperta, acquisizione, identificazione e puntamento (o guida). Come si può notare il missile non viene considerato un attore del sistema in quanto la relativa interazione con lo stesso è indiretta, ossia viene sempre mediata da altri attori. In fase di lancio i comandi arrivano al relativo dispositivo che si occupa di interagire localmente con il missile. Durante la fase di crociera (tipicamente composta da una fase iniziale di volo “cieco” e dalla fase guidata) il missile riceve le coordinate del bersaglio sia dal radar di puntamento, sia, tipicamente, da un’antenna installata sulla testata del missile stesso. Per terminare, l’eventuale ordine di autodistruzione viene impartito ancora attraverso apposito segnale radio inviato dal radar. Ovviamente tutti i missili hanno un proprio sistema di autodistruzione temporizzato: se il bersaglio non viene colpito entro un opportuno lasso di tempo (che dipende dalla gittata del missile, dalla sua velocità, ecc.) il missile esplode automaticamente. Un discorso del tutto analogo vale per gli aerei intercettati dai radar: la loro interazione con il sistema viene mediata da altri attori (i radar, appunto). In genere sistemi di questo tipo prevedono una serie di radar di scoperta il cui obiettivo è monitorare continuamente e con un ampio raggio di azione lo spazio aereo circostante. Tipicamente ci sono due tipologie di radar di scoperta: quelli per alte e altissime quote (raggiungono distanze superiori ai 100 km) e quelli per le basse quote il cui raggio di azione è limitato ai 40/50 km. Una volta individuata una traccia, i relativi dati vengono forniti al sistema (Comunica traccia “sospetta”) che prevede a inoltrarli sia al Radar di acquisizione il

65

UML e ingegneria del software: dalla teoria alla pratica

Figura 3.25 — Use case diagram di un improbabile sistema d’arma contraerei.

Comunica traccia "sospetta" Radar di Scoperta

Abbandono traccia

Radar di Acquisizione

Avvia sequenza identificazione «extend»

Assegna priorita' identificazione Modifica priorita' identificazione

Radar

Operatore Radar

Comunica traccia acquisita

Identifica tipologia traccia Radar di Identificazione

Comunica dati traccia aggiornati

Comando di autodistruzione Avvio sequenza lancio «extend»

Radar di Puntamento

Avvio sequenza lancio Comandante

Modifica priorita' di lancio Distruzione missile Coordinate missile

Abbandono sequenza lancio Coordinate target Sistema pronto per il lancio Comando di lancio Radar di Puntamento

66

Capitolo 3. Introduzione agli Use Case

quale, se non occupato con altri bersagli, si predispone a “puntare” la traccia, sia all’Operatore radar, il quale in ogni momento può decidere di abbandonare la sequenza di identificazione (Abbandono traccia), perché magari nel frattempo si è scoperto un jet più minaccioso o perché l’aereo oggetto di attenzione si sta allontanando dall’area protetta dal sistema d’arma, e così via. Nel caso si ritenga necessario procedere con l’identificazione della traccia, l’Operatore radar conferma l’avvio della sequenza di identificazione (Avvia sequenza identificazione). Tipicamente i caccia nemici eseguono manovre di attacco in squadriglie secondo opportune tattiche. In questi casi il sistema, in base ai dati ricevuti, prevede ad assegnare le priorità di identificazione delle tracce (Assegna priorità identificazione; da notare che si è utilizzata la relazione di extend anziché una di include, perché la funzione deve essere eseguita solo nel caso in cui le tracce scoperte siano più di una). Ovviamente l’operatore può sempre decidere di variare l’assegnazione effettuata dal sistema (Modifica priorità identificazione) ed eventualmente decidere nuovamente di abbandonarne una perché magari si tratta di un aereo accertato “amico” (riconoscibile per esempio dal relativo “corridoio” aereo di volo). Avviata la sequenza di identificazione i dati della traccia vengono forniti (Comunica traccia acquisita) sia al Comandante sia al Radar di identificazione (comunemente denominato IFF, Identification Friend or Foe) il quale provvede a interrogare il transponder del jet sospetto. In particolare il radar invia un treno di impulsi e si attende una precisa sequenza di risposta la cui analisi permette di stabilire se si tratta di un aereo amico o meno (Identifica tipologia target). Chiaramente i jet nemici, nell’atto di un’azione offensiva spengono i relativi transponder, pertanto un radar IFF è in grado di stabilire se si tratta di un jet amico (codice di ritorno corretto) o di uno non identificato (transponder non risponde). Tra l’altro, all’atto dell’interrogazione del transponder, il jet nemico viene gentilmente informato di essere oggetto delle amorevoli attenzioni di un radar nemico. Se il jet procede per la sua rotta e non si tratta di un soggetto amico, allora si dà inizio alla sequenza di lancio dei missili (Avvio sequenza di lancio). A questo punto nelle cure dell’aereo, sicuramente non amico, viene coinvolto anche il Radar di puntamento il quale assolve diversi compiti, ossia è gravato dalle seguenti responsabilità: • fornire i dati iniziali al sistema di lancio: poiché, la fase di volo iniziale di un missile (circa un secondo) è cieca (non viene guidato da alcun segnale) si cerca di lanciarlo nella direzione di intercetto stimato del jet nemico; • fornire costantemente al missile coordinate aggiornate del punto di intercetto stimato, sia per permettergli di raggiungerlo sia affinché esploda in prossimità al bersaglio: tipicamente questo tipo di missili non cerca l’impatto bensì l’esplosione

UML e ingegneria del software: dalla teoria alla pratica

67

nelle vicinanze del bersaglio, in modo da tentare di colpire, con schegge generate dalla deflagrazione, più nemici possibili nel caso che ne siano presenti più di uno nella zona; • inviare al missile, in casi estremi, il segnale di autodistruzione. Nel caso tipico di una attacco portato da diversi jet il sistema provvede nuovamente a riassegnare le priorità di intercettamento (Assegna priorità di lancio) le quali, come è lecito attendersi, possono essere sempre variate a discrezione del Comandante (Modifica priorità di lancio). Da tener presente che un sistema d’arma possiede diversi radar di puntamento, svariati sistemi di lancio, ognuno dei quali armato con diversi missili ecc. Una volta che il sistema di lancio viene stimolato per il lancio, riceve le coordinate di intercetto stimato (Coordinate target), si predispone nell’opportuna direzione e quindi notifica al Comandante di essere pronto per il lancio vero e proprio (Sistema pronto per il lancio): il “pulsante rosso” comincia a lampeggiare. A questo punto il Comandante può decidere di abbandonare la sequenza di lancio (Abbandono sequenza di lancio) o selezionare uno dei missili disponibili sul sistema di lancio prescelto (Comando di lancio). La sequenza potrebbe essere annullata perché per esempio nel frattempo un altro target si è reso più minaccioso, perché lo stesso è stato abbattuto da un precedente lancio, ecc. Una volta lanciato il missile il Comandante può decidere in ogni istante di distruggerlo (Distruzione missile). Nei precedenti paragrafi è stato descritto molto superficialmente il modo in cui potrebbe funzionare un ipotetico sistema d’arma contraerei. Come si potrà ben notare il diagramma dei casi d’uso di per sé non è sufficiente a specificare completamente le funzionalità del sistema: è assolutamente necessario disporre anche della descrizione del comportamento dinamico, nonché della descrizione delle azioni da seguire nel caso in cui si verifichino delle anomalie (per esempio il sistema di lancio ha un bug e non riesce a lanciare il missile selezionato dal Comandante).

Esempio 2: noleggio DVD Viene qui fornita la proiezione statica, corredata da opportuna spiegazione, dei requisiti utente di un ipotetico sistema di gestione di un esercizio commerciale noleggio DVD. Il sistema finale sfrutterà una tecnologia web based al fine di disporre di un unico sistema fruibile sia tramite connessione Internet sia per mezzo di apposite installazioni (chioschi), presenti presso i vari punti “vendita”. L’intero servizio risulterà basato su appositi account virtuali assegnati all’utente all’atto della relativa iscrizione e accessibili attraverso il tradizionale sistema di login e password. Ogni qualvolta un cliente noleggerà un DVD, il relativo account verrà decrementato di

68

Capitolo 3. Introduzione agli Use Case

Figura 3.26 — Overview delle funzionalità del sistema.

Richiede iscrizione via web Inserisce nuovo cliente

Amministratore

Gestione account Ricezione esito Ricarica account

Credit Card Authority

Verifica disponibilita' DVD Utente Cliente

Commesso

Prenota DVD Annulla prenotazione Noleggio DVD Riconsegna DVD Aggiorna stock

un importo proporzionale a specifici parametri decisi in base a DVD prescelto, tempo di noleggio, ecc. La “ricarica” dell’account potrà essere effettuata online dai clienti tramite carta di credito oppure utilizzando metodi più tradizionali direttamente presso l’esercizio. Prima di iniziare la disamina delle varie funzionalità, è stata riportata nella fig. 3.26 una sorta di overview dei servizi disponibili. In particolare è possibile notare la visualizzazione dei “confini” del sistema, evidenziati per mezzo di apposito rettangolo. Obiettivo della fig. 3.26 è fornire una sorta di indice dei servizi e, considerato il fine del diagramma, i casi d’uso mostrati possono risultare leggermente diversi dalla realtà. Per esempio lo use case Gestione account viene illustrato associato all’attore Credit Card Authority mentre nella realtà non è esattamente così. In particolare è il caso d’uso Invia richiesta autorizzazione, non mostrato nell’overview, che effettua uno scambio con la Credit Card Authority.

69

UML e ingegneria del software: dalla teoria alla pratica

Figura 3.27 — Adesione del cliente all’esercizio commerciale. Richiede iscrizione via web

Inserisce nuovo cliente

Utente

Amministratore

Figura 3.28 — Gestione account. Validazione utente «include»

Gestione account «extend»

Cliente

Aggiornamento dati

«extend»

«extend»

Visualizzazione transazioni Ricarica importo

Pagamento via carta di credito

Ricezione autorizzazione «extend» [Richiesta autorizzata]

Credit Card Authority

Ricarica importo

Figura 3.29 — Ricarica account. Validazione utente «include»

Ricarica account Amministratore

Commesso

«extend»

Reperimento account

70

Capitolo 3. Introduzione agli Use Case

La prima operazione (fig. 3.27) che un potenziale cliente deve compiere per poter acquisire lo status di cliente del servizio di noleggio DVD consiste nel registrarsi (Richiede iscrizione via Web). Chiaramente la relativa richiesta deve essere convalidata dall’amministratore del sistema (Inserisce cliente); in altre parole, egli ne ha la completa responsabilità. Qualora il potenziale cliente non disponga di una connessione Internet, la relativa richiesta dovrebbe essere effettuata per mezzo dei chioschi ubicati presso l’esercizio commerciale, eventualmente con il supporto del relativo personale. Uno dei primi servizi a disposizione del cliente è la gestione del proprio account (fig. 3.28). Chiaramente si tratta di una funzione sensibile e quindi l’accesso prevede il riconoscimento cliente eseguito tramite immissione della coppia di valori codice cliente e password. Questo servizio consente al cliente di verificare gli “addebiti” effettuati sul relativo account (Visualizza transazioni), di modificare i propri dati (Aggiornamento dati) o di incrementare l’importo (Ricarica importo). Chiaramente, quest’ultima funzionalità può essere fruita solo dai clienti in possesso di carta di credito. Le relative transazioni necessitano la conferma di apposite autorità esterne. Qualora il cliente non disponga di carta di credito, la ricarica dell’efferente account può essere espletata direttamente nell’esercizio. In questo caso, come mostrato da fig. 3.29, è il commesso a interagire con il sistema anche se fa le veci del cliente. Le metodologie tradizionali, quali per esempio DFD, prescrivono che l’attore è colui che ha interesse nella fruizione di un servizio (in questo caso quindi dovrebbe essere ancora il cliente) e non chi interagisce fisicamente con lo stesso, mentre lo UML prevede l’esatto contrario: l’attore è colui che interagisce direttamente con il sistema anche se fa le veci di un’altra persona. La ricarica dell’account del cliente eseguita presso l’esercizio commerciale è di solito una funzione di competenza dei commessi, ma nulla vieta che a espletarla sia l’amministratore. Dal punto di vista degli use case, l’amministratore risulta essere una specializzazione del commesso: è in grado di eseguire tutte le stesse funzionalità, ma in più è gravato da ulteriori responsabilità. Anche il servizio di ricarica account rientra nella categoria delle funzioni ritenute sensibili per il sistema, quindi la sua fruizione prevede il riconoscimento dell’utente. La ricarica prevede il reperimento dell’account del quale si vuole incrementare l’importo (Reperimento account) che potrebbe essere effettuato sia specificando il codice dell’account sia utilizzando i dati del cliente, ecc. Un’altra funzionalità resa disponibile dal sito è la verifica della disponibilità di specifici DVD. Nel diagramma riportato in fig. 3.30 si è deciso di indicare un attore generico (Utente) per indicare che tale funzione è disponibile per qualsiasi fruitore del sito e anche per utenti occasionali. In altre parole la funzionalità di Verifica disponibilità non è considerata sensibile per il sistema e quindi può essere usufruita senza la necessità di riconoscere l’utente.

71

UML e ingegneria del software: dalla teoria alla pratica

Figura 3.30 — Verifica disponibilità.

Commesso

Verifica disponibilita' DVD Commesso

«include»

Reperimento DVD Cliente

La verifica della disponibilità prevede l’utilizzo della funzione di reperimento DVD (Reperimento DVD). La ricerca può avvenire utilizzando diversi criteri: specificando il titolo eventualmente anche in maniera incompleta, alcuni interpreti, il regista o il periodo di uscita. Un altro servizio messo a disposizione della propria clientela è la possibilità di prenotare DVD (fig. 3.31). Si tratta ovviamente di un servizio appartenente alla categoria di quelli considerati sensibili e pertanto la relativa abilitazione è subordinata alla validazione del cliente. La funzione prevede la validazione del cliente, il reperimento del DVD da prenotare ed eventualmente, nel caso che il DVD sia disponibile, la prenotazione vera e propria con conseguente aggiornamento della disponibilità. In maniera del tutto analoga un cliente può disdire una prenotazione. In questo caso potrebbe essere prevista una penale economica da applicarsi in funzione a opportuni parametri, quali, per esempio, la recidività, la durata della relativa prenotazione ecc. Queste regole dovrebbero essere illustrate dettagliatamente nella descrizione delle business rules. Una delle funzionalità di vitale importanza per l’esercizio è il noleggio vero e proprio di DVD, il quale, nell’esempio illustrato, può essere eseguito unicamente presso i punti vendita dell’esercizio stesso, attraverso appositi dispositivi. In sostanza non si è presa in considerazione la possibilità di consegna a domicilio. Chiaramente si tratta di una funzionalità sensibile per il sistema e quindi fruibile previa validazione dell’utente. Un DVD può venir noleggiato a seguito di precedente prenota-

72

Capitolo 3. Introduzione agli Use Case

Figura 3.31 — Prenotazione DVD.

Reperimento DVD «include»

Prenota DVD «include»

«extend»

Validazione utente

Aggiorna disponibilita'

«include»

«extend»

Cliente

Annulla prenotazione «extend»

Addebita penale

zione oppure sperando che sia disponibile all’atto della richiesta stessa. Si può notare che i due comportamenti alternativi, in quanto tali, sono evidenziati per mezzo di relazione di generalizzazione (fig. 3.32). Nel primo scenario è necessario dapprima reperire la prenotazione (Reperimento prenotazione) e quindi, nel caso che le varie verifiche abbiamo esito positivo e si possa procedere per il noleggio, rimuovere la prenotazione stessa (Chiudi prenotazione). Nel secondo scenario è invece necessario reperire il DVD (Reperimento DVD) che si intende noleggiare. In entrambi i casi, se la funzione di noleggio viene eseguita con successo, è necessario aggiornare la disponibilità di copie del DVD (Aggiorna quantità). Il servizio di noleggio prevede la verifica del credito del cliente (Verifica credito) ma non il relativo addebito; questo viene effettuato in fase di riconsegna in quanto l’importo può dipendere da fattori quali il tempo di noleggio. Specularmente al servizio precedente, ma con le stesse modalità, vi è la riconsegna del DVD noleggiato. Come si può notare dall’analisi del diagramma in fig. 3.33, in questa fase avviene l’addebito dell’importo dovuto per il noleggio del DVD (Addebito importo). Un’altra funzionalità presa in considerazione è quella che consente a commessi e amministratori di aggiornare gli stock a disposizione (fig. 3.34). Ciò può avvenire o perché si acquistano nuove copie di DVD o perché se ne deteriorano altre e quindi esse non risul-

73

UML e ingegneria del software: dalla teoria alla pratica

Figura 3.32 — Noleggio DVD.

Aggiorna disponibilita' «extend»

Validazione utente «include»

Verifica credito «extend»

Noleggio DVD Cliente

Noleggio ex-novo

Noleggio prenotato «include»

«include» «extend»

Reperimento prenotazione

Reperimento DVD Chiusura prenotazione

Figura 3.33 — Riconsegna DVD.

Validazione utente «include»

Riconsegna DVD Cliente

«extend»

Aggiorna disponibilita'

«extend»

Addebito importo

74

Capitolo 3. Introduzione agli Use Case

Figura 3.34 — Aggiorna stock.

Validazione utente «include»

Aggiorna stock Commesso

Inserimento nuovo DVD

Modifica stock «include»

Inserimento nuova copia

Eliminazione copia

Reperimento DVD

tano più disponibili per la clientela. In particolare, l’aggiornamento dello stock può essere relativo a copie di DVD già presenti nel database del sistema, nel qual caso si aggiornano unicamente il numero di copie e i codici delle istanze, o a DVD del tutto nuovi. Il primo scenario si differenzia operativamente dal secondo perché richiede il reperimento del DVD di cui si vuole aggiornare il numero di copie disponibili. In questi paragrafi è stato presentato un ulteriore esempio di proiezione statica dei casi d’uso per un servizio di noleggio DVD. In particolare si è presentata una sua frazione. Ovviamente è possibile estendere i servizi disponibili con ulteriori possibilità, specialmente per quanto concerne il lato back office. Per esempio si potrebbero inserire ulteriori funzionalità per la generazione di dati statistici, altre per verificare inadempienze di clienti, e così via. Ciò nonostante, si spera di aver fornito al lettore una panoramica abbastanza esauriente dei casi d’uso. Nel Capitolo 4 verranno affrontate tematiche relative a un uso più esteso del formalismo dei casi d’uso, con sensibili allontanamenti dalle direttive standard.

Verifiche dei casi d’uso Verificare una vista dei casi d’uso significa sostanzialmente eseguire due attività di test: verifica vera e propria e validazione. Il primo processo serve a confermare che la vista sia stata realizzata correttamente e in accordo alle specifiche raccolte. Obiettivo della

UML e ingegneria del software: dalla teoria alla pratica

75

validazione è accertare che il sistema, così come lo si è modellato, sia effettivamente ciò di cui il cliente e gli utenti finali hanno bisogno. I processi di validazione dovrebbero essere eseguiti, di regola, immediatamente dopo la conclusione di ogni singolo processo con l’obiettivo di neutralizzare il prima possibile eventuali errori, lacune, ecc. Nel contesto degli use case, verosimilmente, è opportuno procedere con le attività di revisione non appena i primi diagrammi siano disponibili. Essi andrebbero sottoposti al e discussi con il cliente: in altre parole la validazione dovrebbe essere eseguita essenzialmente dagli stessi clienti, anche se nella pratica ciò non è sempre possibile.

Un buon criterio per la realizzazione del modello dei casi d’uso consiste nell’utilizzare l’ormai classico approccio iterativo ed incrementale. Invece di dettagliare globalmente ogni singolo use case prima di passare al successivo, potrebbe risultare più opportuno procedere per passi successivi. Per esempio si potrebbe iniziare con una prima versione globale ad alto livello di astrazione, nella quale includere, per ciascun caso d’uso, una breve descrizione. Quindi si potrebbe procedere realizzando una seconda versione associando a ciascun caso d’uso la descrizione del flusso in cui tutto funziona correttamente, ossia trascurando la gestione delle eccezioni, e così via.

L’intento di tale tecnica è quello di riesaminare continuamente i vari modelli prodotti prima di procedere nelle fasi successive. Tale approccio aiuta a neutralizzare uno dei classici problemi della progettazione dei sistemi software: i requisiti solo in casi rarissimi sono chiari e completamente corretti fin dall’inizio. Chiaramente non si tratta di un approccio di “decomposizione-funzionale”, che come si vedrà di seguito può comportare non pochi problemi, bensì di un modo di procedere basato su continui feedback: sottoponendo modelli, anche se non ancora ben definiti, agli utenti e agli esperti del dominio, si permette di neutralizzare il prima possibile eventuali lacune, errori di specifiche, ridondanze ecc. Tra l’altro la modellazione dei casi d’uso, per sistemi non banali, non è certamente un compito elementare, e quindi, è difficilmente ipotizzabile pensare di riuscire a realizzare un modello valido alla prima iterazione. L’obiettivo del processo di validazione è dimostrare che il modello è corretto, completo, rispondente alle aspettative del sistema, e così via. A tal fine è necessario assicurarsi che i fruitori non tecnici dei diagrammi siano effettivamente in grado di comprenderli. I processi di verifica, vitali per i sistemi software, indipendentemente dal contesto in cui li si effettua, dovrebbero essere condotti con una visione pessimistica delle cose: errare humanum est. Dunque l’obiettivo dovrebbe consistere nell’individuare il maggior nu-

76

Capitolo 3. Introduzione agli Use Case

mero di incongruenze ed errori possibili e non semplicemente nell’assicurare che tutto funzioni correttamente e sia rispondente. Un buon processo di verifica dovrebbe evidenziare problemi: meglio iterare più volte la progettazione di uno use case che ridursi a correre ai ripari a progetto in fase di consegna o addirittura ultimato. Come ripetuto più volte, errori o manchevolezze sfuggiti nella fase di analisi dei requisiti utente possono generare ripercussioni devastanti nelle fasi di codifica e soprattutto di test.

Metodologia “Walking the use case” Una tecnica di validazione degli use case diagram frequentemente utilizzata specie negli USA (come sbagliarsi?) prende il nome di “Walking the use case”. Si tratta di un gioco di ruolo, da “praticare” in gruppo (sempre molto in stile USA), in cui diverse persone recitano il ruolo degli attori presenti dei diagrammi d’uso e il master recita il ruolo del sistema stesso. L’esperienza li rende in grado anche di recitare il ruolo di attori non umani, come: sensori, radar, missili… Tale tecnica viene presa così seriamente, e spesso è tale l’immedesimazione, che durante la simulazione si corre il rischio che taluni attori, impersonando ruoli non umani, siano portati a esprimersi con voce metallica… Il gioco comincia con i “partecipanti” che studiano le loro parti, e quindi sottopongono al sistema gli stimoli previsti dall’attore che stanno impersonando. Gli sviluppatori non prendono parte attiva del gioco — come al solito il divertimento e le glorie agli altri — ma fungono da arbitri. Armati di blocchetti — pardon… di portatili — prendono nota di ciò che accade e cercano di individuare eventuali deficienze (sempre quelle dei diagrammi, si intende…). Attraverso l’utilizzo di questa tecnica si evidenzia che talune alternative non sono sufficientemente dettagliate, che taluni stimoli risultano lacunosi, ecc.

Anomalie tipiche del disegno degli use case diagram Nei paragrafi successivi vengono illustrate imprecisioni commesse frequentemente nel disegno dei diagrammi dei casi d’uso. Per molti tecnici si tratta di veri e propri problemi attribuibili al formalismo dei casi d’uso, mentre per l’autore si tratta di equivoci, del tutto comprensibili, nell’utilizzo. Il malinteso basilare che verosimilmente genera diversi inconvenienti è l’errata convinzione, radicata in alcuni progettisti, che i diagrammi dei casi d’uso da soli siano in grado di catturare tutti gli aspetti dei requisiti del cliente: gli use case diagram sono ottimi strumenti per la modellazione della proiezione statica delle funzionalità che il sistema dovrà realizzare, mentre non possiedono alcuna caratteristica che li renda in grado di visualizzare il relativo comportamento dinamico. Per tale proiezione esistono altri formalismi più efficaci (lo si sarà ripetuto a sufficienza?).

UML e ingegneria del software: dalla teoria alla pratica

77

In sintesi si può affermare che probabilmente non è il caso di perdere molto tempo nel disegnare raffinati e onnicomprensivi diagrammi dei casi d’uso, forse è più opportuno relegarli a una funzione di overview per dedicarsi con maggiore attenzione all’analisi del comportamento dinamico delle relative funzionalità. L’imperativo categorico è sempre lo stesso: ottimizzare il tempo — questa maledetta entità che incapsula le nostre vite — a disposizione. Probabilmente è opportuno non dimenticarsi mai che il tempo è una risorsa, sempre troppo limitata, e l’analisi dei requisiti utente è propedeutica alle restanti fasi del ciclo di vita del software: fintanto che il relativo documento non è pronto, diversi elementi del team corrono il rischio di rimanere in uno stato di stand-by per poi essere gravati da una elevata mole di lavoro per via dello scarso tempo a disposizione. Alcuni consigli potrebbero sembrare in contraddizione tra loro. Per esempio si può commettere l’errore di generare use case eccessivamente astratti così come l’opposto, ovvero use case privi di astrazione. Ciò non deve sorprendere, l’informatica insegna sempre a cercare il giusto mezzo. Per esempio si ambisce sempre a un disegno in grado di generare un sistema molto flessibile, salvo scoprire che un’eccessiva flessibilità possa condurre a un sistema in grado di fare nulla.

Eccessiva scomposizione Non è infrequente il caso di architetti che approdino allo UML dopo aver maturato esperienza e successi nell’utilizzo della metodologia di analisi nota con il nome di “analisi strutturata”, e tendano a riadattare al nuovo formalismo gli ormai consolidati schemi mentali. Ciò però non genera sempre buoni risultati. Spesso il vecchio modo di operare porta a organizzare gli use case come si faceva con i DFD: si realizza un primo use case molto generale, poi si produce tutta una serie di diagrammi che aumentano il dettaglio delle singole funzioni e si prosegue fino a giungere a use case abbastanza elementari o “atomici”. Sebbene ciò sia possibile, non sempre è auspicabile in quanto si tende a provocare diversi e ben noti inconvenienti, non ultimo l’inadeguato utilizzo del tempo a disposizione. In primo luogo si corre il rischio di far somigliare gli use case all’organizzazione del codice. La scomposizione funzionale porta a ragionare in termini di eccessivo dettaglio, dimenticando che non c’è alcun bisogno che la struttura interna (il disegno) del sistema assomigli a quella esterna. Si pecca di mancata astrazione, ci si lega immediatamente al “come” realizzare le funzioni (in una fase in cui tra l’altro non sia hanno ancora informazioni a sufficienza per le soluzioni), vincolando fortemente le successive fasi del ciclo di vita del software. Scomponendo ogni singolo use case, ottimizzandolo, conferendogli un opportuno aspetto grafico e così via, forse si utilizza male tempo prezioso che potrebbe essere sfruttato

78

Capitolo 3. Introduzione agli Use Case

proficuamente per le altre attività. Si ricordi che il prodotto della use case view è assolutamente propedeutico alle altre fasi del ciclo di vita software. L’esperienza insegna che verosimilmente nelle fasi iniziali è più importante analizzare i rischi più elevati, demandando i dettagli alle fasi successive. Quando l’autore iniziò a collaborare con una grande azienda londinese, alle sue affermazioni riguardo l’importanza di adottare gli use case nel contesto dell’analisi dei requisiti, si ribatteva che i clienti non erano molto contenti di utilizzare il formalismo dei casi d’uso. Ciò suonava molto strano: di solito i clienti sono felici di giocare con gli omini stilizzati. Visionando poi i diagrammi, l’abnorme quantità di più di qualche migliaio di casi d’uso facilitò decisamente la comprensione del sentimento di rifiuto provato dai clienti.

Mancanza di astrazione Frequentemente un fattore che causa non pochi inconvenienti nella produzione di sistemi software di qualità è la difficoltà di instaurare una piattaforma di conversazione tra il cliente e il personale tecnico. Spesso, al fine di circoscrivere questo fattore di rischio, si tenta di procedere nell’analisi dei requisiti fornendo al cliente opportuni prototipi del sistema finale. Ciò permette di concretizzare l’attenzione e di eliminare alla radice diversi malintesi. Si tratta di un escamotage che correttamente applicato può produrre i suoi buoni frutti, — a patto che i prototipi non diventino magicamente versioni definitive — ma che talune volte può invece generare diversi problemi. L’idea alla base è molto semplice: poiché esistono validi strumenti per realizzare in breve tempo una serie di interfacce utente, è possibile pensare di costruire un primissimo prototipo basato sulle sole schermate di interazione utente/sistema. Si tratta di un vero prototipo “usa e getta”, da utilizzarsi unicamente allo scopo di migliorare il processo di analisi dei requisiti del cliente. Logica conseguenza di ciò è che lo use case viene realizzato in base al prototipo e alle osservazioni scaturite dai colloqui con il cliente. I vantaggi prodotti da tale approccio sono essenzialmente la possibilità di condurre il cliente a ragionare su elementi concreti —forse —, di riuscire a isolarsi da dettagli, di delineare proiezioni meno astratte del sistema finale. Gli svantaggi possono essere diversi. In primo luogo si rischia di ingenerare nel cliente l’idea che il sistema sia ormai pressoché pronto e che quindi la relativa progettazione sia giunta alle fasi finali: parafrasando Stroustrup, “i prototipi possiedono il diabolico difetto di somigliare al sistema finale”. Mentre invece manca ancora tutto. A quel punto non c’è nulla da fare: tutti i tentativi di convincere il cliente del contrario tendono a cadere nel vuoto. Un altro inconveniente è che si rischia di legarsi troppo al prototipo, di non analizzare aspetti ritenuti di secondaria importanza, magari semplicemente perché non previsti dal prototipo realizzato in fasi ancora molto acerbe, e di sottostimare i tempi.

UML e ingegneria del software: dalla teoria alla pratica

79

Una buona idea potrebbe essere quella di presentare prototipi molto grossolani, cosicché anche il gradimento del sistema finale ne verrà favorito, eventualmente composti di soli disegni, magari realizzati per mezzo di carta e penna, o documento elettronico. Resta comunque forte il timore che, anche in questo caso, si possano trovare commerciali in grado di trasformarli magicamente, nel giro di pochi secondi, in prodotti finiti.

Eccessiva astrazione Tutti coloro che operano nel settore dell’Object Oriented in maniera seria (!?), prima o poi, tendono a diventare adepti della “setta dell’astrazione”: ogni cosa può essere “astratta”, anche i familiari. Schemi mentali siffatti sono più che positivi, a patto che non si sconfini in livelli di astrazione eccessivi: ciò nel contesto dei casi d’uso, e non solo, può generare non pochi problemi. Non va mai dimenticato che i principali fruitori della use case view sono i clienti, e pertanto, uno degli obiettivi fondamentali della vista è facilitare un sano scambio informativo tra cliente e team di analisti: non a caso si utilizzano gli scarabocchietti. I clienti molto raramente si trovano a loro agio quando il discorso si eleva sul piano dell’astratto, e quindi si corre il rischio di dover impiegare non poco tempo nel tentativo di illustrare, spiegare e argomentare le varie astrazioni. Ciò diventa ancora più drammatico quando, durante i meeting, non si parla in lingua madre. Pertanto, astrarre i diagrammi più di quanto sia effettivamente necessario potrebbe confondere inutilmente il cliente e, in ultima analisi, limitare o addirittura inibire, l’instaurazione di un dialogo costruttivo. La parola d’ordine per gli use case è sicuramente la concretezza: la mancanza di astrazione a questo livello non incide minimamente sul livello di astrazione delle restanti viste.

Ricapitolando… Il presente capitolo è stato dedicato all’illustrazione dei concetti forniti dallo UML per supportare il delicato processo dell’analisi dei requisiti utente: l’attenzione è stata pertanto focalizzata sulla prima vista dello UML: la use case view (vista dei casi d’uso). Obiettivo di questa fase, è descrivere quali servizi dovrà fornire il sistema senza specificarne il come. L’impegno è cercare di isolarsi, di astrarsi il più possibile da dettagli realizzativi il cui studio è demandato alle fasi successive. I processi di sviluppo del software più popolari incorporano approcci di tipo use case driven (guidati dai casi d’uso), per cui la vista dei casi d’uso diviene il punto di riferimento per tutto il ciclo di sviluppo del sistema, dalla progettazione allo sviluppo ai test. Uno degli obiettivi della use case view è quello di agevolare il processo di analisi dei famosi requisiti utente, ossia le richieste legittime del cliente, le sue elucubrazioni mentali, le distorsioni, le aspettative…

80

Capitolo 3. Introduzione agli Use Case

La proiezione statica della vista use case si basa sugli omonimi diagrammi frutto del lavoro svolto da I. Jacobson durante la sua collaborazione con la Ericsson nell’ambito dello sviluppo di un sistema denominato AXE. Visto il successo riscosso dai diagrammi e percepitene le potenzialità, i Tres Amigos hanno pensato bene di inglobarli nello UML. Gli use case diagram sono utilizzati per dettagliare le funzionalità del sistema, rappresentate graficamente attraverso elementi ellittici denominati anch’essi use case, dando particolare rilievo agli attori che interagiscono con esse. Da tener presente che i diagrammi dei casi d’uso permettono di analizzare la proiezione statica delle funzionalità del sistema mentre, per modellare quella dinamica, è possibile far ricorso agli interaction e activity diagram. In questa fase del ciclo di vita del software vengono utilizzati per rappresentare particolari istanze dei casi d’uso dette scenari. Semplificando al massimo il concetto, è possibile affermare la proporzione: gli scenari stanno agli use case come gli oggetti stanno alle classi. Il ricorso ai suddetti diagrammi, sebbene presenti diversi vantaggi attribuibili essenzialmente all’immediatezza della notazione grafica, può presentare alcuni inconvenienti come la difficoltà di manutenzione, la difficoltà di lettura al crescere delle entità coinvolte, ecc. Probabilmente opportuni template dei casi d’uso finiscono per essere lo strumento migliore: sono chiari e decisamente facili da manutenere. La vista dei casi d’uso è oggetto di interesse per un vasto insieme di figure professionali coinvolte nel ciclo di vita del sistema: dai clienti ai capi progetto, dagli architetti ai programmatori, ai tester, agli addetti al marketing, ai commerciali e così via. Una delle principali entità oggetto di studio nella use case view è l’attore, il quale 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 esterna, di un processo, o di qualsiasi cosa che interagisce con il sistema inviando e/o ricevendo messaggi: scambiando informazioni. Gli attori vengono rappresentati graficamente nello UML attraverso sagome di omini stilizzati (stick man). Nella realizzazione di use case diagram può verificarsi il caso in cui più attori presentino diverse e significative similitudini: per esempio interagiscano con un insieme di casi d’uso con le stesse modalità. In questi casi — come insegnato dai princìpi del paradigma Object Oriented — è 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. Ogni attore deve essere necessariamente connesso con un insieme di casi d’uso (funzioni del sistema) che ne ricevono gli stimoli e che producono i dati richiesti dall’attore stesso. Identificare gli attori del sistema è un’attività molto importante: si identificano le entità interessate a usare e interagire con il sistema e quindi i servizi di cui vogliono usufruire.

UML e ingegneria del software: dalla teoria alla pratica

81

Un caso d’uso 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, un caso d’uso è un tipo di Classificatore (UseCase eredita da Classifier) 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 ed un’altra interagente esterna (attore), sia dalla sequenza di azioni svolte. Ogni caso d’uso è pertanto definito da una sequenza di azioni (comportamento dinamico) che l’entità esegue interagendo con il relativo attore, necessarie per erogare un servizio. La generalizzazione (generalization) applicata ai casi d’uso rappresenta una relazione tassonomica tra un 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. Lo use case figlio eredita gli attributi, le operazioni, il comportamento del genitore, e pertanto può sovrascrivere (override) o aggiungere comportamento (attributi, operazioni…). Da notare che la presenza di una relazione di generalizzazione implica che l’esecuzione di use case “fratelli” può avvenire unicamente in alternativa. 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 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 include comporta l’inserimento della sequenza di azioni dello use case incluso in quella del caso d’uso base sotto il controllo e nella locazione specificata dello use case base. Una relazione di estensione (extend) è una relazione tra un caso d’uso estendente ed uno base che specifica come il comportamento definito dal primo (l’estendente) debba essere inserito nel comportamento di quello base. Spesso, le relazioni di estensione sono utilizzate per modellare delle parti di use case che rappresentano delle azioni 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. 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. Un’istanza di un caso d’uso (scenario) è l’esecuzione dello stesso, avviata dalla ricezione di un messaggio inviato da un’istanza di un attore. Come effetto dello stimolo, lo use case esegue la sequenza delle azioni da esso identificate, eventualmente scambiando messaggi con attori diversi da quello che ha prodotto lo stimolo iniziale. I diagrammi dei casi d’uso permettono di illustrare le singole funzionalità del sistema attraverso opportune relazioni tra use case al fine di rilevare, a grandi linee, la struttura statica degli stessi, evidenziando eventuali sottofunzioni di particolare importanza, men-

82

Capitolo 3. Introduzione agli Use Case

tre non possiedono alcuna caratteristica intrinseca che li renda in grado di rilevare il comportamento dinamico; è inoltre necessario ricorrere a diversi artifici per tentare di evidenziare eventuali condizioni anomale, non è possibile specificare le azioni da intraprendere per la relativa gestione, e così via. Come già evidenziato è possibile esprimere il comportamento dinamico dei casi d’uso sia attraverso il formalismo dei flussi di azione, sia utilizzando appositi diagrammi messi a disposizione dallo UML per la modellazione della componente dinamica del sistema (diagrammi di interazione, activity diagram, e così via). L’esperienza quotidiana suggerisce che lo strumento più opportuno da utilizzarsi per dettagliare il comportamento dinamico dei casi d’uso probabilmente rimane il buon vecchio template: semplice, veloce da realizzare e da far comprendere ai clienti, agevole da manutenere e così via. Una volta realizzato il modello della use case view è necessario procedere alla relativa fase di verifica che implica sostanzialmente due processi di test: verifica vera e propria e validazione. Il primo processo serve a confermare che la vista è stata realizzata correttamente e in accordo alle specifiche raccolte. Obiettivo della validazione è accertare che il sistema, così come lo si è progettato, sia effettivamente ciò di cui il cliente e gli utenti finali hanno bisogno. Spesso la realizzazione della use case view avviene con un tipico approccio top down: si realizza un primo use case molto generale, poi si produce tutta una serie di diagrammi che aumentano il dettaglio delle singole funzioni e si prosegue fino a giungere a use case elementari. Sebbene ciò sia possibile, non è sempre auspicabile in quanto tende a provocare diversi e ben noti inconvenienti: si rischia di far somigliare gli use case all’organizzazione del codice, si pecca di mancata astrazione, si spreca tempo prezioso nel realizzare diagrammi molto dettagliati, e così via. Un fattore che storicamente causa grossi inconvenienti nella produzione di sistemi software di qualità è la difficoltà di instaurare una piattaforma di conversazione tra il cliente e il personale tecnico. Per cercare di arginare questa lacuna, spesso si preferisce realizzare veri e propri prototipi “usa e getta”, da utilizzarsi unicamente allo scopo di migliorare il processo di analisi dei requisiti del cliente. Logica conseguenza di ciò è che lo use case viene realizzato in base al prototipo e alle osservazioni scaturite dai colloqui con il cliente. A fronte dei ben noti vantaggi (si riesce a condurre il cliente a ragionare su elementi concreti, a isolarsi da dettagli, a delineare proiezioni meno astratte del sistema finale, ecc.), gli svantaggi possono essere diversi; si rischia di ingenerare nel cliente l’idea che il sistema sia ormai pressoché pronto, di legarsi troppo al prototipo e quindi di non analizzare aspetti ritenuti di secondaria importanza magari semplicemente perché non previsti dal prototipo realizzato in fasi ancora molto acerbe, di sottostimare i tempi, e così via. Infine è importante tener presente che i clienti, molto raramente si trovano a loro agio quando il discorso si eleva sul piano dell’astratto e quindi probabilmente non è opportu-

UML e ingegneria del software: dalla teoria alla pratica

83

ni realizzare casi d’uso a elevato grado di astrazione: si corre il rischio di dover impiegare non poco tempo in lunghi meeting nel tentativo di illustrare, spiegare e argomentare le varie astrazioni. Pertanto, astrarre i diagrammi più di quanto sia effettivamente necessario potrebbe confondere inutilmente il cliente e, in ultima analisi, limitare o addirittura inibire, l’instaurazione di un dialogo costruttivo. La parola d’ordine per gli use case è sicuramente la concretezza: la mancanza di astrazione a questo livello non incide minimamente sul livello di astrazione delle restanti viste.

Capitolo

4

Modellazione avanzata degli Use Case Uno dei peggiori sprechi di energie che si possa produrre è realizzare un sistema, magari impeccabile dal punto di vista tecnico, che però semplicemente non è quello di cui aveva bisogno l’utente. Cliente: “Vorrei un mezzo di trasporto in grado di teletrasportare persone da Roma a NY in 5 minuti”. Architetto: “OK, si può fare… Sono necessari 103 anni-uomo e 1020 dollari”.

Introduzione Nel capitolo precedente sono stati illustratati in dettaglio i diagrammi dei casi d’uso e in particolare si è visto come siano lo strumento ideale per modellare la proiezione statica dei requisiti utente. Obiettivo del presente capitolo è illustrarne un utilizzo più operativo anche se, a volte, sensibilmente distante dalle direttive standard: ancora una volta il conflitto tra pratica e teoria. Modellare sistemi reali mette in luce una serie di problemi e lacune raramente trattati nei libri e nelle specifiche ufficiali dello UML. Nel presente capitolo viene riportato un esempio completo di modello dei casi d’uso di un sistema per il commercio elettronico. Logica conseguenza è che il capitolo risulta decisamente corposo… Il lato positivo è che non è assolutamente necessario studiare attentamente tutto l’esempio. Il pregio che rende particolarmente attraenti gli use case probabilmente risiede nel formalismo amichevole e nella notazione grafica quasi banale: tutto viene rappresentato — a meno di stereotipizzazioni — attraverso omini stilizzati, ellissi e opportune relazioni.

2

Capitolo 4. Modellazione avanzata degli Use Case

Il problema è che spesso tale pregio si tramuta in difetto. La relativa semplicità spinge tecnici a cimentarsi con la notazione dei casi d’uso senza possederne un’adeguata conoscenza. Il risultato è che si commettono svariati errori, ben noti. Esempio tipico è un utilizzo “procedurale”: si utilizza il formalismo dei casi d’uso, ma con una metodologia riconducibile alla famosa analisi strutturata. In altre parole si dà luogo a una decomposizione funzionale, si realizzano use case via via più raffinati fino a giungere a dettagli implementativi: in sostanza le descrizioni dei singoli casi d’uso risultano essere delle vere e proprie procedure, i vari use case vengono connessi a catena: use case A include use case B, che include use case C e così via. La principale anomalia generata da tale approccio distorto è relativa al fatto che si disegna il sistema con un formalismo inadeguato in una fase in cui non si dispone di una sufficiente conoscenza del sistema. Il formalismo dei casi d’uso, sebbene possa sembrare molto semplice o addirittura banale, richiede un adeguato studio teorico e molta sperimentazione pratica, al fine di riuscire a realizzare modelli validi di use case. I diagrammi dei casi d’uso permettono di illustrare le singole funzionalità del sistema, conferendo particolare risalto alla percezione che ne ha l’utente. Il problema però è che talune volte si tenta di utilizzare strumenti per usi non propri; nel caso degli use case, è diffusa l’opinione che essi siano in grado di catturare ogni dettaglio della vista dei casi d’uso. Nulla di più inesatto: infatti non possiedono alcuna caratteristica intrinseca che li renda in grado di rilevare il comportamento dinamico; è inoltre necessario ricorrere a diversi artifici per tentare di evidenziare eventuali condizioni anomale; non è possibile specificare le azioni da intraprendere per la relativa gestione, e così via. Una prima soluzione a questi inconvenienti è l’utilizzo della tecnica dei flussi di azione, così come prescritto dalle direttive dei Tres Amigos. Opinione comune in molti tecnici — compreso l’autore ovviamente — è che nelle pratica quotidiana anche questo formalismo 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, il tempo stimato per l’esecuzione dello use case e così via. Un’ulteriore alternativa potrebbe essere quella di ricorrere ad altri diagrammi messi a disposizione dallo UML per la modellazione del comportamento dinamico del sistema (diagrammi di sequenza, di attività, ecc). In sostanza si potrebbero utilizzare questi diagrammi, selezionando di volta in volta quello in grado di evidenziare più adeguatamente l’aspetto (il tempo, l’organizzazione il parallelismo, ecc.) ritenuto più importante per la particolare interazione attore/sistema oggetto di studio. Dovendo descrivere il comportamento dinamico di casi d’uso, il livello di astrazione dei diagrammi realizzati dovrebbe essere necessariamente molto elevato ricordando che l’obiettivo è descrivere cosa il sistema dovrà fare e non il come: bisogna impegnarsi a non guardare il sistema al suo interno.

UML e ingegneria del software: dalla teoria alla pratica

3

L’utilizzo dei diagrammi succitati però non solo non risolve tutte le lacune evidenziate per i flussi di azione, ma anzi aggiungerebbe altri inconvenienti, quali per esempio l’aumento significativo del tempo necessario per realizzare i diagrammi stessi, l’estrema difficoltà di manutenzione, ecc. 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. Ai lettori “l’ardua sentenza”.

Template In questa sezione viene presentato un primo esempio di template dimostratosi particolarmente efficace nella documentazione della proiezione dinamica degli use case. La versione illustrata è frutto di una lunga genesi costituita da continui miglioramenti resisi necessari in corso d’uso, sia per saturare le lacune evidenziate dall’utilizzo delle versioni precedenti, sia per includere suggerimenti ricavati dallo studio di vari testi (quali per esempio [BIB10]). Nel Capitolo 7 viene presentato un esempio di modello a oggetti del dominio atto a rappresentare formalmente l'organizzazione delle entità (classi) in grado di modellare la struttura del template. Pertanto, al termine dei paragrafi in cui esso è presentato, l'esame di tale modello potrebbe contruibuire a chiarire le idee e a risolvere eventuali dubbi. In prima analisi, il modello (template) riportato in template 1 può essere suddiviso in quattro macrosezioni: 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. 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. Durante le primissime fasi di studio dei requisiti utente e le relative stesure dei casi d’uso è piuttosto normale che non si abbiano le idee completamente chiare, spesso anche per via di un certo smarrimento tipico di alcuni clienti in merito alle funzionalità che il caso d’uso dovrà fornire, alle relative modalità, e quindi, in ultima analisi, ai suoi reali obiettivi. Spesso vi è una certa difficoltà iniziale nel definire i confini dei casi d’uso; ciò non deve preoccupare più di tanto in quanto di solito la versione definitiva di un use case è il risultato di un certo numero di revisioni. Anche a sistema rilasciato, si può assistere a devastanti variazioni dei casi d’uso: le temutissime change requests (cambiamenti delle specifiche).

4

Capitolo 4. Modellazione avanzata degli Use Case

Template 1 — Esempio di template

CASO D’USO: Codice

Nome del caso d’uso.

Data: Versione: 0.00.000

Descrizione: Priorità:

Descrizione generale del caso d’uso (scope). Indica la priorità attribuita al caso d’uso.

Durata: Attore primario:

Ordine di grandezza stimata della durata del caso d’uso. Nome

Attori secondari:

Interessi nell’esecuzione del caso d’uso Nome

Precondizioni: Garanzie: Avvio:

Interessi nell’esecuzione del caso d’uso Descrizione. Minime: descrizione. Successo: descrizione. Evento che innesca l’avvio del caso d’uso.

Scenario principale. 1.



2. 3.



4.



Primo scenario alternativo. 1.1.

Elenco delle azioni da eseguire come alternativa a quanto prescritto nel primo passo.

Secondo scenario alternativo. 3.1.

Elenco delle azi oni da eseguire come alternativa a quanto prescritto nel terzo passo.

Primo scenario di errore. 2.1.

Elenco delle azioni da eseguire nel caso in cui si verifichi una condizione di errore durante l’esecuzione del secondo passo.

Annotazioni. 4.

Annotazioni relative al punto 4 dello scenario principale.

Nella pratica si evidenzia l’esistenza di una certa proporzionalità tra il numero di versioni di un caso d’uso, le riunioni effettuate con i clienti e gli attori presenti presso la relativa organizzazione abilitati a esternare la propria opinione sul sistema. Al risultato andrebbe poi aggiunto qualche fattore correttivo da imputarsi ai commerciali presenti presso la propria società.

UML e ingegneria del software: dalla teoria alla pratica

5

La convenzione generalmente accettata per l’attribuzione del numero di versione a un caso d’uso è quella di fatto utilizzata nella gestione delle diverse versioni del codice sorgente, che prevede un formato del tipo 0.00.000. La cifra più significativa viene aggiornata solo nel caso in cui, tra una versione e quella successiva, si proceda alla completa riscrittura del caso d’uso. Le due cifre centrali si modificano a seguito di variazioni significative del comportamento del caso d’uso, come per esempio introduzione di passi aggiuntivi o eliminazione di altri presenti. Infine le ultime tre cifre si variano nel caso di correzioni (bugs fixing) e quindi per aggiornamenti di minor rilievo. Nella sezione dedicata alle informazioni generali è necessario specificare una breve descrizione del caso d’uso, la priorità (il che equivale a dire l’importanza), l’ordine di grandezza della durata dell’esecuzione dello stesso, gli attori divisi tra principali e secondari, le precondizioni, le garanzie e la causa innescante (il trigger).

Per ciò che concerne la descrizione è necessario riportare una breve illustrazione dei servizi offerti dal caso d’uso oggetto di studio. Probabilmente non è il caso di spendere molto tempo cercando di descrivere dettagliatamente l’intero use case: poche righe di descrizione riportanti le attività più significative, eventualmente corredate dalle relative condizioni di fallimento, nella pratica, si dimostrano essere più che sufficienti. La descrizione iniziale non è la sede più opportuna per dilungarsi: per la specifica di dettaglio è prevista la sezione dedicata agli scenari.

La priorità permette di evidenziare l’importanza assegnata alle funzionalità che il sistema dovrà fornire, al fine sia di suddividere gli use case in un’opportuna gerarchia di importanza, sia per poter assegnare in maniera più opportuna i vari casi d’uso alle fasi di sviluppo.

Poiché tipicamente il livello di importanza di una funzionalità è proporzionale al relativo rischio, la priorità finisce per fornire un criterio molto importante per il delicato processo di pianificazione delle iterazioni. Da una parte si vuole cercare di affrontare il prima possibile le parti con fattore di richio più elevato, ma dall'altra non e' opportuno concentrarle tutte in un'unica iterazione, in quanto si finirebbe per non avere più un processo iterativo ed incrementale, bensì un processo classico, caratterizzato da un rilascio molto importante e da qualche iterazione minore. Tipicamente le priorità vengono riportate in un’apposita tabella in un foglio elettronico separato, in cui l’identificativo delle varie righe dovrebbe essere dato al codice del caso d’uso. La priorità dovrebbe essere

6

Capitolo 4. Modellazione avanzata degli Use Case assegnata dal business analyst, rispecchiando le direttive del cliente. È buona prassi mediare tali priorità con un coefficiente a disposizione dell’architetto, sia perché ha una visione tecnica del sistema e delle dipendenze tra le varie parti componenti, sia perché dovrebbe essere in grado di effettuare le attribuzioni tenendo conto delle direttive provenienti dal processo di sviluppo utilizzato.1

Come ormai dovrebbe essere arcinoto, un attore è una qualsiasi entità, persona o sottosistema, interessato al comportamento del sistema in generale e di un insieme di use case in particolare. Tipicamente gli attori, nel contesto di uno stesso caso d’uso, vengono suddivisi in primari e secondari. Le peculiarità degli attori primari, molto brevemente, sono di fornire lo stimolo iniziale che avvia l’esecuzione del caso d’uso, e di fruire del relativo servizio. Sono quindi interessati a ricevere i principali messaggi inviati dal sistema, ossia i risultati dell’esecuzione del servizio. I servizi temporizzati costituiscono tipici casi d’uso non avviati esplicitamente da attori. L’avvio viene innescato automaticamente dal sistema stesso a un prestabilito istante di tempo. In tal caso, per rendere i casi d’uso più chiari e comprensibili, si ricorre all’artificio di considerare il tempo come un attore. In merito a questa soluzione si potrebbe aprire un dibattito: probabilmente, secondo le direttive standard dello UML il tempo non potrebbe essere considerato un vero e proprio attore; però, in casi particolari, questa soluzione permette di rendere diagrammi più chiari e comprensibili.

Pertanto, in tali contesti, la personificazione del tempo come attore è più che legittima. Importante è non esagerare iniziando a visualizzare come attore anche un “servizio schedulatore” che, evidentemente è un processo interno al sistema. Considerando attori funzionalità interne del sistema (come per esempio

1

Qualora si utilizzasse un processo di sviluppo di tipo iterativo e incrementale — sempre consigliato in quanto

permette di controllare i fattori di rischio del progetto — è necessario tener presente che ogni iterazione dovrebbe essere guidata da un gruppo ben definito di use case, o addirittura, da un insieme prestabilito di scenario. Per esempio non è infrequente il caso in cui in una determinata iterazione, verosimilmente una di quelle iniziali, si sia interessati a realizzare unicamente il best scenario (scenario in cui tutto funziona bene: si trascura momentaneamente la gestione delle anomalie) di un certo numero di casi d’uso. Ciò ha una sua logica: si è interessati a mostrare al cliente, il prima possibile, una versione del sistema, o per ottenere preziosi riscontri o semplicemente per provare che l’architettura progettata è efficace. Si capisce allora che il processo di assegnazione delle priorità degli use case è un’attività molto importante e finisce per influenzare la pianificazione delle iterazioni che, a sua volta, è un’attività molto sensibile in quanto ha a che fare con i rischi e il loro controllo.

UML e ingegneria del software: dalla teoria alla pratica

7

uno schedulatore appunto), si commette errore in quanto non si analizzano opportunamente servizi che dovranno essere stimati, progettati, implementati ecc. Sempre nel caso dello scheduler, mostrandolo come attore, non si attribuirebbe sufficiente enfasi alle funzioni di prenotazione di eventi, alle eventuali condizioni di avvio, alla relativa comunicazione, cancellazione e così via, che, viceversa sarebbe opportuno analizzare con un certo dettaglio.

Premesso ciò, è opportuno sottolineare che dal punto di vista della progettazione del sistema, ciò che interessa non è molto la ripartizione degli attori in primari o secondari (forse neanche tanto gli attori stessi) né tantomeno i nomi da attribuire ad essi (sebbene ciò possa far risparmiare molto tempo nelle riunioni con i clienti), bensì gli obiettivi che questi intendono raggiungere per mezzo dell’esecuzione del caso d’uso: la funzionalità da dover realizzare. Chiaramente è sempre conveniente scegliere nomi appropriati per le varie entità del sistema; in particolare, nel caso degli attori: i nomi permettono di sintetizzare descrizione del lavoro svolto, background e skill relativi, e così via. Una volta prodotto un caso d’uso, non è infrequente il caso in cui si scopra che magicamente la funzione descritta sia oggetto di interesse per una molteplicità di attori; si consideri per esempio il servizio di Ricarica account descritto in fig. 18. Ebbene questa dovrebbe essere eseguita dal commesso per ricaricare l’account del cliente, ma nulla vieta all’amministratore di espletare lo stesso servizio. Le precondizioni sono i requisiti che devono essere soddisfatti per poter eseguire il caso d’uso al quale si riferiscono: è compito del sistema verificarne l’adempimento prima di avviare l’esecuzione dell’efferente caso d’uso, mentre è responsabilità dell’utilizzatore assicurarne il soddisfacimento. L’esempio di precondizione più classico, quasi onnipresente nelle use case view, è l’ottenimento da parte dell’utente dell’abilitazione del sistema per l’esecuzione di funzioni considerate sensibili. La celebre frase cita letteralmente “precondizione: l’utente abbia eseguito il login e sia stato riconosciuto dal sistema”. Il riscontro pratico delle precondizioni è che l’implementazione dell’efferente caso d’uso preveda una serie di controlli iniziali atti a verificare appunto il relativo soddisfacimento. Si consideri ancora un qualsivoglia sito di e-commerce che offra ai propri utenti la possibilità di acquistare prodotti e/o servizi stando seduti comodamente nella ultracitata poltrona di casa. Si prenda in esame il caso d’uso in grado di effettuare un ordine che contempli prodotti precedentemente inseriti nel carrello della spesa. Le precondizioni per un caso d’uso del genere potrebbero essere che: 1. “l’utente abbia eseguito con successo il login e sia quindi stato riconosciuto dal sistema”;

8

Capitolo 4. Modellazione avanzata degli Use Case

2. “il carrello della spesa non sia vuoto”.

Un errore che spesso capita di commettere è confondere condizioni che solo in particolari casi devono essere esaudite, con le precondizioni che invece devono essere sempre soddisfatte.

Per ciò che concerne le garanzie è necessario citare esplicitamente sia quelle minime, sia quelle di successo. Le prime, come suggerisce il nome, sono le più basilari assicurate dall’esecuzione del caso d’uso. Tipicamente si tratta delle garanzie assicurate dal sistema nel caso in cui non sia possibile fornire il servizio specificato (scopo del caso d’uso non raggiunto) in quanto l’esecuzione è viziata da condizioni di errore o comunque da anomalie rispetto al flusso principale del caso d’uso (best scenario). In condizioni di fallimento nel conseguimento degli obiettivi del caso d’uso, le condizioni minime dovrebbero almeno prevedere il mantenimento di uno stato consistente: tipicamente quello immediatamente antecedente all’avvio dello use case.

Non è infrequente il caso in cui si compilino lunghe liste di condizioni minime. Un errore comune è quello di specificare le condizioni minime per ogni possibile fallimento dell’esecuzione del caso d’uso. Probabilmente non è il caso di dilungarsi e di creare problemi di sincronizzazione tra la lista delle garanzie e le possibili anomalie che possono insorgere nell’esecuzione di un caso d’uso: tutte le condizioni di eccezione hanno un insieme minimo comune di garanzie, pertanto è sufficiente e opportuno riportare unicamente tale insieme minimo.

Spesso per semplificare il compito, invece di riportare le garanzie minime, si sostituisce la dicitura con “garanzie in caso di fallimento”: entrambe sono legittime e la scelta deve ricadere sull’alternativa che fornisce un migliore grado di comprensione. Con riferimento al caso del sito di commercio elettronico, una garanzia minima dell’esecuzione della funzione di compilazione degli ordini potrebbe essere: “l’importo dell’ordine viene addebitato solo a seguito di totale convalida dello stesso”, mentre la relativa versione di garanzia in caso di fallimento potrebbe essere “l’ordine non viene preso in carico e nessun importo viene addebitato al cliente”. Specularmente a quelle minime, le garanzie di successo specificano ulteriori condizioni soddisfatte dal completamento dell’esecuzione del relativo caso d’uso, ossia dopo l’esecuzione dello scenario principale o al termine dell’esecuzione di una sua variante comunque di successo (flussi alternativi).

9

UML e ingegneria del software: dalla teoria alla pratica

Mentre nel caso delle precondizioni è compito dell’utilizzatore del caso d’uso assicurarle, nel caso delle garanzie è compito del sistema, qualora le prime siano soddisfatte, assicurarne il conseguimento. Come si può notare ciò permette di evidenziare e dividere nettamente le responsabilità tra le varie entità che partecipano a un caso d’uso (purtroppo non è sempre possibile richiedere lo stesso a talune organizzazioni…). Le garanzie di successo implicano il soddisfacimento di quelle minime con l’introduzione di ulteriori condizioni rese possibili dal raggiungimento di almeno uno degli obiettivi del caso d’uso.

Figura 1 — Schematizzazione degli scenario di un caso d’uso.

Scenario principale

Scenario alternativo

Scenario alternativo

Scenario di errore

Scenario di errore

10

Capitolo 4. Modellazione avanzata degli Use Case

Nel caso della compilazione di un ordine di acquisto in un sito di commercio elettronico, le garanzie di successo potrebbero prevedere l’accettazione e la presa in carico, da parte dell’organizzazione, dell’ordine effettuato con addebito del relativo importo all’utente. Per ciò che concerne il campo Avvio è necessario descrivere l’evento che determina l’inizio dell’esecuzione del sistema. Per esempio considerando il caso d’uso di interazione tra l’utente e un sistema ATM (il nostrano Bancomat), lo use case viene avviato a seguito dell’introduzione da parte dell’utente della relativa carta di credito. Prendendo in considerazione funzioni eseguite periodicamente dal sistema, l’avvio è generato dallo scadere di un determinato intervallo di tempo. Si consideri per esempio il sito per il commercio elettronico ed in particolare il caso d’uso che permette di inviare gli ordini effettuati dal cliente al legacy system dell’organizzazione. Nel caso in esame l’avvio del caso d’uso avviene o a intervalli di tempo prestabiliti: ogni n ore, oppure a orari ben stabiliti: alle ore 13 e alle 24, e così via. La sezione successiva del modello è dedicato agli scenari. Tipicamente si distinguono tre tipologie (fig. 1): 1. 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, ecc.; 2. 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. 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; 3. scenari di fallimento o di errore (failure scenario): gli scenari che specificano le azioni da intraprendere nel caso in cui nell’esecuzione delle azioni dello scenario 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”, ecc. verosimilmente esiste un flusso alternativo o di errore associato con la verifica negativa del test. Talune volte capita di non riuscire a distinguere chiaramente flussi alternativi da quelli di errore. Per esempio intervengono condizioni anomale che però possono essere gestite. Una regola semplice semplice — a volte banale e inutile — consiste nell’interrogarsi se il

UML e ingegneria del software: dalla teoria alla pratica

11

flusso oggetto del conteso generi o meno il fallimento dello use case. In caso di fallimento si tratta evidentemente di un flusso di errore, altrimenti di uno alternativo.

La descrizione dei flussi alternativi o di errore è molto importante e permette di evidenziare delle dinamiche che invece tenderebbero a essere rilevate solamente durante la fase di test. Qualora alcuni comportamenti non siano ben specificati o mancanti, è tendenza naturale di molti programmatori assumere comportamenti più consoni alle proprie esigenze di implementazione, di tempo, ecc.

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 relativo compimento, per poi fornire le condizioni anomale che possono intervenire e le azioni da intraprendere. Vi è una stretta similitudine con la pratica quotidiana 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.”. 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). Tale approccio è stato utilizzato nell’esempio relativo al sito per il commercio elettronico. 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. 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

12

Capitolo 4. Modellazione avanzata degli Use Case

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 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 iterazioni future quelli tralasciati. L’illustrazione degli scenari alternativi effettuata attraverso opportuni template evidenzia il grande vantaggio offerto da questo formalismo rispetto a quello grafico ove, tipicamente, è necessario disegnare un nuovo diagramma per ogni alternativa, il che richiede una quantità decisamente superiore di tempo per la realizzazione e rende difficoltosa la gestione delle relative modifiche. L’ultima sezione, del tutto opzionale, prevede di riportare eventuali annotazioni, che possono essere riferite sia all’intero use case, sia a singole azioni. Qualora si decida di ricorrere ai template, c’è da tener presente che molte persone ritengono tali modelli complicati, “fuorvianti” perché distolgono l’attenzione dai contenuti e così via. Probabilmente le stesse persone, dopo aver utilizzato un programma di video scrittura, lascerebbero tracce di bianchetto sullo schermo. Il vantaggio dei template è quello di conferire una migliore organizzazione alle informazioni, renderne più facile il reperimento, infondere maggiore coerenza alla descrizione dei casi d’uso ecc. Qualora, però le idiosincrasie dovessero rimanere, si consiglia di stampare le varie descrizioni senza i bordi tipici delle tabelle. Invece, per i nostalgici delle schede perforate, i bordi vanno benissimo… Anzi, magari si potesse fare a meno anche dei grafici.

Flussi alternativi e sottoflussi (subflow) Uno dei problemi storicamente più critici insito nei processi di sviluppo di sistemi informatici consiste nella adeguata comprensione delle esatte necessità dell’utente. L’avvento del formalismo dei casi d’uso, la cui escogitazione si deve prevalentemente al lavoro portato avanti da Ivar Jacobson (lo svedese dei Tres Amigos) tra la fine degli anni Ottanta e gli inizi degli anni Novanta, sembrerebbe aver contribuito in modo decisivo a risolvere il problema, sebbene il formalismo da solo non basti.

13

UML e ingegneria del software: dalla teoria alla pratica

L’esperienza dell’autore mostra che, benché la notazione dei casi d’uso sia piuttosto diretta e relativamente semplice, molte organizzazioni considerano il ricorso ai casi d’uso molto complicato, oneroso e difficile da capire e gestire. Dove risiederà mai il problema? La vocina interna suggerirebbe di cercare in diverse direzioni. La scarsa voglia di un aggiornamento non superficiale, ma anche il mettere tutto nero su bianco e la relativa necessità di chiarezza implica responsabilità che ben pochi si vogliono assumere; e cosa dire di coloro che fanno roccaforte dei quattro concetti da loro conosciuti? In ogni modo, la definizione iniziale del formalismo prevedeva quasi esclusivamente la notazione grafica, che, tra l’altro, contemplava un insieme piuttosto limitato di elementi per strutturare i casi d’uso. Ben presto però si capì che queste ellissi, troppo “vuote”, erano eccessivamente “leggere” per specificare le funzionalità che il sistema doveva prevedere: il solo nome non aiutava granché. Nella definizione iniziale del formalismo dei casi d’uso, dunque, il ruolo del comportamento dinamico era ridotto quasi al minimo, affidato a un testo generico. Attualmente, grazie anche ai template provvisti dal processo della Rational, si assiste al problema diametralmente opposto (fig. 2). In molte organizzazioni si tende a ridurre ec-

Organizzazione diagramma

Figura 2 — Il diagramma mostra una verosimile evoluzione dell’utilizzo tipico del formalismo dei casi d’uso, nell’arco del decennio (inizi degli anni Novanta a oggi), nelle organizzazioni informatiche.

Prima introduzione del formalismo

Tendenza non infrequente

Descrizione del comportamento

14

Capitolo 4. Modellazione avanzata degli Use Case

cessivamente la strutturazione del diagramma dei casi d’uso a favore della descrizione del relativo comportamento dinamico. Vi è un attore che interagisce con un caso d’uso in maniera piuttosto lineare, salvo però poi accedere alla descrizione dello stesso e trovarsi di fronte a decine di pagine di specifiche, in cui compaiono flussi alternativi, sottoflussi, e così via. A questo punto ci si interroga sulla necessità di disegnare il diagramma che perde completamente, o quasi, il suo valore. Da tener presente che la definizione di flussi alternativi, sottoflussi, ecc., all’interno della descrizione di un singolo caso d’uso non è perfettamente in linea con le direttive standard dello UML. I flussi alternativi, se non utilizzati per mostrare in maniera più elegante un costrutto if … then … else …, e quindi se eccedenti le 3-4 righe, dovrebbero essere evidenziati per mezzo di un ulteriore caso d’uso associato a quello di partenza tramite una relazione di extend. Per ciò che riguarda i sottoflussi, essi rappresentano veri e propri use case da associarsi a quello oggetto di studio per mezzo della relazione di include. A questo punto, le domande che potrebbero sorgere sono essenzialmente due: 1. perché potrebbe risultare importante non inglobare più casi d’uso in uno solo? 2. perché invece spesso si preferisce procedere con tale approccio? Per ciò che concerne il secondo interrogativo, la risposta potrebbe essere legata alla necessità di contenere il numero dei casi d’uso e la complessità dei relativi diagrammi. Uno dei principali motivi per cui è importante modellare risiede nella necessità di sostenere la comunicazione. Si tratta di un problema molto rilevante specie nelle fasi iniziali del ciclo di vita del software, ove gli interlocutori sono gli utenti, i quali, tipicamente, dispongono di un background profondamente diverso da quello dei tecnici.

Contenere la complessità dei diagrammi, pertanto, aiuta il processo di comunicazione. Da tenere presente che, sebbene il formalismo dei casi d’uso sia piuttosto accattivante, è comunque avvertito come estraneo dagli utenti.

Inoltre, disporre di una fitta rete di use case potrebbe generare non pochi problemi al processo mentale di ricostruzione della dinamica del servizio descritto: bisognerebbe interrompere più volte la lettura della descrizione di un caso d’uso per “saltare” a quella di un altro e così via. Infine, molto spesso gli utenti, che per definizione devono riesaminare i modelli dei casi d’uso, sono intimoriti da elevate quantità degli stessi.

UML e ingegneria del software: dalla teoria alla pratica

15

Tale abnorme proliferazione però, spesso è dovuta ad alcuni “modellatori” di casi d’uso, i quali dimenticandosi che questo formalismo non appartiene alla fase di analisi e disegno del sistema, danno luogo a vere e proprie attività di progettazione della struttura interna del sistema attraverso i casi d’uso. Comprese le ragioni per le quali alcune volte si tenta di inglobare la descrizione dei casi d’uso in uno solo, vediamo ora di capire perché ciò non sia auspicabile, rispondendo quindi alla prima domanda. In primo luogo si rischia di attenuare l’importanza della dichiarazione di pre- e postconditions. Dovendo soddisfare un gruppo di casi d’uso, queste dovrebbero necessariamente subire un processo di estrapolazione del “massimo comune divisore”: ossia si correrebbe il rischio di evidenziare solo pre- e postcondizioni condivise da tutti i casi d’uso, rinunciando al necessario dettaglio. Qualora, invece si decidesse di specificarle tutte le pre- e postcondizioni attraverso apposita lista, si correrebbe il rischio di generare confusione circa l’associazione di ciascuna di esse al relativo use case di appartenenza. Ciò rappresenterebbe un problema specie per le precondizioni che in ultima analisi costituiscono dei controlli da dover effettuare all’avvio del relativo servizio. Se invece si decidesse di ripetere la sezione di pre- e post-conditions per la descrizione di ciascun caso d’uso, verrebbe meno la necessità di raggrupparli in un unico documento/ modello. Inglobare descrizioni di casi d’uso, inoltre, non ne semplifica il riutilizzo e tantomeno permette di evidenziare importanti sezioni di comportamento condiviso. Doug Rosenberg e Kendall Scott — autori dei testi Applied Use Case-Driven Object Modeling (Addison-Wesley, 2001) e Use Case-Driven Object Modeling with UML (AddisonWesley, 2001) — asseriscono che “bisogna preoccuparsi qualora le descrizioni dei casi d’uso siano lunghe quattro pagine. Lo scenario principale dovrebbe essere lungo due-tre paragrafi al massimo. Ogni flusso alternativo dovrebbe essere di una-due frasi. Qualche volta può capitare di avere casi d’uso molto brevi, specie quando sono utilizzati come tessuto di collegamento, altre volte potrebbe esserci bisogno di use case di maggiori dimensioni. Ma è necessario utilizzare tecniche […] atte a raggruppare a fattore comune il comportamento condiviso per scrivere use case più concisi che meglio si prestano al riutilizzo”. Ancora, la priorità assegnata potrebbe non essere chiaramente scindibile tra i vari casi d’uso specificati all’interno di una stessa descrizione del comportamento dinamico, con conseguenze sul processo di decisione dei casi d’uso/scenari da incorporare in ciascuna iterazione. Per finire, inglobare i casi d’uso può complicare sia le attività di realizzazione dei test case, sia la pianificazione del processo (stima della complessità, delle risorse da allocare, ecc.) e così via. A questo punto cosa fare? Probabilmente la soluzione migliore consiste nel tentare di trovare “il giusto mezzo”, sebbene l’autore sia un sostenitore della antiglobalizzazione dei casi d’uso, come si avrà ben modo di costatare successivamente.

16

Capitolo 4. Modellazione avanzata degli Use Case

Sezioni aggiuntive Analizzando modelli, relativi alla descrizione del comportamento dinamico dei casi d’uso, presenti in varie organizzazioni informatiche, è possibile imbattersi in versioni con un numero di sezioni supplementari non sempre indispensabili. In questi casi, l’inutile e noioso lavoro aggiuntivo rende possibile intuire le ragioni dei tecnici che tendono a rinnegare i processi accademici di sviluppo del software.

Una delle sezioni a cui è possibile rinunciare senza troppo sacrificio è quella dedicata alla descrizione delle relazioni che coinvolgono il caso d’uso oggetto di specifica. Non solo si tratta di un gruppo di informazioni poco proficue (quindi trattasi di meri dati): è sufficiente ispezionare visivamente il diagramma dei casi d’uso per evincerle. Esse tendono anche a generare lavoro extra gratuito: ogni qualvolta si aggiornano delle relazioni nel diagramma (per esempio si decide che una determinata relazione sia più conveniente visualizzarla per mezzo di un extend anziché di un include) è necessario rivedere le varie descrizioni del comportamento dinamico dei casi d’uso.

Un gruppo di informazioni che invece può valere la pena di riportare è quello relativo ai cosiddetti requisiti speciali (special requirements). Si tratta di descrizioni testuali che raggruppano l’insieme di requisiti non funzionali che influenzano l’implementazione dello use case oggetto di specifica. Da tener presente che per questi dati tipicamente è previsto un opportuno manufatto, documento e/o foglio elettronico, denominato NFR (Non Functional Requirements, requisiti non funzionali).

Riportare gli NFR nel dettaglio degli use case soggetti a tali vincoli può risultare molto comodo, ma solo se il repository rimane il relativo documento e negli use case c’è un link alla sezione desiderata. Ciò al fine di minimizzare le ripercussioni dovute a variazioni dei requisiti non funzionali (individuare tutti gli use case in cui sono riportati tali requisiti, aggiornarli, ecc.).

Esempi tipici possono essere relativi all’area: • sicurezza: ¤ la parola chiave deve avere una dimensione superiore ai 6 caratteri e contenere caratteri “speciali”; ¤ la password non deve appartenere all’insieme delle ultime 4 utilizzate;

UML e ingegneria del software: dalla teoria alla pratica

17

¤ l’algoritmo di criptazione prevede chiavi asimmetriche di lunghezza 128 bit; • performance: ¤ l’utente non deve attendere più di x secondi per l’espletamento del servizio; ¤ in condizioni di regime il sistema deve essere in grado di supportare la connessione di y utenti contemporanei;

Un semplice processo per produrre la descrizione dei casi d’uso Di seguito illustriamo un processo essenziale, frutto dell’esperienza personale dell’autore, in grado di agevolare la produzione di appropriate descrizioni del comportamento dei casi d’uso. In effetti, è un’attività spesso non banale e, verosimilmente, non è del tutto corretto asserire che si tratti di una mera attività di descrizione (altrimenti sarebbero necessari “tecnici” dotati unicamente di skill linguistico, magari scrittori), bensì è necessario eseguire un vero e proprio processo di investigazione e scoperta dei requisiti utenti. Il processo, sinteticamente, si articola sui seguenti passi: 1. definizione del goal; 2. definizione delle precondizioni; 3. produzione del dettaglio dello scenario principale; 4. specificazione degli scenari alternativi; 5. inclusione delle rimanenti informazioni. In primo luogo è consigliabile definire chiaramente quale sia lo scopo dei casi d’uso che si vuole descrivere, ossia le post-conditions (alcuni esempi sono: autorizzare l’utente ad accedere al sistema, validare il carrello della spesa, eseguire un ordine, ecc.). Qualora anche questa attività dovesse presentare incertezze, sarebbe il primo campanello d’allarme che qualcosa sia stato definito in modo non accurato. Dichiarati gli obiettivi dei caso d’uso è importante iniziare a definire le precondizioni: è importante in quanto si tratta di requisiti che devono essere soddisfatti da tutti i flussi. Qualora, nel procedimento di individuazione e descrizione di flussi alternativi ci si dovesse accorgere che ciò non è vero, ci si troverebbe di fronte a due eventualità: 1. le precondizioni non sono state definite con molta precisione, e quindi è sufficiente correggerle;

18

Capitolo 4. Modellazione avanzata degli Use Case

2. i flussi rappresentano effettivamente casi d’uso diversi e quindi è necessario procedere con un’attività di ristrutturazione dell’organizzazione degli use case. Il passo successivo consiste nel dettagliare lo scenario principale. In sostanza si definisce come giungere all’obiettivo sancito dal caso d’uso nell’ipotesi in cui tutto funzioni meravigliosamente bene. Per questo motivo, spesso, ci si riferisce allo scenario principale con la definizione di happy days scenario (scenario dei giorni felici). Per esempio nello use case di login, nello scenario principale si potrebbe assumere che il profilo dell’utente esista già, la password inserita concordi con quella definita nel profilo, e così via. Qualora la descrizione dello use case sia troppo prolissa o, viceversa, eccessivamente breve, potrebbe essere opportuno verificare se sia possibile scomporre ulteriormente il caso d’uso (prima evenienza) estraendo comportamento comune o ripetitivo o, (seconda evenienza) se sia possibile inglobare lo stesso in altri casi d’uso, tenendo conto anche di quanto sancito precedentemente. In molti processi di sviluppo del software, si preferisce utilizzare un approccio iterativo e incrementale anche nella costruzione del modello dei casi d’uso. Invece di definire completamente ogni singolo caso d’uso prima di passare al successivo, si preferisce realizzare una certa percentuale di ciascuno di essi (magari solo lo scenario principale) al fine di produrre rapidamente una prima versione da rivedere con gli utenti. Approcci di questo tipo sono particolarmente utili qualora il sistema da realizzare preveda un numero non trascurabile di casi d’uso.

Definito lo scenario principale, altra attività importante consiste nell’individuare i flussi alternativi o di errore. Si tenga in mente che la presenza di questi flussi è del tutto normale, mentre la loro assenza potrebbe essere imputabile alla mancata individuazione di percorsi possibili.

Qualora esistano diversi flussi alternativi (flussi comunque in grado di conseguire l’obiettivo dichiarato) dovrebbe risultare abbastanza intuitivo utilizzare quello più frequente come flusso principale… Il condizionale è d’obbligo perché capita comunque di imbattersi in situazioni opposte.

Ogniqualvolta nel flusso principale siano presenti frasi del tipo “il sistema verifica”, “controlla”, “valida”, “si accerta”, ecc. la presenza di flussi alternativi diventa obbligatoria. Spesso si commette l’errore di non evidenziare questi verbi. Per esempio invece di affermare “il sistema tenta di reperire il profilo utente”, si asserisce “il sistema reperisce il profilo utente”. Frasi sì congegnate tendono a

19

UML e ingegneria del software: dalla teoria alla pratica rendere più difficile l’individuazione di flussi alternativi e/o di errore. In generale, un buon criterio per individuare i flussi alternativi consiste nello scorrere il flusso principale, punto per punto, e nell’interrogarsi se, per ciascuno di essi sia possibile che qualcosa non funzioni come ci si aspetti, che il sistema si comporti diversamente, che l’attore agisca in modo diverso da quanto sancito e così via.

Definiti anche i flussi alternativi e di errore, le rimanenti attività sono completamente volte all’individuazione delle restanti informazioni che, a questo punto, non dovrebbero più creare eccessivi problemi.

Esempio: Internet University Booking System In questa sezione viene fornito un esempio relativo a una parte di un sistema universitario atto a fornire a studenti e docenti universitari una serie di servizi fruibili attraverso un comune browser Internet. Obiettivo del “progetto” è realizzare sia un sito Internet/Intranet dinamico e interattivo, sia uno strato di comunicazione (wrapper) tra il sito stesso e il sistema “gestionale” dell’università (un’istanza di legacy system). In questo paragrafo, l’attenzione viene focalizzata sul sito Internet e in particolare sulla funzione di prenotazione sessioni di esami. Gli utenti (studenti universitari) collegati al sito universitario, dopo essere stati opportunamente riconosciuti dal sistema (attraverso la

Figura 3 — Use case prenotazione Internet sessioni d’esame.

Prenota appello

Studente Universitario

«extend»

«include»

[funzione abilitata]

Selezione posizione scaletta di accettazione

Verifica propedeudicita' «extend» [previsto vincolo di singola prenotazione per appello]

Verifica singola prenotazione per sessione

20

Capitolo 4. Modellazione avanzata degli Use Case

classica coppia login + password), possono consultare le varie sessioni di appello previste per gli esami appartenenti alla propria facoltà, ed eventualmente possono prenotarsi. Si inizi con il considerare lo use case diagram riportato nella fig. 3. che illustra graficamente la proiezione statica del caso d’uso per la prenotazione Internet degli esami universitari. Dalla relativa analisi è possibile evincere l’unico attore, chiaramente principale, (lo Studente Universitario), e le principali funzioni e la relativa organizzazione strutturale.

Si ricordi che probabilmente non è il caso di perdere troppo tempo nel realizzare diagrammi dei casi d’uso eccessivamente dettagliati. L’eccessivo dettaglio oltre a incidere negativamente sul tempo necessario per produrre la versione iniziale del diagramma, ne complica inutilmente la manutenzione e genera tutta una serie di effetti indesiderati, come il tentativo di definire l’architettura del sistema con strumenti sbagliati, vincolare improduttivamente le restanti fasi del processo e così via.

I diagrammi dei casi d’uso sono assolutamente soggettivi: la struttura evidenzia le “sottofunzioni” ritenute più significative dal progettista in funzione di quelle che sono le esigenze del cliente. Può capitare per esempio che un cliente pretenda di vedere visualizzato uno specifico use case, anche se dal punto di vista tecnico risulta assolutamente insignificante e non coerente… Come al solito bisogna mettere il padrone dove vuole l’asino (spesso è molto più razionale del contrario). Molto probabilmente, se si fornissero esattamente le stesse specifiche a dieci tecnici diversi, si otterrebbero altrettante diverse legittime versioni. Tuttavia, verosimilmente, sarebbe possibile evidenziarne alcune, per così dire, più corrette o almeno più convenienti di altre. Per esempio nello use case Prenota Appello visualizzato nella fig. 3 si è deciso di evidenziare le funzioni di Verifica propedeuticità, Selezione posizione scaletta di prenotazione e Verifica singola prenotazione per sessione. In altre parole si è deciso di evidenziare che lo studente per potersi prenotare a una determinata sessione di appello deve aver superato gli eventuali esami propedeutici a quello a cui si vuole iscrivere e, nei limiti legati alla disponibilità, può selezionare la posizione desiderata nella scaletta di accettazione. È altresì possibile evincere che per talune sessioni di esame, a discrezione dell’insegnante, è possibile iscriversi a uno solo degli appelli previsti nella relativa sessione (per esempio quando ne sono previsti diversi a scadenze piuttosto ravvicinate). Un servizio così concepito correrebbe il rischio di essere eccessivamente rigido e particolarmente odiato dagli studenti. Una soluzione conveniente potrebbe consistere nel dare

21

UML e ingegneria del software: dalla teoria alla pratica

Figura 4 — Use case eccessivamente dettagliato per la prenotazione automatica sessioni d’esame.

Verifica posizione amministrativa studente

Selezione materia d'esame

«include»

«include»

Prenota appello Studente Universitario

«include»

Verifica propedeuticita' «include» «include»

Reperimento propedeuticita'

«include»

Selezione posizione scaletta di accettazione

Selezione posizione specifica

Selezione sessione d'appello

Selezione posizione in coda

la possibilità agli studenti di effettuare prenotazioni con riserva. Questa tipologia verrebbe selezionata automaticamente dal sistema nel caso in cui la posizione amministrativa dello studente e/o la verifica delle propedeuticità non risultassero completamente in regola. Nel diagramma in fig. 4 viene illustrata la stessa funzionalità ma attraverso un diagramma dei casi d’uso decisamente più dettagliato. Sebbene esso sia a tutti gli effetti corretto è comprensibile che il relativo livello di dettaglio sia eccessivo. Per il proseguimento della trattazione si faccia riferimento al diagramma di fig. 3. A questo punto, definita la proiezione statica dello use case prenotazione Internet sessioni di esami è necessario passare allo studio del comportamento dinamico. Una prima alternativa potrebbe essere quella di utilizzare i diagrammi dello UML preposti per lo studio/documentazione degli aspetti dinamici del sistema. Ovviamente il livello di astrazione dovrebbe essere elevato al fine di risultare compatibile con i principali fruitori: gli utenti. Anche se i diagrammi mostrati di seguito non sono stati ancora oggetto di una presentazione organica, il relativo utilizzo, almeno in questa fase, è così intuitivo da non richiedere spiegazioni di dettaglio.

22

Capitolo 4. Modellazione avanzata degli Use Case

In particolare in fig. 5 è presentato un diagramma di sequenza (sequence diagram) che illustra un ipotetico scenario di successo per la funzione di prenotazione esami.

Figura 5 — Esempio di sequence diagram relativo unicamente allo scenario di successo della funzione di prenotazione esame.

:Sistema

:Interfaccia database server

:Studente Universitario 1. Esegue funzione

2. Ottiene dati sessione studente

3. Richiede dati studente (dettaglio) 4. Reperisce dati studente 5. Verifica posizione amministrativa studente

6. Richiede elenco esami 7. Reperisce elenco esami 8. Visualizza pagina "elenco esami" 9. Seleziona esame 10. Richiede propedeuticita' esame 11. Reperisce prop. esame 12. Richiede esami superati dallo studente 13. Reperisce esami superati dallo studente 14. Verifica rispetto propedeuticita'

15. Richiede dati sessioni d'appello 16. Reperisce sessioni d'appello 17. Visualizza sessioni previste 18. Seleziona sessione 19. Richiede dati appelli

20. Reperisce appelli

21. Visualizza elenco appelli

22. Seleziona appello

23. Richiede lista di accettazione 24. Reperisce lista di accettazione 25. Verifica rispetto singola prenotazione per appello

26. Visualizza ordine di accettazione

27. Seleziona posizione nella lista 28. Richiede memorizzazione prenotazione 29. Esegue prenotazione 30. Comunica esito prenotazione

UML e ingegneria del software: dalla teoria alla pratica

23

Molto brevemente, nella prima riga vengono riportati gli oggetti che partecipano alla realizzazione della funzione, l’asse verticale illustra il trascorrere del tempo e le varie frecce illustrano lo scambio di messaggi tra i vari oggetti. Il vantaggio offerto dai sequence, e dai diagrammi in generale, è l’essere particolarmente accattivanti grazie al formalismo grafico che ne facilita notevolmente comprensione e memorizzazione. Però, ahimè a fronte di questo vantaggio esiste una serie di svantaggi che probabilmente ne sconsigliano l’utilizzo a favore di strumenti più pragmatici. In primo luogo è necessario più tempo per produrli e il loro processo di manutenzione è piuttosto oneroso. Il diagramma presentato in figura illustra unicamente lo scenario di successo; oltre ad esso sarebbe necessario produrne diversi per illustrarne gli scenari alternativi. Volendo sarebbe anche possibile specificare i vari flussi alternativi nello stesso diagramma: sebbene ciò da un lato limiterebbe il numero di diversi diagrammi da dover realizzare, dall’altro renderebbe il diagramma decisamente più complicato e quindi più difficilmente interpretabile. Questo approccio è consigliabile qualora sia presente un numero limitato di flussi alternativi; eventualità che però non corrisponde al caso in questione. Tipicamente si preferisce realizzare lo scenario di successo e tanti altri diagrammi quanti sono gli scenari alternativi. In questo caso si capisce bene che una modifica allo scenario principale potrebbe ripercuotersi in tutti i rimanenti diagrammi. Un altro inconveniente è legato al fatto che tali diagrammi mostrano il comportamento dinamico di oggetti del sistema, quindi, anche se molto ma molto superficialmente, è necessario dare una prima “sbirciata” all’interno dello stesso. Probabilmente l’inconveniente principale rimane l’impossibilità di specificare ulteriori informazioni molto importanti quali precondizioni, garanzie, durata, ecc. Ovviamente nulla vieta di inserirle come note aggiuntive, nonostante il risultato sarebbe comunque meno formale e meno “standardizzato” e quindi completamente demandato alla buona volontà dei singoli tecnici. Per via di questi inconvenienti, nella pratica si preferisce illustrare il comportamento dinamico dei casi d’uso attraverso strumenti più vicini al testo. Come già menzionato si potrebbe ricorrere al flusso di azioni così come da direttive standard UML, sebbene però sia più agevole e frequente l’utilizzo di opportuni template. L’analisi del sequence diagram in fig. 5 potrebbe originare nei lettori più attenti un’obiezione degna di rilievo: se il sistema interagisce con un server, quest’ultimo allora dovrebbe essere un attore nel caso d’uso in questione. In uno “scenario” logico la risposta è sicuramente affermativa. Chiaramente nessuno vorrebbe replicare i dati di ciascun studente in più sistemi benché, tipicamente, i clienti risultino abbastanza restii nel concedere l’accesso diretto ai propri database. Sistemi del genere, progettati accortamente (!?), dovrebbero presentare diverse soluzioni di accesso (server socket, CORBA, EJB, COM+, ecc.). In casi — sempre frequenti — di progettazione non completamente lungimirante può capitare di dover interagire con sistemi legacy

24

Capitolo 4. Modellazione avanzata degli Use Case

(per esempio attraverso emulatore 3270) simulando la modalità dell’operatore (screen scrapling): si sottopone un opportuno comando al sistema, si attende la schermata di risposta, la si analizza e, se i dati forniti sono completi, si termina la transazione, altrimenti si prosegue con il successivo comando, si rianalizza la schermata di risposta e così via. Nel caso in questione invece si suppone di avere un database ridondante a disposizione del solo sistema Internet — non è assolutamente un caso improbabile — e quindi essendo parte integrante del sistema, il povero DB server non ha “dignità” di assurgere al ruolo di attore. Nella fig. 6 è riportato un esempio di activity diagram utilizzato per descrivere la funzione prenotazione esami. Si tenga presente che nella fase del processo di sviluppo relativa alla cattura dei requisiti utente spesso si assiste a una sorta di “guerra” tra gli utenti e il team tecnico e quindi tutti i mezzi che permettono di instaurare una valida piattaforma di dialogo sono benvenuti. Nella pratica — come sarà dettagliato nel capitolo successivo — molto spesso si rivela provvidenziale utilizzare i diagrammi delle attività per descrivere il comportamento dinamico dei casi d’uso per tutta una serie di motivi, tra i quali 1. trattandosi di un formalismo grafico ne condivide i vantaggi: la mente umana tende a comprendere e memorizzare una maggiore percentuale del contenuto informativo laddove presentato attraverso opportuno formalismo grafico; 2. visualizza in maniera inequivocabile la suddivisione di responsabilità tra le varie entità e le operazioni che eventualmente possono essere compiute in parallelo; 3. nelle fasi di “cattura” dei requisiti li si utilizza con un livello di astrazione molto elevato che ne evidenzia le affinità con il progenitore flowchart e anche il cliente più ignorante — informaticamente parlando of course — ha familiarità con tale notazione: chi non ha mai realizzato un flowchart in vita sua? A fronte dei succitati vantaggi, rimangono le condizioni sfavorevoli comuni alle varie notazioni grafiche 1. all’aumentare della complessità, i diagrammi tendono a diventare inevitabilmente confusi, nonostante il grande dispendio di tempo ed energia profuso dai realizzatori nel vano tentativo di rendere il tutto più lineare possibile; 2. problemi pratici di “visualizzazione” che vanno tenuti presenti: diagrammi di dimensioni medio-grandi sono difficili da stampare (spesso richiedono modelli A3 non facilmente reperibili);

25

UML e ingegneria del software: dalla teoria alla pratica

Figura 6 — Esempio di activity diagram della funzione di prenotazione esame.

Studente esegue servizio prenotazione appelli

Sistema

ottiene id. studente reperisce dati di dettaglio dello studente verifica posizione amministrativa [posizione amministrativa irregolare]

memorizza anomalia

[posizione amministrativa regolare]

reperisce elenco esami visualizza elenco esami seleziona esame valuta selezione [termina funzione] [selezionato esame]

reperisce propedeuticita' esame [lista propedeuticita' vuota] [lista propedeuticita' non vuota]

reperisce esami sostenuti dallo studente verifica rispetto propedeuticita' [propedeuticita' non rispettate]

memorizza anomalia

[propedeuticita' rispettate]

reperisce sessioni d'appello [sessioni di appello non reperite] [sessioni di appello reperite]

visualizza messaggio di errore

visualizza sessioni d'appello seleziona sessione d'appello valuta selezione [nessuna sessione selezionata] [sessione desiderata selezionata]

reperisce appelli visualizza appelli seleziona sessione d'appello valuta selezione [nessuna sessione selezionata] [sessione desiderata selezionata]

verifica vincolo singola prenotazione [(vincolo singola prenotazione presente) & (prenotazione dell'appello selezionato gia' effettuata)]

visualizza messaggio di errore reperisce elenco prenotazioni visualizza elenco prenotazioni seleziona posizione lista prenotazione valuta selezione [nessuna posizione selezionata] [posizione selezionata]

blocca posizione richiesta [posizione non piu' disponibile] [(posizione amministrativa corretta) & (vincolo propedeuticita' soddisfatto)]

effettua prenotazione

[posizione richiesta disponibile] [(posizione amministrativa non corretta) or (vincolo propedeuticita' non soddisfatto)]

effettua prenotazione con riserva

visualizza esito prenotazione

26

Capitolo 4. Modellazione avanzata degli Use Case

3. tipicamente richiedono un periodo di tempo maggiore e talune volte decisamente spropositato per la manutenzione, tempo peraltro amplificato poi dall’aumentare della complessità degli stessi. Nella pratica il formalismo che alla fine risulta più conveniente è decisamente quello dei template, di cui nelle pagine seguenti viene riportata l’applicazione al caso in esame.

Template 2 CASO D’USO: UC_IUBS03 Descrizione:

Prenotazione automatica sessioni d’esame.

Data:

10/01/2001

Versione: 0.01.002

Priorità:

Consente agli studenti universitari, preventivamente autorizzati, di visualizzare le sessioni di esami previste per la materia di interesse ed eventualmente prenotarsi. L’accettazione incondizionata della prenotazione è vincolata al rispetto di eventuali vincoli di propedeuticità previsti dai singoli esami e dalla regolarità posizione amministrativa. Elevata.

Durata: Attore primario:

Minuti. Studente universitario. Ha interesse nel ricevere informazioni relative alle sessioni di appello previste per gli esami relative al proprio corso di studi ed eventualmente, ha interesse nel prenotarsi per gli appelli previsti per specifici esami.

Attori secondari:

Amministratore del sistema Viene informato qualora si verifichino gravi condizioni di anomalie del sistema.

Precondizioni:

Lo studente deve essere stato preventivamente riconosciuto dal sistema nel corso dell a sessione. È disponibile il profilo dello stesso (amministrativo e studi). Minime: La sessione avviata dello studente rimane valida. Successo: Lo studente prenota la sessione di appello desiderata.

Garanzie:

Avvio:

Lo studente richiede espli citamente l’esecuzione del servizio di prenotazione sessioni di esame.

UML e ingegneria del software: dalla teoria alla pratica

27

Template 3

Scenario principale. 1. 2.

Sistema: Reperisce dati studente. Sistema: Verifica posizione amministrativa studente.

3. 4.

Sistema: Reperisce le materie relative alla facoltà/corso a cui lo studente risulta iscritto. Sistema: Visualizza l’elenco delle materie precedentemente reperite.

5. 6.

Studente: Selezione la materia di interesse. Include(Verifica propedeuticità).

7. 8.

Sistema: Reperisce l’elenco delle sessioni di appello previste per la materia selezionata. Sistema: Visualizza l’elenco delle s essioni di appello reperite.

9. 10.

Studente: Selezione la sessione ritenuta soddisfacente. Sistema: Visualizza i diversi appelli previsti per la sessione.

11. 12.

Studente: Selezione l’appello desiderato. Punto di estensione: Verifica singola prenotazione per sessione. Condizione: L’appello prevede il vincolo di singola prenotazione all’interno di una stessa sessione Sistema: Reperisce l’elenco delle prenotazioni per la sessione prescelta.

13. 14. 15. 16.

Sistema: Visualizza l’ordine di accettazione visualiz zando unicamente quali “posti” sono disponibili e quali no. Studente: Seleziona ordine di accettazione (in coda o posizione specifica)

17.

Punto di estensione: Selezione ordine di accettazione. Condizione: Funzione abilitata Sistema: Effettua prenotazione.

18. 19.

Sistema: Comunica avvenuta prenotazione. Sistema: Termina l’esecuzione con successo.

28

Capitolo 4. Modellazione avanzata degli Use Case

Template 4 I scenario alternativo. 2.1. 2.2.

Sistema: La posizione amministrativa dello studente non risulta in regola. Sistema: Memorizza condizione di riserva.

II scenario alternativo. 6.1. Sistema: La propedeuticità prevista dall’esame non è rispettata dagli sostenuti dallo studente. 6.2. Sistema: Memorizza condizione di riserva.

esami

III scenario alternativo. 17.1. Sistema: Presenti condizioni di riserva nella sessione. 17.2. 17.3.

Sistema: Effettua prenotazione con riserva. Sistema: Comunica allo stude nte la tipologia della prenotazione e le cause.

IV scenario alternativo. 17.1. Sistema: Fallita prenotazione per conflitto di posizione prescelta. 17.2. 17.3.

Sistema: Comunica situazione anomala. Sistema: Riprende dal punto.

I scenario di errore. Punti: 5, 9, 11, 15. 5.1. Studente: Seleziona la terminazione dell’esecuzione del caso d’uso. 5.2. Sistema: Termina l’esecuzione del caso d’uso con insuccesso. II scenario di errore. 7.1. Sistema: Sessioni di appello per la materia selezionata non disponi bili. 7.2. 7.3.

Sistema: Comunica allo studente l’assenza delle sessioni di appello. Sistema: Termina l’esecuzione del caso d’uso con insuccesso.

III scenario di errore. 12.1. Sistema: (Prevista singola prenotazione per appello nell’ambito della stessa sessione) e (lo studente risulta già prenotato ad un appello per la materia nella sessione corrente). 12.2. 12.3.

Sistema: Comunica l’inibizione della funzionalità allo studente. Sistema: Propone la schermata di cui al punto 4.

Annotazioni. 14. Per questioni di tutela della privacy, la funzione di visualizzazione della prenotazione dell’ordine di accettazione dovrebbe visualizzare unicamente le posizioni ancora disponibili e quelle già riservate senza fornire informazioni relative all’identità degli studenti prenotati.

UML e ingegneria del software: dalla teoria alla pratica

29

Dall’esame dello use case si può notare che in svariati punti la fruizione del servizio può fallire per problemi di carattere tecnico e non per questioni relative all’applicazione delle regole di business. Per esempio il sistema può fallire nell’ottenere una sessione di connessione al database necessaria per ottenere i dati relativi alla situazione amministrativa dello studente, nel reperire l’elenco degli esami previsti, la stessa connessione al database potrebbe non funzionare, ecc. In breve possono verificarsi eccezioni per così dire di sistema.

La domanda è se è il caso o meno di inserire eccezioni di sistema nella descrizione dei casi d’uso. La risposta dell’autore è: probabilmente no. Finirebbe unicamente per rendere i casi d’uso più complessi, pesanti da leggere, ecc. senza peraltro fornire alcuna informazione. Il consiglio pertanto è di non inserire questi casi (a meno di situazioni veramente particolari da trattare in maniera “non standard”) nei casi d’uso e prevedere un documento incluso nel modello dei requisiti utente in cui specificare il trattamento di questa tipologia di eccezione. Tale documento si presterebbe a essere rielaborato nelle fasi più tecniche con una sezione relativa alle direttive per il programmatore.

Come si può notare, gran parte della descrizione del comportamento dinamico del caso d’uso è stata assorbita dalla descrizione dell’utilizzo della GUI. Diversi autori consigliano di non perdere troppo tempo in dettagli di questo tipo e di demandarli al relativo modello/prototipo, qualora presente. Chiaramente è sempre consigliato realizzare qualche forma di navigazione della GUI (da quella completamente automatica detta anche prototipo a quella rappresentata per mezzo di tabelle di un qualsivoglia editor grafico) poiché semplifica il processo di individuazione dei casi d’uso e di flussi alternativi. Altro processo che risente positivamente del prototipo della GUI è quello di verifica: qualora la descrizione di un use case non dovesse essere consistente con la descrizione della relativa GUI, probabilmente qualche problema potrebbe sussistere.

L’autore è dell’opinione che la specifica delle azioni che un utente tipico esegue interagendo con le schermate del terminale aiuti ad accrescere il livello di comprensione della percezione del punto di vista dell’utente. Non è opportuno tuttavia riferirsi a dettagli, per così dire, “cosmetici” della GUI: l’utente seleziona la materia dalla relativa lista video, preme quindi il pulsante per la visualizzazione della finestra relativa alla descrizione di dettaglio, ecc.

Template 5

30

Capitolo 4. Modellazione avanzata degli Use Case

Template 5 Requisiti speciali. 1. In condizioni di massimo utilizzo, il servizio di prenotazione dovrebbe essere fruito, contemporaneamente, da 80/100 studenti. In condizioni di regime il numero si dovrebbe ridurre a 20/30 unità. 2. In numero di conflitti, dovuti a studenti impegnati a realizzare una prenotazione per lo stesso appello relativo ad una specifica materia di esame, non dovrebbe mai eccedere le 10 unità.

Di questo avviso non sono tutti coloro che, non riuscendo mai a dire un loro parere, visto il relativo livello di “professionalità”, si prodigano in guerre di religione, degne delle crociate, sulle interfacce utente (colore dell’etichetta, dimensione del tasto, ecc.). In sintesi, la descrizione dell’interazione utente/GUI semplifica la comprensione della prospettiva del sistema posseduta dallo stesso. Volendo incorporare i cosiddetti requisiti “speciali”, si potrebbe riportare una tabella come il template 5.

Dichiarazione di include ed extend nel template Si vedranno ora le convenzioni di notazione utilizzate all’interno del template dei casi d’uso per evidenziare dichiarazioni di inclusione e di estensione. Tali convenzioni possono essere estese a qualsiasi altro tipo di documento. In primo luogo, per entrambi i casi si tratta di un’operazione che può essere esclusivamente a carico del sistema — non avrebbe molto senso evidenziare inclusioni ed estensioni del comportamento degli attori — pertanto è inutile riportare la dichiarazione che è il sistema a eseguire il punto (Sistema :). Per quanto concerne le clausole di inclusione c’è ben poco da dire: è sufficiente riportare nel punto desiderato la dichiarazione include, magari in corsivo per conferire più enfasi, con il caso d’uso incluso racchiuso tra parentesi tonde: include () Per ciò che attiene la relazione di estensione, la situazione è più complessa per tutta una serie di motivi, quali: • l’estensione avviene sotto il controllo di una condizione; • un caso d’uso estendente può estenderne svariati in diversi punti; • un singolo caso d’uso può essere esteso negli stessi punti da diversi casi d’uso.

UML e ingegneria del software: dalla teoria alla pratica

31

Le ultime due argomentazioni possono essere sintetizzate riportando che esiste una relazione n a n tra i punti che possono essere estesi di un caso d’uso esteso e i segmenti che ne specificano il comportamento nei casi d’uso estendenti. La prima considerazione genera il problema di quale use case (esteso o estendente) debba ospitare la condizione di estensione. La risposta corretta sarebbe: nessuno dei due. Si tratta infatti di una condizione relativa alla relazione stessa e non ai singoli casi d’uso. Pragmaticamente però, è conveniente specificarla nel caso d’uso esteso, ciò perché uno stesso caso d’uso estendente può estenderne svariati in funzione di diverse condizioni. Quindi inserire tale clausola nello use case esteso può dare luogo a ripetizioni (più use case estesi da uno stesso in funzione della medesima condizione) e può dar luogo a noiosi elenchi di condizioni (più use case ne estendono uno nello stesso punto, quindi per ogni relazione di estensione va riportata la clausola) però si tratta di una tecnica consistente. La circostanza in cui uno stesso use case estendente ne estende altri in diversi punti si risolve dichiarando: 1. all’interno dei casi d’uso estesi, uno specifico nome (magari riportato ancora in carattere corsivo per evidenziarlo) per ogni punto il cui comportamento può essere esteso. Chiaramente tale nome deve essere univoco all’interno di ogni caso d’uso. Inoltre, come illustrato poc’anzi, è opportuno riportare anche la condizione: Punto di estensione: Condizione: 2. all’interno dei casi d’uso estendenti, per ogni segmento definito, il punto dello use case esteso: Punto di estensione: Quindi, se la condizione risulta soddisfatta, si può immaginare che nello use case esteso vengano sostituiti tutti i punti di estensione dichiarati, con i segmenti corrispondenti nel caso d’uso estendente (l’abbinamento avviene attraverso il parametro ). Per quanto concerne la relazione di generalizzazione il discorso è abbastanza semplice. Lo use case che specializza quello base deve menzionare tutti i punti in cui il comportamento “base” va specializzato, eventuali punti aggiuntivi, punti da non eseguire, ecc.

Esempio: sistema di banking Di seguito viene preso in esame lo studio di un’infrastruttura per il supporto dell’area finanziaria (finance) di una banca (investment bank and market). L’autore si scusa se le varie parti presentate in questo paragrafo appaiono inquadrate in maniera non completa in

32

Capitolo 4. Modellazione avanzata degli Use Case

un contesto difficilmente riconoscibile. La ragione di certe reticenze esiste: come al solito incombono vitto e alloggio a spese del Governo, e inoltre, trattandosi in questo caso di quello britannico, la qualità delle due componenti è rispettivamente scarsissima e scarsa, e il tutto assume tinte decisamente più tragiche del solito… Prima di addentrarsi nell’esame dei vari casi d’uso selezionati si ritiene opportuno presentarne brevemente l’ambiente. Tipicamente le strutture informatiche di sistemi complessi come quelli bancari/di investimento prevedono una serie di sottosistemi, ognuno specializzato nella erogazione di un insieme ben definito di servizi, che cooperano (scambiandosi messaggi) al fine di raggiungere l’obiettivo comune di fornire determinati servizi (situazione del sistema di sistemi esaminata nel Capitolo 2 UML: struttura, organizzazione, utilizzo). Esempi tipici di sottosistemi (fig. 7) che si possono trovare in un sistema bancario sono quello Internet (il famoso .com), il sottosistema specializzato per la gestione del front office (sportello), quello demandato all’accounting, altri finalizzati alla gestione degli investimenti e relativi controlli, quello dedicato alla sicurezza, quello atto a controllare che il sistemi funzioni correttamente (health monitoring), altri ancora dedicati alla gestione in tempo reale dei dati delle quotazioni di mercato dei prodotti finanziari e così via. L’interconnessione tra i vari sistemi è ottenuta di solito attraverso opportuni middleware indicati con l’acronimo MOM (Messaging Oriented Middleware, sistemi orientati alla gestione della messaggistica). L’obiettivo è disaccoppiare il più possibile i vari sistemi: una volta il problema era a livello di classi o al più di package, ma oggigiorno il livello di astrazione è in crescita continua. Al momento in cui questo libro viene redatto si assiste sempre più frequentemente all’utilizzo di MOM di nuova generazione basati su architetture JMS come da specifiche Sun (Java Messaging System, sistema di messaggistica Java), in grado di funzionare sia in modalità point to point (PTP, punto a punto), sia in publish and subscribe (Pub&Sub, pubblica e sottoscrivi). Secondo questo scenario, il bus di comunicazione (MOM) viene ripartito in una serie di canali logici in funzione della tipologia di messaggi da scambiare (dati dinamici, dati statici, dati necessari per fini statistici, dati riportanti segnalazioni di anomalie, ecc.). Un sistema per emettere/ricevere messaggi deve necessariamente e preventivamente registrarsi (subscribe) al o ai canali sui quali intende, rispettivamente, trasmettere (publish) o ricevere. Come evidenziato in figura, l’attuazione di architetture di questo tipo prevede la necessità di adattare legacy system di vecchia concezione ad architetture più moderne. Il problema viene affrontato cercando di incapsulare il sistema stesso in un apposito layer (wrapper, ancora una volta) in grado di comunicare con i due mondi: MOM da una parte e legacy system dall’altra.

33

UML e ingegneria del software: dalla teoria alla pratica

Figura 7 — Frammento di un esempio di architettura di un sistema bancario. Nello scenario mostrato, alcuni sistemi risultano ex-novo (Static Data Manager, Market Data System, Banking on-line) e quindi sono realizzati direttamente per dialogare via MOM con altri sistemi. Gli altri, essendo di vecchia generazione (Investment Management System, Exception Management System, Accounting System, Data Warehouse) necessitano di un apposito strato di wrapping.

System healt monitoring

Banking on-line

Accounting System

Data Warehouse

Security Manager

Messaging services JMS

MOM JMS Messaging services

Static Data Manager

Investiment Management System

Exception Management System

Market Data System

Legacy System Wrapper

Altri sistemi invece (come per esempio quello di banking online) essendo di nuova generazione, dovrebbero venir progettati per poter naturalmente interagire con middleware di comunicazione e quindi non hanno bisogno di particolari strutture di wrapping. Analizzando gli attori che interagiscono con i vari sottosistemi, potrebbe nascere un dilemma. Per ciò che concerne gli utenti veri e propri, non ci sarebbe nulla di nuovo, ma per quanto riguarda l’interazione con altri sottosistemi, potrebbe nascere il dubbio se visualizzare unicamente il MOM come attore “tecnologico” oppure, di volta, in volta menzionare i vari sottosistemi. L’alternativa ritenuta più corretta dall’autore è la seconda. In primo luogo perché l’attore MOM sarebbe troppo legato allo spazio delle soluzioni e poco a quello del problema:

34

Capitolo 4. Modellazione avanzata degli Use Case

si tratta di un meccanismo di comunicazione e probabilmente sarebbe come dire che il modem o il telefono potrebbero, a loro volta, assurgere al ruolo di attori. Trattandosi poi di un attore unico “scudo” di ciascun sottosistema, quindi assolutamente privo di “volontà e necessità proprie”, renderebbe più difficile inquadrare i servizi in un contesto organico di cooperazione tra sottosistemi. Un’altra argomentazione contraria al MOM come attore è relativa al fatto che l’utente medio, che, per definizione, non dispone di conoscenze informatiche, tende a non comprendere dettagli di soluzioni tecnico-architetturali e quindi ad aver problemi nell’identificare il MOM come attore.

Wrapper sistema di gestione dei trade Si cominci con l’analizzare un frammento di un dispositivo di wrapper atto a gestire la manutenzione dei dati statici nel sistema di gestione dei trade (Trade Management System, TMS). Compito della sezione del wrapper preso in esame è ricevere i dati statici e incorporare gli aggiornamenti nel TMS. Analizzando lo schema dell’architettura (fig. 7), è possibile evidenziare un sistema denominato Static Data Manager (Gestore dei Dati Statici), il cui compito è assicurare l’uniformità dei dati, definiti tecnicamente statici, utilizzati dall’intero sistema. Esempi di dati statici sono le informazioni relative alle “controparti” (entità che prendono parte in un trade, acquistano e/o vendono prodotti finanziari), alle valute (Euro, Sterlina, ecc.), alle istruzioni per i “settlement”, ai prodotti finanziari, ecc. In particolare, lo Static Data Manager, tipicamente prevede tre macro componenti: 1. un’interfaccia utente (tipicamente web-based), atta a consentire all’utente la gestione dei dati statici (presentation layer); 2. un middle-tier di comunicazione con le tabelle del database. Espone una serie di servizi atti a reperire i dati da mostrare nell’interfaccia utente e conseguentemente per memorizzare dati nelle tabelle (business logic); 3. un sistema di messaggistica atto a pubblicare i dati, attraverso opportuni messaggi immessi sul MOM, in risposta agli aggiornamenti effettuati. Per lo scambio dei messaggi si preferisce ricorrere a formati non proprietari, quali per esempio XML (al momento in cui viene scritto il libro è a tutti gli effetti lo standard de facto) per consentire a sistemi utilizzanti tecnologie diverse di comunicare tra loro. Ciò implica la necessità però di effettuare il parsing e la conversione dello stesso in una forma più vicina al sistema (oggetti opportunamente relazionati). A questo punto si consideri la porzione del wrapper che consente di aggiornare i dati relativi ai libri (book) del sistema finanziario.

35

UML e ingegneria del software: dalla teoria alla pratica

Brevemente i book sono le più piccole unità di un’organizzazione finanziaria, utilizzate per organizzare i trade. Ognuno di essi appartiene a una sottoorganizzazione (Processing Organization, specializzazione di una LegalEntity) dell’istituto finanziario, sita in una città ben definita. Questa associazione è molto importante perché permette sia di stabilire molte informazioni, quali l’orario della chiusura delle attività finanziare della città di riferimento e quindi l’orario di avvio di opportune procedure batch (come per esempio rivalutazioni di fine giornata, end of day revaluation), il calendario delle festività (Holiday Calendar) la cui conoscenza permette di evitare che specifiche date (maturazione trade) cadano in giorni festivi, ecc. Da tener presente che non sempre le Processing Organisation utilizzano la valuta della città/nazione in cui sono ubicate e che il calendario delle festività di una determinata città, non coincide con quello della nazione di appartenenza (si considerino, per esempio, le feste patronali). Per ciascun Book è anche necessario poi specificare la valuta base di riferimento. Figura 8 — Frazione del modello a oggetti del dominio relativo al book.

City - id : String * - name : String - description : String ...

LegalEntity is located 1

...

Currency - isoCode : String - value : float - name : String - decimal : byte - symbol : char ...

*

... s up gro

*

1

Book

Trade

- id : String

1

*

- endOfDay : Time ...

Holiday - holidayDate : Date - name : String

includes

1

*

1 ...

holds

1

*

- id : String - date : Date - time : Time ... ...

...

HolidayCalendar

...

1

*

has base currency - name : String

...

- id : String - description : String ...

- id : String - name : String - description : String ...

...

lt fau de sa a h

observes

AccountingBook

- id : String - name : String - description : String ...

36

Capitolo 4. Modellazione avanzata degli Use Case

Sebbene il formalismo del diagramma delle classi mostrato (fig. 8) non sia stato ancora illustrato in dettaglio, il suo significato dovrebbe essere sufficientemente chiaro. Si tratta di un frammento (quello relativo ai book ovviamente) di un modello del dominio. Su questo argomento si avrà modo di spendere più tempo nel corso del Capitolo 7 e in particolare nel Capitolo 8. Per ora basti sapere che il modello del dominio è una rappresentazione concettuale delle entità coinvolte nell’area business oggetto di studio. Si tratta di un manufatto di estrema importanza. Fornisce una prima versione utile per il modello di disegno, per l’organizzazione in componenti del sistema, per la progettazione del database e dell’interfaccia utente, ecc.

Nel contesto dei casi d’uso si consiglia vivamente di riferirsi ai diagrammi a “oggetti” del dominio o del business qualora presenti. Ciò favorisce una migliore comprensione dell’area oggetto di studio, permette di bilanciare gli stessi diagrammi dei casi d’uso, favorisce il conferimento della giusta enfasi ai dati principali di eventuali interfacce, aiuta ad evidenziare la necessità di pianificare specifiche funzioni, e così via. Con il termine “bilanciare”, si intende dire che, una volta catturati i requisiti del sistema, si vuole disporre di diagrammi dei casi d’uso e del dominio (o business) corretti e completi.

Un modo per ottenere ciò, consiste nel verificare che: 1. tutti gli oggetti menzionati nei casi d’uso siano presenti nel modello a oggetti del dominio, opportunamente relazionati (bilanciamento del modello del dominio rispetto ai casi d’uso); 2. tutte le classi presenti nel modello del dominio, siano, in qualche misura riferite nei diagrammi dei casi d’uso, con particolare riferimento alle funzionalità di cui hanno bisogno (inserimento, aggiornamento, ecc.) e/o che sono in grado di provvedere bilanciamento dei casi d’uso rispetto al modello a oggetti del dominio. Per esempio, nel caso in cui non fosse ben chiara la relazione tra le entità Book e Trade, analizzando le funzioni necessarie per l’elaborazione di un messaggio relativo a un Book, e in particolare relativo alla eliminazione dello stesso, si potrebbe correre il rischio di non considerare alcune casistiche (flussi) di una certa importanza e, conseguentemente, lasciare inesplorate diverse aree. Potrebbe ad esempio sfuggire la necessità di prevedere una funzione di reperimento dei Trade associati a un Book. Questa funzione è necessaria per assicurare che non siano presenti Trade appartenenti al Book da eliminare, in quanto in caso contrario, procedendo con l’eliminazione del Book, si perderebbero tutti i dati dei

37

UML e ingegneria del software: dalla teoria alla pratica

Trade ad esso associati (magari qualche milione di Euro…). A tal fine sarebbe necessaria una semplice operazione di check (Trade presenti o meno); invece si ricorre a un reperimento, in quanto qualora presenti dei Trade associati al book da eliminare, i relativi Id andrebbero comunicati al sistema di risoluzione delle eccezioni. Ciò al fine di far precedere alla procedura di eliminazione di un Book, quella di “spostamento” dei Trade dal Book da rimuovere a un altro. Gli use case di fig. 9 mostrati con una tonalità più intensa di giallo, mostrano le funzionalità comuni (pattern) eseguite a seguito della ricezione di un messaggio, indipendentemente dalla natura dello stesso. Verosimilmente, con riferimento alla descrizione dell’architettura riportata in fig. 7, i primi quattro use case rappresentano lo strato ribattezzato Messaging services utilizzabile dai vari sistemi di wrapper. Prima di descrivere il comportamento dinamico è inevitabile una breve disquisizione (probabilmente è più opportuno definirlo soliloquio, anche perché trattandosi di un libro è un po’ complicato gestire un dibattito) relativa al livello di dettaglio del diagramma di casi d’uso presentato in figura. Il punto focale è efferente la questione: “il diagramma dei casi d’uso è troppo descrittivo?”. Figura 9 — Diagramma dei casi d’uso relativo all’elaborazione del messaggio inerente ad aggiornamenti relativi all’entità “book”.

Conversione messaggio

«include»

Elaborazione messaggio

«include»

Comunica messaggio dati statici

Static Data Manager

«extend»

Invia messaggio di errore

«extend»

«extend»

Elaborazione messaggi relativi a book

Exception Management System

TMS Aggiornamento book

Eliminazione book

Inserimento nuovo book «include»

Reperimento Trade associati al book

38

Capitolo 4. Modellazione avanzata degli Use Case

Benché per molti tecnici la risposta potrebbe essere affermativa, si considerino i seguenti elementi: • System use case. La versione presa in esame è quella che generalmente viene denominata di sistema. Ciò è facilmente comprensibile poiché il confine del diagramma è uno specifico sottosistema e non l’intero sistema. I fruitori non sono più esclusivamente gli utenti, ma anche il team di sviluppo e quindi, in questa fase del processo di sviluppo, un livello di astrazione non eccessivamente elevato non è assolutamente vietato. • I singoli use case esistono sebbene non li si mostri esplicitamente. Nella descrizione del comportamento dinamico degli elementi appartenenti al diagramma, è comunque possibile evidenziare specifiche sezioni relative ai casi d’uso mostrati in fig. 9. Chiaramente mostrare tutti i diagrammi derivanti dal raggruppamento logico di sezioni della descrizione del comportamento dinamico sarebbe una follia e potrebbe generare problemi visti nel capitolo precedente. Mostrare quelli più importanti, però agevola l’evidenziazione di comportamenti comuni. Da tener presente che, non sempre è possibile realizzare tutte le fase del processo di sviluppo del software per questioni legate a vincoli temporali. In questi casi, tipicamente la fase che più frequentemente viene sacrificata è quella di analisi. In tal caso evidenziare qualche elemento in più nei diagrammi dei casi d’uso risulta molto conveniente. Una preoccupazione che induce gli analisti del business a condensare i casi d’uso è l’overhead (extra lavoro) per descrivere i singoli casi d’uso richiesto da taluni template. Dall’analisi del caso d’uso di template 6, si può notare che per i punti di estensione 2.2 e 3.2, non sono state riportate le condizioni di estensione: queste sono implicite dal fatto che sono comportamenti appartenenti a un flusso di eccezione. La condizione quindi è quella del flusso stesso. Qualora si abbia a che fare con sistemi di messaggistica è utile riportare uno o più specifici use case per la comunicazione di messaggi in quanto semplificano il processo di individuazione e bilanciamento dei messaggi presenti nel sistema. Lo use case descritto in template 7 potrebbe appartenere alla categoria di quelli non strettamente necessari. Si è invece deciso di riportarlo separatamente sia perché evidenzia una funzionalità comune e importante che vale la pena evidenziare, sia perché essendo comune a tutte le tipologie di messaggio viene definito una sola volta ed utilizzato in molti contesti. Come si può notare, le sezioni ritenute non significative (Attori, Avvio, ecc.) in questo contesto, sono state placidamente omesse. In genere ha senso specificare il campo Avvio solo nel caso in cui lo use case richieda un’interazione con un attore; in tutti gli altri casi si tratterebbe dell’invocazione da parte di un altro caso d’uso.

39

UML e ingegneria del software: dalla teoria alla pratica

Template 6 CASO D’USO:

Data: Comunica messaggio dati statici

UC_BNK_FRM_01

13/02/2001

Versione: 0.01.000

Descrizione:

Il sottosistema SDM notifica (attraverso opportuno messaggio pubblicato sul MOM) l’avvenuta variazione del proprio stato (variazione di dati statici) al fine di consentire agli altri sottosistemi di sincronizzare il proprio stato.

Priorità:

Media. (Nelle prime versioni questa funzionalità può essere simulata attraverso un sistema di GUI).

Durata: Attore primario:

Secondi. SDM (Static Data Manager).

Precondizioni: Garanzie:

Avvio:

Ha interesse nel pubblicare messaggi contenenti informazioni relative ad aggiornamenti effettuati nel dominio dei dati statici. Il sistema si sia precedentemente sottoscritto per la tipologia del messaggio disponibile. Minime: Il messaggio rimane disponibile. Successo: Il sistema prende correttamente in carico il messaggio ed riflette le relative operazioni nella propria base dati Viene pubblicato un messaggio appartenente ad una delle tipologia per la quale il sistema si è sottoscritto.

Scenario principale. 1. SDM: Pubblica un nuovo messaggio relativo a variazioni dei dati statici per la quale il sistema si sia precedentemente registrato. 2. 3.

Sistema: Acquisisce il messagg io. Sistema: Include(Conversione messaggio)

4.

Sistema: Include(Elaborazione messaggio)

Primo scenario di errore. 2.1. 2.2.

Il sistema fallisce nell’acquisizione del messaggio Punto di estensione: Notifica condizione di errore.

2.3.

Termina lo use case in maniera anomala

Secondo scenario di errore. 3.1. Il sistema fallisce la conversione del messaggio 3.2. 3.3.

Punto di estensione: Notifica condizione di errore. Termina lo use case in maniera anomala

40

Capitolo 4. Modellazione avanzata degli Use Case

Template 7 Data:

CASO D’USO: Conversione messaggio UC_BNK_FRM_02 Descrizione:

Priorità: Durata: Precondizioni: Garanzie:

13/02/2001

Versione: 0.00.003

Esegue la conversione del messaggio. Se il messaggio ricevuto è conforme al formato atteso, viene trasformato in quello previsto dal sistema (tipicamen te si assiste ad una trasformazione dal formato XML ad una rappresentazione ad aggetti) Media. Secondo. Il sistema abbia ricevuto un nuovo messaggio. Minime: Il messaggio non viene convertito. Successo: Il messaggio viene correttamente trasformato nel formato previsto dal sistema.

Scenario principale. 1. Sistema: Acquisisce la tipologia del messaggio. 2. 3.

Sistema: Reperisce le regole che consentono di convertire la particolare tipologia di messaggio. Sistema: Converte il messaggio in base alle regole reperite.

Primo scenario di errore. 2.1. Il sistema fallisce il reperimento delle regole 2.2.

Termina lo use case in maniera anomala

Secondo scenario di errore. 3.1. Il sistema fallisce la conversione del messaggio 3.2.

Termina lo use case in maniera anomala

41

UML e ingegneria del software: dalla teoria alla pratica

Template 8 CASO D’USO:

Data: Invio messaggi di errore

UC_BNK_FRM_03 Descrizione:

14/02/2001

Versione: 0.00.001

Priorità:

Pubblica un apposito messaggio al fine di notificare all’Exception Management System il verificarsi di un’anomalia. Media.

Durata: Attore primario:

Secondi. Exception Management System.

Precondizioni: Garanzie:

Riceve il messaggio di errore emesso dal sistema. Il sistema disponga dei dati atti a descrivere l’errore verificatosi. Minime: I dati efferenti l’errore restano inalterati. Successo: Il messaggio viene correttamente pubblicati.

Scenario principale. 1. 2.

Punto di estensione: Notifica condizione di errore. Sistema: Struttura secondo il formato previsto il messaggio di errore.

3.

Sistema: Pubblica il messaggio.

Primo scena rio di errore. 3.1. 3.2.

Il sistema fallisce l’invio del messaggio. Termina lo use case in maniera anomala.

Annotazioni. 3.1. Cosa fare quando si fallisce la pubblicazione di un errore? Sufficiente registrare l’anomalia in un opportuno file di log?

42

Capitolo 4. Modellazione avanzata degli Use Case

Template 9 CASO D’USO:

Data: Elaborazione messaggio

UC_BNK_FRM_04 Descrizione:

16/02/2001

Versione: 0.00.001

Priorità:

Elabora il messaggio ricevuto in base alla tipologia dei dati presenti e della funzione da eseguire. Media.

Durata: Attore primario:

Secondi. Trade Management System (TMS)

Precondizioni: Garanzie:

Il sistema di gestione dei trade esegue la transizione r ichiesta dall’elaborazione del messaggio al fine di sincronizzare i pr opri dati statici con quelli presenti presso il SDM. Sono disponibili i da ti relativi al messaggio ricevuto nel formato atteso dal sistema (rappresentazione ad oggetti). Minime: I dati relativi al messaggio non vengono alterati. Successo: Il messaggio viene elaborato correttamente in funzione alla sua tipologia e la relativa transazione originata viene eseguita correttamente dal TMS.

Scenario principale. 1. Sistema: Acquisisce la rappresentazione ad oggetti del messaggio. 2.

3. 4.

Punto di estensione: Elabora il messaggio. (Il messaggio è elaborato in funzione della relativa tipologia) Condizione: Relazione con use case “Elabora messaggi relativi ai book” (Tipologia messaggio = book) Relazione con use case “Elabora messaggi relativi a Trade” (Tipologia messaggio = trade) ... Sistema: Rifinisce il formato de i dati per adattarlo a quello previsto dalla transazione da richiedere al sistema TMS.

5.

Sistema: Richiede al sistema TMS di eseguire la transazione predisposta al punto 2. TMS: Riceve ed esegue la transazione.

6. 7.

TMS: Comunica esisto transazione. Sistema: Verifica che la transazione sia stata eseguita correttamente

Primo scenario di errore. 2.1. Sistema: La funzione di elaborazione del messaggio fallisce. 2.2. 2.3.

Punto di estensione: Notifica condizione di errore. Sistema: Termina lo use case in maniera anomala

Secondo scenario di errore. 7.1. Sistema: Transazione fallita 7.2. 7.3.

Punto di estensione: Notifica condizione di errore. Sistema: Termina lo use case in maniera anomala.

43

UML e ingegneria del software: dalla teoria alla pratica

Template 10 CASO D’USO: UC_BNK_BKM_01 Descrizione:

Reperimento dati trade associati al book

Data:

18/02/2001

Versione: 0.01.000

Priorità:

Reperisce dal sistema di gestione dei trade le informazioni relative ad eventuali Trade associati al Book specificato. Media.

Durata: Attore primario:

Secondi. TMS

Precondizioni: Garanzie:

Esegue la funzione di reperimento dei Trade associati al Book specificato. È disponibile nel sistema il codice del Book del quale si vuole reperire i dati dei Trade associati. Minime: Nessun dato viene alterato. Successo: La funzione di reperimento viene eseguita correttamente.

Scenario principale. 1. Sistema: Acquisisce il codice del Book da reperire. 2. 3.

Sistema: Richiede il reperimento dei trade associati al book. TMS: Reperisce le informazioni dei trade richiesti.

4. Sistema: Memorizza le informazioni dei Trade reperiti. Primo scenario alternativo. 4.1. 4.2.

Non esistono nel sistema Trade associati al Book richiesto. Nessun informazione relativa ai Trade viene memorizzata.

4.3

Termina lo use case senza comunicare alcun errore.

44

Capitolo 4. Modellazione avanzata degli Use Case

Template 11 CASO D’USO: UC_BNK_BKM_02 Descrizione:

Elaborazione messaggi relativi a book

Data:

16/02/2001

Versione: 0.00.004

Priorità:

Elabora il messaggio inerente il Book specificato. In particolare, in funzione della tipologia del messaggio, il sistema deve eseguire una delle seguenti transazioni: - inserimento di un nuovo Book; - aggiornamento di uno esistente; - eliminazione di un Book. Media.

Durata: Attore primario:

Secondo. TMS

Precondizioni: Garanzie:

Comunica i dati relativi al Book richiesto. Il messaggio ricevuto è relativo ad un Book i cui dati sono disponibili nella rappresentazione attesa dal sistema. Minime: I dati relativi al messaggio non vengono alterati. Successo: Il messaggio viene elaborato correttamente, e vengono preparati i dati in funzione della transazione da eseguire.

Scenario principale. 1. 2.

Punto di estensione: Elabora il messaggio. Sistema: Acquisisce il codice del Book.

3. 4.

Sistema: Richiede al sistema TMS le informazione relative al Book. TMS: Comunica le informazioni relative al Book specificato.

5.

Punto astratto: Organizza le informazioni in funzione della transazione da eseguire sui dati del Book Primo scenario di errore. 5.1. 5.2.

Sistema: Lo use case “concreto” che definisce il comportamento astratto termina con insuccesso. Sistema: Termina lo use case in maniera anomala

Nella pratica, la corretta applicazione di processi di sviluppo richiede una buona dose di pragmatismo: tutto ciò che non concorre ad aumentare il livello conoscitivo può essere trascurato.

45

UML e ingegneria del software: dalla teoria alla pratica

Use case astratti sono i più difficili da far comprendere agli utenti, quindi il loro utilizzo andrebbe opportunamente ponderato. In sostanza quello poc’anzi descritto fornisce una procedura comune per tutte le funzioni relative alla gestione dei dati dei book. Il primo punto sancisce che la relativa esecuzione avviene solo nel caso in cui i dati contenuti nel messaggio siano relativi a un oggetto book. I seguenti due contengono del comportamento comune, mentre l’ultimo punto rappresenta un passo astratto. Pertanto è compito degli use case “specializzanti” fornirne il comportamento concreto in base alla funzione da eseguire.

Template 12 CASO D’USO:

Data: Inserimento nuovo book

UC_BNK_BKM_04 Descrizione: Priorità: Durata: Precondizioni: Garanzie:

18/02/2001

Versione: 0.00.001

Organizza i dati per eseguire una transazione di inserimento di un nuovo Book. Media. Secondo. Sono disponibili i dati del nuovo Book da inserire nel sistema di gestione trade (TMS). Minime: I dati relativi inerenti il nuovo Book non vengono modificati. Successo: I dati vengono organizzati correttamente in funzione alla transazione di inserimento da es eguire.

Scenario principale. 1. Definizione punto astratto Organizza le informazioni in funzione della transazione da eseguire sui dati del Book 2.

Sistema: Verifica che la funzione di reperimento dati Book non abbia indiv iduato alcuna istanza. (In sostanza non esiste un Book con lo stesso codice di quello da inserire)

3. Sistema: Organizza i dati per eseguire una transazione di inserimento. Primo scenario di errore. 2.1. 2.2.

Sistema: Un Book con codice equivalente a quello da inserire è già pr esente nel sistema. Sistema: Termina lo use case in maniera anomala

46

Capitolo 4. Modellazione avanzata degli Use Case

Template 13 CASO D’USO:

Data: Aggiornamento book

UC_BNK_BKM_05 Descrizione: Priorità: Durata: Precondizioni: Garanzie:

18/02/2001

Versione: 0.00.001

Organizza i dati per eseguire una transazione di aggiorn amento di un Book. Media. Secondo. Sono disponibili i dati del Book da aggiornare. Minime: I dati relativi inerenti il Book non vengono modificati. Successo: I dati vengono organizzati correttamente in funzione alla transazione di aggiornamento da eseguire.

Scenario principale. 1. Definizione punto astratto: Organizza le informazioni in funzione della transazione da eseguire sui dati del Book. 2. 3.

Sistema: Verifica che la funzione di reperimento dati Book abbia ind ividuato l’istanza richiesta. (Il Book da modificare è presente nel TMS) Sistema: Organizza i dati per eseguire una transazione di aggiorname nto Book.

Primo scenario di errore. 2.1. Sistema: Non è presente nel TMS un Book con codice equivalente a quello da specificato nel messaggio. 2.2.

Sistema: Termina lo use case in maniera anomala

47

UML e ingegneria del software: dalla teoria alla pratica

Template 14 CASO D’USO:

Data: Eliminazione book

UC_BNK_BKM_06 Descrizione: Priorità: Durata: Precondizioni: Garanzie:

18/02/2001

Versione: 0.00.002

Organizza i dati per eseguire una transazione di eliminazi one di un Book. Media. Secondo. Sono disponibili i dati rela tivi ad un Book da eliminare. Minime: I dati relativi inerenti il Book non vengono modificati. Successo: I dati vengono organizzati correttamente in funzione alla transazione di eliminazione da eseguire.

Scenario principale. 1. Definizione punto astratto: Organizza le informazioni in funzione della transazione da eseguire sui dati del Book 2.

3. 4. 5.

Sistema: Verifica che la funzione di reperimento dati Book abbia ind ividuato l’istanza richiesta. (Il Book da modificare è presente nel sistema TMS) Sistema: Include(Reperimento dei dati associati al Book) Sistema: Verifica che non ci siano Trade associati al Book da eliminare. (La precedente funzione restituisce una lista vuota) Sistema: Organizza i dati per eseguire una transazione di eliminazione Book.

Primo scenario di errore. 2.1. Sistema: Non è presente nel sistema TMS un Book con codice equivalente a quello da specificato nel messaggio. 2.2. Sistema: Termina lo use case in maniera anomala Seconfo scenario di errore. 4.1. Sistema: Sono presenti dei dati associati al Book da eliminare 4.2. 4.3.

Sistema: Memorizza tutti gli Id dei Trade in un’apposita lista da inserire nel messaggio di errore. Sistema: Termina lo use case in maniera anomala

48

Capitolo 4. Modellazione avanzata degli Use Case

Figura 10 — Dipendenze tra gli use case a livello di sistema e gli altri manufatti. Chiaramente la dipendenza del modello dei casi d’uso e di quelli non funzionali non implica che questi confluiscano nei casi d’uso, bensì che alcune funzionalità potrebbero essere, per così dire, riviste in funzione di altri vincoli.

«use case»

Requisiti non funzionali

Modello Business

«refine»

«use case» Modello di Sistema

Modello di Architettura

Sistema di banking online Si illustrerà uno dei servizi offerti dal sistema di banking online; in particolare viene presentata la funzione che consente ai clienti accreditati di acquistare azioni (shares) online. Tali clienti devono possedere un conto corrente presso la banca e aver ottenuto conferma alla relativa richiesta di fruizione del servizi di trading online. Si tratta del solito ozioso utente condannato a starsene seduto comodamente a casa sua e a navigare in Internet. Questo servizio non è stato selezionato casualmente, ma in virtù degli spunti che è in grado di offrire. In particolare viene evidenziato come gli use case appartenenti al modello di sistema, oltre a inglobare indicazioni provenienti dall’architettura dello stesso, incorporano sezioni, o meglio suggerimenti, derivanti dai requisiti non funzionali. Una struttura online, destinata a operare secondo le specifiche del protocollo sincrono HTTP, difficilmente potrebbe funzionare utilizzando unicamente i meccanismi offerti da

49

UML e ingegneria del software: dalla teoria alla pratica

sistemi MOM. In particolare, il sistema dovrebbe soddisfare anche precisi requisiti non funzionali, come per esempio stringenti tempi di risposta non garantiti dai sistemi di messaggistica. Questi, tipicamente, si fanno carico di una serie di assicurazioni (i messaggi vengono consegnati correttamente e solo una volta), ma non forniscono alcuna certezza sulle prestazioni, sebbene poi nella stragrande maggioranza dei casi riescano a effettuare la consegna nell’arco dei secondi. Ulteriore considerazione è relativa al tempo necessario per costruire i vari messaggi in formato XML (in spedizione) e per trasformarli nuovamente in un apposito grafo di oggetti (in ricezione). Tempi che non sempre sono trascurabili. Il vincolo di ricevere informazioni molto rapidamente è una restrizione decisamente pressante non solo per motivi legati alla fruizione del servizio in Internet, ma anche per la natura del servizio stesso. Per esempio, dovendo visualizzare le quotazioni in tempo reale delle azioni, non si può di certo tollerare di ricevere i dati con una latenza tale da rendere gli stessi non più attendibili. Le precedenti considerazioni sono state illustrate graficamente nella fig. 10.

Figura 11 — Use Case relativo all’acquisto online di shares.

Comunica dati shares

Market Data System

Verifica fattore di rischio

«include»

«include»

Acquisto shares on-line

Cliente «include»

«extend» «extend»

Invia dati conto corrente cliente

Invia messaggio di errore

Exception Management System

Accounting System

Invia messaggio dati transazione

TMS

50

Capitolo 4. Modellazione avanzata degli Use Case

Ricapitolando, i diagrammi dei casi d’uso della fase di sistema devono sia dettagliare i corrispondenti al livello dei requisiti (o di business, se si preferisce), sia incorporare eventuali suggerimenti derivanti dai requisiti non funzionali (NFR, non functional requirements, consultare il Capitolo successivo) e direttive provenienti dall’architettura. Quest’ultima a sua volta deve dipendere dai NFR nonché dagli use case stessi. A questo punto si prenda in considerazione (finalmente) il diagramma dei casi d’uso riportato nella fig. 11. Gli attori previsti dallo use case sono: • il cliente (l’investitore); • Market Data System, utilizzato per reperire i dati relativi alle quotazioni di mercato in tempo reale di determinati prodotti finanziari. Tipicamente si tratta di un sistema interno alla banca alimentato da servizi offerti da terze parti; • Accounting System, necessario per reperire la situazione finanziaria dell’investitore che richiede di acquistare le varie azioni; • Exception Management System, al quale vengono inviati eventuali messaggi relativi a gravi anomalie occorse; • Trade Management System, al quale vengono affidate, attraverso opportuni messaggi, le transazioni effettuate online. Per quanto concerne la descrizione del comportamento dinamico del precedente caso d’uso, il template adottato è sensibilmente differente da quello utilizzato in precedenza. In questo caso la parte dedicata allo scenario principale è ripartita in tante sezioni quante sono le entità coinvolte (gli attori più il sistema stesso). La peculiarità della nuova versione proposta è di evidenziare chiaramente le azioni svolte dalle varie entità e quindi le efferenti responsabilità. Per molti versi ricorda molto gli activity diagram. A fronte di questo vantaggio, presenta lo svantaggio di occupare molto più spazio. Spesso, il non riuscire a visualizzare tutto un flusso in una pagina può creare dei problemi di comprensione dello stesso.

51

UML e ingegneria del software: dalla teoria alla pratica

Template 15 CASO D’USO:

Data: Acquisto share on line

UC_BKL_BKS_01 Descrizione:

03/12/2000

Versione: 0.09.003

Durata:

Fornisce ai clienti abilitati il servizio on-line di acquisto di titoli azionari. L’utente, previa opportuna autorizzazione, fruendo del servizio attraverso un comune browser Internet, è in grado di ricevere informazioni ed eventualmente acquistare prodotti finanziari. Minuti.

Priorità: Attore primario:

Elevata. Cliente della banca. Ha interesse a ricevere informazioni relative a parti prodotti finanziari ed eventualmente ad acquistarli.

Precondizioni:

Garanzie minime:

Garanzie successo:

Avvio:

colari

Il cliente dispone di almeno un conto corrente presso la banca, è abilitato alla fruizione del servizio, ed è stato precedentemente autorizzato dal sistema nell’ambito dell a sessione (corretta esecuzione della procedura di log-in). Lo situazione azionaria ed il conto corrente del cliente non vengono modificate: permangono nello stato presente prima dell’esecuzione. Il cliente effettua una transazione di acquisto di titoli azionari. Le informazioni relative alle transazioni vengono inviate per mezzo di opportuno messaggio al sistema di back office. Il cliente richiese esplicitamente l’esecuzione del servizio di acquisto di titoli azionari.

52

Capitolo 4. Modellazione avanzata degli Use Case

Template 16 Scenario principale. 1.

SISTEMA Visualizza le specializzazioni in cui si suddividono le azioni disponibili.

2. 3.

4. 5.

CLIENTE

Seleziona la specializzazione di int eresse. Visualizza l’insieme delle azioni appartenenti alla specializzazione selezionata. Seleziona il titolo di interesse. Include(Comunica dati shares) Visualizza i dati aggiornati relativi al titolo selezionato

6. 7. 8. 9.

Imposta la quantità desiderata. Include(Verifica fattore di rischio) Include(Invia dati conto c orrenti cliente). Visualizza dati conto correnti int estati al cliente

10.

Seleziona il conto corrente da utilizzarsi per la transazione

11.

Include(Comunica dati shares)

12.

Visualizza i dettagli economici ed in particolare l’ultimo prezzo dis ponibile per il titolo oggetto di acquisto. Chiede conferma a procedere con la transazione.

14. 15. 16.

Accetta la transazione Punto di estensione: Invio messaggio dati transazione Condizione: Utente accetta transazione

UML e ingegneria del software: dalla teoria alla pratica

53

Template 17 I scenario alternativo. 15.1. Utente: Non conferma la transazione nei limiti di tempo prestabiliti. 15.2. 15.3.

Sistema: Comunica apposito messaggio all’utente. Sistema: Torna ad eseguire dal punto 10.

I scenario di errore. Punti: 2, 4, 6, 15. 2.1. Utente: Non imposta i dati attesi e richiede la terminazione della funzione. 2.2.

Sistema: Termina lo use case con insuccesso.

II scenario di errore. 7.1. Sistema: Fattore di rischio dell’operazione richiesta superiore ai limiti previsti. 7.2. 7.3

Sistema: Visualizza apposito messaggio all’utente. Sistema: Termina lo use case con insuccesso.

III scenario di errore. 5.1. Sistema: Errore nel reperimento dati share. 5.2. 5.3.

Sistema: Comunica momentanea non disponibilità del sistema. Extension point: Invio messaggio di errore

5.4.

Sistema: Termina l’esecuzione della funzione con insuccesso.

IV scenario di errore. 8.1. Sistema: Errore nel reperimento dati conto correnti utente. Resto come da scenario di errore precedente. V scenario di errore. 11.1. Sistema: Errore nel reperimento dati share. Resto come da scenario di errore precedente. VI scenario di errore. 16.1. Sistema: Fallisce invio messaggio dati transazione. Resto come da scenario di errore precedente.

54

Capitolo 4. Modellazione avanzata degli Use Case

Template 18 CASO D’USO:

Data: Comunica dati shares

UC_BKL_BKS_02 Descrizione:

03/12/2000

Versione: 0.01.002

Durata:

Permette di reperire le quotazioni di mercato in tempo reale dei prodotti finanziari richiesti. Secondo.

Priorità: Attore primario:

Elevata. Market Data System (MDS).

Precondizioni: Garanzie minime: Garanzie successo:

Fornisce le quotazioni di mercato in tempo reale relative ai prodotti specificati. Sia disponibile una connessione con il Market Data System e sia disponibile il codice del prodotto da reperire. ?. Vengono reperite le quotazioni dei prodotti richiesti.

Scenario principale. 1.

2.

SISTEMA Reperisce il codice del prodotto del quale si vuole reperire la quotazione. Richiede il servizio forni tura quotazioni di mercato in tempo reale delle azioni.

3.

Riceve la richiesta di esecuzione del servizio. Estrae il codice del prodotto del quale si intende reperire la quotazione.

4. 5. 6. 7.

Market Data System

Reperisce i dati della quotazione Restituisce i dati di quotazione del prodotto reperiti. Memorizza i dati ricevuti.

I scenario di errore. 6.1. MDS: Fallisce il reperimento dei dati relativi al prodotto specificato (codice non riconosciuto, problemi di connessione, ecc). Restituisce un mes saggio di errore. 6.2.

Sistema: Termina l’esecuzione della funzione con insuccesso.

II scenario di errore. 7.1. Sistema: Scade il time -out associato alla fruizione del servizio. 7.2.

Sistema: Termina l’esecuzione della funzione con insuccesso.

55

UML e ingegneria del software: dalla teoria alla pratica

Template 19 CASO D’USO:

Invia messaggio dati transazione

UC_BKL_BKS_03 Descrizione:

Data:

03/12/2000

Versione: 0.01.002

Durata:

Invia al sistema di gestione trade un messaggio relativo alla transazione effettuata on-line dall’utente. (Chiaramente la stessa verrà considerata effettiva solo a seguito della relativa elaborazione effettuata nel TMS). Secondi.

Priorità: Attore primario:

Elevata. TMS. Riceve il messaggio con i dati relativi alla transazione on-line eseguita dall’utente. L’utente abbia impostato e confermato una transazione, i relativi dati siano disponibili ed il sistema abbia eseguito correttamente tutti i controlli. I dati della transazione non subiscono alcuna variazione.

Precondizioni:

Garanzie minime: Garanzie successo:

I dati della transazione vengono organizzati in un opportuno messaggio, e lo stesso viene inviato al TMS.

Scenario principale. SISTEMA

1.

2.

3.

TMS

Punto di estensione: Invio messaggio dati transazione Reperisce i dati della transa zione. Tra cui: codice cliente; codice prodotto acquistato; quantità e quotazione in tempo reale del prodotto; data e orario della conferma della transazione; Organizza i dati secondo il formato previsto (per esempio in base ad uno schema XML) . Pubblica il messaggio

4.

Prende il carico il messaggio

I scenario di errore. 3.1. 3.2.

Sistema: Fallisce la pubblicazione del messaggio. Sistema: Termina l’esecuzione della funzione con insuccesso.

56

Capitolo 4. Modellazione avanzata degli Use Case

Template 20 CASO D’USO: UC_BKL_BKS_04 Descrizione:

Reperisce dati conto corrente cliente

Data:

03/12/2000

Versione: 0.01.002

Durata:

Permette di reperire le informazioni relative ai conto corrente intestati all’utente. Secondi.

Priorità: Attore primario:

Elevata. Accounting System (AS).

Precondizioni:

Fornisce i dati relativi ai conto corrente intestati all’utente. Sia disponibile il codice del cliente e questi disponga di almeno un conto corrente gestito dalla banca.

Garanzie minime: Garanzie successo:

La sessione avviata dal cliente rimane valida. Vengono reperiti i dati relativi ai conto corrente del cliente compresi i relativi saldi.

Scenario principale. 1. 2.

SISTEMA Reperisce il codice dell’utente. Richiede il servi zio fornitura dati conto corrente utente.

3.

Riceve la richiesta di esecuzione del servizio Estrae il codice del cliente

4. 5.

Reperisce i dati relativi ai conto corrente corredati dai rispettivi saldi Restituisce i dati relativi ai con to corrente del cliente

6. 7.

Accounting System

Memorizza i dati ricevuti.

I scenario di errore. 6.1.

6.2.

AS:

Fallisce il reperimento dei dati relativi ai conto corrente intestati all’utente (codice cliente non riconosciuto, problemi di conne ssione, ecc). Restituisce un messa ggio di errore. Sistema: Termina l’esecuzione della funzione con insuccesso.

II scenario di errore. 7.1. Sistema: Scade il time -out associato alla fruizione del servizio. 7.2.

Sistema: Termina l’esecuzione della funzione con insuccesso.

57

UML e ingegneria del software: dalla teoria alla pratica

Template 21 CASO D’USO:

Data: Invia messaggio di errore

UC_BKL_BKS_05 Descrizione: Durata: Priorità: Attore primario:

Precondizioni: Garanzie minime: Garanzie successo:

Versione: 0.01.002

Invia al sistema di gestione delle eccezioni un messaggio con i dati relativi all’anomalia verificatasi. Secondi. Elevata. Exception Management System (EMS). Riceve il messaggio con i dati relativi all’anomalia verificatasi. Nel sistema si sia verificata una anomalia grave e i dati identificanti la stessa siano disponibili . I dati efferenti restano disponibili nel sistema. I dati della anomalia vengono organizzati in un opportuno messaggio, secondo il formato previsto, e lo stesso viene inviato al relativo sistema di gestione.

Scenario principale.

1. 2.

3.

SISTEMA Extension point: Invio messaggio di errore Reperisce i dati relativi all’anomalia verificatasi.

EMS

Organizza i dati secondo il formato previsto (per esempio in base ad uno schema XML). Pubblica il messaggio.

4.

Prende il carico il messaggio

I scenario di errore. 3.1. 3.2.

03/12/2000

Sistema: Fallisce la pubblicazione del messaggio. Sistema: Termina l’esecuzione della funzione con insuccesso.

58

Capitolo 4. Modellazione avanzata degli Use Case

Template 22 CASO D’USO:

Data: Verifica fattore di rischio

UC_BKL_BKS_07 Descrizione: Durata:

03/12/2000

Versione: 0.01.002

Verifica che il fattore di rischio associato con la transazione richiesta dal cliente rientri nei limiti previsti dal relativo profilo. Secondi.

Priorità: Precondizioni:

Elevata. Nel sistema siano disponibili i dati relativi alla transazione richiesta dall’utente ed il relativo fattore di rischio calcolato.

Garanzie minime: Garanzie successo:

I dati relativi al rischio restano disponibili nel sistema. Viene eseguita la valutazione del fattore di rischio.

Scenario principale. 1.

SISTEMA Reperisce i dati relativi al profilo dell’utente.

2. 3.

Calcola il valore del rischio connesso con la transazione richiesta dall’utente. Verifica la compatibilità del fattore di rischio calcolato con quello impostato nel profilo utente.

4.

Restituisce la conferma del rispetto del fattore di rischio

I scenario alternativo. 4.1. 4.2.

Sistema: Fattore di rischio della transazione fuori dai limiti di tolle ranza. Sistema: Termina l’esecuzione restituendo esito negativo della verifica.

Annotazioni. 3. Per le specifiche formali relative al calcolo dei fattori di rischio si consulti il d ocumento delle business rule sezione 3.2.

Iterazioni nella costruzione del modello dei casi d’uso Indipendentemente dalla modalità stabilita per descrivere il comportamento dinamico dei casi d’uso è importante considerare che difficilmente un buon modello si ottiene con un’unica iterazione. Nella pratica è necessario effettuarne diverse, per dar modo agli stessi utenti di chiarirsi le idee, per riuscire a considerare situazioni meno frequenti, per raggiungere un livello qualitativo omogeneo ed elevato (tipicamente la qualità dei primi casi d’uso è pessima), ecc. Per questi motivi, è buona norma organizzare la fase di analisi dei requisiti secondo un processo iterativo e incrementale.

UML e ingegneria del software: dalla teoria alla pratica

59

La validità o meno di ricorrere ad un simile processo anche per la costruzione del modello dei casi d’uso, è stato argomento di dibattito in diversi simposi tenuti tra l’autore del presente libro e Frank Armour (coautore del testo [BIB13]), mangiando un surrogato di pizza londinese accompagnata da un discreto Chianti. In particolare, nel suo libro, Frank, illustra un processo ben definito per realizzare il modello dei casi d’uso basato su tre macroiterazioni: 1. initial use case description (descrizione iniziale dei casi d’uso), denominata così in quanto prevede la definizione, per ogni use case, di pochi basilari elementi, come il nome, gli attori e la descrizione. Si tratta chiaramente di una fase iniziale da avviare congiuntamente all’analisi dei requisiti; 2. base use case description (descrizione base dei casi d’uso). La versione precedente viene estesa focalizzando l’attenzione sullo scenario principale, ossia si considera il percorso ideale, trascurando per il momento flussi alternativi e di eccezione; 3. elaborated use case description (descrizione elaborata dei casi d’uso). Si tratta dell’ultima fase nella quale vengono aggiunti tutti i restanti dettagli che permettono di terminare (almeno fino alla prossima variazione dei requisiti), la descrizione dei casi d’uso. Quindi vengono inglobate la descrizione e gestione delle situazioni di errore, eventuali flussi alternativi, ecc. In questo processo, i veri use case (i diagrammi) risultano opzionali da utilizzarsi per una prospettiva ad alto livello. Il ricorso a un processo di questo tipo sarebbe piuttosto opinabile in sistemi di dimensioni contenute, mentre agevola moltissimo la vita in sistemi di dimensioni medio-grandi. In particolare, permette di dare maggior tempo agli utenti per riflettere sul funzionamento dei casi d’uso ed eventualmente cambiare idea prima che papiri di documentazione siano realizzati: non è necessario attendere la completa realizzazione del caso d’uso, ma è possibile riesaminarli già dalla seconda versione (base). Lo stesso team di management può iniziare in anticipo la pianificazione dell’intero processo con particolare riferimento alle varie iterazioni (con la speranza di mettere numeri non casuali nelle caselline del GANTT). Prima di definire completamente una funzionalità è possibile avere un quadro più chiaro del funzionamento dell’intero sistema. Ciò permette sia di aumentare il livello di consistenza delle varie funzionalità, sia di fornire abbastanza materiale al team degli architetti per avviare la progettazione del sistema.

60

Capitolo 4. Modellazione avanzata degli Use Case

A fronte di questi vantaggi esistono alcuni inconvenienti. Per esempio dover ritornare più volte su questioni già affrontate con i clienti (generalmente questa cosa tende a infastidirli, soprattutto quando si parla con i manager… Ah, novelli Paganini…), e quindi “sprecare” tempo per fare mente locale, riorganizzare workshop, ricontrollare il materiale, ecc. Poi, sebbene il processo realizzato in modo iterativo sia più flessibile e aiuti ad aumentare il livello qualitativo, è anche vero che apparentemente tende a richiedere più tempo, che non sempre si ha a disposizione. Dopo lunghe argomentazioni, qualora si utilizzi un approccio basato sui casi d’uso come ariete da sfondamento (nel capitolo successivo ne viene presentato uno diametralmente opposto basato sugli activity diagram), si è convenuto che un buon approccio potrebbe essere: 1. iniziare il processo realizzando un modello basato quasi esclusivamente sui diagrammi dei casi d’uso con eventualmente una descrizione molto sintetica di due, tre righe (dice nulla questo inizio?). Nell’evenienza in cui si tratti di clienti con difficoltà a comprendere i meccanismi della relazione di estensione, è saggio avvalersi di un approccio pragmatico che consideri l’utilizzo della sola relazione di inclusione; 2. realizzare una prima descrizione che contempli, ovviamente il main scenario, gli attori, le precondizioni, le varie garanzie e i flussi alternativi e di eccezioni più semplici ed evidenti; 3. concludere il tutto incorporando suggerimenti provenienti dal team degli architetti, dalla revisione con i clienti, ecc. Dopo la fase iniziale, qualora si verifichi la necessità, è necessario ristrutturare i diagrammi dei casi d’uso. Nel caso in cui si decida di ricorrere a processi iterativi e incrementali anche per l’analisi dei requisiti, è utile prevedere una serie di informazioni supplementari, da associare ai vari manufatti prodotti, necessarie per tener traccia della relativa evoluzione. Queste informazioni dovrebbero riportare le varie versioni dei manufatti (in questo caso use case), gli autori, la data di prevista o avvenuta consegna, la descrizione e così via. Chiaramente non c’è alcuna necessità di specificarle per ogni singolo caso d’uso (singolo ellisse), ma è sufficiente riportarle solo per le macrofunzionalità (in altre parole al livello di use case diagram). Con riferimento all’esempio del sistema di banking online, sarebbe utile visualizzare l’evoluzione dello use case diagram acquisto share on-line, mentre sarebbero di scarso interesse per tutti i singoli casi d’uso coinvolti verifica fattore di rischio, Invia messaggio di

61

UML e ingegneria del software: dalla teoria alla pratica

errore, e così via. Ovviamente le informazioni specificate per lo use case generale si riferiscono anche a tutti i casi d’uso di cui è composto. Come suggerito anche dal processo della Rational Software Corporation (RUP Rational Unified Process) è consigliabile riportare, per ogni use case diagram, un template come il seguente:

Template 23 n. Iterazione

Autore

Consegna

1A

LVT

10 II 2001

1A

RV

12 II 2001

1B

LVT

20 II 2001

1B

AR

22 II 2001

Descrizione Initial draft – Diagramma e descrizione Base – Definizione del main scenario Elaborated – Inclusione degli scenari alternativi e di errore. Revisione – Aggiunta feed back utente

Probabilmente potrebbe risultare altrettanto conveniente disporre di un altro template, in qualche modo speculare al primo, collocato alla fine della descrizione del comportamento dinamico di ogni use case diagram. Lo scopo è di ospitare, in un sol punto, eventuali considerazioni del personale coinvolto nello sviluppo e nel controllo della qualità del modello. Sebbene sia possibile specificare eventuali considerazioni anche nel modulo utilizzato per descrivere il comportamento dinamico di ogni singolo caso d’uso, probabilmente è comodo disporre anche di uno più generale. Ciò consente di raggruppare le varie note in un solo punto evitando di doverle cercare per tutto il progetto. Un altro vantaggio consiste nella semplificazione della gestione dello stato di avanzamento delle osservazioni. Si supponga che durante il processo di revisione dei casi d’uso nascano delle perplessità o suggerimenti: le relative descrizioni andrebbero riportate nel template conclusivo di ogni use case diagram. Pertanto è possibile verificare se ci siano o meno quesiti in attesa di risposta, accedendo direttamente a questi template. Template 24 n. 1

Osservazione Come comportarsi nel caso x,y,z

Autore R.V.

Stato Risolto. Consultare punto a.

62

Capitolo 4. Modellazione avanzata degli Use Case

La sicurezza nel modello dei casi d’uso Prima di procedere con l’esempio conclusivo del capitolo, si è ritenuto utile esaminare l’argomento relativo all’incorporazione della sicurezza nel modello dei casi d’uso. In primo luogo va ricordato che si tratta di requisiti non funzionali che come tali costituiscono una parte, anche molto importante, dell’apposito manufatto (tale manufatto è presentato al capitolo successivo). Inoltre si tratta di un vincolo che, tipicamente, si applica a tutti i sistemi e quindi alla stragrande maggioranza di casi d’uso. Ora si potrebbe utilizzare l’approccio più tradizionale, ossia includere i relativi casi d’uso in tutti gli use case che devono applicare i requisiti di sicurezza. Esempi di questi use case potrebbero essere autenticazione utente (serve per accertarsi che l’utente sia effettivamente quello che dichiara di essere), autorizzazione ad eseguire la funzionalità selezionate, autorizzazione a trattare i dati richiesti, scrittura di appositi log per tener traccia della navigazione dell’utente, ecc. Per ciò che concerne l’autenticazione, è da notare che il problema si pone anche per lo scambio di messaggio con altri sistemi. Per quanto attiene alle autorizzazioni è evidente che se un utente viene autorizzato a espletare un servizio (per esempio Visualizzazione estratto conto) ciò non significa che lo potrà richiedere per qualsiasi insieme di dati (per esempio estratto conto di un’altra persona). Questo approccio, sebbene molto semplice, pone una serie di svantaggi: • altera la logica business mostrata dai vari diagrammi dei casi d’uso. Includendo in un apposito use case, quelli relativi all’autenticazione e/o autorizzazione, le relazioni con gli altri inclusi di carattere business tendono a diventare automaticamente degli extend: vengono eseguiti solo se si ottiene l’autorizzazione; • la descrizione dei casi d’uso viene inutilmente complicata con dettagli già conosciuti al livello dell’intero sistema.

Il consiglio pertanto consiste nel ricorrere alla seguente tecnica: 1. riportare gli use case della sicurezza “isolati” dagli altri. Ciò è molto utile per specificare particolari comportamenti/requisiti. Per esempio, dopo che l’utente è stato riconosciuto, si potrebbe richiedere di verificare se il relativo profilo sia stato bloccato, oppure se la relativa password sia scaduta, ecc. 2. redigere la sezione del manufatto dei requisiti non funzionali (o eventualmente un apposito documento) descrivendo dettagliatamente anche il comportamento da applicare ai diversi casi d’uso.

UML e ingegneria del software: dalla teoria alla pratica

63

3. eventualmente nelle precondizioni dei vari use case citare le clausole standard: “l’utente è stato riconosciuto (autenticato) e autorizzato a eseguire il servizio” (da notare che la seconda clausola implica la prima) e nella descrizione del comportamento dinamico, far riferimento alla specifiche sezioni menzionate nel punto precedente.

Per esempio in un sistema Internet/Intranet di un’organizzazione potrebbe essere presente il vincolo che gli utenti possano accedere unicamente ai dati relativi al proprio dipartimento. Questa restrizione si potrebbe suddividere in due sezioni: • reperimento: i dati da mostrare all’utente devono essere filtrati in base al relativo dipartimento di appartenenza; • inserimento: tutti i dati devono riportare l’informazione relativa al dipartimento di appartenenza. Nel documento NFR potrebbe essere opportuno riportare queste due sezioni (ovviamente con maggiori dettagli) e quindi riferirle nei casi d’uso in ogni punto in cui si effettui una ricerca o si aggiorni il database.

e-commerce: un esempio In questo paragrafo viene presentata un’ampia sezione della use case view di un sistema del tutto originale: sito e-commerce. Visto che è stato citato abbondantemente negli esempi, si è deciso di presentarlo finalmente in maniera più organica. Quella che viene fornita è evidentemente una versione iniziale, uno scheletro facilmente espandibile per la descrizione di scenari reali (la realtà è sempre più complessa di quanto riportato nei libri). Per esempio non sono presi in considerazione servizi di supervisione, di risoluzioni anomalie, statistiche, ecc. Tipicamente, sistemi di questo tipo possono essere scomposti almeno in tre sottosistemi: il sito Internet ovviamente, il legacy system, e il layer di interfacciamento (wrapper) necessario per consentire ai due precedenti sistemi di interagire. Molto raramente accade che un’azienda decida di “lanciarsi” nel commercio elettronico partendo da zero, ossia avendo contestualmente la necessità di allestire il sito Internet e la propria infrastruttura informatica gestionale. Queste rarissime occasioni costituiscono il sogno — sì proprio di sogno si deve parlare — dei tecnici in quanto consentono di realizzare il tutto attraverso tecnologie omogenee e moderne — ossia permettono di far giocare i tecnici con nuovi giocattoli — e soprattutto evitano tanti problemi di connessione che si generano tra sistemi costruiti con tecnologie eterogenee.

64

Capitolo 4. Modellazione avanzata degli Use Case

Spesso i sistemi gestionali presenti presso i clienti (legacy system) risultano privi di meccanismi di interfacciamento con altri sistemi automatizzati, oppure risultano dotati di incomprensibili modalità di funzionamento con le quali, ahimè, è necessario interagire. Per esempio può capitare il caso di utilizzare sistemi che prevedano la quotazione degli articoli inseriti in un ordine solo dopo la conferma dell’ordine stesso. Come dire prima si effettua l’ordine e poi si viene a conoscenza del relativo importo. Il caso decisamente più frequente è rappresentato da aziende di dimensioni mediograndi, già dotate di ben collaudati sistemi di gestione dei propri processi interni (si tratta, tipicamente, di sistemi funzionanti su architetture hardware del tipo Mainframe, IBM AS400, IBM AIX, HP UX, Bull, ecc.), che decidono di sfruttare i vantaggi offerti dal commercio elettronico. In tutti questi casi è necessario realizzare, oltre al sito Internet, anche il famoso sistema wrapper in grado di realizzare la comunicazione con il legacy system. Chiaramente è lungi dalla volontà delle organizzazioni introdurre nuovi sistemi con la necessità di replicare i processi manuali di manutenzione dati. Per esempio è ovvio che si cerchi di evitare l’inserimento dei dati efferenti nuovi prodotti dapprima nel legacy system e poi nel sistema per il commercio elettronico, così come non si desidera reinserire manualmente nel sistema di legacy ordini effettuati online. Si esige chiaramente che i dati presenti in un sistema vengano replicati automaticamente negli altri. I sistemi di wrapper offrono un insieme di servizi prestabilito, ottenibili essenzialmente da quelli disponibili nel legacy system, presentandoli al mondo esterno attraverso opportune “interfacce” sfruttanti tecnologie moderne (CORBA, RMI, server socket, ecc.). Si Figura 12 — Schema di un sistema di wrapping. Interfacce CORBA/RMI/COM+

Protocollo proprietario

X1

Legacy Wrapper System Xn

oggetti/componenti java/C++/ ...

stored procedure routine RPG ...

65

UML e ingegneria del software: dalla teoria alla pratica

tratta di un layer proxy in grado di offrire una percezione diversa e decisamente più moderna di un sistema di legacy. In altre parole sono sistemi paragonabili ai politici del Belpaese: facce nuove che celano vecchie logiche. Nel caso presentato si è considerata unicamente l’ipotesi semplificata di sistemi che necessitino solo di uno scambio asincrono di informazioni. Nella realtà, spesso si verifica contestualmente la necessità di uno scambio sincrono e asincrono di informazioni tra sistemi. Si supponga per esempio di dover realizzare il sito Internet del legacy system in grado di quotare gli ordini solo dopo averne ricevuto conferma. In tal caso il sistema Internet dovrebbe inviare i dati dell’ordine e quindi attendere la risposta dell’elaborazione avvenuta nel legacy system al fine di visualizzare all’utente l’importo dell’ordine effettuato (Sembra assurdo vero? Non si sente la vocina interna dell’esperienza sbraitare?). Figura 13 — Macro funzioni del sistema oggetto di studio.

Gestione carrello della spesa

Consulta catalogo

Consulta offerte

Ricerca articolo

Effettua ordine

Utente

Spedizione ordini

Credits Authority

Ricezione esito elaborazione ordini

Effettua sottoscrizione

Aggiorna profilo

Invia dati catalogo

Legacy System

66

Capitolo 4. Modellazione avanzata degli Use Case

Prima di procedere con l’illustrazione dei vari diagrammi, risulta molto utile riportare l’elenco di tutti gli attori previsti con le relative descrizioni, come illustrato nel capitolo precedente (modello per la documentazione degli attori). Si tratta di un semplice esercizio demandato ai lettori. Non è infrequente anche l’illustrazione di un primo use case diagram con i vari macroservizi offerti dal sistema. L’obiettivo è appunto mostrare le principali funzioni, senza però fornirne alcun dettaglio: una sorta di indice grafico. Volendo è possibile paragonarlo al diagramma Top Level previsto dalla metodologia DFD. Ovviamente si tratta di un solo livello di decomposizione: guai altrimenti; si rischierebbe di cadere nella trappola della decomposizione funzionale. Nel sistema oggetto di studio la relativa struttura dovrebbe risultare simile a quella riportata nella fig. 13. Si cominci con il considerare le prime due funzionalità basilari fornite dal sito Internet: registrazione di un nuovo utente (effettua sottoscrizione) e aggiornamento del relativo profilo (aggiorna profilo). Template 25 n. Iterazione 1°

Autore LVT

Consegna 10 II 2001

Descrizione Initial draft

Figura 14 — Servizi di sottoscrizione e aggiornamento profilo utente.

Effettua sottoscrizione

«extend»

Invia conferma e password via e-mail «include»

Reperisce offerte

Utente Validazione utente

Aggiorna profilo

67

UML e ingegneria del software: dalla teoria alla pratica

Template 26 CASO D’USO:

Data: Effettua sottoscrizione.

UC_ECMMRC01 Descrizione:

Versione: 0.00.002

Consente agli utenti di registrarsi al fine di poter effettuare ordini di acquisto on-line presso il sito. Minuti. Elevata.

Durata: Priorità: Attore primario:

Utente. Ha interesse di registrarsi presso il sito per poter fruire dei servizi offerti dal sistema. L’utente dispone di una connessione Internet.

Precondizioni: Garanzie di fallimento: Garanzie di successo: Avvio:

01/II/2001

L’utente non viene registrato. Il sistem a visualizza un appropriato messaggio di errore. L’utente viene registrato correttamente ed il sistema provvede ad inviargli l’e-mail di conferma sottoscr izione. L’utente richiede esplicitamente di registrarsi.

Scenario principale. UTENTE 1.

Esegue esplicitamente la funzione di registrazione presso il sito.

2. 3.

Visualizza gli accordi commerciali (il contratto) e chiede conferma a procedere Conferma la propria volontà di procedere.

4. 5.

SISTEMA

Visualizza la pagin a con la richiesta dati dell’utente (profilo) Imposta i proprio dati e quindi e ffettua un click sul bottone procedi.

6. 7.

Verifica i dati dell’utente. Genera password iniziale

8.

Punto di estensione: Invia conferma e password via e-mail Condizione L’utente confermi la registrazione

9.

Memorizza i dati permanentemente nel database del sistema Visualizza messaggio di avvenuta registrazione.

10.

68

Capitolo 4. Modellazione avanzata degli Use Case

Template 27 Scenario alternativo: I dati impostati dall’utente non risultano corretti. 6.1. 6.2.

Sistema: Visualizza un messaggio indicante i dati ritenuti non corretti. Sistema: Riprende dal punto 4.

Scenario di errore: L’utente richiede di terminare anticipamene il servizio. Punti: 3, 5. 3.1. Sistema: Visualizza apposito messaggio. 5.2. Sistema: Termina l’esecuzione dello use case con insuccesso. Scenario di errore: Tentativo di invio e-mail fallito. 8.1 Sistema: Comunica momentanea non disponibilità del sist ema. 8.2. 8.3.

Sistema: Esegue procedura di notifica situazione anomala Sistema: Termina l’esecuzione dello use case con insuccesso.

Annotazioni. 6 Per quanto concerne i dati da impostare fa riferimento al prototipo di interfaccia utente XX.YY. I dati relativi la registrazione di un utente rimangono confinati nel database del sito fino all’effettuazione del primo ordine. Il relativo invio al sistema di legacy avviene associandoli ai dati relativi al primo ordine effettuato dell’utente. A questo punto l’utente viene memorizzato anche nel sistema di back office operante presso l’organizzazione e quindi l’utente ne diviene un cliente a tutti gli effetti.

Un primo elemento di riflessione potrebbe essere relativo alla necessità di dettagliare nei casi d’uso i campi appartenenti al profilo utente. Ebbene nella pratica, purtroppo, risulta fondamentale stabilire anche dettagli di questo tipo. A dire il vero essi dovrebbero essere specificati nel modello del business attraverso opportuno diagramma delle classi e nel modello della GUI. Eventualmente, queste informazioni andrebbero inserite nel template illustrato nel capitolo successivo (“Modello per le interfacce”), nel quale è possibile specificare quali dati sono obbligatori e quali no. Riportare questi dettagli comunque permette di evitare continue richieste di variazione del disegno della pagina HTML dipendenti dal personaggio dell’organizzazione del cliente che la analizzi. Poiché però è comunque diritto del cliente variare le specifiche e quindi anche i campi da visualizzare, nell’eventualità che ciò accada è possibile dimostrare, prove alla mano, che si tratta di un vero e proprio change request e come tale implica specifiche conseguenze come per esempio la dilatazione dei tempi di consegna. Un secondo motivo di riflessione potrebbe scaturire dall’analisi del comportamento del sistema in caso di gravi anomalie quali per esempio impossibilità di inviare e-mail o di

UML e ingegneria del software: dalla teoria alla pratica

69

memorizzare i dati del nuovo cliente (quest’ultima non è stata neanche citata). In tal caso è prevista una generica funzione di notifica situazione anomala. Verosimilmente questa funzione dovrebbe prevedere l’aggiornamento di un apposito file di log e l’invio di messaggi a un operatore umano (quale per esempio webmaster) al fine di avvisarlo dell’anomalia. Questa considerazione potrebbe suggerire di inserire tale operatore nel ruolo di attore. Si è deciso di non visualizzare questi aspetti sia perché attinenti all’area tecnica, sia perché si tratta di comportamenti che dovrebbero essere dichiarati in un apposito documento e quindi applicati consistentemente ogniqualvolta occorra un problema tecnico. La considerazione finale è relativa alla comunicazione descritta tra l’utente e il sistema. Come si può facilmente notare sia nel precedente diagramma, sia in quelli mostrati di seguito, si tratta di scambi di messaggi interattivi e prolungati nel tempo.

In base alle direttive formali dello UML le comunicazioni tra attori e sistema dovrebbero essere modellate, quanto più possibile, in modo asincrono. Logica conseguenza sarebbe che ogni diversa tipologia di messaggio originata da un attore dovrebbe avviare uno specifico caso d’uso. In questo contesto si è deciso di non ricorrere a tale approccio perché avrebbe finito per complicare notevolmente la fruizione dei requisiti senza apportare alcun reale vantaggio.

Considerando la descrizione nel precedente template e volendo aderire il più possibile alle direttive formali, sarebbe stato necessario riportare un caso d’uso atto a inviare all’utente le informazioni relative gli accordi commerciali (punto 2), un altro per ricevere il relativo feedback (punto 3), un altro ancora per la richiesta dei dati profilo utente (punto 4), un altro per l’acquisizione dei relativi dati (punto 6) e così via. Come al solito il premio per il diagramma più formale non è ancora stato istituito, mentre lo stress generato da problemi di comunicazione con il cliente è più che reale. Come si può notare, se il presente caso d’uso di template 28 termina con insuccesso non viene preso alcun provvedimento, in quanto si tratta di una responsabilità dello use case “chiamante”.

70

Capitolo 4. Modellazione avanzata degli Use Case

Template 28 CASO D’USO:

Data: Reperisce offerte

UC_ECMMRC03 Descrizione:

01/II/2001

Versione: 0.00.001

Durata: Priorità:

Verifica se siano presenti delle offerte commerciali valide da comunicare al cliente. Secondo. Elevata.

Precondizioni: Garanzie di fallimento:

Nessuna offerte viene reperta.

Garanzie di successo:

Vengono reperite le offerte commerciali preparate secondo un opportuno formato (per esempio pagine HTML).

Scenario principale. SISTEMA 1. Verifica se nel sistema sono presenti offerte valide. 2. 3.

Preleva i dati relativi le offerte commerciali Impagina i dati offerte in funzione al template specificato.

Scenario alternativo: Nel sistema non sono presenti offerte valide. 1.1. Sistema: Termina l’esecuzione senza caricare alcuna offerta.

Template 29 CASO D’USO:

Data: Reperisce offerte

UC_ECMMRC03 Descrizione:

01/II/2001

Versione: 0.00.001

Durata: Priorità:

Verifica se siano presenti delle offerte commerciali valide da comunicare al cliente. Secondo. Elevata.

Precondizioni: Garanzie di fallimento:

Nessuna offerte viene reperta.

Garanzie di successo:

Vengono reperite le offerte commerciali preparate secondo un opportuno formato (per esempio pagine HTML).

Scenario principale. SISTEMA 1. Verifica se nel sistema sono presenti offerte valide. 2. 3.

Preleva i dati relativi le offerte commerciali Impagina i dati offerte in funzione al template specificato.

Scenario alternativo: Nel sistema non sono presenti offerte valide. 1.1. Sistema: Termina l’esecuzione senza caricare alcuna offerta.

UML e ingegneria del software: dalla teoria alla pratica

71

Questo use case appartiene alla categoria di quelli che, dal punto di vista del comportamento dinamico, potrebbero essere tranquillamente omessi dal relativo diagramma dei casi d’uso: sarebbe sufficiente citare un paio di righe nella descrizione del caso d’uso invocante. Durante la realizzazione della use case view si incontrano molti esempi di questo tipo.

Nel dubbio se evidenziare o meno alcuni casi d’uso, bisogna prestare attenzione a non commettere l’errore di pensare in termini implementativi. Ciò che dal punto di vista tecnico potrebbe essere poco rilevante, perché magari richiederebbe solo alcune linee di codice, dal punto di vista del cliente, potrebbe avere un’importanza elevata. Questo è il caso dello use case in questione. Il cliente è interessato a evidenziare che l’utente, all’atto della registrazione, venga informato di eventuali offerte promozionali. Bisogna sempre tendere al giusto compromesso. Se da un lato mostrare l’organizzazione di alcune funzioni nei diagrammi dei casi d’uso ne può facilitare la comprensione, allo stesso tempo, eccedendo nel dettaglio si corre unicamente il rischio di generare diagrammi eccessivamente complicati e di non facile comprensione. Ancora più grave, si corre il rischio di iniziare a disegnare il sistema (per decomposizione funzionale) con gli strumenti errati e senza tener presenti aspetti molto importanti non ancora analizzati/disponibili.

Dall’esame della descrizione dello use case riportato in template 30 è possibile constatare che non si menziona mai esplicitamente il numero massimo di fallimenti consecutivi consentiti all’utente. Si tratta di un buon espediente sia per non vincolare la funzione stessa, sia per limitare il lavoro le correzioni da svolgere in caso di variazioni. Il dettaglio di queste informazioni andrebbe riportato nel documento delle regole di business.

72

Capitolo 4. Modellazione avanzata degli Use Case

Template 30 CASO D’USO:

Data: Validazione utente

UC_ECMMRC04 Descrizione: Durata: Priorità: Precondizioni: Garanzie di fallimento:

Garanzie di successo: Avvio:

01/II/2001

Versione: 0.00.001

Si occupa di eseguire le funzioni necessarie per riconoscere l’utente connesso. Minuti. Elevata. L’utente non viene riconosciuto e quindi non viene abilitato a fruire delle funzioni ritenute sensibili per il sistema. L’utente viene riconosciuto e quindi viene create la relativa sessione. L’utente esegue la procedura di log-in.

Scenario principale. UTENTE SISTEMA Segmento di estensione: valida utente 1.

Verifica che il numero di tentativi effettuati dal cliente sia inferiore a quello previsto. Visualizza la finestra con la richiesta della login e password.

2. 3. 4.

5. 6. 7.

Imposta login e password. Verifica che la coppia di valori (login, password) individui un cliente nel database del sistema Verifica che il profilo utente non sia stato bloccato. Verifica che la password dell’utente non sia scaduta Trasferisce in memoria i dati del clie nte.

Scenario alternativo: Verifica login e password fallita. 3.1. Sistema: Riprende dal punto 1. Scenario di errore: Tentativi effettuati pari al numero massimo previsto. 1.1.

Sistema: Termina l’esecuzione dello use case con insuccesso.

Scenario di errore: L’utente decide di non impostare login e password. 3.1. Sistema: Termina l’esecuzione dello use case con insuccesso. Scenario di errore: Profilo utente bloccato. 5.1. Sistema: Termina l’esecuzione dello use case con insuccesso. Scenario di errore: Password scaduta. 6.1. Sistema: Termina l’esecuzione dello use case con insuccesso. Annotazioni. 6.1. Cosa fare nel caso che la password dell’utente sia scaduta? non autorizzarlo oppure forzarlo a cambiare password?.

Semplicemente

73

UML e ingegneria del software: dalla teoria alla pratica

Template 31 CASO D’USO:

Data: Aggiorna profilo

UC_ECMMRC05 Descrizione: Durata: Priorità: Attore primario:

Precondizioni:

01/II/2001

Versione: 0.00.001

Permette ad un utente precedentemente registrato di modificare i dati efferenti il proprio profilo. Minuti. Media. Utente. Ha interesse ad aggiornare i dati efferenti il proprio profilo. L’utente registrato nel sistema (esista il relativo prof ilo) e sia stato autorizzato ad eseguire il servizio.

Garanzie di fallimento: Garanzie di successo:

Il profilo dell’utente non viene aggiornato. Le modifiche apportate dall’utente al proprio profilo vengono memorizzate permanentemente nel sistema.

Avvio:

L’utente richiede di eseguire aggiornamento del proprio profilo.

la

funzione

di

Scenario principale. 1.

UTENTE L’utente richiede esplicitamente di eseguire la funzione di aggiornamento profilo.

2. 3.

SISTEMA

Visualizzazione della pagina profilo utente con i relativi dati impostati Modifica i dati del pr opri profilo, eventualmente anche la password

4.

Verifica i dati impostati dall’utente.

5.

Aggiorna il profilo dell’utente

Scenario alternativo: Utente annulla la funzione di aggiornamento del proprio profilo. 3.1. Sistema: Termina l’esecuzione dello use case con insuccesso. Scenario alternativo: Dati impostati non corretti. 5.1. Sistema: Visualizza un messaggio con indicati i dati ritenuti non corre tti. 5.2.

Sistema: Riprende l’esecuzione dal punto 3.

Dall’analisi della descrizione del comportamento dinamico del presente caso d’uso emerge chiaramente che esiste una sezione comune con lo use case di registrazione utente. Ciò potrebbe spingere a cercare di strutturare ulteriormente i casi d’uso per mezzo di relazioni di generalizzazione. Benché possibile, probabilmente, in questo caso non è opportuno. Si ricordi che l’obiettivo di questa fase è catturare i requisiti del cliente, capire

74

Capitolo 4. Modellazione avanzata degli Use Case

cosa dovrà fare il sistema e non disegnare lo stesso. Eventuali mancanze di astrazione presenti in questa fase non incidono assolutamente sulle restanti e tantomeno sul disegno del sistema.

Template 32 n.

Osservazione

Autore

Necessario decidere il comport a- RV mento da adottare qualora la password dell’utente sia scaduta. (UC_ECMMRC04)

1

Stato Pending

Da notare che, sebbene non specificato esplicitamente, la modalità con cui i casi d’uso sono raggruppati e presentati, ne riflette l’organizzazione in package. Un altro insieme di use case presi in esami è quello relativo al reperimento delle informazioni relative agli articoli venduti nel sito per il commercio elettronico. Come evidenziato dal diagramma riportato nella descrizione dei casi d’uso, le funzioni di consultazione sono ritenute non sensibili per il sistema e quindi fruibili da tutti gli utenti anche da quelli non registrati. Chiaramente, qualora l’utente manifesti interesse per uno specifico articolo e desideri aggiungerlo nel proprio carrello della spesa, in quel caso si entrerebbe nell’insieme delle funzionalità ad accesso limitato ai soli utenti registrati. Questo passo si renderebbe necessario anche per reperire la specifica istanza (quella appartenente al cliente) del carrello della spesa in cui inserire l’articolo. Si tratta ovviamente di regole richieste dal committente del sito, ma nulla vieta di averne diverse.

Template 33

n. Iterazione 1°

Autore LVT

Consegna 12 II 2001

Descrizione Initial draft

75

UML e ingegneria del software: dalla teoria alla pratica

Template 34 CASO D’USO:

Data: Consulta catalogo

UC_ECMMRC10

05/II/2001

Versione: 0.00.001

Descrizione:

Permette ad un utente di consultare i prodotti mercanteggiati nel sito con modalità sia sequenziale, simile a quella con cui si sfogliano i cataloghi cartacei, sia ipertestuale (navigazione basata sui link presenti nelle varie pagine HTML). Nell’eventualità che un utente fosse interessato ad uno specifico articolo, la funzione ne consente inserimento nel carrello.

Durata: Priorità:

Minuti. Elevata.

Attore primario:

Utente. Ha interesse nel ricevere informazioni relative al catalogo degli articoli.

Precondizioni: Garanzie di fallimento:

Disponibilità del catalogo degli articoli. Nessun dato viene alterato.

Garanzie di successo:

L’utente naviga all’interno del catalogo ed eventualmente aggiunge specifici articoli nel proprio carrello della spesa. L’utente richiede di consultare il catalogo degli art icoli.

Avvio: Scenario principale. 1. 2. 3. 4. 5.

UTENTE Richiede esplicitamente di eseguire la funzione di consultazione articoli.

SISTEMA

Visualizza la pagina indice del catalogo. Seleziona la pagina da esaminare. Visualizza la pagina richiesta. Prosegue nella navigazione scorre ndo (avanti e indietro) le varie pagine, seguendo i link presenti nella stessa, tornando all’indice generale, oppure terminando la consultazione.

6.

Visualizzala pagina richiesta dall’utente

Scenario alternativo: Utente seleziona un articolo specifico. UTENTE 5.1. 5.2.

Seleziona un articolo specifico.

5.3.

Seleziona l’opzione di inserimento del prodotto nel carrello della spesa.

5.4.

SISTEMA

Visualizza la pagina di dettaglio dell’articolo selezionato.

Punto di estensione: aggiungi articolo nel carrello della spesa. Condizione: l’utente decide di aggiungere l’articolo al proprio carrello

76

Capitolo 4. Modellazione avanzata degli Use Case

Figura 15 — Use case per il reperimento delle informazioni. Consulta catalogo

Consulta offerte

«extend»

«extend»

Aggiungi articolo al carrello della spesa

Utente Ricerca articolo

«extend»

Una funzionalità che potrebbe essere interessante aggiungere è quella relativa alle segnalazioni di offerte: un utente potrebbe indicare uno specifico articolo come oggetto di proprio interesse e quindi richiedere al sistema di ricevere una comunicazione qualora, nell’arco di tempo specificato, l’articolo sia oggetto di specifiche offerte commerciali.

Template 35 CASO D’USO: UC_ECMMRC11 Descrizione: Durata: Priorità:

Aggiungi articolo al carrello della spesa

Data:

06/II/2001

Versione: 0.00.003

Permette di aggiungere l’articolo selezionato al carrello della spesa. Secondi. Elevata.

Precondizioni:

L’utente sia stato autenticato dal sistema, e abbia selezionato l’articolo che intende aggiungere al relativo carrello della spesa.

Garanzie di fallimento: Garanzie di successo:

Nessuna modifica viene apportata al carrello della spesa. L’articolo sele zionato dall’utente viene correttamente inserito nel relativo carrello della spesa.

Avvio:

L’utente richiede esplicitamente di inserire l’articolo selezionato nel relativo carrello della spesa.

Scenario principale. 1.

SISTEMA Reperisce il carrello della spesa dell’utente

2. 3.

Ottiene i dati salienti dell’articolo (compreso il prezzo) Memorizza i dati dell’articolo nel carrello della spesa.

Scenario alternativo: Il cliente non dispone di un carrello della spesa. 1.1. Il sistema genera una nuova istanza vuota del carrello.

77

UML e ingegneria del software: dalla teoria alla pratica

Template 36 CASO D’USO:

Data: Consulta offerte

UC_ECMMRC12 Descrizione:

06/II/2001

Versione: 0.00.001

Durata:

Permette all’utente di consultare eventuali offerte commerciali proposte nel sito ed eventualmente di inserire gli articoli oggetto di offerta nel carrello della spesa. Minuti.

Priorità: Attore primario:

Elevata. Utente.

Precondizioni: Garanzie di fallimento: Garanzie di successo:

Avvio:

Ha interesse nel ricevere informazioni relative ad eventuali prodotti in offerta. Siano presenti le tabelle relative alle promozioni commerciali. Il carrello della spesa del cliente non viene modific ato. L’utente fruisce della lista degli articoli in offerta ed eventualmente ne aggiunge alcuni nel proprio carrello della spesa. L’utente richiede di consultare la lista degli articoli in offerta.

Scenario principale. UTENTE 1. Richiede esplicitamente di consultare le offerte commerciali disponibili. 2. 3. 4.

SISTEMA

Reperisce la lista degli articoli in o fferta Visualizza la pagina commerciali valide.

con le offerte

Naviga le pagine delle offerte, eventualmente segue i link presenti e/o seleziona determinati articoli, oppure decidere di terminare la consultazione.

Scenario alternativo: Offerte commerciali non di sponibili. SISTEMA 2.1.

Non sono presenti offerte commerciali (oppure quelle presenti non soddisfano i criteri di applicabilità, come per esempio periodo scaduto).

2.2.

Visualizza apposito messaggio e conclude la funzione.

Scenario alternativo: L’utente seleziona un articolo specifico. UTENTE SISTEMA 4.1. 4.2.

Seleziona un articolo specifico.

4.3.

Seleziona l’opzione di inserimento dell’articolo nel carrello della sp esa-

4.4.

Visualizza la pagina di dettaglio dell’articolo selezionato.

Punto di estensione: aggiungi articolo nel carrello della spesa. Condizione: l’utente decide di aggiungere l’articolo al proprio carrello

78

Capitolo 4. Modellazione avanzata degli Use Case

Template 37 CASO D’USO:

Data: Ricerca articolo

UC_ECMMRC13 Descrizione: Durata: Priorità: Attore primario:

Precondizioni:

06/II/2001

Versione: 0.00.001

Permette all’utente di reperire uno specifico articolo ed eventualmente inserirlo nel proprio carrello della spesa. Minuti. Elevata. Utente. Ha interesse nel reperire informazioni relative a specifici articoli. Il catalogo degli articoli non sia vuoto.

Garanzie di fallimento: Garanzie di successo:

Il carrello della spesa del cliente non viene modific ato. L’utente reperisce l’articolo desiderato ed eventualmente lo inserisce nel proprio carrello della spesa.

Avvio:

L’utente richiede di eseguire la funzione di reperimento articoli.

Scenario principale. 1.

UTENTE Richiede esplicitamente l’esecuzi one della funzione di reperimento articoli

2. 3.

SISTEMA

Visualizza la pagina per la richiesta dei criteri di ricerca. Imposta il criterio di ricerca desiderato (codice, descrizione, categoria, limiti di prezzo, ecc.).

4.

Reperisce l’articolo risp ondente al criterio impostato dal cliente.

5.

Visualizza la pagina con l’elenco degli articoli rispondenti ai dati forniti dall’utente.

Scenario alternativo: Richiede l’inserimento dell’articolo nel carrello della spesa. 6.

UTENTE Seleziona l ’opzione di inserimento dell’articolo nel carrello della sp esa.

7.

SISTEMA

Punto di estensione: aggiungi articolo nel carrello della spesa. Condizione: l’utente decide di aggiungere l’articolo al proprio carrello

Scenario di errore: L’utente termina l’esecuzio ne della funzione. UTENTE SISTEMA 3.1.

3.2.

Non imposta alcun criterio e richiede di terminare l’esecuzione della funzione. Termina l’esecuzione della funzione.

Scenario di errore: Nessun articolo risulta rispondente alla chiavi di ricerca. SISTEMA 4.1. 4.2.

Non sono presenti nel catalogo articoli rispondenti al criterio impostato dall’utente. Termina l’esecuzione dello use case con errore.

79

UML e ingegneria del software: dalla teoria alla pratica

Template 38 n.

Osservazione

Autore

Stato

Template 39 n. Iterazione 1A

Autore LVT

Consegna

Descrizione

10 II 2001

Initial draft

Figura 16 — Use case gestione carrello della spesa.

Verifica carrello della spesa «include»

Gestione carrello della spesa

Rimuove articolo dal carrello della spesa

Aggiorna quantità Svuota carrello

Utente

80

Capitolo 4. Modellazione avanzata degli Use Case

Template 40 CASO D’USO: UC_ECMMRC17 Descrizione:

Gestione carrello della Spesa

Data:

08/II/2001

Versione: 0.00.003

Durata:

Permette all’utente di gestire l’elenco degli articoli inseriti nel relativo carrello della spesa, rimovendone alcune, modificando le quantità di altri, e così via. Minuti.

Priorità: Attore primario:

Elevata. Utente.

Precondizioni:

Ha interesse nel gestire le informazioni efferenti il proprio carrello della spesa. L’utente sia stato autorizzato ad eseguire il servizio e disponga di un’istanza del carrello della spesa.

Garanzie di fallimento: Garanzie di successo:

Il carrello della spesa non viene modificato. L’utente apporta le modifiche desiderate ai dati relativi agli articoli presenti nel relativo carrello della spesa.

Avvio:

L’utente richiede di eseguire la funzione di gestione del carrello della spesa.

Scenario principale. 1.

UTENTE L’utente richiede esplicitamente l’esecuzione del servizio di gestione del carrello della spesa.

SISTEMA

2.

Reperisce il carrello della spesa relativo all’utente.

3. 4.

Include(verifica carrello della spesa) Visualizza la pagina con i dati del carrello della spesa.

5.

Punto astratto: Modifica dati carrello della sp esa

Scenario di errore: Carrello della spesa dell’utente vuoto. 2.1. 2.2.

Visualizza apposito messaggio e conclude la funzione. Termina l’esecuzione dello use case con insuccesso.

81

UML e ingegneria del software: dalla teoria alla pratica

Template 41 CASO D’USO:

Data: Verifica carrello della spesa

UC_ECMMRC18 Descrizione:

Durata:

08/II/2001

Versione: 0.00.003

Esamina il carrello della spesa al fine di verificare se vi siano variazioni sostanziali nei dati impostati (prezzi degli articoli variati, artico li non più disponibili, offerte scadute ecc.). Minuti.

Priorità: Precondizioni:

Elevata. Il carrello della spesa del cliente è disponibile e vi sono degli articoli inseriti.

Garanzie di fallimento: Garanzie di successo:

Il carrello della spesa del cliente non viene modific ato. Il carrello della spesa viene analizzato ed eventuali incoerenze vengono opportunamente segnalate.

Scenario principale. SISTEMA 1. 2.

Preleva l’elenco degli articoli inseriti nel carrello della spesa. Verifica che tutti gli articoli siano ancora disponibili nel catalogo.

3. 4.

Reperisce i dati di tutti gli articoli ancora disponibili. Verifica che il prezzo degli articoli non sia variato nell’intervallo di tempo che va dal momento in cui l’articolo è stato in serito nel carrello della spesa al momento attuale.

5. 6.

Verifica che le eventuali offerte presenti non siano scadute. Verifica che per gli articoli inseriti non siano disponibili eventuali offerte non considerate.

Scenario alternativo: Presenti articoli nel carrello della spesa rimossi dal catal ogo. 2.1. Ognuno di essi viene segnalato ed un opportuno messaggio di non disponibilità viene aggiunto. Scenario alternativo: Il prezzo di alcuni articoli è variato. 4.1. Mostra il nuovo prezzo ed aggiunge opportuno messaggio informativo. Scenario alternativo: Alcune offerte presenti all’atto dell’inserimento dell’articolo nel carrello della spesa sono cessate. 5.1. Mostra il prezzo ordinario ed aggiunge opportuno messaggio informativo. Scenario alte rnativo: Per alcuni articoli è disponibile un’offerta non disponibile all’atto dell’inserimento dell’articolo nel carrello della spesa. 6.1. Aggiunge messaggio di segnalazione.

82

Capitolo 4. Modellazione avanzata degli Use Case

Template 42 CASO D’USO: UC_ECMMRC19 Descrizione: Durata: Priorità:

Rimuove articolo dal carrello della spesa

Data:

08/II/2001

Versione: 0.00.001

Permette all’utente di rimuovere articoli precedentemente inseriti nel carrello della spesa. Minuti. Elevata.

Precondizioni:

Il carrello della spesa del cliente è disponibile e vi sono degli articoli inseriti.

Garanzie di fallimento: Garanzie di successo:

Il carrello della spesa del cliente non viene modific ato. Dal carrello della spesa vengono rimossi gli a rticoli selezionati dall’utente.

Avvio:

L’utente richiede esplicitamente di rimuovere degli articoli presenti nel carrello della spesa.

Scenario principale. 1. 2.

UTENTE Seleziona uno o più articoli presenti nel carrello della spesa Preme il tasto di eliminazione degli articoli selezionati

3. 4. 5.

SISTEMA

Chiede conferma a procedere. Conferma l’eliminazione degli articoli Elimina gli articoli selezionati

Scenario di errore: L’utente non conferma l’eliminazione degli articoli. 4.1. Termina lo use case con insuccesso.

Il presente use case e i successivi dispongono di una descrizione del comportamento dinamico piuttosto ridotta. Ciò potrebbe portare alla conclusione che probabilmente si è scelto un eccessivo livello di granularità. Si tratta di un’opinione del tutto plausibile; in questo contesto si è deciso di riportare comunque questi use case per poter visualizzare, direttamente dall’analisi visiva dello use case diagram, quali funzioni siano disponibili nel menù di gestione del carrello della spesa.

83

UML e ingegneria del software: dalla teoria alla pratica

Template 43 CASO D’USO:

Data: Aggiorna quantità

UC_ECMMRC20 Descrizione: Durata: Priorità:

08/II/2001

Versione: 0.00.001

Permette all’utente di variare la quantità di un articolo inserito nel carrello della spesa. Minuti. Elevata.

Precondizioni:

Il ca rrello della spesa del cliente è disponibile e vi sono degli articoli inseriti.

Garanzie di fallimento: Garanzie di successo:

Il carrello della spesa del cliente non viene modific ato. Viene variata la quantità di alcuni articoli presenti nel carrello della spesa.

Avvio:

L’utente varia la quantità di uno specifico articolo presente nel carrello della spesa.

Scenario principale. 1.

2.

UTENTE Seleziona aggiorna il campo quantità riportato a fianco degli articoli presenti nella lista.

SISTEMA

Preme il tasto di aggiornamento delle quantità

3.

Memorizza le nuove quantità

4.

Ricalcala i vari totali

84

Capitolo 4. Modellazione avanzata degli Use Case

Template 44 CASO D’USO:

Data: Svuota carrello

UC_ECMMRC21 Descrizione:

08/II/2001

Versione: 0.00.001

Permette all’utente di eliminare tutti gli articoli presenti nel carrello della spesa. Secondi. Elevata.

Durata: Priorità: Precondizioni:

Il carrello della s pesa del cliente è disponibile e vi sono degli articoli inseriti.

Garanzie di fallimento: Garanzie di successo:

Il carrello della spesa del cliente non modificato. Il carrello della spesa viene svuotato.

Avvio:

L’utente richiede esplicitamente di sv uotare il carrello della spesa.

Scenario principale. UTENTE 1. 2. 3.

SISTEMA

Preme il tasto di eliminazione degli articoli presenti nel carrello. Chiede conferma a procedere. Conferma l’eliminazione degli articoli

4.

Elimina gli tutti gli arti coli dal carrello.

Scenario di errore: L’utente non conferma l’eliminazione degli articoli. 3.1. Termina lo use case con insuccesso.

Template 38 n.

Osservazione

Autore

Stato

Template 39 n. Iterazione 1A

Autore LVT

Consegna 10 II 2001

Descrizione Initial draft

85

UML e ingegneria del software: dalla teoria alla pratica

Figura 17 — Use case effettua ordine. Verifica carrello della spesa «include»

Seleziona articoli dal carrello della spesa «include»

Effettua ordine Utente «extend»

Modifica dati utente

Credits Authority

«extend»

Esegue transazione

L’attore identificato con il nome generico Credits Authority rappresenta uno di quei sistemi che offrono servizi automatici di controllo ed esecuzione di transazioni economiche digitali effettuate tramite carta di credito. Utenti di tali servizi sono prevalentemente organizzazioni commerciali. Il ciclo di vita tipico degli ordini non prevede l’immediato invio presso il legacy system: essi vengono invece parcheggiati in una cartella dedicata ove soggiornano attendendo che un apposito servizio temporizzato li prelevi, li organizzi in un messaggio e quindi, finalmente, li spedisca al legacy system. Template 45 CASO D’USO:

Data: Effettua ordine

UC_ECMMRC22 Descrizione:

10/II/2001

Versione: 0.01.001

Durata:

Permette all’utente di effettuare un ordine selezionando gli articoli precedentemente inseriti nel carrello della spesa. Minuti.

Priorità: Attore primario:

Elevata. Utente.

Precondizioni:

Ha interesse nell’effettuare un ordine di acquisto on-line. L’utente è stato autorizzato ad eseguire il servizio ed il carrello della spesa dell’utente non è vuoto.

Garanzie di fallimento: Garanzie di successo: Avvio:

L’ordine non vi ene effettuato e nessun importo viene addebitato al cliente. L’ordine viene confermato ed il relativo importo viene debitamente ascritto al cliente. L’utente richiede di eseguire la funzione di compilazione ordini.

86

Capitolo 4. Modellazione avanzata degli Use Case

Template 46 Scenario principale. UTENTE 1. 2.

Reperisce il carrello della spesa relativo all’utente. Include(seleziona articoli dal carrello della spesa)

3. 4.

Reperisce i dati relativi al profilo dell’utente. Visualizza la pagina con i dati dell’ordine impostati.

5. 6.

Verifica i dati ed eventualmente d ecide di modificare i propri.

7.

Punto di estensione: modifica dati utente. Condizione: L’utente intende modificare i propri dati. Richiede conferma a procedere.

8. 9. 10.

SISTEMA

Richiede esplicitamente di effettuare un ordine.

Conferma dati ordine

11.

Punto di estensione: esegue transazione condizione: l’utente conferma l’ordine Memorizza l’ordine.

12.

Svuota il carrello della spesa.

Scenario di errore: L’utente richiede di terminare anticipa tamente il servizio. Punti 6,9: 6.1. 6.2.

Visualizza apposito messaggio. Termina lo use case con insuccesso.

Scenario di errore: La selezione degli articoli presenti nel carrello della spesa termina con errore. 3.1. Visualizza apposito messaggio. 3.2.

Termina lo use case con insuccesso.

Scenario di errore: Credits Authority nega autorizzazione a procedere con la transazi one. 11.1. Visualizza apposito messaggio. 11.2.

Termina lo use case con insuccesso.

Da notare che non viene eseguita alcuna verifica in merito alla presenza di articoli nel carrello della spesa, in quanto ciò rappresenta una precondizione.

87

UML e ingegneria del software: dalla teoria alla pratica

Template 47 CASO D’USO: UC_ECMMRC23 Descrizione:

Seleziona articoli dal carrello della spesa

Data:

10/II/2001

Versione: 0.00.003

Durata:

Permette all’utente di selezionare gli articoli presenti nel carrello della spesa da incorporare nell’ordine di acquisto, eventualmente modificandone la quantità. Minuti.

Priorità: Attore primario:

Elevata. Utente.

Precondizioni: Garanzie di fallimento: Garanzie di successo:

Ha interesse nel selezionare gli articoli da inserire nell’ordine di acquisto. Il carrello della spesa dell’utente è disponibile e non è vuoto. Il carrello della spesa non viene modificato. L’utente seleziona la lista degli articoli da impostare nell’ordine, eventualmente variandone la quantità.

Scenario principale. UTENTE 1. 2.

SISTEMA Include(verifica carrello della spesa) Visualizza l’elenco degli articoli presenti nel carrello della spesa con eventuali segnalazioni scaturite dal controllo eseguito nel punto precedente.

3.

Seleziona gli articoli che intende far confluire nell’ordine e ventualmente variandone la quantità.

4. 4.

Preme il tasto di conferma. Genera la lista con selezionati dall’utente

gli

articoli

Scenario di errore: L’utente richiede di terminare anticipatamente la funzione. Punti 3, 4 3.1.

Termina lo use case ritornando la lista degli articoli vuota.

88

Capitolo 4. Modellazione avanzata degli Use Case

Template 48 CASO D’USO:

Data: Modifica dati utente

UC_ECMMRC24 Descrizione:

10/II/2001

Versione: 0.00.003

Durata:

Permette all’utente di modificare i propri dati e di impostarne altri di carattere fiscale (codice della carta di credito). Minuti.

Priorità: Attore primario:

Elevata. Utente.

Precondizioni:

Ha interesse a modificare i propri dati. È disponibile il profilo utente.

Garanzie di fallimento: Garanzie di successo:

I dati dell’utente non subisco alcuna modifica. L’utente imposta i dat i fiscali ed eventualmente modifica altri dati (quali per esempio indirizzo di spedizione, fascia oraria, ecc.)

Scenario principale. UTENTE

SISTEMA

1.

Visualizza la pagina con i dati dell’utente presenti nel sistema. Richiede di inserire i dati fiscali.

2. 3. 4.

Imposta i dati fiscali ed eventua mente modifica quelli generali. Preme il tasto di conferma.

l-

5. 6.

Richiede di selezionare l’indirizzo di spedizione. Seleziona uno degli indirizzi presenti oppure ne imposta un altro.

7.

Memorizza i dati impostati

Scenario di errore: L’utente richiede di terminare anticipatamente il servizio. Punti 3,4,7 3.1. 3.2.

Visualizza apposito messaggio Termina lo use case con insuccesso.

89

UML e ingegneria del software: dalla teoria alla pratica

Template 49 CASO D’USO:

Data: Esegue transazione

UC_ECMMRC26 Descrizione:

10/II/2001

Versione: 0.00.003

Durata:

Il sistema richiede l’autorizzazione ad eseguire transazioni commerciali per mezzo di carta di credito al fine di accreditare l’importo dell’ordin e. Secondi.

Priorità: Attore primario:

Elevata. Credits Authority.

Precondizioni:

Ha interesse nel riceve il messaggio di richiesta autorizzazione della transazione economica. I dati fiscali dell’ordine sono disponibili.

Garanzie di fallimento: Garanzie di successo:

Non viene eseguito alcun addebito al cliente. La transazione viene accettata e quindi l’importo dell’ordine viene accreditato sulla carta di credito del cliente.

Scenario principale. CREDITS AUTHORITY 1. 2. 3. 4.

Riceve la richiesta.

5. 6.

Esegue le verifiche del caso. Invia esito.

7.

SISTEMA Preleva tutti i dati necessari per la transazione. Organizza la richiesta secondo le specifiche della Credits Authority. Effettua la richiesta.

Verifica esito positivo autorizzazione.

Scenario di errore: Fallisce la richiesta del servizio. 4.1. 4.2.1.

Se si sono effettuati un numero di tentativi pari a quello definito. Esegue procedura di notifica situazione anomala.

4.2.2. 4.3.

Visualizza apposito messaggio e conclude la funzione con insuccesso. Numero di tentativi è inferiore a quello definito.

4.3.1. 4.3.2.

Incrementa il contatore di tentativi Attende t secondi e ritenta l’esecuzione del punto 4.

Scenario di errore: Scaduto time -out. 7.1. La risposta della credit authority non perviene entro l’intervallo di tempo stabilito. 8.2. 8.3.

Esegue procedura di notifica situazione anomala. Visualizza apposito messaggio e conclude la funzione con insuccesso.

Scenario di errore: Transazione rifiutata. 8.1. Comunica opportuno messaggio all’utente. 8.2. Termina lo use case con insuccesso. Annotazioni. 7.1. Nel caso di time -out è necessario investigare le misure da attuare. Reiterare la richiesta potrebbe portare ad un addebitamento ripetuto.

90

Capitolo 4. Modellazione avanzata degli Use Case

Template 38 n.

Osservazione

Autore

Stato

Template 39 n. Iterazione 1A

Autore LVT

Consegna 10 II 2001

Descrizione Initial draft

Figura 18 — Use case spedizione ordini.

Spedizione ordini «extend» [primo ordine effettuato dal cliente]

Timer Reperimento dati utente

Legacy System

Invio e-mail «extend»

Cliente Ricezione esito elaborazione ordini

Lo use case illustrato in fig. 18 mostra il processo che permette al sistema di comunicare al Legacy System gli ordini effettuati online. Il Legacy System, dal canto suo, origina messaggi relativi allo stato di avanzamento degli ordini (in lavorazione, spedizione merce, ecc.) con le efferenti informazioni. Si è deciso di visualizzare come attore il Legacy system anziché il sistema di wrapper in quanto si suppone che il software presente presso l’organizzazione del cliente sia predisposto per l’acquisizione/invio di messaggi tipicamente effettuati per mezzo di protocolli FTP.

91

UML e ingegneria del software: dalla teoria alla pratica

Template 50 CASO D’USO:

Data: Spedizione ordini

UC_ECMMRC30 Descrizione: Durata: Priorità:

10/II/2001

Versione: 0.00.002

Consente al sistema di inviare al legacy system gli ordini effettuati ed ancora residenti nel sito. Secondi. Elevata.

Attore primario:

Legacy system Riceve un messaggio con gli ordini effettuati on -line.

Precondizioni:

Siano disponibili degli ordini non ancora inviati al legacy system.

Garanzie di fallimento: Garanzie di successo:

Nessun ordine viene inviato al legacy system e gli stessi permangono nell’apposita directory del sistema. Tutti gli ordini in attesa di essere inviati vengono opportunamente organizzati in un messaggio e quindi trasferiti al legacy sytem

Avvio:

Attivazione temporizzato del processo di invio ordini.

Scenario principale. LEGACY SYSTEM

SISTEMA

1.

Verifica che ci siano ordini da trasferire al legacy system.

2. 3.

Reperisce tutti gli ordini da inviare Per ciascuno di essi organizza i dati secondo un apposito formato predefinito.

4.

Punto di estensione: Reperimento dati utente. Condizione: Si tratta del primo ordine del cliente.

5.

Trasferisce (tipicamente tramite protocollo FTP) il file al sistema l egacy.

6. 7.

Riceve il file con l’elenco dei recenti ordini effettuati dagli utenti del sito. Aggiorna lo stato degli ordini trasferiti (stato=inviati).

Scenario di errore. Il sistema fallisce il trasferimento del file degli ordini. 5.1. 5.2.1.

Se si sono effettuati un numero di tentativi pari a quello definito. Esegue procedura di notifica situazione anomala.

5.2.2. 5.3.

Visualizza apposito messaggio e conclude la funzione con insuccesso. Numero di tentativi è inferiore a quello definito.

5.3.1. 5.3.2.

Incrementa il contatore di tentativi Attende t secondi e ritenta l’esecuzione del punto 5.

92

Capitolo 4. Modellazione avanzata degli Use Case

Template 51 CASO D’USO:

Data: Reperimento dati utente

10/II/2001

Versione: 0.00.001

UC_ECMMRC31 Descrizione:

Reperisce i dati efferenti uno specifico utente.

Durata: Priorità:

Secondo. Elevata.

Precondizioni: Garanzie di fallimento:

È disponibile il codice dell’utente del quale si desiderano reperire i dati. Nessun informazione viene reperita.

Garanzie di successo:

Vengono reperiti i dati relativi all’utente specificato.

Scenario principale. SISTEMA Segmento di estensione: reperimento dati utente. 1. 2.

Preleva il codice dell’utente del quale si desidera reperire le relative informazioni. Reperisce le informazioni dell’utente.

Figura 19 — Ciclo di vita di un ordine

memorizzazione ordine transazione rifiutata

REGISTRATO transazione accettata modificato

CONFERMATO

transazione accettata

inviato al Legacy System

RESPINTO

ordine non accettato

INVIATO protocollato dal Legacy System

PRESO IN CARICO ordine spedito annullato

PENDING

93

UML e ingegneria del software: dalla teoria alla pratica

Template 52 CASO D’USO: UC_ECMMRC32 Descrizione:

Ricezione esito elaborazione ordini

Data:

10/II/2001

Versione: 0.00.002

Durata:

Consente al sistema di ricevere il file proveniente dal legacy sytem con l’elenco l’aggiornamento dello stato di elaborazione degli ordini inviati in precedenza. Per ognuno di essi è prevista apposita comunicazione via e’mail all’utente. Secondi.

Priorità: Attore primario:

Elevata. Legacy system

Precondizioni: Garanzie di fallimento: Garanzie di successo:

Avvio:

Invia un messaggio con lo stato di avanzamento degli ordini effettuati on-line. Diversi ordini effettuati on -line siano stati precedent emente inviati al Legacy system. Nessun ordine viene aggiornato. Lo stato degli ordini presenti nel file ricevuto viene aggiornato ed opportune comunicazione via e -mail vengono inviate agli utenti che hanno effettuato gli ordini. Il legacy system invia il file degli esiti dell’elaborazione degli ordini ricevuti.

Scenario principale. LEGACY SYSTEM 1.

SISTEMA

Invia il file con gli esiti degli ordini inviati in precedenza

2.

Acquisisce il file inviato dal legacy system

3.

Scorre tutto uno per uno tutti i record del file. Per ogni record letto:

3.1. 3.2.

- reperisce il relativo ordine dal db; - aggiorna lo stato dell’ordine letto in funzione a quanto specificato nel record del file;

3.3.

Punto di estensione: Invio notifica al cliente. Condizione: Aggiornamento ordine effettuato.

Scenario alternativo: Il sistema fallisce il reperimento dell’ordine d al proprio database. 3.1.1. Esegue la procedura di comunicazione situazione anomala. 3.1.2. Continua lo use case esaminando il record successivo. Scenario alternativo: Il sistema fallisce l’invio dell’e -mail al cliente. 3.3.1. Esegue la procedura di comunicazione situazione anomala. 3.3.2.

Continua lo use case esaminando il record successivo..

Scenario di errore: Il file ricevuto risulta vuoto. 2.1. Esegue la procedura di comunicazione situazione anomala. 2.2.

Termina con insuccesso lo use case.

94

Capitolo 4. Modellazione avanzata degli Use Case

Volendo dettagliare il ciclo di vita di un ordine, ossia gli stati nei quali può transitare e gli eventi che ne determinano la transizione da uno stato all’altro, si potrebbe allegare alla use case view un diagramma di stato abbastanza generale come quello visualizzato nella fig. 19.

Ogni qualvolta si ha a che fare con entità del business degne di nota, il cui ciclo di vita è limitato a un insieme ben definito di stati (in altre parole è descrivibile per mezzo di un automa a stati finiti), è opportuno tracciarne lo state chart diagram. Questo offre il vantaggio di chiarire specifici requisiti (stati in cui può transitare, eventi che ne generano il cambiamento stato, ecc.) in maniera chiara, molto intuitiva. Questi diagrammi permettono inoltre di verificare “cammini” che altrimenti potrebbero rimanere celati. In genere il diagramma di stato del ciclo di vita di un oggetto vale 1000 parole e fornisce documentazione molto valida anche per le fasi di costruzione del sistema.

Si tratta di un diagramma molto semplice e di facile comprensione: un ordine dopo essere stato memorizzato localmente nel sito si trova nella stato di registrato. Da questo stato transita in quello di confermato a seguito del ricevimento dell’autorizzazione della transazione da parte del Credits Authority. Una volta inviato al Legacy System, l’ordine passa allo stato di inviato permanendovi fino alla ricezione della risposta dell’elaborazione da parte del Legacy System. Ciò genera la transizione allo stato di preso in carico. Il ciclo di vita di un ordine termina quando l’organizzazione invia, al cliente, la mercanzia specificata nell’ordine stesso. Il diagramma visualizza l’esistenza di due stati di errore: • pending ossia il sistema non riesce a effettuare la transazione; • respinto quando il Legacy System per qualche motivo ha respinto l’ordine. n.

n. Iterazione 1A

Osservazione

Autore LVT

Autore

Consegna 10 II 2001

Stato

Descrizione Initial draft

95

UML e ingegneria del software: dalla teoria alla pratica

Figura 20 — Use case ricezione dati catalogo.

Invia dati catalogo

Legal System

La funzione Invia dati catalogo permette, come suggerito dal nome, di aggiornare il catalogo presente presso il sistema per il commercio elettronico con i dati inviati dal Legacy System. Gli aggiornamenti possono essere relativi all’inserimento di nuovi articoli, all’eliminazione di altri, alla modifica di alcune informazioni come il prezzo di un articolo o la relativa immagine e così via. Qualora si fosse realizzato lo use case per consentire la richiesta delle segnalazioni (ciascun cliente poteva selezionare un determinato articolo e quindi specificare le condizioni per ricevere segnalazioni di offerte ad esso efferenti, come per esempio riduzione del prezzo del 10%, oppure offerte 3 × 2, ecc.) lo use case relativo alla funzione di segnalazione vera e propria sarebbe stato un extend del presente.

96

Capitolo 4. Modellazione avanzata degli Use Case

Template 53 CASO D’USO:

Data: Invia dati catalogo

UC_ECMMRC33 Descrizione:

10/II/2001

Versione: 0.02.001

Durata:

Consente al sistema di ricevere il file con gli aggiornamenti da apportare al catalogo dei prodotti. Nello stesso file potrebbero essere presenti anche dati relativi ad offerte promozionali relativa a particolare prodotti. Secondi.

Priorità: Attore primario:

Elevata. Legacy system

Precondizioni:

Invia un messaggio con i dati relativi agli aggiornamenti da apportare al catalogo prodotti presente nel sito. Il sistema disponga di un catalogo articoli.

Garanzie di fallimento: Garanzie di successo:

Non viene modificato alcun prodotto. Gli aggiornamenti relativi al catalogo correttamente inglobati nel s istema.

Avvio:

Il legacy system invia il file con gli aggiornamenti del catalogo.

Scenario principale. LEGACY SYSTEM 1. Invia il file con gli aggiornamenti del catalogo

vengono

SISTEMA

2.

Acquisisce il file inviato dal legacy system.

3.

Scorre t utto uno per uno tutti i record del file. Per ogni record letto:

3.1.

- reperisce il relativo articolo dal db;

3.2.

- aggiorna l’articolo letto in funzione a quanto specificato nel record del file;

Scenario alternativo: Il sistema fallisce il reperime nto dell’articolo dal proprio database. 3.1.1. 3.1.2.

Esegue la procedura di comunicazione situazione anomala. Continua lo use case esaminando il record successivo.

Scenario di errore: Il file ricevuto risulta vuoto. 2.1. Esegue la procedura di comunic azione situazione anomala. 2.2. Termina con insuccesso lo use case.

UML e ingegneria del software: dalla teoria alla pratica

97

Ricapitolando… Nel presente capitolo vengono illustrate una serie di tecniche di modellazione dei casi d’uso, di orientamento specificamente operativo anche se, a volte, sensibilmente distante dalle direttive standard. Modellare sistemi reali mette in luce una serie di problemi e lacune raramente trattati nei libri e nelle specifiche ufficiali dello UML. La prima sezione è dedicata alla presentazione di un modello atto a descrivere il comportamento dinamico dei casi d’uso. Tale modello, in prima analisi, può essere suddiviso in quattro macrosezioni: 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. Nell’intestazione viene impostato un codice simbolico univoco per il caso d’uso, il nome, la data dell’ultima modifica e la versione. La sezione dedicata alle informazioni generali ospita la descrizione del caso d’uso (non più di qualche riga), la priorità, vincoli temporali relativi all’esecuzione dello stesso, gli attori divisi tra principale e secondari, le precondizioni, le garanzie e l’evento innescante (il trigger). Per ciò che concerne la descrizione è necessario riportare una breve illustrazione dei servizi offerti dal caso d’uso oggetto di studio senza però dilungarsi troppo: la descrizione completa viene specificata nei vari scenari. La priorità permette di evidenziare l’importanza assegnata alle funzionalità che il sistema dovrà fornire, al fine sia di suddividere gli use case in un’opportuna gerarchia di importanza, sia per poter assegnare in maniera più opportuna i vari casi d’uso alle varie iterazioni di cui si compone tipicamente un processo di sviluppo del software. Il processo di assegnazione delle priorità degli use case è un’attività molto importante e sensibile per l’esito dell’intero progetto software in quanto si ripercuote sulla pianificazione delle iterazioni dello stesso. Gli attori, come già indagato precedentemente, sono entità, persone o sottosistemi, interessati al comportamento del sistema. Tipicamente, nel contesto di uno stesso caso d’uso, gli attori vengono suddivisi in primari e secondari. I primi tipicamente forniscono lo stimolo iniziale che avvia l’esecuzione del caso d’uso e fruiscono del relativo servizio, i secondi invece, in genere, ricevono comunicazioni, dati, ecc. generati dallo stesso. Le precondizioni sono i requisiti che devono essere soddisfatti per poter eseguire il caso d’uso al quale si riferiscono: è compito del sistema verificarne l’adempimento prima di avviare l’esecuzione dell’efferente caso d’uso, mentre è responsabilità dell’utilizzatore di assicurarne il soddisfacimento. Il riscontro pratico delle precondizioni è che l’implementazione dell’efferente caso d’uso preveda una serie di controlli iniziali atti a verificare appunto il relativo soddisfacimento. Per ciò che concerne le garanzie è necessario citare esplicitamente oltre a quelle minime anche quelle di successo. Le prime, come suggerito dal nome, sono le più basilari assicura-

98

Capitolo 4. Modellazione avanzata degli Use Case

te dall’esecuzione del caso d’uso. Tipicamente si tratta delle garanzie assicurate dal sistema nel caso in cui non sia possibile fornire il servizio specificato in quanto l’esecuzione è viziata da condizioni di errore o comunque da anomalie rispetto al flusso principale del caso d’uso (best scenario). Specularmente alle garanzie minime, quelle di successo specificano ulteriori condizioni soddisfatte dal completamento dell’esecuzione del relativo caso d’uso, ossia dopo l’esecuzione dello scenario principale o al termine dell’esecuzione di una sua variante comunque di successo. Mentre è compito dell’utilizzatore del caso d’uso assicurare il soddisfacimento delle precondizioni, è responsabilità del sistema adempiere alle garanzie, qualora le prime siano soddisfatte. Per ciò che concerne il campo Avvio è necessario descrivere l’evento che determina l’inizio dell’esecuzione del sistema. La sezione successiva del modello è dedicata agli scenario. Tipicamente si distinguono tre tipologie: 1. scenario principale di successo (main success scenario): lo scenario che produce il servizio richiesto dall’attore primario in cui tutto funzione perfettamente; 2. 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. 3. scenari di fallimento o di errore (failure scenario): gli scenari che specificano le azioni da intraprendere nel caso in cui l’esecuzione di azioni dello scenario principale sia impossibilitata dal verificarsi di condizioni di errore. 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 relativo compimento, per poi fornire le condizioni anomale che possono intervenire e le azioni da intraprendere. Molto importante è evidenziare chiaramente quale entità (Attore, Sistema) svolge ciascuna azione. 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. L’illustrazione degli scenari alternativi effettuata attraverso opportuni template evidenzia il grande vantaggio offerto da questo formalismo rispetto a quello grafico, ove, tipicamente, è necessario disegnare un nuovo diagramma per ogni alternativa, il che richiede una quantità decisamente superiore di tempo per la realizzazione e rende difficoltosa la gestione delle relative modifiche.

UML e ingegneria del software: dalla teoria alla pratica

99

L’ultima sezione del modello prevede l’impostazione di eventuali annotazioni, che possono sia essere riferite all’intero use case, sia a singole azioni. L’utilizzo del modello descritto viene illustrato per mezzo di un primo esempio: sottosistema universitario atto a fornire a studenti e a docenti universitari una serie di servizi fruibili attraverso un browser Internet. In questa sede vengono considerati anche gli altri strumenti utilizzabili per modellare il comportamento dinamico dei casi d’uso, quali i diagrammi di sequenza e quelli di attività. Sebbene i formalismi grafici offrano tutta una serie di vantaggi (forma più intuitiva ed accattivante, superiore grado di memorizzazione ecc.), presentano anche una serie di svantaggi tali da renderli non preferibili al modello descritto in precedenza. In particolare, all’aumentare della complessità i diagrammi tendono a diventare inevitabilmente confusi, richiedendo una quantità di tempo maggiore e talune volte decisamente spropositata per realizzazione e successiva manutenzione. Il caso oggetto di studio successivo prevede l’analisi di alcuni componenti di un sistema di banking. Questo esempio offre tutta una serie di spunti importanti tra i quali quelli relativi al processo di sviluppo del software. In particolare si evidenzia come i diagrammi dei casi d’uso della fase di analisi debbano sia dettagliare i corrispondenti al livello dei requisiti (o di business come si preferisca), sia incorporare direttive provenienti dai requisiti non funzionali (NFR, non functional requirements) e dall’architettura, la quale a sua volta dipende dai NFR, nonché dagli use case stessi. Indipendentemente dalla modalità stabilita per descrivere il comportamento dinamico dei casi d’uso, qualora si decida di ricorrere a processi di sviluppo iterativi e incrementali, è utile prevedere una serie di informazioni supplementari, da associare ai vari manufatti prodotti, necessarie per tener traccia della relativa evoluzione. Queste informazioni dovrebbero riportare le varie versioni del manufatto, gli autori, la data di prevista o avvenuta consegna, la descrizione e così via. Chiaramente non c’è alcuna necessità di specificarle per ogni singolo caso d’uso, è sufficiente riportarle solo per le macrofunzionalità (in altre parole al livello di use case diagram). Probabilmente potrebbe risultare altrettanto conveniente disporre di un altro template, in qualche modo speculare al primo, collocato alla fine della descrizione del comportamento dinamico di ogni use case diagram. Lo scopo è di ospitare in un sol punto eventuali considerazioni del personale coinvolto nello sviluppo e nel controllo della qualità del modello. Il terzo caso di studio che conclude il capitolo prevede un’ampia sezione della use case view di un sistema del tutto originale: sito e-commerce. Si tratta di un sistema molto citato in tutto il libro, per cui si è deciso di presentarlo finalmente in maniera più organica.

Capitolo

5

Completamento dell’analisi dei requisiti Una modifica che eseguita in fase di analisi dei requisiti costa un dollaro, potrebbe costarne 1000 dopo il rilascio. [LVT]

Introduzione Il presente capitolo si pone un duplice obiettivo: presentare una tecnica dimostratasi particolarmente valida nell’arduo compito dell’analisi dei requisiti, e illustrare ulteriori manufatti (artifact) che permettono di completare il modello dell’analisi dei requisiti. Per quanto concerne il primo punto, lungi dall’idea di voler affrontare per l’ennesima volta le problematiche connesse con le analisi dei requisiti, in questo paragrafo si focalizza l’attenzione sul problema della fatale incomunicabilità che a volte si instaura tra i clienti, competenti della propria area di business, e il team dei tecnici esperti nella costruzione di sistemi informatici. Il problema, in ultima analisi, si riconduce al trovare una piattaforma di dialogo comune e costruttiva, una tecnica che rappresenti un valido raccordo tra i due diversi linguaggi tecnici: quello dell’area business utilizzato dagli utenti e quello informatico parlato dagli esperti di sistemi software. Come si è avuto modo di constatare nei capitoli precedenti, il problema dei diversi linguaggi è così importante da influenzare anche le diverse versioni del modello dei casi d’uso caratterizzate dal transitare via via dal mondo business a quello tecnico informatico. Un valido strumento di analisi è indubbiamente rappresentato dagli activity diagram; essi, oltre ai nomali vantaggi offerti da ogni formalismo grafico (aspetto accattivante, facilità di comprensione e memorizzazione, immediatezza, ecc.), ne offrono un altro tutto

2

Capitolo 5. Completamento dell’analisi dei requisiti

particolare e insuperabile: sono molto simili agli antenati flow chart. Chi non ne ha mai realizzato o interpretato uno? Chi non è in grado di comprenderli? (Qui la costante vocina dell’autore si fa sentire…) Pertanto, i diagrammi di attività si delineano come strumento ideale di dialogo tra utenti e team tecnico, almeno nelle prime fasi del ciclo di vita del software. Muovendosi da queste considerazioni, è possibile escogitare una tecnica molto efficace per catturare i requisiti utente, anche se un po’ agli antipodi con quelle classiche. Generalmente si tenta di descrivere la proiezione statica del modello dei requisiti per mezzo dei casi d’uso, poi se ne dettaglia la descrizione dinamica e infine si uniforma il tutto. Nella tecnica presentata l’approccio è diametralmente opposto: prima si cerca di capire il comportamento dinamico della funzione oggetto di studio per mezzo degli activity diagram e poi si risale alla relativa proiezione statica. Naturalmente, una volta redatti gli activity diagram, i vari template e/o flussi degli eventi assumono un interesse molto relativo: mostrerebbero le stesse informazioni con un formalismo diverso. Ovviamente è consigliabile evitare manufatti ridondanti essenzialmente per questioni di manutenzione: ogni modifica va ripetuta per i vari manufatti. Gran parte dei capitoli precedenti sono stati dedicati all’illustrazione della vista dei casi d’uso i quali, attraverso un formalismo amichevole e relativamente semplice, favoriscono i processi di analisi e descrizione delle funzionalità del sistema, conferendo particolare attenzione alla percezione che ne hanno gli attori. Il modello dei casi d’uso, pur essendo un artifact molto importante, da solo non è assolutamente in grado di fornire sufficienti informazioni per il pieno svolgimento delle restanti fasi del ciclo di vita del sistema. Inoltre, nello sviluppo di progetti Object Oriented guidati dal modello dei casi d’uso, esiste il rischio potenzialedi produrre modelli funzionali: tale rischio, oltretutto, è accresciuto da personale non espertissimo del formalismo ed era ben noto fin dall’inizio allo stesso Jacobson, tanto che egli stesso evidenziò (1991-92) la necessità di bilanciare il modello dei casi d’uso con altri manufatti, quali: il Domain Object Model, il documento di specifica delle interfacce, il documento di architettura (o meglio la versione disponibile nella fase di analisi dei requisiti) e così via. La completa realizzazione della vista dei requisiti del sistema, dunque, prevede la produzione di ulteriori manufatti complementari al modello dei casi d’uso. Il Domain Object Model, molto in breve è una versione del diagramma delle classi volto a fornire la rappresentazione Object Oriented del vocabolario vigente nello spazio del problema. In esso dovrebbero essere presenti unicamente entità appartenenti al mondo concettuale di business che il sistema in qualche modo dovrà automatizzare. A questo particolare modello è dedicato ampio spazio nel Capitolo 8.

Nei paragrafi successivi sono presentati i manufatti relativi alla specificazione delle interfacce, i test case, il documento dei requisiti non funzionali e le business rule.

UML e ingegneria del software: dalla teoria alla pratica

3

Una tecnica di analisi dei requisiti basata sugli activity diagram Presentazione Prima di addentrarsi nell’illustrazione della tecnica, si ritiene opportuno presentare un primo basilare esempio. Si consideri di studiare un sottosistema di workflow atto a gestire situazioni di malfunzionamento (eccezioni) che potrebbero insorgere nei vari componenti di un qualsivoglia sistema. Strumenti di questo tipo sono di frequente applicazione in sistemi a elevata disponibilità e con pressanti esigenze di recupero di situazioni anomale. In altre parole in sistemi come quelli bancari. Apparati di questo tipo prevedono che, a seguito del verificarsi di una anomalia di un certo rilievo non risolvibile automaticamente venga generata opportuna comunicazione da inviare al workflow. Compito di questo dispositivo è ricevere i dati relativi a tali anomalie, registrarle, assegnarvi la priorità e quindi convogliarle a un opportuno operatore umano in grado di risolvere l’anomalia. L’assegnazione della priorità tipicamente avviene in funzioni a parametri configurabili e la selezione dell’operatore a cui convogliare l’anomalia deve avvenire indirizzandosi a chi ha le competenze necessarie per risolvere quel particolare tipo di anomalia. Il diagramma di fig. 5.1 è abbastanza semplice e non richiede particolari spiegazioni: è proprio questo il vantaggio di utilizzare i diagrammi di attività! Il sistema riceve i dati relativi ad un’anomalia, genera il relativo work item (in sostanza vengono estratti i dati significativi), li memorizza, effettua l’analisi di quelli ritenuti salienti: codice identificativo del sistema che ha generato l’eccezione, stadio in cui si è verificato l’errore, tipologia, ecc. Quindi verifica se sia possibile raggruppare il work item con altri pendenti originati dalla stessa anomalia. Nel caso in cui la verifica sia positiva, avviene il raggruppamento e quindi la priorità del gruppo di appartenenza viene estesa al nuovo work item, mentre in caso negativo (il raggruppamento non è possibile) è necessario assegnare la priorità in base sia ai dati salienti dell’anomalia, sia ad opportuni criteri presenti nel sistema; come si vedrà di seguito, le regole utilizzate per calcolare la priorità sono esempi di business rule. Una volta superata anche questa fase, viene selezionato l’operatore appropriato, ancora una volta combinando i dati salienti dell’anomalia con quelli relativi agli operatori connessi al sistema — considerando ovviamente anche il carico di lavoro pendente su di essi — e quindi l’anomalia viene assegnata. Tipicamente, gran parte del lavoro viene risolto in fase di configurazione: tutte le tipologie di anomalie previste dal sistema vengono catalogate e raggruppate. Per ciascun gruppo identificato si predispone un’opportuna coda in cui inserire i dati delle varie anomalie. Le code sono quindi associate a ruoli da assegnare ai vari addetti. L’operatore riceve la segnalazione e, in base alle informazioni contenute, esegue determinate procedure atte a risolvere il problema. Risolta l’anomalia, notifica l’avvenuto assolvimento, eventualmente descrivendo le operazioni eseguite. Il sistema quindi riceve la segnalazione di avvenuta risoluzione del problema e, per anomalie non note, ne memorizza la soluzione.

4

Capitolo 5. Completamento dell’analisi dei requisiti

Figura 5.1 — Activity diagram di un gestore di anomalie.

Sistema

Errors Workflow System

Operatore

Comunicazione errore Ricezione segnalazione errore

Generazione work item

Memorizzazione work item

Analisi dati work item

Verifica regole di raggruppamento [Raggruppamento possibile]

[Raggruppamento non possibile]

Raggruppamento work item

Attribuzione priorita'

Selezione operatore

Emissione comunicazione operatore Presa in carico work item

Soluzione anomalia

Comunica conferma soluzione work item Analisi descrizione soluzione

[Nuova tipologia]

[Soluzione nota]

Memorizza soluzione

5

UML e ingegneria del software: dalla teoria alla pratica

Figura 5.2 — Activity diagram di un sistema di gestione delle anomalie con evidenziati i diversi flussi. Sistema

Errors Workflow System

Operatore

Comunicazione errore Ricezione segnalazione errore

Generazione work item

Memorizzazione work item

Analisi dati work item

Verifica regole di raggruppamento

Raggruppamento work item

Attribuzione priorita'

Selezione operatore

Emissione comunicazione operatore Presa in carico work item

Soluzione anomalia

Comunica conferma soluzione work item Analisi descrizione soluzione

Memorizza soluzione

6

Capitolo 5. Completamento dell’analisi dei requisiti

Un passo propedeutico da utilizzarsi per “estrarre” i diagrammi dei casi d’uso dai relativi dall’activity diagram consiste nell’evidenziare i messaggi che il sistema scambia con i relativi attori. Generalmente, il percorso compreso tra due messaggi consecutivi rappresenta un’unica funzionalità da descriversi attraverso uno use case.

Per esempio la parte di activity diagram compresa tra la ricezione di una segnalazione di errore e la comunicazione del relativo work item all’Operatore rappresenta logicamente uno use case. Ora, verosimilmente, tale use case risulterebbe notevolmente complesso da rappresentare attraverso un solo caso d’uso, e quindi è opportuno ripartire alcune funzionalità in altri casi d’uso inclusi o estesi. Analogamente la porzione di activity diagram compresa dalla ricezione del messaggio di conferma soluzione fino alla fine delle attività si presta a essere rappresentata per mezzo di un caso, eventualmente da espandere.

La parte di percorso relativa alla diramazione del flusso — quella generata dagli elementi condizionali che nella fig. 5.2 sono mostrati con una tonalità più scura rispetto a quella del flusso principale — rappresenta verosimilmente un’alternativa rispetto al flusso base. Tali flussi si prestano a essere descritti o attraverso specifici use case estendenti o, più semplicemente, attraverso dei flussi alternativi. La decisione dipende da vari fattori, come per esempio l’importanza che si desidera assegnare al flusso (use case distinti conferiscono maggiore enfasi), la corposità del comportamento da descrivere, gli obiettivi che permettono di raggiungere (se le post-condizioni sono diverse da quelle del flusso base allora molto probabilmente si tratta di uno use case separato) ecc.

Come si può riscontrare dall’analisi del diagramma dei casi d’uso mostrato in fig. 5.3, il relativo livello di dettaglio è decisamente inferiore a quello presente nell’activity diagram (ci sono meno informazioni). Molte attività ritenute di secondaria importanza (Memorizza work item, Analisi dati work item, ecc.) sono state inglobate in uno use case più generale (Gestione work item). A questo punto può insorgere qualche problema. Gli utenti, nell’esaminare i diagrammi dei casi d’uso ottenuti da quelli delle attività realizzati con il loro contributo, tipicamente si attendono di vedere una corrispondenza biunivoca tra i casi d’uso e le attività presenti nei relativi diagrammi. Ciò chiaramente non sempre è consigliabile. Diversi tool commerciali permettono di tenere traccia automaticamente di tali corrispondenze. In casi

7

UML e ingegneria del software: dalla teoria alla pratica

Figura 5.3 — Use case del sistema di gestione anomalie.

Assegna priorità Esegue raggruppamento

Selezione operatore «extend»

«extend»

«include»

Gestione work item «include»

Invia notifica errore Operatore

Sistema "Assistito" Conferma soluzione anomalia «extend»

Memorizzazione soluzione

estremi è possibile realizzare apposite matrici di “corrispondenza”, in cui si specifica l’allocazione delle attività ai casi d’uso. Il problema, ancora una volta, è che la gestione di tali tabelle è un’attività decisamente dispendiosa. Si consiglia pertanto di evitare per quanto possibile il ricorso a queste tabelle: richiedono molto tempo per essere realizzate e necessitano di essere riviste ogniqualvolta si ristrutturano i diagrammi dei casi d’uso a cui si riferiscono. Un’altra constatazione è relativa alle attività inserite in diramazioni del flusso (costrutti if then else) presenti negli activity diagram (attività da svolgere solo al verificarsi di precise condizioni come per esempio Assegna priorità ed Esegui raggruppamento ). Come specificato precedentemente, quelle ritenute importanti, e quindi da visualizzare anche nel diagramma dei caso d’uso (Memorizzazione soluzione), si prestano ad essere riprodotte per mezzo della relazione di estensione. In alcuni casi si può ricorrere anche a relazioni di generalizzazione. Ciò, quando possibile e badando bene al significato semantico, permette di rappresentare più chiaramente l’esecuzione alternativa di precise attività. Per esempio nel diagramma dei casi d’uso precedente si sarebbe potuto ricorrere a questo espediente per strutturare le attività di Assegna priorità ed Esegui raggruppamento. A essere onesti, in questo caso l’utilizzo sarebbe stato artificioso

8

Capitolo 5. Completamento dell’analisi dei requisiti

Tabella 5.1 — Matrice di mapping tra use case e activity. Conferma risoluzione anomalia

Memorizzazione soluzione

——

——

——

Soluzione anomalia

——

——

——

——

——

——

——

×

Generazione work item

×

Memorizzazione work item

×

Analisi dati work item

×

Verifica regole di raggruppamento

× ×

Raggruppamento work item

×

Attribuzione priorità

×

Selezione operatore ×

Emissione comunicazione operatore

Conferma soluzione anomalia Memorizzazione soluzione

Selezione operatore

——

Ricezione segnalazione errore

Assegna priorità

——

×

Esegue Raggruppamento

——

Comunicazione errore

Gestione work item

——

Activity

Invia notifica errore

Presa in carico work item

Use Case

× ×

e quindi non di facile comprensione: il raggruppamento non può essere considerato una specializzazione dell’assegnazione della priorità sebbene, qualora eseguito, generi come effetto collaterale l’assegnazione automatica della priorità. L’iterazione con l’attore Operatore è stata suddivisa in due use case: uno inserito in quello denominato Gestione work item per l’invio della comunicazione all’attore e l’altra nel caso d’uso Conferma risoluzione anomalia al fine di ricevere il relativo feedback.

Si ricordi che nello UML è preferibile modellare le interazioni in modo asincrono. Ciò però non implica necessariamente che il sistema le realizzi secondo tale criterio: nel contesto dell’analisi dei requisiti è necessario catturare i requisiti utente e non le soluzioni.

9

UML e ingegneria del software: dalla teoria alla pratica

Figura 5.4 — Frammento semplificato di un activity diagram utilizzato per dettagliare la procedura di approvazione di primo livello delle richieste di variazione profilo utente.

Addetto Sicurezza

Esegue la funzione di approvazione richieste

Sistema

Team Leader

Supervisore sicurezza

Reperisce i dati dell'utente

Reperisce le richieste in coda dell'addetto Visualizza la lista con le richieste pendenti Eventualmente seleziona una richiesta dalla lista Valuta i dati impostati dall'utente [richiesta terminazione servizio] [l'utente seleziona una richiesta] Reperisce i dati di dettaglio della richiesta Verifica che la richiesta non sia relativa allo stesso addetto Sia in caso di approvazione, sia di rigetto, l'utente puo' associare propri commenti.

Business rule X.Y: Non e' permesso ad un addetto alla sicurezza di approvare una richiesta a lui riferita.

[richiesta riferita allo stesso utente] [richiesta riferita ad un altro utente] Visualizza i dettagli dell'utente e della richiesta

Effettua la selezione Valuta la selezione operata dall'utente [richiesta terminazione servizio] [effettuata selezione] Aggiorna lo stato della richiesta

Verifica esito della selezione

Invia comunicazione al supervisore della sicurezza Riceve notifica Memorizza motivazione

Invia comunicazione al supervisore al team leader Riceve notifica

10

Capitolo 5. Completamento dell’analisi dei requisiti

Figura 5.5 — Frammento dello use case diagram relativo all’activity riportato nella fig. 5.4.

Reperisce richieste pendenti in carico all'utente «include»

Valuta richieste pendenti Addetto Sicurezza

Approva richiesta

Rigetta richiesta

Supervisore Sicurezza

Team Leader

Riflessione conclusiva di questo primo esempio: tipicamente è necessario riportare un ulteriore caso d’uso generale (Gestione work item) con funzioni sia di collante dei vari casi d’uso (una sorta di main che invoca sottoroutine), sia di raccoglitore delle funzionalità ritenute di eccessivo livello di dettaglio (il contenitore del flusso principale o main scenario). Il secondo esempio presentato è stato introdotto al fine di illustrare l’utilizzo della relazione di generalizzazione. In particolare viene riportato un frammento del processo, vigente presso un’ipotetica organizzazione (per esempio una banca), atto ad assegnare ai propri dipendenti i profili (profile) per l’accesso al sistema. In questo contesto si suppone che ogni utente disponga di un solo profilo operativo.Tale profilo stabilisce quali funzioni l’utente può eseguire e su quali dati. Per esempio, se un utente dispone di un profilo che abilita l’esecuzione del servizio di verifica dei conti correnti dei clienti di una determinata filiale, non è detto che lo stesso utente possa eseguire tale servizio per i conti correnti dei clienti di altre filiali. Il flusso ipotizzato è che il Team Leader, ogniqualvolta gli sia assegnato un nuovo dipendente, o quando questi cambi di funzione/ruolo, compili

UML e ingegneria del software: dalla teoria alla pratica

11

un’apposita richiesta di assegnazione di un nuovo profilo per tale dipendente. Si ipotizza poi che tale richiesta debba passare attraverso due livelli di approvazione: una prima a carico di un addetto della sicurezza appartenente al dipartimento dell’utente e una seconda a carico del supervisore della sicurezza. La funzione analizzata di seguito è relativa alla prima approvazione. Si consideri ora il diagramma delle attività riportato nella fig. 5.6. Si tratta della funzione denominata Monitor of Off-Market Transactions, ossia di un processo batch a carico del back office di un sistema finanziario atto a verificare che i tassi applicati ai Trade stipulati nel sistema di Front Office siano consistenti con quelli di mercato. Brevemente, con il termine Trade ci si riferisce a contratti di scambio di prodotti (in questo contesto finanziario e tipicamente in valuta), tra due parti: cliente e una organizzazione finanziaria.

In altre parole si vuole controllare che ai Trade siano applicati tassi compatibili con quelli di mercato. Per esempio, se un Trade è relativo a un prodotto FX (Foreign eXchange, scambio di valuta) tra EUR (Euro) e GBP (Sterlina del Regno Unito), e il relativo tasso di mercato nell’istante della stipula era uguale a x, si vuole accertare che quello realmente applicato sia all’interno di un opportuno intervallo (per esempio, ± 10%) del valore di mercato più le commissioni. Il Monitor of Off-Market Transactions è uno dei controlli che avvengono presso le banche alla chiusura della attività di mercato. In ultima analisi si controlla l’operato dei vari dealer (personale addetto alla contrattazione e gestione dei Trade finanziari), con l’obiettivo di accertare che gli stessi non effettuino, per qualche misterioso motivo, condizioni di favore, magari ancora vantaggiose per l’organizzazione finanziaria ma comunque eccessivamente vantaggiose per il cliente (controparte). Tipicamente, i Trade risultati OffMarket vengono trasmessi al manager del Front Office ed al “controllore finanziario”, i quali hanno la responsabilità di effettuarne la revisione Per questioni di completezza è necessario dire che esistono dei casi di eccezione — non riportati nella trattazione al fine di contenere la complessità e mantenere l’obiettivo sulla tecnica e non sul business bancario — solitamente denominati Historical Rate Rollovers. Brevemente, in alcuni casi può capitare che un cliente — probabilmente di particolare rilievo — stipuli un contratto con precise quotazioni di mercato in un istante determinato di tempo e, alla scadenza dello stesso, il cliente chieda di prolungarne il termine. Per esempio un cliente stipula un contratto per 1 000 000 di dollari da pagare in Euro. Alla scadenza, lo stesso richiede di prolungare il contratto per un altro mese. La procedura corretta sarebbe di chiudere il primo contratto e di effettuarne uno nuovo con i tassi di mercato aggiornati. Per specifici mercati e particolari clienti è invece permesso di stipulare un nuovo contratto con i tassi di quello precedente.

12

Capitolo 5. Completamento dell’analisi dei requisiti

Figura 5.6 — Diagramma delle attività del processo di controllo dei valori di tolleranza dei Trade. Servizio Telematico Dati Quotazioni Prodotti Finanziari

Sistema di Gestione delle Eccezioni

Sistema

AVVIO: Schedulatore PRECONDIZIONI: Disponibili dati relativi ad un nuovo Trade

Preleva primo Trade in coda Verifica necessita' di applicare il controllo di tolleranza

[controllo richiesto] Verifica disponibilita' dati quotazione Trade (data ora del Trade)

Controllo di tolleranza non richiesto per tipologie di Trade: - option take-up - consegna fisica di opzioni relative a valute

[quotazioni aggiornate non disponibili] Richiesta quotazioni di mercato Ricezione richiesta Verifica richiedente [richiedente non autenticato o non autorizzato] [richiedente autenticato ed autorizzato]

Messaggio di errore

Reperisce dati Invia risposta Ricezione responso Verifica responso [responso conforme] [responso non conforme] Comunica eccezione "Reperimento dati quotazioni fallito" Memorizza dati quotazioni ricevuti Reperisce tabelle relative alla tolleranza Trade [tabelle reperite] [tabelle non reperite] Reperisce tabelle valori di default [tabelle reperite] [tabelle non reperite] Comunica eccezione "Tabelle tolleranza non disponibili"

Ricezione dati eccezione

Calcola quotazione di mercato del Trade Gestione eccezioni

Memorizza i valori calcolati Verifica rispetto dei livelli di tolleranza [Livelli di torellaranza rispettati]

[Livelli di torellaranza non rispettati] Comunica eccezione "Livelli di tolleranza non rispettati"

Verifica presenza di Trade in coda [presenti Trade in coda da analizzzare] preleva Trade in coda [Analizzati tutti i Trade]

UML e ingegneria del software: dalla teoria alla pratica

13

Si tenga presente che l’obiettivo del presente paragrafo è ancora una volta illustrare la tecnica descritta in precedenza e non modellare una porzione del back office della banca, pertanto l’autore si scusa per eventuali approssimazioni. Nel diagramma riportato in fig. 5.6 sono presenti imprecisioni ed errori tipici di un diagramma reale. Si è deciso di presentarli sia per avere ulteriori spunti di riflessione, sia per riportare situazioni più vicine alla realtà. In primo luogo un errore tipico in cui è facile incorrere consiste nel tentare di modellare o comunque di descrivere le specifiche di sistemi al di fuori di quello oggetto di studio. In questo equivoco, a dire il vero, i clienti sono maestri: non appena cominciano a prendere dimestichezza con strumenti di analisi, quali essi siano (use case, activity diagram, ecc.), vengono affetti dalla sindrome denominata specificomania: vorrebbero scrivere le specifiche per ogni entità incontrata… Anche di sembianze umane! Nel diagramma in questione il fantomatico servizio denominato Servizio Telematico Dati Quotazioni Prodotti Finanziari rappresenta evidentemente un sistema esterno a quello oggetto di studio: un attore a tutti gli effetti, il cui comportamento è stato però impropriamente modellato anche se solo parzialmente. Ciò non è un peccato mortale fintantoché sia ben chiaro che tali dettagli hanno unicamente valenza descrittiva e quindi non devono confluire negli use case diagram. Un altro elemento di interesse potrebbe essere la presenza del sistema di gestione delle eccezioni descritto in precedenza. Ad esso vengono convogliate tutte le segnalazioni di situazioni anomale: problemi con la comunicazione con il sistema esterno, impossibilità di reperimento delle tabelle di tolleranza, ecc. Tornando al processo Monitoring Off-Market Transactions, esso ha inizio alla chiusura della giornata finanziaria. La prima verifica da effettuarsi è relativa alla necessità di eseguire il processo di controllo del rispetto dei limiti di tolleranza. In caso di esito negativo si passa ad esaminare l’eventuale Trade successivo, altrimenti si prosegue verificando se nel sistema siano disponibili i dati relativi alle quotazioni di mercato inerenti il particolare Trade in un accettabile intervallo temporale del momento in cui è avvenuta la stipula del Trade stesso: si è interessati al valore di mercato del Trade all’atto della stipula. Nel caso in cui i dati siano disponibili si procede con il processo, altrimenti si richiedono le quotazioni a un ipotetico Servizio Telematico Dati Quotazioni Prodotti Finanziari. Tale sistema è esterno a quello oggetto di studio (outside scope), sebbene riportarne la descrizione nel diagramma delle attività non sia peccato mortale. Ricevuto il messaggio di ritorno, questo viene analizzato. Se la struttura e i contenuti attesi non sono conformi a quelli attesi viene generata una segnalazione di errore, inviata poi al Sistema di Gestione delle Eccezioni. Se invece la risposta ottenuta risulta conforme a quanto atteso, si salvano i dati contenuti e si prosegue per il normale flusso del processo. Si tenga presente che il fatto che nelle specifiche utente il dominio del processo di verifica sia il singolo Trade non deve essere considerato un vincolo per il sistema finale.

14

Capitolo 5. Completamento dell’analisi dei requisiti

Gli use case dei requisiti utente servono appunto per specificare cosa si attende l’utente e non come ottenerlo. Per esempio nulla vieta che il sistema finale presenti alcune ottimizzazioni, come per esempio accumulare le richieste di quotazioni in un apposito messaggio da inviare ad intervalli predefiniti invece di richiedere ogni singolo valore di quotazione appena necessario. Ottenuti i dati si prosegue tentando di reperire le tabelle di tolleranza del particolare Trade; nel caso di impossibilità si procede tentando di reperire quelle standard. Fallendo anche questo secondo tentativo, si emette relativa segnalazione di errore ed il processo prosegue con l’esame del Trade successivo. Ottenuti anche i dati relativi alle tabelle, si effettuano i vari calcoli, li si salva e quindi si effettua il tanto agognato controllo del rispetto dei vincoli di tolleranza. Se il Trade risulta al di fuori dei limiti previsti, come al solito si emette opportuna segnalazione al Sistema di Gestione delle Eccezioni, altrimenti il processo termina placidamente. Il Sistema di Gestione delle Eccezioni dovrebbe riconoscere la tipologia del messaggio e quindi inserire appositi WorkItem nelle code relative al Manager del Front Office e al controllore finanziario, i quali sono formalmente chiamati a intrapren-

Figura 5.7 — Use case diagram del processo di controllo dei livelli di tolleranza.

Timer

Monitoring Off-Market transactions «extend» [Dati quotazioni di mercato non disponibili nel sistema]

Comunicazione correnti quotazioni di mercato

«include»

Reperimento tabelle di tolleranza

Sistema Telematico Dati Prodotti Finanziari

«extend» «extend»

Invia notifica errore

Controllo livelli tolleranze

Sistema di Gestione delle Eccezioni

UML e ingegneria del software: dalla teoria alla pratica

15

dere opportune azioni (verifiche, sanzioni, ecc.) il cui esito deve essere memorizzato nel sistema. In fig. 5.7 viene mostrato il caso d’uso, che non dovrebbe presentare grosse sorprese. Una perplessità che potrebbe sorgere analizzando i due diagrammi riguarda la ripetizione del ciclo di analisi per tutti i trade stipulati nell’arco del giorno. In particolare, mentre nel diagramma delle attività è ben evidente, nel rispettivo diagramma dei casi d’uso non lo è. Poco male, la ripetizione verrà definita nella descrizione del main scenario dello use case Monitoring Off-Market Transactions.

Vantaggi e svantaggi I vantaggi del metodo dovrebbero essere ormai chiari: 1

semplifica il processo di instaurazione di una piattaforma comune di dialogo tra gli utenti e il team che dovrà sviluppare il sistema;

2. riduce il tempo necessario per addestrare il personale non tecnico all’utilizzo del formalismo dei diagrammi delle attività, e ne aumenta l’entusiasmo (in genere è molto più divertente giocare con dei diagrammi piuttosto che scrivere del testo); 3. la presenza di requisiti descritti tramite diagrammi li rende più chiari e più immediati e quindi riduce il rischio di incomprensioni; 4. agevola lo scambio delle informazioni e fornisce un’eccellente documentazione da utilizzare per addestrare il personale sia dell’area tecnica, sia di quella business; 5. i tool commerciali supportano bene questa strategia. A fronte di questi vantaggi — a dire il vero se ne potrebbero riportare diversi altri — esiste però una serie di inconvenienti: 1. superata una certa complessità, i diagrammi delle attività tendono a divenire confusi e bisogna investire molto tempo nel tentativo di renderli lineari e comprensibili; 2. gli activity diagram, tipicamente, non riescono a sopperire completamente all’esigenza di redigere la descrizione dei casi d’uso. Ciò perché non è immediato inserire tutte le informazioni (trigger, pre- e postcondizioni, requisiti speciali, ...) ed è difficile specificare i dettagli di tutti i flussi nei diagrammi stessi; 3. disponendo sia della descrizione dei casi d’uso, sia dei diagrammi delle attività, diviene molto costoso mantenere sincronizzati i due modelli;

16

Capitolo 5. Completamento dell’analisi dei requisiti

4. il processo di aggiornamento dei diagrammi di attività richiede molto più tempo rispetto all’aggiornamento del testo di un diagramma dei casi d’uso.

Dall’analisi dei vantaggi e degli svantaggi del metodo è possibile constatare che nonostante gli inevitabili inconvenienti, il metodo possiede delle peculiarità (maggiore coinvolgimento degli utenti, facilità di scambio delle informazioni, ...) che, specie in progetti non banali, sarebbe veramente un peccato non sfruttare. Il consiglio allora potrebbe essere quello di utilizzare comunque la tecnica dei diagrammi delle attività almeno fino al punto in cui i requisiti, e quindi i relativi modelli, raggiungono un’accettabile stato di stabilità. A questo punto si potrebbero placidamente trascurare questi diagrammi, concentrandosi unicamente sui diagrammi dei casi d’uso e sulla relativa descrizione.

Modello per le interfacce Un aspetto molto importante della progettazione di un sistema è legato alla definizione del confine dello stesso. Un manufatto che ne favorisce la determinazione, e che agevola la definizione formale delle responsabilità che attori e sistema assumono gli uni nei confronti dell’altro, è il modello di specifica delle interfacce. Questo, una volta completato, dovrebbe rendere immediata la definizione delle classi “boundary” presenti nel modello di analisi (cui si accenna nel Capitolo 2 e che viene trattato nel Capitolo 8). Spesso le entità esterne al sistema non sono esclusivamente utenti “in carne e ossa”, bensì altri sistemi (legacy system, moduli agent, sensori, software forniti da terze parti, sistemi di fornitura di servizi on demand, ecc.). In questi casi è ancora più sentita la necessità di definire formalmente le interfacce tra il sistema e gli attori poiché, essendo dispositivi, potrebbero davvero essere meno accomodanti degli attori umani (almeno teoricamente!). Analizzare tali interfacce è molto importante anche perché finiscono per influenzare direttamente l’elaborazione del modello dei casi d’uso: dovendo interagire con sistemi esterni di cui non si ha il controllo, è più facile che il sistema oggetto di studio si adatti alle interfacce predefinite piuttosto che il viceversa. Considerando infine che il paradigma preferenziale dei sistemi di nuova generazione è il famoso component-based, secondo il quale il sistema è definito in termini di specifici componenti in grado di erogare servizi definiti nelle relative interfacce, si comprendono ancora più approfonditamente i vantaggi derivanti dall’iniziare a definire formalmente le interfacce del sistema già durante le prime fasi del processo di sviluppo del software. Sebbene le due tipologie di interfacce presentino un diverso livello di astrazione e obbiettivi sensibilmente diversi, esiste una chiara dipendenza delle interfacce dei componenti dalle rispettive del sistema: in ultima analisi i componenti realizzano i servizi richiesti al sistema.

UML e ingegneria del software: dalla teoria alla pratica

17

Comunque, la definizione delle interfacce fornisce sia ulteriori requisiti da utilizzare per iniziare la modellazione del sistema in termini più architetturali, sia importanti informazioni per l’evoluzione del Domain Object Model: è lecito attendersi che i dati scambiati con gli attori siano opportuni sottoinsiemi di quelli presenti nel modello a oggetti. In caso contrario, probabilmente è opportuno accertarsi che nel Domain Object Model non siano presenti lacune. La relazione tra le interfacce del sistema e il modello del dominio è così forte che spesso il processo di analisi delle interfacce viene eseguito dopo aver accertato la validità del modello a oggetti del dominio: d’altronde è lecito attendersi di comunicare informazioni a propria disposizione e di ricevere informazioni in grado di essere trattate. Come ultimo punto si consideri il successo che stanno incontrando le infrastrutture di messaggistica (i famosi MOM, Messaging Oriented Middleware) nei sistemi di nuova generazione e il protocollo XML: è evidentissimo come molte delle interfacce — quelle relative allo scambio di messaggi tra dispositivi fisici — risultino prime versioni dei messaggi che viaggeranno nel sistema. Dunque iniziare a progettare anticipatamente questi messaggi agevola il processo di produzione del software.

Ricapitolando, è vantaggioso realizzare la definizione formale delle interfacce già durante la fase di analisi dei requisiti, perché: • favorisce la definizione dei confini del sistema; • offre ottimi spunti al modello dei casi d’uso; • fornisce preziose informazioni o permette di bilanciare il modello a oggetti dominio; • agevola l’individuazione delle interfacce dei sistemi basati sui componenti; • favorisce la definizione dei messaggi circolanti nel sistema, qualora si ricorra ad un’infrastruttura di messaggistica.

La tab. 5.2 presenta un template rivelatosi utile per l’analisi iniziale delle interfacce sistema/attori. Qualora si desideri analizzare interazioni, specificatamente sistema/attori umani, probabilmente potrebbe risultare più opportuno, considerati i vari tool a disposizione, definire un prototipo di interfaccia utente con tutti i rischi del caso (cliente che confonde il prototipo con il sistema finale, perdita di generalità, ecc.) esaminati nei capitoli precedenti. Nella sezione dedicata alle informazioni generali è necessario riportare il nome dell’interfaccia, il codice, la data e la versione. Per ciò che concerne il campo codice, tipicamente, si preferisce ricorrere a identificatori mnemonici. Un possibile sistema di selezione del codice potrebbe consistere nell’utilizzare:

18

Capitolo 5. Completamento dell’analisi dei requisiti

Tabella 5.2 — Template utilizzato per l’analisi iniziale delle interfacce. Informazioni generali. NOME PROPOSTO:

CODICE PROPOSTO:

DATA:

VERSIONE:

BREVE DESCRIZIONE:

Attori. NOME:

BREVE DESCRIZIONE:

FORNITORE/ FRUITORE

BREVE DESCRIZIONE:

FORNITORE/ FRUITORE

OBIETTIVI:

Casi d’uso. NOME:

OBIETTIVI:

Definizione interfaccia. GRUPPO

CLASSE NEL DOM

NOME

(Se presente)

ATTRIBUTO

TIPO/DIM

OBBL.

DESCRIZIONE

Occorrenze GRUPPO

NOME ATTRIBUTO

#

DESCRIZIONE

Osservazioni: CODICE

AUTORE

DESCRIZIONE

STATO

DATA

le prime tre lettere fisse al fine di indicare che si tratta della definizione di un’interfaccia (per esempio INT);

UML e ingegneria del software: dalla teoria alla pratica

19

• un carattere atto ad individuare la fase del ciclo di vita di appartenenza del documento (ad esempio B = business, R = requirements, A =analysis, ecc.); • tre lettere indicanti l’attore principale (per esempio AMM = amministratore); • sei lettere indicante il contenuto (per esempio PRFUTN = per profili utente). Nella sezione attori, come lecito attendersi, si dichiarano le entità esterne al sistema che utilizzano l’interfaccia, la relativa descrizione e i motivi che inducono l’utente ad utilizzare l’interfaccia stessa (obiettivi). In particolare è opportuno specificare se si tratta di un attore che fornisce i dati specificati nell’interfaccia o se li riceve dal sistema. L’intera sezione va ripetuta per tutti gli attori che interagiscono attraverso l’interfaccia stessa. Per ciò che concerne la sezione dedicata ai casi d’uso, il discorso è del tutto analogo a quanto riportato poc’anzi per gli attori. Nel caso generale, l’interfaccia può essere utilizzata da diversi attori e casi d’uso. Nella definizione degli attributi che compongono l’interfaccia, è necessario tenere a mente che questi dovrebbero essere presenti nel Domain Object Model (modello a oggetti del dominio); in caso contrario è opportuno accertarsi che nel modello a oggetti non siano presenti lacune. Nel caso in cui l’attributo sia contemplato nel modello del dominio, occorre precisare la relativa classe di appartenenza (seconda colonna del template), altrimenti è necessario specificarne il tipo e la dimensione. Un campo che potrebbe destare qualche perplessità è quello relativo al gruppo. Si tratta di un artificio utilizzato per indicare la necessità di ripetere più volte un gruppo di attributi che non necessariamente coincidono con la definizione di una classe. Nella definizione di un’interfaccia, tipicamente, alcune informazioni prevedono diverse occorrenze. Ora, se si tratta di singoli attributi, non sussiste alcun problema: è sufficiente riportare il nome dell’attributo nella sezione dedicata alle occorrenze e quindi specificare la frequenza. Nel caso di classi il discorso è pressoché simile. Nel caso in cui però si tratti di un insieme di attributi (magari sottoinsieme di una classe) che è utile ripetere, ecco che l’informazione “gruppo” risulta utile: si associano logicamente tra loro un insieme di attributi e quindi se ne specifica la frequenza nella sezione dedicata alle occorrenze. È chiaro che se si specifica un gruppo come elemento da ripetere, non ha senso specificare il nome di un attributo e viceversa. Per chiarire quanto esposto nei periodi precedenti, si riporta un semplice esempio relativo a un’interfaccia atta a erogare dei dati rappresentanti il listino prezzi previsto da un determinato hotel organizzato in base alle stagioni. In particolare nel listino sono riportati i prezzi relativi a una sistemazione standard in stanza singola e doppia. Dopo aver letto il presente paragrafo, i lettori più attenti potrebbero chiedersi: “perché non rappresentare direttamente tali interfacce attraverso lo UML e in particolare attraver-

20

Capitolo 5. Completamento dell’analisi dei requisiti

Tabella 5.3 Informazioni generali. NOME PROPOSTO:

CODICE PROPOSTO:

ListinoPrezziHotel

INT-R-CLN-LSTPRZ

DATA:

VERSIONE:

10 Agosto 2001

0.00.003

BREVE DESCRIZIONE: Questa interfaccia contiene informazioni relative al listino prezzo di un hotel organizzato in stagioni, efferente da una sistemazione standard in camera singola e doppia.

Attori. NOME:

BREVE DESCRIZIONE:

Cliente

Cliente abilitato alla fruizione del sistema di

FORNITORE/ FRUITORE Fruitore

booking on-line. OBIETTIVI: Ricevere le informazioni relative al listino prezzi dell’hotel selezionato

Casi d’uso. NOME:

BREVE DESCRIZIONE:

UC-PRZHTL-002

Reperisce ed invia all’attore il listino prezzi in

Invio listino prezzi hotel

esercizio presso l’hotel richiesto.

FORNITORE/ FRUITORE Fornitore

OBIETTIVI: Fornire all’utente il listino prezzi dell’hotel selezionato.

Definizione interfaccia. GRUPPO

CLASSE NEL DOM (Se presente)

NOME ATTRIBUTO

Hotel

id

Hotel

nome

Hotel

descr

TIPO/DIM

OBBL.:

DESCRIZIONE

Si

Id hotel

Descrizione breve

TipologiaCamere

descrStnd

Descrizione della

tipologia

standard StagionePrezzi

Stagione

nome

Stagione

descrizione

Stagione

inizio

Stagione

fine

Tariffe

singola

Tariffe

doppia

Occorrenze GRUPPO

NOME ATTRIBUTO

StagionePrezzi

#

DESCRIZIONE

N

Tante occorrenze quante previste dal tariffario dell’hotel

Osservazioni: CODICE

AUTORE

DESCRIZIONE

STATO

DATA

UML e ingegneria del software: dalla teoria alla pratica

21

so il formalismo del diagramma delle classi?”. Si tratta di un quesito legittimo. In effetti una rappresentazione grafica apporterebbe moltissimi vantaggi: • quelli tipici dei formalismi grafici (immediatezza, maggiore chiarezza, migliore memorizzazione, ecc.); • altri di integrazione con i tool commerciali di supporto (i vari diagrammi relativi alle interfacce potrebbero essere associati alle relazioni attori/use case e quindi essere visualizzati attraverso un doppio click eseguito sulla relazione stessa); • agevolare il “tracciamento” del manufatto durante le varie fasi del processo, e così via..

Tutto quindi farebbe propendere per tale soluzione, e in effetti è quella più apprezzata dal personale tecnico. A fronte di questi vantaggi però esiste un unico grande svantaggio: gli utenti di solito si sentono più a loro agio con fogli elettronici, documenti e template, piuttosto che con i diagrammi UML. Poiché in questa fase gli utenti recitano ancora un ruolo fondamentale, la decisione sul formalismo da utilizzare deve essere assolutamente basata su ciò che riesce a stimolare e coinvolgere maggiormente l’utente.

I famosi test case I test case, come lascia presagire il nome, sono artifact fondamentali per la fase di test, uno degli ultimi stadi del processo di sviluppo del software, o di ogni iterazione, qualora si utilizzi un processo iterativo ed incrementale. Ciononostante si è deciso di presentarli in questo capitolo poiché esiste un vincolo singolare tra i casi d’uso ed i casi di test: i primi descrivono le funzionalità che il sistema dovrà realizzare mentre i secondi servono ad accertarsi che il sistema fornisca realmente i servizi promessi. Nel mondo dell’ideale si desidererebbe verificare ciascuna linea di codice di cui un sistema è costituito e ogni possibile percorso. Ciò indubbiamente favorirebbe l’individuazione di anomalie, però probabilmente un tale processo di verifica potrebbe risultare un “minimo” oneroso e non sempre possibile, sia perché tipicamente si utilizzano anche sottosistemi/librerie di cui non si hanno i codici sorgenti, sia perché i possibili percorsi tendono ad essere un’infinità, magari numerabile, ma comunque di infinità. Tale approccio empirico, tra l’altro, potrebbe anche finire per non raggiungere l’obiettivo primario: verificare che il sistema realizzi le funzionalità di cui l’utente ha realmente bisogno. Per questo motivo è necessario realizzare un opportuno piano di test che tenga

22

Capitolo 5. Completamento dell’analisi dei requisiti

conto di specifiche priorità basate principalmente sulle necessità degli utenti, al fine di conferire maggiore enfasi ai test relativi alle funzionalità eseguite più frequentemente.

In generale, i test andrebbero eseguiti in ciascuna fase del ciclo di sviluppo del software e non appena un artifact si renda disponibile o subisca degli aggiornamenti. L’obiettivo è neutralizzare eventuali errori o lacune prima che i relativi effetti possano essere amplificati dal passare del tempo o meglio dall’evoluzione viziata del processo di sviluppo del software. Risulta fondamentale poi che il personale che esegue le varie verifiche non sia lo stesso che ha prodotto il manufatto: ciò perché i modelli mentali utilizzati per la verifica sono gli stessi di quelli adottati per la produzione e quindi non si tende a effettuare test che violino la struttura del manufatto stesso.

I test di maggiore importanza, tuttavia, sono indubbiamente quelli eseguiti dopo ogni build (implementazione ottenuta come risultato di una nuova iterazione). I manufatti di maggiore interesse nella fase di verifica sono indubbiamente i test case, i quali rivestono molta importanza non solo nei processi più accademici (come il RUP della Rational), ma anche in quelli più “leggeri” come XP (eXtreme Programming). In effetti, quest’ultimo prevede la definizione dei test case, magari direttamente attraverso il codice (test unit), come prerequisito irrinunciabile dell’attività di codifica (quanti programmatori XP ricordano questo “dettaglio”?).

Nei processi tradizionali, la prima versione dei test case è ottenuta direttamente dal modello dei casi d’uso. Questo fornisce un ottimo punto di partenza, ma da solo non è assolutamente sufficiente: in un sistema tante cose possono non funzionare correttamente — come enunciato chiaramente dalla “legge di Murphy”: “se c’è la possibilità che qualcosa vada male, sicuramente andrà male — e non tutte si prestano a essere evidenziate per mezzo dei casi d’uso.

Certo è che il sistema deve essere verificato approfonditamente prima di essere consegnato. Nessuno — teoricamente — gradirebbe fornire un sistema non robusto, pronto ad andare in crash, o in uno stato indefinito, alla prima situazione anomala non considerata. Si provi a considerate cosa potrebbe accadere in settori diversi, magari nella fabbricazione di automobili… Si potrebbe candidamente confidare al cliente che la versione di-

UML e ingegneria del software: dalla teoria alla pratica

23

sponibile del sistema frenante dell’autovettura da lui acquistata non funziona correttamente… “ma poco male, perché si tratta di un’anomalia già riscontrata e di cui a breve verrà resa disponibile apposita patch!”… In un sistema complesso molte sono le caratteristiche da verificare: requisiti funzionali, quelli non funzionali, interazione con l’utente, ecc. Il tutto però deve avvenire in modo organico secondo un piano sistematico studiato a priori. In molte organizzazioni la fase di test è un’attività da svolgersi nell’eventuale lasso di tempo che intercorre tra l’ultima release del sistema — congenitamente rilasciata in ritardo — e la data di consegna del progetto. In tali ambienti l’algoritmo di test utilizzato è il famoso criterio di “ricerca a carponi”: si provano a eseguire alcuni servizi con qualche dato di prova pensato al momento (tale esercizio in gergo è indicato con la frase “fare il giro del sistema”). Per alcuni manager l’attività di test è dovuta alla negligenza del team di sviluppo, dimenticando che è semplicemente impossibile verificare alcune parti prima dell’integrazione del sistema e che, comunque, come in ogni altra attività errare humanum est. Chiaramente in tutto c’è un fondo di verità, e indubbiamente profondere attenzione e impegno maggiori al proprio lavoro sicuramente riduce il numero di potenziali errori. Inoltre il paradigma Object Oriented, grazie alla naturale tendenza a produrre una miriade di piccoli oggetti cooperanti e quindi più facilmente verificabili, aiuta a realizzare sistemi di maggiore qualità… Nonostante ciò vale la pena utilizzare un criterio sintetizzato da una celebre frase proferita dall’ex presidente statunitense Reagan nella cornice storica dei difficili rapporti con l’allora URSS: “Trust but verifiy” (“Fidati ma verifica”). La corretta e completa definizione del piano di test esula dai fini del presente testo, ma si è ritenuto opportuno fornire alcune utili indicazioni. Su questo argomento esistono molti trattati, tra i quali molto importante è quello proposto dallo standard IEEE n1998d.

Nel contesto dei processi di sviluppo iterativi e incrementali, lo sviluppo del piano di test è più complesso giacché va eseguito ad ogni iterazione. In particolare, in ogni progettazione del piano di test bisogna svolgere attività aggiuntive come: • esclusione di test case divenuti obsoleti con la nuova versione del sistema; • realizzazione di nuovi test case; • aggiornamento di specifici test case.

24

Capitolo 5. Completamento dell’analisi dei requisiti

Tipicamente un piano ben congegnato è composto almeno dalle seguenti sezioni: • Definizione degli obiettivi. Si tratta di una vera e propria introduzione nella quale si dichiara quale siano il sistema da verificare, gli obiettivi del piano, e così via. • Dettaglio dei test. In questa sezione si stabilisce quali parti del sistema si intende verificare e quali no. Probabilmente, il modo migliore per rappresentare queste informazioni è ricorrere a una tabella in cui elencare le verifiche previste dal piano di test e quelle invece trascurate. • Allocazione delle risorse. Questa è la sezione in cui si stabilisce a chi toccherà il gravoso compito della verifica del sistema, chi ne assumerà la responsabilità, ecc. • Elenco dei rischi. Nella sezione dedicata ai rischi è necessario identificare i fattori che potrebbero annullare, o comunque ridimensionare, quanto sancito nel piano dei test.

Il “modello” di test non è composto esclusivamente dai test case, sebbene ne rappresenti l’artifact fondamentale, bensì anche dalle test procedure nonché da eventuali componenti di test. Le test procedure specificano come eseguire i test case, ossia specificano se eseguirne uno o una serie, o tutti, se eseguire completamente ciascuno di essi o solo particolari sezioni e così via. I componenti di test servono per eseguire alcuni test in maniera automatica, per semplificare la realizzazione di altri: per esempio potrebbe rendersi necessaria la realizzazione di un componente atto a simulare un sistema remoto con il quale quello oggetto di studio deve interagire, la configurazione di un apposito sistema di simulazione dell’utente, robot, e così via. Come visto in precedenza, si prestano a fornire il punto di partenza per la realizzazione del piano di test del sistema poiché i casi d’uso specificano i requisiti funzionali del sistema. In particolare la descrizione del flusso di eventi di ciascun caso d’uso prevede la descrizione della successione di passi che globalmente realizzano il servizio (scenari principale e alternativi). Non solo; qualora l’esecuzione del flusso possa essere inquinata dal verificarsi di situazioni anomale, nel flusso stesso è necessario riportare la descrizione di tali eccezioni corredate dalla relativa gestione. In sintesi la descrizione del flusso degli eventi dei casi d’uso permette di programmare sia i positive (verifiche positive), che i negative test (verifiche negative). I primi servono ad accertare la correttezza del sistema: in condizioni ideali (dati di ingresso validi, ambiente completamente operativo, ecc.) il sistema eroga correttamente il

UML e ingegneria del software: dalla teoria alla pratica

25

servizio esaminato e quindi produce i risultati attesti. I secondi in maniera diametralmente opposta ai primi, concorrono a verificare robustezza e/o fault tolerance del sistema, ossia si verifica la capacità del sistema di riconoscere e gestire correttamente situazioni anomale: dati di input non corretti, ambiente parzialmente non operativo (per esempio connessioni non funzionanti), e così via. Brevemente con il termine correttezza si fa riferimento alla capacità del sistema di fornire i servizi così come definiti nel documento delle specifiche. Sebbene si tratti di una caratteristica imprescindibile del sistema — non dovrebbe essere di particolare interesse disporre di un sistema, magari molto accattivante, efficiente, ecc., se poi semplicemente non fornisce i servizi richiesti — la pratica dimostra che non è così facile da conseguire. Come analizzato nei capitoli precedenti, già il prerequisito per ottenere la correttezza — corretta analisi e definizione dei requisiti utente — è un’attività molto insidiosa e per certi versi controversa. Con il termine di robustezza si fa riferimento alla capacità del sistema di riconoscere situazioni anomale e quindi di gestirle correttamente. Pertanto, dovrebbe essere chiaro che la robustezza è il logico completamento della correttezza. Anche questa proprietà importantissima del sistema nasconde diverse insidie, alcune condivise con la correttezza del sistema (difficoltà di fornire delle specifiche precise), altre relative all’eccessiva genericità del concetto di “situazione anomala” (mancato rispetto di business rule, malfunzionamento di dispositivi hardware e software, ecc.).

Nella tab. 5.4 viene riportato un modulo in grado di semplificare la descrizione dei casi di test. L’utilizzo del modulo di tab. 5.4 dovrebbe risultare piuttosto intuitivo. In ogni modo di seguito viene fornita qualche informazione supplementare. La prima sezione è dedicata alla catalogazione/reperimento del modulo. In particolare vi trovano posto il codice, la descrizione del test case, la data e la versione. Dall’analisi dei campi successivi è possibile evidenziare la diretta dipendenza tra i casi d’uso e quelli di test: una modifica ai primi, tipicamente, si ripercuote sui secondi. Il campo versione si rende necessario al fine di tener traccia delle eventuali variazioni del modello. La sezione successiva del template è utilizzata al fine di evidenziare da quale caso d’uso dipende il test case descritto nel modulo. In merito al processo di attribuzione delle priorità è necessario conferire particolare attenzione alle precedenze stabilite più o meno implicitamente dall’utente. Le priorità, in ultima analisi, permettono di ripartire più opportunamente l’intervallo di tempo a disposizione della fase di test, dedicando maggiore attenzione alle funzionalità con priorità più elevata. Queste vanno assegnate considerando sia la frequenza con la quale ciascuno use case viene eseguito dai relativi attori, sia alla criticità della funzione stessa (si consideri per esempio il caso d’uso relativo all’aggiornamento del conto corrente).

26

Capitolo 5. Completamento dell’analisi dei requisiti

Tabella 5.4 — Modulo per la semplificazione della descrizione dei casi di test TEST CASE: Codice

Data: Versione: 0.00.000

Breve descrizione del test case

Caso d’uso: Codice Priorità:

Breve descrizione. Priorità attribuita al caso di test.

Set up:

Elenco delle impostazioni necessarie per poter svolgere il test.

Descrizione test. Scenario Passi selezionati

Descrizione

Positivo ‰

ID. Err

‰ ‰ ‰ Rapporto dettagliato dei risultati. Stato: Tester: Pending/ Cognome e nome Passed/ Errors

Data:

Eventuali anomalie. ID. Errore Tipologia

Descrizione

errore critico, errore, warning Annotazioni supplementari.

Figura 5.8 — Legame di dipendenza tra Use Case e Test Case.

Use Case

«trace»

Test Case [Test a scatola nera]

27

UML e ingegneria del software: dalla teoria alla pratica

Figura 5.9 — Schematizzazione di un flusso degli eventi di un generico caso d’uso.

Trigger

Scenario alternativi

Scenario di errore

Scenario principale

Alcuni tecnici particolarmente scaltri utilizzano una tecnica che prevede l’assegnamento di opportuni “pesi” alle valutazioni degli utenti… Chiaramente l’entità del peso è direttamente proporzionale all’importanza del particolare utente. Un conto è stare a sentire le lagnanze di un povero utente frustrato, un altro è prestare attenzione alle lamentele di un manager irritato. Per ciò che concerne il campo denominato Set up, esso è utilizzato per riportare l’elenco delle operazioni propedeutiche all’esecuzione del test. Se, per esempio, si dovesse verificare il funzionamento di servizi relativi a un sistema di account bancario, alcune operazioni propedeutiche potrebbero consistere nell’inserire un nuovo account intestato al sig. Paolo Rossi, quindi effettuarvi un trasferimento di importo x, e così via. Lo svolgimento della procedura di Set up (magari eseguita attraverso un apposito componente di test) è volto a porre il sistema in uno stato iniziale predefinito (come per esempio la memorizzazione di prestabiliti dati di prova nella base dati) da utilizzarsi come base di partenza per eseguire i vari test. Per ciò che concerne la sezione dedicata alla descrizione del test, e in particolare il campo scenario, è necessario aver presente come è di solito costituito un flusso degli eventi di un generico caso d’uso (come mostrato nel capitolo precedente). Un generico flusso degli eventi prevede le tre tipologie (fig. 5.9): • Principale: è l’unico obbligatorio e descrive la sequenza delle attività che permettono di fornire il servizio. In questa sequenza viene assunto che tutto “funzioni” bene e che quindi non intervenga alcuna eccezione;

28

Capitolo 5. Completamento dell’analisi dei requisiti

• Alternativo: si tratta di una variante al flusso principale, non viziata da errori, che quindi è in grado di erogare del servizio, sebbene venga utilizzato un percorso alternativo; • Errore: evidenzia determinati errori che possono verificarsi durante l’esecuzione dei predetti flussi. L’insorgenza di un errore, evidentemente, inibisce l’erogazione del servizio. Ciascuno di essi viene corredato dall’insieme delle azioni che il sistema deve compiere per la relativa gestione. A questo punto è possibile chiarire cosa si intenda con i campi scenario da inserire nel modulo dei test case. Essi rappresentano percorsi di verifica, ossia opportune sequenze di passi selezionati dal corrispondente caso d’uso.

Tabella 5.5 — Frammento di caso d’uso relativo ai vari flussi (principale, alternativi e di errore). Flusso principale 1. Descrizione del primo passo 2. Descrizione del secondo passo … … N Descrizione dell’ennesimo passo 1° Flusso alternativo 2.1. 1° passo del 1° flusso alternativo relativo al 2° passo del flusso principale. 2.2 2° passo del 1° flusso alternativo relativo al 2° passo del flusso principale. 2° Flusso alternativo i.1 1° passo del 2° flusso alternativo relativo al i-esimo passo del flusso principale. … … i.n n-esimo passo del 2° flusso alternativo relativo al i-esimo passo del flusso principale. 1° Flusso di errore 1.1 1° passo del 1° flusso di errore relativo al 1° passo del flusso principale. … … 1.n n-esimo passo del 1° flusso di errore relativo al 1° passo del flusso principale. 2° Flusso di errore …



29

UML e ingegneria del software: dalla teoria alla pratica

Figura 5.10 — Esempio di albero dei percorsi da verificare che potrebbe scaturire dall’analisi di un caso d’uso. Come si può notare vanno verificati tutti i percorsi: il principale e ogni percorso alternativo e di errore.

Set-up

1

2 2.1

E1.1 E1.2

3

2.2

E3.1 E3.2 n

Gli scenario da verificare (fig. 5.10) su base obbligatoria sono quelli che permettono di erogare il servizio: • flusso principale: passi 1, 2, 3, …, n; • primo flusso alternativo: 1 , 2, 2.1, 2.2, 3, …, n e così via. Poiché però si è interessati anche a verificare che il sistema sia in grado di riconoscere e gestire situazioni anomale (verifiche negative), altri scenari da verificare (fig. 5.10) sono dati dalle sequenze: 1, E1.1, E1.2 e 1, 2, 3, E3.1, E3.2, dove la lettera E maiuscola evidenzia appunto che si tratta di un passo appartenente ad un flusso di errore.

30

Capitolo 5. Completamento dell’analisi dei requisiti

Chiaramente il numero di possibili scenario in un caso reale è decisamente molto elevato e, verosimilmente, è impossibile verificarli tutti. Pertanto risulta importante individuare gli scenario più frequenti, quelli più critici e così via. Per ogni scenario, oltre a riportare una breve descrizione delle azioni intraprese (per esempio: selezionato il conto corrente del sig. Paolo Rossi, effettuato un pagamento per un importo y, ecc.), si vuole evidenziare l’esito del test. In caso di insuccesso (il sistema non si è comportato come previsto) è opportuno catalogare l’errore assegnandovi un identificatore univoco. Ciò è utile sia per riportare una breve descrizione nell’apposita sezione, sia per l’utilizzo di software che permettono di memorizzare gli errori scaturiti e di assegnarli automaticamente al personale competente. Le rimanenti sezioni del modulo risultano così intuitive da non richiedere ulteriori spiegazioni.

Sebbene molti tecnici siano portati a credere che il testing sia un’attività piuttosto semplice da compiersi su base randomica (il famoso criterio a carponi), magari da assegnarsi all’ultimo arrivato, in realtà le cose non sono esattamente così. Un piano di test adeguato richiede una sofisticata comprensione del sistema oggetto di verifica. È necessario essere in grado di sviluppare una vista astratta di quelle che sono le dinamiche del flusso del controllo del sistema, comprendere lo spazio del problema, capire i requisiti funzionali e non, e così via. In sintesi, anche se non si eseguono attività di disegno, è necessario avere una comprensione del problema e del sistema paragonabile a quella di un architetto.

Per terminare questa breve sezione dedicata ai test case, si vuole fornire un suggerimento ai tecnici tester: munirsi di un semplice registratore vocale da utilizzare durante l’esecuzione dei vari test. Si tratta di uno strumento molto produttivo per tenere traccia della storia dei test eseguiti, ossia della successione di azioni che hanno generato l’errore. (Inserito record X, eseguita funzione Y, aggiornato dato Z, e così via). A chi non è mai capitato di analizzare un report prodotto dal team di test, magari molto dettagliato riportante anche la descrizione minuziosa dell’eccezione generata, con l’unico neo di non sapere come riprodurlo? Un errore, per essere studiato e quindi risolto, come minimo deve poter essere riprodotto, altrimenti la relativa analisi risulta decisamente molto complessa o addirittura impossibile: un errore riproducibile è un errore mezzo risolto!

UML e ingegneria del software: dalla teoria alla pratica

31

Nella decisione del tempo e delle risorse umane da dedicare alle fasi di test, si tenga bene a mente che ciò che non si spende prima, si paga dopo… ma con gli interessi.

Requisiti non funzionali Nel corso dell’illustrazione dei processi di sviluppo del software, nonché nei capitoli precedenti, si è menzionato frequentemente il manufatto relativo ai requisiti non funzionali. Si tratta di informazioni di importanza vitale, tipicamente analizzate e definite durante la fase di analisi dei requisiti, che però, ahimè, non sempre, e non tutte, si prestano a essere rappresentate per mezzo di un ben definito formalismo grafico. Pertanto, generalmente, la relativa descrizione è realizzata per mezzo del classico documento. I requisiti non funzionali rientrano nel contesto più generale dell’analisi delle specifiche di un sistema software (SRS Software Requirements Specification, specifica dei requisiti roftware) e pertanto è possibile reperire molta documentazione al riguardo. Particolarmente degne di nota sono lo standard IEEE Std 830-1998 e la relativa rielaborazione (Supplementary Specification, specifiche supplementari) incorporata nel processo della Rational (RUP). La rappresentazione dei requisiti del sistema, sia pure limitando l’attenzione a quelli non funzionali, è un’attività molto importante nel contesto del ciclo di vita del software. In ultima analisi si specificano le caratteristiche alle quali l’utente è interessato. Il relativo adempimento, quindi, concorre a determinare il successo o l’insuccesso dell’intero progetto. Il documento dei requisiti non funzionali è di particolare valore nelle fasi di disegno, implementazione e test del sistema. In questo contesto non solo è importante realizzare le funzioni definite nel modello dei casi d’uso, ma bisogna rispettare anche vincoli legati a performance, sicurezza, affidabilità, ecc. I requisiti individuati, generalmente, vengono suddivisi in tre categorie di importanza: • essenziali, ossia requisiti che devono essere necessariamente soddisfatti per l’accettazione del sistema; eventuali insolvenze determinano il rifiuto del sistema da parte del cliente; • condizionali, ovvero requisiti che se soddisfatti permettono di realizzare un sistema evoluto, ma che, se non presenti, non determinano l’insuccesso del progetto; • opzionali, si tratta di requisiti che non forniscono un considerevole valore aggiuntivo e quindi potrebbe o meno valer la pena soddisfare. Nell’elaborazione del documento dei requisiti non funzionali bisogna porre particolare attenzione ad alcune proprietà molto importanti, come:

32

Capitolo 5. Completamento dell’analisi dei requisiti

• correttezza: il documento viene considerato corretto se, e solo se, ciascun requisito menzionato rappresenta una reale caratteristica che il sistema dovrà soddisfare; • completezza: il documento dei requisiti non funzionali è definito completo se sono incorporate tutte le caratteristiche di interesse (ciascun requisito significativo, vincoli di disegno, prestazioni, …). La definizione dello standard IEEE prevede la presenza di sezioni supplementari come per esempio quella dedicata alle interfacce esterne, che però, considerata l’importanza, probabilmente è più opportuno analizzare in un apposito documento; • precisione: un documento dei requisiti è preciso (ossia non ambiguo) se, e solo se, ciascuna delle caratteristiche richieste si presta a essere interpretata in modo univoco, non solo da chi ha prodotto il documento, ma anche dai lettori. Condizione necessaria, ma non sufficiente, è che ciascun requisito disponga di un nome univoco. Utilizzando come strumento di specifica il linguaggio naturale (tipicamente l’inglese che quindi poi non per tutti è così naturale…), il quale per definizione non è preciso, la caratteristica di non ambiguità non è sempre facilmente ottenibile; • consistenza nel senso di consistenza interna. In particolare il documento è internamente consistente se, e solo se, nessun requisito risulta in conflitto con un altro. Come ogni documento di qualità che si rispetti, è opportuno includere la classica introduzione al fine di fornire un’appropriata overview del documento. Se ne dichiarano gli obiettivi, l’area di interesse (il sistema a cui fa riferimento il documento), il vocabolario con le definizioni, gli acronimi utilizzati, e così via. Terminata la sezione “cerimoniale”, comunque importante e d’obbligo, si inizia la descrizione delle caratteristiche non funzionali di interesse. In generale la prima ad essere descritta è l’utilizzabilità (usability) del sistema che prevede informazioni del tipo: • tempo necessario all’addestramento del personale che dovrà utilizzare il sistema (utenti). Particolarmente importante è distinguere tra le varie tipologie di utente (amministratore, utente generico, ecc.), nonché definire i vari livelli di produttività (per esempio marginale quando l’utente è in grado di eseguire le attività basilari, oppure operativa quando l’utente è in grado di svolgere tutte le attività richieste durante una tipica giornata lavorativa, e così via); • tempo richiesto mediamente all’utente per lo svolgimento delle attività tipiche: si individuano (dalle informazioni riportate nella descrizione dei casi d’uso) i servizi

UML e ingegneria del software: dalla teoria alla pratica

33

richiesti più frequentemente e quindi, per ciascuno di essi, si determina la stima del tempo necessario per l’espletamento, considerando sia il tempo umano, sia quello richiesto dall’elaborazione; • requisiti base di utilizzabilità: si descrivono gli ambienti conosciuti dall’utente che, quindi, concorrono a rendere il sistema maggiormente amichevole (user-friendly); • direttive per la definizione standard di interfacce utente (Sun user interface guidelines, IBM’s CUA standars, GUI standards per Microsoft Windows 95, ecc.). Un’altra caratteristica molto importante è relativa all’affidabilità del sistema (reliability), la quale, tipicamente è definita attraverso le seguenti informazioni: • disponibilità: in particolare è necessario specificare la percentuale di disponibilità del sistema (esempio 96%), oppure formule del tipo 24 × 7 (24 ore per 7 giorni alla settimana) e così via. Altrettanto importante è definire eventuali non disponibilità e stime del funzionamento degradato del sistema. Degradato in quanto parallelamente allo svolgimento dei task utente si eseguono operazioni di manutenzione che rendono momentaneamente non disponibili (in parte o todo) determinate componenti del sistema. Per esempio in un sito per il commercio elettronico durante l’aggiornamento del listino dei prodotti alcune tabelle/record non possono essere accedute. • tempo medio tra due errori consecutivi (MTBF, Mean Time Between Failure). La definizione di questa stima è piuttosto singolare. Chiaramente tutti desidererebbero un valore tendente all’infinito + 1. Ciononostante, molte cose possono andare storte: errori software, guasti hardware, caduta delle comunicazioni, ecc. Chiaramente bisogna porre molta attenzione nella definizione di questa informazione (verba volant scripta manent, e se la fortuna è cieca la sfortuna ci vede benissimo…); • tempo massimo di non disponibilità consentito al sistema per la riparazione di eventuali guasti (MTTR, Mean Time To Repair). Chiaramente in questo contesto si vorrebbero valori prossimi allo zero, sebbene valori tipici sono dell’ordine delle decine di minuti/ore. • accuratezza o risoluzione che richiedono misure in termini di eventuali standard presenti presso l’organizzazione o l’area di business oggetto di studio. Lavorando in sistemi che trattano copiosamente dati numerici, potrebbe risultare importante definire l’accuratezza in termini di posizioni decimali valutate e arrotondamenti. Queste informazioni potrebbero assumere un valore importantissimo qualora i

34

Capitolo 5. Completamento dell’analisi dei requisiti

numeri rappresentino cifre di denaro e gli arrotondamenti possano fare la differenza (ogni riferimento agli ambienti bancari è puramente voluto…); • bug e/o difetti: sono stime definite in rapporto alle linee di codice (si consiglia di aggiungere molte linee di commento) e vengono effettuate per le diverse categorie di errore: minori, significanti, critici. Chiaramente è necessario definire formalmente cosa si intende, nel contesto del sistema oggetto di studio, con tali categorie di errore. Un altro insieme di requisiti non funzionali di particolare interesse è quello relativo alle procedure di backup. In questa sezione è necessario specificare quale strategia di backup si intende adottare, quando fisicamente eseguire i vari backup, su quale tipo di dispositivo, ecc. Altrettanto importante è dichiarare le procedure da attuare qualora si renda necessario eseguire il recovery, e la porzione di informazioni che si rischia di perdere nell’intervallo che intercorre tra l’esecuzione dell’ultimo backup e il sopraggiungere di fallimenti catastrofici. Esistono sistemi per i quali la perdita di dati, anche se relativa a pochi di minuti, è così gravosa — si considerino per esempio i sistemi centrali delle compagnie aeree — che, al fine di correre il minor rischio possibile, si realizzano configurazioni hardware particolarmente ridondanti, come per esempio 12 unità di backup in linea, con tempo di disallineamento dell’ordine dei 10 minuti. Altro gruppo di requisiti non funzionali decisamente importanti, specie per sistemi Internet o comunque esposti a connessioni remote, è quello della sicurezza (security). Si tratta di requisiti così importanti che, probabilmente, meriterebbero anch’essi di essere trattati in un apposito documento. In questa sezione è necessario definire le politiche di riconoscimento (autenticazione) degli attori (coppia login/password, firma digitale, riconoscimento della porta fisica richiedente la connessione, ecc.), le diverse tipologie di utente con la relativa matrice dei servizi fruibili, la modalità con cui proteggere i dati, sia quelli memorizzati permanentemente, sia quelli scambiati per mezzo di connessioni, algoritmi di crittazione, difese da parte di eventuali hacker, log con la storia delle azioni eseguite dagli utenti, ecc. Il problema della sicurezza è diventato assolutamente impellente con l’avvento di Internet. Infatti, se da un lato il web fornisce alle imprese la possibilità di disporre di un network mondiale a costi contenutissimi, dall’altro si tratta di un rete “libera”, virtualmente aperta a tutti. È necessario, quindi, fornire opportuni meccanismi atti a proteggere i dati circolanti nonché l’organizzazione stessa. Una sezione assolutamente tipica per il documento dei requisiti non funzionali è quella relativa alle performance del sistema. In questa sezione è necessario riportare informazioni del tipo:

UML e ingegneria del software: dalla teoria alla pratica

35

• tempo di risposta delle transazioni più significative. È lecito attendersi che i servizi richiesti più frequentemente e quelli assoggettati a stringenti vincoli temporali, siano erogati il più rapidamente possibile: la definizione di tale valore richiede l’individuazione delle funzioni ai requisiti testé enunciati e quindi la relativa stima; • throughput (numero di lavori eseguiti nell’unità di tempo, tipicamente transazioni per secondo). Chiaramente non ha senso stimare questo valore per tutte le transazioni, ma solamente per quelle ritenute più significative coerentemente a quanto definito nel punto precedente: spesso si preferisce fornire questi insiemi di valori sia in condizioni normali sia in condizioni di picco; • capacità, definita in termini di numero di utenti simultanei che il sistema deve essere in grado di gestire in condizioni normali e in condizioni di funzionamento degradato da attività di manutenzione; • utilizzo delle risorse ovvero percentuale di utilizzo delle principali risorse del sistema: connessioni, database, server, ecc. Un’altra sezione degna di interesse è quella relativa alla supportabilità (supportability) del sistema. Con questo termine di fa riferimento a tutti quegli standard e a quelle direttive che semplificano la manutenibilità del sistema da costruire: standard di codifica, componenti atti al controllo del sistema, naming convention (standard per l’attribuzione dei nomi), change cases, accessi per la manutenzione, e così via. Proseguendo nell’analisi dei requisiti non funzionali è necessario specificare eventuali vincoli di disegno esistenti. Esempi tipici sono: linguaggi di programmazione, paradigma utilizzato (Object Oriented, component-based, ecc.), architettura di riferimento, database management system, network, piattaforma software e hardware, legacy system presenti, ecc. Da notare che il dettaglio di queste informazioni dovrebbe essere presente nel SAD (Software Architecture Document, documento di architettura software). Un elemento spesso dimenticato, che invece assume una certa importanza per l’utente, è relativo alla documentazione utente. Pertanto è necessario definire i requisiti relativi alla documentazione online, sistemi di aiuto, manuali utente, e così via. Un altro gruppo di requisiti importante è relativo ai componenti software/sistemi della cui licenza si dispone che devono essere necessariamente utilizzati o che si intende utilizzare. Per esempio, in determinati progetti, potrebbe essere obbligatorio utilizzare uno specifico database management system perché magari già esiste un’applicazione che utilizza tale database con cui bisogna interagire, o perché si ha alle proprie dipendenze per-

36

Capitolo 5. Completamento dell’analisi dei requisiti

sonale esperto o semplicemente perché l’azienda dispone delle necessarie licenze (probabilmente è più opportuno inserire anche questa tipologia di informazioni nel SAD). Il template del documento relativo ai requisiti non funzionali (detto Supplementary Specifications, specifiche supplementari) provvisto dalla Rational, come accennato in precedenza, prevede la definizione delle interfacce fornite dal sistema, sia quelle utente, sia quelle dirette verso altri sistemi software ed hardware. Come ripetuto più volte, si tratta di un manufatto di importanza così elevata che probabilmente è il caso di realizzare un apposito documento. Le ultime sezioni del documento dei requisiti non funzionali sono dedicate a “requisiti” relativi a vincoli di utilizzo di software, eventuali restrizioni, copyright e standard da utilizzare definiti nell’ambiente business oggetto di studio. Quindi è necessario analizzare eventuali standard/vincoli legali, come per esempio avvisi di copyright da citare, eventuali notifiche, e così via. Il documento viene concluso con l’obbligatoria sezione dedicata ai riferimenti.

Le famose regole del business Si presenta ora un manufatto (artifact) di notevole importanza per la modellazione dei requisiti utente e, più in generale, per l’intero processo di sviluppo: le famose regole del business (business rules). Si tratta nuovamente di un concetto che, anche per via della difficoltà di fornirne una definizione precisa, si presta facilmente a essere equivocato. In questo paragrafo si tenterà di fornire informazioni ed esempi reali nella speranza di contribuire a neutralizzare la confusione che regna attorno al concetto. Al momento in cui viene scritto il libro, anche le regole del business sono fonte di dibattito, specie per ciò concerne le modalità di documentazione. In generale con “regole del business” ci si riferisce a princìpi, policy, leggi, dettagliati algoritmi di calcolo, relazione tra oggetti, ecc. che influenzano direttamente il dominio del problema che il sistema, in qualche misura, dovrà automatizzare. Pertanto rappresentano informazioni che, in ultima analisi, dovranno confluire nell’implementazione del sistema. Non a caso nelle architetture moderne multi-tier è presente uno strato denominato Business Service. Come si può notare la definizione è piuttosto ampia: si va da leggi sia scritte che non, a policy, a specifiche procedure di calcolo, ecc. Quello che è sicuro è che queste hanno un notevole impatto sul sistema da realizzare e che quindi è assolutamente necessario considerarle e documentarle appropriatamente. Il rischio che si corre nel non analizzarle propriamente è di realizzare un sistema, magari efficientissimo e ben congegnato, ma che semplicemente non risponde alle necessità dell’utente. Per esempio, viola le policy dell’azienda committente o non contempla regole che, seppur non formali, di fatto hanno un notevole impatto nel business, e così via.

37

UML e ingegneria del software: dalla teoria alla pratica

Da quanto specificato fino a questo punto, molte regole del business non sono altro che particolari requisiti utente. Allora una domanda che potrebbe sorgere è “perché utilizzare un manufatto diverso da quello dei casi d’uso? E, in caso, quale criterio utilizzare per decidere che un gruppo di funzionalità/specifiche debbano essere descritte per mezzo di un apposito manufatto?”.

Figura 5.11 — Diagramma delle business rule nel contesto del processo di sviluppo del software.

Use case ne definiscono gli stimoli

Statechart diagram

documentano

Collaboration diagram si riferiscono ne definiscono le collaborazioni

BUSINESS RULES

si riferiscono

Domain/ Business Object Model

risolvono

Activity diagram

ne definiscono le collaborazioni verificano

Analysis and Design Object Model

implementano

Test case

Sequence diagram Implementation

38

Capitolo 5. Completamento dell’analisi dei requisiti

Le motivazioni alla base della realizzazione di un apposito manufatto sono diverse. Una prima giustificazione è relativa alla tipologia della regole di business. Qualora per esempio si debba descrivere dettagliatamente un algoritmo, evidentemente i casi d’uso non rappresenterebbero la modalità migliore. Verosimilmente in questi casi è opportuno ricorrere a formalismi più adeguati come quello dei diagrammi delle attività. Altri metodi che possono essere utilizzati sono relativi al livello di dettaglio e alla complessità delle norme da descrivere. Qualora si abbia la necessità di specificare dettagliatamente delle regole, e nei casi in cui queste risultino particolarmente complesse o articolate, potrebbe essere opportuno descriverle in un apposito manufatto utilizzando i formalismi ritenuti, caso per caso, più appropriati (pseudocodice, tabelle diagrammi di flusso, ecc.). È opportuno che i casi d’uso non scendano eccessivamente nei dettagli, sia per non dar luogo alle anomalie descritte nei precedenti capitoli, sia per non perdere di vista l’aspetto generale della funzionalità che si vuole illustrare. Ancora, per questioni legate alla minimizzazione del lavoro di revisione dovuto a cambiamenti delle specifiche utente, potrebbe essere intelligente isolare requisiti potenzialmente soggetti a ripensamenti. Per esempio, una specifica dell’area della sicurezza che prevederebbe di sospendere l’account di un utente a seguito di 3 consecutivi fallimenti del log-in, rappresenterebbe una buona candidata a divenire business rule. Ciò permetterebbe di neutralizzare gli aggiornamenti da apportare qualora l’utente decidesse di aumentare a 4 o a 6 il numero di consecutivi fallimenti, in quanto le modifiche avverrebbero in un solo punto. Ancora, qualora delle regole siano presenti in diversi manufatti, potrebbe essere il caso di inserirne la descrizione in un apposito documento e quindi semplicemente referenziarle. Da tener presente che, durante il processo di estrapolazione delle business rule, bisogna fare attenzione a non esagerare nella catalogazione, altrimenti si corre il rischio sia di utilizzare in modo non appropriato moltissimo tempo, sia di avere use case eccessivamente parametrici da risultare praticamente incomprensibili o svuotati di ogni contenuto.

Come si può notare le regole del business hanno un impatto non indifferente in molte fasi e numerosi modelli del processo di sviluppo di sistemi software. In alcuni casi giungono addirittura a guidare la stessa realizzazione degli artifact. Un esempio è dato dai test case. Uno dei loro compiti è infatti verificare che il sistema software inglobi correttamente le business rule. Per esempio, una delle funzionalità dei sistemi di back office delle banche è verificare che le tariffe concordate con i clienti con cui si sono stipulati dei contratti (trade) siano in un determinato intorno delle quotazioni di mercato. Questa funzionalità

UML e ingegneria del software: dalla teoria alla pratica

39

si rende necessaria al fine di controllare l’operato dei broker, in altre parole per verificare che nessuno si comporti irregolarmente, magari accordandosi con qualche cliente per tariffe più vantaggiose in cambio di qualche ricompensa. La regola potrebbe prevedere che, se i limiti iniziali non sono rispettati ma comunque la variazione risulti ristretta ad un certo fattore (per esempio 10%), il sistema emetta un warning di cui il responsabile del team dei broker dovrà prendersi carico, mentre se risultasse superiore anche a quest’ultimo limite, allora lo stesso trade dovrebbe essere bloccato. Chiaramente, uno dei compiti dei casi di test è verificare che questi controlli funzionino effettivamente come sancito da queste regole. Una verifica potrebbe richiedere la somministrazione al sistema diversi trade con parametri fuori mercato al fine di controllare che il sistema sia in grado di riconoscere le varie versioni di anomalie e quindi intraprendere i necessari provvedimenti. Altri manufatti strettemente vincolati alle business rules, sono il modello dei casi d’uso e quello a oggetti del dominio (o la versione business). I primi contengono veri e propri riferimenti per i motivi visti in precedenza, mentre i secondi forniscono una rappresentazione Object Oriented delle regole. In sostanza leggendo il diagramma delle classi, e in particolare scorrendo le varie associazioni che legano le entità descritte, è possibile evidenziare tutta una serie di regole business, che probabilmente però non è il caso di trascrivere tutte in un apposito documento. Sarebbe anche un’attività molto utile, sia per verificare il modello, sia per renderlo fruibile a una platea non esperta dei diagrammi delle classi (come per esempio gli utenti che comunque dovrebbero validarlo). Però, in progetti di medio/grandi dimensioni, il tempo richiesto per la redazione del documento ne rende poco fattibile la produzione. Questa categoria di regole è, tipicamente, etichettata come strutturali. Per esempio, nel contesto di un sistema di una compagnia di assicurazioni, un apposito diagramma delle classi, potrebbe illustrare le seguenti regole: una compagnia di assicurazione può stipulare diversi contratti; ciascuno di essi può essere riferito a una o più persone, o a una singola azienda, ecc. Da questo punto di vista, le regole del business possono essere considerate unità di conoscenza del business. Spesso i diagrammi delle classi non sono sufficienti ad esprimere correttamente le regole e quindi è necessario avvalersi di ulteriori formalismi, come il linguaggio OCL. I diagrammi delle attività si prestano a descrivere regole del business denominate computazionali e comportamentali, mentre per ciò che concerne i diagrammi degli stati, esse rappresentano gli stimoli che determinano l’avvio del ciclo di vita di particolari oggetti, le transizione da uno stato a un altro, ecc. Spesso l’acquisizione delle regole del business è un’attività tutt’altro che facile. Non è infrequente, infatti, il caso in cui molti utenti, anche esperti dell’area business, giunto il momento di definire formalmente determinate regole vigenti nel proprio dominio, comincino a vacillare. Un esempio? I sistemi di backoffice bancari. A questo punto ci si dovrebbe interrogare su quale sia la novità…

40

Capitolo 5. Completamento dell’analisi dei requisiti

Tabella 5.6 — Modello per le regole di business. BUSINESS RULE: Codice

Nome della business rule.

Data: Versione: 0.00.000

Descrizione: Dettaglio:

Descrizione generale della business rule. Dettaglio della business rule.

Esempio: Criticità:

Esempio chiarificatore. Impatto della business rule nel sistema..

Sorgente:

Sorgente dalla quale è stata prelevata o è scaturita la regola business. Codice Nome della regola business oggetto di documentazione

Business rule riferite:

CICLO DI VITA Azione

Data

Descrizione

Autore

Definizione Aggiornamento

Si consideri la realizzazione del sottosistema di sicurezza del sistema informatico di un’azienda. Una policy vigente potrebbe prevedere che ogni utente disponga di un unico ruolo di sicurezza, ossia di un’assegnazione che abiliti l’esecuzione di specifici servizi e ne inibisca altri. Per esempio un ruolo di contabilità potrebbe abilitare i servizi relativi alla compilazione dei salari dei dipendenti, ed escludere quelli relativi alla pianificazione dei progetti aziendali. Pertanto, la realizzazione di un sistema che preveda diversi ruoli di sicurezza per uno stesso utente sarebbe chiaramente una violazione della policy. I sistemi ovviamente devono essere in grado di automatizzare sia le regole scritte sia quelle esistenti di fatto, altrimenti si realizzerebbe un sistema molto povero. In molte banche, per esempio, è prevista un’ulteriore regola che sancisce che modifiche ai dati appartenenti a determinate aree siano sottoposte a un doppio controllo. Per cui un utente inserisce determinati dati e un altro li controlla ed, eventualmente, li valida. Questi due utenti chiaramente si trovano a uno stesso livello gerarchico. Cosa avviene spesso nella realtà? Semplice, lo stesso utente effettua un doppio login nel sistema con due differenti utenze e quindi inserisce e convalida i dati! Altri esempi di regole di business potrebbero essere: la procedura utilizzata per calcolare il codice fiscale, quella per verificarne l’esattezza (calcolo del codice di controllo, ultimo carattere del codice fiscale), per verificare l’esattezza della partita IVA, ecc.

41

UML e ingegneria del software: dalla teoria alla pratica

A questo punto il primo problema che si pone è quale sia la forma migliore per descrivere le regole del business. La risposta più semplice e immediata potrebbe essere quella di ricorrere alla sempre valida forma testuale, magari resa più accattivante per mezzo di un apposito modello (tab. 5.6). Le varie informazioni riportate sono piuttosto immediate da comprendere. L’unica cosa a cui è necessario porre particolare attenzione è il codice: è importante assicurarne l’univocità dal momento che viene riferito in altri manufatti (diagrammi dei casi d’uso, diagrammi delle classi, e così via). Tabella 5.7 — Esempio di regole business. BUSINESS RULE: BR-CSF-023 Descrizione: Dettaglio:

Esempio:

Spawned Deals

20 Ago 2001 Data: 0.00.002 Versione:

Spawned Deals è la procedura che, tipicamente, si rende necessaria qualora un FX Trade (Foreign eXchange) sia relativo a valute desuete. La necessità di eseguire questa procedura si presenta quando per le valute scambiate nel FX Trade non esiste la corrispondente dealing position nel book di appartenenza del trade stesso. Tipicamente ciò avviene per scambi tra valute desuete. In queste condizioni, il Trade effettuato nel front office dà luogo ad altre due istanze nel back office, in quanto viene introdotta, come valuta di intermediazione, il dollaro americano (USD). Si dice quindi, che il trade è “spawned against USD”. Si supponga di ricevere un FX Trade del tipo: 1223 MYR/SGD MYR=x e SGD = y Ossia uno scambio tra x Malaysian Ringgit ed y Singapore Dollar Poiché questo scambio è decisamente infrequente, non esiste un corrispondente dealing position e quindi è necessario dar luogo allo spawned contro dollari US 1223 MYR/SGD MYR=x e SGD = y 1224 MYR/USD MYR=x e USD =z 1225 SGD/USD SGD= z e USD= y

Criticità: Sorgente:

Importante. Analisi del sistema di legacy.

Business rule riferite: CICLO DI VITA Azione Definizione

Data 10-6-00

Descrizione Definizione iniziale

Autore LVT

Aggiornamento

20-6-00

Inserimento esempio

RV

Aggiornamento

20-8-00

Correzione esempio.

RV

42

Capitolo 5. Completamento dell’analisi dei requisiti

Si consideri l’esempio di tab. 5.7. La regola business considerata fa riferimento a una procedura che si applica nel back office di sistemi bancari, in determinate condizioni, nella gestione del prodotto denominato FX (Foreign eXchange): scambio di valute estere. Il principio di base è molto semplice: si scambia una certa quantità di una valuta con un’altra quantità di un’altra valuta. Ebbene questa evoluzione del baratto è il prodotto Tabella 5.8 — Esempio di regole business relative alle SSI. BUSINESS RULE: BR-SSI-002

Data:

10 Ago 2001 Versione: 0.00.001

Selezione SSI della controparte (Select counter-party’s SSI)

Descrizione:

Questa regola specifica i criteri da utilizzarsi per selezionare la specifica regola di pagamento della controparte con cui la banca ha stipulato un Trade. Qualora la banca non disponga di queste informazioni è necessario generare un Work Item al fine di informare l’apposito personale della lacuna.

Dettaglio:

La procedura prevede di: 1. reperire tutte le SSI appartenenti alla Processing Organization relative alla sola controparte con cui si è stipulato il Trade, il cui periodo di validità includa la data di maturazione del Trade; 2. selezionare solo quelle di pagamento (SSI.type = “receive”) o solo quelle di addebito (SSI.type = “pay”) in funzione del flusso a cui si è interessati. Da tener presente che un’istruzione di pagamento di una Processing Organization corrisponde ad un addebito per la controparte e viceversa; 3. selezionare unicamente quelle relative al prodotto venduto nel trade. Da tener presente che le SSI possono riferirsi ad un unico prodotto, ad un insieme o a tutti (ALL); 4. selezionare esclusivamente le SSI relative alla stessa valuta utilizzata per il flusso in questione (pagamento/addebito). Per il vincolo sulla valuta valgono le regole citate nel punto precedente; 5. verificare che eventuali ulteriori vincoli presenti nelle SSI e relative al Trade siano verificate (per esempio si potrebbero avere regole diverse a seconda degli importi); 6. qualora l’intero processo preveda come risultato una lista di SSI, selezionare quella a maggiore priorità; 7. nel caso in cui la priorità non risolva ancora il problema selezionare la SSI più recente.

Esempio: Criticità: Sorgente: Business rule riferite:

Elevata. Work shop con gli utenti

CICLO DI VITA Azione

Data

Descrizione

Autore

Definizione

10-8-01

Definizione iniziale

LVT

UML e ingegneria del software: dalla teoria alla pratica

43

che muove qualche decina di miliardi di dollari al giorno, prevalentemente tra New York, Londra e Tokyo per fini, ovviamente, speculativi. Sempre nel sistema bancario, altre entità molto importanti sono le famose Istruzioni di pagamento. Ne esistono due versioni: quelle da applicare (SSI, Settlement Standing Instruction) e quelle applicate (Settlement Instruction). La differenza consiste nel fatto che le prime forniscono regole generali, nel senso che specificano per ogni trade effettuato, in base a diversi criteri (cliente, prodotti trattati, valute, ecc.), quali conti correnti utilizzare per addebitare e prelevare, mentre le seconde sono particolari istanze delle prime, che una volta applicate perdono di generalità e vengono associate al trade. Ciò permette di modificare le SSI, magari per esempio perché un cliente vuole utilizzare un nuovo conto corrente, evitando che le modifiche si riversino in regole già applicate. Chiaramente di queste regole esistono quelle associate ai clienti, e quelle della banca. In altre parole bisogna conoscere sia i conti correnti dei clienti sia quelli della banca cui addebitare e accreditare (tab. 5.8). Molto spesso alcune regole del business sono rappresentate da algoritmi necessari per calcolare specifici valori (regole computazionali). Per esempio, nei sistemi bancari è necessario calcolare i fattori di rischio di determinati trade. In questi casi, non è infrequente che, per la descrizione della regola di business, alla soluzione testuale ne sia preferita una più intuitiva basata sui diagrammi delle attività. In questo contesto, tipicamente, essi sono utilizzati come particolari versioni dei famosi diagrammi flowchart. In ultima analisi, si tratta di scegliere una forma diversa per mostrare le stesse informazioni. In generale, le regole del business si prestano a essere rappresentate per mezzo di diversi formalismi, come il diagramma delle classe e l’Object Constraint Language. Si consideri, per esempio, la regola secondo la quale ogni conto corrente può appartenere a diversi clienti ma, nel caso in cui si tratti di un’azienda, allora il cliente deve essere unico. La regola del business rappresenta un vincolo relativo a entità che effettivamente esistono nell’area business oggetto di studio e pertanto il formalismo dei diagrammi delle classi, accompagnato da un vincolo in OCL, rappresenta il formalismo più idoneo da utilizzarsi. Affrontato il problema della forma, è necessario passare a quello inerente la sostanza.

Quali direttive seguire per assicurarsi che le regole siano ben analizzate e rappresentate? La risposta migliore è seguire l’usuale principio di agevolare la comunicazione: fare in modo che le regole del business siano il più possibile complete ed espresse nella forma più chiara possibile. Pertanto è necessario cercare di specificare regole coesive, focalizzando l’attenzione su una singola regola alla volta. In breve, è importante che ad ogni regola del business corrisponda un unico concetto ben definito. Nella pratica, capita di imbattersi in business rule che non rispettano questi princìpi. Generalmente regole sifatte tendono a generare confusione nel lettore, soprattutto qualora quest’ultimo non sia esperto dell’area business oggetto di analisi.

44

Capitolo 5. Completamento dell’analisi dei requisiti

Figura 5.12 — Algoritmo di calcolo della stringa di login. Da notare che il diagramma rappresenta un requisito e quindi non è stata introdotta alcuna ottimizzazione. Queste appartengono allo spazio delle soluzioni. Un esempio potrebbe essere quello di utilizzare una strategia più “furba” per la risoluzione dei conflitti. Per esempio, dopo un certo numero di tentativi falliti, si potrebbe risolvere il problema effettuando una ricerca nella tabella utente utilizzando come chiave i primi caratteri che generano il conflitto e quindi sfruttare il primo “buco” tra due codici successivi.

name = user.getName() surname = user.getSurname()

login = user.trim()

Verifica lunghezza stringa di login [lunghezza > 9 caratteri]

login = substring(login,1,9)

[lunghezza 10 caratteri]

login = substring(login,1,10)

[lunghezza Animale > Mammifero > Cane > ...

6

Capitolo 6. Object Oriented in un chicco di grano

Banalizzando estremamente e con tutte le inesattezze del caso, si può pensare alle classi come stampi per statue fittili e agli oggetti come le statue stesse realizzate attraverso le matrici. In questa banale similitudine, si trascura che anche l’impasto, il relativo colore, ecc. hanno il loro peso, ma l’importante è cercare di spiegarsi...

Elementi fondamentali di un oggetto “Un oggetto possiede stato, comportamento e identità; la struttura e il comportamento di oggetti simili sono definiti nella loro classe comune; i termini di istanza e oggetto sono intercambiabili.” [Grady Booch].

Stato Gli oggetti, tipicamente, non vengono creati per permanere in un determinato stato; al contrario, durante il loro ciclo di vita transitano in una serie di fasi. Alcuni di essi sono vincolati a evolvere attraverso un insieme finito di fasi, mentre per altri è infinito oppure molto grande. Quindi, mentre per i primi (o meglio per la classe di cui sono istanza) può avere molto senso descrivere il diagramma degli stadi attraverso i quali i relativi oggetti possono transitare durante la propria vita, per gli altri l’esercizio è decisamente più complesso e non sempre fattibile e/o utile. Si consideri una classe che rappresenta una semplice lampadina, in questo caso l’insieme degli stati dei relativi oggetti prevede due soli elementi: acceso e spento. Si consideri ora una sua evoluzione, ossia una lampadina digitale con un numero ben definito di diverse intensità luminose. In questo caso l’insieme degli stati potrebbe prevedere: spenta, accesa intensità 1, accesa intensità 2, ..., accesa intensità max. Altri oggetti, invece, durante l’arco della propria vita evolvono attraverso una serie di stati, che però non sono numerabili. Si consideri per esempio un sistema di illuminazione delle stanze la cui funzione sia accendere/spegnere i vari faretti in funzione del numero di persone presenti nelle stanze. Sebbene questo numero sia delimitato (il numero massimo di persone stipabili all’interno della stanza), non è conveniente indicare i vari stati dell’oggetto (una persona, due persone, tre persone, …). Un’ultima categoria è costituita dagli oggetti il cui stato è (teoricamente) infinito. Per esempio, si consideri un’estensione del sistema precedente, in cui la decisione di accendere e/o spegnere l’illuminazione dipenda anche dal valore dell’intensità luminosa segnalata da un apposito oggetto (sensore). In questo caso, il dominio dei valori dei dati forniti sarebbe teoricamente infinito: si tratterebbe della trasposizione digitale di grandezze fisiche (segnali continui per definizione). In pratica la differenza tra due misure successive non è infinita, bensì è legata alla sensibilità del trasduttore (dispositivo atto a tradurre grandezze fisiche in segnali di altra natura, tipicamente, elettrica), al numero di bit del convertitore analogico/digitale, ecc. Lo stato di un oggetto è molto importante poiché ne influenza il comportamento futuro. Gli oggetti, almeno loro, hanno una certa memoria storica. Tipicamente, sottoponen-

UML e ingegneria del software: dalla teoria alla pratica

7

do opportuni stimoli a un oggetto (invocazione dei metodi, o invio di messaggi se si preferisce), questo tende a reagire, nella maggior parte dei casi, in funzione del suo stato interno. Si consideri l’esempio della lampadina elementare. Se una sua istanza si trova nello stato di accesa e ne viene richiesta nuovamente l’accensione (turnOn()), nulla accade, così come nella versione digitale a diverse luminosità, se è spenta e si tenta di variarne l’intensità luminosa, nuovamente, nulla accade. Un ennesimo esempio è fornito dal lettore CD: se si preme il tasto di play senza aver inserito un CD, nulla accade (viene generata un’eccezione), mentre la pressione dello stesso tasto, con CD inserito, avvia il suono della musica. Pertanto il comportamento di molti oggetti è influenzato dal relativo stato. In queste circostanze l’ordine con cui ne vengono invocati i messaggi è, tipicamente, importante. Se si inserisce il CD e si preme il tasto play si assiste a uno specifico comportamento, che è diverso da quello generato dalla sequenza: pressione del tasto play e inserimento del CD. Lo stato di un oggetto è un concetto dinamico e, in un preciso istante di tempo, è dato dal valore di tutti i suoi attributi e dalle relazioni instaurate con altri oggetti (che alla fine sono ancora particolari valori, indirizzi di memoria, attribuiti a specifici attributi). Come si vedrà successivamente, è molto importante nascondere quando possibile lo stato di un oggetto al resto del mondo. Sicuramente deve esserne sempre nascosta l’implementazione (principio dell’information hiding) e quando possibile anche lo stato stesso (minimizzare l’accoppiamento di tipo). Quest’ultima possibilità dipende ovviamente dagli obiettivi (responsabilità) della classe. Se per esempio una classe rappresenta un sensore atto a valutare la temperatura del reattore nucleare di una centrale atomica, potrebbe aver senso comunicare all’esterno lo stato dei relativi oggetti.

Comportamento Una volta studiato e formalizzato lo stato di un oggetto si è effettuato un passo in avanti nel processo di astrazione, ma non ancora è sufficiente per la completa descrizione dello stesso. Molto importante è analizzarne anche il comportamento. Un oggetto non solo non viene creato per essere lasciato oziare in uno specifico stato, ma neanche per lasciarlo morire di solitudine. Tipicamente un oggetto interagisce con altri scambiando messaggi, ossia rispondendo agli stimoli provenienti da altri oggetti (richiesta di un servizio) e, a sua volta, inviandoli ad altri al fine di ottenere la fornitura di “sottoservizi” necessari per l’espletamento del proprio. Quindi, il comportamento di un oggetto è costituito dalle inerenti attività (operazioni) visibili e verificabili dall’esterno. Come visto poc’anzi, lo scambio di messaggi, generalmente, varia lo stato dell’oggetto stesso. In sintesi “il comportamento stabilisce come un oggetto agisce e reagisce, in termini di cambiamento del proprio stato e del transito dei messaggi.” [Booch]. Un’operazione è una qualsiasi azione che un oggetto è in grado di richiedere a un altro al fine di ottenere la reazione desiderata. Per esempio un oggetto ContoCorrente potrebbe richiedere l’inserimento di un credito in un altro oggetto variandone lo stato, così

8

Capitolo 6. Object Oriented in un chicco di grano

come potrebbe richiederne l’estratto conto, senza apportare alcuna modifica allo stesso oggetto. È evidente che la relazione esistente tra lo stato di un oggetto e il comportamento è di mutua dipendenza: è possibile considerare “lo stato di un oggetto, in un certo istante di tempo, come l’accumulazione dei risultati prodotti dal relativo comportamento”, il quale, a sua volta, dipende dallo stato in cui si trovava l’oggetto all’atto dell’esecuzione del “comportamento”.

Identità L’identità di un oggetto è la caratteristica che lo contraddistingue da tutti gli altri. Spesso ciò è dato da un valore univoco. Per esempio un oggetto ContoCorrente è identificato dal relativo codice, da una persona, dal codice fiscale, e così via.

Figura 6.2 — In questo (meta) modello sono distinte più nettamente le caratteristiche a tempo di implementazione (Classe, ProprietàComportamentali e ProprietàStrutturali) da quelle a tempo di esecuzione (Oggetto, Identità e Stato). Le proprietà comportamentali rappresentano l’insieme dei metodi esposti da una classe e quindi invocabili da parte di oggetti istanze di altre classi, mentre quelle strutturali rappresentano l’insieme degli attributi. Per quanto concerne la notazione, per il momento si consideri il diamante pieno (relazione di composizione) come una relazione strutturale molto forte tra due entità, di cui le istanze della classe con il diamante rappresentano il concetto generale costituito dalle istanze delle altre classi associate.

ProprietaComportamentali 1 Classe 1 1

ProprietaStrutturali

è istanza di

*

Identita 1

Oggetto 1

Stato

UML e ingegneria del software: dalla teoria alla pratica

9

Tutti gli oggetti indossano un’interfaccia Nella consultazione dei seguenti paragrafi dedicati ai concetti di interfaccia si presti bene attenzione a non disorientarsi. In effetti con questo termine si indicano diversi concetti non solo nella comunità dell’Object Oriented ma anche in quella più vasta dell’informatica in generale. In particolare e brevemente, nel presente paragrafo si fa riferimento a una caratteristica intrinseca posseduta da tutte le classi, data dall’elenco dei metodi e attributi non privati che permette di definire responsabilità ed estensibilità degli oggetti istanza della classe. Nei paragrafi successivi si fa riferimento al concetto UML e Java di interfaccia ossia come definizione pura di tipo. Anche le classi permettono di definire un tipo, ma a differenza delle interfacce, vi definiscono anche l’implementazione.

Come visto nei paragrafi precedenti, una volta definita una nuova classe, a meno di vincoli particolari (vedi per esempio il pattern Singleton), è possibile dar luogo a quante istanze (oggetti) della classe si vuole. Chiaramente avrebbe ben poco senso dar vita a nuovi oggetti per poi lasciarli vivere isolatamente. Sebbene ogni oggetto sia, potenzialmente, in grado di realizzare specifiche funzionalità dall’inizio alla fine, tipicamente, non lo fa per propria iniziativa, bensì come risposta a esplicite richieste da parte di altri oggetti (detti messaggi). Ciò è possibile poiché esiste un meccanismo che permette a un oggetto di richiedere a un altro di “fare qualcosa”. Ogni oggetto è in grado di soddisfare solo tipi ben definiti di richieste, specificati dalla propria “interfaccia”. Pertanto in questo contesto, con il termine interfaccia non si fa riferimento a elementi come per esempio il costrutto interface del linguaggio Java, dotati esclusivamente della firma dei propri metodi, bensì alla caratteristica intrinseca posseduta da ogni classe: l’elenco di metodi e attributi non privati corredati dalla relativa firma (ciò che nella figura 6.2 è stato rappresentato dalla classe ProprietàComportamentali). Per essere più precisi, in tutti i linguaggi Object Oriented che prevedono visibilità protected (indica metodi e attributi di una classe visibili solo dalle classi ereditanti), è possibile esprimersi in termini di due versioni di interfaccia: quella “visibile” a tutte le classi (elenco di metodi e attributi pubblici) e quella relativa alle sole classi ereditanti (costituita dai metodi e dagli attributi pubblici e protetti). Volendo si potrebbe complicare ulteriormente la situazione considerando anche la visibilità di tipo package che quindi genererebbe una terza versione di interfaccia contenente le due precedenti. Si ricordi che in UML un elemento con visibilità package (indicata con il carattere tilde ~) di una classe X è visibile da tutte le classi appartenenti allo stesso package (o a uno da esso annidato qualsiasi livello) della classe X.

10

Capitolo 6. Object Oriented in un chicco di grano

Figura 6. 3 — Le due versioni di un’interfaccia. Come da standard UML il segno meno (-) posto davanti a un metodo o attributo ne indica una visibilità privata e pertanto l’elemento non appartiene all’interfaccia esposta dalla classe. Il segno più (+) indica una visibilità pubblica e quindi l’elemento appartiene all’interfaccia visibile da tutti gli oggetti istanza di classi relazionate, in qualche maniera, a quella che possiede l’elemento. Il segno diesis (#) rappresenta una visibilità protetta e quindi i relativi elementi appartengono all’interfaccia per così dire di eredità. Interfaccia visibile unicamente dalle classi che ereditano dalla ClasseX

ClasseX - attributo1 : TipoA - attributo2 : TipoC # attributo5 : TipoA # attributo6 : TipoB # attributo7 : TipoY + attributo3 : TipoZ + attributo4 : TipoX - metodo1() + metodo2() + metodo3() + metodo4() # metodo5() # metodo6() # metodo7() Interfaccia esposta a tutte le classi

Volendo è possibile considerare l’interfaccia propria di una classe con una vera e propria “interface” non dichiarata, incorporata nella classe stessa. Pertanto le richieste che un oggetto può soddisfare sono specificate dalla propria interfaccia, o, meglio, dall’interfaccia della classe di appartenenza. L’interfaccia, anche se implicita, rappresenta un contratto stipulato tra gli oggetti fornitori e quelli utilizzatori di servizi: in essa vengono condensate tutte le assunzioni che gli oggetti client fanno circa quelli di cui utilizzano i servizi (server). Chiaramente l’interfaccia determina la o dipende dalla struttura interna degli oggetti fornitori di servizi. L’invocazione di un metodo di un oggetto (per motivazioni storiche derivanti dal linguaggio SmallTalk), tecnicamente, è considerato come l’invio di un apposito messaggio allo stesso oggetto.

UML e ingegneria del software: dalla teoria alla pratica

11

Si consideri, per esempio, un sistema di riscaldamento di una casa, pilotato centralmente da un computer. Ogni radiatore potrebbe essere rappresentato da un’istanza di un’apposita classe (Radiator), i cui metodi eseguibili potrebbero essere: turnOn turnOff turnFanOn turnFanOff increasePower decreasePower

(avviamento) (spegnimento) (attivazione della ventola) (disattivazione della ventola) (aumento potenza) (decremento potenza)

L’interfaccia di un oggetto è spesso definita protocollo. In effetti stabilisce i servizi offerti e la “sintassi” (firma dei vari metodi) con cui utilizzarli. Tipicamente, in oggetti non banali, il protocollo può essere ripartito in gruppi di comportamento, definiti ruoli. Questi rappresentano delle “maschere” che un oggetto può indossare (“Uno, Nessuno e Centomila”, magari “nessuno” sarebbe un po’ difficile) per stipulare contratti con altri oggetti. Per esempio i dipendenti di una certa organizzazione, potrebbero essere modellati attraverso una specifica classe denominata Employee. Ora una determinata istanza di questa classe, in una particolare relazione con i progetti, potrebbe recitare il ruolo di manager, caratterizzato da particolari proprietà, per questioni amministrative potrebbe recitare il generico ruolo del dipendente, e così via. L’interfaccia propria di un oggetto è il luogo in cui sono specificate le assunzioni che oggetti cliente possono fare circa le istanze della classe.

Figura 6.4 — Rappresentazione UML della classe Radiator. Indipendentemente dalla presenza o meno di un’interfaccia esplicita, la classe Radiator ne possiede una implicita data dalla lista di metodi e attributi non privati, corredati dalla relativa firma.

Radiator +turnOn() +turnOff() +turnFanOn() +turnFanOff() +increasePower() +decreasePower()

12

Capitolo 6. Object Oriented in un chicco di grano

Interfaccia (Java/UML) In questo paragrafo con il termine interfaccia si fa riferimento al concetto a cui normalmente si è portati a pensare: ossia al costrutto che permette di definire un “tipo puro” (in quanto non direttamente istanziabile e quindi senza implementazione) attraverso la definizione di un insieme di operazioni identificato da un opportuno nome. In altre parole si fa riferimento al costrutto interface del linguaggio di programmazione Java e alla metaclasse estendente il classificatore nel metamodello UML. Per essere precisi, sebbene il concetto sia lo stesso, esiste una sottile differenza tra la versione Java e quella UML: nel linguaggio Java un’interfaccia può possedere attributi, sebbene questi siano automaticamente static e final (la versione Java del concetto di costante). Al fine di evitare ogni possibile confusione alla fonte, in questo paragrafo ci si riferirà al concetto di interfaccia con i termini di interfaccia esplicita.

Un’interfaccia è un insieme, identificato da un nome, di operazioni (corredate dalla firma) che caratterizzano il comportamento di un elemento. Si tratta di un meccanismo che rende possibile dichiarare esplicitamente le operazioni visibili dall’esterno di classi, componenti, sottosistemi, ecc. senza specificarne la struttura interna. L’attenzione è quindi focalizzata sulla struttura del servizio esposto e non sull’effettiva realizzazione (separazione tra la definizione di tipo e l’implementazione). Per questa caratteristica, le interfacce si prestano a demarcare i confini del sistema o del componente a cui appartengono: espongono all’esterno servizi che poi altre classi (interne) hanno la responsabilità di realizzare fisicamente. Qualora una classe implementi un’interfaccia, deve necessariamente dichiarare (o eventualmente ereditare) tutte le operazioni definite da quest’ultima. Le interfacce non possiedono implementazione o stati: dispongono unicamente della dichiarazione di operazioni, definita firma, e possono essere connesse tra loro tramite relazioni di generalizzazione. Visibilità private dei relativi metodi avrebbero ben poco significato, sebbene sia sempre possibile imbattersi in programmatori intenti nella disperata impresa di dichiarare metodi privati e pertanto visibili solo all’interno — non esistente — di un’interfaccia. Grazie al cielo i compilatori non permettono la dichiarazione di visibilità non compatibili con il contesto. Una singola interfaccia dovrebbe dichiarare un comportamento circoscritto, ben definito, a elevata coesione e, quindi, elementi con comportamento complesso potrebbero realizzare diverse interfacce. Il concetto di interfaccia rappresenta un’astrazione di estrema importanza per il disegno di modelli Object Oriented: è la chiave per la realizzazione di sistemi eleganti, flessibili, in grado di agevolare l’assorbimento dei famosi change requirements (variazione dei requisiti), ecc. La sua centralità è addirittura cresciuta esponenzialmente con l’introduzione dei sistemi component-based, caratterizzati da una ancora più netta separazione tra i servizi esposti da un componente e la relativa implementazione.

13

UML e ingegneria del software: dalla teoria alla pratica

Figura 6.5 — Nel modello illustrato sono presenti i tre livelli di astrazione di un oggetto: l’interfaccia che costituisce la specificazione, la classe che ne rappresenta l’implementazione e l’oggetto che ne rappresenta l’immagine a tempo di esecuzione. Come si può notare, una classe può implementare diverse interfacce così come un’interfaccia può essere implementata da diverse classi.

Interface

* è implementata ProprietaComportamentali

*

1

Classe 1 1

ProprietaStrutturali

è istanza di

*

Identita 1

Oggetto 1

Stato

Il concetto di interfaccia rende possibile un insieme di meccanismi, come l’aggiunta (apparentemente) indolore di nuove funzionalità, la rimozione di altre, la sostituzione di componenti con altri più moderni (magari versioni più efficienti che rispondono meglio alle nuove richieste dei clienti, …), la realizzazione di Framework, ecc. Queste peculiarità derivano dal fatto che l’interfaccia costituisce un vero e proprio strato di indirezione tra le classi che la implementano e la restante parte del sistema. Quindi, gli oggetti che interagiscono con altri facendo riferimento alle relative interfacce delle classi di appartenenza vedono questi ultimi solo attraverso quanto dichiarato nell’interfaccia stessa, senza possedere alcuna conoscenza diretta delle classi che la implementano. Una classica similitudine, spesso riportata per chiarire il concetto di interfaccia, è relativa agli slot di espansione dei computer. Gli slot possono essere visti come la definizione

14

Capitolo 6. Object Oriented in un chicco di grano

formale di un’interfaccia (volendo essere precisi gli slot sono più potenti: possiedono condizioni pre-, post- e durante l’utilizzo), il computer è il sistema software (qui lo sforzo di immaginazione richiesto è piuttosto ridotto) mentre le scheda rappresenta la classe (probabilmente sarebbe più opportuno esprimersi in termini di package) che realizza i servizi esposti dall’interfaccia. Il computer è in grado di utilizzare un certo numero di nuovi dispositivi, la cui interfaccia però deve uniformarsi a quella prevista dai relativi zoccoli degli slot. In caso di necessità è possibile sostituire una scheda con una più moderna (si consideri il caso di una scheda grafica): fintantoché l’interfaccia resta la stessa il tutto continua a funzionare perfettamente. È anche possibile rimuovere una scheda (un intero servizio) senza sostituirla e, nei limiti dettati dall’importanza del servizio rimosso, il tutto continua a funzionare. Sebbene ormai da tanto tempo esista il concetto del Plug & Play (ribattezzato dai tecnici esperti della materia Plug & Pray), inserire fisicamente una nuova interfaccia, sostituirne una con un’altra più moderna, o semplicemente eliminarla, senza dover eseguire ulteriori funzionalità aggiunte è qualcosa che nella pratica non accade quasi mai. Tipicamente è necessario eseguire qualche operazione di configurazione. Allo stesso modo, anche nei sistemi software, tipicamente è necessario eseguire alcune operazioni supplementari, come dichiarare l’esistenza in una nuova classe in un apposito file di configurazione, modificare un factory per la creazione delle relative istanze, ecc. In conclusione della metafora, si può pensare che ogni qualvolta nel disegno di un sistema si introduce un’interfaccia è come se si installasse uno zoccolo per schede di espansione in un PC e quindi, virtualmente, si realizza un punto di “plug-in” in cui è possibile cambiare funzionalità, aggiornare la scheda, aggiungere altri servizi ecc. Sintetizzando, un’interfaccia è vista dal sistema come un protocollo predefinito da utilizzarsi per interagire con le classi che fisicamente la implementano. Ciò permette di realizzare concetti dell’ingegneria del software come minimo accoppiamento, i cui vantaggi sono ben noti e legati, principalmente, alla riduzione delle dipendenza tra le diverse componenti del sistema (sistemi flessibili, quindi più recettivi alle modifiche, maggiore possibilità di riutilizzo del codice, ecc.). Per l’attribuzione del nome alle interfacce, esistono diverse convenzioni. Alcuni preferiscono premettere la lettera maiuscola I mentre altri prediligono lasciare il nome inalterato all’interfaccia per poi aggiungere il suffisso Impl alla classe che la implementa (questa convenzione funziona bene solo nei contesti in cui ogni interfaccia esposta possegga esattamente una classe che la implementi). Le particelle grammaticali candidate per rappresentare il nome delle interfacce sono, come per le classi, i sostantivi. Talune volte si utilizzano anche i verbi, qualora la classe serva per definire in maniera astratta una o più operazioni. Il primo caso tende a implicare che ci si riferisce ad un accesso a dati (ITicket, IHotel), mentre un verbo rappresenta la richiesta di una computazione (IBooking, ICheckAvailability). Quest’ultima in generale non è un’ottima pratica, in quanto è sufficiente avere la necessità di aggiungere un ulteriore metodo

UML e ingegneria del software: dalla teoria alla pratica

15

che il nome dell’interfaccia rischia di diventare inconsistente. Pertanto è sempre opportuno utilizzare un sostantivo (IBooker, IAvailabilityChecker, ecc.).

Conversando con personale di varie organizzazioni, l’autore ha avuto modo di constatare come alcuni tecnici junior tendano a incontrare problemi nel far sposare il disegno Object Oriented dei sistemi con concetti quali estensibilità, flessibilità, adattamento (nel significato di “customizzazione”), ecc. Ciò non è completamente incomprensibile: la naturale tendenza del disegno Object Oriented è organizzare il sistema in una miriade di oggetti specializzati che interagiscono per realizzare vari servizi (sebbene non sia infrequente imbattersi in sistemi dotati di poche “macro” classi “tuttofare”). In altre parole ogni scenario di utilizzo è realizzato attraverso la collaborazione in stretta sequenza temporale di oggetti. In disegni privi di interfacce, si genera una mancanza di flessibilità dovuta al fatto che le classi che partecipano ad associazioni o che ricevono messaggi, sono dichiarate esplicitamente nel codice. In sostanza ogni oggetto si riferisce esplicitamente a un altro. In queste situazioni si capisce bene che aggiungere una nuova classe oppure modificare l’implementazione di un’altra può costituire un problema: è necessario individuare tutti i riferimenti alla classe e quindi modificare il codice. Poiché poi la creatività di alcuni tecnici è sconfinata, spesso il problema viene arginato facendo ereditare la nuova da quella da sostituire. Ciò chiaramente non sempre risolve il problema… Cosa fare in caso di una nuova funzionalità che deve coesistere con quella predefinita? Oppure come risolvere il problema qualora si utilizzi un linguaggio come Java che non prevede ereditarietà multipla nel caso in cui la classe abbia necessità intrinseche di ereditare? Chiaramente la soluzione consiste nell’utilizzare le interfacce. Il livello di astrazione offerto permette di ignorare lo specifico oggetto con il quale si interagisce, fintantoché la relativa interfaccia sia rispettata. L’utilizzo delle interfacce permette anche di arginare relativamente l’eterno problema del cambiamento continuo dei requisiti. Infatti, analizzando il disegno, è possibile evidenziare classi/packages la cui probabilità di subire modifiche sia elevata e isolarle, attraverso l’introduzione di opportune interfacce, dal resto del disegno. Questa tecnica, in molte situazioni, potrebbe fornire un buon meccanismo per assorbire le modifiche: la reingegnerizzazione del sistema potrebbe vertere, molto frequentemente, sulle classi isolate dalle relative interfacce. In questi casi, l’aggiornamento non inciderebbe sulla restante parte della struttura che continuerebbe a vedere unicamente l’interfaccia. In alcuni sistemi, considerata la frequenza del cambiamento delle idee da parte degli utenti, la tecnica potrebbe portare a circondare tutte le classi con diverse interfacce. Probabilmente si tratterebbe di un sistema molto flessibile ma con un costo di realizzazione di qualche ordine di grandezza superiore a quello iniziale.

16

Capitolo 6. Object Oriented in un chicco di grano

A questo punto, considerati i vantaggi generati dall’uso delle interfacce, perché non disseminarle ovunque nel disegno del sistema? Le risposte sono semplici: • il sistema necessiterebbe di tempi e quindi costi di sviluppo di qualche ordine di grandezza superiore (inserire interfacce tipicamente non è sufficiente: è necessario introdurre meccanismi generici per creare e gestire gli oggetti istanza delle classi protette dalle interfacce, come per esempio factory); • spesso l’eccessiva flessibilità è sorgente di caos; • in genere, il coefficiente di flessibilità di un sistema è inversamente proporzionale alla chiarezza e semplicità del relativo disegno (caratteristiche sempre molto apprezzate).

In merito al primo punto, non è infrequente analizzare modelli in cui siano state introdotte correttamente delle interfacce, salvo poi eliminarne tutti i vantaggi associando le classi direttamente a quelle che implementano l’interfaccia e non all’interfaccia stessa (fig. 6.6). Questo punto verrà dettagliato nel Capitolo 8. Per adesso basti pensare che il problema risiede nel fatto che sebbene le classi possano riferirsi in maniera astratta a interfacce, a tempo di esecuzione nel sistema esistono unicamente oggetti. Pertanto i riferimenti alle interfacce dovranno essere sostituiti da oggetti concreti istanza di classi che implementano le varie interfacce. Si pone quindi il problema di selezionare in modo “astratto” la particolare classe che implementa l’interfaccia di cui creare un’istanza, e quindi crearla. Si tratta ovviamente di un problema noto e risolto elegantemente dal sottoinsieme dei pattern detti creazionali (Creational Patterns [BIB04]).

Astrazione L’attitudine all’astrazione è una delle proprietà tipiche del cervello umano che permettono di minimizzarne le limitazioni nell’affrontare la complessità dei sistemi. Si tratta di una caratteristica che gli individui iniziano a sviluppare fin dai primi anni di vita e che poi continuano a migliorare con il passar del tempo (salvo molteplici eccezioni). Considerata le difficoltà della mente umana nel gestire la complessità dei sistemi nella loro interezza, non resta che rassegnarsi a ignorarne i dettagli per concentrarsi su un modello idealizzato e generalizzato. La qualità e quantità di dettagli da trascurare, chiaramente, dipende dal livello di dettaglio a cui si è interessati.

17

UML e ingegneria del software: dalla teoria alla pratica

Figura 6.6 — Esempio di una situazione di mancata astrazione nella creazione degli oggetti istanza di classi che implementano un’interfaccia. Da notare che non sempre ciò è un errore. Sebbene la classe ClassA preveda, in linea teorica, la possibilità di trattare in modo astratto oggetti istanza di classi che implementano l’interfaccia Interface1, questa capacità viene meno, giacché la ClassA crea direttamente le istanze delle classi ClassB e ClassC. Ciò inibisce diversi vantaggi offerti dall’utilizzo delle interfacce: l’inserimento e/o la rimozione di classi che implementano la medesima interfaccia non può avvenire senza modificare il codice della classe ClassA.

ClassA ...

InterfaceI1

... «create»

ClassB

ClassC

...

...

...

...

«create»

In generale l’atto dell’astrarre consiste nell’individuare similitudini condivise tra oggetti, processi ed eventi appartenenti alla vita reale e nella capacità di concentrarsi su queste, tralasciando momentaneamente le differenze. In sostanza si tratta di un meccanismo che permette di enfatizzare alcuni aspetti del sistema e di trascurarne altri in funzione degli obiettivi previsti e quindi del livello di dettaglio confacente. Chiaramente il punto critico consiste nel saper riconoscere gli aspetti significativi in una particolare fase, o per uno specifico modello, da quelli che invece possono essere in quel caso trascurati. Il criterio di distinzione, però, è soggettivo e dipende dai fini che si desidera raggiungere. Per esempio un acquirente medio pensa a un’automobile in termini di design, colore, optional, affidabilità, ecc.; un’istanza di Paperon de’ Paperoni ne valuta i consumi, le tasse, ecc.; un meccanico è interessato al numero di cilindri, ai relativi litri, alla coppia, alla potenza, ecc.; un playboy vede la macchina in termini di possibili “abbordaggi”, e così via. Pertanto

18

Capitolo 6. Object Oriented in un chicco di grano

ciascuno di questi personaggi, quando parla dello stesso concetto di autovettura, costruisce nel proprio cervello un modello completamente diverso della stessa entità: ciò che tipicamente viene definito proiezione. L’obiettivo centrale del processo di astrazione è la produzione di un concetto (modello) che permetta di identificare in maniera univoca, completa e semplificata l’entità a cui si riferisce. Deve essere, pertanto, in grado di distinguere l’oggetto di riferimento da tutti gli altri, evidenziarne i confini, le caratteristiche proprie, ecc. A tal fine, il processo di astrazione è, intrinsecamente, focalizzato sulla vista esterna degli oggetti, allo scopo (specialmente nel contesto nel mondo Object Oriented) di separare il comportamento dalla relativa interfaccia che, in qualche modo, costituisce il confine tra l’astrazione e l’implementazione. Per esempio quando si pensa a un televisore, ciò che viene in mente è la scatola con lo schermo, la visualizzazione delle immagini, l’audio, lo “scettro del potere” che permette di cambiare programma e di certo non il tubo a raggi catodici o gli elettroni, i circuiti stampati, ecc. Nella pratica quotidiana, più o meno consciamente, si dà luogo a tutta una serie di astrazioni relative a concetti completamente diversi tra loro. Per esempio si possono astrarre delle entità che effettivamente esistono nel dominio del problema o che traspaiono da esso (in un sistema bancario alcuni esempi sono le valute, i prodotti, i trade, ecc.), le azioni e/o gli eventi che si verificano (sempre nello stesso sistemi esempi di eventi sono la rivalutazione di fine giornata, la ricezione di un nuovo trade, ecc.), le operazioni, i servizi e così via. Come illustrato nei paragrafi precedenti, focalizzarsi sulla proiezione esterna di un’entità, nel mondo Object Oriented porta alla definizione dell’interfaccia (implicita e/o esplicita), ossia il contratto che gli oggetti utilizzatori stipulano con quelli fornitori di servizi: il repository delle assunzioni effettuate dagli oggetti client circa i relativi fornitori. Il protocollo, ossia l’elenco delle operazioni visibili dagli oggetti client che costituiscono l’interfaccia degli oggetti fornitori. rientra negli argomenti trattati in dettaglio nei paragrafi dedicati all’Abstract Data Type.

Leggi fondamentali dell’Object Oriented in breve Ogni qualvolta si parla di disegno o programmazione Object Oriented, le parole “magiche” che immediatamente vengono alla mente sono: ereditarietà (inheritance), incapsulamento (encapsulation) e polimorfismo (polymorphism), ossia le leggi fondamentali del disegno orientato agli oggetti.

Ereditarietà L’ereditarietà è indubbiamente la legge più nota del mondo Object Oriented, che, tanto per non volersi sempre ripetere, non è esente da diversi fraintendimenti. Si tratta di un

19

UML e ingegneria del software: dalla teoria alla pratica

meccanismo attraverso il quale un’entità più specifica incorpora struttura e comportamento definiti da entità più generali. Il fine cui si dovrebbe tendere attraverso l’utilizzo dell’ereditarietà è il “riutilizzo del tipo” (interfaccia implicita di un oggetto) e non dell’implementazione, sebbene questo sia un effetto ben desiderato. Il problema, come si vedrà, è tentare di riutilizzare unicamente l’implementazione per mezzo dell’ereditarietà. Gli elementi da cui si eredita sono detti genitori, mentre quelli ereditanti sono detti figli. Proseguendo nella metafora, un elemento legato a uno di partenza, nella direzione del genitore attraverso più relazioni di ereditarietà (nonno, bisnonno, etc.), è detto antenato (ancestor). Mentre un elemento legato a uno di partenza, nella direzione del figlio (nipote, pronipote, ecc.), attraverso diverse relazioni di ereditarietà è detto discendente (descendant). Nel caso di diagrammi delle classi, gli elementi genitori sono anche detti superclassi (superclass), mentre i figli sono definiti sottoclassi (subclass).

Figura 6.7 — Esempio di classificazione. La definizione dei vari concetti è ottenuta combinando il meccanismo della condivisione del comportamento comune con la definizione incrementale dei concetti. La relazione di generalizzazione tra due classi è mostrata in UML attraverso una freccia collegante l’elemento figlio al proprio genitore, con un triangolo vuoto posto in prossimità di quest’ultimo. Su questo argomento si tornerà con maggior dettaglio nel prossimo capitolo. Da notare che sebbene fin dalle scuole superiori venga insegnato che la circonferenza è una degenerazione dell’ellisse, spesso si possono incontrare problemi nel rappresentare questa specializzazione. In effetti un’ellisse è definibile in termini di fuochi mentre per una circonferenza è sufficiente specificare le coordinate del centro e la misura del raggio. Questa apparente incompatibilità si risolve considerando le equazioni canoniche di queste due “curve”. Shape

Polygon

Triangle

Ellipse

Rectangle

Square

Circle

Spline

...

20

Capitolo 6. Object Oriented in un chicco di grano

L’albero visualizzato nella figura precedente rappresenta una struttura di classificazione che si presta a diverse estensioni, mostrando per esempio le specializzazioni della figura piana triangolo (isoscele, rettangolo, equilatero e scaleno), il pentagono, l’esagono, ecc. La relazione tra elemento genitore e quello figlio in UML è detta generalizzazione, e spesso viene indicata come is-a (= “è un”). In effetti un “quadrato è un rettangolo”, il quale, a sua volta “è un poligono”, e così via. Per quanto concerne gli antenati e i discendenti, si può notare, per esempio, che la classe Square è discendente di quella Polygon, e quindi quest’ultima è sua antenata. Gli elementi figli, ovviamente, sono completamente consistenti con quelli più generali da cui ereditano (ne possiedono tutte le proprietà, i membri e le relazioni) e in più possono specificare struttura e comportamento aggiuntivi. Gli elementi figli duplicano ed estendono l’interfaccia “implicita” dei genitori. L’ereditarietà non implica necessariamente l’inclusione di comportamento aggiuntivo nelle classi discendenti. Eventualmente, queste possono lasciare inalterata la definizione implicita dell’interfaccia del genitore, modificando “semplicemente” il comportamento della classe genitrice (ridefinizione di opportuni metodi). Per esempio la classe astratta Shape potrebbe ridefinire metodi come draw(), getArea(), la cui implementazione varia a seconda della specializzazione della classe (chiaramente il calcolo dell’area della circonferenza differisce da quella del rettangolo). Questo concetto, noto come polimorfismo (illustrato nei successivi paragrafi), è ottenuto grazie all’overriding dei metodi. Nel caso in cui gli elementi ereditanti si limitano a specializzare il comportamento dichiarato nelle classi genitrici, si parla di sostituzione pura e gli elementi ottenuti mostrano la stessa interfaccia delle classi genitrici (caso classico del polimorfismo). Le classi figlie però possono anche inibire specifici comportamenti e/o strutture di quelle genitrici. Anche se ciò non sempre rappresenta esattamente un buon disegno, spesso risulta una tecnica molto comoda per risolvere diversi problemi. Si consideri la fig. 6.8 nella quale viene mostrata l’organizzazione gerarchica delle interfacce Java che consentono di utilizzare le collezioni di dati indipendentemente dai relativi dettagli implementativi. Come si può notare, al fine di rendere più facilmente gestibili le collezioni, gran parte del comportamento è dichiarato nelle interfacce antenate, anche in modo un po’ artificioso. Ciò comporta che qualora si tenti di eseguire un metodo definito da un’interfaccia il cui oggetto implementante non ne prevede la realizzazione, l’oggetto stesso comunichi un’apposita eccezione (UnsopportedOperationException). Con un semplice paragone, è possibile pensare all’ereditarietà come a un meccanismo in grado di prendere un elemento di partenza, clonarlo e di modificare e/o aggiungervi struttura e comportamento ulteriori. Un elemento definito per mezzo della relazione di generalizzazione è, a tutti gli effetti, un nuovo tipo che “eredita” dal genitore tutto ciò che è dichiarato come tale (in sostanza tutto tranne attributi e metodi dichiarati privati). Chiaramente vengono inibiti i riferimenti diretti agli elementi dichiarati privati, mentre quelli indiretti restano ancora possibilissimi (si pensi ai metodi get/set).

21

UML e ingegneria del software: dalla teoria alla pratica

Figura 6.8 — Diagrammi rappresentanti le interfacce del core Collection di Java. Brevemente, una collezione rappresenta un gruppo di oggetti, detti elementi. Alcune implementazioni prevedono elementi duplicati, altri no; alcune sono ordinate, altre mantengono l’ordine di inserimento. L’interfaccia Collection non viene direttamente implementata, bensì rappresenta l’elemento in comune (antenato) delle collezioni più specifiche. Un Set (insieme), come suggerisce il nome, è una particolare versione di collezione che non ammette elementi con stessa chiave (duplicati). La List (lista) rappresenta una collezione ordinata in grado di contenere elementi duplicati. L’interfaccia Map (mappa), permette di rappresentare delle coppie (chiave, valore) e quindi non ammette elementi con la stessa chiave. Collection

Set

+add(element : Object) : boolean +addAll(collection : Collection) : boolean +clear() +contains(element : Object) : boolean +containsAll(collection : Collection) : boolean +equals(element : Object) : boolean +hashCode() : int +isEmpty() : boolean +iterator() : Iterator +remove(element : Object) : boolean +removeAll(collection : Collection) : boolean +size() : int +toArray() : Object[] +toArray(array : Object[])

List +add(element : Object) : boolean +addAll(collection : Collection) : boolean +indexOf(o : Object) : int +lastIndexOf(o : Object) : int +listIterator() : ListIterator +listIterator(index : int) : ListIterator +remove(index : int) : Object +set(index : int, element : Object) : Object +subList(fromIndex : int) : List

+clear() +containsKey(key : Object) : boolean +containsValue(value : Object) : boolean +entrySet() : Set +equals(o : Object) : boolean +get(key : Object) : Object +hashCode() : int +isEmpty() : boolean +keySet() : Set +put(key : Object, value : Object) : Object +putAll(t : Map) : void +remove(key : Object) +size() : int +values() : Collection

Set

SortedSet

SortedMap +comparator() : Comparator +firstKey() : Object +headMap(key : Object) : SortedMap +lastKey() : Object +subMap(fromKey : Object, toKey : Object) : SortedMap +tailMap(fromKey : Object) : SortedMap

Da quanto detto è chiaro che l’ereditarietà presenta punti di contrasto con il principio dell’incapsulamento: la classe antenata deve esporre propri dettagli interni alla classe ereditante. Ciò, tipicamente, comporta che la comprensione del funzionamento di una classe discendente dipende dalla logica interna di quella antenata e quindi modifiche alle classi antenate tendono a ripercuotersi su quelle discendenti. Un altro effetto negativo è dovuto al fatto che l’ereditarietà (qualora non applicata al dominio del problema) può generare una certa difficoltà nel comprendere il disegno/codice: la cognizione esatta di comportamento e struttura di una classe figlia prevede la conoscenza delle classi antenate. La capacità dei linguaggi di supportare direttamente l’ereditarietà, rappresenta la differenza tra linguaggi Object Oriented e quelli Object Based (come per esempio Java Script).

22

Capitolo 6. Object Oriented in un chicco di grano

L’ereditarietà, come si vedrà di seguito, può essere simulata: questo concetto, però, è diverso dal supportare. L’ereditarietà è la trasposizione informatica dello strumento che permette di modellare i risultati dei processi di classificazione, in uso fin dai tempi antichi (probabilmente Aristotele fu il primo disegnatore Object Oriented di cui si abbia notizia…). Tale processo permette di organizzare gerarchicamente entità effettivamente esistenti nel mondo reale, realizzando una struttura in cui la descrizione di un elemento è fornita in maniera incrementale, attraverso la localizzazione del comportamento comune nelle classi progenitrici (si sposta più in alto possibile nella gerarchia), specializzandolo via via che si procede verso il basso, ossia nelle classi discendenti. Sebbene l’estrazione del comportamento comune sia, in genere, una buona pratica, giacché favorisce il riutilizzo del codice da parte di tutte le classi discendenti, é opportuno non abusarne per tutta una serie di motivi, non ultimi la necessità di dovere inibire determinate parti nelle classi discendenti (situazione non sempre consigliabile), l’immutabilità delle relazioni disegnate, ecc.

Figura 6.9 — Applicazione parziale del pattern Command definito nel libro della Gang Of Four (BIB04). In questo modello si è voluto mostrare un utilizzo più operativo della relazione di eredità. In particolare, invece di rappresentare relazioni tra oggetti esistenti nel dominio del problema, la si è utilizzata per mostrare parte dell’infrastruttura di un sistema. Come si può notare è possibile definire una classe generica Command dotata di un metodo astratto execute() e quindi tutta una serie di specializzazioni atte a definire il comportamento del metodo in funzione delle responsabilità dello specifico comando. Per esempio, nella classe PasteCommand, il metodo execute() si occupa di copiare quanto presente nel buffer nell’area selezionata; nella classe QuitCommand, il metodo ha l’incarico di terminare l’esecuzione del programma (eventualmente chiedendo conferma) e, nel caso in cui vi siano cambiamenti non ancora memorizzati, ha l’ulteriore responsabilità di richiedere se salvare o meno i cambiamenti prima di terminare l’esecuzione.

Command +execute()

PasteCommand

FontCommand

buffer

newFont

+execute()

+execute()

SaveCommand +execute()

save

QuitCommand +execute()

UML e ingegneria del software: dalla teoria alla pratica

23

L’ereditarietà è una tecnica molto potente e i vantaggi apportati sono la semplificazione della modellazione di sistemi reali, la riusabilità del codice, nonché il polimorfismo. La riusabilità del codice è dovuta al fatto che il comportamento comune è definito una sola volta nella classe progenitrice e poi utilizzato in tutte le classi discendenti. Ciò è molto importante anche perché, tipicamente, genera modelli più snelli e tende a neutralizzare gli effetti generati da alcuni aggiornamenti. Infatti, se questi ultimi sono relativi unicamente alla porzione condivisa, è sufficiente modificare opportunamente la classe progenitrice e, immediatamente, tutte le discendenti ne rifletteranno le variazioni. Pertanto riduce la quantità di lavoro richiesta dalle operazioni di modifica e minimizza la possibilità di creare accidentali inconsistenze dovute a processi di modifica non eseguiti completamente. Uno dei principi fondamentali impliciti nell’ereditarietà è la sostituibilità, definito formalmente da Barbara Liskov. In particolare questo principio afferma che “un’istanza di una classe discendente può sempre essere utilizzata in ogni posto ove è prevista un’istanza di una classe antenata”. Ciò è abbastanza intuitivo considerando che le classi figlie ereditano comportamento e struttura di quelle genitrici e in più vi aggiungono comportamento specifico. Considerando l’esempio precedente, è possibile utilizzare, in ogni luogo in cui è previsto un oggetto di tipo Command (che tra l’altro non può esistere in quanto Command è una classe astratta), un’istanza di una sua specializzazione ( PasteCommand , FontCommand, ecc.). In altre parole, è possibile, per esempio, disporre di un metodo in grado di eseguire diversi comandi che preveda come parametro formale un oggetto di tipo Command (executeCommand(c : Command)) a cui fornire come argomenti attuali le istanze delle classi specializzanti. Ciò permette di eseguire, con lo stesso metodo, comandi di paste, di quit, e così via. Il vantaggio è che la parte di sistema che utilizza una classe antenata non cambia in funzione delle classi discendente e non ha alcuna visione di quale particolare specializzazione si tratti. Come accennato in precedenza, l’ereditarietà favorisce il meccanismo del polimorfismo (al livello di operazioni). In particolare, questo meccanismo permette di dichiarare per una stessa operazione (metodo) definita in una classe antenata, diverse implementazioni ognuna localizzata in una delle classi discendenti. A tempo di esecuzione, la particolare versione da invocare è determinata dalla classe di cui sono istanze gli oggetti ai quali sono applicate, piuttosto che dallo stato del chiamante. In altre parole, una stessa classe può disporre di molteplici specializzazioni e ciascuna di queste può definire, in funzione delle relative esigenze, proprie versioni di un’operazione condivisa da tutte le altre classi (i fratelli). Si consideri per esempio il modello di fig. 6.6. Si sarebbe potuto specificare in tutte le classi un metodo per il calcolo della superficie, per esempio calculateArea(), (nella superclasse Shape si sarebbe trattato di un metodo astratto) la cui implementazione sarebbe dovuta variare da classe a classe (il calcolo della superficie del quadrato è ben diversa da quella di un’ellisse). Questa tecnica offre la possibilità di trattare oggetti di una classe come se fossero istanze di una classe progenitrice (upcasting, ciò avviene automati-

24

Capitolo 6. Object Oriented in un chicco di grano

camente nei linguaggi di programmazione). In altre parole, l’oggetto non viene trattato come un tipo specifico, bensì come il genitore. Il vantaggio è definire codice indipendente dagli specifici tipi. Per esempio è possibile richiedere a una figura (Shape) di disegnarsi nel canvas, senza sapere a quale particolare tipo di figura si faccia riferimento (invocazione polimorfa). Un altro vantaggio è relativo alla possibilità di aggiungere nuove figure (o nuovi comandi) senza dover modificare il codice esistente e così via. Come si vedrà nell’apposita sezione del capitolo successivo, in UML, l’ereditarietà è mostrata attraverso la relazione di Generalizzazione. Si tratta di una relazione tassonomica, transitiva e antisimmetrica, tra un elemento generale e uno più specifico, in cui quest’ultimo risulta completamente consistente con il genitore e vi aggiunge comportamento supplementare. Pertanto un’istanza di un elemento figlio può sempre essere utilizzata in ogni posto in cui è previsto l’utilizzo dell’istanza padre.

La classificazione La classificazione è un processo mentale che permette di organizzare la conoscenza. Nel mondo Object Oriented ciò si traduce raggruppando in un’opportuna organizzazione gerarchica le caratteristiche comuni di specifici oggetti. Questo permette di dar luogo a modelli più leggeri e quindi più semplici da comprendere, sebbene, qualora utilizzata in maniera impropria, possa generare non pochi inconvenienti, come una visione distorta della realtà. Il problema della classificazione “intelligente” è ovviamente presente in tutte le scienze e riuscire a individuare un’organizzazione opportuna ai propri scopi è un’attività cruciale, poiché favorisce il processo di comprensione tipicamente attuato dalla mente umana. In generale, la costruzione di modelli significativi di oggetti ed eventi osservati è spesso propedeutica allo sviluppo di nuove teorie scientifiche. Un esempio? La legge dell’ereditarietà di Mendel. Più specificatamente, nel mondo dell’Object Oriented la classificazione è estremamente importante in ogni aspetto del disegno: permette di identificare generalizzazioni, specializzazioni, strutture gerarchiche e così via. Chiaramente non esiste una regola o un solo metodo per classificare: tutto dipende dagli obiettivi che si intendono perseguire e ciò è ovviamente valido in ogni disciplina, da quelle a carattere meno formale a quelle più marcatamente scientifiche. Esempi celebri possono essere trovati nella classificazione del DNA, degli elementi chimici, delle specie viventi, ecc. Il problema è analogo a scalare una montagna: una volta giunti sulla vetta tutto appare chiaro. Con ciò si intende dire che l’incognita è dare vita a una classificazione appropriata:

25

UML e ingegneria del software: dalla teoria alla pratica

Figura 6.10 — Esempio di classificazione relativa ai pattern presentati nel famosissimo libro della “combriccola dei quattro” (Gang of Four) [BIB04].

Pattern

Creational

AbstractFactory

Singleton

Prototype

Structural

Builder

FactoryMethod

Adapter

Façade

Flyweight

Decorator

Proxy

Behavioral

Bridge

Composite

ChainResponsability

Command

Observer

Iterator

Mediator

Memento

State

Interpreter

TemplateMethod

Strategy

Visistor

una volta ottenuto tale risultato tutto diventa facilmente comprensibile. In generale, i disegni migliori sono quelli semplici — forse sarebbe opportuno dire meno complessi — ma la semplicità richiede tanta esperienza e soprattutto molto tempo di lavoro. Il processo di classificazione, si presta ad essere applicato in maniera iterativa e incrementale. Si definisce una prima soluzione (in termini di una struttura di classi) che tipicamente è realizzata per risolvere uno specifico problema (in altre parole è realizzata ad hoc). Poi, si comincia a studiare la soluzione e ci si rende conto che essa, opportunamente astratta, si presta a risolvere diversi altri problemi e quindi si avanza con un processo atto ad aumentare il livello di generazione. L’attività di disegnare è molto opportuna anche per questo: il costo di tale refactoring al livello di disegno è molto contenuto (in fondo si tratta di spostare dei rettangolini nello schermo, di cambiarne eventualmente il nome e di muovere attributi e operazioni). Nel processo di aumento del livello di astrazione bisogna fare attenzione a non giungere fino all’estremo opposto: il disegno delle soluzioni è così generale da risultare caotico.

I tranelli dell’ereditarietà Come descritto in precedenza l’ereditarietà è probabilmente la legge più nota dell’Object Oriented ma, verosimilmente, anche quella di cui si fa maggiore abuso. Se da una parte è vero che l’identificazione del comportamento condiviso da più classi permette di accentrarne una versione generalizzata in un’apposita classe antenata, dando luogo a una migliore ristrutturazione gerarchica (le altre classi estendono e specializzano il comportamento in comune), dall’altro bisogna tenere in mente che l’eredità non è esente da controindicazioni. L’importante è considerare che l’obiettivo da perseguire è l’ereditarietà dell’interfaccia delle classi. I vantaggi derivanti da un’intelligente classificazione, in que-

26

Capitolo 6. Object Oriented in un chicco di grano

st’ambito sono relativi all’incentivazione del riutilizzo del codice, alla razionalizzazione del modello — che tende a divenire più “leggero” — e così via. Come tutti gli strumenti però, possiede il proprio dominio di applicazione che, se non rispettato, non solo non aiuta a risolvere lo specifico problema, ma può addirittura generare tutta una serie di gravi anomalie nel sistema. Il problema, come al solito, non è tanto legato all’ereditarietà, quanto all’utilizzo forzato (la solita aspirina utilizzata per guarire un’ulcera) che spesso ne viene fatto. Nella pratica succede che disegnatori junior, non appena “fiutino” l’eventualità di un minimo comportamento condiviso — magari un paio di attributi e/o metodi — si affrettino a dar luogo a generalizzazioni le quali, ahimè, spesso risultano abbastanza stravaganti. La “debolezza” dell’ereditarietà è intrinseca nella propria definizione: la staticità e la rigidità della struttura gerarchica. Una volta che una classe viene incastonata in questo tipo di organizzazione non ne può più uscire. Quindi, se un determinato oggetto nasce di un tipo, non può evolvere durante la propria vita: è inevitabilmente condannato a morire

Figura 6.11 — Esempio errato di utilizzo della relazione di ereditarietà. Da notare che con il termine di sviluppatore si intende far riferimento ai diversi ruoli implicati nella costruzione di sistemi (architetti, programmatori, tester, ecc.).

Dipendente - id : String - cognome : String - nome : string - dataDiNascita : Date - eMail : String ...

Commerciale - provvigione : float ...

Sviluppatore ...

- budget : double ...

0..n è responsabile

Direttore

0..n

1

assegnato

dirige

0..n

0..n

AreaGeografica ...

Progetto ...

0..n

UML e ingegneria del software: dalla teoria alla pratica

27

nello stesso tipo (in altre parole non può subire delle metamorfosi). Ciò è ovviamente vero per tutti gli oggetti. Qualora però un’istanza di una determinata classe abbia necessità, in qualche modo, di mutare “tipo” durante il proprio ciclo di vita, ecco che è necessario rappresentare questo comportamento per mezzo di opportune versioni della relazione di associazione (composizione) e non con legami di generalizzazione. In sostanza, l’informazione relativa al “tipo variante” va realizzata non attraverso una rigida relazione di ereditarietà, bensì tramite relazioni di composizione con altri oggetti, che rappresentano appunto il tipo. Trattandosi di relazioni di composizione è sempre possibile “staccare” l’associazione con un’istanza di una determinata classe e “attaccarla” (realizzarne una nuova) all’istanza di un’altra classe. Se queste classi destinazione dell’associazione modellano in qualche modo l’evoluzione dell’oggetto, ecco fornita una modalità per “tramutare” gli oggetti in altri tipi… o meglio per simularne la trasmutazione. Per chiarire quanto espresso, si consideri il seguente esempio relativo alla classificazione dei ruoli in un’organizzazione. Si tratta di una situazione molto frequente: in tutte le organizzazioni, gli attori umani del sistema (clienti, dipendenti, ecc.) necessitano di essere modellati in una struttura gerarchica di ruoli. Il problema opportunamente generalizzato, potrebbe essere ricondotto a un caso di ereditarietà: è possibile identificare una serie di oggetti che esibiscono segmenti comuni di comportamento e/o struttura (per esempio la classe Persona) al quale ognuno aggiunge ulteriori specializzazioni (Cliente, Manager, Contabile, ecc.). Sebbene la situazione, in prima analisi, possa essere considerata il più classico esempio di ereditarietà, le cose invece non stanno esattamente così. Si consideri il diagramma di fig. 6.11. Cosa accade se un Commerciale, a un certo punto del suo ciclo di vita decide di diventare uno Sviluppatore? L’autore immagina che tale interrogazione possa aver generato qualche sorrisino malizioso sulla bocca di diversi lettori, ma in questo caso la domanda verte unicamente sulla qualità del modello (che triste lavorare in un mondo in cui la gente non crede più nei miracoli…). Ebbene, sarebbe necessario dar luogo a un’altra istanza, questa volta di tipo Sviluppatore, e quindi avere due oggetti diversi relativi allo stesso individuo con due identificatori distinti, con tutti i problemi derivanti. Ancora, cosa succederebbe se alcuni sviluppatori (per esempio Antonio Rotondi, Roberto Virgili), fossero così eccelsi da svolgere anche funzioni di Direttore? Queste due semplici domande sono sufficienti a dimostrare tutti i limiti dell’utilizzo della relazione di generalizzazione in contesti come questo. Chiaramente con ciò non si intende “offuscare il prestigio” della relazione di ereditarietà ma si vuole solo fornire un esempio del suo cattivo impiego. Da queste brevi constatazioni è possibile enunciare un paio regole semplici ma efficaci: • qualora in una struttura gerarchica un oggetto possa “trasmutare”, evidentemente l’applicazione della relazione di estensione è inappropriata e quindi è opportuno ricorre alla composizione;

28

Capitolo 6. Object Oriented in un chicco di grano

Figura 6.12 — Rappresentazione dei ruoli attraverso la relazione di composizione. Da notare che la classe RuoloDipendente non è strettamente necessaria.

Dipendente - id : String - cognome : String - nome : string - dataDiNascita : Date - eMail : String ... 1 esercita 1..*

RuoloDipendente ...

Commerciale - provvigione : float ...

Sviluppatore ...

Direttore - budget : double ...

• ogniqualvolta in una struttura gerarchica un oggetto possa appartenere a più “tipi”, nuovamente non è opportuno utilizzare la relazione di generalizzazione. Per quanto possa sembrare strano, anche la relazione di composizione — si tratta di una versione della relazione di associazione, ma con una semantica più forte — si presta ad essere utilizzata, con le opportune cautele, per estendere le responsabilità degli oggetti delegando parte del lavoro ad altri. In parole semplici, la composizione è in grado di simulare la relazione di ereditarietà. Questa affermazione, tipicamente, genera le perplessità di tecnici ancora non molto esperti. In virtù di quanto detto, il diagramma di figura si presta ad essere rappresentato come riportato in fig. 6.12. Questo modello è riconducile al pattern denominato Actor-Participant elaborato da Coad. Ciò che potrebbe lasciare perplessi è la molteplicità 1 della relazione esercita dal lato Dipendente. Essa sancisce che un oggetto Dipendente può possedere diversi ruoli, e che una specifica istanza della classe RuoloDipendente è relativa a un solo oggetto Dipendente. L’utilizzo della composizione — come si vedrà nel prossimo capitolo — sancisce una forte connotazione tutto-parte (whole-part) tra le classi associate ed enfatizza il

29

UML e ingegneria del software: dalla teoria alla pratica

controllo e il possesso da parte della classe tutto (Dipendente) nei confronti delle proprie parti (specializzazioni delle classe RuoloDipendente). Ciò implica, tra l’altro, che la creazione e distruzione degli oggetti parte debba avvenire sotto il controllo di quello tutto. Il modello di fig. 6.12 è decisamente più flessibile ed è in grado di rappresentare tutte le situazioni in cui un dipendente cambi ruolo nell’arco della collaborazione con un’azienda, oppure abbia responsabilità di diversi ruoli, ecc. Tipicamente, in quasi la totalità dei modelli di analisi del dominio è possibile sostituire relazioni di generalizzazione con opportune composizioni, mentre, per quanto concerne il modello di disegno, con particolare riferimento alle classi di “infrastruttura” bisogna essere più cauti. Se da una parte è vero che la composizione permette di simulare l’ereditarietà, dall’altra bisogna tenere presente che non tutte le caratteristiche di quest’ultima possono essere riprodotte. Quando si eredita da una classe, ciò che viene ereditato non è solamente l’implementazione (simulabile con la composizione) ma l’interfaccia (in effetti questo dovrebbe essere l’obiettivo primario dell’ereditarietà), cosa che invece non avviene con la composizione. Chiaramente anche per scavalcare questo problema è possibile individuare diversi espedienti: si tratta comunque pur sempre di artefici. Si consideri ora la limitazione Java legata all’impossibilità di realizzare ereditarietà multiple. Qualora se ne abbia la necessità il problema può essere risolto come riportato nella fig. 6.13.

Figura 6.13 — Nel diagramma è illustrata la tecnica canonica che permette di simulare l’ereditarietà multipla: implementazione di interfacce e composizione.

ClassA

ClassB

+ method1() + method2() + method3()

+ methodA() + methodB() + methodC()

ClassC ...

ClassA

InterfaceB

+ method1() + method2() + method3()

+ methodA() + methodB() + methodC()

ClassC + methodA() + methodB() + methodC() ...

ClassB + methodA() + methodB()

1

... ClasseB b = new CalssB() ... public methodA(. . .) { b.methodA(. . .); }

1 + methodC()

30

Capitolo 6. Object Oriented in un chicco di grano

La soluzione prevede di selezionare una delle due classi (per esempio ClassB) e quindi realizzare un’apposita interfaccia (InterfaceB). Ciò è importante per ottenere l’obiettivo principale dell’ereditarietà: il riutilizzo del tipo. Ove non fosse strettamente necessario trattare le istanze di ClassC come se fossero istanze della classe ClassB, si potrebbe evitare di dar luogo all’InterfaceB. Nella soluzione canonica, ClassC eredita da ClassA e “implementa” InterfaceB . ClassC quindi deve implementare i metodi definiti nell’interfaccia: anche qualora non implementasse InterfaceB, dovrebbe comunque implementarli, altrimenti non si spiegherebbe la necessità di ereditare da ClassB. Ridefinire l’implementazione di tutti i metodi spesso non costituisce una buona idea. La soluzione consiste nel delegare, all’interno di ClassC, il comportamento dei metodi dichiarati nell’InterfaceB alla classe ClassB che a sua volta implementa InterfaceB. Da notare che anche se si utilizza la relazione di composizione, è possibile che la classe ClassB reciti il ruolo di classe composta in altre classi. Come si vedrà nel capitolo successivo, la relazione di composizione impone che ogni istanza dell’elemento composto (ClassB) sia associata, in ogni istante di tempo, a un solo oggetto “tutto” (ClassC, e altri eventuali). Altri problemi intrinseci nell’ereditarietà sono legati al forte legame di dipendenza che si instaura tra classe genitore e quella figlio. Logica conseguenza di ciò è una violazione dei principi dell’incapsulamento. Ciò è dovuto essenzialmente a due fattori: • la sottoclasse deve conoscere diversi dettagli della superclasse; • ancora la classe ereditante non è protetta dai cambiamenti nella classe genitore. Pertanto, ogni variazione apportata in una superclasse, potenzialmente è motivo di revisione e aggiornamento di tutte le sottoclassi (questo fenomeno è noto con il nome di changes ripple, ossia propagazione delle variazioni). Questo rischio si presta a essere minimizzato attraverso una corretta applicazione della relazione di ereditarietà. In particolare è necessario, nei limiti del possibile, strutturare le classificazioni con un basso grado di coesione. Qualora una particolare estensione di una classe genitore necessiti di inibire un comportamento definito in quest’ultima, è opportuno interrogarsi se ciò è veramente quello che si vuole o se magari sia necessario introdurre altre classi intermedie. Soprattutto nei primi modelli, realizzati durante la fase dell’analisi dei requisiti, non è sempre consigliabile organizzare le classi gerarchicamente solo perché condividono del comportamento (per le ottimizzazioni c’é sempre tempo…). Ciò rende i diagrammi più difficilmente comprensibili perché si mostrano delle regole che non appartengono al dominio che si sta cercando di modellare, bensì si tratta di artefici introdotti solo per questioni tecniche.

UML e ingegneria del software: dalla teoria alla pratica

31

Genitore unico Con questo termine ci si riferisce all’ennesimo argomento molto dibattuto nella comunità Object Oriented soprattutto in passato, relativo alla necessità o meno che tutte le classi debbano discendere da uno stesso antenato. Ci sono linguaggi come il C++ in cui non esiste questa organizzazione gerarchica, e altri come Java, in cui tutti gli oggetti discendono dalla classe denominata Object. L’ereditarietà in questo caso è implicita per via del fatto che in Java non è possibile definire ereditarietà multiple (più genitori), quindi se tutti gli oggetti ereditassero esplicitamente da quello base Object non sarebbero in grado di ereditare da nessun altro oggetto, il che, di fatto, renderebbe impossibile l’utilizzo dell’ereditarietà. Tutte le classi definibili in Java quindi condividono un insieme seppur minimo di comportamento, ossia definiscono un piccolo segmento di interfaccia comune che permette, in qualche modo, di uniformali tutti allo stesso tipo fondamentale. Il disporre di questa gerarchia permette di richiedere l’esecuzione di tutto un insieme di metodi a ogni classe, come per esempio toString(), permette di realizzare diverse classi di utility, come per esempio il Vector, l’Hashtable, ecc. nelle cui istanze è possibile memorizzare oggetti eterogenei trattandoli come istanze dell’antenato Object. In questi casi, l’inserimento di un oggetto richiede l’upcasting (implicito) alla classe Object, mentre il reperimento necessita il downcasting all’oggetto specifico. Quest’ultimo punto non è esattamente un vantaggio. In effetti, l’utilizzo di queste classi obbliga a eseguire un casting continuo non controllabile a tempo di compilazione — fonte di errori non facilmente identificabili — e quindi rende il linguaggio meno type-checked. La tecnica della gerarchia con singola radice, inoltre, permette di semplificare la realizzazione di molti meccanismi, come per esempio il garbage collector; infatti è possibile inviare a ogni oggetto precisi messaggi sapendo che questo dispone dei metodi appropriati per trattarli. Anche il debugging è relativamente agevolato, è sempre possibile determinare l’identità di un oggetto, e così via. Ciò però non significa assolutamente che la presenza del genitore unico sia un requisito imprescindibile dei linguaggi Object Oriented.

Ereditarietà multipla Fino a questo punto si è considerata la situazione in cui una classe erediti da un solo genitore (una sorta di autofecondazione); nel caso più generale — non previsto da tutti i linguaggi — è possibile che una stessa classe erediti da più genitori. In questi casi si parla di ereditarietà multipla. La comunità Object Oriented è stata spesso divisa circa l’utilità di ricorrere a tale meccanismo. Ciò è dovuto, principalmente al fatto che disegni basati sull’eredità multipla, se non eseguiti accuratamente, possono essere fonte di tutta una serie di anomalie. Un’affermazione molto celebre di Booch è che “l’ereditarietà multipla è come un paracadute: non sempre se ne ha bisogno, ma quando viene la necessità, si è molto felici di averne uno a portata di mano”.

32

Capitolo 6. Object Oriented in un chicco di grano

L’autore del presente testo, limitatamente a questo argomento è solito utilizzare un approccio decisamente pragmatico. Quando si disegnano diagrammi delle classi precedenti a quello di disegno (dominio, business e analisi) — e quindi a forte connotazione descrittiva dell’area business e a minore impatto sull’implementazione — se la realtà si presta intrinsecamente a essere modellata attraverso l’eredità multipla, é appropriato utilizzarla. Si ricordi che l’obiettivo di questi modelli e descrivere nel modo più semplice e accurato possibile l’area business oggetto di studio. Quando poi si giunge al modello di disegno, che quindi dovrebbe essere quasi in corrispondenza biunivoca con l’implementazione (sebbene alla fine “solo il codice sia sincronizzato con sé stesso” [S. Ambler]) è possibile scomporre l’ereditarietà multipla secondo le tecniche descritte in precedenza. Per l’autore questa tecnica è diventata obbligatoria nel corso degli ultimi anni, dal momento che i sistemi sviluppati sono esclusivamente basati sul linguaggio Java, che, come noto, non supporta l’ereditarietà multipla. L’esempio classico citato da molti testi è legato alla classificazione dei veicoli. Infatti, in prima analisi li si potrebbe suddividere in terresti, nautici e aerei, salvo poi avere i mezzi anfibi che, chiaramente, hanno sia capacità tipiche dei mezzi terrestri sia di quelli nautici. Un altro esempio è relativo alla classe associazione (association class) che, come si vedrà nel capitolo successivo, nel metamodello UML è rappresentata attraverso un particolare elemento che ha caratteristiche sia di classe, sia di relazione e quindi eredita da entrambi gli elementi del metamodello. Un ulteriore esempio, di carattere più operativo, è mostrato nel paragrafo successivo. Uno dei problemi legati all’ereditarietà multipla è che se una caratteristica è dichiarata esattamente nello stesso modo (per esempio, metodo con stessa firma) in due diverse classi, dalle quali erediti una terza che non specializza tale caratteristica, si crea un conflitto nel modello. In caso di invocazione/accesso alla caratteristica dichiarata indipendentemente dai due genitori, a quale far riferimento? (Alcuni linguaggi, come il C++, permettono di risolvere questi tipi di conflitti attraverso la dichiarazione esplicita del metodo da utilizzare). Chiaramente lo UML non fornisce meccanismi per risolvere conflitti di questo tipo: il tutto è affidato al disegnatore.

Il problema del “diamante” nell’ereditarietà multipla Scopo del presente paragrafo è fornire qualche indicazione relativa al famoso problema del diamante… Sebbene ciò possa portare a pensare a problemi connessi con l’eredità di un singolo bene materiale da parte di diversi eredi, in effetti le cose non sono esattamente così. Non si sta parlando neanche di regalo alla propria partner, sebbene anche… il diamante Java sia per sempre. Nella comunità Object Oriented, con tale termine si fa riferimento a un noto problema relativo all’ambiguità insita nei modelli in cui una classe erediti da due altre (ereditarietà multipla), entrambe discendenti dal medesimo genitore. Si consideri l’esempio di voler realizzare un nuovo componente Java AWT che presenti caratteristiche di una normale

33

UML e ingegneria del software: dalla teoria alla pratica

Figura 6.14 — Esempio del problema del diamante. Il nome del pattern è dovuto alla forma che ricorda un rombo, spesso chiamato diamond (diamante) in inglese. Component + getGraphics() + isFocusTrasversable() + keyDown() + keyUp() + requestFocus() + setEnabled() + setFont() + setName() + setSize() + setVisible() ...

List

TextComponent

+ select() + addItem() + getSelectedIndex() + delItem() + countItems() + delItems() + getSelectedItem() ...

+ setText() + getCaretPosition() + isEditable() + getText() ...

TextField + getColumns() + setText + setColumns() + getEchoCar() ...

TextFieldList

lista (List) e che contemporaneamente permetta di inserire dinamicamente voci (item) aggiuntive attraverso i meccanismi di una normale TextField. Una buona idea, considerando per assurdo che il linguaggio Java lo consenta, potrebbe essere quella di ricorrere all’ereditarietà multipla: realizzare il nuovo componente TextFieldList, ereditando dai due precedenti (si consideri il diagramma della fig. 6.14). Come si può notare, questo disegno genera la congiuntura del diamante: sia List, sia TextField ereditano dalla classe astratta Component, e TextFieldList eredita da entrambe. Chiaramente una soluzione alternativa potrebbe essere realizzata utilizzando una delle tecniche descritte in precedenza.

34

Capitolo 6. Object Oriented in un chicco di grano

Ora, disponendo di un’istanza del nuovo componente ( T e x t F i e l d L i s t textFieldList : new TextFieldList()) , l’invocazione dei metodi ereditati da Component genererebbe situazioni di ambiguità (textFieldList.keyUp()). Chiaramente il problema nascerebbe anche qualora, nella nuova classe, si presentasse la necessità di riferirsi a un metodo della superclasse (il super non sarebbe molto di aiuto: quale genitore?) o si volesse accedere a un attributo dichiarato nella classe Component. Come risolve il problema Java? Semplice: non realizzando alcun meccanismo diretto di ereditarietà multipla tra classi e demandando la soluzione all’uso di interfacce e/o composizioni. In C++ il problema viene tipicamente risolto attraverso il meccanismo delle virtual base classes [B. Stroustrup, The C++ Programming Language, 3rd edition – Addison Wesley – §15.2.4, Virtual base classes] e con lo scooping esplicito.

Incapsulamento L’incapsulamento è il meccanismo che rende possibile il famoso principio dell’information hiding (nascondere le informazioni). Con tale termine ci si riferisce alla capacità degli oggetti di celare al mondo esterno, la propria organizzazione in termini di struttura e logica interna: non si tratta pertanto di nascondere le informazioni ai colleghi, come molti sarebbero portati a credere… Il principio fondamentale è che nessuna parte di un sistema deve dipendere dai dettagli interni di una sua parte. Non di rado accade di accorgersi, nella fase di test, che determinati oggetti causano un eccessivo consumo di memoria oppure eseguono specifiche operazioni con pessime performance. In questi casi, se si è utilizzato con intelligenza il principio dell’incapsulamento, è possibile reingegnerizzare le classi da cui derivano tali oggetti, senza dover modificare altre parti del sistema. Pertanto è possibile sostituire sezioni di implementazione, o addirittura intere classi o componenti, senza dover apportare cambiamenti all’esterno. Sebbene uno dei vantaggi sbandierati dal paradigma Object Oriented prima, e dal Component Based poi, sia il riutilizzo del codice (che in realtà per mille motivi, non ultimi i continui cambiamenti dei requisiti utenti, avviene molto raramente), il vero punto di forza probabilmente consiste nella possibilità di sostituire — in maniera quasi indolore — porzioni di codice. (consultare Martin Fowler e il suo incommensurabile Refactoring, Addison Wesley. RV). Nelle fasi iniziali del disegno di modelli a oggetti, tipicamente, si è interessati a “scoprire” — e spesso a inventare — oggetti definendone formalmente il comportamento esterno: in altre parole è necessario eseguire un processo di astrazione. Poi, nella realizzazione del comportamento definito, è fondamentale seguire le norme dettate dal principio dell’incapsulamento che è maggiormente focalizzato nella fase di realizzazione delle astrazioni. In sintesi questi due concetti permettono di separare nettamente le due componenti fondamentali di ogni oggetto: l’interfaccia (definita attraverso l’attività dell’astrazione) e la relativa implementazione (sia delle astrazioni esposte al mondo esterno sia dei meccanismi necessari per realizzare tale comportamento).

UML e ingegneria del software: dalla teoria alla pratica

35

Una delle motivazioni alla base dell’information hiding è data dalla necessità di creare uno strato di separazione tra gli oggetti clienti e quelli fornitori. In altre parole è necessario separare l’interfaccia propria di un oggetto dalla sua implementazione interna. In sostanza l’interfaccia (anche se implicita) rappresenta il contratto stipulato tra gli oggetti client e quelli server. Ciò è vantaggioso al fine di aumentare il riutilizzo del codice e di limitare gli effetti generati dalla variazione della struttura di un oggetto (questo argomento è trattato nella sezione successiva dedicata al Design by Contract). In genere l’incapsulamento standard prevede che le classi non abbiano alcuna conoscenza della struttura interna delle altre, e in particolare di quelle di cui possiedono un riferimento, con la sola eccezione della firma dei metodi esposti nella relativa interfaccia. Ciò permette a ogni classe di modificare, aggiungere, rimuovere parte del proprio comportamento e della propria struttura interna senza generare alcun effetto sulle restanti classi. Questo è vero fintantoché le variazioni non abbiano come dominio metodi appartenenti all’interfaccia della classe: è sufficiente anche la variazione di un solo parametro della firma di un metodo dell’interfaccia per rendere necessaria la modifica delle classi client. Da quanto riportato appare evidente che i princìpi dell’incapsulamento e dell’ereditarietà, per molti versi, presentano diversi punti di discordanza. Il nascondere il più possibile l’organizzazione della struttura delle classi, di fatto, limita o addirittura inibisce l’ereditarietà. Metodi e attributi privati non sono ereditati automaticamente, o meglio, sono ancora ereditati ma non accessibili, e quindi ridefinibili, dalla classe ereditante. In estrema sintesi si può asserire che la privacy non aiuta l’ereditarietà (al contrario di quanto avviene nella vita…). Tipicamente, il principio dell’incapsulamento standard, in Object Oriented, si realizza rendendo privata la struttura interna della classe. Chiaramente una classe con tutti i metodi e gli attributi privati sarebbe di ben poco utilizzo (eternamente condannata alla ricerca della comunicabilità). Ciò che si desidera è, in definitiva, conferire una visibilità privata a quanta più parte di struttura e comportamento possibile (soprattutto agli attributi), limitandosi a esporre specifici metodi. L’incapsulamento totale si ha quando ogni classe non dispone assolutamente di alcuna conoscenza delle altre, non solo per ciò che concerne il comportamento e la struttura interna, ma neanche in termini di esistenza.

Dipendenza dal tipo Un modo appropriato di descrivere un oggetto consiste nel cercare di immaginarlo come una sorta di “scatola nera” dotata di un insieme di capacità ben definite. Ciò equivale ad affermare che è necessario focalizzare l’attenzione sui servizi che un oggetto è in grado di fornire e non sul come questi siano effettivamente realizzati (l’interfaccia). Il vantaggio è che se l’implementazione è “nascosta” è possibile variarla senza che ciò influenzi le classi clienti. Tipicamente, per fornire i servizi esposti, un oggetto necessita di memorizzare delle informazioni (attributi), spesso in modo permanente: tutti gli oggetti

36

Capitolo 6. Object Oriented in un chicco di grano

hanno la propria memoria, in genere stanziata per altri oggetti. Secondo un approccio Object Oriented purista, il modo con cui queste informazioni sono rappresentate all’interno dell’oggetto dovrebbe essere del tutto irrilevante. Contrariamente a molte convinzioni comuni, ciò non significa semplicemente che tutti gli attributi dell’oggetto debbano avere una visibilità privata ed essere esposti per mezzo di opportuni metodi get e set. Sebbene questa sia la tecnica generalmente utilizzata — molto spesso inevitabile —, in ultima analisi si tratta di un modo prolisso di esporre direttamente gli attributi. Avere metodi di accesso agli attributi membro è molto utile, specie se il relativo aggiornamento può generare delle conseguenze (per esempio una variazione del colore di un oggetto grafico ne richiede il ridisegno), se l’insieme valori impostabili è condizionato da altri elementi, e così via. Nonostante ciò, anche con questi metodi non si fa altro che fornire l’accesso ai dati membro dell’oggetto. La filosofia Object Oriented specifica che le capacità di un oggetto siano esercitate attraverso scambio di “messaggi”: un oggetto cliente richiede l’esecuzione di un servizio esposto da un altro oggetto inviandogli apposito messaggio. I metodi get, molte volte, non fanno altro che continuare a esporre i relativi attributi membro attraverso il valore restituito. Per esempio, dichiarare privato un determinato attributo x di tipo y, e poi realizzarne un metodo y : getX() che ne restituisce il valore secondo il tipo di dato (y) non è esattamente un esempio di incapsulamento totale. Infatti, una modifica del tipo di dato dell’attributo, richiede l’aggiornamento dell’implementazione delle classi clienti che utilizzano il metodo getX(). Quindi, se per qualche motivo varia la rappresentazione interna di un attributo membro di un oggetto, è necessario individuare tutti gli oggetti clienti ed eventualmente variarne il codice. Probabilmente ciò non è completamente coerente con una delle promesse dell’Object Oriented che prevede “la possibilità di variare anche completamente l’implementazione di un oggetto senza variare il codice degli oggetti cliente”. Sempre secondo un approccio purista, invece di richiedere a un oggetto la fornitura di uno specifico dato, bisognerebbe chiedere all’oggetto stesso di eseguire sul dato in questione la funzione di cui si ha bisogno. Poi, quanto questo sia sempre fattibile è un altro argomento. Per esempio, in un’organizzazione, dopo aver definito un oggetto Dipendente, invece di accedere al nominativo dello stesso bisognerebbe definire metodi del tipo “stampa sulla busta paga il nominativo”, “visualizza a video il nominativo”, ecc. Considerazioni circa fattibilità e vantaggi offerti da tale approccio vengono demandati ai lettori…

Polimorfismo Polimorfismo deriva dalle parole greche polys (= molto) e morphé (=forma): significa quindi “molte forme”. Si tratta di una caratteristica fondamentale dell’Object Oriented, relativa alla capacità di supportare operazioni con la medesima firma e comportamenti diversi, situate in classi diverse ma derivanti da una stessa antenata. Chiaramente il corretto utilizzo del principio dell’ereditarietà — e anche l’implementazione di interfacce nel

UML e ingegneria del software: dalla teoria alla pratica

37

caso Java — è propedeutico al polimorfismo. Esempio tipico è il calcolo dell’area di una figura. Ciascuna classe espone la stessa firma del metodo (per esempio getArea() : real), ma l’algoritmo di calcolo varia in ognuna di esse in funzione della figura che rappresentano (per esempio nella classe Rettangolo si avrà area = latoMinore * latoMaggiore, in quella Triangolo area = (base * altezza) / 2, nella Cerchio area = raggio * piGreco2, e così via). In sostanza, il polimorfismo fornisce una diversa dimensione per separare l’interfaccia di una classe dalla relativa implementazione, ottenuta in termini di tipi. Si tratta di un meccanismo brillante che, opportunamente applicato, permette di migliorare il disegno del sistema nonché la flessibilità e l’estensibilità: è possibile accrescere la base conoscitiva del sistema e/o aumentarne l’organizzazione senza modifiche al codice. Anche se nella realtà spesso è necessario apportare qualche modifica, l’utilizzo intelligente del polimorfismo permette di minimizzarle. Nel caso delle figure, per esempio, si potrebbero aggiungere nuove classi, come il pentagono, l’esagono, ecc. e la logica di funzionamento sarebbe ancora in grado di comandarle, magari richiedendone il calcolo dell’area, senza averne una conoscenza diretta. Per poter espandere il sistema senza modificare il codice esistente, non è sufficiente disporre dei meccanismi del polimorfismo e dell’ereditarietà, ma sono necessarie tecniche che permettano di generare, in modo astratto, istanze di oggetti polimorfi. In parole povere, è necessario prevedere opportuni meccanismi di generazione di oggetti (pattern creazionali [BIB04]). Nel diagramma relativo alle figure piane, per esempio, è necessario poter generare istanze della figura desiderata (esagono, pentagono ecc.) nel modo più astratto possibile, altrimenti si finirebbe unicamente per spostare le dipendenze e ciò finirebbe per limitare il plug-in indolore di nuove funzionalità. La possibilità di attuare il polimorfismo richiede propedeuticamente la facoltà conoscere a priori una porzione dell’interfaccia di un gruppo di classi, o se si preferisce, di poter raggruppare insiemi di classi attraverso segmenti di interfaccia condivisa. Ciò, naturalmente, si ottiene attraverso l’utilizzo dell’ereditarietà: tutte le classi discendenti ereditano l’interfaccia di quella antenata e quindi è possibile trattare i relativi oggetti come se fossero istanze di uno stesso tipo (la classe antenata). L’utilizzo del meccanismo delle interfacce (nel senso di costrutto public interface) rende anch’esso possibile trattare in maniera astratta un opportuno insieme di classi (quelle che implementano l’interfaccia): in effetti l’implementazione può essere considerata una versione di ereditarietà (la classe che implementa l’interfaccia ne eredita il tipo definito). Per poter realizzare il polimorfismo, i linguaggi di programmazione devono realizzare meccanismi di collegamento dinamico (dynamic binding, detto anche late binding, ossia collegamento ritardato). Con ciò si fa riferimento alla capacità di associare l’invocazione di un metodo alla relativa implementazione presente in un oggetto in tempo di esecuzione. Il motivo è abbastanza evidente: l’istanza della classe che deve eseguire il metodo è conosciuta solo durante l’esecuzione del programma in quanto, in diversi periodi dell’esecuzione, la stessa invocazione potrebbe essere soddisfatta da oggetti appartenenti ad istanze

38

Capitolo 6. Object Oriented in un chicco di grano

di classi diverse (che ereditano da una stessa classe o che implementano una comune interfaccia). Si consideri l’esempio del pattern Command: un metodo di una generica classe atto a eseguire uno specifico comando non è in grado di sapere a priori quale specifico comando dovrà eseguire fintantoché alla relativa istanza non venga fornito uno preciso oggetto istanza di una classe che eredita da Command. Lo stesso metodo, verosimilmente, si troverà a eseguire svariate tipologie di comandi, ossia a utilizzare diversi oggetti istanze di diverse specializzazioni della classe Command. Ciò determina l’impossibilità di associare l’invocazione di un metodo alla relativa implementazione staticamente a tempo di compilazione. In questi casi, il compilatore può unicamente verificare che l’operazione invocata esista con una firma compatibile a quella specificata dall’invocazione stessa. In un’organizzazione gerarchica ottenuta per mezzo dell’ereditarietà, si effettua un upcasting ogniqualvolta un oggetto di una classe discendente viene trattato (casted) come se fosse un’istanza della classe progenitrice (ciò avviene in maniera implicita). Per esempio, quando un’istanza di una classe che specializza la classe Command è trattata come se fosse di quest’ultimo tipo. Il termine deriva dal conformare un’istanza alla forma (casting into a mold) di una classe superiore (up). Mentre, con il termine di downcasting, si intende l’operazione opposta, ossia si tenta di trattare un riferimento di un tipo antenato come un’istanza di uno specifico discendente. Per esempio, quando si inserisce un oggetto in una struttura dati come il Vector, quest’ultimo effettua, implicitamente un’operazione di upcasting: l’oggetto fornito come parametro viene trattato come se fosse un’istanza della classe Object. Mentre, quando lo si preleva, il Vector restituisce un’istanza della classe comune Object ed è quindi necessario effettuare il downcasting alla classe originaria dell’oggetto per poterne utilizzare tutte le caratteristiche. La variante mostrata fino a questo punto, viene denominata da Booch monopolimorfismo per differenziarla dal polimorfismo multiplo (multiple polymorphism). Questo caso si ottiene combinando l’overriding con l’overloading dei metodi. Per esempio, si supponga di dotare tutti gli oggetti di tipo Shape di un apposito metodo denominato draw. Si supponga ancora di prevedere diverse modalità di disegno delle immagini: una normale (draw()) e una ridotta (draw(width : int, heigh: int)), da utilizzarsi per servizi di anteprima. In questo caso, si potrebbe sia specializzare il metodo draw per le varie figure (overriding), sia prevederne diverse versioni, con differenti firme per quella ridotta (overloading).

Overloading e Overriding L’autore del libro ha sempre sognato di scrivere un paio di righe su questi due semplici concetti… Si suggerisce comunque di usare questi “paroloni” con moderazione: parlando con presunti programmatori Object Oriented si potrebbe essere tacciati di utilizzare un gergo difficile… Figuriamoci poi se si utilizzassero parole come up- e downcasting: “Eretico! Eretico! Al rogo!”.

UML e ingegneria del software: dalla teoria alla pratica

39

Il termine overriding è intimamente legato al polimorfismo: quando in una classe discendente si ridefinisce l’implementazione di un metodo, in gergo si dice che se ne è effettuato l’overriding. In sostanza si crea una nuova definizione della funzione polimorfica nella classe discendente. L’esempio è dato dal diagramma relativo al pattern Command in cui la funzione execute(), è ridefinita nelle varie classi discendenti da quella astratta Command. Il termine overloading ha a che fare con la definizione di diversi metodi con nome uguale, ma firma diversa (non esattamente, visto che la variazione del solo tipo di ritorno non costituisce un overloading). Per l’utilizzo di questa tecnica non è necessario dar luogo a particolari legami di ereditarietà. Nel disegno dei modelli a oggetti è molto importante tentare di utilizzare nomi quanto più chiari e rispondenti alla realtà argomento di studio. Ciò sia per rendere il sistema più facilmente leggibile e quindi mantenibile, sia per tentare di simulare quanto più realisticamente possibile il sistema reale. La scelta dei nomi più adatti non deve essere circoscritta alle classi, ma anche ai metodi: d’altronde rappresentano i servizi forniti. Ora gli stessi servizi possono essere erogati su tipi diversi e con diversi livelli di genericità (leggasi diversi parametri formali) e quindi risulterebbe piuttosto innaturale e decisamente poco chiaro definire metodi diversi per stessi servizi su diversi domini di dati. In ultima analisi, il concetto dell’overloading appartiene alla natura umana. Si è abituati a dire “leggi il libro”, “leggi il giornale”, e così via, non di certo “leggiLibro” o tantomeno “leggiGiornale”. Per esempio, in Java la classe astratta Component (da cui deriva la quasi totalità degli oggetti grafici), definisce il metodo repaint (il cui significato è evidente) con una serie di overloading, come: repaint() repaint(i repaint(l repaint(l

: : : :

void int, j :int, k : int: int) : void long) : void long, i : int, j :int, k : int: int) : void

A questo punto in cui tutto è chiaro circa l’overriding e l’overloading, si pone il seguente quesito… Il metodo add(index : int, element : Object) : void, presente nell’interfaccia List, è un esempio di overriding o di overloading? La risposta al quesito è inserita nella sezione “Ricapitolando…” in fondo al capitolo.

Massima coesione e minimo accoppiamento Con i termini di massima coesione e minimo accoppiamento ci si riferisce a due celeberrimi principi della Computer Science, i cui nomi sono destinati a rimanere indissolubilmente legati. Si tratta di leggi inizialmente formulate per la programmazione struttu-

40

Capitolo 6. Object Oriented in un chicco di grano

rata, che poi sono state inglobate tra i princìpi fondamentali del disegno (e quindi della programmazione) Object Oriented. Per quanto concerne la coesione, Booch afferma che: “un modulo presenta un’elevata coesione quando tutti i componenti collaborano fra loro per fornire un ben preciso comportamento”. Da tener presente che il concetto di coesione può essere applicato a diversi livelli di dettaglio (metodi, attributi, classi, package, componenti, ecc.): per esempio, un metodo presenta un elevato grado di coesione quando svolge una sola funzione ben definita. In questo paragrafo si focalizza l’attenzione sull’applicazione a livello di classi. Da questo punto di vista è possibile definire la coesione come la misura della correlatività delle proprietà strutturali (attributi e relazioni con le altre classi) e comportamentali (metodi) di una classe. Chiaramente si desidera un valore di coesione più elevato possibile (i vari attributi e metodi sono fortemente correlati tra loro). Non è infrequente anche il caso in cui il grado di coesione di una classe venga analizzando limitando l’attenzione alle responsabilità. Si tratta di un livello di astrazione superiore, in quanto, in ultima analisi, sia le proprietà strutturali, sia quelle comportamentali sono disegnate al fine di permettere a una classe di assolvere specifiche responsabilità. Per esempio, se una classe dovesse rappresentare un utente del sistema e si avessero attributi del tipo nominativo, sesso, dataDiNascita, ecc. combinati con altri del tipo valuta, valore, ecc. sarebbe piuttosto evidente che i due insiemi di attributi risulterebbero ben poco correlati tra loro (sembra un esempio bizzarro eh?). In termini di database relazionali, il concetto è equivalente alla presenza di dipendenze funzionali diverse degli attributi. Ancora, se in una classe rappresentante il carrello della spesa degli utenti di un sistema per il commercio elettronico, si trovassero dei metodi del tipo svuotaCarrello, aggiungiItem, verificaValiditaContenuto, ecc. ed altri del tipo effettuaOrdine, reperisciUtente, ecc. evidentemente qualche problema di coesione potrebbe esistere anche in questo caso. Come spesso accade, benché dal punto di vista intuitivo il concetto sia chiaro, si tratta di un criterio non facilmente quantificabile la cui valutazione dipende da diversi fattori non tutti facilmente esprimibili. Ciò che invece risulta possibile è individuare una serie di segnali di allarme in grado di evidenziare disegni non esattamente a elevata qualità per problemi legati a uno scarso grado di coesione. Il più evidente è connesso alle dimensioni delle classi. Qualora una classe contenga troppi attributi, oppure troppe relazioni con altre classi, oppure un numero eccessivo di metodi, molto probabilmente il livello di coesione di questi elementi non dovrebbe essere molto elevato e presumibilmente si ha a che fare con una classe che ne ingloba altre. I problemi generati da classi di questo tipo (troppo prolisse) sono relative alla difficoltà di comprensione del codice e quindi di manutenzione, di laboriosità nell’eseguire i vari test, inabilità nel riutilizzo, difficoltà di isolare elementi soggetti a variazioni, ecc. Un altro segnale chiarissimo si ha qualora non si riesca a identificare un nome preciso per una classe oppure questo risulti troppo generi-

UML e ingegneria del software: dalla teoria alla pratica

41

co. Ancora una volta ciò potrebbe essere dovuto al fatto che una classe possiede troppe responsabilità (fa troppe cose). In effetti questo principio ha una sua ratio facilmente riscontrabile anche nella vita quotidiana. Se, per esempio, il disegnatore capo deve occuparsi anche del management del progetto, del processo di sviluppo del software, della definizione del disegno di dettaglio, della codifica, sarà ben difficile per lui riuscire ad assolvere a tutti questi impegni garantendo un sommo grado di qualità. In certi casi c’è da essere felici constatando la mancata richiesta di provvedere alle pulizie degli uffici a fine giornata. I concetti citati per le classi sono facilmente estendibili ad altri elementi, come i package, per esempio. In questo ambito, un buon criterio da seguire per organizzare i modelli di disegni in package è raggruppare le classi al fine di massimizzare la coesione interna del package e minimizzarne l’accoppiamento con gli altri. Dovrebbe essere ormai chiaro, ma si ritiene opportuno presentare un breve riepilogo dei vantaggi legati a “componenti” software con elevata coesione. In particolare: • si aumenta il grado di riusabilità dei componenti: disporre di componenti con un insieme ben definito e circoscritto di responsabilità, evidentemente ne aumenta la probabilità di riutilizzo; • robustezza: un componente con responsabilità ben definite è più facile da comprendere e da verificare, e quindi rende l’intero sistema più robusto. In merito al primo punto, molti autori credono che la tanto agognata riutilizzabilità vantata dall’Object Oriented, nella pratica sia più una chimera che una realtà, per tutta una serie di motivi imputabili anche ai disegnatori/programmatori. Ciò che invece è sicuramente dimostrato è che il paradigma dell’Object Oriented semplifica la sostituzione di parti di codice, magari per via del cambiamento dei requisiti, perché si ha bisogno di codice più efficiente, ecc. La facilità di sostituire componenti software non è assolutamente un aspetto meno importante: in ultima analisi accresce la manutenibilità dell’intero sistema che incide notevolmente (circa il 60%) sul ciclo di vita dei sistemi. Qualunque sia la visione, un elevato grado di coesione favorisce sia la riusabilità, sia la sostituibilità dei componenti software.

Nel mondo della costruzione di sistemi informatici, un altro principio di importanza fondamentale è relativo al minimo accoppiamento. Come si vedrà tra breve — limitatamente al settore dell’informatica — tale proprietà è particolarmente ricercata in quanto capace di generare tutta una serie di vantaggi, del tutto equivalenti a quelli generati alla massima coesione: aumento della probabilità di riutilizzo del codice, semplificazione delle attività di manutenzione, ecc. La proprietà di minimo accoppiamento — caso abbastanza raro — possiede sia una definizione, sia un metro di verifica meno problematici.

42

Capitolo 6. Object Oriented in un chicco di grano

Per quanto concerne la definizione, si tratta della misura della dipendenza tra componenti software (classi, package, componenti veri e propri, ecc.) di cui è composto il sistema. Si ha una dipendenza tra due elementi, per esempio classi, quando un elemento (client) per espletare le proprie responsabilità ha bisogno di accedere alle proprietà comportamentali (metodi) e/o strutturali (attributi) dell’altro (server). Chiaramente quest’ultimo non dipende dai client, mentre è vero il contrario. Ciò comporta che un cambiamento all’elemento che fornisce i servizi genera la necessità di revisionare ed eventualmente aggiornare degli elementi client. In sintesi, la dipendenza di un componente da un altro, implica che il funzionamento del componente stesso dipende dal corretto funzionamento di altri componenti. Come si vedrà nel capitolo successivo, la relazione con minore livello di accoppiamento è la dipendenza (nelle varie forme), per poi passare all’associazione, all’aggregazione, terminando con la composizione e l’ereditarietà. Disponendo di un diagramma delle classi, è possibile verificare il grado di accoppiamento di ogni classe in maniera visiva: è sufficiente contare il numero di relazioni “non entranti” nella classe stessa. In sostanza relazioni per le quali la classe oggetto di studio non svolga un ruolo di fornitore puro di servizi. Con i termini “non entranti” si intende sottolineare che si è interessati unicamente a relazioni di dipendenza in cui la classe recita il ruolo di classe dipendente (client, la classe è attaccata alla coda della freccia) e relazioni in cui la classe all’altra estremità del segmento risulti navigabile. Come si vedrà nel prossimo capitolo, asserire che, relativamente a una specifica relazione, una classe sia navigabile implica che gli oggetti istanza dell’altra (client) devono memorizzare il riferimento agli oggetti istanza della classe server, al fine di poter accedere ad alcune proprietà strutturali e/o comportamentali della stessa. Nella relazione di associazione che lega l’utente alle password, si può notare che la classe User non è navigabile. Ciò implica che tale classe deve prevedere un attributo (una lista) al fine di permettere alle proprie istanze di memorizzare i riferimenti agli oggetti Password ad esse associate, mentre non deve avvenire il contrario. Le istanze della classe Password non devono memorizzare riferimenti agli oggetti User a cui appartengono. Pertanto dagli oggetti della classe User si può navigare nei corrispondenti della classe Password, mentre non è vero il contrario. Dal punto di vista dell’accoppiamento è evidente che la classe User è accoppiata a quella Password, da cui dipende, mentre la classe Password non dipende da quella User.

Un accoppiamento elevato non è desiderabile per una serie di motivi, tra i quali i più importanti sono: • la variazione di un componente genera a cascata la necessità di verificare ed eventualmente aggiornare i componenti dipendenti;

43

UML e ingegneria del software: dalla teoria alla pratica

• qualora si volesse riutilizzare uno specifico componente, è necessario riutilizzare (o comunque portarsi dietro) tutti i componenti da cui questo dipende; ecc. In merito all’ultimo punto, un elevato accoppiamento può creare un pericoloso ciclo vizioso. Per esempio volendo riutilizzare una classe A è necessario copiare anche la classe B, da cui A dipende, poi la classe C e D utilizzate da B e così via. L’obiettivo da perseguire è minimizzare il grado di accoppiamento. Chiaramente eliminarlo non avrebbe molto senso: si potrebbe correre il rischio di generare la situazione opposta ovvero classi mastodontiche che non sono accoppiate perché fanno tutto da sole. Si genererebbe quindi un problema di minima coesione… Spesso nella progettazione di sistemi Object Oriented complessi si “sorvola” qualora le classi presenti all’interno di un package non presentino esattamente un accoppiamento ridotto al minimo: ciò su cui però bisogna porre molta attenzione è che l’accoppiamento tra package sia veramente ridotto al minimo indispensabile. I package dovrebbero raggruppare classi molto correlate tra loro e quindi scarsamente accoppiate con il resto del sistema. Qualora si commetta un errore nel selezionare il package di appartenenza di una classe, questo inconveniente dovrebbe venir immediatamente evidenziato da un corrispondente aumento dell’accoppiamento dei diversi package legato a uno di scarso livello della classe stessa con le restanti presenti nel package di appartenenza.

Figura 15 — Frammento di diagramma delle classi relativo agli elementi User, Profile e Password. In particolare un utente è associato con diverse parole chiave di cui una sola è quella attiva (ciò non si evince da questo particolare diagramma): per esempio è importante memorizzare quelle utilizzate in passato da ogni singolo utente per evitare che le riutilizzi. Per ciò che concerne il legame con i profili, si tratta esattamente della stessa situazione: un utente dispone di diversi profili, ma solo uno è quello attivo.

User

1

1..n

Password

1 1..n

Profile

Navigabilità della classe User = false, ciò comporta che l'implementazione della classe Password non preveda un riferimento alla classe User (private user : User)

44

Capitolo 6. Object Oriented in un chicco di grano

Per comprendere più scrupolosamente il concetto di accoppiamento lo si esamini a un livello di dettaglio inferiore: al livello dei metodi delle classi. In prima analisi, i metodi di ciascuna classe possono essere suddivisi in tre macrocategorie: 1. metodi che forniscono un servizio semplicemente elaborando i dati di input senza ricorrere all’utilizzo di altri dati e tanto meno senza utilizzare lo stato dell’oggetto (per esempio in Java i metodi della classe java.lang.Math, come Math.abs()); 2. metodi che comunicano una porzione dello stato interno di un oggetto, oppure elaborano risultati dipendenti da esso, senza modificarli (per esempio i metodi getX()); 3. metodi che aggiornano lo stato interno di un oggetto (per esempio i metodi setX()). Per quanto concerne la prima tipologia di metodi, essi presentano un accoppiamento minimo quando risultano privi di effetti collaterali — i famigerati side effects — e operano dunque esclusivamente sui parametri di input. Qualora questi metodi utilizzino altri dati, magari privati all’oggetto, di cui però si abbia strettamente bisogno, si ha ancora un accoppiamento contenuto, ma non più minimo. Per quanto concerne i risultati generati, il metodo deve produrre unicamente un dato atomico o eventualmente un altro oggetto di cui viene fornito il riferimento in memoria. Per mantenere un accoppiamento minimo, il metodo, durante la propria esecuzione, non deve poi delegare ad altri parte del proprio processo (non deve invocare altri metodi). Da quanto riportato risulta evidente che non sempre un accoppiamento minimo è assolutamente indispensabile e desiderabile. Anzi spesso, sono accettabilissimi alcuni compromessi, magari al fine di soddisfare altri requisiti di qualità del software, come rendere i metodi più leggibili, manutenibili, riusabili, per esempio, magari derogando parte del loro lavoro ad altri metodi. Nel caso di metodi del secondo tipo, si ha un accoppiamento minimo quando il metodo, per generare i risultati della propria elaborazione, utilizza i parametri di input ed accede ai soli attributi e metodi della classe, sia statici che non. Ancora una volta restituisce un valore atomico o un riferimento a un apposito grafo di oggetti o, eventualmente, genera un’eccezione per comunicare uno stato di errore. Metodi di questo tipo, pertanto, accedono allo stato dell’oggetto senza però modificarlo e utilizzano esclusivamente proprietà (metodi e attributi) della classe o dell’oggetto stesso. Per i metodi dell’ultimo tipo, la materia non varia di molto. La differenza è che la propria esecuzione altera lo stato dell’oggetto. Chiaramente un accoppiamento minimo non prevede la variazione dello stato di altri oggetti. Premesso ciò, è possibile formulare una definizione, per così dire induttiva, di minimo accoppiamento a livello di classe, ossia “una classe prevede un livello minimo di accoppia-

UML e ingegneria del software: dalla teoria alla pratica

45

mento quando tutti i rispettivi metodi presentano un livello minimo di accoppiamento”. Da tenere presente che limitare la propria attenzione ai soli metodi sarebbe un errore piuttosto grossolano: un forte accoppiamento tra classi si instaura con relazioni di ereditarietà. In questo caso si ha accoppiamento tra classi/interfacce quando: • una classe o interfaccia sottoclasse discende (o eredita) dall’altra; • una classe implementa un’interfaccia;

Abstract Data Type (tipo di dato astratto) Tutti coloro che si sono formati nel settore dell’Object Oriented attraverso lo studio dei “testi sacri” ricorderanno — forse — con un certa nostalgia la teoria relativa all’ADT. In effetti molti autori si riferiscono alla progettazione Object Oriented come la pianificazione di tipi di dati astratti e delle relazioni tra essi. Ciò è del tutto consistente, specie se si ricorda che in linguaggi puramente Object Oriented (quali per esempio Small Talk) ogni entità è un oggetto, anche quelle relative a elementi base come numeri interi, caratteri, e così via. Chiaramente in questi paragrafi non è possibile illustrare tutti i dettagli dell’ADT, però si è ritenuto comunque importante presentarne le basi formali corredate da qualche esempio: si cercherà di evitare quello inflazionatissimo dei tipi di dato relativi ai numeri complessi. Come riportato nei paragrafi precedenti, una delle prerogative del paradigma Object Oriented consiste nel realizzare sistemi attraverso la descrizione dello spazio del problema, in sostanza il sistema dovrebbe essere composto dalla trasposizione Object Oriented del vocabolario dell’area business. Quindi, in teoria, la lettura di un sistema realizzato secondo i principi dell’Object Oriented dovrebbe coincidere con la lettura delle business rules del dominio che il sistema dovrà, in qualche modo, automatizzare. Nella pratica, poi, ciò non è quasi mai possibile per una serie di motivi, non ultima la necessità di realizzare l’infrastruttura informatica che, ovviamente, non vive nello spazio del problema… In ogni modo, uno dei primi problemi che bisogna affrontare consiste nel realizzare un modello dell’area business. Per coloro che hanno avuto il piacere di studiare la teoria di Booch, si tratta di dare una forma alle varie nuvolette identificate. A tal fine è necessario comprendere l’area business oggetto di studio, riuscire a distinguere i dettagli trascurabili da quelli oggetto di interesse, rappresentare questi ultimi formalmente ecc. Occorre quindi applicare il processo dell’astrazione al fine di rappresentare una vista del mondo reale confacente ai fini preposti. Per esempio, dovendo definire un modello per un sistema di back office di una investment bank, il modo di lavorare tipico dei broker, il relativo linguag-

46

Capitolo 6. Object Oriented in un chicco di grano

gio utilizzato, le informazioni consultate ecc., sebbene possano essere intrinsecamente molto interessanti, lo sono molto di meno dal punto di vista del modello da realizzare. In tale contesto si sa che giungono informazioni relative ai trade stipulati nel front office di cui bisogna farsi carico, mentre non si ha alcun interesse nel sapere come questi vengono contrattati. Una volta terminato il processo d’astrazione — capita di visionare modelli che potrebbero far invidia ai capolavori di Dalì — dovrebbero emergere due categorie di informazioni: • dati trattati nel sistema, opportunamente raggruppati e relazionati tra loro; • funzioni che, agendo sui dati stessi, realizzano specifici servizi. Si supponga che sia necessario rappresentare le valute coinvolte nel sistema bancario. Una lista di attributi individuabili potrebbe essere: • codice standard (ISO); • nome in inglese; • nome in lingua originale; • descrizione; • data di istituzione; • simbolo della valuta; • numero di cifre decimali significative; • elenco dei formati (tagli) previsti; • nome delle frazioni; e così via. Chiaramente, nell’ambito del sistema da realizzare, non si è sempre interessati a tutti gli attributi. Alcuni di essi, tipicamente, diventano trascurabili poiché non sono specifici del problema, mentre magari sarebbero di notevole interesse in contesti diversi. La selezione degli elementi di interesse costituisce un primo passo verso la modellazione. In effetti, si realizza un modello della valuta con tutti e solo le informazioni che soddisfano i requisiti del sistema.

47

UML e ingegneria del software: dalla teoria alla pratica

Figura 16 — Rappresentazione schematica del tipo di dato astratto (Abstract Data Type).

ADT Operazioni Struttura del tipo di dato

Interfaccia

La descrizione dei dati richiesti è sicuramente un’attività molto importante, ma decisamente non sufficiente: è necessario definire anche i servizi a cui si è interessati. In altre parole è necessario definire rigorosamente le operazioni eseguibili sull’ADT e definire la visibilità delle stesse. Il processo di astrazione permette di rappresentare il “nebuloso” spazio del problema in una ben definita struttura di entità corredate di dati e operazioni che nel paradigma Object Oriented devono essere considerate come caratteristiche inscindibili. Evidentemente ciò che fino a questo punto si è definito entità non è altro che il concetto ben noto di classe: ciascuna di esse definisce la struttura dei dati corredata dalle operazioni con le quali è possibile accedere ad esse. L’insieme delle operazioni rappresenta l’interfaccia esposta dall’entità. Un’entità che rispetti quanto detto finora rappresenta un tipo di dato astratto che comunemente viene detto “classe”. Una volta che un ADT è creato, e la struttura di dati viene riempita con gli opportuni valori, si ha quella che viene definita un’istanza. A meno di vincoli particolari, è possibile generare quante istanze si desidera di ogni ADT. Considerando il caso della valuta, si potrebbero avere, per esempio, istanze relative all’euro (EUR), al dollaro americano (USD), alla sterlina del regno unito (GBP) e così via.

48

Capitolo 6. Object Oriented in un chicco di grano

Proprietà Le proprietà che caratterizzano un ADT sono: 1. il tipo che esportano; 2. l’interfaccia, ossia le operazioni esposte che rappresentano l’unico meccanismo utilizzabile per accedere ai dati; 3. assiomi e condizioni dettate dallo spazio del problema. Per quanto concerne la prima proprietà, c’é ben poco da dire. Una volta definito un nuovo tipo di dato, utilizzando apposite operazioni (costruttori), è possibile generare quante istanze del tipo si desiderano. Dietro il secondo punto si nasconde ovviamente la legge dell’incapsulamento descritta nell’apposito paragrafo. Da tener presente che in questo contesto il principio è molto importante anche perché si vuole definire una serie di ADT non vincolati a uno specifico linguaggio di programmazione — prescindendo dalla particolare implementazione — la cui logica interna può variare senza che ciò abbai ripercussioni sulla restante parte del sistema che la utilizza. Per quanto concerne poi l’ultimo punto, è evidente che oltre a definire ogni funzione, in base ai princìpi che regolano l’ADT, è necessario evidenziare opportune condizioni. Per esempio, al fine di ottenere un elemento da una tabella è necessario fornire un valore non vuoto per la chiave, se si desidera effettuare il pop su uno stack, è necessario che questo non sia vuoto, e così via. Una volta definito un ADT, a meno di particolari vincoli, è possibile definire quante istanze si desiderano. Non è infrequente il caso in cui la definizione di un ADT sia basata su quella di altri o li includa, al fine di fornire i servizi esposti nell’interfaccia. L’esempio classico è dato dalle collezioni. In effetti è possibile definire liste, pile, code, hashtable, ecc. che per essere utili devono operare necessariamente su altri dati. Per esempio liste di interi, liste di stringhe, ecc. Si tratta ancora degli stessi elementi, ma ciò che varia è il tipo di dato su cui si opera. In sostanza si realizzano tipi di dato astratti parametrici denominati tipo di dato astratto generico (Generic Abstract Data Type), le cui “specializzazioni” si ottengono dichiarando il tipo di dato su cui deve operare. Per chi ha esperienza del mondo C++, si tratta dei famosi template che, vista la potenza, sembrerebbero dover confluire nella prossima revisione del linguaggio Java a totale carico del compilatore, cioè senza modifiche alle specifiche della VM.

Per esempio, si supponga di aver definito un elemento di tipo lista; la versione relativa alle stringhe sarà:

UML e ingegneria del software: dalla teoria alla pratica

49

List ListOfString

dove, l’elemento racchiuso tra parentesi angolari rappresenta la specifica variante del tipo di dato astratto generico. Il nuovo elemento condivide la stessa interfaccia di quello generico (List) solo che le relative operazioni sono eseguite su un tipo stringa.

Notazione Con l’avvento dello UML potrebbe essere del tutto legittimo utilizzare il formalismo dei diagrammi delle classi per descrivere gli ADT (rimarrebbe però il problema di specificare la logica delle operazioni). Ciò nonostante, la notazione storica è di carattere più descrittivo e meno grafico. In ogni modo si tratta di una notazione astratta e quindi non legata a un singolo linguaggio. Da quanto riportato fino a questo punto, dovrebbe essere abbastanza chiaro che le parti costituenti sono due: • dati: in questa sezione è riportata la descrizione della struttura utilizzata dal particolare ADT. La descrizione può essere effettuata con diverse notazioni, sebbene non sia infrequente il caso in cui se ne seleziona una informale, specie per descrivere strutture non banali. La sezione dati potrebbe essere omessa (espressione massima di incapsulamento) se non fosse per necessità relative alla definizione formale dei metodi; • operazioni (l’interfaccia): anche in questo caso è possibile selezionare diversi livelli di formalità, sebbene sia opportuno tentare cercare di essere più rigorosi possibili. Una descrizione Object Oriented dei metodi dovrebbe prevedere la dichiarazione esplicita degli argomenti dei metodi (firma) e delle relative condizioni (pre-, post- e durante l’utilizzo). Queste ultime informazioni, come si vedrà nel prossimo paragrafo, sono la base della tecnica nota con il nome di “disegno per contratto” (Design by Contract). La lista delle operazioni si presta a essere specializzata in quattro sottocategorie: 1. costruttori: descrivono le azioni da eseguire per creare un’istanza del tipo di dato; 2. distruttori: si tratta delle operazioni speculari alle precedenti e descrivono le azioni da compiere prima di distruggere l’istanza; 3. selettori: questi permettono di ottenere informazioni relative a un’istanza senza modificarne lo stato;

50

Capitolo 6. Object Oriented in un chicco di grano

4. modificatori, si tratta di metodi che variano lo stato interno dell’istanza dell’ADT. Per cui la notazione potrebbe essere: ADT DATA OPERATIONS constructor: destructor: ... END

Per quanto concerne la definizione formale, un ADT è costituito dalla tripla dove: • D è l’insieme dei possibili domini {D1, D2, … , Dn}, all’interno del quale è possibile individuare quello di interesse per l’ADT che si sta definendo; • F è l’interfaccia, ossia l’insieme delle possibili funzioni {F1, F2, … , Fm} che possono essere eseguite sulla struttura di dati dell’ADT. Ciascuna di esse è vincolata ad avere dominio e codominio (banalizzando, rispettivamente insieme dei valori di input e quelli di output) appartenenti a D e inoltre, il dominio di interesse deve essere o il codominio della funzione, oppure essere presente tra gli insiemi che ne definiscono il relativo dominio; • E è l’insieme degli elementi che denotano valori di particolare importanza.

Probabilmente a questo punto si è riusciti a rendere complicato un concetto che per sua natura non lo sarebbe. Al fine di eliminare questo effetto si consideri l’originalissimo esempio dello stack. Si tratta della versione di lista governata dalla regola: Last In First Out (l’ultimo arrivato è il primo a essere prelevato), in cui un nuovo elemento viene inserito nella cima della pila (TOS, Top Of Stack, cima della pila) e quindi è il primo a poter essere eliminato. La versione presa in esame fa riferimento a uno stack con capacità di memorizzazione infinita, in altre parole non contempla lo stato Pieno.

51

UML e ingegneria del software: dalla teoria alla pratica

Figura 17 — Diagramma degli stadi di uno Stack.

new Stack() push()

Vuoto size() + 1< MaxSize and push() size() > 1 and pop()

Caricato

pop() / exception

size() = 1 and pop() pop()

Pieno

push() / exception

size() + 1< MaxSize and push()

In questo ambito gli elementi sono: • D è l’insieme dei domini, ossia {stack, tipo_base, boolean}; • F è l’insieme delle operazioni applicabili sull’ADT; • E è l’insieme delle costanti che in questo caso contempla un unico elemento: EmptyStack (ossia pila vuota). Prima di definire le operazioni è necessario fissare le convenzioni base: S è un’istanza dello Stack e x è un particolare valore del tipo_base; a questo punto le operazioni di interesse sono: • new(): ÆStack. Genera un nuovo stack vuoto (costruttore). • reset(S):StackÆStack. Restituisce una particolare pila, ossia quella vuota (EmptyStack); • isEmpty(S):StackÆboolean. Restituisce true se la pila è vuota, false altrimenti; • push(S,x):(Stack,tipo_base)ÆStack. Restituisce un nuovo Stack ottenuto da quello di input, aggiungendo il carattere x in cima. Se lo Stack di input è

52

Capitolo 6. Object Oriented in un chicco di grano

formato dai valori {an, an-1, …, a1, a0} (in cui an è l’ultimo elemento inserito), quello ottenuto come risultato della funzione push(S,x0) è {x0,an, an-1, …, a1, a0}. • pop(S):StackÆStack. Questa funzione è l’opposta della precedente; è dato uno Stack di input, ne produce uno di output, ottenuto eliminando l’elemento in cima alla lista dallo stack di input. Quindi se la pila di ingresso è {an, an-1, …, a1, a0}, il risultato della funzione pop(S) è {an-1, …, a1, a0}. Questa funzione è applicabile qualora la pila di ingresso non sia vuota (precondizione). • tos(S):StackÆtipo_base. Qualora la pila di input non sia vuota, restituisce l’elemento in cima. Da notare che queste funzioni si prestano ad essere descritte formalmente, e indipendentemente da un linguaggio di programmazione, attraverso l’OCL. La rappresentazione formale prevederebbe: ADT Stack DATA Array pila di n elementi del tipo base; top intero indicante l’elemento in cima alla pila OPERATIONS constructor: new() post: top = -1, result = EMPTY_STACK selectors: isEmpty(stack : Stack) post: result (stack.top == -1) tos(stack : Stack) : BasicType pre : stack.isEmpty() == false post : result = pila[top] modifiers: push(stack : Stack, x : BasicType) post: pila[++top] = x pop(stack : Stack) pre : stack.isEmpty() == false post : top— END

UML e ingegneria del software: dalla teoria alla pratica

53

Tre parole sul Design by Contract (DbC, disegno per contratto) Introduzione Una delle caratteristiche imprescindibili di ogni sistema ingegneristico, e non solo, dovrebbe essere l’affidabilità (di nuovo la vocina interna dell’autore diviene incontrollabile). In effetti nessuno acquisterebbe un’autovettura che in curva sia incline a capovolgersi o installerebbe un ascensore che tenda a bloccarsi, o tanto meno costruirebbe un ponte che oscilli spaventosamente, e così via (chissà poi come mai per ciascuna delle precedenti asserzioni esiste una controprova). Anche nei sistemi software chiaramente si è interessati a questa caratteristica (ancora una volta raramente soddisfatta… Purtroppo alcuni celebri errori in sistemi informatici, sono tragicamente passati alla storia.) Nell’ambito dei sistemi software, il termine di affidabilità implica due altri concetti: • correttezza: il sistema realizza le funzioni per il quale è stato progettato. Quindi a stimoli corretti di ingresso produce gli effetti di output previsti; • robustezza: il sistema è in grado di rilevare e gestire situazioni anomale (dati di ingresso non corretti, parti del sistema non funzionanti, ecc.). In parole povere l’affidabilità è sintetizzabile con ciò che sembra essere un miraggio informatico: la realizzazione di sistemi privi di errori (bug). Se poi si considera che un altro sogno dell’Object Oriented (enfatizzato dai sistemi component-based) è la riutilizzabilità del codice, si comprende come questa caratteristica assuma ancora più rilevanza. Il problema da porsi è come mai sia così difficile realizzare sistemi affidabili, o meglio ancora, quale tecnica utilizzare per costruire sistemi affidabili. Chiaramente non esiste una risposta univoca e tanto meno semplice. Esistono tuttavia una serie di comportamenti e di best practices che permettono di aumentare la qualità e l’affidabilità del software. Un approccio particolarmente apprezzato è il famoso Design by Contract (disegno per contratto, tecnica ideata da Bertrand Mayer nei laboratori del linguaggio Eiffel). Come si vedrà, si tratta di una metodologia particolarmente valida per via della sua sistematicità. Sebbene si tratti di una tecnica la cui validità è universalmente accettata, il suo successo sembra essere concretamente inferiore alle attese. L’utilizzo è principalmente relegato ad ambienti accademici. Ciò probabilmente è dovuto sia al grande quantitativo di tempo necessario per la formulazione matematica delle varie condizioni, sia a lacune presenti “nativamente” in alcuni linguaggi di programmazione, sebbene siano poi disponibili dei pre-processori per diversi linguaggi di programmazione.

54

Capitolo 6. Object Oriented in un chicco di grano

Figura 18 — L’immagine rappresenta un’amabile schematizzazione del Design by Contract. Si tratta della rielaborazione di una figura presente nel libro [BIB16]. Contratto

Supplier

Client

Il punto di partenza è che il sistema può essere immaginato come una serie di componenti (eventualmente oggetti, d’altronde Eiffel è uno dei linguaggi orientati agli oggetti più formali) comunicanti, la cui interazione è basata su precise obbligazioni tra oggetti richiedenti i servizi (client) e quelli che invece li forniscono (supplier). Tali obbligazioni costituiscono quello che viene definito contratto. In quest’ambito si fa riferimento ad accordi tra due parti (probabilmente sarebbe più opportuno esprimersi in termini di tipologie di parti) e pertanto alcuni obblighi risultano a carico del richiedente mentre altre sono di responsabilità del fornitore. Ovviamente ciascuna delle parti si aspetta di ottenere dei benefici dal contratto e per questo accetta, come contropartita, alcune obbligazioni. In ultima analisi, un contratto stabilisce, in maniera formale, l’elenco dei benefici e delle obbligazioni che ogni parte contrae. Da una parte, ogni oggetto cliente si fa carico di rispettare le condizioni pattuite nel richiedere i servizi, mentre dall’altra i server assicurano il rispetto di determinate condizioni relative la fornitura dei servizi. Qualora un cliente non rispetti le condizioni pattuite, ovviamente il contratto perde di validità e quindi l’altra parte non è più vincolata a rispettare le obbligazioni pattuite.

55

UML e ingegneria del software: dalla teoria alla pratica

Chiaramente il concetto di contratto è un qualcosa che appartiene alla vita quotidiana e viene utilizzato sistematicamente, più o meno consapevolmente, da ogni persona. I contratti si applicano ogniqualvolta ci sia uno scambio di prodotti (e/o servizi) tra un cliente e un fornitore. Per esempio, quando si utilizza la carta di credito per effettuare degli Tabella 1 PARTE Cliente

OBBLIGAZIONI (Rispettare le pre-condizioni Essere in regola con i pagamenti precedenti e non eccedere, per il periodo in corso, i limiti di spesa prestabiliti.

Fornitore (Assicurare le post-condizioni Garantire il cliente per la cifra richiesta

BENEFICI (Ottenuti dalle post-condizioni) Ottenere la somma richiesta dall'attuale transazione in modo semplice, veloce e senza rischi (Assunzione delle pre-condizioni) Non garantire (rifiutare la transazione) clienti con inadempienze nei precedenti pagamenti o che intendano fare acquisti oltre i limiti previsti.

acquisiti, implicitamente si utilizza un servizio governato da un apposito contratto. Una semplificazione di questo contratto è riportato nella tab. 1. Un contratto è quindi vantaggioso e protegge entrambi i contraenti sancendo, da una parte, natura e qualità del servizio (postcondizioni) e, dall’altra, sollevando il fornitore (precondizioni) da ogni imputabilità qualora il cliente richieda la fornitura del servizio non rispettando gli accordi. Si consideri ora un esempio di contratto applicabile a una classe di un sistema software. A tal fine si consideri l’inserimento di un oggetto in un “dizionario” (map). Queste strutture permettono di gestire un insieme di coppie (chiave, valore), pertanto vi è una corriTabella 2 PARTE

OBBLIGAZIONI (Rispettare le pre-condizioni)

Cliente

Assicurare che il valore della chiave non sia nullo (Assicurare le post-condizioni)

Fornitore

Inserire il valore nel dizionario

BENEFICI (Ottenuti dalle post-condizioni) Memorizzare la coppia chiave valore. (Assunzione delle pre-condizioni) Non deve eseguire alcuna operazione qualora la chiave sia nulla

56

Capitolo 6. Object Oriented in un chicco di grano

spondenza biunivoca tra le chiavi e i relativi valori. Ciò implica che gli elementi inseriti siano reperibili specificando il valore della relativa chiave. Come visto anche nella presentazione dell’Abstract Data Type, il contratto governa le interazioni tra ogni metodo di un oggetto e i potenziali clienti. Pertanto, per ogni metodo, definisce informazioni di vitale importanza: le assicurazioni che ogni parte deve garantire per una corretta fruizione del servizio (invocazione del metodo) e il conseguimento del risultato pattuito. Nuovamente anche questo argomento, probabilmente, non è privo di malintesi. Diversi autori sostengono che il soddisfacimento di una precondizione implica che il cliente “debba garantire lo stato dell’oggetto server prima dell’invocazione di un suo servizio”. L’autore del presente testo non condivide questo punto di vista. Sembra addirittura un controsenso che un oggetto debba garantire lo stato interno di un altro che fornisce il sevizio. Ciò che invece appare più verosimile è che il cliente possa garantire alcune condizioni sui parametri di un servizio. Per esempio, dovendo invocare una funzione che calcoli la radice di un numero, l’oggetto cliente può e deve farsi carico di verificare che l’argomento sia un numero non negativo, oppure nel richiedere l’autorizzazione di una transazione commerciale, verificare che l’importo sia diverso da zero, ecc. Il fatto che alcuni obblighi siano a carico dell’oggetto client non significa poi che nell’oggetto server i controlli non siano eseguiti. In sostanza si applica ancora una volta il famoso concetto della fiducia con verifica. Un esempio apparentemente semplice ma che invece presenta delle anomalie è relativo al metodo di pop di uno stack. Verosimilmente si potrebbe dichiarare qualcosa del genere: precondition: postcondition:

this.isEmpty() == false; return this.getTOS();

Riflettendo bene, benché sia strettamente necessario eseguire la verifica sulla condizione di stack vuoto, probabilmente non è corretto esprimersi in termini di precondizione. Ciò perché forzerebbe il cliente a verificare lo stato interno dell’oggetto server. Se poi ci si trovasse in un sistema concorrente, allora tra una invocazione del metodo isEmpty e pop potrebbe avvenire di tutto (anche passaggio dello stack da non vuoto a vuoto) e quindi come potrebbe un cliente essere sicuro dello stato dell’oggetto client? Chiaramente nessuno vorrebbe inserire il concetto di lock nel Design by Contract. Fino a questo punto si è visto come pre- e postcondizioni si applicano al livello di servizio, o metodo se si vuole. Esiste tuttavia un’altra serie di asserzioni il cui dominio invece è la classe. Tali condizioni sono dette invarianti (queste sono state presentate brevemente nel capitolo panoramico). Si tratta di condizioni valide per tutte le istanze di una specifica classe, detta per l’appunto classe invariante. Per esempio, la valutazione del metodo isOfAge() (è maggiorenne) per tutte le persone che posseggono di una carta di credito deve restituire il valore true. La dichiarazione di questi vincoli è molto importan-

UML e ingegneria del software: dalla teoria alla pratica

57

te sia per l’implementazione della classe stessa, sia per lo sviluppo dei test poiché stabilisce dei criteri che devono essere sempre soddisfatti dalle istanze della classe, indipendentemente dalla relativa evoluzione. A questo punto l’idea del contratto alla base della relativa tecnica dovrebbe assumere una forma più compiuta: esso è definito in termini di condizioni, ognuna delle quali appartiene a una delle seguenti tipologie: • precondizioni: vincoli che gli oggetti client devono rispettare per poter fruire di un servizio; • postcondizioni: garanzie assicurate dal fornitore del servizio, qualora le precondizioni siano rispettate; • invarianti: condizioni sempre valide. Le varie condizioni possono essere considerate delle asserzioni (espressioni booleane relative allo stato dell’oggetto) la cui valutazione a tempo di esecuzione deve restituire un valore true per il corretto proseguimento. Il caso in cui la valutazione restituisca un valore false costituirebbe la segnalazione della violazione di una clausola del contratto. Si tratta quindi di check point (punti di controllo) inseriti in luoghi precisi del sistema. In particolare è necessario verificare le precondizioni immediatamente prima di eseguire un metodo (all’entrata), le postcondizioni subito dopo la fine dell’esecuzione o meglio poco prima della fornitura dei risultati (all’uscita), mentre le invarianti dovrebbero poter essere valutate in ogni momento in quanto definiscono condizioni che dovrebbero sempre essere verificate. Dovrebbe risultare abbastanza chiaro che l’Object Constraint Language è un formalismo che si presta facilmente per specificare rigorosamente i vincoli e le condizioni del disegno per contratto. Per essere precisi, è molto più potente. Il Design by Contract è stato inizialmente ideato per essere utilizzato in fasi ad elevato dettaglio tecnico, per quanto, recentemente, diversi autori ritengano che il suo utilizzo possa includere le prime fasi del ciclo di vita di un sistema software, caratterizzate da un maggior livello di astrazione rispetto a dettagli tecnici. Sebbene si tratti di una rappresentazione a livello di ADT e quindi non legata a uno specifico linguaggio di programmazione, probabilmente un utilizzo durante le fasi di analisi potrebbe creare qualche difficoltà, legata sia al tempo necessario per una loro corretta formulazione, sia alla difficoltà che riscontrerebbero gli utenti medi a fruirne i relativi contenuti. Rimanendo in un ambito puramente implementativo, è possibile notare come il luogo ideale per la definizione formale dei contratti dovrebbe essere dato dalle interfacce che espongono servizi forniti dalle relative classe. Il problema è che ciò non sempre è possibile. Per esempio, linguaggi come Java non consentono (nativamente) di specificare più

58

Capitolo 6. Object Oriented in un chicco di grano

della firma dei metodi esposti da una classe (per essere precisi permettono di specificare anche valori costanti), mentre non è possibile specificare nulla relativamente al comportamento. Ad onore del vero esistono vari strumenti non standard in grado di estendere la grammatica Java abilitando questo linguaggio all’utilizzo della tecnica del disegno per contratto. Uno dei più celebri è indubbiamente iContract realizzato da Reto Kramer. Si tratta di uno strumento molto intelligente che permette di dichiarare le varie condizioni (pre-, post- e invarianti) nel commento iniziale, sia dei metodi (@pre, @post), sia della classe (@inv), attraverso opportuni tag, in modo del tutto analogo a JavaDoc. Ciò fa sì che il codice, processato da un opportuno pre-compilatore, sia in grado di utilizzare le peculiarità del Design by Contract, grazie alla generazione automatica di tutta una serie di classi utilizzate per verificare il rispetto delle condizioni, mentre compilato direttamente con javac dia luogo alla generazione del codice atteso. La prima modalità potrebbe rilevarsi molto utile in fase di test, mentre la seconda, essenzialmente per questioni di performance, dovrebbe essere preferita per la produzione. La grammatica prevista per la specifica delle condizioni è un sottoinsieme del linguaggio OCL.

Il sottocontratto Nell’ambito della progettazione dei sistemi Object Oriented, un aspetto di particolare importanza è dato dalla combinazione della metodologia del Design by Contract con l’ereditarietà. In altre parole è necessario esaminare come un contratto pattuito da una classe antenata sia ereditato da una classe discendente. Il problema è che non si ha a che fare unicamente con l’ereditarietà, bensì anche con concetti quali il polimorfismo: non è infrequente il caso di una classe discendente che ridefinisce un metodo dichiarato in una antenata. Questa situazione costituisce sicuramente una fonte di potenziale pericolo. La soluzione a questo problema è data dal concetto del subcontratto, il quale stabilisce una serie di norme derivanti essenzialmente dal principio di sostituibilità (ogni istanza di una classe discendente deve poter essere utilizzata in ogni punto in cui è previsto un oggetto di una classe antenata). In particolare, una classe ereditante, per ogni metodo, può: • mantenere le stesse precondizioni definite in quella antenata oppure definirne altre più deboli; • mantenere le stesse postcondizioni definite in quella antenata oppure definirne altre più forti; Queste regole rappresentano un sottocontratto definito onesto, il cui non rispetto può generare non pochi problemi. Da tenere presente che in linguaggi come Java, il concetto

UML e ingegneria del software: dalla teoria alla pratica

59

del sottocontratto diviene importante per classi figlie, implementanti un’interfaccia, annidate e per interfacce estendenti altre.

Eccezioni Il concetto della gestione delle eccezioni, sebbene, non sia di per sé un aspetto primario della teoria del Design by Contract, in effetti rappresenta un aspetto di tipo implementativo, mentre la teoria resta pur sempre un fattore con il quale prima o poi bisogna confrontarsi. Come visto in precedenza ogni oggetto possiede un’interfaccia, alcuni la espongono esplicitamente per mezzo di appositi elementi, mentre in altri si esaurisce in quella implicita. Come logica conseguenza, ciò permette di affermare che ogni oggetto possiede un contratto e nuovamente ci sono oggetti che lo dichiarano apertamente, mentre per altri è più implicito (quest’ultima condizione tende ad aumentare il carico di lavoro in fase di documentazione). Un fallimento nel rispetto delle condizioni sancite da un contratto porta all’impossibilità da parte della classe server di fornire il servizio richiesto e, in ultima analisi, a un’eccezione che necessita di essere gestita; chiaramente non è mai opportuno lasciare il sistema in uno stato indesiderato, che in questo contesto potrebbe significare violare le invarianti di un oggetto. Il guaio è che le eccezioni possono insorgere anche durante la fornitura di un servizio per cause indipendenti dalle parti, come per esempio un problema dovuto a un malfunzionamento dell’hardware. In ogni modo ciò che è necessario svolgere, pragmaticamente, indipendentemente dalla tipologia dell’eccezione, consiste nel riportare l’oggetto server in uno stato consistente e quindi notificare l’anomalia.

Vantaggi Come degna conclusione del paragrafo si è deciso di specificare formalmente i vantaggi della metodologia del Design by Contract: • fornisce una tecnica sistematica per la realizzazione di sistemi senza errori; • realizza un framework per il debugging e il test delle diverse parti del sistema; • permette di documentare più agevolmente i sistemi software (come si è visto, si tratta di una tecnica derivante direttamente dall’Abstract Data Type); • agevola la rilevazione e gestione di condizioni anomale, ecc. Il Design by Contract è a tutti gli effetti un’evoluzione del disegno Object Oriented e pertanto ne enfatizza le caratteristiche peculiari. Permette di realizzare sistemi di migliore

60

Capitolo 6. Object Oriented in un chicco di grano

qualità, però ciò è pur sempre realizzato attraverso altro codice (alla fine le varie asserzioni vengono rese attraverso una programmazione, magari in OCL) e come tale, è soggetto a sua volta a errori.

Classi ben disegnate Il presente capitolo non poteva che terminare cercando di rispondere a uno degli interrogativi sui quali più frequentemente si arrovella la mente dei neofiti del mondo Object Oriented: “Quando una classe può essere definita ben disegnata?”. Si tratta di una bella domanda per la quale, caso raro, è possibile formulare una buona risposta, tenendo presente sia quanto riportato nei paragrafi precedenti, sia gli insegnamenti dei maestri dell’informatica, con particolare riferimento al solito Booch ([BIB13]). Tipicamente, la precedente domanda tende a evocarne un’altra decisamente più insidiosa: “Quando un modello di disegno si può considerare ben progettato?”, alla quale si tenterà di dare una risposta nel capitolo ottavo. Tornando al primo quesito, sicuramente una classe ben disegnata deve rispettare i principi della massima coesione e del minimo accoppiamento. Quindi, riassumendo quanto riportato nei precedenti paragrafi, i relativi elementi devono essere molto correlati tra loro, la classe deve possedere il minor numero possibile di dipendenze con le altre classi, e queste devono essere le più deboli realizzabili. Una classe deve essere in grado di rappresentare correttamente la relativa astrazione e soprattutto deve strutturarne sufficienti caratteristiche al fine di permettere un’interazione valida ed efficiente con le classi client. Nel caso in cui tale proprietà non sia rispettata, la classe stessa potrebbe considerarsi, per ovvi motivi, inutilizzabile. Questa caratteristica è stata denominata da Booch sufficiency (sufficienza). Violazioni della proprietà di sufficienza sono facilmente riscontrabili: durante la progettazione delle dinamiche interne del sistema: ci si accorge che in una o più iterazioni l’interfaccia di una o più classi non prevede i servizi necessari. Ciò comporta l’impossibilità di dar luogo all’iterazione desiderata o di ottenerla comunque attraverso un percorso tortuoso che coinvolge molti più oggetti del necessario. Come è possibile individuare la classe a cui appartiene l’operazione mancante? Il metodo mancante dovrebbe essere membro della classe in cui sono presenti gli elementi che presentano una forte correlazione con il metodo stesso. In altre parole, la classe prescelta, dopo l’inserimento del metodo, deve continuare a mantenere un elevato grado di coesione e un minimo accoppiamento (ciò perché il metodo potrebbe delegare parte delle proprie responsabilità ad altri metodi presenti in diverse classi). Per esempio, una classe la cui responsabilità sia memorizzare un elenco di oggetti individuabili attraverso un identificatore univoco (una sorta di hashtable specializzata), presenterebbe un’interfaccia non sufficiente, qualora non preveda un metodo atto verificare la presenza di uno specifico elemento: un risultato null di un metodo di Object : getElement(key : Object), non permetterebbe di discernere il caso in cui la

UML e ingegneria del software: dalla teoria alla pratica

61

chiave non sia stata inserita da quello in cui sia stata volutamente accoppiata con un valore null. La sufficienza è una caratteristica, per così dire, necessaria ma non sufficiente. In effetti, si vuole che l’interfaccia della classe sia anche completa, ossia l’interfaccia catturi tutte le caratteristiche significative della relativa astrazione. Questa caratteristica è tipicamente indicata con il nome di completeness (completezza). Quindi, mentre la proprietà di sufficienza comporta un’interfaccia minima, quella di completezza implica un’interfaccia che copra tutti gli aspetti dell’astrazione. Una classe è completa quando la relativa interfaccia è così generale da poter essere utilizzata da ogni classe cliente. In questo caso si ha a che fare con un criterio per molti versi soggettivo. Ancora una volta però, una violazione del principio di completezza, nel contesto di uno specifico disegno, è facilmente ravvisabile: è possibile individuare una classe cliente che abbia bisogno di un determinato servizio da un’altra fornitore non presente nella relativa interfaccia e non ottenibile attraverso la combinazione dell’invocazione di diversi metodi. Quest’ultimo concetto è molto importante e si collega al principio della primitiveness (primitività). Una classe oltremodo ingegnerizzata di certo non può definirsi ben disegnata e quindi, a meno di motivazioni importanti, si vuole evitare che nell’interfaccia di una classe siano presenti metodi non primitivi, ossia ottenibili dall’invocazione di altri. In generale, una classe possiede la caratteristica di primitività qualora non siano esposti nell’interfaccia metodi che possano essere ottenuti dall’esecuzione combinata di altri. Sebbene, in prima analisi, il concetto di primitività possa sembrare in antitesi a quello di completezza, riflettendo bene ciò è vero molto parzialmente. La primitività non richiede di omettere nell’interfaccia metodi non utilizzati, bensì di “valutare attentamente” l’opportunità di inserire metodi in grado di fornire servizi ottenibili attraverso l’invocazione di più metodi primitivi. La primitività non sempre è una caratteristica da applicare alla lettera. Per esempio, in diverse classi tipo vettore, collezione, ecc., sono previsti dei metodi che invece di aggiungere un singolo elemento aggiungono delle liste. Volendo essere puntigliosi, si potrebbe affermare che metodi di questo tipo invalidano la proprietà di primitività della classe. I vantaggi generati sono però tali da rendere del tutto accettabile una riduzione del livello di tale proprietà. Altra caratteristica indispensabile che deve possedere una classe è l’incapsulamento, pertanto la classe non deve esporre all’esterno i propri dettagli implementativi ed, eventualmente, neanche il proprio stato (salvo che non rappresenti un’astrazione deputata a tale fine). Oltre alle caratteristiche formali che deve possedere una classe per considerarsi ben disegnata, esistono una serie di segnali che possono aiutare a capire situazioni anomale. Uno dei più semplici si ha qualora non si riesca ad attribuire un “bel” nome significativo a una classe. Ciò potrebbe essere il risultato di uno scarso livello di coesione, magari perché si sono inglobate più classi in una sola, oppure di un elevato accoppiamento tra due o più classi: si è scissa un’entità in più classi, e così via. Un altro segnale potrebbe venire da classi con troppe responsabilità o, se si gradisce, di dimensioni elevate. Ciò potrebbe essere frutto nuovamente di una classe con scarso livello di coesione.

62

Capitolo 6. Object Oriented in un chicco di grano

Ancora, una classe con un’interfaccia particolarmente prolissa potrebbe essere risultato o di uno scarso livello di coesione, oppure di un processo di over-ingegnerizzazione (violazione ingiustificata della proprietà di primitività). Qualora si abbiano le idee confuse circa il buon disegno di una classe, la prova del nove potrebbe essere fornita dalla progettazione formale del relativo tipo di dato astratto. Da tenere presente che tale progettazione non è sempre indolore: per classi complesse tende a innescarne una serie preliminare atta a progettare gli ADT relativi alle classi da cui dipende quella oggetto di studio. In ogni modo, qualora sia possibile dar luogo ad un corretto ADT relativo alla classe, si può tranquillamente asserire che la stessa sia ben disegnata; negli altri casi evidentemente è presente qualche anomalia. Ricapitolando, le proprietà di una classe ben disegnata ([BIB13]) sono: • massima coesione; • minimo accoppiamento; • sufficienza; • completezza; • primitività.

Ricapitolando… Nel presente capitolo sono state introdotte brevemente le nozioni basilari dell’Object Oriented. L’intento dell’autore è stato presentare le nozioni utilizzate nei capitoli successivi, senza avere la presunzione di trattare in maniera approfondita un argomento così importante e vasto, per il quale si rimanda a testi più specifici. Il modo migliore per iniziare è indubbiamente la definizione di classi e oggetti enunciata da Booch: “un oggetto rappresenta un articolo (item), unità o entità individuale, identificabile, reale o astratta che sia, con un ruolo ben definito nel dominio del problema e con un confine ben stabilito”. Gli oggetti presenti in ogni sistema, molto genericamente, possono essere suddivisi in due categorie: “cose” che “esistono” (più o meno tangibilmente) nell’area di business oggetto di analisi, e oggetti che invece vivono nel modello di disegno e vengono introdotti per realizzare l’infrastruttura informatica, come per esempio Vector, File, IOStream, e così via. Per quanto concerne gli oggetti appartenenti alla prima tipologia, si può dire che tali entità sono un qualcosa che esiste (o “traspira”) nel mondo concettuale e, come tali, se ne può parlare, eventualmente li si può toccare o manipolare in qualche modo. “Ma cosa sono le cose del dominio del problema? Molte di queste cose probabilmente appartengono a una delle seguenti cinque categorie: oggetti tangibili, ruoli, episodi, interazioni e specificazioni” [Booch]. Bruce Eckel, nel libro Thinking in Java, afferma che esiste una connessione stretta tra gli oggetti e i calcolatori: “ogni oggetto assomiglia, in qualche modo, a un piccolo computer; possiede degli stati e delle

UML e ingegneria del software: dalla teoria alla pratica

63

operazioni che vi si possono invocare”. Un oggetto può anche essere considerato come un tool che permette di impacchettare insieme strutture dati e funzionalità per concetti, in modo da far loro rappresentare appropriatamente un’idea appartenente allo spazio del problema piuttosto che essere forzata a utilizzare un idioma del calcolatore sottostante. Tutti gli oggetti sono “istanze” di classi, ove per classe si intende un qualcosa che consente di descrivere formalmente proprietà e comportamento di tutta una categoria di oggetti simili. L’obiettivo è creare una corrispondenza biunivoca tra gli elementi del dominio del problema (oggetti che realmente esistono) e quelli dello spazio delle soluzioni. La difficoltà consiste nel riuscire a descrivere formalmente, precisamente e completamente un’astrazione a partire dagli esempi delle relative istanze (corpo dell’astrazione). Si può pensare al rapporto che intercorre tra un oggetto e la relativa classe come a quello esistente tra una variabile e il relativo tipo, in un qualsivoglia linguaggio di programmazione. Sempre secondo Booch, “un oggetto possiede stato, comportamento e identità; la struttura e il comportamento di oggetti simili sono definiti nella loro classe comune; i termini di istanza e oggetto sono intercambiabili”. Lo stato di un oggetto è un concetto dinamico e, in un preciso istante di tempo, è dato dal valore di tutti i suoi attributi e dalle relazioni instaurate con altri oggetti. Si tratta di un concetto molto importante poiché influenza il comportamento futuro dell’oggetto. Tipicamente, sottoponendo opportuni stimoli a un oggetto (invocazione dei metodi), questo tende a reagire, nella maggior parte dei casi, in funzione del suo stato interno. Il comportamento di un oggetto è costituito dalle inerenti attività (operazioni) visibili e verificabili dall’esterno. Lo scambio di messaggi tra oggetti (invocazione di metodi), generalmente, varia lo stato dell’oggetto stesso. “Il comportamento stabilisce come un oggetto agisce e reagisce, in termini di cambiamento del proprio stato e del transito dei messaggi” [Booch]. Da quanto emerso, è evidente che la relazione esistente tra lo stato di un oggetto e il comportamento è di mutua dipendenza: è possibile considerare “lo stato di un oggetto come l’accumulazione dei risultati prodotti dal relativo comportamento”, il quale, a sua volta, dipende dallo stato in cui si trova l’oggetto. L’identità di un oggetto è la caratteristica che lo contraddistingue da tutti gli altri. Spesso ciò è dato da un valore univoco. Per esempio, un oggetto “conto corrente” è identificato dal relativo codice, una persona cittadina italiana dal codice fiscale, e così via. Ogni oggetto è dotato di una propria interfaccia (si faccia attenzione a non confondersi con il concetto di interfaccia UML/Java) scaturita dall’elenco dei metodi esposti agli altri oggetti corredati dalla relativa firma. Quindi le richieste che un oggetto può soddisfare sono specificate dall’interfaccia della classe di cui è istanza. Questa, anche se implicita, rappresenta un contratto stipulato tra gli oggetti fornitori e quelli utilizzatori di servizi. Nell’interfaccia sono condensate tutte le assunzioni che gli oggetti client fanno circa quelli di cui utilizzano i servizi (server). L’interfaccia di un oggetto viene anche definita protocollo, poiché stabilisce i servizi offerti e la “sintassi” (firma dei vari metodi) con cui utilizzarli. Al concetto di interfaccia implicita si aggiunge quello di interfaccia esplicita (quella a cui si è portati comunemente a pensare). In UML un’interfaccia è definita come un insieme di operazioni, identificato da un nome, che caratterizza il comportamento di un elemento. Si tratta di un meccanismo che rende possibile dichiarare esplicitamente, in appositi costrutti esterni, le operazioni implementate dalle classi. L’attenzione è quindi focalizzata sulla struttura del servizio esposto e non sull’effettiva realizzazione. Le interfacce non possiedono implementazione, attributi o stati; dispongono unicamente della dichiarazione di operazioni (definita firma) e possono essere connesse tra loro tramite relazioni di generalizzazione. Visibilità private dei relativi metodi avrebbero ben poco significato e quindi non sono ammesse. Il meccanismo delle interfacce rende possibile tutto un insieme di meccanismi, come l’aggiunta quasi indolore di nuove funzionalità, la rimozione di altre, la sostituzione di componenti con altri più moderni (magari resi più efficienti, maggiormente rispondenti alle nuove richieste dei clienti) ecc. Gli oggetti che interagiscono con altri attraverso interfacce vedono questi ultimi solo attraverso quanto dichiarato nella

64

Capitolo 6. Object Oriented in un chicco di grano

relativa interfaccia senza necessità di possedere conoscenza diretta delle classi che implementano l’interfaccia. Queste ultime quindi si possono considerare come punti di plug-in nel disegno, in cui è possibile cambiare funzionalità, aggiornare la scheda, aggiungere altri servizi ecc.. Nella progettazione di sistemi Object Based, un’attitudine particolarmente richiesta è quella dell’astrazione. Si tratta di una proprietà tipica del cervello umano che permette di far fronte alle limitazioni nell’affrontare la complessità dei sistemi. In generale l’atto dell’astrarre consiste nell’individuare similitudini condivise tra oggetti, processi ed eventi appartenenti alla vita reale e nella capacità di concentrarsi su queste, tralasciando momentaneamente le differenze. Chiaramente, molto importante è anche il concetto di livello di astrazione che permette di enfatizzare alcuni aspetti del sistema e di trascurarne altri. Ogni qualvolta si parla di disegno o programmazione Object Oriented, le parole magiche che immediatamente vengono alla mente sono: ereditarietà, incapsulamento e polimorfismo, ossia le leggi fondamentali del disegno orientato agli oggetti. L’ereditarietà è indubbiamente la legge più nota del Object Oriented. Si tratta di un meccanismo attraverso il quale un’entità più specifica incorpora struttura e comportamento definiti in entità più generali. Tipicamente, quest’ultimi sono detti genitori, mentre gli elementi ereditanti sono detti figli. Questi, ovviamente, sono completamente consistenti con quelli più generali da cui ereditano (ne possiedono tutte le proprietà, i membri e le relazioni) e in più possono specificare struttura e comportamento aggiuntivi. Da un punto di vista intuitivo, è possibile pensare all’ereditarietà come a un meccanismo in grado di prendere un elemento di partenza, clonarlo e di aggiungervi ulteriore comportamento. Un elemento definito per mezzo della relazione di generalizzazione è, a tutti gli effetti, un nuovo tipo che eredita dal genitore tutto ciò che è dichiarato come tale. L’ereditarietà è una tecnica molto potente e i vantaggi apportati sono la semplificazione della modellazione di sistemi reali, la riusabilità del codice, nonché il polimorfismo. Uno dei principi più importanti dell’ereditarietà afferma che “un’istanza di una classe discendente può sempre essere utilizzata in ogni posto ove è prevista un’istanza di una classe antenata” (principio della sostituibilità di Liskov). Attraverso l’ereditarietà è possibile rappresentare formalmente i risultati dei processi di classificazione, ossia del processo mentale che permette di organizzare la conoscenza. Nel mondo Object Oriented ciò si traduce raggruppando, in un’opportuna organizzazione gerarchica, le caratteristiche comuni di specifici oggetti. Ciò permette di dar luogo a modelli più piccoli e quindi più semplici da comprendere, sebbene, qualora non utilizzata propriamente, possa generare diverse anomalie. Come tutti gli strumenti, anche l’ereditarietà ha un proprio dominio di applicazione che, se non rispettato, non solo non fornisce la formulazione di valide soluzioni, ma addirittura può generare gravi anomalie. Molto spesso disegnatori junior, non appena “fiutano” l’eventualità di un minimo comportamento condiviso (magari un paio di attributi e/o metodi), non si lasciano sfuggire l’occasione e danno luogo a generalizzazioni abbastanza stravaganti. Sebbene l’ereditarietà sia uno dei principi fondamentali dell’Object Oriented e offra moltissimi vantaggi se utilizzata appropriatamente, ciò non significa che si tratta dell’acqua santa. L’idea alla base della generalizzazione è il riutilizzo del tipo e non del codice. Il “limite” della generalizzazione è che una volta realizzata non può più variare: si tratta di una versione di soluzioni hard-coded. I problemi, come sempre, nascono quando si utilizza la generalizzazione in maniera forzata. L’esempio classico è relativo alle persone che possono essere imbarcate in un volo. Le tipologie sono, essenzialmente tre: piloti, assistenti di volo e passeggeri. In prima analisi potrebbe sembrare una classica situazione di ereditarietà salvo poi accorgersi che sia i piloti, sia il personale viaggiante possono — spesso e volentieri viste le tariffe agevolate — recitare il ruolo di passeggeri. Allora cosa fare? Le opzioni sono: a. ignorare il problema e dar luogo a copie di oggetti ogni qualvolta sia necessario: ciò comporta che ogni qualvolta sia necessario aggiornare i dati di un oggetto bisogna inventarsi un meccanismo impossibile per trovare eventuali copie;

UML e ingegneria del software: dalla teoria alla pratica

65

b. dare luogo a ulteriori specializzazioni: “pilota-passeggero” e “personale di volo-passeggero”, creando classificazioni assurde ad enorme ridondanza di codice; c.

sostituire l’ereditarietà con un’aggregazione, in modo da avere un nocciolo di comportamento e struttura comune a tutti gli oggetti e una tipologia variabile nel tempo.

Questo problema è noto con il nome di “anomalie dei ruoli”. Nell’Object Oriented non esiste unicamente il caso in cui una classe erediti da un solo genitore, bensì è possibile che una stessa classe erediti da diversi genitori: ereditarietà multipla. Questa tecnica, in diversi linguaggi (come per esempio Java) non è direttamente implementabile, pertanto è consigliabile non utilizzarla nel modello di disegno che dovrebbe essere quanto più possibile vicino al codice, mentre non esistono particolari controindicazioni, qualora ricorrano le condizioni, nell’utilizzarla nei modelli precedenti il cui scopo principale è ancora descrivere l’area business oggetto di studio. L’incapsulamento è il meccanismo che rende possibile il famoso principio dell’ information hiding (nascondere le informazioni) che, come suggerito dal nome, consiste nel nascondere i dettagli della struttura interna di una classe al resto del sistema. Il principio fondamentale è che nessuna parte di un sistema complesso debba dipendere dai dettagli interni di un’altra. A tal fine è necessario creare uno strato di separazione tra gli oggetti clienti e quelli fornitore, ottenuto separando l’interfaccia propria di un oggetto dalla sua implementazione interna. In generale l’incapsulamento viene suddiviso in due grandi categorie: quello standard che prevede che le classi non abbiano alcuna conoscenza della struttura interna delle altre, e in particolare di quelle di cui possiedano un riferimento, con la sola eccezione della firma dei metodi esposti nella relativa interfaccia. L’incapsulamento totale si ha quando ogni classe non dispone assolutamente di alcuna conoscenza delle altre, non solo per ciò che concerne il comportamento e la struttura interna, ma, neanche in termini di esistenza. Da quanto riportato appare evidente che i princìpi dell’incapsulamento e dell’ereditarietà, per molti versi, presentano diversi punti di discordanza. Il nascondere il più possibile l’organizzazione della struttura degli oggetti, di fatto, limita o, addirittura inibisce l’ereditarietà. Secondo un approccio purista Object Oriented, il modo con cui le informazioni sono rappresentate all’interno dell’oggetto dovrebbe essere del tutto irrilevante. Contrariamente a molte convinzioni comuni, ciò non significa semplicemente che tutti gli attributi dell’oggetto debbano avere una visibilità privata ed essere esposti per mezzo di opportuni metodi get e set. Questi metodi get, molte volte, non fanno altro che continuare a esporre i relativi attributi membro attraverso il valore restituito. Ciò implica che, se per qualche motivo varia la rappresentazione interna di un attributo membro di un oggetto, è necessario individuare tutti gli oggetti clienti e variarne conseguentemente il codice. Polimorfismo deriva dalle parole greche polys (= molto) e morphé (=forma): significa quindi “molte forme”. Si tratta di una caratteristica fondamentale dell’Object Oriented, relativa alla capacità di supportare operazioni con la medesima firma site in classi diverse con comportamenti diversi (derivanti però da una stessa antenata). La possibilità di poter attuare il polimorfismo, richiede, propedeuticamente, la facoltà di conoscere a priori una porzione dell’interfaccia di un insieme di classi, o se si preferisce, di poter raggruppare un insieme di classi attraverso segmenti di interfaccia condivisa. Ciò, naturalmente, si ottiene attraverso l’utilizzo dell’ereditarietà: tutte le classi discendenti ereditano l’interfaccia di quella antenata e quindi è possibile trattare i relativi oggetti come se fossero istanze di uno stesso tipo (la classe antenata). L’utilizzo del meccanismo delle interfacce (nel senso di costrutto public interface), rende anch’esso possibile trattare in maniera astratta un opportuno insieme di classi (quelle che implementano l’interfaccia). In un’organizzazione gerarchica ottenuta per mezzo dell’ereditarietà, si effettua un upcasting ogniqualvolta un oggetto di una classe discendente viene trattato (casted) come se fosse un’istanza della classe progenitrice, mentre con il termine di downcasting si intende l’operazione opposta.

66

Capitolo 6. Object Oriented in un chicco di grano

Il termine overriding è intimamente legato al polimorfismo: quando in una classe discendente si ridefinisce l’implementazione di un metodo, in gergo si dice che se ne è effettuato l’overriding. In sostanza si crea una nuova definizione della funzione polimorfica nella classe discendente. Il termine overloading ha a che fare con la definizione di diversi metodi con lo stesso nome, ma diversa firma. Due principi fondamentali della Computer Science sono quelli noti con i termini di massima coesione e minimo accoppiamento. Per quanto concerne la coesione, Booch afferma che: “un modulo presenta un’elevata coesione quando tutti i componenti collaborano fra loro per fornire un ben preciso comportamento”. Il concetto di coesione può essere applicato a diversi livelli di dettaglio (metodi, attributi, classi, package, componenti, ecc.): per esempio, un metodo presenta un elevato grado di coesione quando svolge una sola funzione ben definita. Al livello di classe la “coesione è la misura della correlatività delle proprietà strutturali (attributi e relazioni con le altre classi) e comportamentali di una classe (metodi)”. Chiaramente si desidera avere un valore di coesione che sia il più elevato possibile (i vari attributi e metodi sono fortemente correlati tra loro). Disegni i cui elementi presentano scarsi livelli di coesione possono generare una serie di anomalie. Per esempio rappresentano un problema le classi “mastodontiche” dotate di un numero eccessivo di attributi, relazioni con altre classi e/o metodi. Ciò non è auspicabile per una serie di motivi: si complica la comprensione del codice e quindi la relativa manutenzione, diviene laborioso eseguire il test, il riutilizzo della classe diviene complesso, si rende difficile isolare elementi soggetti a variazioni, ecc. Questo problema è tipicamente generato quando in un’unica classe ne vengono inglobate più d’una. I vantaggi generati da “componenti” software a elevata coesione sono:

• aumento del grado di riusabilità. Disporre di componenti con un insieme ben definito e circoscritto di responsabilità, evidentemente ne aumenta la probabilità di riutilizzo;

• robustezza. Un componente con responsabilità ben definite è più facile da comprendere, da verificare e quindi l’intero sistema risulta più robusto. La proprietà di minimo accoppiamento è definita come la “misura della dipendenza tra componenti software (classi, packages, componenti veri e propri, ecc.) di cui è composto il sistema”. Si ha una dipendenza tra due elementi, per esempio classi, quando un elemento (client) per espletare le proprie responsabilità ha bisogno di accedere alle proprietà comportamentali (metodi) e/o strutturali (attributi) dell’altro (server). Chiaramente quest’ultimo non dipende dai client, mentre è vero il contrario. Ciò comporta che un cambiamento all’elemento che fornisce i servizi genera la necessità di revisionare ed eventualmente aggiornare degli elementi client. In sintesi, la dipendenza di un componente da un altro implica che il funzionamento del componente stesso dipende dal corretto funzionamento di altri componenti. Un accoppiamento elevato non è desiderabile per una serie di motivi, tra i quali i più importanti sono:

• la variazione di un componente genera a cascata la necessità di verificare ed eventualmente aggiornare i componenti dipendenti;

• qualora si volesse riutilizzare uno specifico componente è necessario riutilizzare (o comunque portarsi dietro) tutti i componenti dipendenti; ecc. L’obiettivo da perseguire è minimizzare il grado di accoppiamento. Chiaramente eliminarlo non avrebbe molto senso: si potrebbe correre il rischio di generare la situazione opposta: classi mastodontiche che non sono accoppiate perché fanno tutto da sole e quindi si genererebbe un problema di minima coesione…

UML e ingegneria del software: dalla teoria alla pratica

67

Spesso nella progettazione di sistemi Object Oriented complessi si “sorvola” qualora le classi presenti all’interno di un package non presentino esattamente un accoppiamento minimo: ciò su cui però bisogna porre molta attenzione è che l’accoppiamento tra package sia veramente ridotto al minimo indispensabile. L’Abstract Data Type (tipo di dato astratto) è un linguaggio “matematico” che permette di specificare collezioni di entità atte a manipolare informazioni attraverso operazioni di generazione, distruzione, accesso, modifica delle relative istanze. Poiché queste collezioni sono definite attraverso un linguaggio ad alto livello di tipo matematico, ne segue che si è interessati più alla specifica dell’interfaccia che al metodo con cui l’ADT viene implementato e di come ne vengono trattati i dati. Le proprietà che caratterizzano un ADT sono: il tipo che esportano, l’interfaccia (ossia le operazioni esposte che rappresentano l’unico meccanismo utilizzabile per accedere ai dati), gli assiomi e condizioni dettate dallo spazio del problema. Le parti costituenti un ADT sono, essenzialmente, due: quella relativa ai dati, in cui è riportata la descrizione della struttura utilizzata dal particolare ADT e quella relativa ai servizi forniti. Per quanto concerne la prima, potrebbe essere omessa (espressione massima di incapsulamento) se non fosse per necessità relative alla definizione formale dei metodi. Anche per ciò che concerne l’altra sezione, quella dedicata alle operazioni (l’interfaccia), è possibile selezionare diversi livelli di formalità, sebbene sia opportuno tentare cercare di essere più rigorosi possibili. Una descrizione Object Oriented dei metodi dovrebbe prevedere la dichiarazione esplicita degli argomenti dei metodi e delle relative condizioni (pre, post- e durante l’utilizzo). Le operazioni si possono suddividere nelle categorie costruttori, distruttori, selettori e modificatori. Una caratteristica fondamentale importanza per ogni sistema è rappresentata dall’affidabilità. Nell’ambito dei sistemi software, con tale termine si includono due altri concetti: correttezza (il sistema realizza le funzioni per il quale è stato progettato) e robustezza (il sistema è in grado di rilevare e gestire situazione anomale). Ciò in pratica si traduce nel realizzare sistemi senza errori. La tecnica del Design by Contract (disegno per contratto), ideata da Bertrand Mayer nei laboratori del linguaggio Eiffel, grazie alla metodicità e formalità, rappresenta un ottimo strumento per realizzare sistemi (più) affidabili. La tecnica, come suggerito dal nome è basata sul concetto di contratto che stabilisce precise obbligazioni tra oggetti client e quelli fornitore. Da una parte, ogni oggetto cliente si fa carico di rispettare le condizioni pattuite nel richiedere i servizi, mentre dall’altra, i server assicurano il rispetto di determinate condizioni relative la fornitura dei servizi. Qualora un cliente non rispetti le condizioni pattuite, ovviamente il contratto perde di validità. Un contratto è quindi vantaggioso e protegge entrambi i contraenti sancendo, da una parte, la natura e qualità del servizio (postcondizioni) e, dall’altra, sollevando il fornitore (precondizioni) da ogni imputabilità qualora il cliente richieda la fornitura del servizio non rispettando gli accordi. Il contratto, in ultima analisi, governa le relazioni tra ogni metodo di un oggetto e i potenziali clienti. Pertanto definisce le assicurazioni che ogni parte deve garantire per una corretta fruizione del servizio (invocazione del metodo) e il risultato pattuito. Oltre alle pre- e postcondizioni, esiste un’altra serie di asserzioni il cui dominio invece è la classe. Tali condizioni sono dette invarianti poiché la relativa valutazione deve essere vera per tutte le istanze della specifica classe detta invariante. Quindi, le condizioni che costituiscono un contratto sono:

• precondizioni: vincoli che gli oggetti client devono rispettare per fruire di un servizio; • postcondizioni: garanzie assicurate dal fornitore del servizio, qualora le precondizioni siano rispettate;

68

Capitolo 6. Object Oriented in un chicco di grano

• invarianti: condizioni sempre valide. Un aspetto di particolare importanza è dato dalla combinazione della metodologia del Design by Contract con l’ereditarietà. In altre parole è necessario esaminare come un contratto pattuito da una classe antenata venga ereditato da una classe discendente. La soluzione a questo problema è data dal concetto del sottocontratto, il quale stabilisce una serie di norme dovute essenzialmente al principio di sostituibilità, quindi la classe estendente deve mantenere le stesse precondizioni definite in quella antenata oppure definirne altre più deboli e mantenere le stesse postcondizioni definite in quella antenata oppure definirne altre più forti. I vantaggi della metodologia del Design by Contract sono legati alla sua sistematicità che agevola per la realizzazione di sistemi senza errori, alla realizzazione di un framework per il debugging e il test delle diverse parti del sistema, all’aumento della qualità della documentazione, all’agevolazione della rilevazione e gestione di condizioni anomale, ecc. Il presente capitolo non poteva che terminare rispondendo ad una delle domande che più frequentemente sono poste dai neofiti del mondo Object Oriented: “Quando una classe può essere definita ben disegnata?”. Alla domanda è possibile rispondere, tenendo presente sia quanto riportato nei paragrafi precedenti, sia gli insegnamenti dei maestri dell’informatica. In particolare, una classe è ben disegnata quando rispetta le caratteristiche di:

• massima coesione (tutti i relativi elementi presentano un elevato grado di correlazione); • minimo accoppiamento (la classe possiede il minor numero possibile di relazioni con le altre classi);

• sufficienza (rappresenta correttamente la relativa astrazione e soprattutto modella sufficienti caratteristiche che permettono un’interazione valida ed efficiente con le classi client);

• completezza (l’interfaccia della classe cattura tutte le caratteristiche significative della relativa astrazione);

• primitività (non ci sono metodi esposti nell’interfaccia che possono essere ottenuti dalla combinazione dell’esecuzione di altri).

RISPOSTA ALLA DOMANDA Il metodo add(index : int, element : Object) : boolean, presente nell’interfaccia List, è un esempio di overloading. Ebbene sì. In effetti, poiché l’interfaccia List estende Collection, automaticamente eredita il metodo add(element : Object) : boolean, e quindi il metodo in questione ne rappresenta una versione. Ragion per cui si tratta di un meccanismo di overloading.

Capitolo

7

Gli oggetti: una questione di classe Lo UML diventerà il nuovo linguaggio di programmazione. Non esistono barriere tecniche, solo politiche. IVAR JACOBSON Le episodiche volte in cui un mio modello viene realizzato nella sua interezza senza modifiche, provo quasi un senso di commosso smarrimento. LVT

Introduzione Obiettivo del presente capitolo è illustrare il formalismo dei diagrammi delle classi che, verosimilmente, rappresenta uno dei più affascinanti e noti tra quelli definiti dallo UML. Non si tratta di una notazione completamente inedita, come del resto avviene anche per gli altri formalismi grafici, ma le sue origini possono essere individuate nei corrispondenti diagrammi dalla metodologia di Booch, che a loro volta discendono dai famosi diagrammi “entità–relazione” (entity–relationship diagram). Benché non si tratti di un formalismo eccessivamente complesso in quanto prevede un numero piuttosto contenuto di elementi e regole, realizzare validi modelli orientati agli oggetti è un’attività tutt’altro che banale. Con l’ampliamento dell’esperienza si riesce a comprende sempre più intimamente quanto sia complesso realizzare modelli validi; basti considerare la fase di disegno, una delle più complesse e critiche dell’intero ciclo di vita del software: è necessario prendere un numero di decisioni verosimilmente superiore a ogni altra fase dell’intero processo. La costruzione dei modelli a oggetti, oltre a richiedere tutti i talenti necessari per la realizzazione di un qualsivoglia modello (capacità di

2

Capitolo 7. Gli oggetti: una questione di classe

astrarre, di rappresentare rigorosamente concetti presenti nel mondo reale, e così via), esige ulteriori competenze, sia per l’elevato grado di formalità richiesto (i diagrammi delle classi sono l’espressione ultima delle leggi sancite nei “testi sacri” del paradigma OO), sia per via delle problematiche cui bisogna far fronte nella progettazione di un sistema(capacità di “prevedere” le evoluzioni future del sistema, di riuscire a circoscrivere le aree più vulnerabili, di rendere l’architettura flessibile, scalabile ed efficace, di saper coniugare i requisiti relativi alle prestazioni con altri non funzionali, quali la sicurezza per esempio, e così via). Queste capacità si conseguono costruendo sistemi informatici che funzionano. Capacità di astrazione sono principalmente richieste nel disegno dei primi modelli a oggetti (dominio e business), in cui l’obiettivo primario è descrivere formalmente l’area business argomento di studio, mentre quelle più tecniche diventano indispensabili nella realizzazione di modelli di analisi e disegno in cui l’obiettivo primario diventa costruire il sistema. La stessa nomenclatura in vigore non sempre aiuta e, in alcuni casi, concorre ad aumentare il livello di caos. Per esempio, in molte occasioni si utilizzano i concetti di oggetti e classi come sinonimi, altre volte i diagrammi delle classi sono indicati con il nome di “modelli a oggetti” (per esempio “modello a oggetti del dominio”), e via dicendo. Sebbene ciò risulti del tutto naturale per il personale esperto, in genere lo è di meno per quello coinvolto nello sviluppo di sistemi con non molta esperienza in materia. Lo stesso nome “diagramma delle classi”, quantunque ormai di uso comunissimo, probabilmente non è il migliore cui si potesse pensare. Un nome più appropriato, come dichiarato anche nel documento ufficiale delle specifiche, potrebbe essere “diagramma della struttura statica”. Gran parte dei diagrammi delle classi illustrati nel presente capitolo sono esempi di modelli a oggetti del dominio (domain object model). Le motivazioni vanno ricercate sia nella semplicità di comprensione dei concetti esposti (trattandosi della descrizione del dominio, nulla è dato per scontato), sia nell’elevato livello di astrazione rispetto a dettagli tecnici. Il modello a oggetti del dominio e quello business vengono realizzati durante le prime iterazioni del processo di sviluppo del software. L’obiettivo è realizzare un modello che permetta di descrivere formalmente gli oggetti che esistono nell’area business oggetto di studio e che il sistema, in qualche misura, dovrà implementare (su questo argomento si tornerà più approfonditamente nel capitolo successivo). Logica conseguenza è che dovrebbero essere rappresentate esclusivamente classi a carattere business, mentre quelle più strettamente tecniche dovrebbero comparire in modelli successivi (analisi e disegno). Qualora la corrispondenza tra entità dell’area business e classi presenti nel modello del dominio non sia del tutto immediata, è comunque possibile individuare regole, eventi, e così via, vigenti nel dominio del problema, che hanno dato origine ad essa. Per queste sue caratteristiche peculiari, il modello a oggetti del dominio concorre a diminuire il gap esistente tra lo spazio del problema e quello delle soluzioni; in altre parole supporta la

UML e ingegneria del software: dalla teoria alla pratica

3

descrizione formale del vocabolario in vigore nell’area business che il sistema dovrà realizzare. Ciò dovrebbe permettere di avviare l’edificazione del ponte comunicativo tra personale tecnico e quello esperto dell’area business oggetto di studio. Le predette caratteristiche, rendono il modello a oggetti del dominio uno strumento formidabile per la produzione di esempi: la sua comprensione non richiede conoscenze specifiche di un linguaggio o di una tecnologia particolare, e tanto meno è necessario conoscere approfonditamente il dominio oggetto di studio, giacché è descritto dal modello stesso. Nonostante questi vantaggi, bisogna ricordare che, in ultima analisi, i sistemi devono pur essere costruiti (ebbene capitano anche queste iatture…) e il modello in cui il formalismo delle classi raggiunge la sua massima espressione è sicuramente quello di disegno.

Diagrammi delle classi I diagrammi delle classi mostrano la struttura statica di un modello i cui elementi sono classi, interfacce, relazioni ecc. e il cui significato varia a seconda della fase del processo di sviluppo a cui appartiene il modello. Tecnicamente un diagramma delle classi è definito come un grafo di elementi (i famosi classificatori) connessi attraverso opportune relazioni. Pertanto vi albergano elementi come classi, interfacce, package, tipi di dati, e così via. Si tratta di elementi che nel metamodello UML specializzano la metaclasse astratta Classifier. Anche per questo motivo, probabilmente, un nome più appropriato per i diagrammi delle classi dovrebbe essere “diagramma della struttura statica”. Molto importante è sottolineare come questi diagrammi mostrino unicamente aspetti statici del modello. Con sommo dispiacere di alcuni tecnici, non permettono di rappresentare comportamenti temporali. Questi aspetti dinamici si prestano a essere modellati attraverso altre notazioni, come i diagrammi di interazione, degli stati e delle attività (cfr Capitoli 9 e 10).

Classi in UML Nell’ambito della progettazione di sistemi OO, con particolare riferimento ai diagrammi di classe, non ci si dovrebbe stupire del fatto che concetti come interfacce, classi e relative relazioni rappresentino gli elementi fondamentali. In particolare, le classi e gli oggetti permettono di descrivere cosa c’è all’interno di sistemi OO o component based, mentre le relazioni tra i vari oggetti consentono di evidenziarne l’organizzazione. Una classe è una descrizione di un insieme di oggetti che condividono struttura (attributi), comportamento (operazioni) e relazioni. La rappresentazione grafica prevede un rettangolo, tipicamente suddiviso in tre sezioni dedicate rispettivamente al nome, agli

4

Capitolo 7. Gli oggetti: una questione di classe

Figura 7.1 — Rappresentazione grafica di una classe.

Circonferenza - coordinataX : integer - coordinataY : integer - raggio : float + Circonferenza(x: integer, y ; integer, r : float ) + calcolaSuperficie() : float + calcolaArea() : float + disegna() + sposta(x: integer, y ; integer)

Compartimento nomi Compartimento attributi

Compartimento metodi

attributi e alle operazioni. Di queste, solo la prima è obbligatoria. Da tener presente che l’omissione di un compartimento non permette di effettuare alcuna deduzione. In altre parole se la sezione degli attributi non viene visualizzata, ciò non significa assolutamente che la classe ne sia sprovvista. Tale condizione è rappresentata mostrando il compartimento degli attributi vuoto. Il nome di una classe può essere riportato nella forma semplice, ossia solo il nome, oppure corredato dalla lista dei package che ne descrive il percorso (path name). Da notare che gli elementi della lista vengono separati da una coppia del carattere “due punti” (::). Esempi di nomi estesi potrebbero essere: java::util::Vector oppure com::lvt::system::dialoglayer::MainServlet. Generalmente, di una classe viene mostrato unicamente il nome. Ciò implica che essa sia definita nel package oggetto di disegno, mentre qualora sia importata da uno differente, sotto il nome sarebbe opportuno riportare il path del package di appartenenza. Per esempio, nome della classe (MainServlet) e package di provenienza (com::lvt::system::dialoglayer). Il nome di una classe dovrebbe essere un sostantivo oppure un’espressione appartenente al vocabolario del sistema che si sta modellando. Qualora la classe rappresenti un’istanza di un determinato stereotipo, è possibile specificarne il nome al di sopra di quello della classe, racchiuso dalle famose parentesi angolari (, , ). Una notazione grafica alternativa prevede di accostare al nome dello stereotipo una piccola icona, rappresentante lo stereotipo stesso, nell’angolo in alto a destra o, addirittura, di mostrare l’intera classe con la forma dell’icona (cfr fig. 7.2). Chiaramente deve sussistere una relazione biunivoca tra il nome dello stereotipo e la relativa icona.

5

UML e ingegneria del software: dalla teoria alla pratica

Figura 7.2 — Rappresentazione del compartimento del nome di una classe. Lo stereotipo “controller” è tipicamente utilizzato nel modello di analisi che, come si vedrà nel prossimo capitolo, si usa per rappresentare formalmente i requisiti funzionali, catturati attraverso i vari diagrammi dei casi d’uso, e per dar luogo a una primissima versione del modello di disegno. Spesso, lo use case a cui si riferisce la classe è mostrato attraverso l’introduzione del tagged value UseCase, che, chiaramente, non ha alcun significato per lo UML, ma è utile al lettore: in sistemi di dimensioni medie e grandi agevola il tracciamento (tracking) della provenienza degli elementi controller. Infine l’attributo leaf appartiene al metamodello e viene utilizzato per indicare che la classe non può essere ulteriormente specializzata. Equivale alla parola chiave final in Java.

«controller» AuthorizationMaganer {leaf, use case="Authorize user"}

«controller» AuthorizationMaganer {leaf, use case="Authorize user"}

AuthorizationMaganer

Subito sotto il nome della classe, è possibile specificare una lista di stringhe riportanti o appositi valori etichettati (tagged value), o attributi del metamodello. Per esempio classi astratte possono essere evidenziate sia riportando il relativo nome in carattere corsivo, sia visualizzando l’attributo del metamodello {Abstract = true}. Qualora si tratti di un campo booleano, la presenza della relativa parola chiave rappresenta automaticamente un valore true, mentre l’assenza equivale a un valore false. Quindi {Abstract = true} e {Abstract} sono equivalenti. La realizzazione dei vari modelli dello UML, e in particolare di quelli facenti riferimento a classi e oggetti, andrebbero realizzati utilizzando la lingua inglese. A questo punto già si possono sentire le urla di dolore di coloro che con tale idioma non hanno una “corrispondenza di amorosi sensi”. Ciò è indispensabile in contesti di aziende multinazionali, ed è comunque utile per consolidare i vantaggi propri dello UML (come facilità di circolazione e riutilizzo dei modelli, ecc.). Infine serve per evitare storpiature dovute alla derivante lingua “mista”, come metodi getIndirizzo o setDataFineValidita. La notazione per la rappresentazione grafica delle classi prevede le seguenti regole: •

il nome in grassetto centrato riportato nell’apposita sezione;



le altre parole chiave (come il nome dello stereotipo o eventuali tagged value) riportati in carattere tondo sempre nel compartimento dedicato al nome della classe;

6

Capitolo 7. Gli oggetti: una questione di classe •

il nome della classe segue la convenzione generalmente accettata di riportare la prima lettera in maiuscolo e le restanti in minuscolo (Articolo, Fattura, Utente, ecc.), salvo utilizzare nuovamente una lettera maiuscola per distinguere nomi composti (CarrelloDellaSpesa, LineaFattura, IntestazioneFattura, sempre in maiuscolo la prima lettera di eventuali altri nomi);



gli attributi e le operazioni riportati a caratteri tondi allineati a sinistra;



la convenzione dei nomi rispetta quella specificata per le classi, con l’eccezione della primissima lettera riportata in minuscolo anziché maiuscolo;



metodi astratti e nome delle classi astratte vanno riportati in corsivo;



metodi e attributi statici sottolineati.

Quanto riportato di seguito, sebbene per molti lettori possa risultare piuttosto insolito, corrisponde a verità. La notazione dello UML prevede (tool permettendo) la possibilità di definire compartimenti supplementari specificati dall’utente, da utilizzarsi per indicare ulteriori proprietà del modello. Per esempio si potrebbero utilizzare compartimenti supplementari per evidenziare concetti quali le regole del business, responsabilità, eventi gestiti, eccezioni scatenate, ecc. Queste informazioni supplementari potrebbero essere rappresentate sia attraverso una lista semplice, sia per mezzo di formati più complessi messi a disposizione dallo UML. Per esempio un compartimento dedicato alle responsabilità (cfr fig. 7.3) di una classe potrebbe essere utile sia per agevolare il lavoro dei programmatori, sia per applicare metodologie derivate dalle CRC Cards (si tratta di un metodo che sarà descritto in dettaglio nel Capitolo 8). Qualora si dia luogo a compartimenti supplementari, è molto importante riportare il nome che li identifica, sia per fornire maggiori informazioni, sia per distinguerli chiaramente da quelli standard. I compartimenti sono identificati dal nome che va riportato in alto al centro del compartimento stesso. Per questioni di comodità i nomi dei compartimenti predefiniti non vengono visualizzati. Un attributo è una proprietà della classe, identificata da un nome, che descrive un intervallo di valori che le sue istanze possono assumere. Il concetto è del tutto analogo a quello di variabile di un qualsiasi linguaggio di programmazione. Una classe può non avere attributi o averne un numero qualsiasi. Quando però gli attributi superano l’ordine della decina, può essere il caso di verificare che non si siano inglobate più classi in una sola. Esempi di attributi sono il colore di un oggetto grafico, le coordinate, la data di nascita di un impiegato, le dimensioni di una ruota, il simbolo di una valuta, e così via. Un attributo è, a sua volta, un’astrazione di un tipo di dato o di stato che un oggetto di una classe può assumere.

UML e ingegneria del software: dalla teoria alla pratica

7

Figura 7.3 — Esempio di utilizzo di un compartimento relativo alle responsabilità di una classe. La classe potrebbe essere parte di un sistema per il commercio elettronico, e in particolare potrebbe appartenere a un componente in grado di gestire le informazioni necessarie a un servizio di “allarme”. Si tratta di un agente in grado di segnalare all’utente il verificarsi di una precisa condizione (per esempio “prodotto in sconto”). La realizzazione del servizio, chiaramente, richiede la collaborazione di diverse classi non visualizzate. La classe da sola potrebbe rappresentare piuttosto un’interfaccia, magari di un componente EJB, che una classe vera e propria. AlertManager operations +AlertManager() +addNewAlert() +addNewAlerts() +removeAlert() +removeAlerts() +getAlert() +getMaxAlerts() +createPriceAlertCondition() +createStockAlertCondition()



responsibilities manages all the operations necessary to deal with alert requests

Figura 7.4 — Esempio di utilizzo di un compartimento dedicato alle eccezioni scatenabili dalla classe. In genere però, si preferisce una notazione alternativa che prevede di rappresentare le varie eccezioni attraverso apposite classi connesse alla classe in grado di scatenarle attraverso relazione di dipendenza, come mostrato di seguito. Transaction operations +Transaction() +addAction() +removeAction() +startTransaction() +commit() +rollBack() y y y

exceptions resourceLocked emptyTransaction actionNotRecognized

8

Capitolo 7. Gli oggetti: una questione di classe

Figura 7.5 — Rappresentazione delle eccezioni scatenabili da una classe. L’utilizzo dello stereotipo delle relazioni di dipendenza è del tutto legittimo se si considera che, nel metamodello UML, le eccezioni rappresentano una particolare versione di segnali. Come si può notare, la rappresentazione in fig. è più precisa poiché permette di specificare esattamente quali sono i metodi in grado di generare le eccezioni.

Transaction +Transaction() +addAction() +removeAction() o +startTransaction() +commit() o +rollBack()

«send»

«exception» ActionNotRecognisedException

«send»

«exception» ResourceLockedException

o

«send»

«exception» EmptyTransactionException

Nel metamodello UML (cfr fig. 7.6), la metaclasse Attributo è definita come specializzazione della caratteristica strutturale (StructuralFeature) associabile alla metaclasse Classifier (classificatore) di cui la metaclasse Class rappresenta una specializzazione. In altre parole, gli elementi che specializzano i classificatori (classi, attori, componenti, ecc.), a meno di particolari vincoli, posso dichiarare una serie di attributi.

Di un attributo è possibile indicare solo il nome, oppure il nome e il tipo, oppure la precedente coppia corredata da un valore iniziale. Eventualmente è possibile specificarne lo stereotipo. I seguenti sono validi esempi dello UML: - n o m e : s t r i n g , numeroPagine:integer = 0, customerId:String. Le convenzioni relative agli attributi sono specificate poco più avanti. Un’operazione può essere considerata come la trasposizione, in termini informatici, di un servizio che può essere richiesto ad ogni istanza di una classe per modificare lo stato del sistema od ottenere un servizio. Pertanto, un’operazione (livello di specifica dei metodi) è un’astrazione di qualcosa che può essere eseguito su tutti gli oggetti di una stessa classe. Una classe può anche non disporre di alcun metodo, sebbene ciò sia piuttosto improbabile, oppure averne un numero qualsiasi. Come nel caso degli attributi, qualora il numero dei metodi presente nella classe superi l’ordine delle decine, è consigliabile verificare se sia il caso di suddividerla in più classi di dimensioni ridotte, specializzate per compiti con un

9

UML e ingegneria del software: dalla teoria alla pratica

grado superiore di coesione. Ciò, entro i limiti della razionalità, favorisce la riusabilità del codice. Per ciò che concerne le convenzioni sul nome dei metodi, essi dovrebbero essere costituiti da brevi frasi contenenti dei verbi che rappresentano qualche comportamento o funzionalità della classe di appartenenza. La convenzione sul modo di scriverli rispecchia pienamente quella degli attributi: la prima lettera in minuscolo e le prime lettere delle parole successive in maiuscolo. Alcuni esempi di metodi sono +isEmpty():boolean, +move(p:Point), +addElement(). Nel metamodello UML (cfr fig. 7.6), la metaclasse metodo (Method) è una specializzazione della metaclasse caratteristica comportamentale (BehavioralFeature) che a sua volta è specializzazione della metaclasse Feature da cui eredita anche la superclasse di attributo. Anche Method è associata alla metaclasse Classifier. Ciò equivale a dire che, a meno di particolari restrizioni, gli elementi dello UML che specializzano il classificatore possono dichiarare un insieme di metodi.

Figura 7.6 — Attributi e metodi nel meta-modello UML. Il diagramma mostrato in figura presenta un livello di complessità eccessivamente elevato per coloro con non molta esperienza del formalismo dei diagrammi delle classi. Il consiglio è di riesaminarlo al termine dello studio del presente capitolo. Ogni classificatore è costituito da diverse caratteristiche (Feature); queste sono di due tipologie: comportamentali (BehavioralFeature ) e strutturali (StructuralBehavior ). Quest’ultima è data dagli attributi. Le caratteristiche comportamentali sono espresse attraverso la descrizione delle operazioni (livello di specifica), ognuna delle quali è implementata da uno o più metodi. Come si può notare, le classi Classifier, Feature, BehavioralFeature e StructuralBehavior sono astratte. owner

Classifier

0..1

1 type

*

feature

Feature

*

typeFeature

StructuralFeature

BehavioralFeature

-multiplicity : Multiplicity -changeability : Changeability -targetScope : ScopeKind -ordering : OrderingKind

Attribute -initialValue : Expression

-isQuery : boolean

Operation -concurrency : CallCouncurrencyKind -isRoot : boolean -isLeaf : boolean -isAbstract : boolean -specification : String

Method specification 1

-body : ProcedureExpression

*

10

Capitolo 7. Gli oggetti: una questione di classe

Figura 7.7 — Organizzazione della lista dei metodi per mezzo dell’uso degli stereotipi. Purtroppo, ben pochi tool commerciali permettono di realizzare un effetto come quello di figura. Circonferenza - coordinataX : integer - coordinataY : integer - raggio : float «constructor» + Circonferenza(x: integer, y ; integer, r : float ) «query» + calcolaSuperficie() : float + calcolaArea() : float ... «update» + disegna() + sposta(x: integer, y ; integer) ...

Quando si disegna una classe non è assolutamente necessario riportarne tutti gli attributi e/o tutti i metodi; molto spesso essi sono in numero così alto che non ci sarebbe neanche lo spazio fisico per visualizzarli tutti. Qualora non si vogliano specificare tutti gli elementi di una sezione è consigliabile riportare quelli ritenuti più significativi nel contesto oggetto di studio e riportare i punti di sospensione (...), in fondo alla lista, al fine di evitare ogni possibile ambiguità. Un’altra possibilità offerta dallo UML per organizzare lunghi elenchi (proprietà o metodi) consiste nell’organizzarli introducendo opportuni stereotipi. È appena il caso di ricordare come, nel caso degli attributi, la visibilità pubblica vada assolutamente utilizzata con parsimonia. Facendo riferimento ai princìpi dell’information hiding e dell’incapsulamento, si ricorda che è necessario che gli attributi siano manipolati dalle classi di essi “proprietarie”, giacché dovrebbero essere le uniche a conoscerne la logica e le eventuali operazioni causate dai valori assunti. Sempre valide sono poi le regole di massima coesione, minimo accoppiamento, ecc.

Classi annidate Classi dichiarate all’interno di altre sono definite annidate (nested o inner). Una classe annidata appartiene all’ambito di denominazione (namespace) della classe dove è definita e quindi è utilizzabile unicamente al suo interno. Con la versione 1.4 dello UML è possibile mostrare l’annidamento di una classe all’interno di un’altra attraverso un apposito formalismo: si connettono le due classi con un segmento riportante un apposito simbolo detto anchor (ancora) all’estremità della classe dichiarante. L’anchor è una croce inscritta in una circonferenza come mostrato nella fig. 7.8.

UML e ingegneria del software: dalla teoria alla pratica

11

Figura 7.8 — Rappresentazione di classi annidate.

ClasseDichiarante

ClasseAnnidata

In questa sezione, prendendo spunto da quanto esposto in uno dei migliori libri scritti su Java, ormai assurto al ruolo di “classico”, ovvero Thinking in Java di Bruce Eckel [BIB34], si mostra un esempio relativo alle classi annidate. Il concetto di annidamento tra classi risulta molto utile nel contesto dei framework di controllo. Una trattazione adeguata esula comunque dagli obiettivi del presente libro: esistono volumi interi dedicati all’argomento. Tanto per cambiare, anche il termine di framework appartiene all’insieme di quelli abusati, utilizzati per indicare tanti concetti, spesso anche discordanti. In questo contesto il termine è utilizzato per indicare un insieme di classi opportunamente disegnate e organizzate per risolvere categorie di problemi simili. La logica conseguenza è che il framework, per risolvere una specifica istanza di dominio, necessita di un processo preliminare di “personalizzazione”. Tale processo, in genere, consiste nel definire l’implementazione di interfacce predefinite o di specializzare opportunamente il comportamento di determinate classi. Il principio di funzionamento si basa sulla possibilità di disporre di classi che espongono un “comportamento” esterno predefinito — o stabilito in specifiche interfacce da implementare o in classi da cui ereditare, tipicamente dichiarate astratte — perciò conosciuto dalla logica interna del framework. Quest’ultima è quindi in grado di riferirsi alle nuove classi in modo astratto. La specializzazione del comportamento (ridefinizione o override di opportuni metodi) permette poi di risolvere i compiti specifici. Il framework di controllo è particolarmente efficace nei contesti di sistemi guidati dai messaggi (event driven system), ossia in sistemi che devono rispondere alla ricezione di segnali, generalmente, in tempi molto brevi. Un esempio tipico è la gestione dell’interfaccia utente in cui le azioni eseguite dall’utente (pressione di un bottone, inserimento del testo in un apposito campo, ecc.) corrispondono a eventi che il sistema deve gestire.

12

Capitolo 7. Gli oggetti: una questione di classe

Figura 7.9 — Esempio leggermente semplificato del framework control proposto nel libro Thinking in Java.

TimeEventSet timeEventSet = null; ... TimeEvent e = null; while ( (e=timeEventSet.getNext()) != null) { if (e.ready()) { debug.write(e.getDescription()); e.action(); } }

TimeEventSet

HouseControl -light : boolean -thermostat : String = "day" -water : boolean

+addevent(t : TimeEvent) +getNext() +removeEvent(t : TimeEvent) ...

LightOn +action()

LightOff +action()

+addEvent() +run() o

WaterOn

WaterOff

+action()

+action()

ThermostatNight

ThermostatDay

+action()

+action()

TimeEvent 1..*

-descriptor : String -timeEvent : Time +TimeEvent(time : Time) +ready() : boolean +action() +getDescription()

Un semplice esempio è riportato nella fig. 7.9. Per tutti coloro che non hanno molta padronanza dei diagrammi delle classi può risultare decisamente complesso e, verosimilmente, diverrà più chiaro con il procedere del capitolo. In ogni modo si tratta di un semplice prototipo di un sistema di controllo di una casa in grado di gestire automaticamente determinate operazioni opportunamente “schedulate”, quali accendere e spegnere le luci, aprire e chiudere l’acqua degli annaffiatoi, e così via. In questo contesto, le azioni sono simulate dall’impostazione del valore di precise variabili mentre, in un sistema reale,

UML e ingegneria del software: dalla teoria alla pratica

13

potrebbero essere sostituite da un opportuno impulso inviato a un servomeccanismo opportunamente collegato a un computer. La classe in cui avviene il controllo è HouseControl, che dichiara al suo interno una serie di altre classi annidate, quali: LightOn, LightOff, WaterOn, WaterOff, ThermostatNight e ThermostatDay. Queste rappresentano eventi particolari: ereditano tutte dalla stessa classe TimeEvent. Come si vedrà in seguito, ciò significa che ne ereditano e specializzano il comportamento. Il discendere da una classe comune, e quindi disporre del medesimo “aspetto”, consente alla classe HouseControl di conoscerne la porzione di comportamento necessario per controllarle. La classe HouseControl si avvale della collaborazione della classe TimeEventSet, per memorizzare gli eventi da gestire. In particolare, ciascuno di essi è caratterizzato dall’orario in cui deve essere eseguito e da un descrittore. Per esempio, l’acqua degli annaffiatoi deve essere aperta alle 18 per poi essere chiusa alle 20. Ciò è rappresentato dalla presenza di due istanze di evento nella classe TimeEventSet. Tali istanze sono la specializzazione WaterOn e WaterOff. Il funzionamento prevede la configurazione iniziale in cui si creano tutte le istanze degli eventi da inserire nell’istanza della classe TimeEventSet, e quindi l’invocazione del metodo run(). In effetti non si tratta del modo migliore di realizzare tale metodo... Verosimilmente, così congegnato, costituisce uno dei quei metodi in grado di monopolizzare la CPU. In ogni modo il suo compito è cercare all’interno della lista degli eventi per individuarne qualcuno che abbia raggiunto l’orario della propria esecuzione (l’invocazione del metodo ready() restituisce un valore true), al fine di invocarne il metodo action(). Chiaramente la logica di controllo non ha la minima idea di quali azioni poi il metodo esegua. Nel diagramma in questione, ciascuna specializzazione della classe TimeEvent si limita ad agire sulla propria variabile dichiarata nella classe HouseControl. Per esempio, il metodo action() della classe LightOn si occupa di inizializzare la variabile booleana light al valore true. Come si può notare, la struttura della classe HouseControl non è molto dissimile da quella di una classe utilizzata per gestire gli eventi originati da un particolare oggetto GUI: si dichiarano opportune classi annidate, che implementano interfacce listener predefinite, per potervi veder eseguito il corrispondente del metodo action() qualora si verifichi il relativo evento.

Interfacce In UML un’interfaccia è definita come un insieme di operazioni, identificato da un nome, che caratterizza il comportamento di un elemento. Si tratta di un meccanismo che rende possibile dichiarare esplicitamente le operazioni visibili dall’esterno di classi, componenti, sottosistemi, ecc. senza specificarne la struttura interna. L’attenzione è quindi focalizzata sulla struttura del servizio esposto e non sull’effettiva realizzazione. Per questa caratteristica, le interfacce si prestano a demarcare i confini del sistema o del componente a cui appar-

14

Capitolo 7. Gli oggetti: una questione di classe

tengono: espongono all’esterno servizi che poi altre classi interne realizzano fisicamente. Qualora una classe concreta implementi un’interfaccia deve necessariamente dichiarare tutte le operazioni definite da quest’ultima. Le interfacce non possiedono implementazione, attributi o stati; dispongono unicamente della dichiarazione di operazioni (firma) e possono essere connesse tra loro tramite relazioni di generalizzazione. Visibilità private dei relativi metodi possiedono ben poco significato. Tipicamente una singola interfaccia dichiara un comportamento circoscritto, ben definito, ad elevata coesione; pertanto elementi con comportamento complesso possono realizzare diverse interfacce. Nel metamodello UML le interfacce sono definite come specializzazioni della metaclasse Classifier, quindi possono essere rappresentate graficamente attraverso il classico formalismo del rettangolo diviso in compartimenti (come mostrato nel paragrafo relativo alle classi). In questo caso però è necessario riportare la parola chiave sopra al nome dell’interfaccia, proprio per evidenziare la particolare semantica di questo classificatore. Il compartimento dedicato agli attributi è solitamente omesso perché sempre vuoto, mentre la lista delle operazioni dichiarate dall’interfaccia si riporta nel relativo compartimento. Eventualmente è possibile rappresentare graficamente le interfacce attraverso le apposite notazioni alternative: nel compartimento del nome aggiungere, allineata a destra, l’icona corrispondente all’interfaccia (cerchio), oppure mostrare direttamente l’icona al posto del rettangolo con specificato unicamente il nome della stessa. Qualora si decida di utilizzare quest’ultima notazione, il cerchio è associato al classificatore che l’implementa attraverso una linea continua. Il significato è abbastanza intuitivo: il classificatore definisce tutte le operazioni dichiarate dall’interfaccia. Un classificatore può implementare diverse interfacce e quindi ciascuna dichiara esplicitamente una sezione del relativo comportamento. Sebbene l’obiettivo della presente spiegazione sia l’interfaccia, nel contesto dei diagrammi delle classi si utilizza spesso la denominazione generale di classificatore, poiché quanto asserito mantiene la propria validità in tutti i contesti in cui è possibile utilizzare il concetto di interfaccia: casi d’uso, diagramma delle classi, diagramma dei componenti ecc. Si ricordi che Classifier è la metaclasse astratta le cui specializzazioni sono: classi, interfacce, nodi, tipi di dati, componenti, casi d’uso, ecc.

La notazione basata sull’esclusiva visualizzazione dell’icona offre il vantaggio di mostrare più elegantemente le interfacce, di evidenziare nettamente la separazione della dichiarazione dall’implementazione degli elementi presenti in un diagramma, e così via. Però, a fronte di questi vantaggi, esiste il limite, per altro eluso da diversi tool, di non poter visualizzare la lista delle operazioni. Ciò fa sì che molto spesso si preferisca utilizzare la notazione classica del classificatore (rettangolo) al fine di poter visualizzare l’elenco delle operazioni. In tal caso, l’interfaccia viene associata al classificatore che la realizza attraver-

15

UML e ingegneria del software: dalla teoria alla pratica

Figura 7.10 — Notazioni grafiche utilizzabili per visualizzare istanze di interfacce. Nell’immagine in alto l’interfaccia è visualizzata utilizzando la notazione classica del classificatore, mentre in quella in basso si utilizza la corrispondente icona. In questo esempio l’interfaccia permette di separare nettamente la dichiarazione di un carrello della spesa dalla relativa implementazione. Questo permette alla classe di gestione degli ordini di ottenere le informazioni desiderate dagli oggetti “carrello della spesa” senza dover necessariamente far riferimento a un’implementazione specifica, o conoscerne l’implementazione della classe.

«interface»

ShoppingCart

OrderManager

OrderManager

«use»

+addItem() +getId() +getItem() +getItems() +getItemsNumber() +getLastUpdate() +getTotalAmount() +removeItem() +removeAllItems() +setId() ...

«use»

ShoppingCartImpl

ShoppingCartImpl ShoppingCart

so un segmento tratteggiato culminante con il triangolino (una sorta di relazione di generalizzazione con segmento tratteggiato) rivolto nella direzione dell’interfaccia. La relazione tra gli elementi che utilizzano l’interfaccia per pilotare in modo astratto i classificatori che implementano l’interfaccia stessa e questi ultimi, a seconda del contesto, può essere visualizzata sia attraverso una relazione di dipendenza sia per mezzo di opportune versioni della relazioni di associazione. In quest’ultimo caso è opportuno indicare la navigabilità (freccia diretta verso l’interfaccia) per evitare ogni possibile confusione di notazione.

Classi parametrizzate: i famosi template Per coloro che non possono vantare una certa esperienza di linguaggio di programmazione C++ il concetto di template potrebbe risultare piuttosto inconsueto. In effetti non tutti i linguaggi di programmazione (come per esempio Java) lo prevedono. Un template è un descrittore di una classe con uno o più parametri. Pertanto un template non definisce una singola classe, bensì una famiglia, in cui ciascuna istanza è caratterizzata

16

Capitolo 7. Gli oggetti: una questione di classe

Figura 7.11 — Notazione grafica utilizzata per le classi parametrizzate.

Map

Item Value Buckets : int

+ bind( in i : Item, in v : Value) : boolean {query} + isBound(in i : Item) : boolean ...

«bind» (Customer, Order, 3)

OrderedMap

dal valore assunto da tali parametri. Questi valori diversificano le varie classi condizionando il comportamento di specifici metodi. Un template non è direttamente utilizzabile in quanto la presenza dei parametri non permette di definire una singola classe. Quando poi il valore dei parametri viene precisato, si ha la specializzazione del template e quindi si dispone di una classe ben definita e utilizzabile. Un template può partecipare a diverse relazioni con altre classi. Per esempio può ereditare da una classe ordinaria. Ciò equivale a dire che tutte le classi ottenute dal template, specificando opportunamente i relativi parametri, ereditano dalla stessa classe genitore. La notazione grafica di un template prevede di rappresentare normalmente la classe, aggiungendo un rettangolo tratteggiato nell’angolo in alto a destra destinato a ospitare la dichiarazione della lista dei parametri (fig. 7.11). Il template è un meccanismo utilizzato da molti linguaggi di programmazione per poter definire collezioni omogenee di oggetti. In sostanza questo meccanismo permette di definire classi equivalenti alle collezioni Java (Vector, Hashtable, ArrayList, ecc.). Poiché in C++ non esiste il concetto della gerarchia di classi che ereditano da un progenitore comune (quello che in Java è la classe Object), il template rappresenta l’unico meccanismo per realizzare classi generiche da specializzare, in grado di gestire collezioni omogenee di oggetti. La differenza più evidente tra le collezioni Java e i template, risiede nel fatto che questi ultimi permettono di definire classi che dichiarano il tipo di dato che il template tratta. Ciò permette di avere maggiore controllo degli elementi che l’oggetto è in grado di memorizzare e di evitare i noiosissimi, e virtualmente pericolosi, downcast.

17

UML e ingegneria del software: dalla teoria alla pratica

Il tipo enumerato (enumeration) Il tipo enumerato rappresenta un tipo di dato definito dall’utente le cui istanze sono costituite da insiemi di “nomi”, sempre definiti dall’utente. Questi nomi sono caratterizzati dal possedere un determinato ordine, ma nessuna algebra. Un tipo enumerato, generalmente, viene associato a un attributo per vincolare l’insieme dei valori che può assumere. Così come definito, il tipo enumerato non ha molta attinenza con i concetti di classe, però si è deciso ugualmente di introdurlo in questa sezione sia perché a questo si faranno parecchi riferimenti, specie più avanti, sia perché la relativa notazione presenta molte similitudini con quella delle classi. In ultima analisi si tratta di un altro discendente della metaclasse Classifier. Ciò, tra l’altro, permette di rappresentare i tipi enumerati attraverso il classico rettangolo con il nome inserito nell’apposito compartimento e la parola chiave per evidenziarne la natura. Nel compartimento centrale (quello tipicamente riservato agli attributi) si specificano i valori: uno per ogni linea, mentre in quello finale eventualmente è possibile specificare alcune operazioni inerenti il tipo enumerato. Per esempio, per il tipo enumerato Boolean è possibile specificare le seguenti operazioni: and(with:Boolean) or(with:Boolean) xor(with:Boolean) not()

: : : :

Boolean Boolean Boolean Boolean

Definizione formale di attributi e metodi Nel presente paragrafo viene illustrata la definizione rigorosa del concetto di attributo e metodo nello UML. Data la sua natura si tratta di un paragrafo dedicato a un pubblico amante delle definizioni formali: per questo può essere trascurato o comunque letto molto rapidamente da tutti gli altri lettori. Figura 7.12 — Esempi di tipi enumerati. «enumeration»

«enumeration»

«enumeration»

«enumeration»

EJBSessionType

Boolean

MaritalStatus

ProfileStatus

Statefull Stateless

true false

divorcee married separated livingWithPartner widowed single

SUSPENDED TERMINATED LOOKED PENDING STANDBY OPERATIVE ELAPSED

18

Capitolo 7. Gli oggetti: una questione di classe

Si cominci con l’esaminare la definizione formale di attributo riportata di seguito: visibilità nome

: =

tipo-espressione [molteplicità ordinamento] valore-iniziale {stringa-proprietà}

La visibilità specifica le regole secondo le quali il relativo attributo è accessibile da parte di altri oggetti. In particolare, le tipologie di visibilità previste sono: • pubblica: l’attributo è accessibile da qualsiasi altro oggetto dotato di riferimento all’oggetto che contiene l’attributo in questione; • privata: l’attributo è accessibile solo all’interno della classe di appartenenza (dichiarante); • protetta: l’attributo è accessibile da tutte le istanze delle classi che “ereditano” da quella in cui l’attributo è definito; • package: l’attributo è accessibile da qualsiasi altro oggetto istanza di classi appartenenti allo stesso package o in un altro ad esso annidato a qualsiasi livello.

Per evidenti motivi di praticità, i nomi delle visibilità sono sostituiti con i simboli standard: + indica la visibilità pubblica – indica la visibilità privata # indica la visibilità protetta ~ indica la visibilità package.

Nulla vieta però di utilizzare icone più accattivanti, come avviene in diversi tool commerciali. La visibilità è un campo obbligatorio, sebbene la visualizzazione possa essere omessa. Qualora ciò avvenga, non esiste alcun default: in altre parole, se non è esplicitamente espresso, non è corretto assumere che la visibilità sia di tipo pubblica o privata o checchessia. Infine, è possibile aggiungere ulteriori visibilità dipendenti dal particolare linguaggio di programmazione utilizzato, come per esempio la visibilità “implementativa” (implementation) prevista dal C++. Per quanto concerne il nome, si tratta di un identificatore rappresentante appunto il nome dell’attributo. Le convenzioni seguono quelle universalmente accettate dai linguaggi di programmazione: deve necessariamente iniziare con un carattere alfabetico o un sottolineato (underscore, _ ), essere seguito da una qualsiasi ripetizione di caratteri e nu-

UML e ingegneria del software: dalla teoria alla pratica

19

meri ecc. Le convenzioni tipografiche prevedono che la prima lettera sia riportata in minuscolo. In alcuni linguaggi di programmazione si utilizza la convenzione di premettere il carattere sottolineato per distinguere variabili membro di una classe (attributi) da comuni variabili utilizzate per realizzare i vari metodi. In linguaggi quali Java, ciò non è necessario in quanto, qualora si abbia la necessità di evidenziare un attributo membro di una classe, è sufficiente premettere all’attributo stesso la parola chiave this. Il campo tipo-espressione può essere il nome di un’altra classe, interfaccia ecc., oppure una stringa, sintassi e semantica delle quali dipendono dal particolare linguaggio di programmazione scelto, che però deve possedere una corrispondenza con il ProgrammingLanguageDataType (tipi di dato del linguaggio di programmazione). ProgrammingLanguageDataType è una metaclasse del metamodello introdotta con la versione 1.4 dello UML. Appartiene al package Core ed eredita dalla metaclasse DataType. Si tratta di un meccanismo che permette di specificare tipi di dato dipendenti dal particolare linguaggio di programmazione utilizzato. A tal fine è disponibile l’unico attributo expression, che rappresenta appunto un’espressione la cui sintassi e semantica dipendono dello specifico linguaggio di programmazione.

Da notare che, sebbene sia possibile specificare come tipo di un attributo un’altra classe, tipicamente si preferisce rappresentare tali informazioni per mezzo di apposite relazioni. Qualora, nel modello di disegno, un tipo sia uno di quelli definiti dal linguaggio di programmazione (Date, Time, ecc.) o da una nuova classe di utilità (e quindi a scarso valore semantico), si preferisce riportare un apposito attributo senza evidenziare tutte le relazioni per migliorare la leggibilità e l’eleganza dei diagrammi (si evitano molte linee — associazioni — di scarsa importanza). I campi molteplicità e ordinamento sono facoltativi (presenza di parentesi quadre). Qualora omessi, la molteplicità viene assunta uguale a uno (1..1) e quindi il campo ordinamento perde di significato: in genere non ha molto senso definire l’ordine dell’unico elemento di una lista. La molteplicità è espressa specificando il limite inferiore seguito da due caratteri punto ( . ) e dal limite superiore (indice-inferiore..indice-superiore). Qualora il numero di elementi sia superiore all’unità, può avere senso definire l’ordinamento. Valori tipici sono: ordered (ordinato) o unordered (non ordinato). Per default si assume l’assenza di ordinamento. La molteplicità può essere associata a diversi elementi dello UML; qualora sia associata a un attributo indica quanti valori del tipo specificato possono essere gestiti dall’oggetto in cui l’attributo viene dichiarato. Qualora il limite inferiore e quello superiore coincidano (l’intervallo degenera in un singolo valore) è sufficiente riportare il valore stesso (3..3 3), mentre se il limite superiore può essere virtualmente infinito si specifica il carattere asterisco ( * ) che equivale ad affermare una molteplicità del tipo “0..*”. Per esempio in una classe Persona potrebbe prevedere i seguenti attributi:

20

Capitolo 7. Gli oggetti: una questione di classe

firstName middleName surname gender eMail[0..3] height dateOfBirth eyeColor[1..2] ...

: : : : : : : :

string string string Gender string float Date EyeColor

Mentre la classe Date appartiene al linguaggio di programmazione, Gender potrebbe essere stata introdotta per indicare il sesso della persona e EyeColor per rappresentare il colore degli occhi. L’attributo relativo a quest’ultima caratteristica somatica prevede uno o due valori. Il primo caso potrebbe essere utilizzato per persone con entrambi gli occhi dello stesso colore, mentre il secondo potrebbe essere utilizzato per persone con occhi di colore d diverso: sebbene molto raro, è comunque un caso possibile che potrebbe essere di interesse per l’anagrafica di un centro di ricerca. Il valore-iniziale è un’espressione dipendente dal linguaggio di programmazione prescelto, il cui valore, esito della valutazione della relativa espressione, rappresenta il valore dell’attributo nel momento in cui l’oggetto dichiarante viene creato. Infine il campo stringa-proprietà, del tutto opzionale, indica valori di proprietà riferibili all’attributo. La presenza delle parentesi graffe potrebbe creare qualche confusione. In questo contesto indica che un attributo può avere un numero qualsiasi di proprietà-stringa (0 o diverse), le quali, tipicamente, sono racchiuse tra parentesi graffe, appunto. Un’altra informazione molto importante di attributi e metodi è il campo d’azione (scope). In particolare sono previsti due tipi: • istanza (instance): si tratta di quella di default, in cui ogni istanza della classe gestisce una propria copia dell’attributo; • classe (classifier): esiste un solo valore dell’attributo che viene condiviso da tutte le istanze della classe (attributo statico). Tipicamente, quando si disegna una classe, si descrive la struttura e il comportamento dei relativi oggetti. Chiaramente non è possibile utilizzare queste caratteristiche fintantoché non sono create istanze della classe. Questo è vero per un campo d’azione di tipo istanza. Le cose variano quando si ha a che fare con campi d’azione statici. In tal caso gli attributi, i metodi e/o le relazioni, dichiarati come tali non sono legati a un singolo oggetto, bensì sono condivisi da tutte le istanze. Proprio per questo motivo si dice che lo scope è a livello di classificatore. In questo caso è possibile accedere agli elementi dichiarati statici anche senza aver generato un’istanza della classe che li contiene.

UML e ingegneria del software: dalla teoria alla pratica

21

Gli attributi con scope a livello di istanza sono rappresentati normalmente, mentre a quelli statici viene aggiunta un’apposita sottolineatura. Infine, per default il valore degli attributi è modificabile nel tempo ({changeable}), qualora invece ciò non sia vero è possibile specificare diversamente. A tal fine sono disponibili due opzioni: • {frozen} (congelato): i valori assunti dall’attributo non possono essere modificati dopo che l’oggetto di appartenenza è stato istanziato e i relativi attributi sono stati inizializzati. Qualora l’attributo sia una collezione, non è possibile aggiungere altri valori dopo l’inizializzazione. • {addOnly} (solo aggiunta): chiaramente questo vincolo ha senso solo qualora un attributo individui un insieme di valori (Array, Vector, HashTable, ecc.). Esso sancisce che, una volta assegnati dei valori all’attributo, questi non possono essere più variati, mentre è sempre possibile aggiungerne degli altri. I vincoli e le proprietà specificabili per gli attributi si materializzano nel metamodello UML come attributi della metaclasse StructuralFeature da cui Attribute eredita (ordering, changeability, multiplicity e targetScope, cfr fig. 7.6).

Per ciò che concerne i metodi, la definizione formale è la seguente: visibilità nome(lista-parametri):tipo-espressione-di-ritorno {stringa-proprietà}

Per quanto attiene al campo visibilità, vale quanto detto per gli attributi. Ovviamente, il frammento di frase “l’attributo è accessibile”, va modificato con “il metodo è invocabile”. Il campo nome, a sua volta, segue le stesse regole riportate per gli attributi, così come il campo tipo-espressione-di-ritorno è completamente equivalente a quello tipoespressione visto in precedenza. Per quanto concerne lista-parametri, si tratta di un insieme di parametri formali separati dal carattere virgola ( , ), ciascuno dei quali rispetta la seguente sintassi: Tipologia nome:tipo-espressione = valore-di-default

Il campo Tipologia prevede le seguenti alternative: in, out, inout, con significati piuttosto naturali: i parametri in sono veri e propri parametri di input e quindi non modificabili all’interno del metodo (per essere precisi, possono anche essere modificati, ma queste modifiche vengono perse poco prima che il metodo termini), quelli di out invece sono generati per comunicare un’informazione al metodo chiamante, mentre inout sono parametri di input modificabili. Per default, un parametro è di tipo in. Da tener presente che non tutti i linguaggi di programmazione permettono di definire parametri

22

Capitolo 7. Gli oggetti: una questione di classe

modificabili all’interno del metodo: un esempio è Java in cui il passaggio dei parametri è unicamente per valore (salvo poi copiare il riferimento invece che il valore per questioni di prestazioni). I campi nome e tipo-espressione hanno il medesimo significato visto in precedenza, mentre valore-di-default è del tutto assimilabile a quanto riportato per il campo valore-inziale associato a un attributo. Anche per quanto riguarda i metodi, quelli statici (scope a livello di classe) vengono evidenziati attraverso apposita sottolineatura. Metodi che non generano cambiamento di stato del sistema (in altre parole esenti da effetti collaterali) possono essere evidenziati per mezzo della proprietà {query}. L’eventuale tipologia di concorrenza prevista da un metodo è evidenziabile per mezzo della proprietà {concurrency=nome}, dove il nome può assumere i seguenti valori: sequential (sequenziale), guarded (controllata), concurrent (concorrente). Un metodo astratto, ossia del quale viene dichiarata unicamente la “firma” (signature), in modo del tutto consistente con quanto avviene per le classi, può essere visualizzato in carattere corsivo oppure associandovi la proprietà {abstract}. I metodi preposti per l’accettazione dei segnali, in oggetti in grado di operare ricevendo e rispondendo a precisi stimoli esterni provenienti sotto forma di segnali, possono essere enfatizzati con la parola chiave . Alcuni esempi di metodi sono: # + + +

setId(id:long) getId():long setName(name:String) repaint() {guarded}

A conclusione del paragrafo, viene riportato un esempio ricorrente: il famoso design pattern (modello di disegno) denominato Singleton. La particolarità consiste nel fatto che l’unica classe di cui è costituito dà luogo, in fase di esecuzione, a una sola istanza. Questa peculiarità lo rende indispensabile ogni qualvolta sia necessario gestire centralmente un insieme di risorse: pool di connessioni al database, pool di thread, elenco dei parametri di inizializzazione di un sistema (i parametri che venivano specificati nei file .ini), ecc. La struttura generale del Singleton prevede che il costruttore abbia visibilità privata: quindi nessun client può richiedere la generazione di istanze. Poiché almeno una volta questo metodo deve pur essere invocato, ossia almeno un’istanza deve essere creata, è necessario dichiarare all’uopo un opportuno metodo statico, + getInstance(). Questo metodo viene invocato dalle classi client per ricevere il riferimento all’unica istanza della classe. Qualora ancora non sia stata ancora creata l’unica istanza prevista, il metodo è comunque invocabile in quanto statico, getInstance() e, in tal caso, si deve occupare di creare l’unica istanza e quindi restituirne il riferimento. Poiché diversi client potrebbero invocare lo stesso metodo contemporaneamente la prima

23

UML e ingegneria del software: dalla teoria alla pratica

Figura 7.13 — Struttura del design pattern Singleton.

- singletonInstance

0..1

«singleton»

Singleton ...

1

«constructor» -Singleton() «misc» +getInstance() : Singleton {concurrency=sequential} ...

volta, la creazione dell’istanza dell’oggetto deve essere controllata, altrimenti si correrebbe il rischio di creare più istanze del Singleton! Per controllare la presenza o meno dell’unica istanza della classe, è necessario dichiarare un attributo (statico ovviamente) di tipo della stessa classe.

Relazioni Le “entità” coinvolte in un diagramma delle classi raramente sono destinate a rimanere isolate; qualora ciò avvenga, potrebbe essere il caso di rivedere molto accuratamente quanto modellato. In situazioni ordinarie tutte le classi rappresentate sono connesse con altre secondo precisi criteri. La modellazione di un sistema, dunque, non solo richiede di identificare le classi di cui è composto, ma anche di individuare i legami che le interconnettono. Nel mondo OO, tutte le relazioni si riconducono ai tipi fondamentali dipendenza, generalizzazione e associazione, da cui derivano tutte le altre. In fig. 7.14 è riprodotta una porzione della struttura statica del componente JFileChooser appartenente alla libreria Swing Java. Si tratta del widget che permette di selezionare uno o più file attraverso una finestra simile a quella che compare quando si seleziona l’opzione “apri file” o “salva con nome” in un generico programma. Si tenga presente che il diagramma è stato ottenuto esaminando il codice e quindi diverse interpretazioni possono risultare opinabili. Ogni relazione fornisce un criterio e quindi una semantica diversa per organizzare le proprie astrazioni. Una relazione di generalizzazione rappresenta un legame tra una classe generale, denominata “superclasse” o “genitore” (parent), e una sua versione più specifi-

24

Capitolo 7. Gli oggetti: una questione di classe

ca, denominata “sottoclasse” o “figlio” (child). Per esempio, tutti i widget Swing estendono la classe astratta JComponent (che a sua volta estende la classe java.awt.Container, a sua volta specializzazione di quella java.awt.Component). Come si può notare, una relazione di generalizzazione può interessare anche le interfacce: ActionListener estende EventListener. Le classi che intendono ricevere gli eventi generati dalla finestra di dialogo, devono sia implementare l’interfaccia, sia registrarsi come “ascoltatori” presso il componente stesso. In sostanza queste classi devono implementare il metodo actionPerformed(ActionEvent e) che gli consente di ricevere l’evento scaturito. La stessa relazione di realizzazione (legame semantico tra elementi in cui uno specifica un contratto che l’altro elemento si impegna a rispettare) non sono altro che particolari versioni della relazione di ereditarietà. In questo caso si eredita unicamente l’interfaccia (il tipo) senza l’implementazione. Da notare che, nel diagramma in fig. 7.14, le interfacce presenti sono state rappresentate con due diverse notazioni: quella tipica delle classi (Accessible) e quella propria delle interfacce (ActionListener ed EventListener). La seconda è più intuitiva e accattivante, mentre la prima viene preferita qualora si intenda visualizzare i metodi dichiarati nell’interfaccia (nulla vieta di definire un ulteriore stereotipo che unisca i due vantaggi).

Figura 7.14 — Relazioni dello UML Accessible

JComponent

(javax.accessibility)

(javax.swing)

-accessory 0..1

EventListener

+getAccessibleContext() : AccessibleContext

(java.util)

Generalizzazione

Realizzazione JFileChooser (javax.swing)

-currentDirectory 1 -selectedFile

File

0..1 -selectedFiles

(java.io)

0..n

Associazione

0..n

Composizione -filters

0..n

0..n

0..n +JFileChooser() +JFileChooser(currentDirectoryPath : String) 0..n +getSelectedFile() : File +setSelectedFile( file : File ) 0..n +getCurrentDirectory() : File +setCurrentDirectory(dir : File ) +rescanCurrentDirectory() +setDialogTitle(dialogTitle : String ) .. .

-dialog

ActionListener (java.util)

Dipendenza Aggregazione

1 1

-fileSystemView

1

-fileView

0..1

FileFilter

JDialog

FileSystemView

FileView

(java.io)

(javax.swing)

(javax.swing.filechooser)

(javax.swing.filechooser)

UML e ingegneria del software: dalla teoria alla pratica

25

Il componente JFileChooser è poi legato con una relazione di dipendenza all’interfaccia ActionListener. Si tratta di una relazione semantica tra due classificatori, in cui una variazione dell’elemento indipendente (fornitore) può comportare aggiornamenti di quello dipendente (cliente). Tale legame di dipendenza potrebbe apparire errato: ci si attenderebbe che la classe JFileChooser memorizzi l’elenco degli oggetti ascoltatori e quindi, in ultima analisi, che sussista una relazione più forte. L’arcano è spiegato dal fatto che la classe che effettivamente memorizza questa lista è JComponent, mentre JFileChooser si limita a reindirizzare le richieste. Una relazione di associazione indica un legame strutturale tra oggetti. Di questa relazione esistono diverse versioni, tra cui l’aggregazione e la composizione. Una relazione di associazione tra due classi indica che — a meno di ulteriori vincoli come la navigabilità — è possibile navigare dagli oggetti di una classe a quelli dell’altra e viceversa. Per esempio la classe JFileChooser ha tre relazioni di associazione con la classe File. Queste sono utilizzate, rispettivamente, per tenere traccia della directory corrente, dell’eventuale file selezionato e degli eventuali file selezionati (nel caso in cui la multiselezione sia abilitata). Come si può notare, è presente un vincolo di navigabilità (freccia riportata in una parte dell’associazione). Questo stabilisce che, dagli oggetti JFileChooser, è possibile navigare in quelli di tipo File associati, mentre non è possibile il contrario. In sostanza gli oggetti di tipo File (legittimamente) non memorizzano un riferimento all’oggetto che li utilizza. Lo stesso widget può essere associato alla classe astratta FileFilter , le cui specializzazioni permettono di definire dei criteri atti a stabilire quali file visualizzare e quali no (per esempio solo i file con estensione .java, quelli con estensione .java, .jar, e così via). Nel diagramma sono presentate due versioni con semantica più forte della relazione di associazione: aggregazione e composizione. Come si vedrà di seguito, entrambe rappresentano relazioni strutturali tra elementi in cui uno rappresenta il tutto e gli altri le parti. Le composizione, oltre a specificare una relazione di tipo “tutto/parte”, impone vincoli aggiuntivi, quali per esempio il fatto che la mancanza di una parte fa perdere di significato al “tutto”, che le parti non possano essere condivise, ecc. Per esempio il componente JFileChooser è basato su una finestra di dialogo e quindi non si potrebbe avere neanche la classe JFileChooser senza la relativa classe (JDialog). Per la FyleSystemView potrebbe valere un discorso analogo: se non si disponesse di un oggetto in grado di interpretare le informazioni lette dal file system, non sarebbe possibile presentare molte informazioni all’utente. In questo caso però si è preferito selezionare una relazione di aggregazione. Ciò perché le classi client che utilizzano il widget JFileChooser possono impostare (metodo set) l’istanza della specifica classe estensione di FyleSystemView, in tal caso nulla assicura che la medesima istanza non venga condivisa con altri oggetti. La non condivisibilità delle parti (nello stesso istante di tempo) è un vincolo fondamentale per una relazione di composizione.

26

Capitolo 7. Gli oggetti: una questione di classe

La relazione di aggregazione con la classe JComponent nel ruolo Accessory, permette di associarvi altri componenti dedicati alla realizzazione di compiti particolari, come la visualizzazione dell’“anteprima” dei file.

Dipendenza La dipendenza è una relazione semantica tra due elementi (o insiemi di elementi) utilizzata per evidenziare una condizione in cui la definizione di un’entità o il relativo funzionamento sono subordinati a quelli di un’altra. Ciò implica che una variazione dell’elemento indipendente (fornitore, supplier) genera la necessità di revisionare l’elemento dipendente (cliente, client). Ciò al fine di verificare l’eventuale necessità di modificare l’elemento cliente come conseguenza degli aggiornamenti apportati a quello fornitore. Esempi tipici di relazioni di dipendenza si hanno quando, in assenza di una relazione più “forte”, un metodo di un elemento prevede come parametri formali istanze di altre classi, quando un oggetto invoca un metodo di un altro, e così via. In tutti questi casi, esiste un’evidente dipendenza tra le classi coinvolte, sebbene esse non siano necessariamente legate da un’associazione esplicita. Tipicamente, si ricorre a una relazione di dipendenza qualora non ne esistano altre più appropriate; in altre parole, la relazione di dipendenza è la più debole di quelle previste. Un altro esempio molto ricorrente in cui si usa la relazione di dipendenza si ha quando una classe necessiti di utilizzarne altre in modo astratto attraverso opportune interfacce (fig. 7.10). In questi casi, evidentemente, eventuali variazioni dell’interfaccia si ripercuotono anche nelle classi utilizzanti e pertanto ha senso associare queste ultime alle interfacce utilizzate per mezzo della relazione di dipendenza (sempre nel caso in cui non sia presente una relazione più forte). Dalla definizione della relazione di dipendenza consegue che non si tratta di un elemento di esclusiva proprietà del formalismo dei diagrammi delle classi, ma può essere utilizzata in altri contesti, come per esempio il diagramma dei componenti. Tipicamente, relazioni di dipendenza non dovrebbero comparire in diagrammi a oggetti del dominio e di business (in tali contesti ha senso evidenziare legami semantici e strutturali tra le varie entità, che quindi sono di natura più forte), e pertanto, nell’ambito dei modelli a oggetti, sono relegate essenzialmente a quello disegno. La definizione del metamodello UML prevede che, nella relazione di dipendenza, qualsiasi ModelElement (elemento del modello) possa svolgere il ruolo di supplier e client; si tratta della metaclasse astratta da cui derivano tutti gli altri elementi dello UML, come per esempio parametri, attributi, metodi, classi, interfacce, nodi, use case, attori ecc. Logica conseguenza di ciò è che tutti gli elementi dello UML possono essere relazionati tra loro per mezzo di una relazione di dipendenza. Ciò è vero a meno di particolari vincoli, definiti in specifiche specializzazioni della relazione di dipendenza, volti a limitarne il dominio di applicabilità. Per esempio la specializzazione Binding utilizzata per “specializzare” i template (illustrata di seguito) prevede i seguenti vincoli: un ModelElement può partecipare come client di una sola relazione di Binding, la relazione dispone di un solo supplier e molti

27

UML e ingegneria del software: dalla teoria alla pratica

Figura 7.15 — Rappresentazione del pattern Data Access Object (DAO). Il BusinessObject (tipicamente si tratta di un Session EJB, ma può anche trattarsi di una Servlet o di un JavaBean) è il client della situazione, ossia l’oggetto che necessita di accedere a una sorgente di dati (un RDMS o un LDAP, una parte di wrapper di un Legacy System, ecc.) per reperire quelli di sua pertinenza. A tal fine crea un’istanza della classe DataAccessObject, il cui scopo è quello di incapsulare l’appropriata DataSource per renderne trasparente l’utilizzo alla classe client. Quindi il DataAccessObject si occupa di interagire con la relativa sorgente dati e di creare un apposito grafo di oggetti (rappresentato per semplicità da un ValueObject) da restituire all’oggetto client (l’istanza del BusinessObject).

BusinessObject

«create» «use»

DataAccessObject

*

dataSource 1

DataSource

«create»

ValueObject

Figura 7.16 — Esempio (tratto dalle specifiche della versione UML 1.4) di utilizzo di varie specializzazioni della relazione di dipendenza.

ClassA

«friend»

ClassB «create»

«call»

ClassC «refine»

ClassD

ClassC combina le due classi logiche

ClassE

28

Capitolo 7. Gli oggetti: una questione di classe

client, il numero degli argomenti deve essere equivalente a quello dei parametri, ogni argomento del ModelElement supplier deve essere dello stesso tipo, o di un tipo discendente, del corrispondente parametro del ModelElement client, ecc. Graficamente la relazione di dipendenza è rappresentata attraverso una freccia tratteggiata collegante due elementi, quello dipendente dal lato della coda e quello indipendente dal lato della freccia. Qualora si verifichi il caso in cui un elemento dipenda da un insieme di altri elementi, la notazione grafica utilizzata prevede il ricorso a una serie di frecce tratteggiate, ciascuna puntante a un elemento indipendente, unite in coda alla freccia associata all’elemento indipendente. È inoltre necessario associare una nota all’associazione proprio nel luogo di congiunzione delle frecce, luogo che eventualmente può essere evidenziato da un apposito punto. La situazione opposta, un insieme di elementi dipendente da uno solo, si presta a essere rappresentata nella medesima maniera, avendo però l’accortezza di invertire l’orientamento delle frecce. Nel diagramma di fig. 7.16 le istanze di ClassA fruiscono dei servizi esposti da oggetti di tipo ClassB; l’invocazione è resa possibile dalla visibilità di tipo friend di alcuni metodi. Per poter disporre di oggetti di tipo ClassC, le istanze di ClassA devono invocare opportuni metodi esposti dalle istanze di ClassB che si occupano di generare oggetti di tipo ClassC da restituire agli oggetti ClassA. Da notare che la definizione di ClassC combina quella di due classi logiche (ClassD e ClassE) che come tali vivono in modelli di altre fasi a maggiore livello di astrazione.

Specializzazioni e stereotipi della relazione di dipendenza La relazione di dipendenza prevede diverse specializzazioni atte a enfatizzare la semantica dell’utilizzo che ne viene fatto (tabb. 7.1, 7.2, 7.3). In particolare, nel metamodello è predefinita una serie di specializzazioni standard (specificate per mezzo delle relative metaclassi: Binding, Abstraction, Usage e Permission) che nella pratica sono utilizzate molto raramente in quanto ancora eccessivamente generiche. Al loro posto si utilizzano specifici stereotipi in grado di rappresentare semantica specializzata. Per esempio la metaclasse Usage, pur specializzando la relazione di dipendenza, fornisce ancora indicazioni piuttosto generiche relative al proprio utilizzo. Al fine di restringere ulteriormente la semantica e quindi fornire informazioni di maggior dettaglio, si preferisce adottare gli opportuni stereotipi (call, create, instantiate e send). La metaclasse Usage è comunque molto importante perché definisce le linee generali della relazione: quali sono i possibili elementi sorgenti, quali quelli destinatari, la semantica generale, e così via, che la contraddistinguono dalle altre famiglie di specializzazioni. Per esempio la metaclasse Abstraction, pur essendo una specializzazione della relazione di dipendenza, è profondamente diversa dalla relazione di Usage : Abstraction indica che gli elementi a cui si applica rappresentano concetti equivalenti a diversi livelli di astrazione.

UML e ingegneria del software: dalla teoria alla pratica

29

Tabella 7.1 — Stereotipi della metaclasse Abstraction, specializzazione della relazione di dipendenza (metaclasse Dependency). Parola chiave abstraction

Nome

Descrizione

Abstraction

È utilizzata per relazionare due elementi (o insiemi) che rappresentano lo stesso concetto ma a diversi livelli di astrazione.

derive

Derivation

Si tratta di uno stereotipo della metaclasse Abstraction che, come suggerisce il nome, specifica una relazione di derivazione tra elementi del modello. Tipicamente, si tratta di elementi dello stesso tipo. Un elemento derivato, in quanto tale, può essere sempre ricavato dall’esecuzione di qualche operazione definita nell’elemento base. Sebbene ridondante, l’elemento derivato viene comunque definito o per rendere più chiaro il modello o per questioni di efficienza.

realize

Realization

Lo stereotipo realize evidenzia una relazione tra elementi del modello, in cui i supplier sono utilizzati per specificare mentre i client sono impiegati per implementare i relativi concetti. Gli elementi implementativi devono necessariamente supportare tutte le operazioni e/o ricevere tutti i segnali specificati nell’elemento dichiarativo. La relazione rappresenta appunto il mapping tra i concetti dichiarati e le relative implementazioni. Questa realizzazione si presta a essere utilizzata per modellare processi di raffinamento, ottimizzazioni, trasformazioni, template, sintesi, framework, composizioni, ecc. Chiaramente non va confusa con la relazione di generalizzazione: in questo contesto ci si riferisce a un livello superiore di astrazione (è pur sempre uno stereotipo della relazione di Abstraction).

refine

Refinement

La relazione di refine associa elementi del modello a diversi livelli di semantica, come per esempio classi a livello di analisi con le corrispondenti a livello di disegno. Il mapping può essere unidirezionale o bidirezionale, ed è utilizzato per modellare le trasformazioni di elementi presenti in artifact a differenti livelli semantici

trace

Trace

Come suggerito dal nome, lo stereotipo di “tracciamento” è utilizzato per illustrare relazioni tra elementi che rappresentano lo stesso concetto in differenti modelli. Questa relazione risulta molto utile per evidenziare gli elementi generati dai requisiti e le variazioni nei vari modelli. Tipicamente, si tratta di relazioni bidirezionali con significato informale e quindi non computabile.

30

Capitolo 7. Gli oggetti: una questione di classe

Tabella 7.2 — Stereotipi delle metaclassi Permission e Binding, specializzazione della relazione di dipendenza (metaclasse Dependency). Parola chiave bind

Nome

Descrizione

Binding

La relazione di binding (legame) rappresenta una relazione tra un elemento Template, che in questo caso rappresenta il supplier, e l’elemento del modello generato dal Template (il client). Poiché la generazione del nuovo elemento avviene per mezzo dell’associazione dei parametri, ne segue che la relazione Binding include i parametri attuali necessari per sostituire quelli formali dichiarati dal Template. L’associazione dei

parametri

(binding

appunto)

produce

la

clonazione

dell’elemento Template, durante la quale avviene la sostituzione dei parametri. Il risultato consiste in un altro elemento del modello che si comporta come se fosse direttamente parte del modello stesso. permission

Permission

La relazione di “permesso” serve a evidenziare che un elemento del modello (client) è abilitato ad accedere a quelli presenti in un differente NameSpace (spazio dei nomi, appartenente al supplier). Per esempio una classe può eseguire i metodi di un’altra, un package può accedere agli elementi pubblici di un altro, ecc. Questa metaclasse prevede tre stereotipi standard: access, import e friend.

access

Access

Si tratta dello stereotipo della specializzazione Permission, utilizzato per evidenziare che un package ha il permesso di referenziare gli elementi pubblici presenti in un altro package.

friend

Friend

Friend è uno stereotipo della specializzazione Permission, i cui partecipanti sono elementi del tipo operazioni, classi, package. In particolare, la relazione sancisce che, l’elemento sorgente (client)

può

accedere

a

quello

destinatario

(supplier),

indipendentemente dalle regole di visibilità dichiarate. Pertanto la relazione di “amicizia” estende la visibilità del supplier al fine di consentire all’elemento client di accedervi. import

Import

La relazione di “importazione” rappresenta uno stereotipo della specializzazione Permission. In particolare definisce che lo spazio dei nomi supplier viene aggiunto a quello del client. In altre parole, il contenuto dichiarato pubblico in un package viene inserito in quello di un altro.

UML e ingegneria del software: dalla teoria alla pratica

31

Tabella 7.3 — Stereotipi della metaclasse Usage, specializzazione della relazione di dipendenza (metaclasse Dependency). Parola chiave use

Nome

Descrizione

Usage

La relazione di utilizzo è una specializzazione di quella di dipendenza, utilizzata per evidenziare la situazione in cui un elemento client richiede un altro (o un insieme, supplier) per la completa implementazione od operatività. Non si tratta di una relazione utilizzata per tener “traccia” dell’evoluzione di un modello, bensì per di una necessità di collaborazione pendente. Ciò implica che i due elementi coinvolti devono necessariamente appartenere allo stesso modello. La classe si presta ad essere ulteriormente stereotipizzata al fine di rappresentare più accuratamente la natura della collaborazione, come per esempio una classe invoca un metodo di un’altra, un metodo possiede parametri del tipo di un’altra classe, una classe istanzia un’altra e così via. La relazione di “chiamata” è uno stereotipo di quella Usage, in cui sia il sorgente (supplier), sia il destinatario (client) devono essere necessariamente operazioni. Eventualmente, la relazione può essere estesa alle classi che definiscono i metodi, con un significato più generale: esiste almeno un metodo delle classi a cui la relazione è applicata. Questa forma risulta molto utile, al fine di ridurre il numero di relazioni da visualizzare, qualora la relazione preveda che diversi metodi della classe chiamante invochino uno o più della classe chiamata. La relazione di create è uno stereotipo di Usage, il cui significato è del tutto inequivocabile: l’elemento client genera istanze dell’elemento supplier. Si tratta di uno stereotipo della relazione Usage, molto simile a quello denominato Create. La differenza risiede nel fatto che in questo è indicata l’operazione dell’elemento client che genera un’istanza dell’elemento supplier. La relazione send è uno stereotipo di Usage. È utilizzata per evidenziare l’operazione della classe client che genera istanze del segnale dichiarato dalla classe supplier.

call

Call

create

Creation

instatiate

Instatiation

send

Send

32

Capitolo 7. Gli oggetti: una questione di classe Le tre tabelle mostrate in precedenza vanno considerate come se fossero una sola e sono suddivise per ragioni di impaginazione. Lo sfondo grigio indica le specializzazioni della relazione di dipendenza specificate per mezzo di apposite metaclassi nel metamodello UML, seguite dalle relative specializzazioni.

Generalizzazione La generalizzazione è la relazione definita nello UML per visualizzare legami di ereditarietà tra classificatori e quindi anche tra classi. La definizione formale sancisce che si tratta di una relazione tassonomica tra un elemento più generale (detto padre) ed uno più specifico (detto figlio). Quest’ultimo è completamente consistente con quello più generale e, tipicamente, definisce ulteriori informazioni, in termini di struttura e comportamento. Pertanto un’istanza di un elemento figlio può essere utilizzata in ogni parte in cui è previsto un oggetto di tipo dell’elemento padre. Nel caso di relazione di generalizzazione tra classi, alla nomenclatura “ecclesiale” (padre e figlio) si preferisce quella più specifica di superclasse e sottoclasse (superclass e subclass). Si tratta della stessa relazione già definita nel contesto dei casi d’uso, non a caso, nel metamodello UML, la relazione di Generalizzazione prevede come dominio di applicazione i classificatori, di cui classi, interfacce, attori, use case, ecc. sono specializzazioni. La generalizzazione è una relazione tassonomica, transitiva e antisimmetrica. La proprietà transitiva implica che se una Classe C generalizza (ossia eredita) una Classe B, la quale a sua volta generalizza Classe A, ne segue che C generalizza anche A. In parole semplici, un discendente (nell’esempio un nipote C) eredita a sua volta quanto il genitore (B) ha ereditato dai suoi antenati (a meno che il genitore non dissipi tutto il patrimonio…). La proprietà antisimmetrica sancisce che se A eredita da B, non è assolutamente vero (anzi è impossibile) il contrario. Nel disegno di sistemi orientati agli oggetti (o component based), tipicamente l’identificazione del comportamento condiviso da diverse entità permette di estrarne e centralizzarne una versione generalizzata, di organizzare le varie classi similari secondo un’opportuna gerarchia, di realizzare il polimorfismo e il trattamento astratto. Questo approccio, se correttamente applicato, permette di realizzare modelli migliori e architetture più semplici. Come al solito però, il principio reca con sé tutta una serie di trabocchetti nei quali è facile incorrere. Lungi dal ripetere per l’ennesima volta le varie problematiche (per le quali si rimanda al Capitolo 6), si tenga presente la limitazione dell’utilizzo dell’ereditarietà: una volta stabilita una gerarchia, in qualche modo si “cementano” le varie classi in tale organizzazione. La relazione di generalizzazione viene visualizzata attraverso un segmento congiungente la sottoclasse alla superclasse, con un triangolo vuoto posto all’estremità di quest’ultimo elemento. Si consideri un tipico sistema bancario con particolare riferimento all’area Treasury. In essa sono presenti diverse entità rappresentanti gli “attori” del business bancario. Per

33

UML e ingegneria del software: dalla teoria alla pratica

Figura 7.17 — Struttura gerarchica di Date, Time e Timestamp del package java.sql. Come si può ben notare sia Time, sia Timestamp sono realizzate specializzando la classe Date.

TimeStamp .. .

Date .. . +Date() +getHours() +getMinutes() +getSeconds() +setHours() +setMinutes() +setSeconds() +setTime() +toString()

+TimeStamp() +after() +before() +equals() +getNanos() +setNanos() +toString() +valueOf()

Time .. . +Time() +getDate() +getDay() +getMonth() +getYear() +setDate() +setDay() +setMonth() +setYear() +toString() +valueOf()

esempio ci sono le Counter Party (terze parti), ossia i clienti della banca ai quali vendere (o meglio, con i quali scambiare) i prodotti finanziari, le Processing Organisation (organizzazioni di processo), che sono i dipartimenti di una banca demandati alla gestione dei trade stipulati con i clienti (Counter Party), i Broker (agenti) che si occupano di facilitare il commercio (principalmente relativo a scambi di valuta) tra la banca e i clienti, ecc. Tutte queste entità hanno un certo insieme di comportamento e struttura comune, con in più peculiari specializzazioni. Per esempio le Processing Organisation gestiscono i Book utili per organizzare i Trade, i quali contengono tutta una serie di informazioni molto importanti: il cliente, l’organizzazione di processo, il broker (il quale pretende di ricevere la provvigione, nota come brokerage, per la stipula del contratto), ecc. Pertanto, a prima vista si potrebbe trattare del classico esempio di generalizzazione: vi sono una Legal Entity che rappresenta il comportamento generico e tante specializzazioni, una per ogni ruolo.

34

Capitolo 7. Gli oggetti: una questione di classe

Figura 7.18 — Modello mostrante i diversi ruoli “interpretabili” da un’entità legale nel sistema business di una banca.

LegalEntity -id : string -shortName : string -longName : string -isCorporate : boolean ... 1

plays 1..n

LegalEntityRole -roleId : string -roleDescription : string ...

Agent

Broker

CalculationAgent

CounterParty

IPA

Investor

ProcessingOrganisation

Trustee

Pur trattandosi di un modello molto semplice e facilmente comprensibile, è sfortunatamente del tutto inadeguato per diversi motivi. Per esempio, alcune Legal Entity possono assumere più ruoli: in qualche modo le relative istanze devono trasmutare durante il loro ciclo di vita. Situazioni di questo tipo si prestano a essere modellate sostituendo l’ereditarietà con un’apposita composizione. Resta il fatto che comunque è opportuno specializzare i ruoli, perché alcuni di essi partecipano a particolari relazioni con comportamenti specifici (cfr fig 7.18). In fig. 7.19 è mostrato un esempio tratto dal package java.security. La classe astratta Permission, rappresenta la base predefinita per la gestione degli accessi a una risorsa del sistema. Tale classe implementa le interfacce Serializable e Guard . La prima è necessaria per consentire alle istanze delle specializzazioni di P e r m i s s i o n la persistenza del proprio stato. Si può notare che l’interfaccia Serializable non dichiara alcun metodo. Ciò potrebbe sembrare abbastanza bizzarro: in realtà si tratta di un meccanismo utilizzato per raggruppare le classi il cui stato deve persistere (per questo motivo anche le discendenti devono implementare l’interfaccia Serializable). L’interfaccia Guard, come suggerito dal nome, rappresenta una guardia le cui istanze sono oggetti utilizzati per proteggere l’accesso ad altri. A tal fine viene definito un unico metodo: checkGuard.

35

UML e ingegneria del software: dalla teoria alla pratica

La classe Permission, in quanto astratta, non può essere istanziata direttamente (include dichiarazioni dei metodi astratti equals, hashCode, implies le cui specializzazioni devono definirne l’implementazione). A tal fine sono presenti le classi UnresolvedPermission (permessi non risolti all’atto dell’inizializzazione della policy), AllPermission (implica tutti gli altri permessi ossia viene disabilitata la sicurezza) e BasicPermission (Permessi basilari). Anche quest’ultima classe è astratta e pertanto valgono gli stessi discorsi della classe Permission. In questo caso la classe specializzante è SecurityPermission. Tutti i metodi newPermissionCollection possono restituire una lista di permessi (in realtà la versione della classe Permission restituisce un valore null) per una determinata risorsa e quindi vi è un’evidente dipendenza dalla classe PermissionCollection. A dire il vero, poiché questa raccoglie una lista di permessi, a sua volta dipende dalla classe Permission. Figura 7.19 — Il diagramma mostra l’organizzazione dei permessi definiti nel package della sicurezza di Java (java.security).

Permission Serializable

+Permission() +hashCode() +getName() +checkGuard() +newPermissionCollection() +equals() +getActions() +implies() +toString()

(java.io)

UnresolvedPermission +UnresolvedPermission() +hashCode() +newPermissionCollection() +equals() +getActions() +implies() +toString() «istantiate»

«istantiate»

PermissionCollection +elements() +add() +setReadOnly() +implies() +isReadOnly() +toString()

«istantiate»

AllPermission +AllPermission() +hashCode() +newPermissionCollection() +equals() +getActions() +toString()

Guard +checkGuard()

BasicPermission +BasicPermission() +hashCode() +newPermissionCollection() +equals() +getActions() +implies() +toString()

«istantiate»

SecurityPermission +SecurityPermission()

36

Capitolo 7. Gli oggetti: una questione di classe

Figura 7.20 — Esempio di relazione di Generalizzazione. Definizione formale della relazione di dipendenza (package Core del metamodello UML).

ModelElement 1..* client

1..* supplier

Relationship

* supplierDependency * clientDependency

Binding

Usage

Dependency

Permission

Abstraction

Un esempio interessante di utilizzo della relazione di generalizzazione è fornito dalla definizione formale della relazione di dipendenza, illustrata nel precedente paragrafo, riportata nel package Core del metamodello dello UML (fig. 7.20). Tutti gli elementi utilizzabili nei diagrammi dello UML discendono da uno stesso elemento: la metaclasse astratta ModelElement (elemento del modello). Si tratta di una tecnica utilizzata per disporre di un’elegante relazione gerarchica e cumulativa di tutti gli elementi dello UML. Una delle sottoclassi di M o d e l E l e m e n t è la metaclasse Relationship, utilizzata per rappresentare legami tra elementi del modello. Dalla metaclasse Relationship discendono tutte le relazioni utilizzabili nei diagrammi UML. Una di queste relazioni è appunto la relazione di dipendenza (Dependency). In questo caso si tratta di una metaclasse concreta (non astratta) e quindi utilizzabile direttamente nel modello. Tale relazione è virtualmente utilizzabile per associare qualsiasi coppia di elementi dello UML, anche tra loro eterogenei. Teoricamente (bisogna sempre fare i conti con i tool) si potrebbe associare una classe con un use case per mezzo della relazione di dipendenza, per indicare magari che la classe deriva da requisiti specificati nello use case. Meglio ancora, si potrebbe associare un’interfaccia a uno use case per indicare da quale use case deriva la specifica di un componente. La metaclasse Dependency non prevede

UML e ingegneria del software: dalla teoria alla pratica

37

restrizioni della semantica, in altre parole non sono presenti ulteriori vincoli. Comunque, per la costruzione dei vari modelli, tipicamente si preferisce utilizzare le specializzazioni di tale metaclasse, le quali prevedono una semantica più specifica e tutta una serie di restrizioni che ne caratterizzano l’utilizzo. Ciò è dovuto alla necessità di condizionarne l’utilizzo alle regole semantiche della specializzazione. Per esempio, nella relazione di Binding l’elemento client deve essere necessariamente conforme al tipo dell’elemento supplier, il numero degli argomenti deve coincidere con quello dei parametri, e così via. Una stessa relazione di dipendenza può coinvolgere diversi supplier e ciascuno di essi può essere coinvolto in svariate relazioni di dipendenza. Lo stesso discorso vale per i client: ancora relazione n a n. Per terminare, la relazione di dipendenza prevede quattro specializzazioni predefinite: Abstraction, Binding, Permission e Usage.

Associazione binaria Un’associazione è una relazione strutturale tra due o più classificatori descrivente connessioni tra le relative istanze. Come per le altre relazioni, anche la sfera di applicazione della relazione di associazione non è circoscritta ai soli diagrammi delle classi, bensì è utilizzabile in diversi contesti. Per questa ragione nell’illustrazione si fa riferimento al concetto generale di classificatore e non a quello più specifico di classe. Volendo circoscrivere la spiegazione ai soli diagrammi delle classi è sufficiente operare una sostituzione dei nomi: ad ogni occorrenza del termine classificatore è necessario inserire il vocabolo classe. Così per esempio, si ha che una associazione binaria è una relazione strutturale che associata istanze di una classe a quelle di un’altra.

In base al numero degli elementi coinvolti nella relazione si hanno diverse “specializzazioni” della relazione: associazione unaria, binaria e n-aria. Il caso decisamente più frequente è costituito dall’associazione binaria che, come lecito attendersi, coinvolge due classificatori, in particolare specifica che oggetti di un tipo sono collegati a quelli di un altro. Pertanto, data un’associazione binaria, è possibile navigare dagli oggetti di un tipo a quelli dell’altro. L’associazione binaria è così importante, da essere stata definita da Rumbaugh come la “colla” che unisce il sistema. In effetti, senza la relazione di associazione le istanze delle classi sarebbero destinate a una vita isolata. Una “degenerazione” dell’associazione binaria è costituita dalla relazione unaria detta anche autoassociazione (self association) in cui un classificatore è associato con sé stesso. Graficamente un’associazione binaria è rappresentata attraverso un segmento che unisce i due classificatori connessi. Tipicamente alle associazioni è assegnato un nome per specificare la natura della relazione stessa e, al fine di eliminare ogni possibile fonte di

38

Capitolo 7. Gli oggetti: una questione di classe

Figura 7.21 — Esempio di relazione binaria.

Nome dell'associazione

Direzione di lettura del nome

Istruttore

Corso insegna

...

...

...

...

frequenta Studente ...

Associazione binaria

...

Figura 7.22 — Illustrazione informale degli attributi della metaclasse AssociationEnd. Da notare che alcuni attributi sono di tipo enumerato, come aggregazione, i cui valori sono none, aggregate e composite.

Corso ... ...

nome navigabilità ordine aggregazione campo d'azione molteplicità modificabilità visibilità

UML e ingegneria del software: dalla teoria alla pratica

39

ambiguità, spesso l’etichetta del nome viene corredata da una freccia che ne indica l’ordine di lettura (cfr fig. 7.21). Nel caso del modello a oggetti del dominio, i nomi delle associazioni concorrono a illustrare le business rule. Nome e direzione devono essere visualizzati nei pressi dell’associazione a cui appartengono, ma non vicini ai classificatori, per non essere confusi con i ruoli. Fin qui l’associazione binaria è stata descritta nella sua forma essenziale: in realtà prevede tutta una serie di ornamenti (adornments) che permettono di illustrarne proprietà specifiche. In particolare, consentono di specificare importanti caratteristiche con cui i classificatori partecipano nelle associazioni. Idealmente, l’intorno del punto dello spazio in cui una terminazione della relazione di associazione incontra il classificatore è sede di tutta una serie di proprietà. Nel metamodello tali proprietà sono rappresentate dagli attributi di un’apposita metaclasse: AssociationEnd (cfr fig. 7.22). Navigabilità Quando la relazione di associazione tra due classi è mostrata senza alcuna direzione ne segue che, nell’ambito della relazione, entrambe le classi sono navigabili, quindi, data un’istanza di una classe, è possibile transitare nelle istanze dell’altra a cui è associata e viceversa. L’associazione così definita in fase di disegno, non rappresenta però una situazione auspicabile poiché genera un forte accoppiamento tra le classi partecipanti alla relazione: entrambe le classi che partecipano alla relazione devono prevedere un riferimento all’altra classe. Ciò permette di navigare dalle istanze di un tipo a quelle dell’altro. Se inoltre il tipo di relazione è n a n, allora la situazione diventa ancora meno piacevole: entrambe le classi devono prevedere una lista di riferimenti alle istanze dell’altra.

In fase di disegno, quando si hanno maggiori informazioni sul sistema, è opportuno ponderare bene l’assenza di navigabilità tra le classi di un’associazione, sebbene spesso non sia possibile farne a meno... Diversamente, invece, nei modelli a oggetti generati nelle fasi precedenti del processo di sviluppo — modelli a oggetti del dominio, del business e di analisi — non sempre è opportuno prendere decisioni che possano condizionare prematuramente la realizzazione del sistema. Pertanto in tali modelli, a meno di casi evidentissimi, non è il caso di investire tempo nello stabilire i vincoli di navigabilità delle varie relazioni di associazione.

Qualora in una relazione di associazione non si voglia permettere alle istanze di una classe di “vedere” quelle dell’altra (essenzialmente, di invocarne i metodi), nella rappresentazione della relazione è necessario inserire una freccia indicante il verso di percorrenza. Pertanto gli oggetti istanza della classe posta nella coda dell’associazione (navigabilità = false)

40

Capitolo 7. Gli oggetti: una questione di classe

possono invocare i metodi e accedere agli attributi pubblici delle istanze della classe puntata dalla freccia (navigabilità = true), mentre non è possibile il contrario (cfr fig. 7.23). In termini pratici, ciò implica la necessità di memorizzare nella classe che non permette la propria navigazione il riferimento (o i riferimenti) alle istanze dell’altra classe. La presenza del vincolo di navigabilità è preferibile poiché diminuisce l’accoppiamento tra classi. Un errore comunemente commesso, specie nella realizzazione di modelli a oggetti del dominio o del business, consiste nell’utilizzare la navigabilità di una relazione di associazione per indicare il verso di lettura del nome dell’associazione. Ciò è dovuto al fatto che molti tool non prevedono la possibilità di visualizzare la freccia indicante il verso di lettura del nome delle associazioni. Chiaramente si tratta di un errore in quanto le informazioni connesse con la direzione dell’associazione hanno un notevole impatto semantico sull’implementazione del modello. In una relazione di associazione è un nonsenso avere entrambe le classi non navigabili, mentre la situazione opposta è generalmente evidenziata attraverso un segmento senza direzione. Nella fig. 7.23 sono mostrate due diverse situazioni. Nella prima, dato uno specifico User è possibile risalire alla relativa Password (nell’implementazione della classe User deve essere presente un membro di tipo Password), mentre data specifica istanza di Password non è possibile scoprirne il proprietario. La seconda relazione mostra il caso in cui dato un ordine è possibile navigare all’utente che lo ha compilato, mentre non è vero il contrario. In questo caso si è voluto mostrare come la direzione del nome dell’associazione non debba necessariamente concordare con il vincolo di navigabilità, sebbene possa avere senso riportare un nome coerente con la navigabilità. La freccia della relazione di associazione ha dunque diverse ripercussioni: definisce il senso della navigabilità, il che tende a ripercuotersi sia sull’organizzazione dell’interfaccia utente, sia sulla realizzazione in termini di componenti del sistema, ecc..

La presenza del vincolo di navigabilità in una relazione di associazione sancisce che da una istanza di una classe (per esempio Utente, fig. 7.23) non sia possibile navigare direttamente in quelle dell’altra classe associata (gli ordini compilati dall’utente). Però ciò non significa che non sia possibile in assoluto, bensì è possibile risalire comunque all’istanza associata, effettuando però un percorso più complesso e coinvolgendo istanze di diverse altre classi. Quindi il vincolo di navigabilità può avere notevoli ripercussioni sulle performance di alcuni servizi forniti dal sistema.

Considerate le varie problematiche, si potrebbe addivenire alla frettolosa conclusione che, sebbene sia una pratica assolutamente non auspicabile, è meglio non specificare vin-

41

UML e ingegneria del software: dalla teoria alla pratica

Figura 7.23 — Esempi di navigabilità.

has

User ... Password password = null ...

Associazione con vincolo di navigabilità Order

... User compiler = null ...

Password

navigabilità = false

compiles

User

navigabilità = true

coli di navigabilità. Purtroppo, questa regola semplicistica e non priva di effetti collaterali (come l’elevato accoppiamento tra le classi) non sempre è applicabile: qualora si utilizzino sistemi component based è necessario decidere accuratamente le navigabilità al fine di poter ripartire le classi in opportuni componenti.

Molteplicità Poiché una associazione è relazione strutturale tra gli oggetti istanze delle classi relazionate, è importante specificare, per ogni istanza di una classe, a quanti oggetti dell’altra può essere connessa e viceversa. Queste informazioni sono definite “molteplicità di un ruolo di associazione” o più semplicemente “molteplicità” (multiplicity). La definizione formale afferma che una molteplicità specifica l’elenco dei valori delle possibili cardinalità che un insieme può assumere. Per quanto concerne la notazione non c’è nulla da aggiungere rispetto a quanto visto in precedenza per gli attributi e i metodi: stringa di intervalli di interi separati da virgola. Anche le convenzioni rimangono inalterate: un carattere asterisco ( * ) posto come limite superiore denota un intervallo infinito ( * = 0..* ), qualora sia riportato un unico valore, ciò indica che l’intervallo è degenerato in un solo valore e quindi i limiti inferiori e superiore coincidono, ecc. Esempi di cardinalità sono: “ 0..1 ”, “ 1 ”, “ 0..* ”, “ * ”, “ 2, 4..6, 8..* ”. L’ultimo esempio evidenzia che la molteplicità, nel caso più generale, è data da una lista di intervalli e non da solo uno. Nei modelli richiesti nelle prime fasi del ciclo di vita del software (modello a oggetti del dominio e del business), le molteplicità sono molto importanti poiché concorrono a illustrare le regole del business del sistema reale oggetto di studio, mentre nel modello di

42

Capitolo 7. Gli oggetti: una questione di classe

Figura 7.24 — Esempi di molteplicità. Una ProcessingOrganisation gestisce diversi Book e ciascuno di essi appartiene a una sola ProcessingOrganisation. I Book sono costituiti da diversi Trade e ciascuno di questi appartiene a un preciso Book in funzione della valuta base. Currency 1 has base currency ProcessingOrganisation

owns 1 1..*

0..*

holds

Book 1

molteplicità Trade

0..*

molteplicità

disegno forniscono importanti informazioni di carattere implementativo. Per esempio, nel modello a oggetti del dominio di una banca (fig. 7.24), l’affermazione che un Book è basato su una sola valuta fornisce un’informazione molto importante: ogni ProcessingOrganisation (dipartimenti in cui una banca è suddivisa) deve possedere diversi Book in cui organizzare i Trade (almeno uno per ogni valuta considerata). In un modello di disegno l’affermazione precedente indica che nell’implementazione della classe Book è necessario un unico attributo per memorizzare il riferimento alla relativa istanza della classe Valuta. Se invece fosse stato possibile associare un Book a diversi oggetti Currency, allora sarebbe stato necessario conservare una lista di riferimenti.

Ruoli associazione Ogni qualvolta un’entità prende parte a un’organizzazione, recita un determinato ruolo; similmente, anche le classi, partecipando a una relazione, svolgono uno specifico ruolo. Questo è espresso per mezzo di una stringa il cui scopo è renderne esplicita la semantica. Qualora si decida di visualizzare il ruolo, lo si deve collocare nei pressi dello spazio in cui il segmento dell’associazione incontra la classe a cui si riferisce. La selezione di tale spazio non è casuale (si consideri la fig. 7.22): rappresenta la sede ideale di una particolare classe (AssociationEnd, terminale di associazione) del metamodello. Tale classe è utilizzata per contenere gli attributi atti a descrivere le proprietà che le classi possono manifestare partecipando a una determinata associazione. In altre parole, questi attributi non appartengono alla classe né tanto meno all’associazione, bensì a entrambe: l’associazione e la classe che vi partecipa. Quindi nel contesto di un’associazione si hanno tanti oggetti “terminali di associazione” quante sono le classi coinvolte. Per esempio in un’autoassociazione si hanno due terminali di associazione, così come in un’associazione binaria.

43

UML e ingegneria del software: dalla teoria alla pratica AssociationEnd è una classe del metamodello, pertanto, quando si realizzano i vari diagrammi delle classi, il suo utilizzo è del tutto trasparente. È interessante però sapere che quando si inserisce il nome di un ruolo, una molteplicità, ecc. non si fa altro che impostare gli attributi di un oggetto di questo tipo opportunamente istanziato dal tool di modellazione che si sta utilizzando. L’attributo ruolo è ereditato dalla classe genitore di tutti gli elementi (ModelElement), nella quale viene denominato, genericamente name e, tipicamente, possiede anche funzioni di identificatore: individua univocamente un terminale di associazione.

Tipicamente l’utilizzo del ruolo è alternativo al nome dell’associazione, nulla però vieta di specificarli entrambi. Nella pratica si utilizzano una serie di semplici regole pratiche atte a semplificare la selezione di quale alternativa utilizzare. In un’autoassociazione è preferibile specificare i ruoli poiché chiariscono il legame con cui una classe è associata con sé stessa (cfr. fig. 7.25). Nei diagrammi prodotti come risultato delle prime fasi di analisi dei requisiti è preferibile specificare i nomi delle associazioni poiché concorrono a definire le regole appartenenti al dominio che il sistema dovrà automatizzare. Un’eccezione si ha qualora sia necessario utilizzare l’OCL (Object Constraint Language) per specificare vincoli, invarianti, pre- e postcondizioni ecc. I nomi dei ruoli sono utilizzati per navigare le varie associazioni. Per esempio, nel caso in cui un’istanza della Classe A possa essere associata, al più, a una della Classe B, l’espressione A.rolename individua la particolare istanza della Classe B a cui

Figura 7.25 — Ruoli in una autoassociazione. Un dipendente è soggetto a un solo responsabile, mentre quest’ultimo gestisce diversi subordinati.

auto-associazione (self-association) lineManager

1

Employee

ruolo

* subordinate

44

Capitolo 7. Gli oggetti: una questione di classe

Figura 7.26 — Utilizzo dei ruoli per rappresentare la navigabilità in OCL. La porzione di modello mostrata è stata volutamente semplificata al fine di non introdurre elementi (classi associazione) non ancora illustrati. In particolare si può notare come un progetto impieghi diversi dipendenti e un unico manager. Qualora un dipendente sia allocato a un progetto non può lavorare contemporaneamente ad altri. Chiaramente ci sono dipendenti le cui funzioni non sono relative ai progetti e quindi non sono allocati a nessuno.

1

Project

0..1

1 -manager 1..* -employees

-subordinate *

Employee

1 -lineManager 1..* 1 -jobTitle

Job

l’oggetto A è legato. Nella fig. 7.26, data un’istanza di progetto, per specificare il relativo manager è necessario specificare la navigabilità project.manager. Nel caso in cui la molteplicità sia “a molti”, allora la navigabilità è ancora valida, però è necessario utilizzare funzioni dell’OCL più complesse. Dato il diagramma di fig. 7.26 è possibile asserire il seguente vincolo: Project.manager.jobTitle.description = #Manager

Un manager di un progetto è un dipendente il cui titolo è appunto quello di manager (si assume che la classe Job abbia un attributo description). Sempre con riferimento al diagramma di fig. 7.26, è possibile definire la funzione che permette di calcolare il costo annuale di un progetto relativo all’impiego delle risorse umane. Project.getAnnualEmployeesCost() = Project.manager->getAnnualIncome() + Project.employees->getAnnualIncome()->sum()

I ruoli dell’associazione sono poi preferiti nel modello di disegno in quanto specificano l’attributo utilizzato per realizzare la relazione. Per esempio, nel caso mostrato nel frammento di fig. 7.26, l’implementazione della classe Employee disporrebbe di un attributo membro jobTitle:Job. Ancora, nel caso in cui fosse necessario memorizzare tutti i dipendenti assegnati a uno specifico progetto, la relativa classe (Project) disporrebbe di un attributo membro privato (di tipo vettore o qualsiasi collezione equivalente) denominato employees, i cui elementi sono istanze della classe Employee.

Ordinamento Nel caso in cui due, o più, classi siano associate per mezzo di una relazione con molteplicità diversa da 1 a 1, ossia almeno un’istanza di una classe possa essere connessa con un

45

UML e ingegneria del software: dalla teoria alla pratica

opportuno insieme di oggetti istanze dell’altra, può verificarsi il caso in cui sia necessario ordinare, secondo un preciso criterio, tali relazioni. Qualora ciò avvenga, la condizione è evidenziata riportando la parola chiave ordered , racchiusa da parentesi graffe, ( {ordered} ) nell’opportuno terminale di associazione. Un esempio è l’associazione binaria tra User e Password mostrata in fig. 7.23: potrebbe essere necessario dover archiviare diverse parole chiavi utilizzate da un utente, magari perché la politica di sicurezza prevede che uno stesso utente non possa riutilizzare le precedenti n password esercitate in passato. In tal caso, tra l’altro, sarebbe opportuno inserire nella classe Password due attributi atti a tenere traccia del periodo di utilizzo della relativa parola chiave e quindi utilizzare il limite inferiore come criterio di ordinamento: la più recente nella prima posizione, la seconda più recente in seconda e così via. Un altro esempio è dato dalla definizione formale del tipo enumerato (Enumeration), in tal caso l’ordine dei relativi valori di cui è composto assume un’importanza fondamentale in quanto è utilizzato come criterio di mapping (cfr. fig. 7.27). Il segmento della relazione dotato del diamante pieno rappresenta una particolare associazione illustrata successivamente. La definizione nel metamodello UML è simile: le classi però non dispongono di nessun attributo poiché lo ereditano dalla classe ModelElement, dalla quale discendono tutte le altre. Per default l’insieme delle relazioni non è ordinato e quindi non è necessario specificare alcuna parola chiave. Eventualmente è possibile utilizzare altri valori definiti dal modellatore per indicare determinate condizioni, come per esempio che l’insieme delle relazioni sono riordinate {sorted}.

Da notare che sia l’informazione relativa all’ordinamento, sia quella attinente alla modificabilità sono utilizzate molto raramente. Tipicamente trovano applicazione nei modelli a maggiore grado di formalità, come per esempio il metamodello UML. Il problema è sempre lo stesso: pochissimi tecnici ne conoscono la semantica e sanno interpretare correttamente questi ornamenti. Pertanto è sempre cosa buona illustrarne le implicazioni attraverso ulteriore formalismo (linguaggio naturale) e quindi l’importanza di specificarle direttamente nel modello passa in secondo piano.

Figura 7.27 — Definizione del tipo enumerato.

Enumeration -enumeration : string

enumeration 1

literal

EnumerationLiteral

1..* -enumerationLiteral : string {ordered}

46

Capitolo 7. Gli oggetti: una questione di classe

Modificabilità Un altro insieme di vincoli che è possibile specificare in un’associazione è relativo alla modificabilità (changeability) dei legami che le istanze delle classi coinvolte nell’associazione instaurano. Tali legami rappresentano la realizzazione dell’associazione stessa. Il particolare vincolo è associabile a ogni lato dell’associazione e quindi, nel metamodello UML, è rappresentato dall’apposito attributo (changeability ) presente nella solita classe AssociationEnd (cfr. fig. 7.22). I valori ammessi sono tre: • changeable (modificabile): si tratta del valore di default; semplicemente rappresenta la condizione in cui non sussista alcun vincolo e quindi le relazioni possono essere modificate liberamente. • frozen (congelato): questo vincolo specifica che, dopo la creazione dell’oggetto, nessuna relazione può essere aggiunta per mezzo dell’esecuzione di un’operazione nella classe sorgente della relazione. A meno di diverse indicazioni, questo vincolo non ha alcun effetto sulla classe destinataria che quindi può aggiungere liberamente altre relazioni; • addOnly (sola aggiunta): la presenza di questo vincolo sancisce che possono essere aggiunte relazioni ogniqualvolta se ne abbia la necessità, ma, una volta create, queste non possono essere più rimosse eseguendo un’operazione nella classe sorgente. Come nel caso precedente, il vicolo di per sé non ha alcun effetto sulla classe destinazione.

Campo d’azione destinatario L’illustrazione dell’elenco delle caratteristiche specificabili in un’associazione si esaurisce con il campo d’azione delle associazioni. In modo del tutto analogo a quanto sancito per gli attributi e i metodi, anche alle associazioni (che in ultima analisi non sono altro che particolari attributi) è possibile specificare il campo d’azione (scope). Le alternative previste sono due: istanza (instance) e classificatore (classifier). Nel primo caso, quello di default, si ha che ogni istanza della classe sorgente è associata, in funzione della molteplicità, con una o più istanze della classe destinazione. Il valore classifier (associazione statica) implica una relazione tra un’istanza della classe destinazione e il classificatore sorgente (non un oggetto). Ciò è comprensibile se si considera che la relazione non deve essere memorizzata in ogni istanza della classe, quindi appartiene al classificatore stesso ed esiste prima della creazione di ogni suo oggetto. In sostanza si tratta di un’associazione che viene stabilita a tempo di “disegno” e non può più variare nel tempo. Chiaramente il secondo caso non rappresenta una situazione molto ricorrente, sebbene in particolari circostanze (come per esempio il pattern Singleton) sia molto utile. In linea

47

UML e ingegneria del software: dalla teoria alla pratica

Figura 7.28 — Esempio di relazione xor.

personalAccount

Account corporationAccount

personOwner

Customer

{xor} corporationOwner

Corporation

di massima, non si tratterebbe di una buona pratica OO in quanto prevede la conoscenza di informazioni a carattere globale.

Associazione xor Una associazione xor (xor-association) rappresenta una situazione in cui, in un determinato istante di tempo per una specifica istanza della classe, può verificarsi solo una delle potenziali associazioni che una stessa classe può instaurare con altre. L’associazione xor viene rappresentata graficamente tramite un segmento che unisce le associazioni (due o più) soggette al vincolo, con evidenziata la stringa {xor} (cfr. fig. 7.28). Chiaramente, le diverse associazioni devono condividere la medesima classe sorgente. Qualora si utilizzi la relazione xor, bisogna porre attenzione ad alcune logiche conseguenze. In primo luogo la molteplicità minima delle classi destinatarie delle relazioni soggette al vincolo xor deve essere necessariamente zero. In altre parole deve essere prevista la possibilità che le relative istanze possano non essere coinvolte nella relazione. Poi, i nomi dei ruoli delle classi di cui sopra devono essere distinti per poter, eventualmente, esprimere la navigabilità. Si tratta di una relazione di importanza molto relativa (è sempre possibile specificare il vincolo per mezzo del linguaggio naturale o di opportune clausole OCL) che però risulta particolarmente efficace ed elegante grazie al grande valore di sintesi offerto dalla parola xor. Lo stesso metamodello UML ne fa uso in diversi diagrammi.

Associazione o dipendenza? Spesso alcuni tecnici tendono a confondere quando utilizzare le relazioni di dipendenza e quando quelle di associazione. Questa confusione purtroppo è abbastanza frequente ed è riscontrabile anche dall’analisi di diagrammi riportati su siti molto importanti.

48

Capitolo 7. Gli oggetti: una questione di classe

Figura 7.29 — Esempio dell’utilizzo della association class tra l’azienda e i dipendenti.

Company

Employs 1

1..*

Job -salary -dateOfEmployment

Employee

JobType *

1 -code -description

Per esempio è possibile osservare classi client collegate a classi Singleton attraverso una relazione di associazione. Ciò è particolarmente sbagliato specie se si pensa che uno dei vantaggi nel ricorrere al pattern Singleton consiste nel permettere alle varie classi di ottenere un riferimento a quella Singleton ogniqualvolta se ne abbia bisogno, senza aver bisogno di mantenere il riferimento stesso. Molto spesso la regola semplice e grossolana utilizzata è che se una relazione è comune a tutta la classe (il relativo attributo è specificato come privato alla classe), allora rappresenta un’associazione; altrimenti siamo in presenza di una dipendenza. Questa regola molto semplicistica trascura il fatto che spesso alcune variabili, per questioni esclusivamente di carattere implementativo, vengono dichiarate una sola volta e riutilizzate in tutta la classe.

Qualora si abbiano dei dubbi circa quale relazione utilizzare, è necessario ricordare che la differenza sostanziale consiste nel fatto che mentre una relazione di associazione è una relazione strutturale (quindi “persistente”), che evidenzia classi semanticamente correlate, la relazione di dipendenza ha un carattere transitorio (al netto di ottimizzazioni), un legame che si instaura (o almeno così dovrebbe essere) temporaneamente, per il lasso di tempo necessario per fruire di un servizio, per creare un oggetto, ecc., per poi perdere di significato.

Classe associazione In un’associazione tra classi spesso si verifica la situazione in cui la relazione stessa possieda proprietà strutturali e comportamentali (attributi, operazioni e riferimenti ad altre classi). In tali circostanze è possibile ricorrere all’utilizzo della classe associazione (association

49

UML e ingegneria del software: dalla teoria alla pratica

class) che, come suggerito dal nome, possiede contemporaneamente proprietà di classe e di associazione. L’esempio che generalmente è presentato è relativo alle mansioni che un dipendente espleta in un’azienda. Un’organizzazione che impiega diverse persone viene modellata attraverso le classi Company e Person, legate dalla relazione employees (fig. 7.29). Tipicamente però non è sufficiente sapere quante e quali persone lavorino per una data azienda, ma è anche importante disporre di ulteriori informazioni, quali per esempio, il ruolo svolto dal dipendente, l’anno di assunzione, ecc. Queste proprietà non appartengono né alla classe Company, né a quella Person, bensì, al legame tra le due. Ecco quindi che la relazione necessita di ulteriori proprietà. Una association class è rappresentata graficamente attraverso il normale formalismo previsto per le classi con, in aggiunta, un segmento tratteggiato di connessione con la relazione che la origina. Il punto di congiunzione (per non far dispiacere a nessuna delle due classi relazionate), dovrebbe appartenere a un intorno del centro del segmento rappresentante l’associazione. Nel diagramma in fig. 7.30 è mostrato un frammento del modello utilizzato per rappresentare le “entità legali” coinvolte nel business bancario. Una stessa LegalEntity può “recitare” diversi ruoli e, per ciascuno di essi, è necessario disporre dei dati relativi ai riferimenti interni all’organizzazione. In altre parole, la situazione generale è che, ogni LegalEntity dispone di un riferimento diverso per ciascun ruolo esercitato (per esempio

Figura 7.30 — Esempio di utilizzo della relazione di associazione.

LegalEntity -id : string -shortName : string -longName : string -isCorporate : boolean ...

Contact

1

plays 1..n

LegalEntityRole

-id : string -mainReferenceName : string -tel : string -fax : boolean -eMail : string -swift : string -telex : string -postcode : string ...

-roleId : string -roleDescription : string ...

Agent

Broker

CalculationAgent

CounterParty

IPA

Investor

ProcessingOrganisation

Trustee

50

Capitolo 7. Gli oggetti: una questione di classe

il sig. Tizio è il riferimento della Legal Entity X nel ruolo di Agent, il sig. Caio è il riferimento della stessa Legal Entity X ma questa volta per il ruolo di Processing Organisation, ecc.). Quindi si ha il caso di una relazione con attributi e dunque la relazione di associazione. Nel ricorso alla classe associazione è necessario tenere bene a mente un vincolo implicito, spesso sottovalutato, di una certa importanza: per ogni singola associazione esiste una e una sola istanza della classe associazione. Si riconsideri l’esempio dell’azienda e dei relativi dipendenti: come si può notare il modello presenta un’anomalia. La condizione in cui uno stesso dipendente interrompa il rapporto di dipendenza con un’azienda per poi riprenderlo in un secondo momento non è gestibile con tale versione del modello. Eventualmente, si potrebbe inserire una nuova istanza della classe Employee, con esattamente gli stessi dati, ma ciò determinerebbe la perdita della storia della collaborazione: le diverse istanze Employee relative allo stesso soggetto non sarebbero in alcun modo relazionate. Si consideri i problemi che ciò potrebbe generare, dovendo per esempio fornire dettagli relativi ai contributi pensionistici e similari. Il modello, per poter annoverare anche il caso predetto, dovrebbe quindi essere modificato come riportato nel frammento di diagramma di fig. 7.31. Quindi, prima di utilizzare la classe associazione si tenga ben in mente il vincolo implicitamente inserito: una sola istanza dell’association class per ciascuna associazione. Analizzando attentamente il modello di fig. 7.32 si può notare come l’utilizzo del meccanismo della eredità multipla tenda facilmente a generare il “problema del diamante” di cui si è parlato nel Capitolo 6.

Figura 7.31 — Esempio del modello Company, Employee senza l’utilizzo della classe associazione. Company

Employs 1

1..*

1

Employee 1

Job *

-salary -dateOfEmployment -dateOfEndEmployment * 1

JobType -code -description

*

51

UML e ingegneria del software: dalla teoria alla pratica

Figura 7.32 — Porzione del metamodello UML relativo all’elemento AssociationClass del package Core – Relationship. Esso mostra come l’elemento AssociationClass sia definito attraverso una eredità multipla. Ciò gli consente di avere caratteristiche sia di classe (attributi, operazioni, relazioni, ecc.) sia di relazione.

GeneralizableElement

Classifier

-partecipant 1 -specification 1

-association *

ModelElement

AssociationEnd

isNavigable : boolean -specifiedEnd ordering : OrderingKind * aggregation : AggregationKind ...

{ordered} connection

Association

2..*

1

0..1 *

Attribute

Class - isActive : boolean

initialValue : Expression

AssociationClass

Figura 7.33 — Classe associazione per due associazioni.

ClassB

ClassA

AssociationClass1

AssociationClassAB

-c1 -c2 ...

ClassC

ClassA

AssociationClassCD

ClassD

ClassC

ClassB

AssociationClass1 -c1 -c2 ...

ClassD

52

Capitolo 7. Gli oggetti: una questione di classe

Poiché la classe associazione è a tutti gli effetti una relazione, ne seguono interessanti constatazioni. Per esempio, anche se talune volte si potrebbe avere la necessità di collegare una classe associazione a più relazioni, ciò non è lecito. Sarebbe come voler collegare più associazioni tra loro. In queste circostanze, la soluzione è abbastanza semplice: si definisce un’ulteriore classe che contenga il comportamento comune e quindi si legano le varie association class a tale classe, come mostrato nella fig. 7.33. Anche sull’utilizzo della relazione di associazione esistono diverse correnti di pensiero. C’è chi non la vorrebbe utilizzare giacché, dal punto di vista dell’implementazione, si tratta di un concetto non direttamente codificabile, c’è chi invece la ritiene assolutamente indispensabile. Con approccio assolutamente pragmatico, l’autore crede che sia opportuno avvalersi dell’association class per tutte le tipologie di modelli che precedono quello di disegno (dominio, business, analisi, …), qualora sussistano le condizioni. Ciò semplicemente perché permette di realizzare modelli più rispondenti alla realtà. Nei modelli di disegno, però, probabilmente, è preferibile utilizzare meccanismi alternativi, in quanto altrimenti si genererebbe un gap di astrazione rispetto alla codifica non sempre desiderabile.

Aggregazione e composizione Un’associazione tra classi mostra una relazione strutturale paritetica (la famosa peer to peer), per cui tra le classi coinvolte non è possibile distinguerne una concettualmente più importante delle altre: sono tutte allo stesso livello. Spesso però è necessario modellare situazioni opposte in cui una classe, in una determinata associazione, esprime una nozione concettualmente più grande delle altre che la costituiscono. In altre parole è necessario utilizzare relazioni del tipo “tutto–parte” (whole–part), in cui esiste una classe che rappresenta il concetto “più grande” (il tutto) costituito dalle restanti che rappresentano i concetti più piccoli (le parti). Questi tipi di relazioni sono detti di aggregazione e, in particolari circostanze, diventano composizioni. Chiaramente una stessa classe può essere la classe aggregata (il “tutto”) di una specifica relazione e contemporaneamente essere parte componente di un’altra, così come una stessa classe può rappresentare la classe aggregata in diverse relazioni whole–part. Discorso analogo vale anche per le classi rappresentanti le parti. Un esempio molto inflazionato di relazione di aggregazione è quello dell’autovettura. In questo caso, la classe Automobile è la classe aggregata costituita dagli elementi Motore, Ruote, Volante, e così via. Questo è uno dei tipici casi in cui l’autore comprende perché gli informatici siano tacciati a volte di essere un po’ tediosi. Graficamente un’aggregazione è visualizzata con un rombo vuoto dalla parte della classe aggregata (whole). Nel caso in cui l’aggregazione sia una composizione, allora il rombo è disegnato colorato al proprio interno. Un esempio un po’ meno noioso di relazione di aggregazione è costituito dalla CurrencyPair (coppia di valute) ossia il prodotto utilizzato per effettuare lo scambio di valute (Foreign eXchange). Si tratta del prodotto finanziario che, quotidianamente, muove

53

UML e ingegneria del software: dalla teoria alla pratica

Figura 7.34 — Esempio di relazione di aggregazione: CurrencyPair.

Currency

CurrencyPair -fixedRate : boolean -spotDays : integer ...

*

*

-primaryCurrency 1 -isoCode : string -name : string -secondaryCurrency -symbol : string -decimal : integer 1 -spotDays : integer ...

un giro di affari dell’ordine dei 900 miliardi di dollari americani, di cui circa il 95% è generato da fini puramente speculativi, mentre il restante è dovuto a scambi commerciali tra nazioni utilizzanti valute diverse (da cui il nome del trade: FX, Foreign eXchange) illustrato in fig. 7.34. Un altro esempio è quello relativo a un biglietto aereo. In particolare, un biglietto (Ticket) tipicamente prevede diverse tratte (Journey) ognuna delle quali è costituita da un determinato aeroporto di partenza e da uno di arrivo (cfr. fig. 7.35). La relazione di aggregazione (e quindi anche quella di composizione) è effettivamente una particolare versione di quella di associazione visualizzata “ornando” l’associazione stessa per mezzo di un rombo nella parte della classe aggregata. La differenza tra l’associazione e l’aggregazione è puramente concettuale, tanto che spesso la decisione su quale utilizzare è veramente una questione di gusti. In ultima analisi un’aggregazione è un’associazione con semantica più forte: la classe aggregata è data delle parti componenti. Un vincolo del tutto logico implicito nella relazione di aggregazione è relativo alla totale mancanza di validità di associazioni di aggregazione circolari. In altre parole non ha senso dire che una Classe A sia composta da una Classe B, composta da una Classe C a sua volta composta dalla Classe A. Anche dal punto di vista implementativo, non esiste grossa differenza tra una relazione di associazione e una di aggregazione; quest’ultima viene utilizzata per enfatizzare relazioni a semantica più forte. Per quanto concerne l’associazione di composizione, è necessario effettuare un discorso diverso. In questo caso si ha una forma di aggregazione con una forte connotazione di possesso e una (quasi) coincidenza del ciclo di vita tra istanze delle classi “parte” e quella istanza della classe “tutto” (classe composta). Più precisamente, le parti possono essere generate anche in un tempo successivo alla creazione dell’istanza della classe composta, ma, una volta generate, queste vivono e sono distrutte con l’istanza della classe composta di appartenenza. Tipicamente, avviene che la classe composta si occupa di eliminare le istanze proprie parti in un momento antecedente alla propria distruzione.

54

Capitolo 7. Gli oggetti: una questione di classe

Mentre la relazione di aggregazione rappresenta una relazione di associazione con un significato più forte, ma senza alcuna implicazione sulla navigabilità o sull’esistenza delle parti componenti, con la relazione di composizione le cose cambiano. Per esempio, in ogni istante di tempo un oggetto può essere “parte” esclusivamente di una composizione. Ciò non preclude alle istanze di una classe di partecipare a diverse relazioni di composizione in differenti istanti di tempo. Questo vincolo spiega il motivo per cui le relazioni che legano le Currency alle CurrencyPair sono legittimamente aggregazioni e non composizioni (una stessa valuta può partecipare a diversi prodotti di scambio di valute: USDEUR, USDGBP, GBPEUR ecc.), così come avviene per gli aeroporti nell’esempio di fig. 7.35. Pertanto, in una relazione di composizione, molteplicità con cui le classi componenti sono associate alla relativa classe composta diverse da 1 o 0..1 potrebbero non avere molto senso. Nell’implementazione di una classe composta, è necessario ricordare che essa ha la responsabilità di creare e distruggere le proprie parti, e quindi deve essere disegnata considerando appositi metodi. Questo concetto è più evidente in linguaggi (per esempio il C++) in cui è necessario allocare e disallocare esplicitamente la memoria dei relativi oggetti; in tal caso la classe composta deve occuparsi di eseguire anche queste funzioni. In tali contesti è più evidente la necessità di visualizzare esplicitamente una relazione di composizione: rende più difficile commettere errori con la gestione della memoria. In linguaggi come Java, in cui è sufficiente dereferenziare un oggetto per consegnarlo alle amorevoli cure del Garbage Collector, la responsabilità di distruggere un oggetto ha un significato meno rilevante. Si consideri il diagramma di fig. 7.36. Esso fa riferimento a un politica tipica dei sistemi di sicurezza atta a impedire che un utente possa riutilizzare la stessa password prima che ne abbia variate un certo numero (per esempio 6). In altre parole un utente è autorizzato a “riciclare” una stessa password solo dopo averne utilizzate almeno altre 6 differenti. Quando un oggetto composto viene generato, tipicamente, si deve incaricare di creare le istanze delle sue parti e di associarle correttamente a sé stesso. Quando poi viene distrutto, esso ha la responsabilità di distruggere tutte le parti: ciò però non significa che necessariamente debba farlo direttamente. Il frammento del modello di fig. 7.37 mostra la struttura gerarchica di un DataSet SQL, relativo allo standard SQL99. Il concetto di Cluster coincide (abbastanza) con quello di un’installazione di server RDBMS (Relational DataBase Management System, sistema di gestione di database relazionali), ed è costituito da un insieme di cataloghi. Questi, nei RDBMS più comuni (Oracle, SQLServer, ecc.), sono comunemente denominati istanze. Sebbene le direttive ANSI standard prevedano che i servizi di sicurezza (utenti abilitati all’accesso dei dati, elenco dei dati visibili, ecc.) siano applicati al livello di Cluster, la quasi totalità di RDBMS commerciali li rendono disponibili a livello di catalogo. Il passo successivo consiste nel considerare il livello di database denominato, sempre secondo direttive standard, Schema. Questo raggruppa un insieme di oggetti come tabelle,

55

UML e ingegneria del software: dalla teoria alla pratica

Figura 7.35 — Porzione di modello relativo a un biglietto aereo. Le istanze della classe Airport, che recita il ruolo di parte in due aggregazioni, in entrambi i casi, possono essere associate a diverse istanze della classe aggregata Journey. departs has a

Ticket

1

*

Journey

*

1

arrives

Airport

1

*

Figura 7.36 — Frammento di modello relativo alla relazione tra gli utenti del sistema e le relative password.

User

has a 1

1

PasswordHistory

consist of 1

1..6

Password

Figura 7.37 — Frammento della rappresentazione gerarchica di un DataSet SQL. Cluster 1..n Catalog 1..n Schema 1..n Object

DataObject

Table

1..n

Module

View

Column

Routine

56

Capitolo 7. Gli oggetti: una questione di classe

viste, moduli, routine, stored procedure, ecc. Gli oggetti di tipo data (DataObject), sono costituiti da colonne (attributi delle tabelle). Il diagramma in questione si presta a essere ulteriormente dettagliato. Per esempio si potrebbero considerare i domini e le regole delle colonne, così come lo schema informativo (INFORMATION_SCHEMA), ossia metadata relativi a tutti gli oggetti memorizzati in un catalogo, ma l’obiettivo dell’esempio è mostrare l’utilizzo dello UML e non spiegare il funzionamento dei database relazionali. Il diagramma di fig. 7.38 mostra una versione semplificata di alcuni componenti utilizzabili per la realizzazione della GUI (Graphic User Interface, interfaccia utente grafica) di applicativi realizzati in linguaggio Java. In particolare si fa riferimento ai componenti Frame e MenuBar del package java.awt. La classe Frame rappresenta una finestra generale utilizzabile per costruire apposite interfacce utente. A tal fine specializza la classe Window, aggiungendo caratteristiche supplementari, come la possibilità di avere una barra di menu, un’icona, un bordo, ecc. Un oggetto di tipo Frame può eventualmente essere disegnato secondo specifiche direttive fornite da un opportuno oggetto istanza di una classe specializzante: quella astratta GraphicsConfigurator. Il disegno “ridefinito” di oggetti “grafici” è utile qualora il dispositivo di destinazione non sia il classico monitor. Un oggetto Frame può eventualmente disporre di un’icona grafica che lo rappresenti (caratteristica non prevista dalla classe Window). Questa deve essere specificata attraverso un’istanza di una classe specializzante: quella base Image. Gli oggetti di tipo Frame sono poi in grado di processare eventi generati dall’utente e, a patto che dispongano della Figura 7.38 — MenuBar nella libreria java.awt. FramePeer

Window

MenuContainer

MenuComponent

(java.awt)

(java.awt)

(java.awt)

+getFont() : Font +postEvent(event : Event) : boolean +remove(comp : MenuComponet)

(java.awt.peer)

Frame (java.awt)

+Frame() +Frame(gc : GraphicConfiguration) +addNotify() +getIconImage() : Image +getMenyBar() : MenuBar +getState() : int +getTitle() : String +isResizable() : boolean +setIconImage(image : Image) +setMenuBar(mb : MenuBar) +setTitle(title : String) .. .

GraphicConfiguration (java.awt)

MenuBar (java.awt)

+MenuBar() +addNotify() ~menuBar +removeNotify() 0..1 +getHelpMenu() : Menu +setHelpMenu(m : Menu) +add(m : Menu) .. .

~menus

~helpMenu

0..*

0..1

Menu (java.awt) -icon

0..1

~items

MenuShortcut (java.awt)

~shortcut 0..1

0..*

Image

KeyEvent

MenuItem

(java.awt)

(java.awt.event)

(java.awt)

~actionListener 0..1

ActionListener (java.awt.event)

UML e ingegneria del software: dalla teoria alla pratica

57

barra dei menu, anche quelli provenienti dalla tastiera. In tal caso, a ogni pressione di un tasto è necessario verificare se questo corrisponda o meno a uno di scelta rapida associato al menu. Pertanto, la gestione dell’evento è demandata alla barra del menu. Ogni Frame può disporre al massimo di una barra del menu, e a tal fine implementa l’interfaccia MenuContainer. La barra del menu è poi costituita da due diverse tipologie di menu: help e menu classici. Del primo tipo ne può esistere una sola istanza, mentre dei secondi è possibile specificarne una lista (l’elenco delle voci visualizzate direttamente nella barra). La classe Menu estende MenuItem (ossia gli elementi che lo costituiscono) in quanto si tratta di un elemento particolare di un item in grado di ospitare altri menu: una voce che richiama un menu. Da un’attenta analisi si può notare che le classi Menu e MenuItem, rappresentano una forma collassata del pattern Composite, in cui la classe base è collassata in quella genitore (MenuItem). Ciò permette di ottenere menu annidati n volte (composti da altri menu, composti da altri menu e così via). Ogni menu può prevedere un MenuShortcut, ossia un oggetto che dichiara il relativo tasto di scelta rapida. Pertanto, quando un utente preme un tasto in un oggetto Frame, si verifica l’esistenza di un oggetto MenuShortcut corrispondente. In caso affermativo viene generato un evento di notifica (ActionEvent). Analizzando il modello, potrebbero scaturire diversi interrogativi. In primo luogo, ci si potrebbe chiedere se gli oggetti Frame utilizzino la barra dei menu — nel qual caso sarebbe stato opportuno utilizzare un’associazione semplice — oppure ne siano costituiti. La differenza è molto sottile ed entrambe le scelte sarebbero valide. L’autore ha scelto la seconda alternativa. Un’altra domanda sulla quale ci si potrebbe interrogare è la tipologia delle relazioni che legano la classe MenuBar a quella Menu. Si tratta di un’aggregazione o di una composizione? In effetti per molti versi potrebbe essere una composizione (almeno l’aggregazione menus), d’altronde che senso avrebbe una MenuBar senza Menu? Però, da come è stato codificato, sembrerebbe che la classe MenuBar non abbia alcuna responsabilità sulla costruzione e distruzione dei propri menu i quali, eventualmente, potrebbero essere utilizzati contemporaneamente da altri oggetti. Per questo motivo si è ritenuto più opportuno ricorrere a una relazione di aggregazione. L’immagine di fig. 7.39 rappresenta un frammento di un modello a oggetti di un sito per il commercio elettronico. In particolare è rappresentata una situazione in cui il carrello della spesa (Trolley) è composto da una serie di “righe” (ItemLine) ognuna delle quali è costituita da un apposito prodotto (Item). In particolare le righe specificano il prodotto selezionato, la quantità, il prezzo, ecc. Ora, un utente, in un qualsiasi momento all’interno del periodo di validità del carrello, può decidere di acquistare alcuni prodotti precedentemente accantonati nel carrello della spesa. Ecco che quindi tale contenuto (o una sua parte) è incorporato in un apposito ordine (Order). Ora, mentre è lecito attendersi una relazione di aggregazione tra Item e ItemLine, uno stessa istanza della classe Item potrebbe essere contemporaneamente parte di molti oggetti ItemLine la presenza della relazione di composizione tra le classi Trolley e ItemLine, e Order e ItemLine potrebbe destare qualche perplessità insita nel fatto che una stessa classe (ItemLine)

58

Capitolo 7. Gli oggetti: una questione di classe

Figura 7.39 — Carrello della spesa e ordine. - items

Trolley 0..1

- item

ItemLine

0..n

0..n - items

Item

1

0..n

Order 0..1

reciterebbe il ruolo di parte in più relazioni di composizione. Da un’analisi più attenta si scopre che proprio la presenza della relazione di composizione va a specificare un funzionamento molto preciso: poiché, in una relazione di composizione, una stessa istanza della classe parte non può appartenere contemporaneamente a più classi composte; ne consegue che, per rispettare tale vincolo, quando una riga del carrello della spesa viene selezionata per essere acquistata, il relativo oggetto (ItemLine) deve essere “staccato” dall’oggetto carrello ed essere attaccato a quello ordine. Per molti autori, ogniqualvolta si utilizza una relazione di composizione, in qualche modo è possibile pensare alle classi rappresentanti le parti della relazione come private della classe composta. Poiché il vincolo implicito nella relazione di composizione è così forte, le classi componenti sono paragonate agli attributi della stessa classe composta. Per questi motivi è possibile visualizzare relazioni di composizione attraverso una rappresentazione grafica alternativa, come quella mostrata in fig. 7.40. L’autore del libro non è completamente d’accordo con il paragone, o meglio, la maggior parte delle volte, una classe parte di una relazione di composizione è veramente simile a un attributo privato della classe composta, ma, in altri casi tale similitudine si rivela

Figura 7.40 — Rappresentazioni alternative della relazione di composizione. Window Classe composta 2

Window

scrollbar : Slider

1

Classi "parte"

1

1

1

molteplicità

title : Header 1

body : Panel

scrollbar

Slider

2

title 1

Header

body

1

Panel

59

UML e ingegneria del software: dalla teoria alla pratica

molto sfumata. Tale considerazione trova giustificazione nel fatto che il vincolo della composizione asserisce che un’istanza di una classe composta, in ogni istante di tempo, può essere parte di una sola composizione. Logica conseguenza è che il vincolo non si applica alla classe stessa bensì alle relative istanze, quindi una classe parte di una composizione potrebbe partecipare a diverse relazioni eventualmente anche di composizione.

Qualificazione o associazione qualificata Nella realizzazione di un modello, spesso capita di dover dar luogo a relazioni di associazione particolari, in cui esiste il problema della selezione (lookup). In altre parole, in una relazione 1 a n (o n a n) tra una Classe A e una B, fissato un determinato oggetto istanza della classe A, è necessario specificare un criterio in grado di individuare un preciso oggetto o un sottoinsieme di quelli associati, istanze dell’altra classe (B). Tali circostanze si prestano a essere modellate attraverso l’associazione qualificata (qualification), il cui nome è dovuto all’attributo, o alla lista di attributi, detti qualificatori i cui valori permettono di partizionare l’insieme delle istanze associate a quella di partenza. La Classe A viene comunemente definita classe qualificata, mentre quella B è detta classe obiettivo. In un contesto implementativo, ai qualificatori si fa comunemente riferimento con il nome di indici. Si tratta di attributi che, tipicamente, appartengono alla relazione stessa e, meno frequentemente, alla classe oggetto della selezione. Nel primo caso, i valori sono specificati dalla classe che gestisce la generazione di nuovi link. Un esempio molto semplice è costituito da un vettore. In questo caso l’indice rappresenta il qualificatore, gli elementi del vettore sono gli oggetti obiettivo (target object), mentre la classe in cui il vettore è definito rappresenta la classe qualificata. Dal punto di vista grafico, l’associazione qualificata viene mostrata per mezzo del classico segmento congiungente le due classi, con aggiunto un rettangolo dedicato alla specificazione dei qualificatori. Da tener presente che tale rettangolo appartiene alla relazione e va posto nelle prossimità della classe qualificata. Pertanto è in grado di individuare un

Figura 7.41 — Esempi di utilizzo dell’associazione qualificata. Bank

Chessboard

Aircraft

accountNumber : string

rank : int file : int 1

pos : char row : int 1

*

deals with

consist of 0..1

Customer

has 1

Square

1

Seat

60

Capitolo 7. Gli oggetti: una questione di classe

sottoinsieme (eventualmente costituito da un solo elemento) degli elementi della classe destinazione associata, quest’ultima attraverso i valori dei qualificatori. Il qualificatore è un attributo opzionale di ogni fine associazione binaria. Nelle relazioni n-arie (relazioni che coinvolgono più di due classi) non viene utilizzato: la comprensione del significato sarebbe una vera e propria impresa. Mentre in ogni associazione binaria, teoricamente, se ne potrebbero specificare due (si tratterebbe di un caso assolutamente raro), uno a ogni capo della relazione, a patto che la stessa relazione sia di tipo molti a molti. Si considerino gli esempi classici riportati in fig. 7.41, relativi a un account bancario, una scacchiera e i posti a sedere su un volo aereo. Per comprendere gli esempi in fig. 7.41 è necessario fornire qualche dettaglio relativo al significato della molteplicità che, in questo contesto assume un’accezione leggermente diversa da quella classica. In particolare, si possono avere i seguenti casi: 0..1 indica che l’utilizzo del qualificatore (qualsiasi ne siano i valori), tipicamente, per-

mette di identificare una sola istanza nella classe di destinazione. Lo zero invece evidenzia la possibilità che nessun oggetto possa scaturire dalla selezione operata per mezzo del criterio specificato dal qualificatore. Chiaramente la molteplicità 1 rappresenta la casistica più frequente. 1 questo è un sottocaso del precedente, nel quale non esiste l’eventualità che nessun

elemento possa venir selezionato. Ciò implica che il dominio dei qualificatori sia un insieme ben definito e chiuso; * indica che i valori dei qualificatori permettono, tipicamente, di individuare un insieme di istanze del destinatario. Questa casistica non sempre aggiunge molto significato in quanto la molteplicità resta sempre “molti”, indipendentemente dalla presenza del qualificatore. L’eventuale utilizzo potrebbe essere evidenziare una chiave (eventualmente composta) in grado di navigare nelle relazioni con gli oggetti obiettivo, oppure sottolineare il fatto che una particolare navigazione sia sottoposta a vincoli di performance per cui una semplice ricerca lineare non sarebbe idonea. L’utilizzo dell’associazione qualificata, sebbene possa creare qualche perplessità iniziale, ha un campo di azione piuttosto ben definito: è utilizzata in tutti quei casi in cui vi è una struttura di lookup da una parte della relazione. Ciò equivale anche a dire che, dal punto di vista dell’implementazione, la relazione dovrebbe essere realizzata per mezzo di opportuni oggetti che agevolino la ricerca, come Hashtable, Map, ecc. Caratteristica peculiare di tali oggetti è la realizzazione di un mapping tra un identificatore e un oggetto. Per esempio, la classe Hashtable in Java possiede i seguenti metodi per memorizzare e reperire oggetti:

61

UML e ingegneria del software: dalla teoria alla pratica put(key:Object, value:Object) get(key:Object )

Pertanto le istanze di questa classe permettono di realizzare un mapping tra un oggetto che rappresenta la chiave (il qualificatore) e un altro che invece rappresenta ciò che si vuole individuare.

Associazione n-aria Come è lecito attendersi, una associazione n-aria è una relazione che coinvolge più di due classi, tenendo presente che (caso rarissimo) una stessa classe può apparire più volte. Così come l’autoassociazione è un particolare caso dell’associazione binaria, allo stesso modo la relazione binaria può essere considerata un caso particolare di quella n-aria. Una sfida interessante nel tracciare questo tipo di relazioni, consiste nell’attribuire i valori delle molteplicità: in questo caso il tutto è molto meno intuitivo. Queste devono specificare, per ogni classe, il potenziale numero di istanze della classe che possono partecipare nella relazione, fissando i valori delle altre n–1 classi. La notazione grafica utilizzata prevede un rombo in cui terminano le relazioni con le classi che prendono parte alla relazione. Eventualmente è possibile specificare il nome della relazione n-aria per mezzo di un’opportuna etichetta sistemata nei pressi del rombo.

Figura 7.42 — Esempio di relazione n-aria con classe d’associazione. Season - start : Date - end : Date ...

* Trainer - name : string - middleName : string - surname : string - dateOfBirth : Date ...

Team * - id : string

*

- name : string ...

Performance - wins : integer - losses : integer - draws : integer ...

62

Capitolo 7. Gli oggetti: una questione di classe

Utilizzare composizioni e/o aggregazioni non ha molto significato in relazioni n-aria. Nella realtà si tende a utilizzare con molta parsimonia relazioni di questo tipo. Si tenga presente che un criterio da perseguire nel disegno del sistema è la semplicità. Nella pratica, si cerca sempre di ridurre relazioni n-arie in una serie di associazioni binarie attraverso l’introduzione di apposite classi di legame. Volendo complicare ulteriormente le cose, è possibile dichiarare attributi/metodi appartenenti a una relazione n-aria. In questo caso la classe associazione viene collegata al rombo rappresentante la relazione per mezzo della solita linea tratteggiata. Per i pochissimi lettori per i quali il calcio è esclusivamente un elemento della tavola periodica, l’esempio di fig. 7.42 mostra un modello relativo alla storia professionale degli allenatori, limitata al ruolo di preparatori tecnici. In particolare, il modello mostra che ciascuno di essi, nell’arco della medesima stagione agonistica, può allenare diversi team (non contemporaneamente si intende). Per complicare le cose si è aggiunta anche la classe associazione Performance, al fine di tener traccia dei risultati ottenuti dall’allenatore nel preparare una precisa squadra durante una determinata stagione. Ogniqualvolta si ha un’associazione di questo tipo, si può immaginare che le istanze della classi coinvolte formino una particolare tupla; in questo caso gli elementi sono (Allenatore, Squadra, Stagione). Ora, per assegnare le molteplicità è necessario fissare la tupla ottenuta eliminando l’elemento del quale si vuole quotare la molteplicità, e quindi deter-

Figura 7.43 — Scioglimento della relazione n-aria con classe associativa.

Season

Team

- start : Date - end : Date ...

Trainer

- id : string - name : string ...

1

- name : string - middleName : string - surname : string - dateOfBirth : Date ...

1

1 * TrainerPerformance *

- wins : integer - losses : integer - draws : integer ...

*

63

UML e ingegneria del software: dalla teoria alla pratica

Figura 7.44 — Proprietà della relazione n-aria. Stagione

Stagione 1

* Calciatore

*

1

Team

Calciatore

1 *

* Iscrizione

* 1

Team

minare il valore. Per esempio volendo stabilire la cardinalità dell’allenatore, è necessario fissare la coppia (Stagione, Squadra) e porsi l’interrogativo “una squadra, durante una determinata stagione, da quanti allenatori diversi può essere allenata?”: la risposta è, ovviamente, n. Analogamente, questa volta fissando l’attenzione sulla coppia (Allenatore, Squadra), la domanda da porsi è “per quante stagioni, un determinato allenatore può allenare una medesima squadra?”. Anche in questo caso la risposta è “molte”. Infine, per determinare la molteplicità mancante, è necessario rispondere all’interrogativo “durante la stessa stagione, quante diverse squadre un allenatore può allenare?”. La risposta è ancora una volta n. Il modello precedente può essere facilmente riportato a quello mostrato in fig. 7.43. In questo caso particolare entrambi i modelli rappresentano adeguatamente la stessa realtà, con un livello di intuitività diverso, sebbene il secondo modello non vieti la presenza ripetuta di una classe TrainerPerformance, relativa allo stesso allenatore, allenante la medesima squadra nel contesto della stessa stagione. Allora la domanda da porsi è se la relazione n-aria sia veramente utile… La risposta è, ovviamente, affermativa. Esistono casi in cui la relativa decomposizione in relazioni più semplici con una classe intermedia richiede l’aggiunta di ulteriori vincoli al fine di ottenere la stessa rappresentazione. Si consideri la situazione in cui una business rule vigente in una competizione calcistica (magari internazionale) escluda la possibilità, nell’arco della medesima competizione, di iscrivere uno stesso giocatore in diversi team (ciò per evitare che un team proiettato verso le fasi finali della competizione possa acquistare giocatori da squadre già eliminate). In altre parole, si consideri il caso in cui per la competizione di ciascuna stagione non sia permesso ai giocatori di cambiare team e continuare a disputare lo stesso torneo. Questa situazione sarebbe facilmente rappresentabile con la relazione n-aria. In sostanza si otterrebbe un modello simile a quello di fig. 7.42, solo con la molteplicità posta = 1 al lato Team (fig. 7.44). Il problema è che la scomposizione porterebbe a un modello il quale, senza ulteriori vincoli, non sarebbe in grado di evidenziare la famosa business rule. Da nessuna parte emerge l’impossibilità di iscrivere uno stesso giocatore, nell’arco della medesima competizione, con squadre diverse. In questo caso le molteplicità non sono di grosso aiuto; infatti, tipicamente, un giocatore effettua diverse iscrizioni per una competizione (chiaramente in anni diversi) e non è infrequente il caso in cui, giochi con diversi Team.

64

Capitolo 7. Gli oggetti: una questione di classe

Attributi e relazioni Al termine della sezione dedicata all’illustrazione delle relazioni definite dallo UML si è deciso di riportare una brevissima dissertazione relativa agli attributi e alle relazioni. In termini programmativi (o, se si desidera, a tempo di esecuzione) non esiste alcuna differenza: entrambi rappresentano riferimenti in memoria “virtuale”. Nel caso di relazioni, l’elemento indirizzato è lo stato di uno specifico oggetto (più qualche altra informazione), mentre nel caso degli attributi si tratta del valore impostato in una varabile di un determinato tipo. Se si considera poi che nei linguaggi puramente OO (come per esempio SmalTalk) non esistono tipi di dato, e tutti gli elementi sono oggetti, anche gli interi, i reali, i caratteri ecc., si comprende come, in effetti, la differenza tra queste due entità sia inesistente. Nonostante ciò, al fine di agevolare la realizzazione di diagrammi leggibili ed eleganti, nella notazione dello UML si è preferito rappresentare gli attributi in un’opportuna sezione della classe e le relazioni attraverso le notazioni grafiche esaminate. Si pensi a quale caos si genererebbe se ogni attributo di una classe dovesse essere rappresentato per mezzo di un’apposita associazione o, viceversa, alla perdita della vista di insieme che si avrebbe qualora le relazioni venissero improvvisamente inglobate nella sezione di una classe. Premesso ciò, molto spesso ci si può imbattere in modelli in cui alcune relazioni siano inglobate nella sezione dedicata agli attributi. Si tratta sempre di un errore? Concettualmente sì. In alcuni casi però si può placidamente chiudere un occhio e rassegnarsi all’idea che non si vincerà alcun premio nella competizione del diagramma a maggiore grado di formalità. Si consideri un primo caso in cui si abbia una relazione (1 a n) tra due classi (per esempio un Cliente può disporre di diversi Indirizzi). Se in questo caso si specificasse, nella classe Cliente, un attributo del tipo Vector dedicato a ospitarne gli indirizzi, verosimilmente non si tratterebbe esattamente di una buona pratica (utilizzando un eufemismo). Diversi modellatori trovano gratificante realizzare una soluzione mista che consiste nel rappresentare sia la relazione, sia l’attributo. La giustificazione addotta deriva dalla necessità di dover specificare l’oggetto che permette di realizzare fisicamente la relazione come per esempio un Vector. Chiaramente si tratta di una motivazione non accettabile: dettagli di questo tipo appartengono al modello implementativo e non a quello di disegno. In questi modelli è possibile specificare il nome dell’attributo attraverso il nome del ruolo dell’associazione. Volendo si possono fornire informazioni supplementari attraverso vincoli quali ordered, che forza a selezionare un tipo di struttura anziché un altro, eventualmente relazioni qualificate, ecc. Tutto il resto non è corretto. Si consideri invece il caso di un diagramma concettuale relativo a un sito per il commercio elettronico. In questo caso, dovendo rappresentare diversi importi soggetti alla valuta utilizzata, se si utilizzasse un oggetto Money a mo’ di tipo base (quindi rappresentato direttamente nella sezione degli attributi anziché attraverso opportune relazioni), non si tratterebbe probabilmente della peggiore bestialità del mondo. Un discorso del tutto analogo

65

UML e ingegneria del software: dalla teoria alla pratica

Figura 7.45 — Istanza del modello relativo alla CurrencyPair Dollaro USA/Euro. In questo diagramma si è deciso di mostrare gli stereotipi per mezzo dell’apposita etichetta. Nulla vieta di utilizzare la relativa icona (circonferenza con un segmento orizzontale alla base). «entity»

usDollar : Currency ccyISOCode = "USD" name = "Dollar" symbol = "$" -primaryCurrency decimal = 4 spotDays = 1 ... «entity»

USDEUR : CurrencyPair «entity»

-secondaryCurrency

euro : Currency ccyISOCode = "EUR" name = "Euro" symbol = "€" decimal = 4 spotDays = 1 ...

vale per gli attributi di tipo data. Verosimilmente non è il caso di trattarli come relazioni con la medesima classe, bensì è sufficiente riportarli nel compartimento degli attributi.

I veri diagrammi a oggetti I diagrammi degli oggetti (object diagram) propriamente detti rappresentano una variante dei diagrammi delle classi, o meglio ne sono istanze, e ne condividono gran parte della notazione. Rappresentano la famosa istantanea del sistema eseguita in preciso istante di tempo di un’ipotetica esecuzione. Quindi, a differenza dei diagrammi delle classi, popolati di elementi astratti come classi e interface, i diagrammi degli oggetti sono colonizzati da oggetti sospesi in un particolare stato. Lo stato di un oggetto è un concetto dinamico e, in un preciso istante di tempo, è dato dal valore di tutti i suoi attributi e dalle relazioni instaurate con altri oggetti (che alla fine sono ancora particolari valori, indirizzi di memoria, posseduti da precisi attributi). Una delle caratteristiche molto apprezzate dello UML è la cosiddetta dicotomia tipo-istanza. Si tratta di un meccanismo che permette di rappresentare sia aspetti generici, sia esempi di elementi concreti derivanti dai primi. I casi classici di dicotomia tipo-istanza sono dati dalle coppie classi–

66

Capitolo 7. Gli oggetti: una questione di classe oggetti, parametri–valori, ecc. In questo contesto, un esempio importante è la dicotomia associazioni-collegamenti (Link). Questi ultimi sono istanze (quindi presenti nei diagrammi degli oggetti) delle associazioni che invece vivono nei diagrammi delle classi. La presenza della metaclasse Link impone la presenza della metaclasse fine collegamento (LinkEnd) corrispondente all’elemento di fine associazione (AssociationEnd). L’esistenza di quest’ultima dicotomia, permette di visualizzare nei collegamenti riportati nei diagrammi degli oggetti diverse informazioni. Per esempio è possibile riportare il nome dei ruoli (nel diagramma di fig. 7.45 sono presenti i ruoli primaryCurrency e secondaryCurrency), l’eventuale nome dell’associazione (coerentemente visualizzato sottolineato: si tratta del nome di un’istanza), altri adornamenti quali composizioni, aggregazioni, navigabilità ecc. Per terminare è possibile evidenziare la modalità con cui una relazione è ottenuta (ossia gli stereotipi della fine collegamento). In particolare sono previste le alternative mostrate nella tabella 7.4. Chiaramente non ha alcun senso riportare le molteplicità giacché le varie relazioni sono riportate in maniera esplicita. Quindi ogni collegamento è, ovviamente del tipo uno-a-uno. Quantunque lo UML preveda la possibilità di specificare molte informazioni nei collegamenti presenti nei diagrammi degli oggetti, si tratta di una pratica abbastanza infrequente: si tratta di informazioni già riportate nei corrispettivi diagrammi delle classi.

Ogni oggetto possiede una sua identità che, nel caso limite, è data dai valori assunti da tutti i suoi attributi e dalle relazioni stabilite. Graficamente è possibile rappresentare tale

Tabella 7.4— Stereotipi della metaclasse LinkEnd utilizzati per rappresentare la natura di una relazione. La rappresentazione grafica riportata (lettera bianca nel quadratino nero) è utilizzata da diversi tool, ma non appartiene alle direttive standard. Stereotipo «association»

Descrizione

Spiegazioni

A

associazione

Indica che l’istanza collegata è visibile per mezzo di una relazione di associazione. Si tratta della situazione di default.

«parameter»

P

Il riferimento all’oggetto è realizzato attraverso un parametro.

«local»

L

parametro di un metodo. variabile locale di un metodo.

«global»

G

variabile globale

L’oggetto di destinazione è visibile in quanto il relativo riferimento è memorizzato in una variabile globale.

«self»

S

auto collegamento

Indica che l’istanza è visibile in quanto è la stessa che effettua la richiesta.

Il riferimento all’oggetto è ottenuto per mezzo di una variabile locale.

67

UML e ingegneria del software: dalla teoria alla pratica

Figura 7.46 — Diagramma degli oggetti relativo al diagramma delle classi di fig. 7.36. consist of

has a

:User

:Password [OPERATIVE]

:PasswordHistory consist of

consist of

:Password [HISTORIC]

:Password [HISTORIC]

identità per mezzo di un apposito nome, e/o mostrare lo stato dell’oggetto (va riportato sotto il nome dell’oggetto racchiuso tra parentesi quadre). Volendo si può anche associare a tale stato un nome simbolico e limitarsi alla rappresentazione di quest’ultimo, evitando magari di dover visualizzare il valore associato a ogni attributo (cfr fig. 7.46). Dall’analisi del diagramma di fig. 7.46 è possibile evidenziare come i diagrammi degli oggetti mostrano un insieme di oggetti istanza di opportune classi, con i legami evidenziati in modo esplicito. Per esempio, nel diagramma delle classi di fig. 7.36, una classe PasswordHistory è associata con, al più, 6 istanze della classe Password per mezzo della relazione consist of, mentre nel relativo diagramma degli oggetti tale relazione è resa esplicita attraverso la visualizzazione di (al più) 6 legami espliciti con altrettanti oggetti di tipo Password. Questi sono rappresentati nel diagramma mostrando unicamente il tipo (oggetti anonimi) e il relativo stato: solo una password è operativa, mentre le restanti sono storicizzate. I diagrammi degli oggetti, come quelli delle classi, sono utilizzati per illustrare la vista statica di disegno del sistema, però, a differenza di quest’ultimi, utilizzano una prospettiva diversa, focalizzata su un esempio, reale o ipotetico, dello stato di evoluzione del sistema, piuttosto che su una sua astrazione. Gli elementi tipicamente presenti in un diagramma degli oggetti sono oggetti e collegamenti espliciti tra di essi. Come tutti gli altri diagrammi, poi, è possibile inserire annotazioni, vincoli, ecc. Spesso si introducono anche elementi come package e sottosistemi per mostrare l’organizzazione strutturale del sistema. La realizzazione del diagramma degli oggetti può essere paragonata a un’ideale “congelamento” del sistema, operato in un preciso istante di tempo al fine di analizzare lo stato degli oggetti presenti. L’idea è paragonabile a un processo di debugging del sistema: si tenta di congelare lo stato (se possibile) e si ispezionano gli oggetti per analizzarne il valore degli attributi, le relazioni, ecc. In effetti, una utility molto interessante, che i tool di sviluppo (IDE) potrebbero prevedere, potrebbe consistere in una funzione avanzata di

68

Capitolo 7. Gli oggetti: una questione di classe

reverse engineering inserita nel debug, in grado di tracciare, su richiesta, il diagramma degli oggetti del sistema in un particolare istante di tempo per un insieme specifico di oggetti e quindi permetterne la navigazione. Chiaramente, i diagrammi degli oggetti possono essere utilizzati anche per mostrare esempi di modelli delle classi che non necessariamente verranno implementati. Per esempio frammenti dei modelli degli oggetti business e di dominio.

I diagrammi degli oggetti servono principalmente per esemplificare: • strutture e relazioni particolarmente complesse o poco chiare, attraverso esempi pseudoreali; • livelli di astrazione che potrebbero generare problemi di comprensione delle caratteristiche relative a singoli meccanismi nel loro dettaglio. Nel realizzare diagrammi degli oggetti, tipicamente, si cerca di conferire rilievo a particolari stati in cui specifici insiemi di oggetti potrebbero trovarsi in un determinato istante di tempo, trascurandone altri di minore interesse. Questi oggetti potrebbero consistere in opportuni frammenti di una struttura di dati, di un particolare meccanismo, e così via. Quindi è molto importante saper discernere gli oggetti che potenzialmente possono concorrere a fornire valore aggiunto alla spiegazione da quelli che invece è preferibile trascurare (in sostanza si definisce un particolare scenario). Ciò evita di realizzare diagrammi di esempio con un numero eccessivo di elementi, che correrebbero il rischio di non raggiungere il risultato sperato. Pertanto, non solo è necessario stabilire quali oggetti visualizzare, ma anche lo stato in cui presentare ciascuno di essi in funzione del meccanismo generale che si vuole descrivere. Ciò comporta la dichiarazione dei valori degli attributi ritenuti più significativi (volendo è possibile dichiarare direttamente lo stato) e l’illustrazione esplicita delle relazioni tra gli oggetti.

Come indicato in precedenza gli oggetti sono visualizzati con una notazione simile a quella delle classi, con la differenza che: •

non ha senso mostrare il compartimento (compartimento relativo ai metodi);



il nome dell’oggetto e la relativa classe di appartenenza sono visualizzati sottolineati (objectName:classname). Da notare che entrambi gli elementi possono essere omessi ma almeno uno dei due deve essere presente (nel caso in cui il nome sia omesso, si dice che ha un’istanza anonima, mentre nel caso in cui sia l’indicazione della classe a mancare, si dice che l’istanza è orfana);



tutte le istanze delle relazioni vengono mostrate;

69

UML e ingegneria del software: dalla teoria alla pratica •

gli attributi vengono evidenziati con il relativo valore (attributeName:type = value). Gli attributi i cui valori non risultano utili possono essere omessi, così come l’intero compartimento.

I diagrammi degli oggetti non sono di fondamentale importanza per la progettazione del sistema, però risultano decisamente utili per esemplificare sezioni particolarmente complesse o poco chiare dei corrispondenti diagrammi delle classi. Da notare che i diagrammi degli oggetti sono gli unici che utilizzano propriamente il concetto di oggetto. Negli altri diagrammi (quali sequenza e collaborazione, per esempio) gli oggetti compaiono con il significato più di ruoli di oggetti che di oggetti veri e propri. Si consideri il diagramma delle classi di fig. 7.47. Esso mostra un frammento di un modello relativo ai possessori di carta di credito. Questi ultimi possono avere diversi o nessuno account primari, e nessuno o molteplici account ordinari (chiaramente almeno uno ne devono possedere…). La differenza tra i due ruoli di account consiste nel fatto che gli ordinari sono utilizzati per gestire carte di credito e quindi tutti i possessori ne debbono possedere almeno uno, mentre i primari sono utilizzati per pagare i conti, pertanto tutti gli account ordinari devono essere associati a uno primario: ciò è mostrato attraverso l’autoassociazione presente nella classe Account. La relazione tra la classe C r e d i t C a r d H o l d e r e A c c o u n t , il cui ruolo è ordinaryAccounts, potrebbe sembrare ridondante (dato un oggetto di tipo Account che recita il ruolo di account primario è possibile risalire a tutti i suoi ordinari). Un’attenta analisi, magari eseguita con l’ausilio di un diagramma degli oggetti, mostra che invece si

Figura 7.47 — Frammento di modello relativo al legame tra i possessori di carta di credito e i relativi account.

-dependentAccounts

CreditCardHolder - id : string - firstName : string - middleName : string - secondName : string - dateOfBirth : Date ...

1 1

-primaryAccounts Account 0..* -ordinaryAccounts - accountCode : string 0..* - isPrimary : boolean ...

0..* 1 -primaryAccount

70

Capitolo 7. Gli oggetti: una questione di classe

Figura 7.48 — Diagramma degli oggetti relativo al precedente diagramma delle classi. Come si può notare, sia Eva Kant, sia Diabolik possiedono carte di credito e quindi Account ordinari per la loro gestione. I relativi conti vengono però addebitati tutti sull’account primario di Eva. Pertanto, il diagramma degli oggetti mostra chiaramente perché l’associazione tra CreditCardHolder e Account il cui ruolo è ordinaryAccounts non è ridondante. 102033689 : Account [Primary]

EvaKant : CreditCardHolder

104433789 : Account [Ordinary] 145532700 : Account [Ordinary] Diabolik : CreditCardHolder

104433789 : Account [Ordinary]

tratta di una relazione di associazione fondamentale. Volendo chiarire il frammento di modello si consideri diagramma degli oggetti di fig. 7.48. Purtroppo, la quasi totalità dei tool non prevede esplicitamente funzionalità per la realizzazione di diagrammi degli oggetti. Tale lacuna può essere in parte raggirata grazie all’utilizzo degli strumenti previsti per la realizzazione dei diagrammi di collaborazione e, raramente, dei diagrammi delle classi stessi.

Istanze nel metamodello Il dualismo comportamentale di alcuni elementi, in gergo denominato “dicotomia tipo–istanza” (type–instance dichotomy), come per esempio classi e oggetti, è presente nel metamodello dello UML. Le istanze, tipicamente, non restano isolate, ma vengono associate a un’opportuna astrazione. La notazione grafica utilizzata prevede l’utilizzo dello stesso simbolo geometrico per la coppia di elementi relazionati. La distinzione degli elementi è ottenuta sottolineando il nome dell’istanza. Ciò è molto utile perché da un lato concorre a mostrare che i due concetti condividano molte similitudini, e dall’altro permette di evidenziarne le differenze in maniera molto semplice e diretta. Il diagramma mostrato in fig. 7.49 rappresenta una versione semplificata delle varie istanze previste dallo UML. Chiaramente si tratta di istanze delle specializzazioni dell’elemento astratto Classifier. Nella figura non sono mostrati gli “stimoli” che tipicamente sono composti da istanze.

71

UML e ingegneria del software: dalla teoria alla pratica

L’interpretazione del diagramma dovrebbe essere ormai chiara. Si tenga presente che si tratta di istanze e pertanto ha senso dire che una particolare istanza di un componente può risiedere al più in un solo nodo (un’istanza di un nodo è, tipicamente, un determinato computer), così come una particolare istanza può risiedere in un solo componente. La metaclasse AttributeLink serve, essenzialmente, per visualizzare i valori degli attributi che caratterizzano una particolare istanza. Questi possono essere legami con altri oggetti o, più semplicemente, classici valori di attributi.

Esempio: definizione formale del comportamento dei casi d’uso Nei prossimi paragrafi si elaborerà un modello delle entità che, opportunamente relazionate, permettono di descrivere formalmente il modello su cui si basa la descrizione del comportamento dinamico dei casi d’uso. In altre parole, utilizzando il formalismo dei diagrammi delle classi, si vuole realizzare il modello di dominio della versione dei flussi degli eventi illustrata nel capitolo relativo ai casi d’uso. L’esempio oggetto di studio è stato selezionato poiché: •

si riferisce ad un dominio (quello dei casi d’uso) che ormai dovrebbe essere ben noto ai lettori;



fornisce ottimi spunti per ricapitolare l’utilizzo degli elementi introdotti nel corso di questo capitolo;



permette di riepilogare e consolidare i princìpi base della descrizione del comportamento dinamico dei casi d’uso, illustrate nel relativo capitolo;

Figura 7.49 — Frammento del metamodello package Common Behavior – Instances.

ModelElement Attribute

attribute * 1

slot

AttributeLink 0..* value

* 1

Classifier

classifier 1

1 * resident *

Instance

ownedInstance 0..* 0..1

0..1

DataValue

SystemInstance

ComponentInstance

resident * 0..1

NodeInstance

Object

72

Capitolo 7. Gli oggetti: una questione di classe •

offre l’opportunità di evidenziare la versatilità dello UML (e dell’OO più in generale) come strumento in grado di rappresentare formalmente modelli reali, non necessariamente vincolati alla realizzazione di sistemi software. Nulla, ovviamente, vieta di utilizzare l’esempio come modello a oggetti del dominio di un sistema atto ad agevolare l’analisi dei requisiti e la produzione di casi d’uso consistenti.

Il modello che si tratterà di seguito rappresenta un’istanza di un modello a oggetti del dominio (Domain Object Model), pertanto l’interesse è focalizzato sugli oggetti coinvolti (o meglio, sulle relative classi), le reciproche relazioni e gli attributi, mentre l’attenzione

Tabella 7.5 — Struttura del template utilizzato per la descrizione del comportamento dinamico dei casi d’uso. CASO D’USO:

Nome

Codice Descrizione:

Breve descrizione.

Durata:

Durata.

Priorità:

Elevata/Media/Bassa.

Attore primario:

Nome attore

Data: dd/mm/yyyy Versione: n.nn.nnn

Coinvolgimento nel caso d’uso Attore secondario:

Nome attore Coinvolgimento nel caso d’uso

Precondizioni:

Elenco precondizioni.

Garanzie di fallimento:

Elenco garanzie di fallimento.

Garanzie di successo:

Elenco garanzie di successo.

Avvio:

Descrizione dell’evento che avvia il caso d’uso. Scenario principale. ATTORE X

1.

Azione 1

2.

Azione 2 ...

n. Azione.

Scenario di errore: Descrizione. Step.

Azione.

Annotazioni. Step.

... Azione n

Scenario alternativo: Descrizione. Step

SISTEMA

Descrizione nota.

UML e ingegneria del software: dalla teoria alla pratica

73

per i metodi è piuttosto relativa. Volendo, anziché rappresentare gli attributi, si sarebbero potuti definire appositi metodi get/set. Chiaramente il contenuto informativo sarebbe stato lo stesso, mentre il livello di chiarezza, probabilmente, ne avrebbe risentito. Per comodità di esposizione, il modello preso in esame è mostrato nella tabella seguente, la cui descrizione dettagliata è riportata nel paragrafo Template del Capitolo 4.

Individuazione delle classi Il processo di scoperta delle classi appartenenti al modello a oggetti del dominio è, tipicamente, un’attività complessa nella quale giocano un ruolo fondamentale caratteristiche oggettive del modellatore quali esperienza, capacità analitiche ecc. In questo capitolo, poiché l’attenzione è focalizzata sul formalismo dei diagrammi delle classi e non sul processo di scoperta delle classi, viene presentato un approccio abbastanza informale, mentre nel capitolo successivo è presentato un approccio più organico e rigoroso.

Qualora nell’area business di cui si vuole realizzare un modello siano disponibili dei moduli, sia cartacei, sia digitali (schermate di sistemi esistenti), questi forniscono un ottimo punto di partenza per realizzare prime versioni del modello. Però, a fronte del vantaggio di avere un riscontro diretto e oggettivo delle entità coinvolte, esiste una serie di rischi cui è opportuno prestare attenzione, come per esempio realizzare un’analisi del dominio limitata alla proiezione mostrata nei documenti, produrre modelli poco flessibili, e così via. Ciò nonostante rappresentano un buon punto di partenza per realizzare un embrione del modello, il cui passo successivo consisterebbe nel realizzarne una versione generalizzata.

Si cominci a costruire il modello partendo da una classe che, in qualche modo, rappresenta il punto di ingresso dell’intero modello il cui nome non può che essere UseCase (fig. 7.50). Questa classe è associata con quella denominata Author, in quanto è opportuno che ciascuno use case sia gestito da una sola persona. Il “numero” della più recente versione del caso d’uso disponibile è mostrato attraverso un’apposita relazione con la classe Version.

Durante il processo di analisi dei requisiti utente, è molto importante assegnare la responsabilità di ciascuno use case a una, e una sola, persona, al fine di evitare perdite di tempo per mancanza di “memoria storica” derivante dagli avvicendamenti, di avere continuità e coerenza nelle specifiche, di disporre di personale specializzato su determinati argomenti e con un rapporto consolidato con gli utenti di riferimento, ecc.

74

Capitolo 7. Gli oggetti: una questione di classe

Figura 7.50 — Particella iniziale del modello rappresentante la classe UseCase e le relative relazioni con Author e Version. Author is responsible for

UseCase code : string name : string lastUpdate : Date description : string duration : integer timeUnit : TimeUnit

*

id : string name : string 1 surname : string eMail : string ...

«enumeration»

TimeUnit MILLISEC SEC MIN HOUR DAY

1

Version 1 rewritings : integer useCaseVewrsion newFanctionality : integer fixings : integer

La classe Version può essere considerata a tutti gli effetti un’estensione dei tipi di dato forniti dal linguaggio (a dire il vero, tutte le classi lo sono, ma alcune più delle altre). Le relative istanze permettono di memorizzare informazioni nel formato “n.n.n” in cui i tre numeri sono legati da un legame gerarchico. In particolare, l’incremento di uno di essi, determina l’azzeramento di quelli che si trovano alla propria destra. Per esempio se si decidesse di riscrivere interamente la versione “0.03.05” perché esasperati dalla scarsa qualità della descrizione dinamica del caso d’uso, la versione successiva sarebbe “1.00.00”. Questo concetto potrebbe essere rappresentato in maniera più sofisticata e flessibile con qualche accorgimento (magari ricorrendo a un pattern Composite). L’aumento di flessibilità prodotto, in questo caso, non solo sarebbe di relativo interesse ma avrebbe addirittura degli effetti negativi: il diagramma risulterebbe decisamente meno chiaro. Il consiglio è di rendere i modelli più flessibili possibile laddove vi sia una concreta necessità. Come al solito, la flessibilità ha il suo costo e nei modelli di dominio è sempre consigliabile tendere alla semplicità. Il passo successivo consiste nel considerare le precondizioni (Precondition), le garanzie in caso di successo ( S u c c e s s G u a r a n t e e ), quelle di fallimento (OnFailureGuarantee) e gli eventi in grado di innescare l’esecuzione del caso d’uso (Trigger). Le prime tre classi estendono (magari in maniera un po’ artificiosa, ma senza effetti collaterali), la classe base Agreement, mentre per quanto concerne i Trigger, si è interessati esclusivamente alla loro descrizione. Procedendo con l’esame del modello, si giunge agli attori e ai primi embrioni dei flussi degli eventi. Per quanto concerne i primi, si può notare che, per ogni caso d’uso, può esisterne al più uno primario e diversi secondari.

75

UML e ingegneria del software: dalla teoria alla pratica

Figura 7.51 — Introduzione dei concetti di Precondition , SucessGuarantee , OnFailureGuarantee e Trigger. Agreement name : string description : string

is subjected

Precondition * SuccessGuarantee * OnFailureGuarantee

*

UseCase

1

Author is responsible for

code : string name : string insures lastUpdate : Date 1 description : string duration : integer guarantees timeUnit : TimeUnit 1

id : string 1 name : string surname : string eMail : string ...

*

useCaseVewrsion

is started

Trigger description : string

*

1 has primary 0..n

«enumeration» TimeUnit MILLISEC SEC MIN HOUR DAY

Version

1 rewritings : integer newFanctionality : integer fixings : integer

1

1

1

0..* has

Actor

0..1

0..n has secondary

id : string description : string type : ActorType

includes ExceptionFlow

1 «enumeration» Priority LOW MEDIUM HIGH «enumeration» ActorType HUMAN TIME SYSTEM

0..n

MainFlow

type : "E"

OptionalFlow

type : "B" AlternativeFlow type : "A" FlowOfEvent id : string name : string type : string priority : Priority

Teoricamente ogni use case dovrebbe essere avviato da un solo attore e, pertanto, ogni caso d’uso dovrebbe disporre almeno dell’attore primario. Ciò però non è vero nel caso di use case solo “inclusi” che eseguono specifiche attività senza mai interagire con gli attori o semplicemente fornendo alle stesse il risultato di opportune elaborazioni. Si tratta di casi d’uso introdotti per condensare funzionalità utilizzate da diversi altri casi d’uso, al fine di rendere il modello più elegante evitando di doverne ripetere ogni volta la descrizione del medesimo servizio. Dal punto di vista degli attori, ciascuno di essi potrebbe non essere mai primario o mai secondario (ciò è evidenziato dalla molteplicità 0..n della classe Actor nelle relazioni has primary e has secondary). Chiaramente entrambe le possibilità sono piuttosto inusuali. Si noti che spesso è utile fornire informazioni supplementari relative all’interazione tra il caso d’uso e l’attore: per esempio quale sia l’interesse di un attore nell’interagire con uno specifico caso d’uso. Tale situazione è esprimibile come illustrato nel modello di fig. 7.53.

76

Capitolo 7. Gli oggetti: una questione di classe

Figura 7.52 — Inclusione degli attori e dei primi embrioni di flussi. Agreement name : string description : string

is subjected

Precondition * SuccessGuarantee * OnFailureGuarantee

*

UseCase

1

Author is responsible for

code : string name : string insures lastUpdate : Date 1 description : string duration : integer guarantees timeUnit : TimeUnit 1

id : string 1 name : string surname : string eMail : string ...

*

1 rewritings : integer newFanctionality : integer fixings : integer

1 is started

Trigger *

description : string

Version

useCaseVewrsion

1 has primary 0..n

«enumeration» TimeUnit

1

MILLISEC SEC MIN HOUR DAY

1

0..* has

Actor

0..1

0..n has secondary

id : string description : string type : ActorType

includes ExceptionFlow

1 «enumeration» Priority

0..n

MainFlow

type : "E"

OptionalFlow

type : "B"

LOW MEDIUM HIGH

AlternativeFlow type : "A" FlowOfEvent

«enumeration» ActorType

id : string name : string type : string priority : Priority

HUMAN TIME SYSTEM

Figura 7.53 — Frammento di modello che permette di evidenziare quale sia lo scopo di un attore nell’interagire con uno specifico caso d’uso. UseCase code : string name : string lastUpdate : Date description : string duration : integer timeUnit : TimeUnit

Actor partecipates 1..*

0..*

ActorInterest isPrimary : boolean description : string

id : string description : string type : ActorType

UML e ingegneria del software: dalla teoria alla pratica

77

Il flusso primario ( M a i n F l o w ) è uno, e uno solo, mentre quelli opzionali (OptionalFlow) sono zero o più. Lo scenario primario viene anche detto best scenario poiché, nella relativa stesura, si assume che tutto funzioni correttamente e non insorga alcuna condizione anomala. I flussi degli eventi opzionali possono essere di due tipi (si noti che OptionalFlow è una classe astratta e quindi deve essere necessariamente specializzata): 1. alternativi (AlternativeFlow): si tratta di percorsi che possono ancora portare al successo del caso d’uso, ma attraverso un percorso diverso da quello canonico sancito nel flusso principale; 2. di errore (ExceptionFlow): sono i flussi generati da una condizione di errore e quindi forniscono dettagli su come gestire la relativa anomalia. Ai vari flussi è associata una lettera identificativa: ciò si rende necessario per il modello dei test case mostrato successivamente, ove è necessario sapere con precisione a quale flusso appartenga ogni passo. Nella classe astratta FlowOfEvents è presente un attributo priority di tipo enumerato. Nel caso in questione sono stati previsti unicamente i valori LOW, MEDIUM e HIGH. Chiaramente nulla vieta di utilizzare più elementi in modo da ottenere indicazioni di maggior dettaglio. L’attributo di priorità è di interesse per il processo (workflow) di Project Management (gestione del progetto) tipicamente presenti nei processi formali. In particolare le priorità sono utilizzate per controllare i fattori di rischio presenti nel progetto attraverso pianificazioni accurate delle iterazioni. Il buon senso (e anche i processi di sviluppo formali) suggeriscono di affrontare prima i rischi maggiori (ma senza esagerare), sia per poter gestire eventuali crisi del progetto il prima possibile quando ancora si ha tempo e non si è sottoposti allo stress della consegna, sia per minimizzare l’eventuale spreco di investimenti qualora si rendano necessari ripensamenti legati allo spazio delle soluzioni. I fattori di rischio, in prima analisi, possono essere suddivisi in due categorie: tecnici (per esempio l’architettura progettata non è in grado di garantire i requisiti non funzionali) e d’uso (il sistema non fornisce i servizi per i quali era stato inizialmente ideato). L’attributo in questione fa riferimento a rischi del secondo tipo; nulla vieta però di inserire ulteriori priorità: una relativa ai rischi tecnici e una finale, risultato di una media pesata delle due precedenti. I fattori di rischio possono variare all’interno di uno stesso use case. Per esempio, potrebbe aver senso assegnare una priorità elevata a uno scenario principale, e prevederne invece di più modeste per quelli opzionali o viceversa. Ciò ha senso considerando che le iterazioni, tipicamente, non prendono in considerazione solo interi casi d’uso, bensì raggruppano insiemi di scenari.

78

Capitolo 7. Gli oggetti: una questione di classe

Figura 7.54 — Dettaglio dei flussi degli eventi. Agreement name : string description : string

is subjected

Precondition * SuccessGuarantee * OnFailureGuarantee

*

UseCase

1

Author is responsible for id : string * 1 name : string surname : string eMail : string ...

code : string name : string insures lastUpdate : Date 1 description : string duration : integer guarantees timeUnit : TimeUnit 1

1 rewritings : integer newFanctionality : integer fixings : integer

1 is started

Trigger description : string

*

Version

useCaseVewrsion

1 has primary 0..n 1

1

Actor

0..1

0..n has secondary

id : string

0..* has

includes

1 «enumeration» TimeUnit MILLISEC SEC MIN HOUR DAY

ExceptionFlow

0..n

MainFlow

type : "E"

OptionalFlow

type : "B" 0..*

AlternativeFlow type : "A"

FlowOfEvent generates «enumeration» Priority LOW MEDIUM HIGH

id : string name : string type : string priority : Priority

Action

1 1

1..* 0..*

id : string description : string 0..1

is performed by «enumeration» ActorType HUMAN TIME SYSTEM

1..* 1

Responsible System

id : string name : string description : string

Obiettivo del passo successivo è definire chiaramente la struttura dei flussi degli eventi. In prima analisi, è possibile notare come ciascun flusso sia costituito da una serie di passi — azioni (Action) — mentre ogni azione può appartenere, al più, a un solo flusso. L’autoassociazione della classe Action mostra che una particolare azione può dipendere da un’altra. Per esempio l’azione “1.2.2” appartiene al sottoflusso dipendente dall’azione “1.2”. In generale, un’azione può averne diverse dipendenti, mentre una può dipendere esclusivamente da un’altra. Il fatto che i flussi opzionali siano diramazioni del flusso principale, originate da particolari passi, è mostrato dalla relazione generates che lega la classe Action a quella

79

UML e ingegneria del software: dalla teoria alla pratica

Figura 7.55 — Modellazioni delle relazioni attraverso le quali è possibile interconnettere i casi d’uso. 0..n Precondition

Agreement name : string description : string

SuccessGuarantee

OnFailureGuarantee

is subjected *

*

*

1

specialises 1

UseCase

Author is responsible for id : string * 1 name : string surname : string eMail : string ...

code : string name : string insures lastUpdate : Date 1 description : string duration : integer guarantees timeUnit : TimeUnit 1 isAbstract : boolean

Trigger

1 rewritings : integer newFanctionality : integer fixings : integer

is started *

1

description : string

has primary

extension 0..n included

Version

useCaseVewrsion 1

0..n

1

1

1

0..n

Actor

0..1 id : string

has secondary 0..*

has

includes

1

ExceptionFlow

0..n

MainFlow

type : "E"

OptionalFlow

type : "B" 0..n

AlternativeFlow type : "A"

FlowOfEvent generates

id : string name : string type : string priority : Priority

System 1 consist of

including

1..n

0..n

groups

Node

Include

id : string description : string

Responsible

1..n {ordered}

id : string name : string description : string

1 ExtendPoint

Step

description : string extended

0..n

*

is performed by

Segment name : string isVisible : boolean

extensionPoint

1..* Action 1

1..n

*

id : string description : string

Extend

0..n

condition : BooleanExpression specifies

ExtensionPointReferences condition : BooleanExpression

0..1

«enumeration» ActorType HUMAN TIME SYSTEM

«enumeration» Priority LOW MEDIUM HIGH

1

«enumeration» TimeUnit MILLISEC SEC MIN HOUR DAY

1..*

80

Capitolo 7. Gli oggetti: una questione di classe

OptionalFlow. Tipicamente, la presenza nel flusso degli eventi di azioni con descrizione

del tipo “verifica”, “controlla”, “tenta”, “assicura”, ecc. è il segnale lampante che tale azione genera flussi alternativi o di errore. Quindi, una particolare azione può generare nessuno o diversi flussi opzionali, mentre ciascuno di essi dipende da un particolare passo nel flusso degli eventi. Nei casi d’uso è poi molto importante mostrare esplicitamente chi esegue una particolare azione. In questo caso gli esecutori possono essere esclusivamente di due tipi: il sistema o uno degli attori. Questa regola è espressa formalmente attraverso la relazione is performed by che lega la classe Action con quella astratta Responsible. Si tratta di una classe astratta che prevede due specializzazioni: System e Actor. A questo punto è giunto il momento di considerare le relazioni che permettono di collegare tra loro i vari casi d’uso (fig. 7.55)… Argh! Qui la situazione si complica. Si proceda per gradi. In primo luogo è stato aggiunto l’attributo isAbstract di tipo booleano nella classe UseCase, al fine di indicare se uno specifico caso d’uso sia astratto o meno. Sempre nella stessa classe è stata aggiunta l’auto-relazione specialises. Questa indica che uno stesso caso d’uso può essere specializzato da diversi altri, mentre ciascuno di essi ne può specializzare al più uno solo. Si ricordi che i casi d’uso costituiscono il veicolo preferenziale per catturare i requisiti utente e quindi per interagire con personale che, per definizione, ha poca o nessuna esperienza informatica. Pertanto, complicare inutilmente i casi d’uso con sofisticate relazioni di generalizzazione di certo non aiuta a conseguire l’obiettivo primario: comunicare. Per descrivere i flussi, è stato aggiunto il pattern Composite (probabilmente si tratta più di un Decorator). Uno dei pregi è relativo alla facoltà di rappresentare facilmente strutture intrinsecamente ricorsive come alberi. In questo contesto permette di avere o passi semplici, oppure composti e quindi rappresentati attraverso appositi segmenti, eventualmente costituiti, a loro volta, da passi semplici e/o altri segmenti, e così via. L’elemento base che termina la ricorsione è un oggetto Step che prevede tre specializzazioni: Include, Extend e Action. È stato necessario definire il concetto di segmento (Segment) per via della relazione di estensione. In tutti i casi in cui uno specifico use case non ne estende un altro, può non avere importanza raggruppare i passi in segmenti. In tali casi, i flussi potrebbero essere composti da un unico segmento di default la cui presenza non è di grande interesse (isVisible = false) in quanto trattasi essenzialmente di un artificio. Nella classe Step sono state migrate diverse caratteristiche della classe Action. Si è deciso di dar luogo a tale ulteriore strutturazione gerarchica in modo da distinguere i passi (istanze delle classi che specializzano Step) relativi a relazioni, da quelli ordinari. Esistono due specializzazioni dei passi relativi a relazioni: Include ed ExtensionPoint. Le inclusioni possono essere considerati “passi attivi” e prevedono l’indicazione esplicita del caso d’uso incluso, per questo motivo è prevista la associazione con la classe UseCase. Un esempio classico è l’inclusione dello use case di reperimento dati articoli. In un flusso degli eventi, tale passo assumerebbe una forma del tipo: Include(Reperimento dati

UML e ingegneria del software: dalla teoria alla pratica

81

articolo). Da notare che uno stesso caso d’uso può essere incluso in molti altri e viceversa. Chiaramente in uno specifico passo del flusso degli eventi è possibile includere un solo caso d’uso. Riguardo la relazione di estensione, nello use case base (quello che deve essere esteso), essa assume una forma per così dire passiva: si limita a dichiarare esplicitamente le locazioni in cui il comportamento dinamico deve essere esteso. Per questo motivo, un passo del flusso degli eventi può dichiarare al più un solo punto di estensione, il cui nome (definition) è specificato nella classe ExtensionPoint. Per un singolo punto di estensione possono essere previste diverse condizioni (per esempio lo use case base viene esteso da diversi altri) e una stessa condizione può essere applicata a diverse locazioni del caso d’uso base. Un use case può estendere ed essere esteso da diversi altri. Qualora un use case ne estenda un altro, è necessario che il primo fornisca i segmenti di comportamento da sostituire ai punti di estensioni dichiarati nel caso d’uso base. Ciò è modellato attraverso la classe di associazione ExtensionPointReferences. A questo punto il modello possiede una forma abbastanza matura e non resta altro da fare che aggiungere informazioni supplementari (ciò che tecnicamente rientra nella categoria dei “fiocchetti”) relative, perlopiù, all’utilizzo dei casi d’uso nel contesto di processi formali e alla gestione di eventuali feedback (cfr fig. 7.56). Per esempio è possibile associare a uno use case una lista di assunzioni, lo stato di elaborazione in cui si trova (base, elaborato, definito sinteticamente e definito completamente), eventuali problemi ecc.

Modello dei test case Obiettivo della presente sezione è di presentare una versione del modello del dominio dei test case, realizzando un diagramma delle classi che descriva formalmente le entità, corredate dalle relative relazioni, che permettono di descrivere rigorosamente i test case. L’argomento dei test case è stato trattato nel Capitolo 5.

Prima di procedere nello studio del modello dei test case, si vuole ricordare ancora una volta che questi dipendono fortemente dagli use case. Pertanto se si è realizzato un modello dei requisiti utente (attraverso il formalismo dei casi d’uso), e questi sono chiari, accurati e completi, allora la pianificazione dei test case si presenta come un’attività piuttosto immediata. Diversamente, l’esercizio diventa decisamente più complesso e non sempre di sicuro effetto o di qualche utilità. Per esempio, si può correre il rischio di non verificare alcuni percorsi di estrema importanza, di non riuscire a definire correttamente i “prodotti” attesi dal sistema come risultato di uno specifico percorso, ecc. Da quanto riportato poc’anzi, è evidente che anche la pianificazione dei test case rappresenta uno strumento per verificare la qualità del modello dei casi d’uso.

82

Capitolo 7. Gli oggetti: una questione di classe

Figura 7.56 — Inclusione delle informazioni relative al processo.

UseCaseState

Issue

date : Date status : UseCaseStatus comment : string

id : string date : Date description : string type : IssueType status : IssueStatus

1..n

0..n

evolved through

1

0..n

UseCase

formulated by

0..n raised by 1

1 Author

1

code : string name : string lastUpdate : Date description : string duration : integer timeUnit : TimeUnit isAbstract : boolean

id : string comment : string 0..n

is related

1

Solution

requires

is responsible for 0..n

1

id : string name : string surname : string eMail : string ...

1..n presumes

«enumeration» IssueStatus

«enumeration» UseCaseStatus

0..n

BASE ELABORATED REALISED_BRIEFLY REALISED_FULLY

Assumption id : string description : string

«enumeration» IssueType TECHNICAL BUSINESS

OUTSTANDING RESOLVED NEXT_RELEASE

Figura 7.57 — Embrione del modello ad oggetti dei test case. traces

UseCase code : string name : string lastUpdate : Date description : string duration : integer timeUnit : TimeUnit isAbstract : boolean

1

needs

TestCase 0..n

id : string name : string description : string priority : Priority

0..n

TestCaseSetup 0..n

id : string name : string description : string 1

consists of 1..n SetupStep

«enumeration» Priority HIGH LOW MEDIUM

id : string description : string

UML e ingegneria del software: dalla teoria alla pratica

83

In maniera del tutto analoga al caso precedente, si cominci a considerare la classe principale denominata TestCase che, in qualche misura, si presta ad essere la radice del modello (cfr fig. 7.57). Gli attributi di interesse sono, al solito, id, name, description e priority. Ogni caso di test è utilizzato per verificare il funzionamento di uno e un solo caso d’uso, mentre ciascuno di questi, generalmente, richiede diversi test (relazione traces). Le procedure di verifica, prima di poter essere eseguite richiedono tipicamente una serie di inizializzazione o passi preliminari. Per esempio, se si volesse verificare un sistema per la gestione dei conto correnti dei clienti di una banca, bisognerebbe eseguire un’attività propedeutica di caricamento della base di dati (inserimento di un nuovo cliente, di un conto, versamento di un predefinito importo nel conto, ecc.), al fine di dotare il sistema di una base conoscitiva su cui poi condurre le varie verifiche. Le procedure di inizializzazione si prestano a essere modellate in svariati modi con diversi livelli di formalità e flessibilità. In questo contesto si è deciso di utilizzare quella più semplice in quanto più flessibile: riportare attraverso descrizione testuale l’elenco ordinato dei passi da eseguire. Nel modello di fig. 7.57, le procedure di inizializzazione sono rappresentate mediante la classe TestCaseSetup. Questa può essere utilizzata da diversi test case, così come uno stesso test case può utilizzare diverse procedure di inizializzazione (relazione needs). Ogni istanza della classe TestCaseSetup è composta da un insieme di passi (SetupStep) propri della procedura di inizializzazione e non condivisibili con altre procedure. Il passo successivo consiste nel dettagliare i percorsi di test previsti dai test case e nel mostrare le relative dipendenze dallo use case di riferimento. Le informazioni a cui si è particolarmente interessati nei test case sono i percorsi da seguire durante ciascun test. Nel diagramma di fig. 7.58, questi percorsi sono rappresentati dalla classe TestPath. Ciascuno di essi è identificato da un apposito id, prevede una descrizione, specifica gli obiettivi che si devono conseguire con la relativa esecuzione, enumera esattamente quali passi dello use case eseguire e dichiara gli eventuali parametri necessari. L’enumerazione dei passi dello use case da eseguire avviene attraverso la relazione comprises. Chiaramente un medesimo passo di uno specifico use case può essere incluso in svariati percorsi test (oggetti di tipo TestPath), così come ciascun percorso prevede l’esecuzione di diversi passi del caso d’uso sotto verifica. Per esempio un percorso di test potrebbe selezionare tutti i passi dello scenario principale al fine di verificare che, partendo da una situazione valida e sottoponendo stimoli corretti, il sistema risponda con gli effetti attesi. Si potrebbe poi impostare un percorso che generi una condizione di errore (flusso di errore) al fine si verificare la capacità del sistema di riconoscere la particolare situazione anomala e quindi reagire come da specifiche. Un percorso di test in genere necessita di diversi parametri (relazione declares). Ciò si rende necessario poiché i casi d’uso specificano un comportamento generale valido per tutti i possibili dati di input, mentre i test devono avvenire su dati ben definiti.

84

Capitolo 7. Gli oggetti: una questione di classe

Figura 7.58 — Sviluppo del modello: definizione del concetto di percorso di test.

Segment 1

is of

TestParameter

Step

name : string isVisible : boolean

id : string description : string 1..n

groups

0..n

ParameterType 1

id : string description : string

1..n

Node declares

comprises

1..n id : string description : string

Examples: String, Integer, Char, Boolean

1..n consist of 1

1

«enumeration» PathType

TestPath

FlowOfEvent id : string name : string type : string priority : Priority

1..n

SUCCESS ERROR

id : string description : string goal : string type : PathType

«enumeration» Priority

1..n OptionalFlow

specifies

type : "B" 0..n

1

includes 1

HIGH LOW MEDIUM

MainFlow

has 1

1 traces

UseCase code : string name : string lastUpdate : Date description : string duration : integer timeUnit : TimeUnit isAbstract : boolean

1

needs

TestCase 0..n

id : string name : string description : string priority : Priority

0..n

TestCaseSetup 0..n

id : string name : string description : string 1

consists of 1..n SetupStep

AlternativeFlow type : "A"

ExceptionFlow

id : string description : string

type : "E"

Per esempio, dopo aver popolato la base dati del sistema di cui sopra, si vuole verificare che esso effettui correttamente l’addebitamento di un importo. In questo caso, il percorso di test da eseguire dovrebbe utilizzare diversi parametri: l’identificatore del possessore del conto corrente, il relativo codice, l’importo da addebitare, ecc. A questo punto l’ossatura del modello è definita e non resta altro da fare che modellare l’esecuzione del test case. In particolare è necessario disporre di informazioni quali: data

85

UML e ingegneria del software: dalla teoria alla pratica

dell’esecuzione, versione del sistema, nome del tester, esito della verifica di ogni test path, eventuali errori individuati, ecc. In primo luogo uno stesso percorso di test può essere eseguito molteplici volte (relazione runs tra la classe TestPath e TestCaseIteration). Ciò è abbastanza naturale considerando che a ogni nuova versione del sistema bisognerebbe eseguire nuovamente

Figura 7.59 — Modello finale dei test case.

Segment 1

Step

ParameterType

name : string isVisible : boolean

1..n

id : string description : string

declares

Examples: String, Integer, Char, Boolean

id : string description : string 1..n

0..n

0..n

Node 1..n

1

1..n

groups

TestParameter

is of

id : string description : string

is for

comprises consist of

1

1 TestPath

FlowOfEvent id : string name : string type : string priority : Priority

1..n

ActualParameter

1

id : string description : string goal : string type : PathType

value : object 0..n initialises 1

1

TestPathResult

1..n OptionalFlow

status : TestStatus

MainFlow specifies

type : "B" 0..n includes

1

0..n collects

has 1

1 traces

UseCase

1

runs

1

code : string name : string lastUpdate : Date description : string duration : integer timeUnit : TimeUnit isAbstract : boolean

1

TestCase 0..n

TestCaseIteration

id : string name : string description : string priority : Priority

0..n

id : string date : Date systemVersion : string status : TestStatus time : Time

0..n

1..n needs

executed by

0..n

1

TestCaseSetup AlternativeFlow type : "A"

ExceptionFlow type : "E"

Tester

id : string name : string description : string

id : string name : string tel : string eMail : string

1 «enumeration» Priority HIGH LOW MEDIUM

«enumeration» PathType SUCCESS ERROR

«enumeration» TestStatus MINOR_ERROR MAJOR_ERROR SUCCESS

consists of 1..n SetupStep id : string description : string

86

Capitolo 7. Gli oggetti: una questione di classe

Figura 7.60 — Use case relativo alla generazione e produzione del documento. La specializzazione dello use case Produzione documento è dovuto al fatto che, in funzione del supporto, debbano essere eseguite diverse procedure. Per esempio la stampa su carta potrebbe richiedere un’operazione di impaginazione, mentre il formato destinato a una smartcard dovrebbe essere strutturato in funzione alla specifica tipologia.

Produzione formato cataceo

Produzione formato digitale Certification Authority

Produzione documento

«extend»

Reperimento dati

«include»

Generazione documento

User

«include»

DataSource

Reperimento template e script

tutte le verifiche necessarie, eventualmente eliminandone alcune divenute non più applicabili, aggiornandone altre e inserendone delle nuove. Ogni test è eseguito o controllato da un Tester, il quale, ahimè, è tipicamente gravato dal compito di effettuare svariati test. L’esecuzione di un percorso di test produce dei risultati che spesso è opportuno registrare. A tal fine è presente la classe TestPathResult.

Un esempio più tecnico: generatore automatico di certificati Presentazione Si presenterà ora un sistema che ha fatto parte dei desideri reconditi dell’autore… Lavorando nel settore IT si tende a contrarre una strana patologia denominata informatizzazione o ottimizzazione indiscriminata, il cui sintomo principale è dato dall’insofferenza all’inutile

87

UML e ingegneria del software: dalla teoria alla pratica

uso della risorsa tempo. La manifestazione classica del sintomo avviene qualora, per qualsivoglia motivo, si incappi in lungaggini burocratiche e/o in strane procedure manuali. In tali evenienze, nel cervello automaticamente si accende la famosa lampadina “si potrebbe fare tutto più velocemente e meglio con un opportuno sistema informatico”… E qui la vocina interna dell’autore è letteralmente presa da una crisi di delirio. L’esempio presentato in questo paragrafo rientra in questa categoria. A chi non è mai capitato di dover perdere intere mattinate in fila per ottenere un banale certificato? Ebbene l’idea è quella di realizzare un sistema a servizio del cittadino in grado di fornire una serie predefinita di documenti. Il sistema potrebbe essere fruito, in un primo momento, attraverso apposite postazioni (kiosk) e in successive evoluzioni direttamente dalla classica poltrona — ormai logora a forza di esempi — della propria abitazione… Chiaramente i “chioschi” potrebbero erogare documenti stampati su appositi moduli filigranati, mentre da una normale postazione Internet si potrebbero ottenere file firmati elettronicamente. Con un lettore/scrittore di smart card si potrebbe poi caricare nella propria carta il documento firmato digitalmente e quindi consegnarlo al destinatario o, più semplicemente, inoltrarlo via posta elettronica... Figura 7.61 — Descrizione delle componenti principali del processo per la produzione dei documenti richiesti.

Reperimento template documento e relativo script

Template e scripts

DataSource Esecuzione script (acquisizione dati)

Fornattazione e produzione documento

88

Capitolo 7. Gli oggetti: una questione di classe Da più di venti anni si sente parlare di uffici “senza carta”. L’autore ha sempre pensato che, con tale espressione, ci si riferisse all’utilizzo di documenti digitali al fine di ridurre il consumo mondiale di carta; forse invece si intendeva parlare della mancanza di carta dovuta all’intenso utilizzo.

In ogni modo, l’obiettivo è realizzare un sistema in grado di fornire tutta una serie di documenti standard utilizzando appositi template. I dati da inserire in questi template sono richiesti direttamente alle sorgenti (datasource): sistemi anagrafici, casellario giudiziario, università, scuole superiori, banche, ecc. Chiaramente, per rendere tutto ciò possibile, ognuno di questi fornitori di dati dovrebbe offrire apposite interfacce (i famosi wrapper) dei loro sistemi, a cui richiedere informazioni utilizzando interfacce predefinite. Il traffico dei dati deve poi avvenire sotto il vigile controllo di un robusto sistema di sicurezza. In contesti come questi, la flessibilità assume un ruolo di primaria importanza: dovrebbe essere sempre possibile aggiungere nuovi template di documenti, nuove sorgenti di dati, ecc. senza dover ogni volta modificare il sistema. Pertanto, più che realizzare un singolo sistema, l’obiettivo è realizzare un apposito framework da personalizzare per le varie esigenze.

Costruzione del modello Si focalizza ora l’attenzione su una serie di componenti del sistema. Particolare attenzione è conferita al motore in grado di raccogliere i dati (Data Collector Engine) sparsi per le varie sorgenti, necessari alla produzione dei documenti. Sono illustrati alcuni componenti ritenuti particolarmente significativi, data l’impossibilità di presentarli tutti in maniera diffusa. Le parti proposte supportano la realizzazione di un semplice interprete che, a seguito della richiesta di produzione di uno specifico documento, sia in grado di inizializzare e quindi eseguire un apposito grafo di oggetti rappresentanti il “programma” da eseguire per il reperimento dati. Il relativo diagramma dei casi d’uso è mostrato in fig. 7.60, mentre nella successiva fig. 7.61 viene illustrato in maniera semplificata il workflow. L’associazione tra tipologia di documento e dettaglio del relativo programma da eseguire, per questioni di flessibilità, dovrebbe essere dichiarativa. In altre parole, è opportuno specificare gli script attraverso appositi file di configurazione (verosimilmente XML) che, per questioni di efficienza, potrebbero essere caricati in memoria all’avvio del sistema. Sempre per questioni di flessibilità, il sistema dovrebbe tentare di acquisirli ogniqualvolta non siano disponibili in memoria. Questo meccanismo permetterebbe di aggiungere dinamicamente nuove tipologie di documentazione, o modificarne altre, senza dover ricompilare il sistema, e tanto meno, senza dover interrompere la fruizione dello stesso. Per cominciare, si analizzi una semplice istruzione. La relativa struttura, dal punto di vista logico, potrebbe avere la seguente forma:

89

UML e ingegneria del software: dalla teoria alla pratica {, } = . ( {})

Un esempio di un semplice script potrebbe essere: userFiscalCode name surname dateOfBirth offencesCommitted

= = = = =

smartcard.getFiscalCode(); smartcard.getName(); smartcard.getSurname(); utilityAnag.getDateFromFC(userFiscalCode); casellarioGiudiziario.getOffences(userFiscalCode, name, surname, dateOfBirth);

L’esempio — che per ovvi motivi risulta un po’ artificioso — prevede tre sorgenti di dati, di cui due esterne e una interna. Le DataSource esterne sono: • la smartcard, da cui prelevare le informazioni relative all’utente; • il casellario giudiziario, in grado di fornire eventuali imputazioni a carico dell’utente; La sorgente interna è costituita da un componente denominato utilityAnag in grado di eseguire elaborazioni sui dati anagrafici forniti in input.

Figura 7.62 — Modello dello script per il reperimento dei dati.

Script id : string name : string description : TestStatus 1..n steps

1..* inputs

DataGetter id : string name : string description : string timeout : long

1

1

FormalParameter

0..*

id : string name : string outputs description : string 1..*

type 0..*

ParameterType

type : string 1 description : string

90

Capitolo 7. Gli oggetti: una questione di classe

Una volta reperito lo script, si tratta di eseguirne tutte le istruzioni. Da tener presente che l’esecuzione di alcune funzioni potrebbe essere bloccata da quella di altre. Per esempio l’ottenimento dello stato penale o, nel caso peggiore, della lista delle condanne inflitte all’utente, è subordinata al reperimento del codice fiscale, del nome, del cognome e della data di nascita di un individuo. Un buon punto di inizio consiste nel modellare le funzioni da eseguire, denominate DataGetter (i singoli passi di cui è composto un programma). Nelle classi del modello successivo non è stato visualizzato il compartimento dedicato ai metodi. Ciò è dovuto al fatto che si tratta di classi entità appartenenti al modello del dominio, e quindi i relativi metodi sarebbero i classici get/set.

Uno script è costituito da diverse funzioni e ciascuna di esse può essere utilizzata in diversi script (relazione di aggregazione tra le classi Script e DataGetter). Analizzando la struttura della classe DataGetter si nota che si tratta di funzioni che, nel caso generale, consentono di produrre come risultato diversi valori. Tra i vari attributi, ne compare uno denominato timeout. Ciò è necessario per evitare che l’esecuzione di uno script possa rimanere in uno stato di attesa indefinita, qualora una funzione non possa reperire i dati dalla relativa sorgente a causa di inconvenienti (per esempio congestione del traffico di rete). Pertanto l’attributo timeout è utilizzato per interrompere l’esecuzione di una specifica richiesta di dati qualora questi non pervengano entro uno specifico lasso di tempo. Diverse classi possiedono campi descrittivi assolutamente inutili per il sistema. Questi però assumono un’importanza notevole per la realizzazione di meccanismi di logging, per il debug e soprattutto per la realizzazione di interfacce utente atte a realizzare le installazioni del framework (i famosi script). Per esempio, dovendo dichiarare la struttura di uno script, potrebbe essere di grande aiuto riuscire a individuare le varie istanze della classe DataGetter da inserire, attraverso i valori degli attributi nome e descrizione. L’esecuzione di ciascuna funzione necessita di ricevere i valori di specifici parametri formali e produrre quelli di output. I parametri formali sono rappresentati dalla classe FormalParameter. Pertanto, un oggetto di tipo DataGetter può disporre di nessuno o di una serie di parametri formali di input e di almeno uno di output. I risultati generati dall’esecuzione di ciascuna istanza DataGetter devono poter essere gestiti dal sistema. Pertanto è necessario realizzare un apposito meccanismo per la memorizzazione e il ripristino dei dati (DataStorage, fig. 7.63). In contesti come questi, l’utilizzo di interfacce, e più in generale del polimorfismo, rappresenta un meccanismo obbligatorio. In particolare si vogliono utilizzare degli oggetti che implementano un’opportuna interfaccia, senza però curarsi dei dettagli della relativa implementazione. Il vantaggio è che quest’ultima può variare senza generare ripercussioni sulle classi clienti: meccanismi come quello per la memorizzazione dei risultati si prestano a essere utilizzati am-

91

UML e ingegneria del software: dalla teoria alla pratica

Figura 7.63 — Modello per la realizzazione di un meccanismo di memorizzazione dei dati (data storage). Le classi DataStorageImp e DataStorageElementImp sono visualizzate senza la sezione dei metodi, giacché quelli più interessanti sono indicati nelle relative interfacce.

DataStorage +isEmpty() +isElementPresent() +getDataElement() +getDataElements() +removeAllElements()

DataStorageImp - id : String - description : String 1

«use» - dataElements DataStorageElement +addElement() +setName() +setType() +setValue() +getName() +getType() +getValue()

0..n

DataStorageElementImp - name : String - value : Object 1 - dataElements

0..n

ParameterType - type : String - description : Object

piamente in diverse parti del sistema, pertanto la variazione dell’implementazione non genera ripercussioni in quanto “protetta” da apposite interfacce. Il modello di fig. 7.63 mostra come un’istanza di DataStorage sia costituita da un insieme di elementi, DataStorageElement. Ciascuno di questi elementi appartiene esclusivamente a un DataStorage (ciò è evidenziato dalla presenza della relazione di composizione). Entrambe le classi prevedono un’apposita interfaccia, ciò fa si che l’implementazione del DataStorage e dei relativi elementi possa variare senza avere ripercussioni sulle restanti parti del sistema, e che lo stesso possa utilizzare diverse versioni (per esempio una persistente e una no) per diversi fini. A questo punto è necessario inserire delle classi di gestione in grado di coordinare e controllare l’esecuzione dei vari script. In altre parole è necessario inserire la classe le cui istanze hanno la responsabilità di gestire l’esecuzione degli oggetti DataGetter che costituiscono i vari script. I modelli presentati finora erano essenzialmente costituiti da elementi statici: rappresentazioni formali di oggetti appartenenti al dominio considerato di carattere passivo. Ora l’attenzione viene spostata sul motore in grado di far funzionare in maniera coordinata i vari ingranaggi (cfr fig. 7.64).

92

Capitolo 7. Gli oggetti: una questione di classe

Figura 7.64 — Meccanismo di esecuzione degli script. Script

DataSource

-id : string -name : string -description : TestStatus 1..n

-dataSource -steps

1..*

+isEmpty() +isElementPresent() +getDataElement() +getDataElements() +removeAllElements()

1 0..*

DataGetter

DataGetterExecutor

-id : string -name : string -description : string -timeout : long 1

DataStorage

+getId() +getDescription() +getSupportedDataGetters() +executeDataGetter() +terminateExecution()

- dataGetter 1

-dataSourceId : string 0..* -dataGetterState : DataGetterStatus +isReadyToCollect() +isCompleted() +isAwaitingForResults() +execute()

1

-manager -esecutors 1

ScriptManager -scriptId : string

0..* +isComplete() +start() 1

1 -inputs

0..*

-outputs

1..*

-actualParameters

FormalParameter -id : string -name : string -description : string 0..*

ActualParameters 1

-type : string -description : string

-dataStorage

1

DataStorageImp -id : String -description : String

-formalParameter 1

-type 1 ParameterType

0..*

-parameter 0..*

1..*

ActualParameter -actualParName : string -initialValue : Object

«enumeration» DataGetterStatus READY AWAITING_INPUTS READY_TO_COLLECT AWAITINGS_RESULTS COMPLETED TERMINATED

In primo luogo, per ogni passo dello script, ossia per ciascun oggetto DataGetter, è necessario selezionare il DataSource in grado di fornire i parametri richiesti. Anche il concetto di DataSource è un formidabile esempio di polimorfismo. Esistono diverse tipologie di sorgenti di dati: remote, locali, realizzate su diversi sistemi, con differenti tecnologie e funzionamenti ecc., tutte pilotate per mezzo di una medesima interfaccia (DataSource appunto); l’invocazione di uno stesso metodo, executeDataGetter() per esempio, genera un comportamento assolutamente diverso in funzione della particolare DataSource che lo implementa. Ogni DataGetter necessita di un apposito oggetto (DataGetterExecutor) che si assume la responsabilità di coordinarne l’esecuzione (cfr fig. 7.64), di reperire i valori dei parametri di input, di memorizzare quelli prodotti in output e di effettuare l’associazione con la sorgente dati in grado di eseguirne la funzione. Chiaramente ogni DataGetter può essere inserito in più script, e quindi, in ciascuno di essi, può eventualmente essere associato con un diverso DataGetterExecutor (per esempio in un opportuno script la stessa funzione viene richiesta a un diverso DataSource ). Le istanze della classe DataGetterExecutor agiscono a livello di singola funzione e pertanto devono essere a

93

UML e ingegneria del software: dalla teoria alla pratica

loro volta controllate da un altro oggetto che opera a livello di script. Tale oggetto è lo ScriptManager che espleta due macroservizi: controllo dell’esecuzione dell’intero script e memorizzazione dei dati prodotti nel corso dell’esecuzione dei vari DataGetter. Quest’ultima capacità è enfatizzata dal fatto che ScriptManager implementa l’interfaccia DataStorage e quindi possiede anche un comportamento di immagazzinamento dati. È interessante notare come questa classe possa essere considerata a tutti gli effetti una specializzazione della classe DataStorageImp. Tale specializzazione non è ottenuta per mezzo della canonica relazione di generalizzazione, bensì attraverso una composizione; in altre parole, ScriptManager deroga a DataStorageImp la fornitura dei servizi di memorizzazione dei dati. Ogni oggetto DataGetterExecutor, non appena avviato, inizializza il proprio stato interno: in altre parole, verifica che tutti i parametri di input di cui necessita il relativo oggetto (DataGetter) siano disponibili (cfr fig. 7.65). In caso di esito positivo, transita nello stato di pronto all’esecuzione (Ready to collect), in caso contrario, invece, transita nello stato di attesa parametri (Awaiting inputs). Questa verifica avviene mol-

Figura 7.65 — Ciclo di vita degli oggetti DataGetterExecutor.

inizializzazione oggetto verifica negativa dei parametri di ingresso

READY

verifica positiva dei parametri di ingresso

AWAITING_INPUTS

verifica positiva dei parametri di ingresso

AWAITING_RESULTS

terminazione prematura della procedura

TERMINATED

READY_TO_COLLECT

avvio esecuzione

ottenimento di tutti i risultati

COMPLETED

94

Capitolo 7. Gli oggetti: una questione di classe

to semplicemente: ottiene dall’oggetto DataGetter la lista dei parametri di input, eventualmente ne cambia il nome come suggerito dagli oggetti ActualParameter, e quindi chiede conferma dell’esistenza all’oggetto ScriptManager (invoca il metodo isElementPresent) che, in questo contesto, si comporta da DataStorage. Come si può notare, la relazione tra ScriptManager e DataGetterExecutor non è dotata di navigabilità in quanto, come appena evidenziato, è necessario poter navigare gli oggetti in entrambi i sensi. Qualora un oggetto DataGetterExecutor sia pronto a essere eseguito e ottenga il permesso dall’istanza ScriptManager (invocazione del metodo execute), acquisisce l’appropriata istanza dell’oggetto DataSource (si tratta dell’istanza di una classe in grado di gestire la comunicazione con il reale fornitore, un proxy), invoca il metodo executeDataGetter e si pone in uno stato di attesa dei risultati (Awaiting results). Ottenuti anche questi, li memorizza nel D a t a S t o r a g e gestito dall’oggetto ScriptManager e quindi si pone nello stato di completamento esecuzione (Completed).

Figura 7.66 — Diagramma degli oggetti relativo a un ipotetico stato di esecuzione dello script mostrato in precedenza.

ScrptMngr023 : DataSotrageImp

RetrieveOffences : ScriptManager

getFiscalCode : DataGetterExecutor

getFiscalCode : DataGetter

userFiscalCode : FormalParameter

getName : DataGetterExecutor

getName : DataGetter

name : FormalParameter

getSurame : DataGetterExecutor

getSurname : DataGetter

surname : FormalParameter

getDataFromFC : DataGetterExecutor

getDataFC : DataGetter

dateOfBirth : FormalParameter

userFiscalCode : FormalParameter utilityAnag : DataSource

getOffences : DataGetterExecutor

getOffences : DataGetter userFiscalCode : FormalParameter

name : FormalParameter

surname : FormalParameter

dateOfBirth : FormalParameter CasellarioGiudiziario : DataSource

smartCard : DataSource

95

UML e ingegneria del software: dalla teoria alla pratica

L’evento di memorizzazione di un parametro nell’oggetto ScriptManager avvia una serie di processi. In primo luogo si verifica se l’intero script abbia terminato la propria esecuzione (tutti gli oggetti DataGetterExecutor si trovano nello stato Completed); in caso negativo si verifica se i nuovi parametri siano in grado di sbloccare qualche esecutore in attesa dei parametri di input. Il ciclo di vita degli oggetti DataGetterExecutor è mostrato nel seguente diagramma degli stati. Sebbene tale formalismo non sia stato ancora presentato, la sua comprensione è piuttosto immediata. Nel diagramma degli oggetti di fig. 7.66 è mostrato come lo script presentato in precedenza possa essere memorizzato negli oggetti istanza del modello disegnato fino a questo punto. Nel diagramma, la rappresentazione di oggetti di tipo DataSource non è correttissima. Chiaramente si tratta di istanze di classi che implementano tale interfaccia e che quindi, per questioni di chiarezza, possono essere considerate come oggetti di tipo DataSource. Per terminare la descrizione di questo meccanismo è necessario riportare alcune riflessioni. In primo luogo si può notare che la relazione tra gli oggetti ScriptManager e i relativi Script è di tipo relazionale e non OO. In altre parole, esso avviene per mezzo dell’esportazione dell’identificatore dello script, scriptId — come si usa nei database relazionali per relazionare le tabelle — anziché per mezzo di apposito attributo che contenga il riferimento in memoria dell’apposito oggetto script. Questo meccanismo è utilizzato per “disaccoppiare” le classi. Ciò permette di raggruppare gli oggetti in sottoinsiemi ad alto livello di coesione e minimo accoppiamento con gli oggetti esterni. Ciò è particolarmente utile per la progettazione component based del sistema (per esempio un componente si potrebbe occupare della gestire degli script, un altro di coordinarne l’esecuzione, ecc). Lo stesso meccanismo dovrebbe essere utilizzato tra gli oggetti ActualParameter e FormalParameter. Un altra constatazione è relativa all’utilizzo dei parametri attuali. Ciò si è reso necessario essenzialmente per due motivi: disporre della capacità di impostare un valore iniziale per i parametri di input (attributo initialValue) ed evitare problemi di omonimia. Si consideri il seguente esempio di un ipotetico script per la generazione di un documento relativo ai beni immobili posseduti da una coppia di coniugi:

userFiscalCode

=

smartcard.getFiscalCode();

userName

=

smartcard.getName();

userSurname

=

smartcard.getSurname();

partnerFiscalCode

=

registryOffice.getPatnerFiscalCode(userFiscalCode);

partnerName, partnerSurname =

registryOffice.getNominative(partnerFiscalCode);

listOfProperties

landRegistryOffice.getProperties(userFiscalCode,

=

partnerFiscalCode);

96

Capitolo 7. Gli oggetti: una questione di classe Chiaramente si tratta di un esempio e come tale va considerato. È evidente che, per poter ottenere documenti più articolati, sarebbe necessario incorporare opportuni costrutti nella grammatica utilizzata dagli script, come flussi alternativi e cicli ripetitivi.

Da una prima analisi dello script, probabilmente, non emergerebbe nulla di strano, ma analizzandolo più in dettaglio sarebbe possibile evidenziare alcuni problemi. Per esempio, la funzione dell’ufficio anagrafico (registry office) in grado di fornire il nome e il cognome dell’utente, verosimilmente, dovrebbe assumere una sintassi come quella riportata di seguito (si ricordi che si tratterebbe di una funzione utilizzata per generare diversi script): userName, userSurname = registryOffice.getNominative(userFiscalCode);

Ciò significa che l’oggetto DataGetterExecutor atto a eseguire la funzione si dovrebbe occupare di reperire dallo ScriptManager — che a sua volta delega al relativo DataStorage — il valore di un elemento di nome userFiscalCode e quindi memorizzare i risultati prodotti negli elementi di nome userName e userSurname. Il problema è che il metodo dovrebbe essere invocato per due persone, e quindi il risultato prodotto durante la seconda chiamata andrebbe a sovrascrivere diverse informazioni individuate dallo stesso identificatore. L’anomalia è risolta grazie alla presenza dei parametri attuali. Pertanto nell’esempio, si avrebbero le seguenti associazioni: userFiscalCode userName userSurname

viene rinominato in viene rinominato in viene rinominato in

partnerFiscalCode partnerName partnerSurname

Questo meccanismo permette all’oggetto DataGetterExecutor, durante il reperimento del parametro di input userFiscalCode, di verificare l’esistenza dei parametri attuali e quindi di reperirne i nomi (partnerFiscalCode). Questi sono quindi utilizzati come chiave di ricerca nel DataStorage. Lo stesso meccanismo è utilizzato per memorizzare i parametri generati dall’esecuzione della funzione. Pertanto la presenza dei parametri attuali consente di definire liberamente e genericamente la sintassi delle funzioni DataGetter senza incidere sulla funzionalità degli script. Nella classe DataGetterExecutor non è presente un legame con la sorgente di dati che dovrà eseguire la funzione, bensì è presente il relativo identificatore. In sostanza avviene un bind dinamico. Affinché ciò possa realizzarsi, è necessario fornire un meccanismo in grado di memorizzare e gestire le varie sorgenti di dati. In sostanza occorre realizzare un registry in cui inserire gli identificatori delle datasource disponibili corredati dal percorso delle relative classi. Questo compito è abbastanza semplice in linguaggi come Java in cui esistono meccanismi della riflessione e tutti gli oggetti discendono da un medesimo genitore. Pertanto, attraverso il metodo classForName è possibile generare dinamicamente

97

UML e ingegneria del software: dalla teoria alla pratica

«singleton»

DataSourceRegistry +getIstance() +addDataSource() +getDataSource()

-instance

dataSourceId : String

Figura 7.67 — DataSourceRegistry.

-dataSources 0..1

DataSource

0..1

«use»

DataSourceRegistryLoader Loader

istanze di classi, il cui percorso è specificato come parametro del metodo. In altri linguaggi sarebbe stato necessario memorizzare istanze — e non il percorso della propria classe — di oggetti implementanti una comune interfaccia. In ogni modo, le classi inserite non sono, ovviamente, le sorgenti di dati, bensì classi (proxy) in grado di gestirne il collegamento: veri e propri strati di interfacciamento. Sebbene, forse per un eccessivo accademismo, diversi tecnici e docenti universitari siano convinti dell’opposto, l’autore ritiene che nel modello di disegno sia assolutamente legittimo utilizzare i meccanismi propri del linguaggio e della tecnologia con cui si è deciso di implementare il sistema. Chiaramente, nel modello di analisi lo sarebbe molto meno.

Il DataSourceRegistry implementa il famoso pattern Singleton, pertanto tutti gli oggetti che ne hanno bisogno possono ottenere dinamicamente il riferimento all’unica istanza (cfr fig. 7.67). L’istanza DataSourceRegistry permette di reperire la descrizione delle DataSource desiderate specificandone unicamente l’id. La presenza dell’associazione di qualificazione implica che si tratta di una relazione tra un’istanza — l’unica — della classe DataSourceRegistry e un insieme di oggetti DataSource, in cui il reperimento delle singole istanze (DataSource) avviene per mezzo del relativo id. Al fine di conferire un aspetto compiuto ai vari frammenti del sistema presentati fino a questo punto, occorre disegnare un meccanismo che si occupi di creare gli oggetti necessari all’esecuzione dei vari script (cfr fig. 7.68). In altre parole, a seguito della richiesta

98

Capitolo 7. Gli oggetti: una questione di classe

Figura 7.68 — ScriptsRepository.

«singleton»

DataStorage

ScriptsRepositry - scriptStorage

+getIstance() +addScript() +getScript() +removeScript() +removeAll()

1

1

+isEmpty() +isElementPresent() +getDataElement() +getDataElements() +removeAllElements() «use»

«use» ScriptManager «use» ScriptDescriptor - id : string

DataStorageElement +addElement() +setName() +setType() +setValue() +getName() +getType() +getValue()

1 1..n DataGetterExecutorDescriptor - dataGetterId : string - dataSourceId : string 1 * ActualParameter

dell’utente di generare un particolare documento, un apposito componente deve acquisire le informazioni del relativo script e quindi realizzarne l’immagine in grado di essere eseguita. In particolare, gli oggetti necessari sono lo ScriptManager corredato dal relativo DataStorage, i vari DataGetterExecutor con gli eventuali ActualParameter. Il problema da risolvere è quello di creare una struttura in grado di memorizzare un’immagine del programma, uno ScriptsRepository, da cui generare le istanze runtime. Gli oggetti da inserirvi devono permettere di descrivere completamente e senza ambiguità gli stati in cui porre i vari oggetti DataGetterExecutor che compongono i vari script. Lo

UML e ingegneria del software: dalla teoria alla pratica

99

stato di ciascuno di essi è dato dai valori assunti dai relativi attributi e dalle relazioni instaurate con gli altri oggetti. Per esempio, per ogni DataGetterExecutor è necessario inizializzare le istanze specificando il DataGetter di riferimento, il DataSource in grado di eseguire la funzione, la lista degli attributi attuali e le relative associazioni con quelli formali, ecc. Quindi, dato l’identificatore di uno script, è necessario reperire la lista dei vari DataGetterExecutor corredati dalla descrizione del relativo stato. Ciò è ottenibile associando allo ScriptsRepository un DataStorage in grado di memorizzare le associazioni tra gli identificatori degli script e opportuni grafi di oggetti. Da tener presente che l’attributo value è di tipo Object e quindi è in grado di memorizzare un qualsiasi grafo di oggetti serializzati. Nel diagramma di fig. 7.68 è mostrato sia lo ScriptsRepository, sia il descrittore di script. Come si può notare, tale struttura risulta completamente idonea a essere memorizzata in un apposito file XML di configurazione. L’unica istanza ScriptsRepository potrebbe venir inizializzata con l’associazione dei vari script sia all’atto dell’avvio del server, sia ogniqualvolta il tentativo di reperimento di uno script fallisca, magari tentando di localizzare un apposito file XML. Chiaramente, qualora fallisse anche questo secondo tentativo, il processo relativo alla richiesta del particolare documento terminerebbe in una condizione di errore. Come si può notare, né l’oggetto DataStorage, né quello ScriptRepository sono al corrente del tipo di struttura che viene memorizzata (ScriptDescriptor).

Classi “di classe” Quali linee guida è possibile seguire per aumentare il livello di eleganza dei diagrammi delle classi? In questa sede si concentrerà l’attenzione sullo stile limitando, per quanto possibile, digressioni relative alla semantica, sebbene poi le due caratteristiche non possano essere trattate in maniera completamente disgiunta. Il formalismo dei diagrammi delle classi si presta a essere impiegato per la realizzazione di molteplici tipologie di modelli statici: da quelli di business e dominio realizzati per aumentare la propria comprensione dell’aera oggetto di studio e dei requisiti utenti, a quelli di analisi e disegno, volti a trasformare i requisiti in soluzioni tecniche sempre di più basso livello, dai modelli della base di dati a quelli dei messaggi, e così via. Logica conseguenza è che lo stile deve essere adeguato ai fini che, di volta in volta, si intende perseguire con la realizzazione dello stesso modello, nonché alla platea dei fruitori. L’analisi dettagliata dei vari modelli realizzabili attraverso il formalismo dei diagrammi delle classi è oggetto di studio del Capitolo 8 dove sono presenti innumerevoli suggerimenti specifici per ogni tipologia di modello presentata.

100

Capitolo 7. Gli oggetti: una questione di classe Contrariamente a quanto si potrebbe pensare, curare lo stile dei diagrammi per renderli armoniosi ed eleganti non è un esercizio fine a sé stesso o un vezzo estetico. Diagrammi ben realizzati anche dal punto di vista grafico risultano più facilmente fruibili, elevano il livello di comprensione e memorizzazione, permettono di aumentare il contenuto informativo — per esempio utilizzando in maniera intelligente i colori —, concorrono a diminuire il grado di rifiuto psicologico che si può instaurare nei fruitori privi di dimestichezza con il formalismo o estranei al modello stesso, e così via.

Organizzazione del modello La realizzazione di modelli eleganti richiede una serie di prerequisiti indispensabili. Uno dei primi consiste nell’aver selezionato nomi significativi per gli elementi utilizzati (package, diagrammi, classi, relazioni, metodi e attributi). Ciò permette di “leggere” i diagrammi minimizzando lo sforzo necessario per la comprensione: un diagramma ben realizzato già da solo è in grado di illustrare l’organizzazione dei relativi elementi, semplicemente collegando i nomi delle classi con le associazioni e i ruoli recitati. Gli stessi diagrammi devono avere nomi chiari che consentano di stabilire immediatamente quale sia l’aspetto o l’organizzazione che intendono illustrare. Spesso, per “risparmiare” qualche carattere alcuni tecnici danno luogo a nomi che solo un potente algoritmo di offuscazione potrebbe rendere meno leggibili. L’esperienza insegna che, senza esagerare, conviene sempre dettagliare quanto più possibile i nomi delle varie entità: tutto diviene più lineare, semplice, si riduce la necessità di ricorrere a ulteriori spiegazioni, e così via. Inoltre, convenzioni di fatto suggeriscono di riportare i nomi delle classi al singolare, anche se rappresentano molteplici oggetti, e di utilizzare per i metodi e le relazioni nomi che enfatizzino il verbo. Un altro importantissimo requisito consiste nel dar luogo a una corretta organizzazione in package (questo argomento è stato trattato nel capitolo precedente e verrà ripreso in quello successivo). Da tener presente che esistono due organizzazioni parallele in termini di package: •

sistema da produrre (quella in cui organizzare i vari moduli di codice e quindi il sistema “eseguibile”);



modello (o più in generale dell’intero progetto).

L’oggetto di studio del presente paragrafo è la seconda, la quale, evidentemente, include l’organizzazione del codice, nel senso che possiede sicuramente tutte le classi che dovranno essere implementate, con in più i diagrammi, che ovviamente forniscono molte informazioni su come implementare il sistema ma non hanno, per così dire, un corrispettivo diretto in termini implementativi.

UML e ingegneria del software: dalla teoria alla pratica

101

Per quanto riguarda la struttura del modello, in ogni package è opportuno disporre di almeno un diagramma illustrante le relazioni tra le classi incluse (che dovrebbero possedere un livello di coesione non trascurabile, criterio principale di raggruppamento). Qualora poi gli elementi o gli aspetti da presentare risultino molteplici, oppure qualora si voglia conferire particolare rilievo all’organizzazione di alcuni di essi, è possibile dar luogo a ulteriori specifici diagrammi delle classi realizzati all’uopo. Non è sempre necessario riportare in ogni diagramma tutte le classi con tutte le relazioni: spesso è sufficiente riportare solo gli elementi di interesse per il contesto preso in esame. Qualora un package o un diagramma sia eccessivamente popolato, andrebbe anche considerata l’eventualità di procedere a un’ulteriore strutturazione. Altra buona norma consiste nel realizzare un diagramma che mostri, per ciascun package del modello, la relativa organizzazione. In particolare, potrebbe risultare vantaggioso dar luogo a un diagramma con il nome del package in cui riportare: 1. una breve descrizione del package corredata dal relativo criterio di aggregazione e delle finalità; 2. la struttura del package stesso, sia in termini di eventuali sottopackage, sia dei diagrammi delle classi contenute, il tutto corredato da una breve spiegazione.

Tutto ciò è particolarmente utile alla navigabilità dei modelli: la quasi totalità dei tool permette di fruire i modelli eseguendo il famoso doppio click sugli elementi mostrati nei vari diagrammi. Pertanto, disponendo, per ogni package, di un diagramma che ne mostri la struttura, permette di visitare le varie parti (diagrammi, sottopackage, classi, ecc.) effettuando un doppio click sugli elementi che le rappresentano.

Criteri generali Anche nell’ambito dei diagrammi delle classi mantengono la loro validità i criteri generali validi per qualsiasi tipologia di diagramma, per esempio, è cosa buona limitare il numero degli elementi presenti in ciascun diagramma a sette (più o meno due): sembrerebbe che questo sia il valore più confacente alla mente umana [BIB28]. È opportuno evitare il più possibile la sovrapposizione di linee, ricercare — anche in modo artificiale — simmetrie, mantenere un elevato aspetto di pulizia e chiarezza, cercare di rappresentare relazioni di ereditarietà con sviluppo verticale, cercare di rappresentare le altre relazioni attraverso linee rette, ecc.

102

Capitolo 7. Gli oggetti: una questione di classe

La coerenza o consistenza recita sempre un ruolo di primo piano, il cui livello è direttamente proporzionale al grado di comprensione e apprendimento. I fruitori dei diagrammi tendono a prendere familiarità con le convenzioni utilizzate e quindi diventano pratici nel riuscire a intuire rapidamente i concetti che stanno alla base di tali convenzioni. Ordine e posizione degli elementi non dovrebbero, in teoria, avere particolare significato ma è evidente che i lettori tendono, più o meno consciamente, a conferire maggiore importanza agli elementi più evidenti e a rispettare un ordine di lettura che va da sinistra verso destra — faranno eccezione lettori arabi o asiatici — e dall’alto verso il basso. Pertanto è utile cercare di sistemare in una posizione centrale l’elemento a cui si intende conferire maggiore attenzione — eventualmente cercando di evidenziarlo artificialmente utilizzando un tratto leggermente più spesso, una tinta più marcata, ecc. — ed è bene riportare gli elementi interconnessi considerando il tipico ordine di lettura.

Così come nella codifica, anche nella modellazione è importante riportare commenti al fine di chiarire particolari linee guida utilizzate per la costruzione del modello. Si potrà quindi spiegare il perché di specifiche decisioni, illustrare porzioni particolarmente complesse, attributi molto importanti, ecc. Le note però non devono “appesantire” il diagramma. Quindi è consigliabile ricorrere all’elemento standard UML — la porzione di foglio di carta con un angolo piegato — impostandone un colore di sfondo trasparente con linee e testo in grigio.

Qualora si utilizzi un pattern, è molto utile sia evidenziarlo, magari variando sensibilmente i colori delle relative classi, sia specificare chiaramente il ruolo recitato dalle varie classi che lo compongono. Ciò è ottenibile attraverso l’utilizzo degli stereotipi eventualmente arricchiti da opportune note. Per esempio (cfr fig. 7.69) qualora si utilizzi il pattern Observer, è opportuno evidenziare le classi osservate (Subject) e quelle osservatrici (Observer). Nel pattern Composite le classi da evidenziare sono quella componente (Component), base (Leaf) e iterativa (Composite). Un altro elemento di stile consiste nel rappresentare interfacce e classi astratte con una tinta più chiara al fine di evidenziarne la funzione principale ovvero definire un tipo e non poter essere istanziate direttamente. Lo stesso vale per elementi secondari, come tipi enumerati, ad esempio, i quali non dovrebbero assolutamente catturare l’attenzione del lettore. Si consiglia pertanto di ricorrere agli stessi colori dei campi note. Per quanto concerne la rappresentazione grafica delle interfacce, è consigliabile ricorrere il più possibile alla notazione basata sul simbolo della circonferenza. Se da un lato questa notazione nasconde importanti dettagli (i metodi di cui è composta la classe), dall’altro permette di evidenziarle a colpo d’occhio e di realizzare diagrammi più eleganti e lineari.

103

UML e ingegneria del software: dalla teoria alla pratica

Figura 7.69 — Diagramma di “stile” che mostra l’utilizzo dei pattern Composite e Observer. In particolare si immagina una situazione — quale per esempio quella di un cinema — in cui una particolare struttura, come una sala (classe Structure) possa essere impostata secondo una qualsivoglia organizzazione gerarchica (pattern Composite) e di cui sia importante sapere, in tempo reale, la situazione dell’allocazione dei posti, per aggiornare sia un display, sia le varie postazioni client per la vendita di biglietti. A tal fine è stato utilizzato il pattern Observer e le classi derogate all’aggiornamento dei vari dispositivi devono estendere la classe ConcreteObserver.

«subject» SubjectObserved ... +addObserver() +removeObserver() +notify() ...

-observers

*

1 for all o in observers { o.update() }

-subject

Structure

*

1

+getStatus() ...

... +update() ...

ConcreteObserver +update() ...

«component» Sector id : string nome : string

«leaf» Seat ...

«observer» Observer

... status = subject.getStatus() ...

*

«composite» Zone ...

1

Livello di dettaglio Un altro elemento molto importante — ricorrente in ogni attività di modellazione e quindi particolarmente presente nella costruzione di modelli UML — è il livello di dettaglio a cui spingersi. Nel contesto dei diagrammi delle classi assume una forma diversa a seconda del particolare modello preso in considerazione. Queste problematiche sono riprese e illustrate abbondantemente nel corso del capitolo successivo. Per esempio, in un modello a oggetti del dominio è necessario mostrare tutte e sole le classi appartenenti all’area oggetto di studio che il sistema dovrà automatizzare (quindi l’analisi non va estesa all’intera area di business); per ciascuna di esse ha senso evidenziare

104

Capitolo 7. Gli oggetti: una questione di classe

i relativi attributi, mentre le operazioni assumono un’importanza secondaria, è utile attribuire un nome esplicativo a ogni associazione e via discorrendo. Per quanto riguarda il modello di disegno esistono due macroversioni: quello di specifica e quello di implementazione. Il secondo dovrebbe essere allineato con il codice, e quindi spesso è ottenuto attraverso procedure di reverse engineering — o sincronizzazione in linea dell’attività di codifica — mentre il primo è il modello da consegnare ai programmatori affinché possano procedere con l’implementazione del sistema. In questo caso il livello di dettaglio cui giungere è cruciale e dipende fortemente dalle capacità del team degli sviluppatori. Se non si dispone di un team preparato e con buona visione del disegno globale, è assolutamente necessario investire molto tempo nel realizzare modelli di dettaglio, relegando, ahimè, i vari sviluppatori al mero ruolo di “riempitori di classi”. Se invece il team di programmatori è ben preparato, è possibile dar luogo a un più razionale utilizzo della risorsa tempo, concentrandosi sul disegno di insieme, limitandosi a disegnare le classi e i metodi più importanti da corredare eventualmente con opportune linee guida. Si demanda quindi alle capacità dei singoli programmatori la definizione del cosiddetto codice di “impalcatura” (scaffolding, attributi e metodi privati, classi helper, metodi di get e set, ecc.). Se poi dovesse verificarsi lo scenario peggiore (architetto con capacità di disegno argomentabili…), allora, per la salvaguardia della salute del team di sviluppo, è opportuno che il modello resti più possibile sospeso nel mondo dell’astratto… tenendo bene a mente che nell’informatica “capolavoro di arte moderna” non sempre assume una connotazione positiva.

Diagrammi delle classi a colori L’utilizzo dei colori nei diagrammi delle classi è un argomento che negli ultimi anni ha assunto un certo rilievo — non fosse altro per il dibattito che vi ruota intorno — tanto da meritare di essere trattato in un apposito paragrafo. Tutti i modellatori, prima o poi, si rendono conto che utilizzare opportuni colori nei diagrammi tende ad aumentarne notevolmente la comprensione: nelle parole di Peter Coad, “i colori forniscono lo strumento per codificare strati aggiuntivi di informazione. Un saggio utilizzo dei colori aumenta il contenuto informativo esprimibile”. Pertanto, invece di utilizzare lo storico giallino per tutti gli elementi, ricorrere a tinte diverse per specifici elementi può aumentare il contenuto informativo dei diagrammi stessi, senza per altro dover introdurre elementi aggiuntivi e quindi senza appesantire l’organizzazione. Per esempio già nell’ambito dei diagrammi dei casi d’uso si è proposto di utilizzare il grigio per evidenziare relazioni di estensione e il nero per quelle di inclusione. Ciò permette, a colpo d’occhio, di distinguere nettamente relazioni a carattere opzionale da quelle obbligatorie. Non solo i colori rendono i diagrammi più accattivanti e comprensibili, ma tendono anche a renderli fruibili più rapidamente. I diversi strati informativi dovuti all’utilizzo dei

UML e ingegneria del software: dalla teoria alla pratica

105

colori “sono visibili a distanza, cosicché il quadro d’insieme del modello (the big picture) emerge chiaramente prima di iniziare a leggerne i contenuti” (Peter Coad). Chiaramente tutto ciò è possibile a patto che i colori siano utilizzati in maniera intelligente, il che implica che sia utilizzata una ristretta palette di colori ben definita con un chiaro significato studiato accuratamente, che la convenzione sia conosciuta dai fruitori, che i vari colori siano applicati coerentemente, ecc. Sebbene tutti i modellatori finiscano per utilizzare un proprio set di colori con significati più o meno specifici, il primo tentativo di stabilire un approccio organico si deve a Peter Coad [BIB23]. In particolare la convenzione iniziale prevede quattro colori base: giallo, rosa, verde e blu. Da notare che si tratta di una convenzione di recente ideazione, frutto di una continua revisione che spesso ha generato un processo di “attenuazione delle tinte”. Ciò non dovrebbe stupire più di tanto: il tempo da sempre attenua le tinte dei ricordi…

Tabella 7.6 — Archetipi, e relativi colori, proposti da Peter Coad. Archetipo

Colore

Descrizione È utilizzato per modellare elementi relativi a momenti nel tempo e intervalli temporali. Rappresenta un’entità di cui

Moment-Interval Momento-Intervallo

bisogna tenere traccia per questioni di business o legali Rosa

che avviene, appunto, in un momento specifico o un intervallo di tempo (eventi, transazioni, ecc). Per esempio il noleggio di una macchina avviene in un determinato istante e prosegue fino alla riconsegna.

Description Descrizione Party, Place or Thing Parte, Posto o Cosa

È un archetipo atto a modellare classi utilizzate Blu

essenzialmente per descrizioni, decodifica di valori, ecc. Si tratta di una collezione di valori utilizzata diverse volte.

Verde

Evidenzia persone o cose che recitano più ruoli nel modello globale. È utilizzato per i ruoli delle classi, ossia il comportamento specifico assunto da un’entità (persona, luogo o cosa ) che prende parte in un determinato contesto. Inizialmente si era ipotizzato un solo colore direttamente per le classi-

Role Ruolo

Giallo

attore; successivamente Coad ha proposto di utilizzarlo per i relativi ruoli. Per esempio, in un sistema di gestione di un campionato di calcio, una generica entità persona dovrebbe essere rappresentata con il colore verde, mentre ruoli di Calciatore, Allenatore, Arbitro, ecc. con il colore giallo.

106

Capitolo 7. Gli oggetti: una questione di classe

Nella sua metodologia, Coad individua quattro elementi base denominati archetypes (archetipi) evidenziati dai colori di cui sopra. L’idea è molto simile a quella degli stereotipi standard dello UML, ma la definizione è meno formale e più flessibile tanto da richiedere l’invenzione di un elemento non standard. In particolare, “un archetipo è una forma dalla quale seguono classi dello stesso tipo, più o meno, considerando attributi, legami, metodi, punti di plug-in e interazioni” (Peter Coad). Il “più o meno” dovrebbe essere l’elemento cruciale che fa la differenza tra stereotipi, che appunto sono più formali e non ammettono nulla di simile, e archetipi. In ogni modo, la convenzione prevede quanto illustrato nella tab. 7.6. Da notare che, sempre secondo la convenzione proposta da Coad (novello impressionista), la palette di colori da utilizzare dovrebbe avere delle gradazioni molto tenui. Da quanto emerso, la convenzione proposta si presta a essere utilizzata nei modelli di struttura statica a carattere non fortemente tecnico, come il modello a oggetti del dominio e quello di business (illustrati in dettaglio nel capitolo seguente). In questi casi può avere molto senso evidenziare con colore diverso elementi (comunque appartenenti alla realtà oggetto di studio) con semantica diversa. L’uso degli archetipi comincerebbe a perdere di utilità già dal modello di analisi (propriamente detto) e ancora di più in quello di disegno, in cui questi oggetti tendono a lasciare la scena ad altri a maggior contenuto tecnico. In questi modelli, volendo continuare ad applicare la tecnica di Coad, probabilmente bisognerebbe riadattare il significato dei vari colori e, verosimilmente, sarebbe necessario ricorrere all’utilizzo di altri (classi infrastruttura, librerie, ecc.). Il rischio che si correrebbe è quello di produrre modelli non chiari, che sfruttano un insieme di colori non sempre ben combinati (veri esempi di “inquinamento ottico”), diagrammi di un livello di professionalità discutibile, ecc. “Due o tre colori tipicamente non sono sufficienti; cinque sono troppi. La combinazione di quattro colori deve essere selezionata con cura: nulla appare peggiore di troppi colori, specie quando mancano di elementi comuni” [Hideaki Chijiwa, Color Harmony]. Come se non bastasse, va tenuto a mente che non sempre è possibile disporre di stampanti a colori, e che comunque il loro utilizzo, tipicamente, prevede costi superiori a quelle in bianco e nero. Da notare che la convenzione di Coad non si limita a evidenziare elementi con specifici colori, ma sulla base di questi viene realizzato tutto un procedimento di sviluppo dei vari modelli. A questo punto è giunto il momento di tirare un po’ le somme circa l’utilità del metodo dei colori di Coad. Per quanto riguarda i vantaggi, potrebbe effettivamente concorrere ad aumentare la chiarezza e il contenuto informativo di modelli del dominio e di business, mentre, verosimilmente, già dal modello di analisi i benefici potrebbero risultare ampiamente ridotti. Gli svantaggi degli archetipi sono relativi al fatto che: • non sempre si dispone di una stampante a colori e la stampa monocromatica di immagini a colori spesso sortisce effetti poco gradevoli;

UML e ingegneria del software: dalla teoria alla pratica

107

• realizzando propriamente i diagrammi (vedi “regola del 7” più o meno 2 elementi), probabilmente le etichette degli stereotipi potrebbero già essere sufficienti; • gli archetipi stessi non sono elementi standard UML e quindi non sono supportati da molti tool commerciali; • non sempre il loro utilizzo apporta vantaggi proporzionali al tempo speso per evidenziarli, ecc. Le convenzioni utilizzate dall’autore sono decisamente più semplici e con una semantica piuttosto limitata il cui obiettivo consiste esclusivamente nel conferire maggiore o minore enfasi ai vari elementi. In particolare: •

le relazioni e i vari adornamenti (nome della relazione, ruoli delle classi coinvolte, molteplicità, ecc.) sono evidenziate con una tinta grigio scura, comunque ben riproducibile con stampanti monocromatiche;



le classi del diagramma non appartenenti al package o al contesto preso in esame, sono rappresentate con uno sfondo grigio chiaro, eventualmente visualizzando solo il compartimento relativo al nome senza ulteriori dettagli;



le classi appartenenti al package e/o al contesto preso in esame, sono visualizzate con una tinta giallina, resa ancora più leggera per le interfacce e le classi astratte;



gli elementi secondari, per esempio i tipi enumerati, sono rappresentati con sfondo trasparente corredati da testo e linee grigie, utilizzando la stessa convenzione stabilita per le note;



infine, qualora si voglia conferire maggiore enfasi a pattern applicati, è possibile rappresentarli con una tinta leggermente diversa, ferma restando l’indicazione dei ruoli che compongono il pattern.

Cosa sarebbe la vita senza colore?

Adornamenti Si prenderanno ora in considerazione delle linee guida da utilizzare per rappresentare concetti come visibilità, firma dei metodi, navigabilità, molteplicità, ecc. Un primo elemento da considerare è la visibilità di attributi e metodi delle classi. Si tratta di un evidentissimo caso di dipendenza dalla tipologia del modello oggetto di studio. Qualora si stia realizzando un modello di disegno è evidente che la visibilità degli elementi assume un ruolo cruciale (specifica le regole secondo le quali il relativo elemento è accessibile da parte di altri oggetti), mentre in tutti gli altri modelli si tratta di dati completamente inutili. Per esempio, l’autore consiglia di costruire modelli ad oggetti del dominio utilizzando un approccio basato sull’analisi dei dati (data-driven), il cui obiettivo è rappresentare un modello logico dei dati coinvolti nell’area oggetto di studio, disinteres-

108

Capitolo 7. Gli oggetti: una questione di classe

sandosi, momentaneamente, dei servizi. In questo contesto ha interesse mostrare i dati, la relativa struttura e la rete delle relazioni tra di essi: è quindi evidente che il concetto di visibilità non ha alcun rilievo. Qualora invece ci si trovi nel contesto di modelli di disegno, non solo è fondamentale mettere in evidenza la visibilità degli elementi, ma è anche molto importante organizzare i due elenchi (attributi e metodi) in modo tale che elementi con stessa visibilità siano raggruppati e che gli elementi con visibilità più estese siano mostrati prima (quindi tutti gli elementi public, poi quelli package, di seguito i protected e in fine i private). Ciò perché gli elementi con visibilità più estesa tendono a fornire maggiori informazioni sul disegno d’insieme del modello e quindi tendono a ricevere maggiore attenzione da parte dei fruitori. La visualizzazione di elementi privati tipicamente ha un’importanza relativa. Probabilmente ha più senso in diagrammi di carattere documentativo (magari in appositi diagrammi di dettaglio delle singole classi). Generalmente la presentazione di tutti gli elementi tende a rendere i modelli decisamente pesanti e confusi, generando inevitabili problemi alla comprensione del disegno globale (the big picture). Quando si realizzano modelli di disegno di specifica, non sempre ha molto senso cercare di disegnare tutti i dettagli, tipicamente si preferisce derogare al team di sviluppo la responsabilità di progettare metodi e attributi privati. Nei casi — frequentissimi — in cui si decida di non visualizzare l’elenco completo dei metodi e degli attributi, è molto importante evidenziare alla fine delle liste il simbolo di elisione costituito dai punti di sospensione ( ... ). La mancanza di questo simbolo tende a generare nei lettori errate conclusioni o comunque confusione: non si è in grado di sapere per certo se siano state mostrate tutte le caratteristiche della classe o solo quelle ritenute più importanti per il particolare contesto. Un problema particolarmente ricorrente nel disegnare i modelli delle classi è relativo alla firma dei metodi. Tipicamente, nella sua versione completa, la firma tende a occupare molto spazio. Ciò si ripercuote sulle relative classi costrette a ingigantirsi orizzontalmente, e quindi sull’intero modello che tende a divenire confuso. Qualora ci si trovi in situazioni simili, un buon consiglio potrebbe essere quello di agire sui parametri. In particolare è possibile rendere la descrizione delle classi più lineare, limitandosi a riportare il nome dei parametri e trascurandone il tipo. Per esempio, invece di riportare la firma: + insertElement(key:Object, value:Object):Object

si potrebbe visualizzare la forma più concisa: + insertElement(key, value):Object

Un accorgimento virtualmente molto interessante consiste nell’indicare, a fianco alla firma dei metodi, l’elenco delle eventuali eccezioni scatenate. Per esempio:

UML e ingegneria del software: dalla teoria alla pratica

109

+ insertElement(key:Object, value:Object):Object {throws NullPointerException, DuplicateKeyException}

Sebbene questa tecnica permetta di specificare informazioni molto utili, aggrava il problema dell’espansione orizzontale delle classi menzionato poc’anzi. È anche assolutamente sconsigliato utilizzare un criterio impreciso: nei metodi in cui la firma lo consente, si riporta l’elenco delle eccezioni, mentre dove non c’è posto lo si omette. La consistenza è una qualità importantissima della modellazione soprattutto nei diagrammi a maggiore connotazione tecnica. Un “approccio misto” tenderebbe a confondere il lettore: non trovando dettagliato l’elenco delle eccezioni scatenabili da un metodo, tenderebbe, legittimamente, a dedurre che il metodo non preveda la possibilità di generare eccezioni, mentre potrebbe trattarsi di un caso di omissione per questioni di spazio. Per quanto concerne gli stereotipi, da un lato è vero che, se utilizzati intelligentemente, concorrono a rendere i diagrammi più chiari, a facilitare una rapida comprensione del disegno di insieme, e così via; ma, dall’altro, è altrettanto vero che, se utilizzati in modo “ridondante”, tendono unicamente ad aumentare il livello di rumore. Per esempio, alcuni tecnici usano premettere stereotipi del tipo get , set a metodi quali getName() , setName(). Si tratta di dati assolutamente ridondanti da evitare poiché non aggiungono alcuna informazione e invece rendono i diagrammi meno chiari. Molto importante è poi riportare le molteplicità con cui le classi partecipano alle relazioni. Non è mai opportuno affidarsi al “default”. Per esempio, spesso la molteplicità di una classe aggregata o composta è omessa in quanto presunta uguale a uno. Sebbene si tratti del caso più ricorrente, non è valido in assoluto. Se si considera per esempio una classe aggregata Team composta dagli elementi Employee, si può notare che un elemento di un team può appartenere a diversi altri ed ecco quindi che la molteplicità posta uguale a uno, dal lato della classe aggregata non è accettabile. Pertanto è sempre opportuno riportare la molteplicità. Molto importante è anche cercare di essere precisi. Per esempio, invece di riportare molteplicità generiche specificate dal carattere asterisco ( * ) è consigliabile riportare forme meno sintetiche del tipo 1..n oppure 0..n. L’interesse relativo alla navigabilità delle associazioni dipende molto dal modello oggetto di studio: è marginale nei modelli relativi alle prime fasi del processo di sviluppo del software nei quali si può fissare bidirezionale convenzionalmente e senza troppi problemi, mentre assume un’importanza notevole in quello di disegno, nel quale, per questioni essenzialmente relative all’accoppiamento tra le classi, si vuole unidirezionale. Ciò equivale a dire che, in una determinata associazione, oggetti di un tipo posso navigare in quelli di un altro ma non viceversa. In termini tecnici, ciò comporta che i primi devono possedere un riferimento (o più riferimenti nel caso che la relazione sia a molti) agli oggetti associati e quindi navigabili. Si tratta evidentemente di decisioni a forte connotazione tecnica che quindi è opportuno prendere nelle relative fasi quando si dispongono sufficienti informazioni sull’ambiente ed il sistema.

110

Capitolo 7. Gli oggetti: una questione di classe

Qualora si decida di evidenziare la navigabilità dell’associazione è consigliato riportare il nome della stessa in modo che l’interpretazione rispetti la direzione della navigabilità. Per esempio, dovendo rappresentare la relazione di associazione tra Persona e Indirizzo, nella stragrande maggioranza dei casi si tratta di un’associazione unidirezionale: istanze di tipo Persona possono navigare in oggetti di tipo Indirizzo associati, ma non viceversa. Quindi è lecito attendersi nomi di associazioni del tipo domicilia, ha residenza, ecc. Sempre nei modelli di disegno è molto importante riportare il ruolo che la classe recita partecipando nelle relazioni: si tratta del nome da assegnare agli attributi che, secondo anche la molteplicità e navigabilità, eventualmente implementano le associazioni stesse. Nei modelli di business e dei requisiti il ruolo tende a divenire un dato ridondante per via del nome dell’associazione, eccezion fatta qualora sia necessario specificare dei vincoli, nel qual caso divengono molto importanti perché permettono di definire gli stessi.

Ricapitolando… Il presente capitolo è stato dedicato all’illustrazione del formalismo dei diagrammi delle classi, probabilmente uno dei più noti e affascinanti tra quelli definiti dallo UML. Nei processi di sviluppo del software, questo formalismo si presta a rappresentare formalmente molteplici artifact (manufatti) presenti in diversi stadi del processo. Nelle primissime fasi (Inception ed Elaboration) lo si adotta per rappresentare i modelli del dominio e di business, nello stadio di analisi è utilizzato per dar luogo all’omonimo modello (rappresentazione formale dei requisiti funzionali sanciti nel modello dei casi d’uso); nella fase di disegno è impiegato per progettare e documentare l’organizzazione in codice del sistema e così via. La modellazione è un ottimo strumento di investigazione e di comunicazione sia tra i membri del team, sia tra diversi team (requisiti, architettura, sviluppo, ecc.). Chiaramente è un’attività difficile, ad alto grado di creatività, legata all’esperienza dei singoli che concede poco spazio all’improvvisazione: tutto vive nel regno delle regole formali dell’OO. Le difficoltà che si incontrano, quindi, non sono tanto dovute all’UML, quanto alla complessità intrinseca del processo di disegno. Tecnicamente un diagramma delle classi è definito come un grafo di classificatori connessi attraverso opportune relazioni. Pertanto, in questa tipologia di diagrammi vengono collocati elementi come classi, interfacce, package, tipi di dati, e così via (elementi che nel metamodello UML sono specializzazioni della classe astratta Classifier). Anche per questo motivo probabilmente un nome più appropriato per i diagrammi “delle classi” dovrebbe essere “diagramma della struttura statica”. Una classe è una descrizione di un insieme di oggetti che condividono la stessa struttura (attributi), il medesimo comportamento (operazioni) e le stesse relazioni (particolari attributi). La rappresentazione grafica efferente prevede un rettangolo, tipicamente suddiviso in tre sezioni dedicate rispettivamente al nome, agli attributi e alle operazioni. Di queste, solo la prima è obbligatoria. In funzione delle esigenze del modellatore, è possibile eventualmente inserirne di ulteriori, dedicate, per esempio, all’elenco delle eccezioni scatenabili, alle dichiarazione delle responsabilità della classe e così via.

UML e ingegneria del software: dalla teoria alla pratica

111

Il nome di una classe può essere riportato nella forma semplice, ossia solo il nome, oppure corredato dalla lista dei package: il percorso (path name). Da notare che gli elementi della lista vengono separati da una coppia del carattere due punti ( :: ) anziché dal singolo punto come avviene in Java. Qualora una classe rappresenti un’istanza di un determinato stereotipo, è possibile specificare il nome di quest’ultimo, opportunamente racchiuso dalle famose parentesi angolari ( < > ), riportato al di sopra di quello della classe. In alternativa è possibile ricorrere all’utilizzo della relativa notazione grafica prevista dallo stereotipo (icona). Subito sotto il nome della classe è possibile specificare una lista di stringhe riportanti o appositi valori etichettati (tagged value) o attributi del metamodello. Un attributo è una proprietà della classe, identificata da un nome, che descrive un intervallo di valori che le relative istanze possono assumere. Il concetto è del tutto analogo a quello di variabile di un qualsiasi linguaggio di programmazione. Una classe può non avere attributi o averne un numero qualsiasi. Di un attributo è possibile indicare il solo nome, oppure il nome e il tipo, oppure la precedente coppia corredata da un valore iniziale. Eventualmente è possibile specificarne lo stereotipo. Un’operazione può essere considerata come la trasposizione, in termini informatici, di un servizio che può essere richiesto a ogni istanza di una classe al fine di modificare lo stato del sistema e/o di fruire di un servizio. Pertanto, un’operazione (metodo) è un’astrazione di qualcosa che può essere eseguito su tutti gli oggetti di una stessa classe. Come nel caso degli attributi, una classe può non disporre di alcun metodo (anche se ciò sarebbe molto discutibile), oppure averne un numero qualsiasi. In UML è possibile mostrare l’annidamento di una classe all’interno di un’altra attraverso un apposito formalismo che consiste nel connettere le classi attraverso un segmento con riprodotto, nell’estremità della classe “esterna”, un apposito simbolo detto anchor (ancora): una croce circoscritta da una circonferenza. In UML un’interfaccia è definita come un insieme, identificato da un nome, di operazioni che caratterizzano il comportamento di un elemento. Si tratta di un meccanismo che rende possibile dichiarare esplicitamente operazioni visibili dall’esterno di classi, componenti, sottosistemi, ecc. senza specificarne la struttura interna. L’attenzione viene quindi focalizzata sulla struttura del servizio esposto e non sull’effettiva realizzazione. Nel metamodello UML le interfacce sono specializzazioni della metaclasse Classifier, quindi ne possiedono tutte le caratteristiche. Si prestano a essere rappresentate graficamente attraverso il classico formalismo del rettangolo diviso in compartimenti, anche se, tipicamente, viene preferita la rappresentazione che fa uso dell’apposita icona (circonferenza). Un template è un descrittore di una classe parametrica in funzione di uno o più parametri. Pertanto un template non definisce una singola classe bensì una famiglia, in cui ciascuna istanza è caratterizzata dal valore assunto dai parametri. Questi valori diversificano le varie classi condizionando il comportamento di specifici metodi. La notazione grafica di un template prevede il tipico formalismo utilizzato per le classi, con l’aggiunta di un rettangolo tratteggiato, nell’angolo in alto a destra, destinato a ospitare la dichiarazione della lista dei parametri. Il tipo enumerato rappresenta un tipo di dato definito dall’utente costituito da una lista di nomi, a loro volta definiti dall’utente. Esso è utilizzato per vincolare i valori impostabili nelle relative istanze a uno degli elementi appartenenti alla lista che costituisce il tipo. Questa lista è caratterizzata dal possedere un rigido ordine (quello della dichiarazione degli elementi della lista) ma nessuna algebra.

112

Capitolo 7. Gli oggetti: una questione di classe

La modellazione di un sistema non solo richiede l’identificazione delle classi di cui è composto, ma anche dei legami che le interconnettono. Tali legami, nel mondo OO, sono definiti relazioni e sono tutte riconducibili ai tipi fondamentali: dipendenza, generalizzazione e associazione (la relazione di realizzazione è una variante della generalizzazione). La dipendenza è una relazione semantica tra due elementi (o insiemi di elementi) utilizzata per evidenziare condizioni in cui una variazione dell’elemento indipendente (supplier) comporta modifiche dell’elemento dipendente (client). Tipicamente si ricorre a una relazione di dipendenza qualora non ne esistano altre più appropriate per associare gli elementi; in altre parole, la relazione di dipendenza è la più debole di quelle previste. Poiché la definizione del metamodello UML prevede che la relazione di dipendenza possa prevedere nel ruolo di supplier e client qualsiasi ModelElement (metaclasse astratta del metamodello UML, da cui derivano tutti gli altri elementi), ne segue che tutti gli elementi dello UML possono essere relazionati tra loro per mezzo di una relazione di dipendenza. Graficamente questa relazione è rappresentata attraverso una freccia tratteggiata che collega due elementi: quello dipendente dal lato della coda e quello indipendente dal lato della freccia. La relazione di dipendenza prevede diverse specializzazioni atte ad evidenziare la semantica dell’utilizzo che ne viene fatto. In particolare, nel metamodello sono predefiniti una serie di specializzazioni standard (le metaclassi Binding, Abstraction, Usage e Permission). Anche a queste, tipicamente, si preferiscono stereotipi — diversi dei quali sono predefiniti nel metamodello — dotati di semantica specializzata. La generalizzazione è una relazione tassonomica tra un elemento più generale (detto padre) e uno più specifico (detto figlio). Quest’ultimo è completamente consistente con quello più generale e, tipicamente, definisce ulteriori informazioni (in termini di struttura e comportamento). Pertanto un’istanza di un elemento figlio può essere utilizzata in ogni parte in cui è prevista oggetto dell’elemento padre. Nel caso di relazione di generalizzazione tra classi, alla nomenclatura “ecclesiale” (padre e figlio) si preferisce quella più specifica di superclasse e sottoclasse (superclass e subclass). La relazione di generalizzazione viene mostrata attraverso un segmento congiungente la sottoclasse alla superclasse, culminante con un triangolo vuoto posto nell’estremità di quest’ultima. Un’associazione è una relazione strutturale tra due o più classificatori che descrive connessioni tra le relative istanze. In base al numero degli elementi coinvolti nella relazione, si hanno diverse “specializzazioni”, quali l’associazione unaria, binaria ed n-aria. Il caso decisamente più frequente è costituito dall’associazione binaria che coinvolge due classificatori e specifica che da oggetti di un tipo è possibile (a meno di vincoli particolari) navigare a quelli dell’altro e viceversa. Tipicamente alle associazioni viene assegnato un nome per specificare la natura della relazione stessa e, al fine di eliminare ogni possibile fonte di ambiguità di lettura, spesso viene visualizzata a fianco del nome stesso una freccia indicante il verso di lettura. Una relazione di associazione tra due classi mostrata senza alcuna direzione implica che, nell’ambito della relazione, entrambe le classi sono navigabili, quindi, data un’istanza di una classe, è possibile transitare nelle istanze dell’altra a cui è associata e viceversa. Qualora invece, in una relazione di associazione, non si voglia permettere alle istanze di una classe di “vedere” quelle dell’altra (essenzialmente, di invocarne i metodi), nella rappresentazione della relazione è necessario inserire una freccia indicante il verso di percorrenza. Pertanto gli oggetti istanza della classe posta nella coda dell’associazione (navigabilità =

UML e ingegneria del software: dalla teoria alla pratica

113

false) possono invocare i metodi ed accedere agli attributi pubblici delle istanze della classe puntata dalla freccia (navigabilità = true), mentre non è possibile il contrario. Poiché una associazione è relazione strutturale tra gli oggetti istanze delle classi coinvolte, è importante specificare, per ogni istanza di una classe, a quanti oggetti dell’altra può essere connessa e viceversa. Queste informazioni vengono definite molteplicità di un ruolo di associazione o più semplicemente “molteplicità” (multiplicity). Ogniqualvolta un’entità prende parte a un’organizzazione, recita un determinato ruolo, quindi, similmente, anche le classi, partecipando ad una relazione, svolgono uno specifico ruolo. Questo viene espresso per mezzo di una stringa il cui scopo è renderne esplicita la semantica. Qualora si decida di visualizzare il ruolo, questo è collocato nei pressi dello spazio in cui il segmento dell’associazione incontra la classe a cui si riferisce. Nel caso in cui due o più classi siano associate per mezzo di una relazione con molteplicità diversa da 1 a 1, ossia almeno un’istanza di una classe possa essere connessa con un opportuno insieme di oggetti istanze dell’altra, può verificarsi il caso in cui sia necessario ordinare, secondo un preciso criterio, tali relazioni. Qualora ciò avvenga, la condizione è evidenziata riportando la parola chiave ordered, racchiusa da parentesi graffe, ({ordered}) nell’opportuno terminale di associazione. Un altro insieme di vincoli che è possibile specificare in un’associazione è relativo alla modificabilità (changeability) dei legami che le istanze delle classi coinvolte nell’associazione instaurano. I valori ammessi sono tre: changeable (modificabile: default), frozen (congelato: dopo la creazione dell’oggetto nessuna relazione può essere aggiunta per mezzo dell’esecuzione di un’operazione nella classe sorgente della relazione), addOnly (sola aggiunta: relazioni possono essere aggiunte ogni qualvolta se ne abbia la necessità, ma, una volta create, queste non possono essere più rimosse). In modo del tutto analogo a quanto sancito per gli attributi e i metodi, anche alle associazioni (che in ultima analisi non sono altro che particolari attributi) è possibile specificare il campo d’azione (scope). Le alternative previste sono due: istanza (instance) e classificatore (classifier), che equivale a dichiarare un’associazione statica. Una associazione xor (xor-association) rappresenta una situazione in cui solo una delle potenziali associazioni, che una stessa classe può instaurare con altre, può verificarsi in un determinato istante di tempo per una specifica istanza della classe. Graficamente è rappresentata per mezzo di un segmento che unisce le associazioni (due o più) soggette al vincolo, con evidenziata la stringa {xor}. In un’associazione tra classi, spesso si verifica la situazione in cui la relazione stessa possegga proprietà strutturali e comportamentali (attributi, operazioni e riferimenti ad altre classi). In tali circostanze è possibile ricorrere all’utilizzo della classe associazione (association class) che, come suggerisce il nome, possiede contemporaneamente proprietà di classe e di associazione. Graficamente è rappresentata attraverso il normale formalismo previsto per le classi con, in aggiunta, un segmento tratteggiato di connessione con la relazione che la origina. Un’associazione tra classi mostra una relazione strutturale paritetica (la famosa peer to peer), per cui tra le classi coinvolte non è possibile distinguerne una concettualmente più importante delle altre: sono tutte allo stesso livello. Spesso però è necessario utilizzare relazioni del tipo “tutto–parte” (whole–part), in cui esiste una classe che rappresenta un concetto più “grande” (il tutto) costituito dalle restanti che rappre-

114

Capitolo 7. Gli oggetti: una questione di classe

sentano concetti più piccoli (le parti). Questi tipi di relazioni sono dette di aggregazione e, in particolari circostanze, diventano di composizione. Graficamente un’aggregazione è visualizzata con un rombo vuoto dalla parte della classe aggregata (whole). Nel caso in cui l’aggregazione sia una composizione, allora il rombo viene colorato al proprio interno. La composizione è una forma di aggregazione con una forte connotazione di possesso e coincidenza della vita tra le classi parti e quella “tutto”. Le parti possono essere generate anche in un tempo successivo alla creazione della classe composta (tutto), ma, una volta generate, queste vivono e sono distrutte con l’istanza della classe composta di appartenenza. Inoltre uno stesso oggetto, in ogni istante di tempo, può essere parte esclusivamente di una composizione. Quando un oggetto composto viene generato, esso si deve incaricare di creare le istanze delle sue parti e associarle correttamente a sé stesso. Quando poi un oggetto composto viene distrutto, esso ha la responsabilità di distruggere tutte le parti (ciò non significa che debba farlo direttamente). Ogni qualvolta si utilizza una relazione di composizione, in qualche modo è possibile pensare le classi rappresentanti le parti della relazione come private della classe composta. Nella realizzazione di un modello, spesso è necessario dar luogo a relazioni di associazione particolari, in cui esiste il problema del lookup. In altre parole, in una relazione 1 a n (o n a n) tra due classi, fissato uno determinato oggetto istanza di una classe, è necessario specificare un criterio per individuare un preciso oggetto o un sottoinsieme di quelli associati istanze dell’altra classe. Tali circostanze si prestano a essere modellate attraverso l’associazione qualificata (qualification), il cui nome è dovuto all’attributo, o alla lista di attributi, detti qualificatori, i cui valori permettono di partizionare l’insieme delle istanze associate a quella di partenza. Come è lecito attendersi, una associazione n-aria è una relazione che coinvolge più di due classi, tenendo presente che una stessa classe può apparire più volte. Così come l’autoassociazione è un particolare caso dell’associazione binaria, allo stesso modo, quest’ultima può essere considerata un caso particolare della relazione n-aria. I diagrammi degli oggetti (object diagram) rappresentano una variante dei diagrammi delle classi, o meglio ne sono istanze e ne condividono gran parte della notazione. Rappresentano la famosa istantanea del sistema eseguita in un preciso istante di tempo di un’ipotetica esecuzione. Quindi, a differenza dei diagrammi delle classi, popolati di elementi astratti come classi e interfacce, i diagrammi degli oggetti sono colonizzati da oggetti sospesi in un particolare stato. Lo stato di un oggetto è un concetto dinamico e, in un preciso istante di tempo, è dato dal valore di tutti i suoi attributi e dalle relazioni instaurate con altri oggetti. I diagrammi degli oggetti, come quelli delle classi, sono utilizzati per illustrare la vista statica di disegno del sistema, però, a differenza di questi ultimi, utilizzano una prospettiva diversa, focalizzata su un esempio, reale o ipotetico, dello stato di evoluzione del sistema.

Capitolo

8

Le classi nei processi “La struttura esterna di un sistema ne determina l’affermazione iniziale, mentre l’organizzazione interna ne sancisce il successo nel tempo” GRADY BOOCH Il modo migliore per determinare il fallimento di un progetto è codificare a partire direttamente dai casi d’uso… Con ciò, ovviamente, non si intende dire che i Casi d’Uso non vadano prodotti. LVT

Introduzione Il presente capitolo è focalizzato sull’utilizzo del formalismo dei diagrammi delle classi nel contesto dei processi di sviluppo del software (modelli a oggetti del dominio del business, di analisi e di disegno). A tal fine sono presi in considerazione i processi più formali: potrebbe non avere molto senso riferirsi a processi in cui la modellazione è considerata l’ultimo degli espedienti. Coerentemente all’impostazione del libro, la presentazione dei vari modelli avviene sia da un punto di vista teorico, sia pratico attraverso l’utilizzo di un esempio concreto: un ipotetico sistema di gestione della sicurezza di un sistema Internet/Intranet. Uno dei punti focali del processo di costruzione dei modelli a oggetti consiste nell’identificazione delle classi — e quindi degli oggetti — che costituiscono il sistema. Si tratta di una delle attività più complicate in assoluto della modellazione OO, che implica, essenzialmente, due capacità distinte: scoperta e invenzione. La prima si utilizza maggiormente durante la produzione dei modelli OO appartenenti alle prime fasi dei processi di svilup-

2

Capitolo 8. Le classi nei processi

po del software (modelli a oggetti del dominio e di business). In questi modelli è necessario focalizzare l’attenzione sulle entità realmente esistenti nell’area oggetto di studio, al fine di realizzare una versione OO dell’area business e quindi del vocabolario del sistema. L’attività di “invenzione” invece diventa più frequente durante le fasi di analisi e disegno, dove agli oggetti propri del domino è necessario aggiungere quelli di “infrastruttura” atti a realizzare i meccanismi che permettono al sistema di funzionare. Spesso non si tratta di una vera e propria attività di invenzione, bensì di riconoscimento di pattern, sia derivanti da soluzioni applicate nella realizzazione di precedenti sistemi funzionanti — in ultima analisi uno dei vantaggi dell’OO dovrebbe essere relativo alla riutilizzabilità del codice — sia dalla classificazione standard. Inoltre, il ricorso ad architetture “standard” come EJB e .net semplifica la realizzazione del modello di disegno: i pattern da applicare per realizzare l’infrastruttura sono ben definiti in partenza. Per quanto concerne strettamente l’attività di scoperta, viene presentata una tecnica dimostratasi efficace nel processo di produzione del modello del dominio. Si tratta tuttavia di un’attività molto complessa legata alle capacità dei singoli, che solo l’esperienza e la conoscenza dell’area oggetto di studio possono rendere meno difficile. Un aspetto al quale si conferisce particolare importanza è relativo all’influenza esercitata dall’architettura del sistema sui vari modelli da produrre. Già durante la fase di analisi dei requisiti, l’architettura stabilita per il sistema recita un ruolo molto importante: permette di stabilire la fattibilità dei requisiti, fornisce suggerimenti su come modellare le richieste dell’utente in modo da farle risultare più confacenti all’architettura del sistema e così via. Anche per questo motivo è opportuno iniziare a redigere il Software Architecture Document (documento sull’architettura software) fin dalle prime fasi del processo di sviluppo del software. Avanzando nelle varie fasi di cui è composto il processo di sviluppo del software, l’influenza dell’architettura diviene legittimamente più pressante: fin dalla fase di analisi è importante che il relativo modello ne rifletta l’organizzazione. Il capitolo si chiude tentando di rispodere all’interrogativo “quando un modello di disegno può considerarsi ben progettato?”: una delle domande più classiche e spinose dell’intero processo di sviluppo del software.

Perché disegnare Quantunque il formalismo dei class diagram si presti a svariati utilizzi, la sua notorietà — anche per ragioni storiche: inizialmente non erano previsti molti altri utilizzi — è indubbiamente legata alla vista di disegno: l’associazione mentale tra i due concetti è piuttosto immediata nella mente di quasi tutti i tecnici. In tale ambito i diagrammi delle classi sono utilizzati per rappresentare l’organizzazione interna del sistema (struttura statica del codice) in termini di classi. Dunque, si tratta di una vista a forte connotazione tecnica, nella quale l’attenzione viene definitivamente spostata dal “cosa” costruire al “come”. In altre parole, si focalizza l’attenzione sulla progettazione delle soluzioni necessarie per rea-

UML e ingegneria del software: dalla teoria alla pratica

3

lizzare i requisiti funzionali, descritti dal modello dei casi d’uso, e quelli non funzionali catturati, inizialmente, nel relativo documento. Al momento in cui viene scritto il presente libro, il modello di disegno è oggetto di un forte contenzioso tra due contrastanti correnti di pensiero… pardon, di implementazione. Sebbene il contenzioso sia più che altro relativo ai processi di sviluppo (XP, RUP, EUP, ecc.), lo UML finisce inevitabilmente per esserne investito direttamente in quanto strumento universalmente adottato per modellare gran parte dei manufatti richiesti dai processi a maggior grado di formalità. In particolare, un elemento di forte contrasto è proprio relativo alla necessità o meno di produrre un modello di disegno di cui il formalismo dei diagrammi delle classi rappresenta la parte più rilevante. Agli estremi dell’infuocato dibattito si collocano, rispettivamente, tecnici che non scriverebbero assolutamente una riga di codice senza aver prima realizzato un modello completo e dettagliato, e quelli che invece considerano la modellazione una perdita di tempo prezioso sottratto alla fase di codifica. L’autore del libro considera la modellazione un’attività di fondamentale importanza nella produzione di un qualsiasi sistema software, sia nel caso in cui il processo utilizzato sia molto accademico, sia qualora si utilizzi un processo più “leggero”. Ciò però non significa che sia necessario disegnare ogni dettaglio prima di avviare la fase implementativa. Lavorando in diverse organizzazioni, dislocate non unicamente sul suolo italiano, è facilmente riscontrabile come l’attività di modellazione di sistemi software sia territorio colonizzato da miti, false credenze e guerre di religione. Non è certo il male peggiore del mondo utilizzare saltuariamente l’attività di codifica come un valido strumento di investigazione, magari per aumentare il livello di comprensione di una nuova tecnologia, o per verificare se uno specifico strumento (per esempio una libreria fornita da un’azienda esterna) sia in grado di garantire i requisiti non funzionali richiesti. Tale approccio diviene veramente efficace qualora i riscontri ottenuti non restino circoscritti a una porzione di codice. È invece opportuno riesaminarli nella cornice di un modello globale che tenga conto anche degli aspetti più generali dell’intero sistema e delle relative evoluzioni. Ciò premesso, l’affermazione che la modellazione costituisce unicamente uno spreco di tempo sottratto alla codifica spesso rappresenta il vano tentativo da parte di alcuni presunti tecnici di celare eventuali inadeguatezze. È sempre molto facile e ordinario denunciare ciò che non si comprende appieno come inutile… Ahimè, tra la possibilità di investire tempo e fatica per comprendere nuove tecnologie emergenti e quella di dichiararle inutili, la seconda alternativa tende a prevalere. La scusa ricorrente è che “modellare equivale a produrre documentazione superflua”. Chiaramente un modello realizzato con strumenti formali costituisce già di per sé un’ottima documentazione, ma è altresì vero che considerare documentazione adeguata un diagramma disegnato su una lavagna, sebbene rappresenti a tutti gli effetti un valido modello, sarebbe piuttosto opinabile (non fosse altro per la difficoltà di inserire la lavagna in un apposito raccoglitore di documenti).

4

Capitolo 8. Le classi nei processi

L’errata convinzione che la modellazione di un sistema sia una perdita di tempo è tipicamente diffusa tra i programmatori junior. Ciò costituisce un vero peccato sebbene comprensibile. In effetti, poiché la loro esperienza è focalizzata nella scrittura del codice, è abbastanza naturale attendersi tali conclusioni. Quando ciò si verifica, chiaramente le responsabilità maggiori sono ascrivibili a coloro che avrebbero dovuto favorire la crescita di professionalità del personale apprendista… (A questo punto la vocina presente nell’autore sta ridendo smodatamente… e si sta ricorrendo a un eufemismo). Quando poi si tratta di una convinzione radicata nella mente del personale tecnico “esperto”, architetti e/o dirigenti, allora il problema diventa veramente serio. In generale, frasi del tipo “il sistema descritto nel libro o studiato all’Università è unicamente appartenente al mondo delle idee”, oppure “il sistema prodotto da tizio è un esercizio di laboratorio che non ha nulla a che vedere con la realtà” dovrebbero incutere molto timore proprio nel personale meno esperto. Indubbiamente, nel modo di produrre software c’è qualche cosa che non funziona propriamente… Come mai la maggior parte dei progetti fallisce, o viene concluso con un tempo e una spesa molto superiori a quanto stimato? Perché si assiste a progetti più volte sospesi, analizzati e poi riavviati o addirittura terminati prematuramente? Le stime parlano chiaro: negli USA, al momento in cui viene redatto il presente capitolo, solo il 15% dei progetti vengono consegnati e con una tolleranza accettabile delle stime iniziali. (Nel Belpaese, probabilmente le statistiche produrrebbero esiti opposti: se un progetto richiede molto più tempo di quello inizialmente pianificato, è sufficiente cambiare la pianificazione iniziale. Come dire: se il risultato non soddisfa, poco male, è sufficiente cambiare i dati di ingresso). La sindrome “da morte prematura dei progetti” potrebbe essere imputata a responsabilità magari legate al sistema operativo che non prevede tutti i servizi occorrenti, oppure al compilatore che non compila abbastanza efficacemente e che non individua errori logici, o ancora alla libreria acquistata che non fornisce correttamente tutti i servizi promessi, o addirittura al linguaggio di programmazione non adeguato. Cosa dire poi del personale? Forse in tutti questi pretesti potrebbe albergare un fondo di verità, ma chiunque è in grado di capire che, tra tutti gli elementi citati, non esiste quello determinante: la prova schiacciante (the body of evidence). Probabilmente il motivo principale risiede nel frequente modo caotico di produrre software, dominato dalle sole attività di codifica e test. Si codifica scrivendo codice alla velocità della luce e poi si comincia a correggerlo a colpi di debugger (chiaramente, in questa seconda attività, il termine di paragone è quello dei tempi dei poveri bradipi). Tipicamente è più difficile non sentirsi a proprio agio nell’attività di codifica. Non si tratta assolutamente di un’attività banale, ma lì, perlomeno, i problemi sono circoscritti a quelli evidenziati dal compilatore oppure ad alcuni a carattere logico che, con il debugger alla mano, possono essere risolti. Poco importa però se poi il sistema nel suo complesso tende a vacillare perché basato su una miriade di decisioni a corto respiro. Come asserisce

UML e ingegneria del software: dalla teoria alla pratica

5

Martin Fowler, “le decisioni a corto raggio (short-term decisions) funzionano piuttosto bene finché il sistema è minimo, ma, come inizia a crescere, diventa difficile aggiungere nuove funzionalità, e i temutissimi bug cominciano a proliferare come funghi, sia in numero, sia in complessità. Caratteristica tipica di sistemi basati su decisioni a corto-respiro, è il lungo periodo di tempo richiesto dalla fase di test prima di dichiarare il sistema completo”. Ciò però non significa che sia necessario sospendere l’attività di codifica fino a che la lunga ed estenuante attività di modellazione non sia giunta a evidenziare anche i dettagli minimi dell’intero sistema. Come al solito è necessario trovare il giusto mezzo in funzione di diversi parametri, non ultimi il proprio team di sviluppatori e la tipologia del progetto. Probabilmente non è necessario disegnare ogni singola classe e ogni metodo (a patto di non lavorare con i soliti programmatori Object Disoriented), così come non è indispensabile realizzare un diagramma di interazione per ogni scenario; è sufficiente concentrarsi sugli elementi più importanti, conferendo una certa libertà al team di sviluppo. Confinare elementi del proprio team al mero ruolo di “riempitori” di classi non è un’attività salutare per nessuno. I modelli sono poi di importanza vitale durante la progettazione in cui una delle parole chiave deve essere comunicare e quindi anche solo questo motivo dovrebbe giustificare l’utilizzo dello UML. Anche la documentazione, per quanto possa risultare odiosa, è molto importante e andrebbe realizzata prima, durante e dopo lo sviluppo di ciascuna porzione del sistema. Lo stesso modello di disegno evolve da versioni iniziali dette di specifica a quelli finali in cui rappresenta esattamente il codice e pertanto viene detto di implementazione. Chiaramente non è sufficiente produrre la documentazione, ma è necessario mantenerla aggiornata, tenendo presente il monito che alla fine “solo il codice è sincronizzato con sé stesso” (Scott Ambler). Realizzare opportuni modelli prima di concentrarsi nell’attività di codifica, magari semplicemente discutendoli con l’ausilio di una lavagna, può aumentare enormemente la produttività dei gruppi: è possibile esplorare le diverse possibili soluzioni prima di codificare, non solo nell’ottica del singolo problema, ma anche nel contesto degli aspetti generali, attuali e futuri, del sistema. Durante l’attività di disegno è possibile scoprire che determinate idee o approcci sono errati o non completamente idonei. Chiaramente tali investigazioni, eseguiti al tempo di disegno, possono costare qualche giorno di lavoro, mentre cambiare direzione in fase di codifica potrebbe costare mesi o addirittura pregiudicare l’intero sistema. In questi casi, accorgendosi di aver sbagliato soluzione, spesso, invece di fermare il tutto ed effettuare una salutare operazione di refactoring, si assiste alla programmazione per “toppe successive”, mentre il destino del “povero” sistema comincia a vacillare e ogni nuova funzionalità appressa la luttuosa fatalità. Generalmente, si tenta di attribuire tutta la colpa agli utenti e ai temutissimi change requirements (cambiamenti dei requisiti). Molti vorrebbero risolvere il problema sempli-

6

Capitolo 8. Le classi nei processi

cemente non ponendoselo proprio, ossia pretendendo di “congelare” i requisiti utente prima di procedere alle fasi successive: si tratta dell’ennesima “dolce” illusione. Umanamente non è possibile pensare tutto a priori e in modo assolutamente corretto. Proprio queste considerazioni dovrebbero far comprendere quanto modellare, progettare tenendo conto che le cose possano cambiare — non tutte, ovviamente — sia una sfida alla quale non è possibile sottrarsi. L’eterna variazione dei requisiti utente non può e non deve essere motivo per non analizzare e modellare con la scusa che “tanto le cose comunque cambieranno”, bensì deve essere un terreno di cimento per realizzare sistemi migliori e più flessibili, in cui le modifiche diventino facilmente pianificabili. In questi contesti la mancata modellazione può portare alla realizzazione di sistemi in cui ogni modifica è fonte di estrema paura da parte del team, e allora cosa fare? Opporsi, opporsi con tutte le forze alle nuove richieste anche a costo di realizzare un sistema non a dimensione utente. Qualora poi le nuove specifiche non siano assolutamente inevitabili, non resta che ricorrere a tutti gli scongiuri possibili e immaginabili e rassegnarsi a modificare, con la speranza che gli aggiornamenti apportati a una parte del sistema non si ripercuotano negativamente proprio dove uno non se lo aspetterebbe. La modellazione è la chiave di volta: si tratta di un ottimo strumento di investigazione e di comunicazione sia tra i membri del team, sia tra diversi team (requisiti, architettura, sviluppo, ecc.). Chiaramente è un’attività difficile, ad alto grado di creatività, legata all’esperienza dei singoli che poco spazio concede all’improvvisazione: tutto vive nel regno delle regole formali dell’OO. Le difficoltà che si incontrano, quindi, non sono tanto dovute all’UML, quanto alla complessità intrinseca del processo di disegno e di costruzione di sistemi che funzionano. È necessario essere consapevoli che per disegnare proficuamente un sistema OO o component-based, è assolutamente indispensabile possedere una buona padronanza della materia e tanta, tanta esperienza. Esperienza che si ottiene con anni dedicati allo studio e allo sviluppo di sistemi OO funzionanti. Purtroppo, esperienze maturate in settori diversi non sono di grande aiuto. Quando si intraprende il viaggio verso un “nuovo” mondo — un “nuovo” modo di pensare le astrazioni — è necessario modificare i propri schemi mentali iniziando dalla base: la classe. Tutto ciò è proprio così… Se vi pare.

Processi di sviluppo e modelli a oggetti Il formalismo del diagramma delle classi si presta a essere utilizzato per produrre tutta una serie di manufatti (artifacts) coinvolti nel processo di sviluppo del software: dal modello a oggetti del dominio a quello del business, dal modello a oggetti di analisi a quello di disegno e così via (fig. 8.1). La descrizione dettagliata dei vari manufatti è oggetto di studio dell’intero capitolo. Come già menzionato più volte, è opportuno ribadire che tutti i modelli basati sui diagrammi delle classi sono comunemente designati come modelli a oggetti (object model).

7

UML e ingegneria del software: dalla teoria alla pratica

Figura 8.1 — Frammento dei manufatti richiesti da un processo di sviluppo del software focalizzando l’attenzione a quelli realizzati per mezzo del formalismo dei diagrammi delle classi. La notazione cromatica indica la misura in cui il relativo artifact si presti a essere realizzato in UML. In particolare il giallo intenso indica che il manufatto si presta a essere prodotto interamente attraverso i diagrammi previsti dal linguaggio, mentre il grigio ne esclude quasi completamente la presenza.

«use case»

«use case»

«GUI»

«class diagram»

«doc»

Business Model

Requirements Model

Prototipo GUI

Def. interfacce esterne

Requisiti non funzionali

«class diagram»

«class diagram»

Modello ad ogg. business

Modello ad ogg. dominio

(alpha ver)

«refine»

«class diagram»

Modello di analisi

«class diagram»

«refine»

Modello di disegno

«doc»

Glossario

Modello del database

Ciò potrebbe lasciare perplesse molte persone, in quanto essendo gli oggetti istanze delle classi si corre il rischio di mettere insieme astrazioni con i relativi corpi. In ogni modo questa è la convenzione generalmente adottata.

Modello a oggetti del dominio Un manufatto di estrema importanza nei processi di sviluppo del software, prodotto durante la fase di analisi dei requisiti utente, è costituito dal modello a oggetti del dominio (Domain Object Model), indicato più semplicemente con il nome “modello del dominio”. L’autore preferisce ricorrere alla prima versione (eventualmente utilizzando l’acronimo DOM) al fine di evitare possibili confusioni in quanto la denominazione di modello del dominio include diversi manufatti, quali il modello a oggetti, gli use case, le business rule, ecc. Il modello a oggetti del dominio, come suggerito dal nome, rappresenta un modello che ha a che fare con entità “realmente esistenti” nell’area oggetto di studio che il sistema

8

Capitolo 8. Le classi nei processi

software, in qualche misura, dovrà automatizzare. Uno degli scopi primari del modello è contribuire alla piena comprensione del contesto (spazio del problema) del sistema focalizzando l’attenzione sulla relativa struttura statica. Non è infrequente riferirsi allo spazio del dominio del problema con l’espressione “mondo concettuale” in quanto, generalmente, sono presenti “oggetti” che, pur non esistendo apertamente nel mondo reale, da esso “traspirano” oppure possono essere visti come derivati da specifici oggetti attraverso lo studio delle strutture e dei comportamenti. Il modello a oggetti del domino è un modello statico (in quanto proiezione indipendente dal fattore tempo) dello spazio del problema che ne rappresenta un’astrazione in termini delle varie entità presenti corredate dalle relative interconnessioni. Le entità individuate sono ottime candidate di classi persistenti, ossia di classi i cui oggetti necessitano di essere memorizzati in forma permanente: in effetti sono le astrazioni dei dati trattati dal sistema. Chiaramente le classi entità individuate in questa fase sono in uno stato embrionale, quindi necessitano di ulteriori elaborazioni per assumere la forma finale utilizzata nel modello di disegno e pronta per essere implementata. Secondo le conclusioni dei Tres Amigos riportate nel libro The Unified Software Development Process ([BIB08]), le entità presenti nel modello del dominio dovrebbero appartenere essenzialmente alle seguenti categorie: • entità “business”, ossia oggetti rappresentanti entità (più o meno tangibili) manipolati nell’area business oggetto di studio. In un sistema per il commercio elettronico, queste entità rappresentano oggetti come: articoli, carrelli della spesa, cataloghi, ecc.; • oggetti e concetti appartenenti al mondo concettuale che il sistema necessita di manipolare. Sempre nel caso del sistema per il commercio elettronico: regole di sconto, segnalazioni del verificarsi di condizioni richieste dall’utente (prodotto in sconto, prezzo dimezzato, ecc.); • eventi che possono verificarsi, come l’aggiunta di un prodotto al carrello della spesa, la notifica a un utente di una promozione relativa a uno specifico articolo, ecc. Nel modello del sistema teatrale presentato nel Capitolo 2, esempi di classi appartenenti al dominio del problema sono: Spettacolo, Rappresentazione, Biglietto, Posto, Listino prezzi ecc. (In sostanza tutte le classi riportate sono dei validi esempi, considerato che si tratta di un modello a oggetti del dominio.) In un sistema di prenotazioni di biglietti di voli aerei, esempi di classi appartenenti al modello del dominio sono: Aeroporti, Aeromobili, Biglietti, Tratte, Vettori, Classi di servizio, ecc.

UML e ingegneria del software: dalla teoria alla pratica

9

Una caratteristica tipica dei modelli a oggetti del dominio consiste nello specificare poche o nessuna operazione nelle varie classi. Spesso si investe molto tempo nel tentativo di dettagliare le proprietà comportamentali delle classi (definizione dei metodi) e non sempre è il caso di investire del tempo in questa attività. Probabilmente è opportuno attendere fasi più mature del processo di sviluppo nelle quali si ha una maggiore comprensione del dominio del problema. In questo stadio si è interessati essenzialmente all’organizzazione dei dati che, al livello di classi, si traduce nella relativa struttura: attributi e relazioni con le altre classi. Invece di specificare gli attributi, si potrebbe definire tutta una serie di metodi get/set (come per i componenti entità nell’architettura J2EE). Limitatamente a questo contesto, si tratterebbe unicamente di una perdita di tempo che tra l’altro renderebbe il modello decisamente meno leggibile, specie per coloro che dovranno rivedere il modello. Questi, tipicamente, sono esperti della area di business senza troppa conoscenza del mondo OO.

I lettori con esperienza della metodologia dell’analisi strutturata potranno constatare molte analogie tra il modello concettuale entity-relationship e il modello del dominio del problema di cui quest’ultimo rappresenta l’evoluzione OO. Il modello del dominio deve essere prodotto in fasi iniziali del processo di sviluppo sia perché permette di bilanciare i casi d’uso rispetto la struttura dei dati, sia perché fornisce il punto di partenza per la realizzazione di tutta una serie di manufatti, non ultimi il modello dei componenti (come si vedrà, in genere ognuno di essi agisce su un opportuno sottoinsieme), il prototipo dell’interfaccia utente, ecc. Considerando l’utilizzo che viene fatto del modello a oggetti del dominio all’interno del ciclo di vita del sistema e tutta la serie di manufatti che lo utilizzano, si può facilmente comprendere come esso possa essere considerato una colonna portante dell’intero sistema. Il modello a oggetti del dominio, una volta terminato, rappresenta un’asserzione dello spazio del problema o, se si desidera, il compendio delle regole di business relative all’organizzazione dei dati manipolati. Per esempio, quando si afferma che un “Trade genera diversi messaggi” (ossia che le istanze della classe Trade sono associate a diversi oggetti Message) o che ogni “Trade è assegnato a un solo Book in funzione della valuta principale”, non si fa altro che modellare le regole del business. Il DOM quindi è sicuramente un manufatto assolutamente necessario allo sviluppo del sistema, ma costituisce (come gran parte dei modelli) anche un prodotto finale, un “delivery” del processo. Si presta ad essere utilizzato per eventuali future reingegnerizzazioni (tipicamente la tecnologia è mol-

10

Capitolo 8. Le classi nei processi

to variabile, il business no), per la progettazione di sottosistemi aggiuntivi, per l’illustrazione del business magari a nuovi dipendenti, ecc.

La produzione del modello del dominio è un’attività molto importante e sensibile per il buon avanzamento dell’intero processo che, teoricamente, dovrebbe essere realizzata dai business analyst. Considerata l’importanza del manufatto, probabilmente è opportuno che il modello venga, per così dire, elaborato con la continua presenza di personale tecnico con esperienza di modellazione. Una buona idea è ricorrere alla collaborazione di un architetto, sia per elevare il grado di formalità del modello stesso, sia per coinvolgere nella definizione dello spazio del problema persone che poi dovranno realizzare il sistema basato sul modello stesso.

Nei paragrafi seguenti viene presentata una tecnica pratica rivelatasi utile per la produzione del modello a oggetti del dominio del problema, corredata da tutta una serie di linee guida a carattere generale frutto dell’esperienza dell’autore. Tutti coloro con esperienza nella produzione di modelli entità-relazioni (Entity-Relationship Diagram) noteranno che è necessario utilizzare le stesse capacità: in ultima analisi si tratta di eseguire la medesima attività con un formalismo diverso.

Presentazione dello spazio del problema Premessa In questo paragrafo è descritto un dominio utilizzato come caso di studio per i vari esempi mostrati nel corso dell’intero capitolo. La difficoltà consiste nell’individuare un’area di studio che sia in qualche modo comune al maggior numero possibile di lettori e pertanto possa configurarsi come una piattaforma costruttiva di “monologo dell’autore”. Il problema è che raramente accade di lavorare in sistemi atti ad automatizzare ambiti di pubblico dominio. In ogni modo si è deciso di presentare un esempio tratto dal mondo della sicurezza con la speranza che la maggior parte dei lettori vi sia entrata in contatto (in effetti si tratta di un’infrastruttura che dovrebbe essere presente in tutti i sistemi). In particolare l’attenzione è focalizzata sulla descrizione di un sottosistema che da un lato automatizzi il workflow necessario per fornire agli utenti del sistema le credenziali per accedervi e dall’altro garantisca il rispetto delle politiche di sicurezza (autenticazione, autorizzazione, auditing degli utenti, incidenti di sicurezza, ecc.). Il contesto in cui viene analizzata l’infrastruttura di sicurezza è relativo a un sistema in tecnologia web di un’azienda

UML e ingegneria del software: dalla teoria alla pratica

11

multinazionale con sedi sparse in tutto il mondo che utilizzi la risorsa Internet come rete di connessione globale condivisa. La presentazione riportata successivamente viene arricchita durante l’illustrazione dell’intero processo. Si è deciso di non aggiornarla direttamente al fine di simulare il più possibile un caso reale in cui, volenti o nolenti, l’aumento della comprensione del dominio avviene procedendo nelle fasi del processo di sviluppo del software. Considerati i fini del libro, l’attenzione è circoscritta al sistema software, tralasciando considerazioni relative alla parte, pur molto importante, dell’infrastruttura hardware (proxy, firewall, ecc). La descrizione è stata riportata con l’obiettivo di disporre di un’area di studio da utilizzare per mostrare le diverse tipologie di modelli a oggetti presenti nelle varie fasi di un tipico processo di sviluppo del software e non per fornire un “white paper” sulla sicurezza. Quindi l’obiettivo è fissare un dominio di riferimento, indipendentemente dalla validità delle business rule riportate. In ogni modo eventuali suggerimenti provenienti da esperti della particolare area saranno ben accolti.

Descrizione La comunità degli utenti del sistema da realizzare è classificata in gruppi di utenti in funzione delle mansioni svolte. Di questi ruoli, quelli strettamente di interesse nel dominio di studio (in quanto veri e propri attori e non meri individui oggetto di controllo) sono i Line Manager, gli addetti alla sicurezza e i supervisori della sicurezza. I primi, oltre ad avere mansioni legate alla propria area di controllo (marketing, accounting, ecc.), hanno tutta una serie di responsabilità legate al loro status di manager. In altre parole, sono classificabili attraverso una doppia ereditarietà: ereditano dall’utente della propria area (devono essere in grado di poter eseguire tutti i compiti dei propri subalterni) e dal manager generico (devono svolgere mansioni manageriali). In sistemi come quello oggetto di studio, la corretta analisi e strutturazione della gerarchia degli utenti si ripercuote sull’organizzazione dell’infrastruttura di sicurezza e in particolare sui diritti di accesso del sistema La relazione tra gli utenti e i relativi gruppi è chiaramente del tipo (n,n): un utente può appartenere a diversi gruppi e ciascuno di essi raggruppa svariati utenti. Inserire un utente ad un gruppo implica l’abilitazione dello stesso ad usufruire di un insieme ben definito di servizi (quelli necessari per espletare i compiti propri del ruolo) e l’esclusione dei restanti. In questo contesto dovrebbe essere evidente come gli attori dei casi d’uso rappresentino i gruppi in cui è possibile ripartire gli utenti del sistema.

12

Capitolo 8. Le classi nei processi

Figura 8.2 — Esempio di classificazione degli attori. Esempio relativo agli addetti del reparto di contabilità.

Utente Sistema

. . .

. . .

. . .

Addetto Contabilita

Line Manager

. . .

. . . Line Manager Contabilita

Per quanto attiene all’infrastruttura di sicurezza oggetto di studio, i Line Manager hanno la responsabilità di richiedere: • le credenziali per l’accesso al sistema per i dipendenti a loro assegnati specificando il profilo del relativo lavoro (dipartimenti accessibili, funzioni eseguibili, …), • la variazione del profilo qualora un loro subalterno cambi la tipologia di lavoro (riceva una promozione, cambi di mansioni/dipartimento, …); • l’eventuale sospensione dell’accesso ogni qualvolta il personale effettui un’assenza prolungata (malattia, vacanze, …);

UML e ingegneria del software: dalla teoria alla pratica

13

• l’immediato lock (blocco), qualora i dipendenti si dimettano o siano licenziati, ecc. Da tener presente che i Line Manager possono richiedere per i propri subalterni allocazioni, variazioni, cancellazione dei profili, ma la responsabilità finale spetta agli addetti della sicurezza. Le varie richieste avvengono attraverso il sistema e in particolare compilando un’apposita scheda denominata Modulo di Accesso. Queste schede, una volta compilate, sono sottoposte al sistema, il quale si occupa di convogliarle ad un apposito addetto alla sicurezza. Quest’ultimo ne effettua i controlli iniziali che possono generare un esito positivo e quindi il modulo è validato, oppure negativo e quindi il modulo viene respinto e posto all’attenzione del Line Manager richiedente. Una volta che un modulo è accettato questo viene automaticamente presentato all’attenzione del supervisore della sicurezza (si tenga a mente che si sta facendo riferimento a un’organizzazione internazionale con personale dislocato in diversi uffici sparsi in diverse nazioni), il quale può approvare definitivamente il modulo rendendolo operativo oppure rifiutarlo. I moduli di accesso respinti, in qualsiasi fase, tornano all’attenzione dei Line Manager richiedenti (corredati dalla motivazione per la sospensione), i quali possono decidere di apportare le necessarie variazioni, oppure, in casi estremi, di cancellare la pratica (per esempio una richiesta di blocco utente potrebbe essere rifiutata poiché l’utente in questione è già bloccato). Chiaramente è necessario che il sistema tenga traccia delle varie fasi attraverso le quali un modulo di accesso transita, memorizzando i vari utenti che ne originano le transizioni nelle varie fasi, i timestamp, eventuali commenti, e così via. Questa descrizione si presta a essere efficacemente sintetizzata e chiarita attraverso un diagramma degli stati riportato in fig. 8.3. Come si può notare la relativa comprensione non richiede approfondite conoscenze di nozioni tecniche e quindi il diagramma si presta ad essere utilizzato come valida piattaforma di colloquio con gli utenti. L’esperienza insegna che diagrammi degli stati non eccessivamente complessi permettono quasi sempre, e in maniera indolore, di chiarire i requisiti utente, renderli più immediati, evidenziare eventuali lacune nell’analisi delle transizione tra gli stati, e così via. Quindi, qualora la parte del dominio oggetto di studio si presti ad essere rappresentata per mezzo della transizione di un oggetto (o di un gruppo opportunamente strutturato) attraverso un insieme finito di stati, è sempre consigliato rappresentare il tutto tramite un opportuno diagramma degli stati.

Si tenga presente che i moduli di accesso non sono relativi esclusivamente alla richiesta di un nuovo profilo per un utente, bensì permettono di richiedere la variazione di un profilo esistente, di sospendere, di bloccare e sbloccare un utente, e così via. Una volta che un Modulo di Accesso è approvato, non significa che automaticamente la richiesta contenuta abbia effetto immediato. Il Line Manager deve indicare il

14

Capitolo 8. Le classi nei processi

Figura 8.3 — Diagramma degli stati del ciclo di vita di un modulo di accesso.

Line Manager compila Modulo di Accesso Line Manager corregge il Modulo di Accesso

SOTTOPOSTO Addetto alla sicurezza rigetta il modulo

RESPINTO

Supervisore sicurezza rigetta il modulo

Line Manager cancella Modulo di Accesso

CANCELLATO

Addetto alla sicurezza valida il contenuto del modulo

VALIDATO Supervisore sicurezza approva il contenuto del modulo

APPROVATO

periodo di validità degli effetti e quindi il sistema si farà carico di assicurare che questi abbiano luogo il giorno e l’ora indicati nel Modulo di Accesso. Ciò equivale a dire che deve essere previsto un meccanismo in grado di “prenotare” dei lavori da eseguire (PendingTask , come per esempio rimozione di una sospensione) e un servizio temporizzato che si occupi di eseguirli. In questa fase, qualora il Modulo di Accesso si riferisca al primo profilo da assegnare all’utente, il sistema ha la responsabilità di generare la prima password temporanea e di notificarla al relativo utente tramite un’e-mail opportunamente crittografata. Da tener presente che il login dovrebbe costituire un identificativo univoco dell’utente da generare all’atto dell’inserimento dei relativi nel sistema di anagrafica dell’organizzazione stessa. Per ciò che attiene alle funzioni relative al Modulo di Accesso, è possibile evidenziare una serie di business rules relative a controlli a carico del sistema: per esempio è necessario inibire ogni tentativo di • compilare moduli di accesso riferiti a sé stesso da parte di un Line Manager;

15

UML e ingegneria del software: dalla teoria alla pratica

• validare moduli di accesso riferiti a sé stesso da parte di un addetto alla sicurezza; • approvare un modulo riferito a sé stesso da parte di un supervisore alla sicurezza, di approvarne altri da lui stesso validati, ecc.

Figura 8.4 — Ciclo di vita di un profilo.

Line manager sottomette un modulo di accesso relativo ad un nuovo profilo

PREPARAZIONE

Line manager cancella il modulo di accesso

Supervisore sicurezza approva il modulo di accesso relativo al nuovo profilo

Nuovo profilo diviene operativo, ma la validita' non eccede quella del presente

DORMIENTE

ATTESA Raggiunta data inizio validita'

OPERATIVO

Nuovo profilo Raggiunta Profilo operativo disattivato, con data di data fine corrente ancora nel periodo di validita' validita' validita' superiore a quella del Raggiunta Disattivato presente data fine tramite apposito validita' modulo di accesso

Disattivato tramite apposito modulo di accesso

Disattivato tramite apposito modulo di accesso

STORICO

Il presente diagramma si presta a essere ottimizzato racchiudendo gli stati di ATTESA, DORMIENTE e OPERATIVO in un macrostato (VALIDO per esempio). Ciò permetterebbe di evidenziare i flussi comuni, una sola volta, al livello del nuovo macrostato, mentre i restanti resterebbero inalterati. In questo contesto si è deciso di non operare in tal senso al fine di non complicare il diagramma.

Molto importante è memorizzare in appositi sistemi di log tutte le attività eseguite dagli utenti, nonché i tentativi di violare le politiche di sicurezza. Questi permettono di eseguire

16

Capitolo 8. Le classi nei processi

appositi report e di rilevare tempestivamente tentativi di intrusione, meglio noti come incidenti di sicurezza. Considerata l’eventuale complessità e distribuzione mondiale del sistema, è necessario introdurre il concetto di “privatezza dei dati”. Nell’infrastruttura di sicurezza presa in considerazione, questo principio comporta l’introduzione del concetto di dipartimento e di filiale (da notare che dipartimento e filiale non sono utilizzati come sinonimi bensì con il loro naturale significato: una filiale è composta da diversi dipartimenti). Fino a questo punto si è considerato un classico livello di sicurezza operante al livello di servizi (considerando un sistema web, i servizi sono rappresentati da opportuni indirizzi URL delle pagine che li rendono fruibili agli utenti). Se per esempio un utente appartiene al gruppo degli addetti alla sicurezza, questo lo abiliterà a eseguire tutto un insieme di servizi (come per esempio validazione dei moduli di accesso) e ne escluderà l’esecuzione di tutto un altro insieme (come per esempio approvazione dei moduli di accesso). Nel sistema preso in esame si tratta della classica condizione necessaria ma non sufficiente. In effetti, se un utente dispone di un ruolo X ciò non lo abilita automaticamente a usufruirne per tutti i dipartimenti e/o filiali (per esempio, il direttore di una filiale di banca non può automaticamente esercitare lo stesso ruolo in tutte le altre filiali). Pertanto, ogni utente deve possedere nel profilo opportune sezioni (contesti di sicurezza) nelle quali è sancito un gruppo di appartenenza e i dipartimenti/filiali in cui l’utente è autorizzato a usufruire dei relativi servizi. Un addetto alla sicurezza, per esempio, sarà in grado di validare o rigettare i Moduli di Accesso solo relativi ai dipartimenti a cui è stato abilitato. Immaginando che l’azienda disponga di filiali a Roma, Milano, Parigi, Londra, … e un utente sia autorizzato a usufruire del proprio ruolo limitatamente per le sedi di Roma e Milano, ciò comporta che egli non sarà mai in grado di vedere e tanto meno manipolare dati relativi ad altre filiali. In alcune aree particolari, l’accesso ai dati di un utente deve essere ristretto anche a un livello inferiore rispetto a quello delle filiali, ecco dunque che spesso è necessario riferirsi al livello di dipartimento. Se per esempio si prende in considerazione un sistema bancario, è possibile notare come un utente con il ruolo di Broker dovrebbe aver accesso ai soli dati relativi ai contratti (più precisamente Trade) da lui stipulati più altri di carattere generare, e quindi il livello di accesso dovrebbe essere addirittura inferiore a quello di dipartimento, mentre un addetto alla risoluzione di eventuali anomalie presenti nelle trascrizione dei contratti (come per esempio regole di pagamento errate) potrebbe operare a livello di un’intera filiale o addirittura di più filiali. Affinché questo meccanismo possa aver luogo, è molto importante che il sistema sia in grado di conoscere l’appartenenza dei vari dati, o gruppi, al fine di poterli confrontare con quelle assegnate all’utente. Ciò si ottiene associando ai dati anche i dipartimenti e le filiali di appartenenza. Se per esempio si utilizzasse un DBMS relazionale, sarebbe necessario indicare i dipartimenti e/o filiali di appartenenza dei vari record di dati. Chiaramente non è necessario restringere l’accesso per tutti i dati bensì solo per quelli ritenuti sensibili. Se per esempio il sistema manipolasse dati di carattere generale (per esempio dati

UML e ingegneria del software: dalla teoria alla pratica

17

statici relativi a valute, nazioni, filiali, e così via) non sarebbe ovviamente necessario restringere l’accesso a questa tipologia di dati. Riassumendo il tutto, quando un utente richiede di eseguire un servizio (inserisce un indirizzo URL nel browser o effettua un click su un link) il sistema reperisce l’elenco dei gruppi di appartenenza dell’utente e verifica se ad almeno uno di questi sia associata l’abilitazione all’esecuzione del servizio richiesto. In caso affermativo, l’utente è autorizzato ad eseguire il servizio, altrimenti ne viene negato l’accesso (livello di sicurezza relativo ai servizi). Una volta ottenuta l’autorizzazione ad usufruire del servizio (per esempio compilazione Modulo di Accesso), l’utente, per poterne fruire, deve tipicamente reperire determinati dati (reperimento del subalterno a cui fa riferimento la richiesta) ed eventualmente aggiornarli (memorizzazione di un nuovo Modulo di Accesso). Nel primo caso i dati reperiti vengono filtrati al fine di far transitare solo quelli relativi ai dipartimenti e filiali per le quali l’utente può esercitare il ruolo mentre, nel caso di inserimenti e/o aggiornamenti, sono bloccati eventuali tentativi di impostare dati appartenenti a dipartimenti e filiali per le quali non è autorizzato. Introdotto il concetto di sicurezza o privatezza dei dati è possibile presentare il concetto di incompatibilità necessario per comprendere le ragioni per cui un addetto alla sicurezza potrebbe respingere un Modulo di Accesso. Una volta strutturata l’organizzazione degli utenti in gruppi è possibile notare che alcuni di essi sono incompatibili tra loro, in altre parole il sistema deve prevenire che i profili assegnati agli utenti possano contenere mansioni, dal punto delle policy aziendali, inconciliabili (per esempio il ruolo di una persona che stipula contratti con i clienti dovrebbe essere incompatibile con quello relativo al controllo dei contratti stipulati). Si sta facendo riferimento al concetto noto con il nome di separazione delle mansioni (separation of duties). In sostanza, si vuole assicurare che uno stesso utente non possa avere il controllo completo di transazioni business dall’inizio alla fine. Il controllo delle transazioni deve essere ripartito tra più persone. Per esempio, se la transazione presa in esame è l’assegnazione di nuovo profilo, le responsabilità relative alla richiesta, prima validazione e approvazione finale, devono essere ripartite tra altrettanti utenti. Si vuole ovviamente evitare che una stessa persona, disponendo del completo controllo di alcune transazioni, possa usufruirne a scopi personali o peggio ancora fraudolenti (come ripeteva il grande Totò “l’occasione fa l’uomo ladro”). Spesso, oltre alla politica della separation of duties, si abbina quella della rotazione delle mansioni (rotation of duties) che comporta la periodica variazione dell’assegnazione dei lavori. Ciò chiaramente serve ad evitare che si instauri un circolo vizioso e fraudolento all’interno dell’organizzazione. In alcuni casi le incompatibilità potrebbero non essere limitate alle mansioni, ma potrebbero includere anche concetti come filiali e dipartimenti (per esempio agli utenti potrebbe essere vietato di poter accedere contemporaneamente ai dati della filiale di Milano e di Roma). Le incompatibilità potrebbero poi essere classificate in warning (il sistema si limita ad evidenziarle demandando all’addetto della sicurezza la decisione finale) ed error

18

Capitolo 8. Le classi nei processi

Figura 8.5 — Diagramma degli stati relativi al ciclo di vita di un utente. Si tratta di un diagramma non completamente corretto dal punto di vista dello UML in quanto lo stato di BLOCCATO dovrebbe memorizzare lo stato da cui vi si arriva (in cui avviene il bloccaggio) per potervici ritornare qualora il blocco venga rimosso. Ciò si ottiene inserendo un particolare sottostato denominato History e indicato con la lettera H. In questo contesto si è deciso di non introdurlo al fine di non appesantire ulteriormente la trattazione.

Dati anagrafici dell'utente presenti nel sistema

INSERITO Compilato Modulo di Accesso per un nuovo profilo

ATTESA

Profilo operativo dell'utente espirato e nessun altro valido disponibile

Rimozione blocco Utente bloccato

BLOCCATO Rimozione

Nuovo profilo entrato nel periodo di validita'

OPERATIVO blocco

Termine sospensione

DISATTIVATO

Utente rimosso dal sistema Utente sospeso

Utente bloccato

SOSPESO

(il sistema blocca il Modulo di Accesso). Il controllo chiaramente dovrebbe essere effettuato durante tutte le fasi (sottomissione, validazione e approvazione): alcune regole di incompatibilità potrebbero venir inserite o aggiornate tra l’intervallo di tempo che va dal momento in cui un Modulo di Accesso sia sottoposto a quello in cui sia validato. Ogni utente del sistema è autorizzato a possedere diversi profili, però è consentito disporre di uno solo nello stato operativo e quindi utilizzabile. Per esempio, dovendo trasferire temporaneamente un dipendente da un dipartimento o da una filiale ad un’altra, è possibile congelarne il profilo corrente (porlo nello stato sospeso) per assegnarne uno nuovo limitatamente alla durata del trasferimento. Per motivi di semplicità, l’accesso al sistema avviene utilizzando il paleolitico metodo basato sulla coppia (login, password). La trattazione ovviamente si presta ad essere facilmente estesa con tecniche più sicure (smart cards, sistemi di riconoscimento biometrico, e così via).

UML e ingegneria del software: dalla teoria alla pratica

19

Per quanto concerne l’entità utente, le informazioni di interesse sono: login (generata automaticamente dal sistema), password (corredata dalla data e orario di scadenza), il codice fiscale, il nome, cognome, la data e città di nascita, l’indirizzo di posta elettronica ed il numero di telefono interno, il recapito a cui contattarlo e il ruolo esercitato all’interno dell’organizzazione. I servizi di blocco e sblocco e sospensione e revoca sospensione utente, oltre a poter essere eseguiti attraverso l’iter del Modulo di Accesso (in tal caso la richiesta è responsabilità del Line Manager), devono poter essere eseguiti direttamente sia dagli addetti alla sicurezza, sia dal supervisore. Le differenze tra Blocco (lock) e Sospensione sono che il primo ha una connotazione molto forte (avviene automaticamente qualora un utente fallisca per tre volte consecutive di seguito l’inserimento della password, in caso di licenziamento, sospetto di intrusione, …) e non ha una scadenza, mentre il secondo è meno forte (si applica per esempio per temporanee assenze dal lavoro: malattie, ferie, ecc.) e ha carattere transitorio: è indicata la scadenza.

Produzione del modello a oggetti del dominio L’obiettivo del presente paragrafo è illustrare un approccio pratico ma efficace all’individuazione delle classi appartenenti al dominio del problema, noto con il nome di “analisi dei nomi e verbi”. Collaborando con molte aziende, è possibile constatare che le domande che più frequentemente ci si sente rivolgere sono relative alle tecniche da utilizzarsi sia per individuare classi appartenenti al dominio del problema, sia, più in generale, per poter materializzare i requisiti utente (magari formalizzati per mezzo dei casi d’uso) attraverso un opportuno modello del disegno. Si tratta di interrogativi piuttosto legittimi le cui risposte, ahimè, sono tutt’altro che banali. In questo paragrafo si focalizza l’attenzione sul primo quesito. Individuare le classi del dominio sta diventando un’attività sempre più cruciale nel processo di costruzione di sistemi basati sui componenti in quanto, per ciò che concerne la realizzazione dell’infrastruttura, si stanno compiendo molti sforzi per renderla sempre più automatica (si considerino per esempio le architetture EJB e .net in cui gran parte dell’infrastruttura è incapsulata direttamente nei tool forniti dai vari vendor). La realizzazione del modello a oggetti del dominio è un’attività complessa fortemente dipendente da quelle che sono proprietà soggettive del singolo individuo (esperienza, cultura, abilità di analizzare, capacità analitiche, ecc.) in cui spesso anche la fortuna gioca il suo ruolo. Questa forte dipendenza dalle caratteristiche soggettive degli individui determina la difficoltà di formalizzare un metodo rigoroso e meccanico. La buona notizia è che con l’esperienza il processo diviene assolutamente naturale (cognitivo) tanto da essere applicato in maniera istintiva. Una similitudine è relativa all’apprendimento delle lingue straniere (qui parla la voce dell’esperienza dell’autore). Quando si parla una lingua da molti anni, si coniugano i verbi senza neanche pensarci — spesso purtroppo —, il tutto

20

Capitolo 8. Le classi nei processi

avviene in maniera immediata, i pensieri si sciolgono spontaneamente in discorsi razionali. Quando invece si è novizi, spesso accade che, prima di riuscire a coniugare i verbi, sia necessario far precedere qualche minuto di riflessione del tipo: il verbo è regolare oppure l’azione non continua nel presente ecc. Ebbene, nell’individuazione delle classi appartenenti al modello del dominio avviene esattamente la stessa cosa: l’esperienza fa sì che sia possibile costruire modelli a oggetti del dominio semplicemente intervistando gli esperti dell’area business o leggendo i documenti di analisi dei requisiti. Continuando nella similitudine, così come il modo migliore per imparare una lingua è frequentare persone madrelingua, così anche nel mondo OO: il modo migliore per accrescere la propria esperienza consiste nel lavorare con personale preparato ed esperto ed eventualmente studiare gli esempi riportati nella letteratura specifica. Infine, è possibile osservare che le persone, nel parlare, tendono a riutilizzare determinati costrutti. Nella modellazione, molto spesso, avviene lo stesso procedimento: si tende a riutilizzare pattern dimostratisi validi in precedenti modellazioni (in fondo non è uno dei punti di forza dell’OO?). Anche se i sistemi a cui si lavora risultano sempre diversi tra loro, tipicamente, è sempre possibile individuare delle aree comuni. Ora, sebbene l’esperienza permetta di sviluppare le capacità necessarie per realizzare validi modelli a oggetti, cosa fare durante il lasso di tempo necessario per acquisire le dovute competenze? In questo paragrafo si presenta una metodo pratico che tenta proprio di risolvere questo problema: agevolare la realizzazione di versioni embrionali del modello a oggetti del dominio. Ciò si ottiene fornendo delle buone idee, consigli empirici, e rendendo consapevoli delle trappole nelle quali si può cadere… Il resto, ahimè è a carico del modellatore. Il metodo può essere di grande aiuto, però non si tratta della tanto agognata bacchetta magica, in grado di far emergere le classi dai modelli di analisi dei requisiti. Come si vedrà si tratta di una tecnica, che sebbene abbia una sua legittimità accademica è stata a lungo oggetto di critiche non sempre giustificate. (Nel 1990 presso il Tokyo Institute of Technology è stato realizzato un software in grado di effettuare l’analisi di un testo dei requisiti al fine di costruire una versione iniziale del modelli a oggetti. Sebbene di questa tecnica esistano diverse versioni, quella iniziale probabilmente è attribuibile a Abbott). Chiaramente è necessario inquadrare ogni strumento nel proprio contesto. Se la tecnica promettesse di sostituire quelle più accademiche, allora probabilmente le critiche risulterebbero legittime, se invece l’obiettivo è fornire un approccio semplice, senza avere grosse pretese di formalità, per costruire versioni iniziali del modello a oggetti del dominio, rimandando alle metodologie più formali le attività di verifica e miglioramento del modello stesso, allora verosimilmente i vari attacchi perderebbero di legittimazione. Le prime iterazioni dei processi iterativi e incrementali di sviluppo del software sono tipicamente incentrate sulla produzione di due manufatti di estrema importanza: il modello dei casi d’uso e quello a oggetti del dominio. Generalmente si preferisce iniziare dal primo, ossia dall’analisi dei servizi (funzioni) che il sistema dovrà fornire ai propri attori,

UML e ingegneria del software: dalla teoria alla pratica

21

per poi focalizzare l’attenzione sulle entità coinvolte. Chiaramente si tratta di “preferenze soggettive”, nulla vieta di utilizzare l’approccio contrario, l’importante è che alla fine i vari modelli risultino coerenti e consistenti. Un altro manufatto molto importante è il vocabolario (o glossario) dei termini utilizzati nell’area business: in ultima analisi il modello a oggetti del dominio può essere visto come la versione OO di un suo opportuno sottoinsieme. Alla fine dell’analisi dei requisiti è quindi necessario che il modello a oggetti del dominio, gli use case ed il glossario siano mutuamente bilanciati. Ciò si ottiene quando gli use case ed il vocabolario contengono riferimenti (nel primo caso) e descrizioni (nel secondo) di tutte (o quasi) le classi illustrate nel modello a oggetti del dominio ed il viceversa, ossia tutti gli elementi menzionati nel modello degli use case e tutti i termini appartenenti alla sezione business del vocabolario siano modellati nel modello a oggetti.

Qualora non sia ancora disponibile un modello dei requisiti, magari perché si vuole costruire i vari modelli contemporaneamente procedendo con una continua sincronizzazione, è possibile applicare la tecnica utilizzando gli appunti emersi durante i vari colloqui e workshop con gli utenti e qualsiasi documentazione esistente, come per esempio un documento di descrizione dell’area business, dell’impresa ecc. Nel caso limite in cui non si disponga di molta documentazione e non si sappia bene da dove iniziare, un buon approccio consiste nel cominciare a redigere una lista riportante tutte le asserzioni relative allo spazio del problema emerse durante i vari colloqui (frasi del tipo “un utente compila diversi ordini, ciascuno di essi è composto da articoli, gli articoli dispongono del prezzo e possono prevedere delle promozioni dipendenti da particolari regole”, ecc.).

Individuazione delle classi Primo passo Il primo passo da seguire consiste nell’individuare l’elenco delle ipotetiche classi. In questo stadio si tratta di un’operazione non eccessivamente complessa, quasi meccanica. Più precisamente è necessario eseguire una sorta di analisi grammaticale. Si legge attentamente il documento (use case, appunti presi durante i colloqui con gli utenti, documentazione varia, ecc.), e si evidenziano tutti i nomi e coppie di nomi/aggettivi incontrati strettamente legati all’area oggetto di studio. Terminata la prima lettura, conviene effettuarne una seconda (eventualmente anche una terza) e quindi riportare quanto evidenziato in un’apposita lista. Chiaramente è sempre consigliabile iniziare una primissima cernita eliminando i nomi che palesemente non potrebbero mai assurgere al ruolo di classe nel contesto del dominio di riferimento ma sono utilizzati unicamente allo scopo di illustrare lo stesso (per esempio entità, sistema, organizzazione, ecc.).

22

Capitolo 8. Le classi nei processi

Tabella 8.1 — Elenco delle ipotetiche classi appartenenti al dominio oggetto di studio. Da notare che alcune candidate al ruolo di classe sono state introdotte artificiosamente (codice fiscale, e-mail, …) al fine di poter illustrare alcune regole riportate di seguito, mentre altre sono state volutamente ignorate sempre per fini espositivi. N.

Nome

Descrizione

1 2

CodiceFiscale ContestoDiSicurezza

Codice fiscale degli utenti del sistema. È parte integrante di un profilo. Accoppia un’istanza di un gruppo utenti alla filiale o dipartimento in cui i servizi specificati dal gruppo possono essere utilizzati. Ciascun profilo dispone di uno o più contesti di sicurezza.

3 4

Dipartimento eMail

Elemento dell’organizzazione gerarchica interna delle filiali. Incapsula un indirizzo di posta elettronica.

5 6

Filiale FirmaDigitale

Rappresenta una sede distaccata dell’organizzazione. Sistema di riconoscimento utente.

7

GruppoUtenti

8

Indirizzo

Ciascuna istanza di questa classe individua una categoria di utenti del sistema (ruolo) e quindi l’elenco dei servizi fruibili. Rappresenta il domicilio di un utente.

9

Località

Rappresenta le località a cui si riferiscono le varie istanze della classe Indirizzo.

10

Log

11

Login

Descrizione di un’azione eseguita dall’utente (auditing), corredate dall’eventuale azione intrapresa dal sistema come risposta. Identificativo univoco di ciascun utente utilizzato come credenziale di riconoscimento.

12

ModuloAccesso

13

Motivazione

14 15

Nazione Notifica

16

Password

17

PendingTask

18

Profilo

19

Record

20 21

RegolaIncompatibilita Report

Regola dichiarante ruoli, filiali e/o dipartimenti tra loro incompatibili. Elenco di informazioni omogenee estratte attraverso l’applicazione di un criterio ben definito all’insieme dei dati memorizzati nel sistema.

22 23

RuoloOrganizzazione Schede

Si tratta dei ruoli propri previsti dall’azienda. Schede compilate dai Line Manager.

24 25

Servizio StadioModuloAccesso

26

Utente

Rappresenta una generica funzionalità fornita dal sistema. Serve per tener traccia dell’evoluzione di una richiesta di accesso. In particolare rappresenta uno stadio attraverso il quale è transito il relativo modulo. Rappresentazione logica del sistema degli utenti registrati e quindi abilitati a fruirne i servizi.

Modulo che consente ai Line Manager di richiedere l’esecuzione di specifici servizi relativi agli utenti e relativi profili. Spiegazione relativa al rigetto di una richiesta di accesso. Rappresenta le nazioni a cui appartengono le varie località. Incapsula la comunicazione che il sistema effettua per notificare agli addetti alla sicurezza ed ai supervisori, l’esistenza di Moduli di accesso da prendere in considerazione e ai Line manager il rifiuto di un modulo da loro sottoposto. Parola segreta dell’utente da utilizzarsi in coppia con la Login per accedere al sistema. Sono le attività che il sistema deve eseguire per rendere operativi gli effetti sanciti nei moduli di accesso approvati dai supervisori della sicurezza. Si tratta di una classe necessaria per assegnare agli utenti le autorizzazioni ad eseguire i servizi offerti dal sistema necessari per espletare le proprie mansioni. Gruppo omogenei di dati trattati dal sistema (si fa riferimento al significato di record proprio dei database).

UML e ingegneria del software: dalla teoria alla pratica

23

Nel selezionare i nomi delle classi, è molto importante che questi siano il più possibile correlati agli oggetti appartenenti al dominio del problema. Chiaramente più i nomi sono ovvi più è facile comprendere il modello. Si tenga presente anche che il modello a oggetti del dominio è un prodotto di input di diverse fasi, destinato ad essere fruito dalle più svariate tipologie di persone (utenti/ clienti, esperti del business, architetti, ecc.) e quindi la facilità di lettura assume un’importanza fondamentale. Quello che andrebbe evitato è il ricorso a nomi che non appartengono all’area oggetto di studio e/o frutto di incomprensibili contrazioni.

Con riferimento alla descrizione del dominio oggetto di studio, sarebbe possibile compilare una tabella come la tab. 8.1.

Secondo passo La tabella precedente costituisce il prodotto di ingresso della fase successiva. In particolare, l’obiettivo del secondo passo è filtrare la lista dei sostantivi presenti nella tabella al fine di eliminare quelli che o non possono assurgere al ruolo di classe oppure, pur avendo tutte le credenziali, esulano dagli obiettivi del progetto (out of scope). In questo passo si evidenza come saper eliminare idee sbagliate è altrettanto importante di escogitarne delle buone. Durante questa attività il livello di impegno richiesto comincia ad essere di una certa consistenza. L’ideale sarebbe disporre di uno strumento formale che permetta di stabilire, per ciascun nome inserito nella lista, se si tratti di una classe o meno (una sorta di cartina tornasole delle classi). Il problema, ancora una volta, è che mentre l’esperienza permette di eseguire tale valutazione istintivamente, l’individuazione di un criterio rigoroso non è altrettanto immediato (altrimenti si potrebbe renderlo automatico attraverso un apposito sistema). Chiaramente tutti i tecnici sono in grado di pensare ad alcuni criteri sindacabili come: l’appartenenza o meno al dominio preso in considerazione, la struttura dell’oggetto, e così via. Tuttavia un criterio razionale e completo è meno facile da ideare. Nella fig. 8.6 è riportato un diagramma delle attività che illustra alcuni criteri utili al processo di distinzione tra classi vere e proprie, e supposte tali. Chiaramente, il modo migliore per valutare le classi consiste nel seguire i consigli forniti nel capitolo relativo agli insegnamenti dell’OO (confronta paragrafo “Classi ben disegnate”). Il problema è che si tratta di direttive ad elevato grado di formalità. In questo ambito si vogliono considerare i medesimi metri di giudizio, presentandoli però da una prospettiva decisamente più operativa e quindi più semplice da capire.

24

Capitolo 8. Le classi nei processi

Figura 8.6 — Diagramma delle attività relativo ai criteri da utilizzarsi come riferimento nel processo di validazione delle classi.

Considera prossimo elemento della lista dei nomi

Valuta se rappresenta un'entità appartenente al dominio del problema [entità non appartenente al dominio oggetto di studio] [entità appartenente al dominio oggetto di studio]

Valuta se le eventuali istanze possono assumere esclusivamente valori atomici ["oggetti" a valore atomico] ["oggetti" a valore composto]

Valuta se l'entità possiede caratteristiche strutturali [non possiede caratteristiche strutturali] [possiede caratteristiche strutturali]

Valuta se l'entità possiede caratteristiche comportamentali [non possiede caratteristiche comportamentali] [possiede caratteristiche comportamentali]

Valuta se il comportamento è significativo per il dominio oggetto di studio [comportamento non significativo per il dominio di interesse] [comportamento significativo]

Valuta se l'entità possiede una propria indipendenza [non possiede una propria indipendenza] [possiede una propria indipendenza]

Valuta se l'entità è parte costituente di un'altra "classe" [è parte di un'altra classe]

Si aggregano le due entità

[non è parte di un'altra classe]

Valuta se l'entità raggruppa concetti tra loro disomogenei [l'entità incapsula concetti diversi]

Si separano i due concetti e li si aggiunge alla lista [l'entità non raggruppa concetti diversi]

Valuta se è stata analizzata l'intera lista

Rimuovi elemento dalla lista

[presenti elementi non acora analizzati] [analizzata intera lista]

Criterio 1: classi in scope Come preannunciato una prima domanda da porsi è se la classe appartenga o meno al dominio oggetto di studio che si intende modellare. Una risposta negativa non significa necessariamente che l’entità che si sta analizzando non sia una classe, ma semplicemente che non è il caso di spendere tempo ad investigare, a studiarne la struttura ed il comportamento giacché comunque non è di interesse per il modello.

UML e ingegneria del software: dalla teoria alla pratica

25

Questa domanda, sebbene in prima analisi possa sembrare piuttosto banale, in alcune situazioni potrebbe non esserlo assolutamente. La difficoltà di stabilire l’appartenenza o meno di una classe al dominio di interesse, in alcuni casi, è imputabile alla mancanza di chiarezza nello stabilire i confini stessi del sistema. In tali circostanze è opportuno meditare sull’eventualità di compiere il famoso passo indietro nel tentativo di definire con maggiore precisione gli obiettivi del sistema.

Per esempio, sebbene possa essere di notevole interesse la rappresentazione della firma digitale, dai requisiti del cliente emerge direttamente che si tratta di un meccanismo non richiesto nella realizzazione delle prime versioni del sistema. Ancora tentare di rappresentare i record trattati, al fine di evidenziare la proiezione della sicurezza a livello di dati, potrebbe risultare interessante, ma sarebbe abbastanza fuori luogo trattandosi addirittura di dettagli appartenenti allo spazio delle soluzioni. Ancora, mentre potrebbe essere di un certo interesse disporre di entità quali il recapito e la località di ogni utente, quella di nazione probabilmente esula dagli obiettivi del sistema. Nel contesto oggetto di studio, queste informazioni si prestano a divenire attributi della classe città. Se, invece, l’obiettivo del sistema fosse stato, per esempio, realizzare un sistema per la prenotazioni viaggi, evidentemente classi rappresentanti concetti come nazione e continente avrebbero assunto un ruolo ben più importante.

Criterio 2: istanze della classe La seconda domanda è relativa alle presunte istanze della classe, e in particolare ai valori che queste potrebbero assumere in un ipotetico caso reale tenendo presente che ci si trova nella fase di studio del dominio e non in quella di disegno.

Taluni attributi, come per esempio le date, pur essendo a tutti gli effetti oggetti e non dati semplici, considerarli più di attributi nel contesto della modellazione dello spazio del problema non porterebbe alcun vantaggio, anzi finirebbe per complicare inutilmente il modello.

Premesso ciò, se ci si dovesse accorgere che le istanze della classe oggetto di verifica possono assumere unicamente valori semplici, verosimilmente non è il caso di considerarla tale, indipendentemente dalla relativa rilevanza nel contesto del sistema. Probabilmente è più opportuno considerarla un attributo. Si consideri la login assegnata agli utenti; sebbene costituisca un’informazione di vitale importante per il sistema, le relative istanze

26

Capitolo 8. Le classi nei processi

assumono valori atomici che si prestano ad essere modellati attraverso un attributo piuttosto che una classe. Il criterio relativo alle istanze della classe potrebbe essere argomentato affermando che in alcuni casi si può dare luogo a classi con un solo attributo, magari per evidenziare o un valore opzionale (per esempio il commento relativo ad una fase del ciclo di vita di un modulo di accesso), oppure il caso diametralmente opposto: una molteplicità superiore a uno (per esempio gli indirizzi di e-mail di un utente). Questi casi però rappresentano più l’eccezione che la norma, specie considerando che ci si trova in una fase iniziale del processo di sviluppo del software e che per le ottimizzazioni c’é sempre tempo.

Criterio 3: analisi della struttura Il punto successivo, che a dir il vero è intimamente legato al precedente (e pertanto si configura più come un doppio controllo che come criterio a sé stante), è relativo all’“analisi” dell’eventuale struttura dell’ipotetica classe. In altre parole, è necessario chiedersi se sia possibile identificare una qualche struttura, in termini di attributi, relazioni, ecc. In caso di risposta affermativa allora potrebbe avere senso considerare la classe come tale, altrimenti si ricade nel caso di aver individuato un potenziale attributo. Per esempio è di importanza fondamentale conoscere, per ogni utente del sistema, il relativo indirizzo di e-mail interno (per esempio per notificargli la password iniziale). Sebbene sia possibile ipotizzare una struttura (nome, cognome, dominio, ecc.), considerare gli indirizzi di e-mail più di un attributo sarebbe piuttosto pretestuoso. Criterio 4: analisi del comportamento Dopo essersi interrogati circa la struttura della classe, è possibile spostare l’attenzione sulle eventuali caratteristiche comportamentali (ciò avviene specificando eventuali servizi fornibili dalla classe). La classe ModuloDiAccesso, per esempio, potrebbe prevedere servizi atti a sottoporla all’attenzione del sistema di sicurezza (submit()), a validarne il contenuto (da parte di un addetto alla sicurezza, verifyIncompatibilitiesRules()), a rifiutarla (reject()), ecc. Questo criterio è molto delicato e non sempre è il caso di prenderlo in considerazione nel contesto dei diagrammi a oggetti del dominio. Qualora sia possibile identificare caratteristiche comportamentali, allora sicuramente è possibile proseguire con la valutazione dei criteri successivi; in caso contrario, la situazione nasconde diverse insidie.

La difficoltà di individuare caratteristiche comportamentali non autorizza a scartare una classe. Come riportato in precedenza, quando si elabora un modello a oggetti del dominio, si focalizza l’attenzione sui dati e sulla relativa organizzazione, mentre il comportamento assume un interesse marginale (forse è il caso di sottolineare che ciò vale solo per il modello a oggetti del dominio, per gli altri

UML e ingegneria del software: dalla teoria alla pratica

27

la mancanza di comportamento costituisce l’elemento più evidente per scartare una classe). Quello che si va ad identificare sono le famose classi entità, ossia veri e propri contenitori di dati che, in qualche modo, somigliano allo schema di tabelle di database relazionali. Queste classi, tipicamente sono costituite da una serie di metodi selettori (get/set) utilizzati per accedere ai relativi attributi. Quindi pur non disponendo di particolare comportamento, si tratta di classi di estrema importanza.

Per esempio, volendo rappresentare le filiali ed i relativi dipartimenti, si potrebbe pensare ad attributi come il codice, il nome, la descrizione, ecc. Ora immaginare un comportamento aggiuntivo diverso dai metodi get/set dovrebbe rappresentare un buon esercizio di fantasia eppure a questo livello sono classi a tutti gli effetti.

Criterio 5: comportamento in scope Una domanda che scaturisce dalla precedente è se il comportamento identificato sia rilevante o meno per l’analisi dell’area business che si sta considerando. Si dovrebbe trattare di un’interrogazione inutile qualora l’analisi richiesta dal primo interrogativo sia stata eseguita accuratamente. Molto spesso però, solamente a questo stadio si ha una migliore percezione della struttura e del comportamento della classe e quindi si è in grado di rivalutare accuratamente il quesito del punto 1. Per esempio l’entità Report (sebbene violerebbe anche altri criteri soprattutto per via della genericità della definizione) potrebbe possedere del comportamento, però risulterebbe fuori scope, e comunque la relativa definizione risulterebbe ridondante: si presterebbe ad incapsulare parti del modello del dominio. Criterio 6: valutazione dell’indipendenza Un ulteriore elemento da considerare è se l’ipotetica classe possegga una propria indipendenza oppure abbia senso esclusivamente nel contesto di una più ampia. Analizzando attentamente il criterio, ci si può facilmente rendere conto della presenza dalla teoria dell’ADT (cfr. Capitolo 6). Equivale ad interrogarsi se l’ipotetica “classe” è un nuovo tipo di dato con un insieme ben definito e separato di operazioni, oppure se queste sono o possono naturalmente confluire tra quelle fornite da un’altra classe già accreditata. Eventualmente è possibile porsi la stessa domanda per le classi individuate in precedenza relativamente a quella correntemente analizzata. Qualora una candidata classe sia parte di un’altra è necessario eliminarla e conseguentemente rivedere la definizione di quella “inglobante”. Un esempio classico è relativo al codice fiscale degli utenti, sebbene tale entità potrebbe superare diverse delle precedenti fasi (si tratta di un dato strutturato, può avere del comportamento come per esempio verificaCodice(), ecc.), nella maggior parte delle situazioni non è il caso considerarlo una classe, probabilmente è più opportuno trattarlo come un attributo di un’altra, quale per esempio “persona”.

28

Capitolo 8. Le classi nei processi

Tabella 8.2 — Evoluzione della tabella riportante l’elenco delle classi appartenenti al dominio oggetto di studio. In questa versione è evidenziato l’eventuale motivo che ha portato a scartare alcune classi candidate mentre la descrizione non è stata riportata per questioni di impaginazione della tabella. N. 1 2 3 4

Nome CodiceFiscale ContestoDiSicurezza Dipartimento e-Mail

5 6 7 8

Esito U 9 9

Spiegazione Non possiede una propria dipendenza.

U

Probabilmente la relativa struttura non ne giustifica l’esistenza separata.

Filiale FirmaDigitale GruppoUtenti Indirizzo

9 U 9

Fuori scope.

U

In questo contesto ha più senso considerarlo come un attributo.

9 10 11

Località Log Login

9 U

12 13 14

ModuloAccesso Motivazione Nazione

9 U

15 16 17 18 19 20 21 22 23 24 25 26

Notifica Password PendingTask Profilo Record RegolaIncompatibilita Report RuoloOrganizzazione Schede Servizio StadioModuloAccesso Utente

U 9 9 9 U 9 U 9 U 9 9 9

U

U

Fuori scope. In questo contesto ha più senso considerarlo come un attributo. Mancanza di indipendenza. In questo contesto ha più senso considerarla come un attributo. Evento fuori scope.

Fuori scope. Fuori scope. Ridondante. Utilizzata come sinomino di Modulo di accesso.

Criterio 7: controlli finali Superata anche l’analisi precedente, le povere candidate classi possono cominciare ad intravedere la luce del canvas di un diagramma delle classi. Gli ultimi controlli da effettuare sono relativi all’eventuale raggruppamento di diversi concetti in uno solo (la classe possiede una coesione molto blanda), al caso opposto, cioè la scomposizione di uno stesso concetto in più miniclassi (questa evenienza dovrebbe essere già stata eliminata dal crite-

UML e ingegneria del software: dalla teoria alla pratica

29

rio precedente), all’eventuale presenza di classi ridondanti. Qualora ci si sia imbattuti nel primo caso (in genere ci si accorge dal fatto che le proprietà strutturali e comportamentali presentano uno scarso grado di coesione, in altre parole la classe possiede tante responsabilità/attributi non molto attinenti tra loro), è necessario eseguire una corretta suddivisione delle stesse. Eventualmente, è possibile riapplicare il processo di verifica per le nuove classi come ulteriore controllo. Sebbene l’iter mostrato in precedenza possa sembrare piuttosto elaborato e non sempre agevole, va considerato che l’esperienza permette di valutare intuitivamente le classi candidate nel 90% dei casi, senza dover passare attraverso un’analisi di questo tipo. Questo processo fornisce comunque validi criteri per tutti i casi di indecisione. Terminata anche questa fase è possibile inserire altre informazioni nella tabella che quindi assume una forma come quella riportata di seguito. La compilazione della tabella è sempre consigliabile anche perché fornisce una valida documentazione sia per comprendere il modello sia per future reingegnerizzazioni dello stesso. La fase successiva consiste nel cercare di collegare tra loro le classi sopravvissute, iniziando a dar vita ad una rete razionale di collegamenti: la prima versione del modello a oggetti del dominio.

Strutturazione delle classi Introduzione Le classi sopravvissute al processo di “selezione della entità” eseguito nel punto precedente avrebbero ben poco senso se destinate ad un ciclo di vita isolato. La situazione normale, al contrario, prevede che ciascuna di esse sia relazionata ad altre e vi scambi messaggi al fine di erogare i servizi tanto cari agli utenti del sistema. La costruzione del modello, ed in particolare l’incastonamento delle varie classi nella rete di relazioni, tipicamente permette di individuarne ulteriori sfuggite all’analisi precedente o semplicemente non menzionate dagli stessi utenti. Prima però di procedere nell’illustrazione dei criteri per la costruzione del modello a oggetti del dominio, vengono illustrati alcuni consigli e trabocchetti in cui, molto frequentemente, si rischia di cadere.

Il primissimo consiglio è stamparsi in mente una frase che usava ripetere Donald E. Knuth, un maestro dell’autore: “l’ottimizzazione prematura è la radice di tutti mali” tratta dal libro The Art of Computer Programming. L’obiettivo delle primissime fasi dei processi di sviluppo dovrebbe essere quello di aumentare la comprensione del dominio oggetto di studio e dei requisiti utente. Dunque, è necessario modellare, nel modo più semplice possibile, lo spazio del problema e non quello delle soluzioni. La progettazione delle soluzioni va affrontata nelle fasi successive, quando si è acquisita una buona conoscenza del dominio del pro-

30

Capitolo 8. Le classi nei processi blema e non si deve più fare i conti con le tipicamente ridotte conoscenze informatiche degli utenti. Questo vincolo è invece importantissimo nelle prime fasi del processo di sviluppo del sistema in cui è necessario coinvolgere il più possibile gli utenti ed esperti del dominio, instaurando una piattaforma costruttiva di dialogo tra le parti. In questo ambito, lo scopo fondamentale dei modelli (e quindi dello stesso UML) è comunicare (e poi documentare). Pertanto è necessario tenere a mente che molti utenti ed esperti del business dispongono di una limitata cultura informatica, ed è quindi consigliabile rimandare le ottimizzazioni alle fasi di analisi e disegno.

Il modello a oggetti del dominio non è il prodotto da implementare, si tratta di un artifact preziosissimo, ma destinato ad essere rifinito, attraverso altri modelli, nelle fasi successive.

Una delle anomalie che più frequentemente si rischia di generare, specie quando il modello a oggetti del dominio è realizzato da personale a forte connotazione tecnica (come architetti per esempio), è di introdurre ottimizzazioni, utilizzare design pattern e raffinate astrazioni che non sempre hanno molto a che fare con il dominio del problema. Ciò, se da un lato può concorrere ad aumentare l’eleganza e la leggibilità per una platea di fruitori esperti, dall’altro può risultare meno comprensibile invece ad un pubblico tecnicamente meno preparato come quello degli esperti del dominio e degli utenti/clienti. Ciò costituisce un grosso limite considerando che proprio questi soggetti vanno annoverati tra i fruitori principali del modello a oggetti del dominio: sono i migliori conoscitori dell’area business oggetto di analisi e quindi, alla fine, dovrebbero essere loro a validare il modello. Disporre di un modello a oggetti del dominio non perfettamente OO è un “male” lieve in quanto curabile nelle fasi successive dal personale tecnicamente più preparato. Disporre di un modello che descrive in maniera errata la realtà è invece un problema decisamente più grave in grado di procurare grossi danni a tutto il processo. Evitare prematuri artifici e raffinatezze è anche consigliato considerando che, generalmente, i modelli del dominio si prestano ad essere modificati diverse volte prima di approdare ad una versione (pseudo) stabile. Spesso gli stessi clienti/utenti non hanno ben chiare le entità che prendono parte nel sistema business e le relative astrazioni. Pertanto una precoce ricercatezza potrebbe presto rivelarsi una perdita di tempo.

31

UML e ingegneria del software: dalla teoria alla pratica

Figura 8.7 — Rappresentazione della struttura gerarchica dell’organizzazione. A sinistra vi è una rappresentazione più fedele ai requisiti, mentre a destra si è ricorsi all’utilizzo del pattern Composite. Organizzazione

Organizzazione

*

* Gruppo

Sezione

Dipartimento

* Filiali

* Dipartimenti

Chiaramente per ogni regola sono presenti le inevitabili eccezioni… In effetti, in alcuni casi proprio non si può fare a meno di usare qualche pattern anche per il modello a oggetti del dominio: parola di architetto! Si consideri l’esempio della strutturazione dell’organizzazione in filiali e dipartimenti. Si esaminino i due diagrammi riportati nella fig. 8.7. Come si può notare quello di sinistra rappresenta lo spazio del dominio in modo più vicino a quanto riportato nei requisiti senza sofisticazioni, mentre quello di destra utilizza una rappresentazione decisamente più concisa ed elegante basata sul pattern Composite (come si vedrà tra breve, la forte connotazione gerarchica delle entità coinvolte nel dominio oggetto di studio porta alla celebrazione del pattern Composite che verrà utilizzato moltissimo nell’arco dell’intero capitolo). Ora, a meno di altri fattori, probabilmente nel modello del dominio è preferibile ricorrere alla versione base, mentre per modelli appartenenti a fasi più tecniche non si discute, è obbligatorio ricorrere a soluzioni più generali e versatili come quella fornita dall’utilizzo del pattern Composite. Chiaramente non sempre è possibile o conveniente evitare l’utilizzo di pattern o ricercatezze tecniche. In ogni modo è necessario ricorrere a queste soluzioni con molta parsimonia. L’esempio mostrato probabilmente appartiene al caso in cui l’utilizzo del pattern è quasi indispensabile, sia perché la strutturazione definita nei requisiti utente non è del tutto chiara, sia perché la struttura delle varie filiali varia molto in funzione anche delle dimensioni. In alcuni casi, per esempio, può rendersi necessario ricorrere a una gerarchia molto dettagliata richiedente molti livelli, mentre in altri potrebbe essere sufficiente un numero esiguo di livelli. Il modello basato sul composite fornisce la flessibilità di poter generare, in maniera molto semplice, una struttura il cui livello di dettaglio è dipendente dalle ne-

32

Capitolo 8. Le classi nei processi

cessità delle singole filiali e non da vincoli dettati da una struttura rigida preordinata. I fattori negativi sono la grande difficoltà nel far digerire tale modello a personale con limitate conoscenze tecniche e il dover introdurre nomi di classi che non necessariamente risultano sempre rispondenti. Per esempio (consultare fig. 8.8) la classe atta a rappresentare intere filiali prende il nome di dipartimento, così come l’intera organizzazione.

Qualora si decida di ricorrere all’utilizzo di soluzioni particolarmente sofisticate o pattern di non immediata comprensione, è consigliabile dar luogo a degli esempi atti ad illustrarne l’utilizzo pratico, magari per mezzo di opportuni diagrammi degli oggetti.

Sempre il personale tecnico tende ad inserire nel modello entità che rappresentano processi da eseguire (tipicamente queste classi hanno la caratteristica di possedere un nome che ha tutta l’aria di essere un’azione). Chiaramente non si tratta di un peccato mortale, però probabilmente è più opportuno non complicare inutilmente il modello, anche perché i processi di sviluppo, tipicamente, prevedono tutta una serie di manufatti in cui è possibile specificare sia azioni da svolgere, sia dettagli a carattere più squisitamente implementativo.

Figura 8.8 — Esempio di applicazione del pattern Composite con particolare riferimento ad una ipotetica filiale dell’organizzazione (SedeDiRoma). OrganizzazioneX : Dipartimento

America : Dipartimento

RegnoUnito : Dipartimento

Nord : Dipartimento

... ...

Amministrazione : Sezione

Europa : Dipartimento

Italia : Dipartimento

Centro : Dipartimento

SedeDiRoma : Dipartimento

Contabilita : Sezione

...

Asia : Dipartimento

Francia : Dipartimento

Sud : Dipartimento

... Sicurezza : Sezione

...

...

UML e ingegneria del software: dalla teoria alla pratica

33

Probabilmente non è neppure il caso di spendere un tempo eccessivo nell’investigare la particolare natura ed i dettagli delle associazioni. In questo stadio affermare che una relazione sia un’aggregazione piuttosto che di una composizione non cambia di molto la sostanza, mentre potrebbe creare problemi al solito cliente non tecnico frastornato dalla nuova simbologia. La differenza tra le due tipologie di associazioni ha molto a che vedere con il ciclo di vita degli oggetti partecipanti e quindi su scelte di disegno che, pertanto, esulano dagli obiettivi del modello a oggetti del dominio.

Alcuni autori consigliano anche di non perdere troppo tempo nello stabilire le molteplicità con cui le classi partecipano alle relazioni. L’autore non condivide questo suggerimento. Le cardinalità sono elementi molto importanti del modello a oggetti del dominio, concorrono a definire le regole del business, permettono di evidenziare eventuali lacune di relazioni o, viceversa, relazioni ridondanti, e così via. Per esempio un conto è asserire che un profilo possa contenere un solo contesto di sicurezza, un altro è sostenere che ne può contenere diversi, come esiste una certa differenza nell’asserire che un profilo appartenga ad un solo utente oppure a svariati.

Ciò che probabilmente è possibile, è non attribuire eccessiva importanza alle navigabilità delle relazioni, assumendo che ogni relazione sia navigabile in entrambi i versi. A meno di casi evidentissimi, la selezione della particolare navigabilità dovrebbe essere una scelta da compiersi sempre nella fase di disegno.

Per esempio si consideri la relazione tra un utente e un modulo di accesso, come riportato in fig. 8.9. Ora, imporre una navigazione da ModuloDiAccesso verso Utente (il che equivale a porre la freccia all’estremità della classe Utente e navigabilità ModuloDiAccesso uguale a false) impone che, data un’istanza di un modulo di accesso, sia possibile ottenere l’utente a cui si riferisce (magari attraverso il metodo getUser()), mentre non è possibile il viceversa. Ciò però non equivale ad asserire che non si possa mai risalire ai moduli di accesso relativi ad uno stesso utente: è ancora possibile ma in modo decisamente più laborioso. Sarebbe necessario reperire dapprima l’istanza del cliente per poi poter individuare tutte le istanze dei relativi moduli di accesso. Ciò potrebbe avere notevoli impatti specie in un sistema component-based, qualora si progetti un componente specializzato per gestire gli utenti e uno per i moduli di accesso.

34

Capitolo 8. Le classi nei processi

Figura 8.9 — Esempio di navigabilità tra le classi Utente e ModuloDiAccesso.

ModuloDiAccesso

si riferisce 0..n

1

Utente

Prima di iniziare la costruzione del modello a oggetti del dominio, si ritiene opportuno sottolineare (ancora una volta) le parole d’ordine: approccio minimalista. Ciò è giustificato non solo al fine di utilizzare al meglio il tempo a disposizione, ma anche perché ci si trova in una delle primissime fasi del processo di sviluppo nella quale, tipicamente, non si dispone di una approfondita conoscenza del sistema. Per cui probabilmente, è meglio rimandare scelte di dettaglio a fasi successive.

Costruzione Una volta individuato l’elenco iniziale delle ipotetiche classi, una porzione importante del lavoro è compiuta. Un’altra attività abbastanza complessa consiste nell’organizzare le entità scaturite dal passo precedente in un modello organico. Il processo stesso offre un’ottima possibilità per rivedere le entità stesse alla luce delle relazioni con cui sono interconnesse. Nella pratica è abbastanza frequente che la costruzione del modello porti a scoprire classi mancanti ed a rielaborare quelle evidenziate. Un buon metodo per dar luogo ad una prima versione del modello consiste nel riconsiderare i casi d’uso (o qualunque altro documento utilizzato nel passo precedente) focalizzando però l’attenzione sulle frasi che coinvolgono le classi evidenziate precedentemente. Durante questo processo è necessario conferire particolare attenzione ai verbi, i quali, tipicamente, rappresentano associazioni tra classi o relativi metodi. Si cominci la costruzione del modello a partire dagli elementi basilari: gli utenti, i profili e le password (fig. 8.10). La descrizione della realtà oggetto di studio sancisce che un utente può disporre di diversi profili, dei quali però uno solo è nello stato OPERATIVO (e quindi utilizzabile) mentre ciascun profilo è riferito ad un solo utente. Di ciascun utente interessa conoscere, oltre i vari dati personali, anche il recapito. Gli utenti accedono al sistema attraverso la coppia (login, password). Quest’ultima ha tutta la dignità di essere considerata una classe in quanto aggrega diversi attributi ed in più una regola del business asserisce che un utente non può riutilizzare una delle sue ultime sei parole chiavi e quindi è necessario memorizzare le password utilizzate in precedenza da uno stesso utente. Questa prima porzione di modello a oggetti fornisce lo spunto per diverse considerazioni. In primo luogo si considerino le relazioni che associano la classe Utente a quelle di Password e Profilo. In entrambi i casi si è deciso di decomporre la relazione in altre due: una che mostri l’associazione con l’istanza “operativa” dell’altra classe e l’altra che

35

UML e ingegneria del software: dalla teoria alla pratica

Figura 8.10 — Primo embrione del modello a oggetti del dominio, focalizzato sulle entità Utente, Password e (parzialmente) Profilo. Password

Utente passwordCorrente

password : String validoDa : Time validoA : Time stato : PasswordStatus

0..1

1

passwordPrecedenti 0..6

1

Localita domicilia

nome : String provincia : String sigla : String prefTel : String capBase : String

1

*

login : String codiceFiscale : String nome : String cognome : String dataDiNascita : Date telefono : String eMail : String interno : String indirizzo : String stato : UtenteStatus

Profilo profiloCorrente 1

1

codice : String 0..1 descrizione : String profili inizioVal : Time fineVal : Time * stato : ProfiloStatus

* esercita *

RuoloOrganizzazione codice : String descrizione : String

«enumeration» PasswordStatus ATTESA SCADUTA STORICA TERMINATA

«enumeration» UtenteStatus INSERITO ATTESA OPERATIVO SOSPESO BLOCCATO DISATTIVATO

«enumeration» ProfiloStatus PREPARAZIONE ATTESA OPERATIVO DORMIENTE STORICO

mostri le rimanenti istanze. In un modello di disegno probabilmente non si tratterebbe della migliore scelta, tra l’altro potrebbe rendere il codice meno semplice (si correrebbe il rischio di complicare le iterazioni relative a tali associazioni), mentre nel modello a oggetti del dominio si tratta di una scelta felice in quanto concorre ad evidenziare delle regole di business. La classe RuoloOrganizzazione si presterebbe ad essere sostituita anch’essa dal pattern Composite. In effetti è lecito attendersi una struttura gerarchica di questi ruoli. In questo contesto si è però optato per la soluzione più semplice (lista di ruoli a pari livello). A questo punto è il momento di considerare il modulo di accesso. In particolare un modulo di accesso si riferisce sempre a un solo utente (richiesta di blocco, sblocco, sospensione, ecc.) ed eventualmente anche a un profilo a questi riferito (richiesta nuovo profilo, modifica di uno esistente, disattivazione, …). Come visto nel relativo diagramma degli stadi, un modulo di accesso, prima di dar luogo ad eventuali effetti, deve transire attraverso un insieme ben definito di stadi. La

36

Capitolo 8. Le classi nei processi

transizione è fornita da una specifica operazione effettuata da utenti appartenenti ad opportuni ruoli di sicurezza. Per esempio un Line Manager sottopone la richiesta, un addetto della sicurezza la valida, il supervisore l’approva. Queste informazioni si prestano ad essere memorizzate nella classe ModelloAccessoStadio, il cui compito è di memorizzare l’utente che ha generato una transizione (compilazione, validazione, ecc.), la data e l’orario, le eventuali note aggiuntive e lo stadio in cui il modulo di accesso è transìto. Questa classe tra l’altro è molto importante per effettuare controlli relativi alla segregazione dei ruoli. Il modello riportato in fig. 8.11 si presta ad essere reso più formale attraverso l’introduzione di un insieme di vincoli che, tra l’altro, permettono di evidenziare business rule molto importanti. Ogni qualvolta si ha a che fare con dei vincoli, la tecnica di documentazione considerata più opportuna consiste nell’inserire i diagrammi (suddivisi per package) in una o più pagine di un apposito word processor e quindi redigere un’apposita sezione con i vari vincoli. Questi dovrebbero essere specificati dapprima per mezzo del linguaggio naturale e poi attraverso un metodo a maggiore grado di formalità, come per esempio l’OCL. Esempi di vincoli: 1. il profilo a cui si riferisce un modulo di accesso deve appartenere all’utente a cui fa riferimento lo stesso modulo di accesso; context ModuloAccesso inv: self.utente.profiloCorrente self.profilo implies self.utente.profili->select(Profilo = self.profilo)->isNotEmpty()

2. un utente non può eseguire alcuna operazione (compilazione, validazione, approvazione, ecc.) relativa ad un modulo di accesso riferito a sé stesso. context ModuloAccesso inv: self.stadi->select(ModuloAccessoStadio.utente = self.utente)->isEmpty() Il termine context definisce il contesto a cui fa riferimento il vincolo, mentre inv rivela un tipo particolare di vincolo denominato invariant (invariante). Brevemente un invariante è un vincolo associato ad un insieme di classificatori e/o di relazioni ed indica che la condizione espressa dal vincolo deve essere soddisfatta da tutte le istanze del classificatore e/o relazioni durante tutto il relativo ciclo di vita. Queste invarianti, chiaramente, devono essere aggiornate man mano che si procede con il raffinamento del modello. Dall’analisi del modello è possibile notare che l’attributo ModuloAccesso.Ti-pologiaModAcc è un’ottimizzazione di una relazione di generalizzazione, in effetti si dovrebbero visualizzare una

37

UML e ingegneria del software: dalla teoria alla pratica

Figura 8.11 — Introduzione dell’entità ModuloAccesso nel modello. ModuloAccessoStadio

ModuloAccesso e' transitato per

transizione : Time note : String stadio : StadioModAcc

* stadi

*

* utente

1 1

Password passwordCorrente

password : String validoDa : Time validoA : Time stato : PasswordStatus

0..1

1

passwordPrecedenti 0..6

1

Localita domicilia

nome : String provincia : String sigla : String prefTel : String capBase : String

1

e' relativo

codice : String 1 descrizione : String tipo : TipologiaModAcc priorita : integer inserimento : Time inizioVal : Time fineVal : Time

*

1

si riferisce 0..1

utente

Utente login : String codiceFiscale : String nome : String cognome : String dataDiNascita : Date telefono : String eMail : String interno : String indirizzo : String stato : UtenteStatus

profilo

Profilo profiloCorrente 1

1

codice : String descrizione : String profili inizioVal : Time fineVal : Time * stato : ProfiloStatus 0..1

* esercita *

RuoloOrganizzazione codice : String descrizione : String

«enumeration» PasswordStatus ATTESA SCADUTA STORICA TERMINATA

«enumeration» StadioModAcc SOTTOPOSTO VALIDATO APPROVATO RESPINTO CANCELLATO

«enumeration» UtenteStatus INSERITO ATTESA OPERATIVO SOSPESO BLOCCATO DISATTIVATO

«enumeration» TipologiaModAcc NUOVO_PROFILO MODIFICA_PROFILO ELIMINA_PROFILO BLOCCO_UTENTE SBLOCCO_UTENTE SOSPENSIONE_UTENTE REVOCA_SOSP_UTENTE

«enumeration» ProfiloStatus PREPARAZIONE ATTEVA OPERATIVO DORMIENTE STORICO

serie di specializzazioni della classe ModuloAccesso: nuovo profilo, aggiornamento profilo, disattivazione profilo, sospensione utente, fine sospensione, blocco utente, fine blocco utente, ecc. Si è deciso di non visualizzare esplicitamente le varie specializzazioni al fine di non complicare ulteriormente (e forse inutilmente) il diagramma.

A questo punto è possibile considerare la classe le cui istanze rappresentano i lavori da eseguire prevalentemente in modalità batch: i famosi lavori pendenti (pending task). Come

38

Capitolo 8. Le classi nei processi

Figura 8.12 — Introduzione della classe PendingTask. ModuloAccessoStadio

PendingTask

ModuloAccesso ha transitato per

transizione : Time note : String stadio : StadioModAcc

* stadi *

codice : String 1 descrizione : String tipo : TipologiaModAcc priorita : integer inserimento : Time inizioVal : Time fineVal : Time

codice : String eseguito : boolean trigger : Time tipo : TipolofiaModAcc

1

1 1 1

SoggettoTask

* 1 utente

utente 1

Password passwordCorrente

password : String validoDa : Time validoA : Time stato : PasswordStatus

0..1

1

passwordPrecedenti 0..6

1

Localita domicilia

nome : String provincia : String sigla : String prefTel : String capBase : String

1

*

1 profilo

Utente login : String codiceFiscale : String nome : String cognome : String dataDiNascita : Date telefono : String eMail : String interno : String indirizzo : String stato : UtenteStatus

0..1

Profilo profiloCorrente 1

1

codice : String 0..1 descrizione : String profili inizioVal : Time fineVal : Time * stato : ProfiloStatus

* esercita *

RuoloOrganizzazione codice : String descrizione : String

«enumeration» PasswordStatus ATTESA SCADUTA STORICA TERMINATA

«enumeration» StadioModAcc SOTTOPOSTO VALIDATO APPROVATO RESPINTO CANCELLATO

«enumeration» UtenteStatus INSERITO ATTESA OPERATIVO SOSPESO BLOCCATO DISATTIVATO

«enumeration» TipologiaModAcc NUOVO_PROFILO MODIFICA_PROFILO ELIMINA_PROFILO BLOCCO_UTENTE SBLOCCO_UTENTE SOSPENSIONE_UTENTE REVOCA_SOSP_UTENTE

«enumeration» ProfiloStatus PREPARAZIONE ATTEVA OPERATIVO DORMIENTE STORICO

riportato nel paragrafo dei requisiti utente, principalmente le istanze di questi lavori sono generate a seguito dell’approvazione di un modulo di accesso. La durata degli effetti delle richieste è specificata dagli attributi di validità (ModuloAccesso.inizioVal e ModuloAccesso.fineVal) del modulo di accesso. Questi attributi, congiuntamente con ModuloAccesso.TipologiaModAcc, stabiliscono quanti oggetti PendingTask il sistema deve generare. Per esempio un modulo di accesso relativo a una sospensione genera due task: sospensione utente nella data e orario di inizioVal e rimozione so-

UML e ingegneria del software: dalla teoria alla pratica

39

spensione nella data e orario di fineVal. Così come avviene nel caso di un nuovo profilo. In alcuni casi, come per esempio una richiesta di blocco utente, o di disattivazione profilo, viene generato un unico task e l’attributo fineVal non viene preso in considerazione. Sempre dal paragrafo dei requisiti utente si evince che alcune azioni (blocco, sblocco, sospensione, …) possono essere eseguite direttamente da un addetto della sicurezza senza dover passare per l’iter del modulo di accesso. In tal caso i lavori non sono prenotati e quindi espletati in modalità batch, ma eseguiti direttamente. Dall’analisi di fig. 8.12 si può effettuare una serie di considerazioni: • l’introduzione della classe PendingTask ha generato la necessità di ristrutturare la classe ModuloAccesso. Volendo si sarebbe potuto pensare di associare direttamente la classe PendingTask alla classe ModuloAccesso. Ciò però avrebbe generato un modello (e quindi una descrizione del problema) errato giacché non tutti i task sono il risultato della sottomissione di un modulo di accesso. Eventualmente si sarebbe potuto evidenziare il legame inverso: il modulo di accesso e i lavori pendenti scaturiti dalla relativa approvazione. Anche questo è stato evitato in quanto, una volta approvato, un modulo di accesso termina il relativo ciclo di vita; • le due classi sono associate alla classe SoggettoTask per mezzo di una relazione di aggregazione. Si sarebbe potuto selezionare anche una relazione di composizione (sebbene la differenziazione appartiene di più ad un modello di disegno). Come ribadito in precedenza, in questa fase non è il caso di investire tempo in questo tipo di speculazioni. Comunque, qualora si fosse deciso di utilizzare una composizione si sarebbe implicitamente sancito l’importante vincolo che una stessa istanza della classe SoggettoTask non possa appartenere allo stesso tempo a più oggetti. A questo punto, il passo successivo consiste nel dettagliare il profilo. Qui inizia qualche problemino… In primo luogo un profilo deve dichiarare i gruppi utente a cui l’utente può appartenere e i dipartimenti (Organizzazioni) in cui i relativi servizi sono usufruibili. Entrambe le entità si prestano ad essere modellate attraverso l’ormai famosissimo pattern Composite. Un esempio di utilizzo è fornito attraverso il diagramma degli oggetti riportato in fig. 8.14. Poiché un utente, tramite un medesimo profilo, può appartenere a più gruppi è stata introdotta la classe ContestoSicurezza, la cui funzione è di accoppiare un’istanza del gruppo utenti ad una sezione in cui l’utente è abilitato ad utilizzarla. Come si può notare, ciascuna istanza della classe ContestoSicurezza accoppia esattamente un ruolo ad un dipartimento, quindi, qualora lo stesso ruolo possa essere utilizzato in diversi dipartimen-

40

Capitolo 8. Le classi nei processi

Figura 8.13 — Modellazione dettagliata del profilo utente.

ModuloAccessoStadio

PendingTask

ModuloAccesso ha transitato per

transizione : Time note : String stadio : StadioModAcc

codice : String eseguito : boolean trigger : Time tipo : TipolofiaModAcc

codice : String 1 descrizione : String

* stadi

tipo : TipologiaModAcc priorita : integer inserimento : Time inizioVal : Time fineVal : Time

*

1

1 1 1

SoggettoTask

* 1 utente

utente 1

Password passwordCorrente

password : String validoDa : Time validoA : Time stato : PasswordStatus

0..1

1

passwordPrecedenti 0..6

1

Localita domicilia

nome : String provincia : String sigla : String prefTel : String capBase : String

1

*

Utente

Profilo 1

1

codice : String 0..1 descrizione : String profili inizioVal : Time fineVal : Time * stato : ProfiloStatus 1 *

ContestoSicurezza

*

codice : String descrizione : String

e' in organico

*

*

1

1

RuoloOrganizzazione

*

*

*

«enumeration» PasswordStatus ATTESA SCADUTA STORICA TERMINATA

GruppoUtenti codice : String descrizione : String

codice : String descrizione : String ...

Dipartimento prevede

1

Organizzazione

codice : String descrizione : String

1 0..1

profiloCorrente

login : String codiceFiscale : String nome : String cognome : String dataDiNascita : Date telefono : String eMail : String interno : String indirizzo : String stato : UtenteStatus *

esercita

profilo

Sezione

GruppoComposto

*

Ruolo

1

«enumeration» StadioModAcc SOTTOPOSTO VALIDATO APPROVATO RESPINTO CANCELLATO

«enumeration» UtenteStatus INSERITO ATTESA OPERATIVO SOSPESO BLOCCATO DISATTIVATO

«enumeration» TipologiaModAcc NUOVO_PROFILO MODIFICA_PROFILO ELIMINA_PROFILO BLOCCO_UTENTE SBLOCCO_UTENTE SOSPENSIONE_UTENTE REVOCA_SOSP_UTENTE

«enumeration» ProfiloStatus PREPARAZIONE ATTEVA OPERATIVO DORMIENTE STORICO

ti, potrebbe rendersi necessario associare al profilo più contesti di sicurezza con il medesimo ruolo e diversi dipartimenti. Molto spesso ciò viene evitato associando il ruolo al

41

UML e ingegneria del software: dalla teoria alla pratica

dipartimento “padre” e quindi includendo automaticamente i vari dipartimenti dipendenti. Un discorso del tutto analogo vale qualora un utente possa utilizzare diversi ruoli nel contesto di uno stesso dipartimento. Poiché uno stesso ruolo nell’organizzazione può necessitare l’abilitazione delle funzionalità associate a diversi gruppi utente, è stata realizzata un’associazione (n,n) tra la classe RuoloOrganizzazione e GruppoUtenti. Ciò permette al sistema di suggerire i gruppi utente da impostare ogni qualvolta un Line Manager compili un ModuloAccesso per la richiesta di un nuovo profilo relativo a un suo subalterno: è sufficiente navigare dall’istanza RuoloOrganizzazione relativa all’utente a tutte le istanze ruolo utente a cui è associata per mezzo della relazione prevede. Questa associazione, tra l’altro, fornisce ulteriori criteri di valutazione agli addetti alla sicurezza. Infatti permette di evidenziare e quindi controllare i casi (eventualmente legittimi) in cui un Line Manager richieda di abilitare, per un proprio subalterno, l’esecuzione di servizi supplementari rispetto a quelli necessari per le mansioni previste dal relativo ruolo nell’organizzazione.

Figura 8.14 — Diagramma degli oggetti mostranti un primo esempio di profilo. LVT : Utente SupervisoreSicurezza : RuoloOrganizzatore

LVT001 : Profilo [Operativo]

Si tratta dell'associazione profiloCorrente. L'associazione profili e' stata ignorata in quanto non di interesse nel presente diagramma.

LVTSC001 : ContestoSicurezza TotalitaGruppi : GruppoComposito

Amministrativi : GruppoComposito

ContReportSic : GruppoSingolo

Sicurezza : GruppoComposito

ValidatoreModuloAccesso : GruppoSingolo

. . .

ApprovatoreModuloAccesso : GruppoSingolo

. . .

FilialeRoma1 : DipartimentoComposito Sicurezza : DipartimentoComposito

Contabilita : DipartimentoComposito

Amministrazione : DipartimentoComposito

. . .

42

Capitolo 8. Le classi nei processi Ciò permette di rinforzare un principio molto importante dei sistemi di sicurezza noto come il nome di “minore livello possibile di privilegi” (Least Privilege). Questo prevede di assegnare a ciascun utente esclusivamente i privilegi strettamente necessari per eseguire le proprie mansioni. Per rendere operativo questo criterio è necessario dettagliare il più possibile l’elenco dei servizi fruibili, o meglio dei ruoli utilizzabili per accedere al sistema (si parla di granularità fine). Per esempio, invece di disporre di un unico gruppo utente per gli addetti alla sicurezza, è più opportuno considerarne diversi, come visualizzatori dati utente, visualizzatori dati moduli di accesso, validatori moduli di accesso, visualizzatori dati di log, e così via. Pertanto il ruolo organizzazione resterebbe addetto alla sicurezza, mentre i gruppi utenti dichiarati nel profilo dell’utente attraverso i contesti di sicurezza sarebbero quelli testé citati. Ciò evidentemente comporta un notevole overhead nella gestione del sistema, che però è il prezzo da pagare qualora si voglia limitare il più possibile l’esposizione del sistema e i pericolosi effetti generabili da un attacco riuscito al sistema di sicurezza.

Nell’esempio di fig. 8.15 è mostrato l’utilizzo del pattern Composite, sia quello associato ai ruoli utente, sia quello relativo all’organizzazione dei dipartimenti. Per quanto concerne il primo, l’insieme di tutti i ruoli è suddiviso, in prima istanza, in quelli appartenenti al settore amministrativo, alla sicurezza e così via. Il sottoinsieme dei ruoli legati alla sicurezza prevede gli elementi di controllore report sicurezza, validatore moduli di accesso, approvatore moduli di accesso ecc. Per quanto riguarda l’applicazione del pattern ai dipartimenti, si è preso in considerazione un primo livello di un’ipotetica filiale. L’utente, all’interno dell’organizzazione, esercita il ruolo di supervisore della sicurezza e pertanto è abilitato ad eseguire tutti i ruoli previsti dal relativo dipartimento. Come si può vedere l’istanza RuoloOrganizzazione è associata all’istanza radice del gruppo di ruoli utente appartenenti all’area della sicurezza abilitando automaticamente tutti i ruoli da esso gerarchicamente dipendenti a qualsiasi livello. Il profilo operativo è identificato dal codice LVT001 che contiene una sola istanza di ContestoSicurezza: LVTSC001. Questa abilita l’utente ad usufruire del proprio profilo per qualsiasi dipartimento della filiale di Roma. L’utente poi è (come era lecito attendersi) in organico al dipartimento di sicurezza, pertanto chiunque voglia vederne le relative informazioni ed eventualmente aggiornarle, deve possedere un opportuno ruolo abilitato per il dipartimento di sicurezza o un suo “genitore” nella struttura dell’organizzazione, come per esempio accade all’utente preso in considerazione. In questo secondo esempio, l’utente esercita il ruolo di addetto alla sicurezza e quindi è abilitato ad esercitare un sottoinsieme più limitato di ruoli. Uno dei vantaggi dell’utilizzo del pattern Composite consiste nella flessibilità che fornisce sia nell’organizzare il grafo gerarchicamente, sia nell’associare le varie classi. Nell’esempio dei ruoli è possibile avere

43

UML e ingegneria del software: dalla teoria alla pratica

Figura 8.15 — Diagramma degli oggetti mostranti un secondo esempio di profilo. AR : Utente

AddettoSicurezza : RuoloOrganizzatore

AR003 : Profilo [Operativo]

ARSC010 : ContestoSicurezza

ARSC011 : ContestoSicurezza TotalitaGruppi : GruppoComposito

Amministrativi : GruppoComposito

ContReportSic : GruppoSingolo

Sicurezza : GruppoComposito

ValidatoreModuloAccesso : GruppoSingolo

. . .

ApprovatoreModuloAccesso : GruppoSingolo

. . .

Amministrazione : DipartimentoComposito

. . .

FilialeRoma1 : DipartimentoComposito Sicurezza : DipartimentoComposito

Contabilita : DipartimentoComposito

un contesto di sicurezza in grado di comprendere tutti i ruoli dell’organizzazione, tutti quelli di un dipartimento o un ruolo singolo. Rispetto alla descrizione del dominio del problema, è necessario modellare due ulteriori aree: quella relativa alle regole di incompatibilità e quella efferente dalle politiche di sicurezza. Entrambe vengono trattate nella paragrafo dedicato alla tecnica alternativa al fine di non appesantire eccessivamente la presente trattazione. A questo punto, prima di terminare il paragrafo, si vuole cogliere l’occasione per evidenziare un problema che tipicamente si manifesta nell’attività di cattura dei requisiti utente… Spesso accade, per cattiva fede o semplicemente per mancanza di conoscenza, che gli intervistati dimentichino di menzionare alcune importanti entità del modello. In questi casi l’esperienza nella costruzione di sistemi (che funzionano) recita un ruolo di fondamentale importanza. Si analizzi il modello… Forse manca qualcosa… Se è vero che i moduli di accesso debbano passare attraverso una serie di fasi prima di poter dar luogo ai relativi effetti, come fanno i relativi utenti (l’addetto della sicurezza che

44

Capitolo 8. Le classi nei processi

Figura 8.16 — Code di lavori a carico dei vari utenti.

Coda codice : String descrizione : String ... 1 *

ElementoCoda

e' relativo a *

1

ModuloAccesso

codice : String descrizione : String priorita : integer inserimento : Time xor 0..1

Utente

0..1

GruppoUtenti

ne effettua la validazione, il supervisore che lo approva, il line manager che eventualmente deve modificarlo a seguito di un rigetto, …) a essere avvisati? Come si può notare si tratta di una questione che ha a che fare con i meccanismi da prevedere per la notifica, ma anche con l’organizzazione di specifiche entità. Per quanto concerne i primi (che tra l’altro dovrebbero essere di interesse relativo per il modello a oggetti del dominio) la risposta è abbastanza semplice: in primo luogo bisognerebbe costruire un motore per la gestione dei workflow, e poi bisognerebbe prevedere un sistema di visualizzazione delle notifiche. Quest’ultimo potrebbe essere realizzato prevedendo una funzione sul lato server da eseguire ad ogni richiesta dell’utente. Questa si dovrebbe occupare di verificare se ci sono lavori pendenti per lo specifico utente connesso o per i gruppi utente presenti nel relativo profilo. In caso di esito positivo si potrebbe visualizzare un apposito messaggio o un’icona in una specifica sezione della pagina HTML generata come risposta alla richiesta dell’utente. Per quanto concerne le entità il discorso è un po’ più complesso. In primo luogo si può notare che esistono alcune attività a carico di uno specifico utente: un Line Manager sottopone un modulo di accesso; se questo viene respinto, solo il medesimo Line Mana-

UML e ingegneria del software: dalla teoria alla pratica

45

ger è abilitato a decidere il da farsi (modificarlo e quindi riprovare a sottometterlo oppure annullarlo direttamente). Altre invece sono a carico di un gruppo. Per esempio quando un line manager sottopone un modulo di accesso, questo può essere validato da un qualsiasi addetto alla sicurezza che abbia però accesso ai dati del dipartimento a cui appartiene l’utente a cui è riferito il modulo di accesso stesso. Chiaramente i dati relativi al modulo di accesso appartengono al medesimo dipartimento cui appartiene l’utente oggetto del modulo in questione. Quanto descritto si presta ad essere modellato dal diagramma riportato in fig. 8.16. Per quanto concerne l’attributo priorita della classe ElementoCoda, questo potrebbe essere impostato all’atto dell’inserimento di un nuovo oggetto nella coda secondo criteri ben definiti. Per esempio è abbastanza lecito attendersi che moduli di accesso con data inizio validità più ravvicinata abbiano priorità più elevata di altri più in là nel tempo, così come richieste per blocco utente siano prioritarie rispetto ad altre di tipo diverso. La priorità però non dovrebbe essere fissa, ma dovrebbe prevedere una soglia massima iniziale assegnabile all’atto dell’inserimento e quindi aumentare in modo proporzionale al tempo di permanenza di un elemento nella coda. A tal proposito si potrebbe prevedere un servizio (un daemon) che si attivi ad intervalli di tempo prestabiliti il cui compito sia scorrere la coda dei lavori, aumentando eventualmente la priorità degli elementi presenti. Qualora poi il valore della priorità superi dei valori di guardia, il servizio si dovrebbe occupare di emettere opportune segnalazioni di warning affinché si prendano dei provvedimenti per il lavoro rimasto in coda da troppo tempo.

Conclusioni Come accennato in precedenza, il metodo presentato è stato oggetto di critiche alquanto severe. Prima di analizzarne alcune, l’autore crede che sia necessario inquadrare, ancora una volta, il procedimento nel corretto contesto. Esso non promette di fornire una metodologia sufficiente a garantire l’identificazione di tutte e sole le classi appartenenti all’area business oggetto di studio, l’obiettivo non è la produzione della versione definitiva del modello a oggetti; bensì l’elaborazione di una primissima versione, un embrione, da utilizzare per condurre i propri studi ed instaurare una piattaforma costruttiva di dialogo con gli utenti e gli esperti del dominio. Dall’analisi delle principali critiche è possibile notare come queste siano relative più ai problemi tipici dell’analisi dei requisiti (investigate nei capitoli relativi ai casi d’uso) che non al procedimento stesso. Chiaramente un’errata analisi dei requisiti tende a generare problemi seri a qualsiasi processo. Tuttavia si è ritenuto opportuno riportare le varie critiche al fine di rendere i lettori consapevoli degli eventuali rischi intrinseci nel metodo, anche al fine di poter pianificare eventuali contromisure, come confronti con gli utenti, verifiche con software esistenti, ecc. Una delle critiche più forti è relativa al principale manufatto di input: il documento dell’analisi dei requisiti che, come esperienza insegna, non bisognerebbe mai concedere

46

Capitolo 8. Le classi nei processi

eccessiva fiducia. Probabilmente un buon modello dei casi d’uso ridurrebbe il rischio. Ovviamente negli esempi si utilizzano costruzioni grammaticali e descrizioni a partire dalle quali chiunque sarebbe capace di individuare classi, relazioni, attributi, ecc. Nella realtà però le cose non sono mai così semplici… All’autore del libro è capitato svariate volte di trovarsi di fronte voluminosi documenti di analisi dei requisiti, e dopo diverse ripetute letture ritrovarsi con uno sguardo smarrito ad interrogarsi su cosa voleva dire il redattore (“ma cosa deve fare il sistema?”). Anche quando il documento e/o il modello dei casi d’uso risultano ben realizzati, non si può nascondere che ambiguità, assunzioni implicite — chiaramente per chi ha redatto il documento — reticenze, ecc. sono peculiari del linguaggio naturale. Queste caratteristiche tendono a generare errate interpretazioni e, come logica conseguenza, possono determinare la produzione di modelli inesatti. Un’altra critica al metodo è relativa all’ambiguità e flessibilità tipica del linguaggio naturale che, chiaramente, tendono a ripercuotersi negativamente sul modello ottenuto dall’analisi delle specifiche. In questo caso però l’autore non è convinto che si tratti veramente di un problema troppo gravoso. Se si considera che, in questo contesto, l’obiettivo è produrre una versione iniziale del modello a oggetti del dominio che rappresenti una visione statica dell’area oggetto di studio, si capisce come ciò possa rappresentare una preoccupazione relativamente marginale. Un problema più importante legato sia all’ambiguità e flessibilità del linguaggio naturale sia a reticenze ed assunzioni implicite, è la non descrizione di alcune entità che invece risultano elementi importanti per il modello a oggetti del dominio (si consideri il caso delle code dell’esempio precedente). Come al solito però non si tratta di un problema del metodo bensì della cattura dei requisiti in generale, che può essere minimizzato sottoponendo le versioni iniziali del modello a oggetti del dominio ad architetti esperti nella costruzione di sistemi.

Un metodo alternativo Nella realtà accade raramente di dover realizzare un sistema inedito per il quale non esista alcun software in circolazione. Molto più frequentemente accade di dover reingegnerizzare un sistema esistente o di dover analizzare un dominio per il quale esistano in commercio software che affrontano problematiche simili. In tutti questi casi è possibile prendere delle scorciatoie traendo vantaggio da queste situazioni.

Per esempio, si può generare il modello del dominio tramite l’analisi dello schema della base di dati. Ciò dovrebbe aiutare a produrre una prima versione del modello o a verificare quella prodotta, per eliminare eventuali classi non necessarie, ridondanti, vaghe o non corrette, per introdurne altre mancanti, e così via. Se si utilizza questa strategia, il modello prodotto va considerato una versione iniziale da discutere e validare con il personale esperto del business.

47

UML e ingegneria del software: dalla teoria alla pratica Sebbene esistano diversi software in grado di eseguire automaticamente il reverse engineering degli schemi dei database, è consigliabile svolgere questo processo manualmente o quantomeno rivedere il modello generato dal tool. Ciò per diversi motivi. In primo luogo, nel modello a oggetti del dominio, verosimilmente, si è interessati ad un numero limitato di tabelle e di campi rispetto a quelli che compongono la base di dati. Tipicamente, molti di questi elementi hanno un significato strettamente legato all’implementazione o alla strutturazione dello stesso, e quindi hanno poco o nulla a che fare con lo spazio del problema. Inoltre è necessario revisionare il modello per conferire un aspetto più “object oriented” di quello che si otterrebbe attraverso una mera traduzione delle tabelle in relative classi. Infine è importante rivedere il modello al fine di adeguarlo alla visione del dominio posseduta dell’azienda committente.

Figura 8.17 — Porzione di uno schema di un database relazionale utilizzato per memorizzare le regole di incompatibilità.

Dipartimenti DIP_ID : char[10] {primaryKey} DIP_NAME : varchar2[20] DIP_DESCR : varchar2[100] {Null} DIP_GRP_FLAG : char[1] DIP_BEL_DIP_ID : char[10] {foreignKey} {Null} 0..1

0..1

*

0..1

* Incompatibilita INC_ID : char[10] {primaryKey} INC_DESCR : varchar2[100] {Null} INC_TYPE_ID : char[1] {foreignKey} INC_SRC_DIP_ID : char[10] {foreignKey} {Null} INC_DES_DIP_ID : char[10] {foreignKey} {Null} INC_SRC_GRP_ID : char[10] {foreignKey} {Null} INC_DES_GRP_ID : char[10] {foreignKey} {Null}

*

*

0..1

0..1

TipologiaIncomp *

GruppiUtente GRP_ID : char[10] {primaryKey} GRP_NAME : varchar2[20] GRP_DESCR : varchar2[100] {Null} GRP_GRP_FLAG : char[1] GRP_BEL_DIP_ID : char[10] {foreignKey} {Null}

0..1

*

1

TPI_TYPE_ID : char[1] {primaryKey} TPI_NAME : varchar2[40] TPI_DESCT : varchar2[100] {Null}

48

Capitolo 8. Le classi nei processi

Figura 8.18 — Esempio di diagramma delle classi ottenuto per mezzo di reingengerizzazione automatica di un frammento della base di dati.

Dipartimenti

belogingDip

Id : String name : String descr : String dipSorg

0..1

0..1

* dipDest

*

0..1

*

Incompatibilita Id : String descr : String type : TipologiaIncompatibilita gruppoSorg

*

gruppoDest

0..1

* 0..1

GruppiUtente id : String name : String descr : String gruppoFlag : boolean

belogingGroup 0..1

*

Durante questo esercizio, è necessario tener ben presenti le regole della particolare tipologia del database management system (relazionale, a oggetti, gerarchico, reticolare) oggetto di analisi. Se, per esempio, il paradigma è a oggetti, allora l’impegno richiesto per “estrarre” il modello del dominio è veramente minimo, mentre negli altri casi l’esercizio può risultare anche molto complesso. Nella fig. 8.17 è riportato un primo estratto semplificato della base di dati utilizzata per memorizzare le regole di incompatibilità menzionate nella descrizione del dominio. La notazione adoperata è un esempio di come utilizzare i diagrammi delle classi per rappresentare degli schemi di basi di dati relazionali. Chiaramente non si tratta dello strumento più adatto, ma offre il grande vantaggio di realizzare diagrammi facilmente inseribili nel file rappresentante il proprio progetto e di non richiedere tool aggiuntivi. Prima di iniziare l’analisi di quanto riportato, è necessario tener presente che nei database relazionali il legame tra le tabelle si realizza tramite esportazione delle chiavi primarie. Per esempio ciascun record della tabella Incompatibilita può essere relazionato a due

49

UML e ingegneria del software: dalla teoria alla pratica

record GruppiUtente, ciò si evince dalla presenza nel tracciato record della prima tabella di due ripetizioni della chiave primaria della tabella G r u p p i U t e n t e (INC_SRC_GRP_ID, INC_DES_GRP_ID). L’esempio delle regole di incompatibilità è stato selezionato per mostrare come una mera traduzione dello schema in un diagramma a oggetti tenderebbe a genere qualche problema. In questo contesto si potrebbe ottenere un diagramma come quanto evidenziato nella fig. 8.18. Sebbene non si tratti poi del diagramma peggiore al mondo, comunque presenta diverse anomalie, come nascondere alcune regole business di una certa importanza. Per esempio non è chiaro se tutti i legami della classe incompatibilità posso essere omessi (hanno cardinalità del tipo 0…1), oppure solo alcuni o coppie di essi; ancora, non è chiaro se sia possibile specificarne uno solo relativo al gruppo utenti ed uno solo al dipartimento, e così via. Nell’analisi di uno schema di un database è necessario tenere presente che molto spesso la struttura finale è risultato di un processo di denormalizzazione effettuato allo scopo di migliorare le prestazioni delle operazioni più ricorrenti (tipicamente si giunge fino alla terza forma normale — 3NF — e quindi si denormalizza). L’effetto di questo processo è che spesso si accorpano alcune tabelle, oppure vengono inserite informazioni ridondanti per evitare ricerche troppo complesse, e così via. Analizzando attentamente le tabelle relative alle regole di incompatibilità, e in particolare a Dipartimenti e GruppiUtente, è possibile notare come queste si prestano a memorizzare dati secondo un’organizzazione ad albero. Ciò nel mondo della modellazione

Figura 8.19 — Esempio di diagramma delle classi ottenuto per mezzo di reingengerizzazione manuale del medesimo frammento della base di dati. «enumeration» TipologiaIncompatibilita

RegolaIncompatibilita code : String description : String tipo : TipologiaIncompatibilita

SOFT HARD

gruppoSorg

GruppiUtenti *

...

dipSorg

1 * IncompGruppiUtenti gruppoDest 1

IncompDiparimenti

*

*

1 dipDest

*

1

1

GruppoComposto

Dipartimenti ...

*

1

GruppoSemplice

IncompComposta

DipSemplice

DipComposto

50

Capitolo 8. Le classi nei processi

OO può essere rappresentato attraverso un pattern Composite. Analizzando la tabella Incompatibilita, si può notare come la relativa classe in realtà possegga una struttura più complessa. Sebbene a prima analisi potrebbe venire in mente che il modello di fig. 8.19 complichi inutilmente una situazione semplice (anomalia che tecnicamente viene indicata con il nome di over-engineering), le cose non sono esattamente così. Il modello infatti, oltre a presentare un superiore grado di formalità, permette di evidenziare diverse regole di business. Per esempio enfatizza l’esistenza di tre tipologie di incompatibilità: quella relativa ai soli gruppi utenti, quella relativa ai soli dipartimenti e quella composta ricavata dalla combinazione delle precedenti due. Ancora, permette di evidenziare che, per quanto attiene la prima tipologia, un’istanza è valida qualora associata a due istanze della classe GruppoUtenti, che nelle incompatibilità relative ai dipartimenti è necessario impostare sia quello sorgente, sia quello destinazione, e via discorrendo. Volendo essere precisi bisognerebbe impostare alcuni vincoli come per esempio che le associazioni gruppo sorgente e destinazione di una medesima istanza della classe IncompGruppiUtenti debbano riferirsi a due diversi gruppi utente. Quanto descritto evidentemente mantiene la propria validità anche per le incompatibilità relative ai dipartimenti. Un’altra area lasciata inesplorata nella realizzazione del modello a oggetti del dominio è quella strettamente relativa all’organizzazione della sicurezza. Ora, considerando gli obiettivi della presente trattazione e quindi non dilungandosi troppo sui dettagli strettamente tecnici, le strategie di sicurezza utilizzate più frequentemente applicate sono quelle che vanno sotto il nome di ACL (Access Control List, Lista di controllo degli accessi) e le Security Policy (politiche di sicurezza). Le prime sono organizzate intorno al concetto di risorsa e quindi (molto grossolanamente parlando) definiscono le liste degli utenti abilitati ad accedervi con le relative modalità, le seconde invece pongono al centro dell’attenzione gli utenti che attraverso opportune politiche di sicurezza (come si vedrà di seguito), vengono associati alle risorse. Quest’ultima tecnica tende ad offrire un migliore controllo della sicurezza ed un maggiore grado di flessibilità e quindi è quella presa in considerazione nella presente trattazione. Si consideri il frammento della base dati riportato nella fig. 8.20. Iniziando l’analisi del diagramma da sinistra, ed in particolare dalle tabelle Dominio, Reame, Regole, si può facilmente notare che si tratta di un’ennesima rappresentazione del pattern Composite. Ora, non è che l’autore sia ossessionato da questo pattern per qualche strana ragione, e tanto meno vuole dare l’impressione che si tratti del pattern “passe partout solvitutto”… La verità è che il Composite trova la sua applicazione ottimale in tutti quei contesti in cui vi sia una forte connotazione gerarchica e ricorsiva dei concetti (in poche parole alberi). Tornando all’analisi della base di dati, le tabelle permettono di raggruppare le risorse da proteggere secondo un’organizzazione gerarchica a tre livelli: il Dominio è suddiviso i Reami i quali contengono le Regole. La presenza della tabella Dominio permette, even-

51

UML e ingegneria del software: dalla teoria alla pratica

Figura 8.20 — Frammento di database relativo alle politiche di sicurezza. Dominio

IndirizzoIP

«primaryKey»DOM_ID : char[10] DOM_NOME : varchar2[60] DOM_DESCR : varchar2[100] {null} 1

GruppiUtente

«primaryKey»IPD_IND_ID : char[10] IPD_IND : char[100] IPD_DESCR : char[100] {null}

«primaryKey»GPU_ID : char[10] GPU_DESCR : varchar2[100] {null} . . .

1

*

1 *

*

Reame

IndirizzoIPPolitiche

«primaryKey»RM_ID : char[10] RM_NOME : varchar2[60] RM_DESCR : varchar2[100] {null} RM_FILTRO : varchar2[255] «foreignKey»RM_DOM_ID : char[10]

PoliticheGruppiUtente

«primaryFKey»IPP_IPD_IND_ID: char[10] «primaryFKey»IPP_PLC_ID : char[10] *

«primaryKey»PLU_ID : char[10] PLU_NOME : varchar2[100] «foreignKey»PLU_UTN_ID : char[10] «foreignKey»PLU_PLC_ID : char[10] *

1 * 1

*

Regole

1

PoliticheRisposte

«primaryKey»RGL_ID : char[10] RGL_NOME : varchar2[60] RGL_DESCR : varchar2[100] {null} RGL_RISORSA : varchar2[255] «foreignKey»RGL_RM_ID : char[10] RGL_ABIL : char[1]

«primaryFKey»PLR_RIS_ID : char[10] «primaryFKey»PLR_PLC_ID : char[10]

1

1

Politiche «primaryKey»PLC_ID : char[10] PLC_NOME : varchar2[100]

1 1

1

1

*

*

PoliticheRegole «primaryKey»PLR_ID : char[10] PLR_DESCR : varchar2[100] «foreignKey»PLR_PLC_ID : char[10] «foreignKey»PLR_RGL_ID : char[10]

Risposte «primaryKey»RIS_ID : char[10] RIS_DESCR : varchar2[100] RIS_SUCC_FAIL : char[1] 1 *

RegoleAzioni * «primaryKey»RAZ_RGL_ID : char[10] «foreignKey»RAZ_AZN_ID : char[10] 1 *

Azioni «primaryKey»AZN_ID : char[10] AZN_NOME : varchar2[60] AZN_DESCR : varchar2[100] {null}

*

*

RisposteParametri

ValiditaTemporale «primaryKey»VLT_ID : char[10] «foreignKey»VLT_PLC_ID : char[10] VLT_GIORNO_SETT : number[1] {null} VLT_GIORNO_MES : number[2] {null} VLT_MESE : number[2] {null} VLT_ANNO : number[2] {null} VLT_ORARIO_INZ : char[4] VLT_ORARIO_FIN : char[4]

«primaryFKey»RSP_RIS_ID : char[10] «primaryFKey»RSP_PAR_ID : char[10] 1 *

Parametri «primaryKey»PAR_ID : char[10] PAR_DESCR : varchar2[100] {null} PAR_VAL_DEF : varchar2[100] {null}

tualmente, di organizzare diversi gruppi di regole (magari perché si vuole proteggere diversi sistemi: URL di un sito web, connessioni, …) o diverse organizzazioni delle stesse. Un ipotetico esempio di utilizzo di tali tabelle è fornito dalla fig. 8.21.

52

Capitolo 8. Le classi nei processi

Figura 8.21 — Frammento di una possibile organizzazione dei dati relativi al Dominio, Reame e Regole di un sistema Internet. DOMINIO DOM_ID

DOM_NOME

DOM_DESCR

SITO1

SITO ... ...

ESEMPIO DI...

REAME RM_DOM_ID

RM_ID

RM_NOME

RM_DESCR

RM_FILTRO

SITO1

RMDATISTAT

REAME DATI STATICI

INSIEME DI ...

/MainServlet/static/

SITO1

RMGESTACC

REAME SERVIZI GEST...

INSIEME DI ...

/MainServlet/gesacc/

SITO1

RMREPORT

REAME SERVIZI DI REP...

INSIEME DI ...

/MainServlet/report/

REGOLE RGL_RM_ID

RGL_ID

RGL_NOME

RGL_DESCR

RGL_RISORSA

RGL_ABIL

RMREPORT

RGRPT001 REPORT LOG FALLITI

...

\security\log\failures

Y

RMREPORT

RGRPT002 REPORT STATO UTENTI

...

\security\users\status

Y

RMREPORT

RGRPT003 REPORT STATO PROFILE

...

\security\profiles\status

Y

Le varie regole, organizzate gerarchicamente, sono collegate alle politiche di sicurezza per mezzo di una relazione di tipo “molti a molti”.

I database relazionali non sono in grado di realizzare direttamente relazioni (n, n) tra tabelle, pertanto è necessario ricorrere a ulteriori tabelle con funzioni di legame che decompongano una relazione (n, n) in due del tipo: (1, n) e (n, 1). Nell’esempio in questione, questo compito è affidato alla tabella PoliticheRegole (come anche il nome lascia presupporre). Tipicamente, tabelle appartenenti a questa tipologia non danno luogo ad altrettante classi nel modello a oggetti a meno che la relazione stessa (non le singole tabelle) non possegga propri attributi. In tal caso, in funzione delle necessità, si può optare per una classe associazione (association class) o per una classe vera e propria (come visto nel capitolo precedente).

53

UML e ingegneria del software: dalla teoria alla pratica

Per esempio, il legame tra Politiche e Regole viene modellato attraverso una classe associazione poiché possiede degli attributi, mentre quello tra Risposte e Parametri viene rappresentato per mezzo di un’associazione semplice. Prima di analizzare in dettaglio le politiche, si può notare come queste siano nuovamente associate per mezzo di una relazione (n, n) con i gruppi degli utenti. Anche in questo caso la relazione va rappresentata per mezzo di una classe associazione per via della presenza dell’attributo PLU_NOME nella tabella PoliticheGruppiUtente. Quanto descritto poc’anzi è rappresentato in fig. 8.22. Dal diagramma di fig. 8.22 è possibile notare l’introduzione di una coppia di classi non presenti nella base di dati: Risorsa e TipologiaRisorsa. Quest’ultima è necessaria per raggruppare le risorse in gruppi omogenei i quali, per esempio, prevedono un insieme ben definito di azioni. Per esempio se la risorsa presa in esame è un indirizzo (URL) del sito da proteggere, le azioni previste potrebbero essere get, post e put. Questa associazione permette di semplificare il processo di definizione delle regole: una volta selezionata la risorsa a cui si riferisce, il sistema potrebbe automaticamente proporre l’elenco delle possibili azioni selezionabili per la specifica risorsa. Per quanto concerne la classe Risorsa, in questo caso si è deciso di non complicare eccessivamente le cose, ma si provi ad immaginare un modo corretto per rappresentarla… Ebbene sì, anche le risorse si prestano ad essere rappresentate attraverso una struttura gerarchica, vedi pattern Composite,

Figura 8.22 — Diagramma delle classi relativo al Dominio, Politiche e GruppiUtente.

Dominio

GruppoUtenti

id : String nome : String descrizione : String

PoliticaGruppi id : String descrizione : String

Reame

rende disponibile

*

abilitata : boolean

* abilita

Azione

è relativa

1

*

id : String nome : String descrizione : String

*

*

è istanza

TipologiaRisorsa

*

* prevede

RegolaPolitica id : String descrizione : String

Risorsa 1

* *

al

Politica

Regola

filtro : String

codice : String descrizione : String ...

id : String * nome : String descrizione : String

54

Capitolo 8. Le classi nei processi

Figura 8.23 — Primo embrione di modello relativo alle Politiche concernente la validità temporale e gli indirizzi IP. è valevole per

IndirizziIP

0..n

*

«enumeration» GiornoSettimana

Politica 1

id : String indirizzo : String descrizione : String

possiede

* ValiditaTemporale id : String descrizione : String

ValiditaTempGiornoSett giorno : GiornoSettimana

orario 1

OrarioValidita

0..1 orarioInzio : String

orarioFine : String

ValiditaTempGiornoMese

ValiditaTempGiornoSpec

giorno : int

giorno : Date

LUNEDI MARTEDI MERCOLEDI GIOVEDI VENERDI SABATO DOMENICA

che consentirebbe alle regole, tra l’altro, di far riferimento a singole risorse o a gruppi di esse. A questo punto nulla salverà l’autore dal venir tacciato di soffrire di un’ossessione compulsiva relativa al pattern. L’utilizzo del Composite consentirebbe un maggiore grado di flessibilità al sistema, sebbene poi l’eccessiva flessibilità spesso è una forma diversa di entropia. Si affronti la sezione dedicata alle politiche, che, come è lecito attendersi, sono uno dei concetti fondamentali dell’infrastruttura di sicurezza presa in esame. In primo luogo è possibile associarvi una validità temporale, la quale stabilisce gli intervalli di tempo in cui la particolare politica è valida. Per default lo è sempre, a meno che non venga specificato diversamente, associando opportuni record della tabella ValiditaTemporale. In questi record è possibile specificare l’orario di inizio e fine validità da applicarsi tutti i giorni, o solo specifici giorni della settimana/mese/anno. Volendo è possibile restringere ulteriormente le clausole di sicurezza limitando l’insieme di indirizzi IP del computer client, per i quali la regola mantiene la propria validità. Ancora una volta si è deciso di non eccedere nella complessità. Probabilmente però si sarebbe potuto rendere il sistema più flessibile permettendo di specificare non semplicemente insiemi di indirizzi IP, bensì intervalli, oppure tutti gli indirizzi di un host, e così via. L’ultimo frammento rimasto da analizzare è relativo all’associazione delle politiche con le risposte. Quest’ultima tabella fornisce la struttura per organizzare i dati di un sistema per la gestione delle risposte da notificare in caso sia di fallimento, sia di successo. Per esempio, in un sistema di un sito web, in caso di fallimento si potrebbe reindirizzare la richiesta ad un’apposita pagina demandata alla gestione delle comunicazione di errori all’utente, associandovi un determinato codice di errore, oppure in caso di successo si potrebbero inserire ulteriori parametri di sincronizzazione, e così via (fig. 8.24). Questi

55

UML e ingegneria del software: dalla teoria alla pratica

Figura 8.24 — Modello relativo al legame tra le Politiche e le Risposte. Politica * include 0..n è costituita da

Risposta *

id : String descrizione : String

RispostaSuccesso

Parametri 1..n

id : String descr : String valDefault : String

RispostaFallimento

Figura 8.25 — Modello finale delle politiche di sicurezza. Al fine di non appesantire eccessivamente il diagramma, sono stati omessi diversi dettagli. Dominio

Reame

PoliticaGruppi rende disponibile

*

è valevole per

possiede

include

*

TipologiaRisorsa

ValiditaTemporale

*

id : String descrizione : String

prevede

* Azione

* id : String

*

1

1

abilita

*

Politica

*

è istanza

id : String descrizione : String

al

è relativa

1

Risorsa

*

*

descrizione : String ...

*

abilitata : boolean

*

* codice : String

id : String descrizione : String

Regola

filtro : String

GruppoUtenti

RegolaPolitica

id : String nome : String descrizione : String

nome : String descrizione : String

1

* IndirizziIP id : String indirizzo : String descrizione : String

Risposta 0..n

id : String descrizione : String * è costituita da

orario

0..1

OrarioValidita orarioInizio : String orarioFine : String

1..n

Parametri id : String descr : String valDefault : String

56

Capitolo 8. Le classi nei processi

parametri potrebbero essere sia prefissati, sia ottenibili per mezzo dell’esecuzione di specifiche funzioni predisposte. In ogni caso, la relazione tra le risposte ed i parametri è nuovamente del tipo (n, n) così come pure quella tra le politiche e le risposte. Ciò permette di definire un insieme di dati basilari utilizzabili ogni volta di cui se ne abbia bisogno. Dall’analisi del diagramma di fig. 8.25, dovrebbe essere chiaro il perché del nome della strategia modellata.

Pro e contro del metodo Di fronte ai vantaggi ben evidenti nel ricorrere all’utilizzo dei tracciati del database per produrre modelli a oggetti del dominio — riuscire a produrre velocemente una versione iniziale del modello evitando molti lunghi meeting (non sempre proficui) con gli utenti (sebbene poi questi ne dovranno convalidare i contenuti), risolvere a priori eventuali contenziosi sulle diverse percezioni dell’area business da parte degli utenti, disporre di una versione dell’organizzazione dei dati dello spazio del problema ben investigata e collaudata — esiste una serie di insidie cui porre molta attenzione. Per esempio si può correre il rischio di spendere troppo tempo nel tentare di comprendere la struttura del database senza ottenere grossi risultati, oppure produrre una versione del modello ad oggetto a carattere eccessivamente tecnico, oppure utilizzare una struttura eccessivamente “denormalizzata” per questioni di performance (mai capitato di dover analizzare un database di un legacy system datato?) e così via. Molti dei rischi citati possono essere attenuati considerando il modello prodotto, ancora una volta, come una versione iniziale da utilizzarsi come piattaforma di colloquio e non come versione finale del modello. Molto importante risulta anche usufruire della continua consulenza di un esperto dell’area business oggetto di studio. Particolarmente importante è ricorrere a risorse interne dell’organizzazione committente sia per sposarne (o per darne sensazione) la relativa visione del business, sia per coinvolgerli durante il processo e quindi ottenere il relativo sostegno. In ultima analisi si tratta di una scorciatoia che, se utilizzata correttamente, offre molti vantaggi; come ormai da prassi è necessario applicarla mantenendo il cervello costantemente sulla posizione on.

Il modello del dominio e i messaggi Al momento in cui viene redatto il presente testo i sistemi di messaggistica sembrano assumere sempre di più un ruolo di primo piano nella produzione di sistemi complessi. Si tratta di un’infrastruttura che permette di disaccoppiare le componenti in cui è strutturato un sistema complesso, mantenendole sincronizzate attraverso lo scambio asincrono di messaggi. Questi messaggi rappresentano un po’ l’evoluzione del concetto di interfaccia: un componente per interagire con un altro deve produrre opportuni messaggi in accordo

57

UML e ingegneria del software: dalla teoria alla pratica

Figura 8.26 — Esempio di messaggio estratto dal modello a oggetti del dominio. MsgAnagraficaUtenti timestamp : Date publisher : String action : MessageAction ...

«enumeration» MessageAction INSERT UPDATE DELETE

1

Utente login : String codiceFiscale : String nome : String cognome : String dataDiNascita : Date telefono : String eMail : String interno : String indirizzo : String stato : UtenteStatus 1

Localita nome : String provincia : String sigla : String prefTel : String capBase : String

1

RuoloOrganizzazione codice : String descrizione : String

1

Organizzazione codice : String descrizione : String ...

allo schema previsto (ancora una versione del concetto di contratto tra cliente e fornitore di servizio). Prima di dar luogo a meccanismi di sincronizzazione delle entità presenti in diversi sistemi è sempre opportuno verificare se ciò sia veramente necessario o se esitano metodi che ne eliminino la necessità, non ultimo il ripensamento della stessa architettura del sistema. In ogni modo, data l’importanza della definizione dei messaggi è opportuno disegnarne versioni iniziali prima possibile. La produzione del modello a oggetti semplifica anche la produzione delle versioni concettuali di tali messaggi: dovrebbe trattarsi dell’estrazione di opportuni gruppi di entità del modello. Se così non fosse, probabilmente il modello del dominio potrebbe non essere strato progettato adeguatamente. Si consideri per esempio il caso in cui nel sistema preso in esame la gestione dei dati relativa ai dipendenti sia responsabilità di uno specifico sistema, e che all’infrastruttura di

58

Capitolo 8. Le classi nei processi

Figura 8.27 — Esempio di messaggio utilizzato per innescare il workflow relativo a un nuovo modulo di accesso. MsgModuloAccesso timestamp : Date publisher : String action : MessageAction ... 1

ModuloAccesso codice : String descrizione : String tipo : TipologiaModAcc priorita : integer inserimento : Time inizioVal : Time fineVal : Time 1

SoggettoTask {xor} 0..1

0..1

Utente

Profilo codice : String descrizione : String inizioVal : Time fineVal : Time stato : ProfiloStatus

login : String codiceFiscale : String nome : String cognome : String dataDiNascita : Date telefono : String eMail : String interno : String indirizzo : String stato : UtenteStatus

0..*

ContestoSicurezza codice : String descrizione : String 1

Organizzazione codice : String descrizione : String ...

1

GruppoUtenti codice : String descrizione : String

UML e ingegneria del software: dalla teoria alla pratica

59

sicurezza sia vietato l’accesso al relativo database. In tale scenario, il sistema di sicurezza necessiterebbe di possedere un’opportuna replica, mantenuta costantemente sincronizzata, della tabella degli utenti (questi per poter svolgere le proprie mansioni devo possedere opportune credenziali per accedere al sistema). Tra le varie alternative percorribili, una consiste nella pubblicazione di opportuni messaggi destinati all’infrastruttura di sicurezza (subscriber, sottoscrittore) da parte del sistema anagrafico (publisher, pubblicatore). Obiettivo di tali messaggi è informare sulle variazioni di opportuni dati relativi ai dipendenti. Ora disponendo del modello a oggetti del dominio, una prima versione del messaggio potrebbe essere facilmente prodotto prelevando le entità di interesse. Come si può notare, data la rappresentazione UML di un messaggio, la produzione del relativo schema dovrebbe essere cosa presto fatta. Un altro esempio potrebbe aversi qualora la gestione del workflow dei moduli di accesso fosse gestito da un apposito sistema (WMS, Workflow Management System, sistema per la gestione dei flussi di lavoro). In questo scenario, l’infrastruttura di sicurezza svolgerebbe il ruolo del pubblicatore mentre il WMS reciterebbe il ruolo di sottoscrittore.

Modello a oggetti business Introduzione “Un modello del business mostra l’ambiente dell’azienda e come questa opera in relazione ad esso. Con il termine ambiente si intende ogni cosa con cui l’azienda interagisce al fine di eseguire i propri processi di business, come i clienti, i partner, le aziende appaltatrici e così via. Il modello mostra i dipendenti e, ad ogni livello, cosa deve essere fatto e quando dovrebbe essere fatto” [Ivar Jacobson].

Quando si parla del modello del business è necessario porre attenzione. Generalmente con lo stesso termine si identificano due manufatti tra loro molto diversi (sebbene concorrano a definire lo stesso contesto da diversi prospettive) ossia: business use case e business object model. In questo ambito si fa chiaramente riferimento al modello a oggetti. Per eliminare ogni possibile ambiguità alla sorgente ci si riferisce al modello con il nome modello a oggetti business.

Qualora in un processo di sviluppo del software si decida di realizzare il modello a oggetti del business, questo dovrebbe essere prodotto durante le primissime fasi del processo stesso, tipicamente prima del modello a oggetti del dominio, sebbene nessuno vieti il contrario. In questo capitolo i due manufatti sono stati presentati in ordine inverso per agevolarne l’esposizione. In effetti, come si vedrà, il modello a oggetti del business può essere visto con

60

Capitolo 8. Le classi nei processi

un modello a oggetti del dominio esteso, nel senso che tutte le classi entità del modello del dominio sono presenti nel modello del business (entity); e in più compaiono altri concetti come worker (lavoratori) e work unit (unità di lavoro). Il Business Object Model descrive come un gruppo di utilizzatori (tecnicamente denominati worker della business area, ossia gli attori dell’area) utilizzano un insieme di entità di business (le classi già evidenziate nel modello a oggetti del dominio) attraverso opportune unità di lavoro (work unit). In sostanza una work unit definisce la logica che specifica i vari servizi erogabili agli attori. In questo contesto l’attenzione viene focalizzata sull’intera area business e non esclusivamente sul sottoinsieme da implementare. Il concetto è equivalente alla differenza tra sistema informativo e quello informatico, in cui quest’ultimo è un sottosistema del primo. Il sistema informativo considera tutti i flussi, anche quelli destinati a restare manuali, e pertanto non di interesse per il sistema informatico. Nel contesto del sistema preso come riferimento, per esempio, flussi informativi tipicamente manuali potrebbero essere: relativamente alla validazione e/o approvazione del modulo di accesso relativo al profilo iniziale di un nuovo utente, la verifica da parte, rispettivamente, degli addetti alla sicurezza e del supervisore che il nuovo dipendente effettivamente esista e che non sia un tentativo di frode, che i privilegi richiesti siano effettivamente i minimi indispensabili per consentirgli di espletare le relative mansioni, e così via. Quindi le funzioni che si considerano sono i servizi che l’area business fornisce ai propri attori, e pertanto, non tutte le funzioni e gli attori evidenziati in questa fase verranno inclusi nel sistema da automatizzare. In prima analisi è possibile affermare che un business object model è un modello a oggetti del dominio arricchito attraverso l’inserimento di concetti come worker e work unit (secondo quest’ottica il modello a oggetti del business dipende da quello a oggetti). Poiché in questa versione del modello a oggetti compaiono elementi come gli attori e classi strettamente correlate con i business use case (le work unit realizzano le funzione definite nei casi d’uso di business), è evidente il legame di mutua dipendenza tra il business object model ed la relativa versione dei casi d’uso. In questi contesto probabilmente il modello Business use case è propedeutico: i casi d’uso che lo costituiscono giustificano tutti gli altri elementi, entità, attori e work unit stesse. Le nuove classi sono aggiunte essenzialmente per fornire le funzionalità descritte nei casi d’uso, pertanto in questa fase è fondamentale specificare anche le operazioni (servizi) che, ovviamente, devono essere di stampo prettamente business. In ultima analisi i servizi sono attuati attraverso specifiche collaborazioni tra opportuni oggetti, realizzate attraverso l’invocazione dei relativi metodi.

Il metodo classico utilizzato per realizzare un modello a oggetti del business prevede: 1. analisi del modello dei casi d’uso di business. Da questo è possibile prele-

UML e ingegneria del software: dalla teoria alla pratica

61

vare l’elenco degli attori (si tratta di un primo elenco; non tutti finiranno nel modello dei requisiti, giacché, tipicamente, non tutti interagiscono con il sottosistema informatico), e le work unit. Qualora i casi d’uso posseggano un livello di granularità idoneo, si genera quasi una corrispondenza uno ad uno tra singolo caso d’uso e unità di lavoro; 2. estrazione delle classi entità (per questo motivo spesso si realizza un modello a oggetti del dominio “allargato”) da incorporare come entity; 3. riorganizzazione del tutto: gli attori interagiscono con le varie unità di lavoro, le quali forniscono i propri servizi accedendo, ed eventualmente modificando, lo stato delle varie entità. Durante questo processo spesso si rende necessario aggiungere altre classi destinate a realizzare altri fattori, come per esempio particolari regole del business.

Il concetto di work unit si presta ad essere rappresentato mediante il package piuttosto che mediante singole classi. Il motivo principale è che le work unit incapsulano responsabilità e funzionalità con un livello di coesione non sempre elevatissimo (si tratta pur sempre di un modello business), che, verosimilmente, si presta ad essere raffinato suddividendo le responsabilità tra un opportuno insieme di classi correlate. Si tenga presente che questo modello va prodotto facendo larga economia del tempo, pertanto attendersi un modello propriamente object oriented è decisamente pretestuoso (anche perché la produzione, solitamente, è affidata a Business Analyst, personale non sempre espertissimo degli insegnamenti dell’OO).

Esempio del modello a oggetti del business Prima di mostrare un esempio del modello a oggetti del business, si vuole ricordare che nella versione 1.4 dello UML è stata integrata la definizione del profilo per la modellazione dell’area business (UML profile for business modeling), come anche riportato nel secondo capitolo del libro. Per quanto concerne la realizzazione del modello a oggetti del business esistono diverse correnti di pensiero anche per via della mancanza sia di un formalismo universalmente accettato sia di uno standard emergente (probabilmente ciò è un segno del limitato interesse per l’argomento). Studiando la (limitata) letteratura relativa ai modelli a oggetti del business è possibile imbattersi in diagrammi tra i più svariati: ci sono alcuni che realizzano tali modelli come varianti di quelli di analisi (presentati più avanti nel capitolo), altri che sembrerebbero una via di mezzo tra diagrammi delle classi e di collaborazione e così via. L’autore, anche per questioni di carattere storico, ha sempre adottato una versione simile a

62

Capitolo 8. Le classi nei processi quella presentata dai Tres Amigos nel libro The Unified Software Development Process (anche se poi vi è un solo esempio), e pertanto gli esempi riportati riflettono questa scelta.

Tra i vari elementi quelli di maggiore interesse sono riportati nella tab. 8.3. Poiché non tutti gli attori e le entità dell’area business confluiranno poi nel sistema, e quindi nei restanti modelli, spesso si preferisce ricorrere ad una notazione grafica leggermente diversa da quella tipica, al fine di evidenziare la connotazione “business” delle succitate entità. Alcuni tool, per esempio evidenziano questa versione dei vari concetti attraverso una linea obliqua, come mostrato nella fig. 8.28.

Tabella 8.3 — Elementi fondamentali del profilo UML per la realizzazione di modelli a oggetti dell’area business. Elemento

WorkUnit

Metaclasse del metamodello System

Worker

Class

CaseWorker

Class

Un CaseWorker è un caso speciale di Worker, caratterizzato dall’interagire direttamente con gli attori esterni al sistema.

InternalWorker

Class

Si tratta di un altro caso speciale di attore, questa volta caratterizzato dall’interagire con altri Worker e Entità interne al sistema.

Entity

Class

Un Entity (Entità) è una classe passiva che può partecipare a diverse realizzazioni di casi d’uso e, tipicamente, sopravvive alle singole interazioni.

Communicate

Association

Si tratta di un’associazione utilizzata per sancire l’interazione delle istanze dei Classificatori associati.

Subscribe

Association

Un subscribe è un’associazione tra due classi che stabilisce che oggetti della classe sorgente (subscriber, sottoscrittore) riceveranno un’apposita notifica al verificarsi di particolari eventi negli oggetti della classi destinazione (publisher, pubblicatore). In questa associazione viene specificato l’elenco degli eventi che determinano l’invio della notifica ai sottoscrittori.

«subscribe»

Breve descrizione

Si tratta di un sottosistema che può contenere una o più entità. È un insieme di oggetti raggruppati in funzione della particolare attività da svolgere che rappresenta un tutt’uno per l’utente finale. Eventualmente può disporre di un façade in grado di definire le entità della WorkUnit rilevanti per l’attore. È una classe che rappresenta un’astrazione di un attore umano che interagisce con il sistema. Un Worker interagisce con altri attori e manipola Entità mentre partecipa alla realizzazione di un particolare caso d’uso.

63

UML e ingegneria del software: dalla teoria alla pratica

Figura 8.28 — Alcuni esempi degli stereotipi utilizzati nell’area business.

Attore business

entity business

use case business

Sebbene non siano standard UML, gli stereotipi mostrati in fig. 8.28 nella pratica sono abbastanza utilizzati. In particolare si fanno apprezzare in quanto permettono di intuire immediatamente il livello di astrazione business dei vari elementi.

Come esempio di modello business, si consideri la sottomissione di una modulo di accesso. Il diagramma di fig. 8.29 vuole modellare il ciclo di un modulo di accesso, dal punto di vista business: 1. il Line Manager inserisce una richiesta, compilando uno specifico modulo di accesso, da sottoporre ad un apposito addetto alla sicurezza. Tale richiesta può essere relativa ad un nuovo profilo, alla modifica di uno esistente, al blocco e sblocco di un utente, ecc.; 2. il (case worker) Gestore Moduli di Accesso si occupa di registrare le relative informazioni, eventualmente verificando il rispetto delle regole di incompatibilità; 3. il Gestore Moduli di Accesso registra il nuovo Modulo di Accesso e contestualmente genera un apposito Job; 3. il Gestore workflow prende in consegna il nuovo Job. 3.1. Se si tratta di un Modulo di Accesso sottoposto da un Line Manager lo sottopone all’attenzione di un apposito Addetto alla Sicurezza; 3.2. Se si tratta di un Modulo di Accesso validato da un Addetto alla Sicurezza lo sottopone all’attenzione di un apposito Supervisore alla Sicurezza; 3.3. Se si tratta di un Modulo di Accesso rigettato lo sottopone all’attenzione del Line Manager che lo aveva sottoposto;

64

Capitolo 8. Le classi nei processi

Figura 8.29 — Esempio di modello di business relativo alla gestione del ciclo di vita di un modulo di accesso.

Regole Incompatibilità

Line Manager

Modulo di Accesso

Addetto Sicurezza Utente

Gestore Moduli di Accesso

Job

Supervisore Sicurezza

Profilo Utente

Gestore Workflow

4. l’Addetto alla Sicurezza può validare o rigettare un Modulo di Accesso posto alla sua attenzione; 5. in entrambi i casi il Gestore Moduli di Accesso genera un nuovo Job (nel primo caso relativo ad un’attività di responsabilità del Supervisore della Sicurezza, mentre nel secondo per avvertire il Line Manager);

UML e ingegneria del software: dalla teoria alla pratica

65

6. il Supervisore alla Sicurezza può approvare o rigettare un Modulo di Accesso posto alla sua attenzione; 7. in entrambi i casi, rifiuto o approvazione, il Gestore Moduli di Accesso genera un apposito Job; 8. il Line Manager può cancellare o modificare un Modulo di Accesso sottomesso e rigettato; 9. nel caso di modifica il Gestore Moduli di Accesso genera un apposito Job, altrimenti il viene aggiornato esclusivamente il modulo di accesso per rifletterne il nuovo stato (cancellato).

Modello a oggetti del dominio “contro” quello di business La stretta somiglianza esistente tra il modello a oggetti business e del dominio finisce per porli su un piano di alternativa. Il problema è che, se si tentasse di produrre tutti i manufatti previsti dai processi più formali per lo sviluppo del software, verosimilmente sarebbe necessario disporre di un lasso di tempo di qualche ordine di grandezza superiore rispetto a quello effettivamente necessario/disponibile. Considerata quindi la canonica carenza di tempo a disposizione, si rende necessario sorvolare su qualche manufatto, pur importante, ma non di importanza vitale per il buon proseguimento del progetto. Da notare che ogni qualvolta si rinuncia alla produzione di un manufatto, implicitamente, si amplifica il fattore di rischio del progetto. Pertanto, per ogni taglio operato, andrebbe considerata un’opportuna contromisura atta a mitigare l’aumento del fattore rischio. Chiaramente la rinuncia dei manufatti principali (per esempio il modello di disegno) è difficilmente compensabile ed il rischio che si corre è di dar luogo ad un magnifico esempio di progetto-kamikaze. Nella lotta alla sopravvivenza, tipica dei progetti reali, il modello a oggetti del dominio tende ad avere la meglio sul relativo al livello di business… In effetti, lavorando in svariate aziende/progetti, molto raramente accade di realizzare/visionare modelli a oggetti dell’area business, mentre quelli del dominio sono meno infrequenti. La preferenza del modello del dominio è legittimata da alcune interessanti considerazioni: • è molto utile disporre di una versione dei dati del dominio del problema perché agevola la progettazione della base di dati, a oggetti o di diverso tipo; • semplifica la scomposizione del sistema in componenti (si individuano gruppi di dati fortemente interconessi e quindi, verosimilmente, destinati ad essere raggruppati in uno stesso componente);

66

Capitolo 8. Le classi nei processi

• semplifica la definizione delle interfacce sia utente, sia degli eventuali messaggi utilizzati per far comunicare i vari sottosistemi, ecc.; • il modello del business può essere (in gran parte) compensato dal modello a oggetti del dominio più la relativa vista dei casi d’uso; • la fase successiva prevede il modello di analisi molto simile a quello business (con la grande differenza che il primo è focalizzato sul sistema da sviluppare e non esclusivamente sull’intera area business), ecc. Molti autori preferiscono invece il modello a oggetti business poiché focalizzando l’attenzione sull’intero business si ha un manufatto di valore molto elevato, anche per finalità non strettamente connesse con la costruzione del sistema software, come la materializzazione del quadro generale da utilizzarsi come punto di partenza per generare diversi progetti, addestramento del personale, ottimizzazioni dei flussi lavorativi, ecc. L’autore, utilizzando un approccio assolutamente pragmatico e forte anche della propria esperienza, crede che il fattore tempo giochi un ruolo fondamentale in pressoché tutti i progetti. Quindi, poiché il modello business, tipicamente, richiede molto più tempo e non è strettamente funzionale alla produzione del sistema, probabilmente può essere trascurato a vantaggio di quello del dominio. Forse si tratta di un bene di lusso che solo poche organizzazioni possono permettersi. Qualora si disponesse delle risorse necessarie (soprattutto tempo e quindi budget), sarebbe molto interessante produrre entrambi i modelli.

Quale viene prima? Nel paragrafo precedente si è visto come il modello a oggetti del business e quello del dominio, nella lotta alla sopravvivenza imposta dalla risorsa tempo, tendano a diventare antagonisti, in una confronto che vede il modello a oggetti del dominio favorito. Qualora però sia abbia la fortuna di poter realizzarli entrambi potrebbe nascere il dilemma su quale modello realizzare per primo. Non c’è una risposta univoca ed entrambe le alternative presentano dei vantaggi. Probabilmente il metodo migliore consiste nel procedere in parallelo, facendo attenzione però, che limitatamente a questo contesto, allocare la realizzazione dei due modelli a team diversi potrebbe non essere un’idea sempre vincente. Si correrebbe il rischio di dover reiterare le interviste con gli utenti/clienti, di produrre diverse versioni dell’area oggetto di studio, di spendere molto tempo per i riscontri, e così via. In ogni modo, l’approccio più tipico prevede di iniziare con la realizzazione del modello a oggetti di business. Ciò per una serie di motivi plausibili. In primo luogo permette di acquisire una visione completa dell’intero sistema in cui l’azienda l’opera e quindi di individuare la giusta collocazione del nuovo progetto. Dato il modello a oggetti del business,

UML e ingegneria del software: dalla teoria alla pratica

67

è abbastanza agevole ricavare il sottoinsieme di interesse per quello del dominio, e così via. A fronte di questi vantaggi, esistono una serie di controindicazioni. Per esempio è più complicato affrontare da subito lo studio dell’intero sistema; molte volte è più agevole iniziare a concentrarsi su un aspetto più limitato (il modello a oggetti del dominio) e quindi procedere estendendo lo stesso. Durante la produzione del modello di business, difficilmente il processo di sviluppo del software può procedere: non si tratta di un manufatto in grado di innescare direttamente altre fasi, mentre la realizzazione del modello del dominio permette di iniziare lo studio della base di dati, il disegno dei messaggi, le interfacce utenti e così via. Oltre a questi criteri oggettivi, la scelta su quale manufatto produrre dipende ovviamente anche da altri fattori, per così dire logistici, come per esempio le risorse umane disponibili, i relativi ruoli, ecc. In ogni modo, l’importante è che alla fine i modelli posseggano la necessaria qualità e che riproducano, con prospettive diverse, lo stesso sistema in modo coerente.

Modello a oggetti di analisi Introduzione L’Analysis Object Model (modello di analisi a oggetti o più semplicemente modello di analisi), come suggerisce il nome, è prodotto durante la relativa fase del processo di sviluppo del software (fase spesso incorporata in quella di disegno). Si tratta di uno stadio per così dire di “raccordo” tra l’analisi dei requisiti utente ed il disegno del sistema; una sorta di l’anello di congiunzione tra il modello a oggetti del dominio (o del business) ed il modello di disegno del sistema, di cui viene considerato un’iniziale astrazione, sebbene esistano importanti differenze. Una prima divergenza tra questi modelli è relativa alle classi presenti: nel modello di analisi sono ancora focalizzate sui requisiti funzionali del sistema, mentre l’incorporazione di quelli non funzionali è rimandata al modello di disegno. Trattandosi di uno stadio intermedio finisce per presentare un giusto compromesso tra la fase di analisi dei requisiti, i cui principali attori sono utenti e business analyst (personale con estesa esperienza del dominio del sistema e minore conoscenza tecnica), e la fase di disegno, territorio del personale che dovrà costruire il sistema (architetti, programmatori, ecc.). Altre differenze sono relative al livello di astrazione: nella fase di analisi il livello di formalismo è sicuramente superiore rispetto a quello delle fasi precedenti, ma non ancora uniformato a quello delle fasi successive. Gli esperti del dominio tendono ancora ad essere coinvolti sebbene con ruoli piuttosto marginali. Tipicamente ne è richiesto l’intervento per chiarire/analizzate situazioni ritenute ambigue, per colmare eventuali lacune (come per esempio alternative lasciate inesplorate), per “ritoccare” requisiti per così dire futuristici, e così via. Ovviamente non gli si richiede di valicare il modello. L’intervento di questi personaggi si rende necessario poiché la produzione del modello di

68

Capitolo 8. Le classi nei processi

analisi richiede l’approfondimento sia dei vari casi d’uso sia del modello a oggetti del dominio. Pertanto, uno degli effetti positivi della produzione del modello di analisi è la revisione formale ed approfondita dei vari modelli prodotti in precedenza. Non è infrequente che alcuni architetti realizzino delle versioni embrionali (usa e getta) del modello di analisi al fine di raggiungere il duplice obiettivo di acquisire una più intima conoscenza del modello dei requisiti e di realizzare una primissima versione del modello di disegno.

Le classi del modello di analisi, per forza di cose, risultano più vicine allo spazio del dominio, più concettuali e verosimilmente meno granulari delle corrispondenti a livello di disegno. In questa fase non si può ancora pretendere un modello dettagliato e tanto meno un elevato grado di formalità. A meno di particolari eccezioni, l’applicazione degli insegnamenti propri dell’OO sono parzialmente sacrificati a favore del risparmio di tempo. Inoltre, come nel caso del modello a oggetti del dominio, ancora non si è molto interessati a definire le operazioni delle varie classi, ciò sia perché probabilmente non si dispone ancora di sufficienti informazioni, sia perché il tempo richiesto per produrre un tale livello di dettaglio non sarebbe giustificabile nel contesto dell’intero processo di sviluppo del software.

A livello di analisi, tipicamente, non si è molto interessati a concetti come interfacce, sebbene l’attribuzione delle responsabilità rimanga comunque assolutamente necessaria. A tal fine, alcune volte si impiegano strumenti non completamente formali, come per esempio del testo da associare alle classi oppure formalismi come il CRC (Class – Responsibility – Collaboration, classe responsabilità e collaborazioni). In questo contesto con la definizione di responsabilità si riferisce ad un sottoinsieme di comportamento ad elevato grado di coesione definito dalle singole classi. In parole più semplici ci si riferisce alle operazioni esposte dalla classe che implementano, o concorrono a implementare, i servizi da erogare all’utente del sistema. Nel modello di analisi, si è interessati a rappresentare classi appartenenti ad una delle categorie illustrate nella fig. 8.30. Si tratta di stereotipi “standard” dell’elemento “Classe” del metamodello, i quali non introducono alcuna caratteristica supplementare se non un forte significato semantico. La descrizione degli stereotipi mostrati in fig. 8.30 è riportata nel Capitolo 2, dedicato alla panoramica sullo UML. In particolare, con la versione 1.4 del linguaggio, sono stati inseriti nel profilo standard denominato Software Development Processes (processi di sviluppo software).

69

UML e ingegneria del software: dalla teoria alla pratica

Figura 8.30 — Stereotipi del modello di analisi

«class diagram»

Analysis Object Model

AnalysisClass

EntityClass

BoundaryClass

ControlClass

Una boundary class (classe di confine) rappresenta un “punto di accesso” al sistema, una componente dello strato di interfacciamento con i relativi attori. Da tenere presente che l’interazione attore/sistema non si realizza esclusivamente attraverso apposite GUI, ma può avvenire per mezzo di altri meccanismi, come per esempio la ricezione di messaggi (in tal caso l’attore è un altro sistema/dispositivo), attraverso nuovi dispositivi di input, e così via. Le boundary class sono parti del sistema fortemente dipendenti dagli attori che le utilizzano. Queste permettono di investigare, chiarire e formalizzare i requisiti del sistema al livello di interfacciamento con entità esterne. L’interazione attore/sistema può richiedere una serie di azioni, quali il controllo dei dati (sia presenti in opportuni messaggi inviati al sistema, sia provenienti dall’immissione dei dati in appositi campi delle GUI), l’invocazione dei metodi previsti dalle classi di gestione dei servizi richiesti (in questo contesto denominate control), la presentazione dei risultati all’utente (sia per mezzo di apposite schermate, sia attraverso l’invio di opportuni messaggi), ecc.

Considerata la fase di questo modello, ed il livello di astrazione ancora abbastanza elevato, verosimilmente non è opportuno dettagliare eccessivamente le classi boundary (per esempio se si tratta di una window non è necessario descriverne tutti i widget), è sufficiente specificare gli obiettivi dell’interazione, i dati che si intendono scambiare, ecc. senza però descrivere come questa poi venga realizzata. La gestione di dettagli di questo tipo è demandata alla fase di disegno.

70

Capitolo 8. Le classi nei processi

Figura 8.31 — Costruzione del modello di analisi. Nel diagramma si è preso in considerazione un solo caso d’uso (quello in basso) e ipotizzando che esso utilizzi esclusivamente tre classi del modello a oggetti del dominio (quelle alla in basso a destra nel modello a oggetti del dominio).

Modello dei casi d'uso

Modello ad oggetti del dominio

Modello ad oggetti di analisi

Per quanto concerne le entity class (classi entità), il relativo significato è esattamente quello atteso: ossia classi atte a modellare informazioni gestite dal sistema che necessitano di essere memorizzate in forma permanente. Tipicamente esiste una corrispondenza biunivoca con le classe individuate nel modello del dominio, con la differenza che a questo stadio la struttura deve essere quanto più vicina possibile agli sviluppatori. Un ulteriore livello di dettaglio si ha nella versione del disegno, ove spesso si rende necessario prevedere dettagli implementativi, quali per esempio quelli legati al sistema per il supporto della persistenza. Per loro definizione le classi entity sono passive, sebbene possano disporre di comportamenti anche complessi legati alle informazioni contenute (per esempio negli Enterprise Entity Java Beans i dati persistenti vengono definiti attraverso opportuni metodi get/set). Uno degli scopi delle classi entity consiste nell’isolare i dettagli della memorizzazione delle informazioni dalle classi che le manipolano (control). Ciò offre il

UML e ingegneria del software: dalla teoria alla pratica

71

vantaggio di creare uno strato di indirezione e quindi limitare l’impatto di eventuali aggiornamenti apportati al livello dell’organizzazione delle informazioni o alla relativa gestione. In ultima analisi, le classi entity formalizzano la struttura logica dei dati e quindi concorrono a chiarire la comprensione delle informazioni su cui il sistema si fonda. Le control class (classi di controllo) come suggerisce il nome, rappresentano il controllo, nel senso più generale del temine (coordinamento, transizione, successione, ecc.), di altri “oggetti”. Tipicamente sono utilizzate per incapsulare la logica di controllo presente nelle funzionalità definite al livello dei casi d’uso. Spesso rappresentano algoritmi di calcolo anche complessi, attuatori delle policy aziendali e regole di business… Chiaramente esula dalla responsabilità di tale classe definire e controllare l’interazione con gli attori del sistema, o strutturare le informazioni che necessitano di essere memorizzate permanentemente nel sistema (responsabilità demandate, rispettivamente alle boundary e alle entity class). Le classi di controllo definiscono formalmente le dinamiche interne del sistema. Ciò è una logica conseguenza del fatto che gestiscono i flussi principali del sistema, coordinando il lavoro svolto da altri oggetti istanze delle boundary e delle entity class. Una delle caratteristiche peculiari delle classi di controllo consiste nell’incapsulare le logiche del sistema, e quindi nel ridurre l’impatto sulle restanti parti dovuto ad eventuali variazioni. Quindi, se delle regole del business dovessero cambiare, gli eventuali aggiornamenti dovrebbero ripercuotersi sulle relative classi di controllo senza ripercuotersi sulle restanti parti del sistema. Tipicamente le classi di controllo tendono ad assumere la fisionomia di “macro-classi” e quindi raramente sopravvivono al modello di disegno, nel quale, ciascuna classe di controllo, tende a dar luogo a svariate classi. La costruzione del modello a oggetti di analisi dovrebbe avvenire dopo aver realizzato i manufatti delle fasi precedenti come il modello dei casi d’uso, quello a oggetti del dominio, ecc. L’analisi di questi manufatti permette di semplificare notevolmente il disegno del modello di analisi (si consideri la fig. 8.31). In particolare: • le classi del modello a oggetti del dominio danno luogo alle entity class del modello di analisi. Generalmente vi è una corrispondenza quasi biunivoca, sebbene spesso nel modello di analisi si preferisca avere un dettaglio minore. Per esempio nel modello del dominio una fattura potrebbe essere rappresentata da diverse classi, come Intestazione, TotaliFattura, Riga, ecc. mentre nel modello di analisi si potrebbe preferire rappresentare globalmente il concetto di fattura per mezzo di una sola entità; • l’analisi dei singoli use case permette di derivare le control class. La corrispondenza dipende dal livello di granularità dei casi d’uso, tipicamente però dovrebbe essere del tipo 1, n: ciascuno di essi dovrebbe dar luogo ad una serie di control class (eventualmente anche una relazione n, n potrebbe risultare legittima), un intervallo tipico è 2-5 classi di controllo per caso d’uso, valori eccessivamente al di fuori

72

Capitolo 8. Le classi nei processi

da questo intervallo potrebbero essere dovuti a un modello dei casi d’uso non ben realizzato; • ogni associazione attore/caso d’uso presente relativo modello è la sede di una o più boundary class.

Dipendenza del modello di analisi dall’architettura Come visto nei capitoli dedicati all’analisi dei requisiti, l’architettura del sistema fornisce importanti riscontri già nella realizzazione del modello dei casi d’uso. Tale influenza diventa più pressante man mano che si procede nelle fasi a maggior contenuto “tecnico/ soluzione” del processo di sviluppo del software. Ciò, in prima analisi, potrebbe sembrare un controsenso: come può l’architettura del sistema, risultato ultimo del modello di disegno (manufatto prodotto in una delle fasi finali del processo), influenzare modelli iniziali come quello dei requisiti e di analisi, che invece hanno luogo in fasi precedenti? La risposta è semplice. I processi formali prevedono che fin dai primissimi stadi si inizi a redigere il famoso documento dell’architettura software (SAD, Software Architecture Document). Si tratta di un documento “dinamico” che segue l’evoluzione del sistema: si assiste ad un passaggio da versioni iniziali ad elevato livello di astrazione fino a versioni sempre più dettagliate. In pratica, ciò che accade più frequentemente è che la produzione del SAD avvenga riutilizzando versioni prodotte per sistemi realizzati in precedenza (nella speranza che non sia troppo sad, triste). Da questa versione è necessario epurare dettagli strettamente legati al sistema precedente (esistono architetti in grado di dimenticarsi di tale dettaglio, e quindi trovarsi a lavorare in un sistema bancario e incontrare riferimenti a un sistema di gestione dei voli aerei!), aggiungere descrizioni/modelli relativi al nuovo sistema, prescrizioni di nuove soluzioni tecnologiche, e così via. Il documento ottenuto viene quindi incorporato nel nuovo processo. Quindi, già dalle primissime fasi del processo, si ha a disposizione un documento di architettura preciso, dettagliato, maturo… Nella malaugurata ipotesi di non disporre di una precedente versione di un SAD, comunque si assiste al classico ciclo con riscontro: le prime versioni dei requisiti utente permettono di realizzare le versioni iniziali dell’architettura del sistema, le quali, a loro volta, forniscono un prezioso riscontro al modello dei requisiti, che, opportunamente aggiornato, comincia ad avvicinarsi all’architettura. La nuova versione dei requisiti permette di raffinare il disegno dell’architettura, e così via fino a raggiungere un’armoniosa consistenza. In ogni modo, indipendentemente dalla strategia utilizzata per redigere il SAD e dalla tecnologia che si decida di impiegare (al momento in cui viene redatto questo libro) l’architettura presa come modello universalmente accettato ha una tipica organizzazione multistrato (multi-tiered da non confondersi con le architetture multi-tired…). Questa è carat-

73

UML e ingegneria del software: dalla teoria alla pratica

Strato client

Figura 8.32 — Rappresentazione di un’architettura multi-strato. Lo strato di oggetti business è stato disegnato leggermente più stretto per evidenziare che in casi particolari e con le dovute cautele è possibile passare dallo strato dei servizi business direttamente a quello di integrazione.

Sistema client (System Client)

Cliente umano (Human Client)

Strato di presentazione (Presentation Layer) Strato di sottoscrizione (Subscription Layer)

Strato interfaccia utente (User Interface Layer)

SISTEMA

Strato dei servizi di business (Business Service Layer)

Strato degli oggetti di business (Business Object Layer)

Strato di integrazione (Integration Layer)

Sorgenti dati (Data Sources)

Sistemi esterni (External Systems)

terizzata dal suddividere logicamente le responsabilità del sistema in livelli, in modo che ciascuno si faccia carico di una sola tipologia di responsabilità. Il sistema pertanto è il risultato di una sequenza di strati, in cui ognuno fornisce servizi allo strato di livello superiore utilizzando altri forniti dal livello inferiore. Senza entrare troppo nei dettagli i vari strati che compongono l’architettura hanno responsabilità ben definite. Strato client. Rappresenta i vari dispositivi (i clienti) che interagiscono con il sistema. Nel caso di attori umani questi possono essere istanze di browser web, applicazioni, ecc.

74

Capitolo 8. Le classi nei processi

Nel caso di attori non umani, si può trattare di sistemi che pubblicano messaggi (per esempio via FTP, sistema di messaggistica, …), che invocano servizi on-line, ecc. Il sistema oggetto di studio può, a sua volta, utilizzare meccanismi analoghi per richiedere servizi ad altri sistemi distinti da quelli clienti (esserne un attore). A tal fine è presente lo strato relativo ai sistemi esterni (nella figura, in basso a destra). Si è deciso di dar luogo ad uno strato diverso perché l’espletamento di un servizio, tipicamente, prevede di percorrere tutta la pila di strati, arrivare fino alle sorgenti dati o ai sistemi esterni per poi risalire, quindi è corretto inserire le funzionalità per richiedere determinati servizi a sistemi esterni in un apposito strato. Il risultato ottenuto è passato a ritroso per i vari strati fino a giungere all’attore che ha richiesto il servizio. Durante il percorso si assiste all’arricchimento/ integrazione del servizio nel contesto di uno più completo. Strato di presentazione. Si tratta dell’interfaccia del sistema con i propri clienti e quindi incapsula la logica richiesta per servirne le richieste (autenticazione, analisi e decodifica richieste, validazione dati, invocazione servizi business, preparazione dei risultati, ecc.). Giacché i client sono di due tipologie, la struttura dello strato ne riflette la tipizzazione. In particolare vi è quello demandato a gestire i clienti umani (validazione dati, costruzione delle pagine con i risultati, ecc.) denominato Strato interfaccia utente e quello atto a comunicare con altri sistemi (ricezione messaggi, decodifica, ecc.) etichettato Strato di sottoscrizione. Strato dei servizi business. Questo strato centralizza la logica di business del sistema. Si dovrebbe trattare quindi di servizi derivati direttamente dalle azioni specificate nei flussi dei casi d’uso. Si tratta di uno strato di schermatura (façade) tra quello di presentazione, da cui riceve le richieste e ne presenta i risultati, e quello sottostante a cui delega parte delle proprie attività. Nella pratica, in alcune situazioni eccezionale può essere legittimo saltare il livello successivo (strato degli oggetti business) per richiedere direttamente i servizi presenti nello stato di integrazione. Strato degli oggetti business. Si tratta di uno strato non sempre presente, il cui compito è di incapsulare un insieme di oggetti che costituiscono il nucleo delle informazioni business corredate da opportune regole e trasformazioni. Gli oggetti appartenenti a questo strato dovrebbero essere estratti dal modello a oggetti del dominio, raggruppandoli in una serie di grafi distinti caratterizzati dal presentare un elevato livello di coesione. In sostanza, ognuno di questi grafi dovrebbe costituire il dominio di un apposito componente. Strato di integrazione. In questo strato vengono inseriti gli oggetti (e/o componenti) che si occupano di interagire con le fonti dati (gestori di database, legacy system, sistemi business-to-business, sistemi esterni per l’autorizzazione di transazioni, etc.). Da notare che i precedenti periodi sulla definizione dell’architettura del sistema e spiegazione dei vari strati rappresentano un eccellente esempio di un paragrafo appartenente al SAD (Software Architecture Document, documento dell’architettura software).

75

UML e ingegneria del software: dalla teoria alla pratica

Una volta stabilita l’architettura è opportuno che fin dal modello di analisi se ne rispetti la struttura. Chiaramente non è strettamente necessario farlo a questo livello né tanto meno indispensabile rispettarla sempre nei minimi dettagli, però forgiare il modello di analisi secondo la struttura dell’architettura offre tutta una serie di vantaggi: permette di realizzare una migliore astrazione del modello di disegno (che invece deve assolutamente rispettare le struttura dell’architettura), fornisce migliori informazioni circa la consistenza finale del sistema e quindi dei tempi e costi di disegno ed implementazione, agevola la pianificazione dei componenti, e così via. A questo punto, stabilita l’architettura, come fa il modello a rispettarne le direttive? Semplice. Ogni servizio va realizzato estendendo uno scheletro basilare come quello riportato nella fig. 8.33. Dall’analisi di fig. 8.33, si evince che tipicamente, a questo livello, i due strati “business” (service e object) sono incorporati in uno solo. Procedendo per gradi, ciascun servizio può essere richiesto sia da un attore sia umano, sia da un altro sistema. Le richieste devono necessariamente essere effettuate o pervenire ad un’apposita interfaccia. Nel caso di attore umano si tratterà di una pagina HTML, di una schermata di un’applicazione, di un ricevitore di input vocali, ecc. Mentre nel caso di attori non umani, si tratterà di un server FTP, di un modulo di ricezione messaggi ecc. Una sola classe boundary, per servizio, non è sempre sufficiente, alcune volte sono necessarie delle altre per presentare i risultare, per autenticare il cliente, ecc. Ad ogni interfaccia utente è necessario associare un apposito Handler (ciò è particolarmente utile per sistemi web). Questo si occupa di gestire e coordinare le varie interfacce, di richiedere gli appositi servizi al livello di business, ecc. Come ricordato poc’anzi, in questa fase, tipicamente è prematuro suddividere lo strato di business in quello di servizi ed oggetti. Se da un lato è corretto rappresentare il sistema a strati, dall’altro bisogna far attenzione a non esagerare con il livello di dettaglio, che correrebbe

Figura 8.33 — Template di un servizio descritto dal modello di analisi.

Attore

Strato Client

Boundary BoundaryHandler

Strato di presentazione

Manager

SorgentiDati

Strato di business

Strato di integrazione

76

Capitolo 8. Le classi nei processi

il rischio di appesantire inutilmente il diagramma, di aumentarne notevolmente la complessità, di richiedere maggiore tempo per la produzione e manutenzione. Per poter espletare i propri servizi, lo strato di business in genere accede ad opportune fonti dati. Le relative interazioni sono schermate appunto dallo strato di integrazione.

Regole fondamentali per la produzione del modello di analisi Come si vedrà di seguito, l’iniziale successo del modello di analisi è stato fortemente ridimensionato dall’introduzione dei sistemi component based e dalla standardizzazione di architetture EJB e .net. In ogni modo, esso è parte integrante di diversi processi di sviluppo del software, tra cui ICONIX (documentato nel libro [BIB05] e a cui si è fatto accenno nel capitolo introduttivo) nel quale è integrato in una metodologia nota con il nome di Robustness Analysis (analisi di robustezza). Questa prevede di prendere come prodotti di partenza il modello dei casi d’uso e quello a oggetti del dominio. In particolare è necessario leggere ogni singola frase dei vari casi d’uso e quindi aggiornare di conseguenza il modello di analisi con gli attori connessi ad appropriate boundary class che rappresentano le interfacce del sistema, poi si aggiungono le control class che raggruppano specifici insiemi di passi dello use case. Queste accedendo alle entity class prelevate dal modello del dominio e forniscono i servizi richiesti dagli attori (come dettagliato nel paragrafo precedente). A modello prodotto, chiunque dovrebbe essere in grado, modello degli use case alla mano, di leggere i vari passi di cui sono composti i flussi e quindi individuare le corrispondenze con il modello. Non è infrequente il caso di architetti che indichino esplicitamente nelle control class i passi degli use case che realizzano. Se da un punto di vista di chiarezza ciò produce diversi vantaggi, esistono tutta una serie di controindicazioni, come per esempio un tempo elevato di produzione, una fortissima dipendenza dal modello dei casi d’uso (una piccola modifica in questi genera la modifica del modello di analisi), e così via. La realizzazione del modello di analisi, secondo la metodologia Robustness, prevede di rispettare le quattro regole fondamentali riportate di seguito (dopo la lettura del paragrafo precedente, queste regole dovrebbero risultare del tutto naturali, quasi superflue). Le quattro regole sono: 1. gli attori possono essere associati unicamente a boundary class. Nel caso in cui l’attore sia un essere umano, la relativa interfaccia è costituita da opportune schermate o da specifici moduli di analisi vocale (chiaramente un attore non si esprime in sequenza di zeri ed uni). Nel caso in cui sia un altro sistema/dispositivo, l’interfaccia è rappresentata da moduli di acquisizione/analisi dei messaggi (per esempio è necessario costruire un modulo sia per sottoscriversi a specifiche code di messaggi, sia per ricevere i messaggi stessi). Ciò è necessario sia per adattare i messaggi ricevuti al proprio sistema, sia per effettuare opportuni controlli: sicurezza, validità dei dati, ecc.;

UML e ingegneria del software: dalla teoria alla pratica

77

2. le classi boundary possono essere associate esclusivamente con attori e control class. In sostanza questa regola equivale ad affermare che non è possibile associare due boundary class tra loro, così come non è possibile collegare direttamente una boundary class con una entity. Ciò costituirebbe un’evidente violazione dell’organizzazione a strati, e sarebbe fonte di tutta una serie di conseguenze non desiderabili: esposizione delle classi entità, realizzazione di interfacce utente pesanti con incorporate le sezioni relative alla business logic, alla sicurezza, ecc. (queste sezioni tra l’altro dovrebbero essere copiate in svariate interfacce), e così via. Per quanto concerne due boundary, qualora necessario, non è mai opportuno stabilire al loro interno quali altre interfacce richiamare: si tratta di un compito delle control class (è sempre opportuno separare i componenti view dai relativi control). Ciò è opportuno per isolare le interfacce utente da eventuali variazioni delle business rule o delle policy aziendali, per mantenere chiare e limitate le responsabilità dell’interfaccia utente, e così via; 3. le classi entità possono essere associate esclusivamente con quelle di controllo. In questo caso si vieta di associare tra loro due entity class e di collegarle direttamente o con le boundary o, addirittura, con gli attori. Per quanto riguarda le ultime postille le motivazioni alla base sono state ampiamente illustrate. Per ciò che attiene al divieto di associare tra loro le classi entità si tratta di una cattiva organizzazione per tutta una serie di motivi: si muoverebbero nell’organizzazione dei dati, regole del business, causando la perdita del livello di indipendenza, la chiarezza del disegno e delle responsabilità, la schermatura dai cambiamenti dei requisiti, ecc.; 4. le classi control non possono essere associate con gli attori. Questa regola dovrebbe essere ormai del tutto evidente: gli attori per interagire con il sistema devono passare attraverso opportuni strati di interfacciamento.

Esempi Sottomissione modulo di accesso Dall’analisi di fig. 8.34, si evince come sia possibile raffinare il diagramma introducendo un ulteriore livello gerarchico come riportato nella fig. 8.35.

Dal punto di vista strettamente tecnico, la nuova struttura è chiaramente più elegante e precisa. In poche parole offre un superiore livello object oriented. Nonostante questi vantaggi, l’esperienza insegna che i clienti non esperti del mondo OO (come nella quasi totalità dei casi) tendono a non sentirsi a proprio agio quando si trovano di fronte ad organizzazioni di use case che facciano uso

78

Capitolo 8. Le classi nei processi di astrazioni, e nel diagramma di figura sono presenti addirittura due livelli. Pertanto, strutture di questo tipo spesso vanno purtroppo sacrificate al fine di instaurare una migliore piattaforma di dialogo con il cliente: obiettivo fondamentale delle prime fasi del processo di sviluppo del software. Comunque non c’è poi da rammaricarsi troppo, l’eventuale mancanza di un’organizzazione astratta dei manufatti appartenenti a queste fasi è assolutamente recuperabile nei modelli prodotti negli stadi successivi a maggiore connotazione tecnica.

Figura 8.34 — Use case Sottomissione Modulo di Accesso. Selezione utente «include»

Sottomissione modulo di accesso

Line Manager

Adetto alla Sicurezza

Sottomissione m.d.a. blocco utente

Sottomissione m.d.a. sospensione utente

Sottomissione m.d.a. sblocco utente

Sottomissione m.d.a. fine sospensione utente

Sottomissione m.d.a. nuovo profilo

Sottomissione m.d.a. aggiornamento profilo «extend» [L.M. conderma m.d.a.]

«extend» [L.M. conderma m.d.a.]

Verifica regole di incompatibilità

Figura 8.35 — Use case Sottomissione Modulo di Accesso riorganizzato. Sottomissione modulo di accesso

Sottomissione m.d.a. gestione profilo

Sottomissione m.d.a. gestione utente «extend»

Sottomissione m.d.a. nuovo profilo Sottomissione m.d.a. aggiornamento profilo

Verifica regole di incompatibilità

Sottomissione m.d.a. sospensione utente Sottomissione m.d.a. sblocco utente Sottomissione m.d.a. blocco utente Sottomissione m.d.a. fine sospensione utente

79

UML e ingegneria del software: dalla teoria alla pratica

Tabella 8.4 — Template A. CASO D’USO:

Sottomissione Modulo di accesso

SICSTTMDA

Data:

10/VI/2002

Versione:0.00.001

Descrizione:

Il servizio permette ai Line Manager (precedentemente autenticati dal sistema) di sottomettere dei moduli di accesso, i quali sono automaticamente posti all’attenzione di un opportuno addetto alla sicurezza per la validazione (confrontare state chart di figura 3). Le tipologie di modulo di accesso previste sono: • richiesta di un nuovo profilo; • variazione di un profilo esistente; • blocco/sblocco utente; • sospensione/fine sospensione utente.

Durata: Priorità:

Minuti. Elevata.

Attore primario:

Line Manager Ha interesse nel compilare un nuovo modulo di accesso.

Attore secondario:

Addetto alla Sicurezza Riceve notifica della presenza di nuovo modulo di accesso da esaminare.

Precondizioni: Garanzie di fallimento:

Il Line Manager sia stato precedentemente autenticato. Non viene preso in carico dal sistema alcun nuovo modulo di accesso.

Garanzie di successo:

Il modulo di accesso viene correttamente registrato nel sistema e posto all’attenzione di un opportuno Addetto alla Sicurezza. Il Line Manager esegue la funzione di sottomissione moduli di accesso.

Avvio:

Per la seguente trattazione il diagramma preso in considerazione è quello di fig. 8.34. Per questioni di spazio, gli unici use case dettagliati sono Sottomissione modulo di accesso e Sottomissione m.d.a. nuovo profilo. Il comportamento dinamico dei restanti non presenta alcuna caratteristica inaspettata. Nel punto 12 del caso d’uso di tab. 8.5 ci si riferisce a un opportuno gruppo di addetti alla sicurezza. Il termine opportuno è dovuto al meccanismo di sicurezza operante a livello di dati/ privacy descritta in precedenza. Il nuovo modulo di accesso sottomesso dal Line Manager implicitamente appartiene ad una determinata organizzazione e quindi è strettamente necessario che sia posto all’attenzione di un gruppo di addetti alla sicurezza abilitati per quella specifica organizzazione. In caso contrario il modulo di accesso non verrebbe mai visualizzato come risultato delle ricerche dei membri del gruppo in quanto non ne possederebbe i necessari privilegi. Da notare che l’entità modulo di accesso non possiede direttamente il riferimento ad un’organizzazione di appartenenza (si faccia riferimento al modello a oggetti del dominio). Questa informazione, come lecito attendersi, è desunta dall’organizzazione in cui è in organico l’utente a cui il modulo di accesso stesso si riferisce.

80

Capitolo 8. Le classi nei processi

Tabella 8.5 — Template 1_B. Scenario principale. LINE MANAGER 1.

Richiede l’esecuzione del servizio sottomissione moduli di accesso.

SISTEMA di

2.

Include(Selezione Utente).

3.

Determina che un utente sia stato effettivamente selezionato. Verifica che l’utente selezionato non sia il Line Manager stesso.

4. 5. 6.

Mostra i dettagli dell’utente selezionato e chiede di confermare la tipologia di modulo di accesso. Imposta la tipologia di modulo di accesso che intende sottoporre.

7.

Verifica la tipologia impostata dal Line Manager.

8.

Punto di astrazione: Acquisisce dati modulo di accesso. Mostra il dettaglio completo dei dati impostati e chiede conferma a proseguire.

9. 10. 11.

Conferma i dati impostati. Memorizza il nuovo modulo di accesso.

12.

Invia notifica della presenza di un nuovo modulo di accesso ad un opportuno gruppo di addetti alla sicurezza.

13.

Comunica all’utente che il servizio è stato portato a termine correttamente.

Scenario di errore. Il Line Manager non seleziona alcun utente. 3.1.

Il sistema termina con insuccesso il caso d’uso.

Scenario di errore. Il Line Manager seleziona sé stesso. 4.1.

Il sistema comunica il tentativo di violazione delle policy di sicurezza.

4.2. 4.3.

Il sistema memorizza i dettagli del tentativo in un apposito file di log. Il sistema termina con insuccesso il caso d’uso.

Scenario di errore. Il Line Manager decide di terminare anticipatamente la sessione. La stessa sequenza vale per i punti 6 e 10. 6.1. 6.2.

Il sistema chiede conferma della terminazione. Line Manager conferma.

6.3.

Il sistema termina con insuccesso il caso d’uso.

Annotazioni. 11.

Lo stato del nuovo modulo di accesso è impostato al valore SOTTOPOSTO. Se il modulo si riferisce ad un profilo (nuovo o variazione di uno esistente), lo stato di quello cui il modulo di accesso fa riferimento è impostato al valore PREPARAZIONE. Qualora il modulo di accesso sia relativo al primo profilo di un utente, lo stato di quest’ultimo viene impostato al valore ATTESA.

UML e ingegneria del software: dalla teoria alla pratica

81

Nel presente template relativo al comportamento dinamico dello use case Sottomissione modulo di accesso e in alcuni dei seguenti, nella sezione relativa alla descrizione dello scenario principale, non è prevista un’apposita colonna dedicata all’attore secondario (Addetto alla Sicurezza), quantunque presente. Pertanto la descrizione dello scenario principale è ripartita nelle sole due colonne: quella dedicata al sistema e quella relativa all’attore principale (Line Manager). Questa decisione è motivata dal fatto che gli attori secondari in questione non eseguono particolari interazioni con il sistema, ma si limitano semplicemente a ricevere delle notifiche. Per questo motivo si è deciso di non pregiudicare la leggibilità del template ripartendo ulteriormente lo spazio con un’altra colonna, in questo caso abbastanza inutile.

Il diagramma riportato in fig. 8.36 rappresenta la proiezione statica del frammento del modello di analisi che realizza lo use case Sottomissione modulo di accesso. Sebbene fornisca molte importanti informazioni, non illustra le collaborazioni che si instaurano tra le varie entità, necessarie per erogare il servizio. In altre parole non vi è alcuna direttiva circa il comportamento dinamico. Questo è fornibile attraverso diversi strumenti, come il linguaggio naturale, i diagrammi di interazione, e così via. Si inizi con il considerare l’illustrazione del comportamento dinamico illustrata per mezzo del linguaggio naturale. Il servizio viene avviato a seguito della esplicita richiesta dell’utente: il Line Manager accede allo schermo (page) relativo alla gestione dei moduli di accesso (ModuloAccessoPage) e ivi seleziona la funzione di sottomissione di un nuovo modulo di accesso. Il controllo passa quindi all’istanza del ModuloAccessoHandler, il quale delega all’istanza SottomissioneMdAHandler il compito di gestire il servizio. Il primo passo da compiere consiste nel selezionare l’utente oggetto del futuro modulo di accesso, pertanto, il controllo passa al control SelezioneUtenteHandler. Confrontando lo use case Sottomissione modulo di accesso, il passo consiste nell’inclusione dello use case Selezione Utente. È presentata all’utente la pagina per l’acquisizione dei dettagli dell’utente da reperire (SelezionaUtentePage). Il Line Manager imposta i valori di alcuni campi da utilizzarsi come criterio per il reperimento e quindi attiva il link di ricerca. Il controllo passa nuovamente all’istanza SelezioneUtenteHandler, che deroga la ricerca al control UtentiManager. Quest’ultimo si occupa di reperire la o le istanze utente corrispondenti ai criteri di ricerca impostati dal Line Manager. I risultati sono rinviati a ritroso fino al Line Manager stesso. Questo processo prosegue fin quando il Line Manager individua e conferma i dettagli relativi all’utente desiderato, oppure decide di terminare prematuramente il processo. Reperito l’utente, il flusso degli eventi da esaminare è quello sancito nello use case Sottomissione modulo di accesso nuovo profilo. Il controllo torna all’istanza del control SottomissioneMdAHandler. A questo punto, quest’ultimo richiede a ModuloAccessoManager di fornire i dati necessari per preparare il modulo per l’immissione di un nuovo modulo di accesso (elenco dei GruppiUtente e Organizzazio-

82

Capitolo 8. Le classi nei processi

Tabella 8.6 — Template 2_A CASO D’USO: SICSTTNPR Descrizione:

Sottomissione modulo di accesso nuovo profilo

Data:

10/VI/2002

Versione:0.00.001

Durata:

Il presente use case specializza il servizio definito nello use case Sottomissione modulo di accesso, con il comportamento necessario per permettere al Line Manager di richiedere l’assegnazione di un nuovo profilo per l’utente selezionato. Minuti.

Priorità: Attore primario:

Elevata. Line Manager

Garanzie di fallimento:

Ha interesse nel compilare un nuovo modulo di accesso per richiedere un nuovo profilo per un proprio subalterno. Non viene preso in carico dal sistema alcun nuovo modulo di accesso.

Garanzie di successo:

Il modulo di accesso viene correttamente registrato nel sistema e posto all’attenzione di un opportuno Addetto alla Sicurezza. Se l’utente a cui si riferisce il modulo di accesso si trova nello stato di INSERITO, questo viene aggiornato in ATTESA.

Scenario principale. LINE MANAGER SISTEMA Definizione punto di astrazione: Acquisisce dati modulo di accesso. Il Line Manager richiede di impostare un modulo di accesso per un nuovo profilo. 1.

2.

Predispone il modulo necessario per impostare un nuovo modulo di accesso relativo ad un nuovo profilo. Prototipo interfaccia X.Y.Z. Imposta i dati presenti. Confrontare Domain Object Model. (Descrizione, priorità, data e orario di inizio e fine validità, ... ) In particolare imposta i vari Contesti di sicurezza, ciascuno dei quali accoppia uno specifica istanza di Gruppi Utente ad una determinata istanza dell’entità Organizzazioni. In sostanza si definiscono le organizzazioni in cui i servizi abilitati dai Gruppi utente saranno abilitati.

3.

Verifica i dati impostati dal Line Manager. (Business Rules x1.y2)

4.

Punto di estensione: Verifica regole di incompatibilità. Condizione: Il Line Manager ha impostato i dati correttamente. Accerta che la verifica del punto precedente non abbia evidenziato violazioni. Termina il presente segmento

5. 6.

83

UML e ingegneria del software: dalla teoria alla pratica

Tabella 8.7 — Template 2_B Scenario alternativo. x.y) I dati impostati dall’utente sono errati (confrontare business rules . 3.1. Il sistema comunica l’errore individuato. 3.2. Lo use case riprende dal punto 1. Scenario alternativo. Il nuovo profilo viola regole di incompatibilità di tipo “warning”. 5.1. L’elenco delle regole violate viene mostrato all’utente. 5.2. Il sistema chiede all’utente di confermare. 5.3. In caso di conferma lo use case procede regolarmente. 5.4. In caso di mancata conferma il sistema esegue il punto 2 dando la possibilità di modificare i valori impostati Scenario alternativo. Il nuovo profilo viola regole di incompatibilità di tipo “error”. 5.1. L’elenco delle regole violate viene mostrato all’utente. 5.2. Il sistema esegue il punto 2 dando la possibilità di modificare i valori impostati Scenario di errore. Il Line Manager decide di terminare anticipatamente la sessione 3.1. Il sistema chiede conferma della terminazione. 3.2. Line Manager conferma. 3.3. Il sistema termina l’intero use case con insuccesso

Figura 8.36 — Porzione dell’Analysis model che realizza lo use case Sottomissione modulo di accesso.

RegolaIncompatibilita

ModuloAccessoPage ModuloAccessoHandler

ModuloAccessoStadio

ModuloAccesso Line Manager

InserimentoMdAPage SottomissioneMdAHandler ModuliAccessoManager Organizzazione

SelezioneUtentePage SelezioneUtenteHandler

UtentiManager

GruppoUtenti

Profilo Addetto alla Sicurezza

NotifichePage

NotificheHandler Utente

84

Capitolo 8. Le classi nei processi

ne selezionabili, l’identificatore del nuovo profilo, ecc.). Richiede inoltre allo stesso di eseguire il controllo che l’utente selezionato non sia lo stesso di quello connesso. Superato positivamente il controllo ed ottenuti i vari dati, questi vengono utilizzati per impostare la pagina InserimentoMdAPage per l’inserimento dei dati. A questo punto il Line Manager imposta i vari dati e quindi preme il tasto di conferma. Le informazioni impostate sono passate all’handler S o t t o m i s s i o n e M d A H a n d l e r che le passa al ModuloAccessoManager il quale verifica che non ci siano violazioni alle regole di incompatibilità e quindi provvede a memorizzare le nuove istanze delle entità ModuloAccesso, ModuloAccessoStadio, ecc. Da notare che i servizi di memorizzazione delle informazioni relative al profilo e allo stato dell’utente sono forniti dall’istanza di UtentiManager. L’ultimo passo prevede di inviare una specifica segnalazione ad un opportuno Addetto alla Sicurezza. Ciò avviene per mezzo della collaborazione tra il control NotificheHandler e NotifichePage. Nel caso in cui si verifichi qualche inconveniente, oppure qualche controllo generi un esito negativo, viene presentata al Line Manager un’opportuna segnalazione invitandolo ad operare le necessarie correzioni nei dati impostati. Per questioni di sintesi, nei precedenti periodi è stato illustrato esclusivamente lo scenario di successo, quello in cui tutto funziona perfettamente, l’utente non fa il birichino ed inserisce i dati correttamente, e così via. Chiaramente quando si disegna un modello di analisi è di fondamentale importanza accertarsi che questo sia in grado di gestire anche flussi degli eventi specificati negli scenari alternativi e di errore. Ora tutta la descrizione riportata è sintetizzabile attraverso un unico diagramma di interazione. In contesti come il presente, tipicamente si preferisce ricorrere all’utilizzo di diagrammi di sequenza piuttosto che a quelli di collaborazione. Probabilmente perché il fattore tempo è ancora quello a cui si preferisce conferire maggiore enfasi. Come si può notare, il formalismo grafico risulta decisamente più immediato ed accattivante. Un altro punto di forza è la capacità di evidenziare chiaramente la collaborazione temporale tra le varie entità. A fronte di questi vantaggi però esistono tutta una serie di svantaggi, come il limitato livello di dettaglio (dovuto a problemi di rendering grafico), l’elevato tempo necessario per produrre il diagramma e soprattutto per mantenerlo aggiornato, ecc. Per quanto riguarda il livello di dettaglio, nel diagramma di fig. 8.37 si è deciso (per questioni di rendering grafico) di non visualizzare le varie entity (si tratta di una riduzione importante del contenuto informativo: non viene mostrato come i dati siano manipolati dai componenti del sistema), di mostrare il servizio dal momento in cui il processo è sotto il controllo del SottomissioneMdAHandler e di non evidenziare la notifica della presenza di un nuovo modulo di accesso all’Addetto alla Sicurezza. L’esempio di fig. 8.38 è relativo all’approvazione del modulo di accesso. In sostanza viene saltato il servizio di validazione, in quanto ritenuto meno interessante.

85

UML e ingegneria del software: dalla teoria alla pratica

Figura 8.37 — Diagramma di sequenza (non molto dettagliato) relativo allo scenario principale del servizio di Sottomissione modulo di accesso nuovo profilo.

LineManager

:SelezioneUtentePage :InserimentoMdAPage :SottomissioneMdAHandler :SelezioneUtenteHandler

(da ModuloAccessoHandler) Richiesta sottomissione nuovo modulo di accesso

:UtentiManager

:ModuliAccessoManager

richiede reperimento utente oggetto del nuovo m.d.a.

visualizza la pagina per l'acquisizione dei criteri di ricerca imposta criteri di ricerca utente

criteri di ricerca utente impostati dal Line Manager reprimento utente elenco utente reperiti

elenco utente reperiti seleziona utente

utente selezionato utente selezionato effettua verifiche preliminari

reperisce dati nuovo per modulo di accesso

reperisce elendo gruppi utente ed organizzazioni pagina con modulo di accesso con impostazioni di default imposta modulo di accesso

dati nuovo per modulo di accesso dati modulo di accesso impostati dal Line Manager

verifica regole di incompatibilita'

inserimento nuovo modulo di accesso inserimento profilo ed eventualmente aggiornamento stato utente

inserimento modulo di accesso

pagina di conferma avvenuta presa in carico del nuovo modulo di accesso

Ancora una volta, un modello più formale, ma probabilmente meno intuitivo per un pubblico di fruitori non tecnici, potrebbe essere quello riportato nella fig. 8.39. Per quanto riguarda la violazione delle regole di incompatibilità, c’è da notare che sebbene il controllo avvenga anche nelle fasi precedenti (Sottomissione e Validazione modulo di accesso), è opportuno effettuare nuovamente la verifica, essenzialmente per due motivi: 1. alcune violazioni della tipologia warning che potrebbero essere state ritenute accettabili dai precedenti attori (Line Manager e Addetto alla Sicurezza), potrebbero non esserlo altrettanto per il supervisore; 2. nell’intervallo temporale che intercorre tra una fase e quella successiva, la tabella delle regole di incompatibilità potrebbe subire degli aggiornamenti.

86

Capitolo 8. Le classi nei processi

Figura 8.38 — Use case Approvazione modulo di accesso.

Selezione moduli di accesso

Reperimento dettagli utente

«include»

«include»

Addetto Sicurezza

Approvazione modulo di accesso Supervisore Sicurezza

«extend» [modulo di accesso relativo ad un nuovo profilo o all'aggiornamento di uno esistente]

Verifiche regole di incompatibilita'

Line Manager

Figura 8.39 — Use case Approvazione modulo di accesso a maggiore grado di formalità e, probabilmente, minore leggibilità da parte di un pubblico di non tecnici.

Reperimento dettagli utente Verifiche regole di incompatibilita'

Selezione moduli di accesso

«include»

«include»

«extend»

Esame modulo di accesso

Addetto Sicurezza

Supervisore Sicurezza

Approvazione modulo di accesso

Rigetto modulo di accesso

Line Manager

87

UML e ingegneria del software: dalla teoria alla pratica

Tabella 8.8 — Template 3_A CASO D’USO: SICAPPMDA Descrizione:

Durata: Priorità: Attore primario:

Attore secondario:

Attore secondario: Precondizioni: Garanzie di fallimento: Garanzie di successo:

Avvio:

4.

12. 13.

14.

15.

Include(Selezione moduli di accesso). Verifica che siano presenti moduli di accesso da approvare. Visualizza l’elenco dei moduli di accesso reperiti. Verifica che il Supervisore alla Sicurezza abbia selezionato un modulo di accesso. Include(Reperimento dettagli utente). Punto di estensione: Verifica regole di incompatibilità. Condizione: Il Modulo di accesso è della tipologia “Nuovo profilo” o “Aggiorna profilo”. Visualizza il modulo di accesso corredato dai dettagli dell’utente cui si riferisce e dalle eventuali regole di incompatibilità violate.

9.

11.

SISTEMA

Seleziona un modulo di accesso.

7. 8.

10.

10/VI/2002

Versione: 0.00.001

Il servizio permette ai Supervisori alla sicurezza (precedentemente autenticati dal sistema) di esaminare e quindi approvare o rigettare, i moduli di accesso sottoposti alla loro attenzione. I moduli di accesso giungono all’attenzione dei supervisori alla sicurezza dopo essere stati validati dagli addetti alla sicurezza. Confronta statechart diagram fig. 3. Una volta approvato, gli effetti specificati nel modulo diventano operativi alla data di inizio validità. Confronta modello ad oggetti del dominio. Minuti. Elevata. Supervisore alla Sicurezza. Ha interesse nell’esaminare il modulo di accesso e quindi approvarlo o rifiutarlo. Line Manager Riceve notifica circa la decisione del supervisore alla sicurezza. In caso di rifiuto, deve intraprendere opportune azioni (correzione e risottomissione o cancellazione del modulo di accesso). Addetto alla sicurezza Riceve notifica circa la decisione del supervisore alla sicurezza. Il Supervisore alla Sicurezza sia stato precedentemente autenticato. Non viene variato lo stato del modulo di accesso. Il modulo di accesso cambia di stato in funzione della decisione presa dal Supervisore alla Sicurezza (APPROVATO / RESPINTO) ed opportuni record sono inseriti nella tabella dei lavori schedulati. Il Supervisore alla sicurezza esegue la funzione di approvazione moduli di accesso.

Scenario principale. SUPERVISORE SICUREZZA 1. Richiede esecuzione del servizio di approvazione moduli di accesso. 2. 3.

5. 6.

Data:

Approvazione modulo di accesso

Approva il modulo di accesso. Eventualmente imposta delle annotazioni. Verifica che il modulo di accesso non sia riferito allo stesso Supervisore alla Sicurezza. Verifica che non ci siano violazioni di tipo “error” delle regole di incompatibilità. Aggiorna lo stato del modulo di accesso (eventualmente anche quello del relativo profilo) ed inserisce appositi record nella coda dei lavori batch da eseguire. Invia opportuna notifica relativa all’avvenuta approvazione, sia al Line Manager, sia all’Addetto alla Sicurezza. Memorizza l’approvazione in un apposito file di log.

88

Capitolo 8. Le classi nei processi

Tabella 8.9 — Template 3_B Scenario alternativo. Il Supervisore alla sicurezza rigetta il modulo di accesso inserendo la motivazione. Violazione “error” delle regole di incompatibilità (punto 8) 10.1. Verifica che il modulo di accesso non sia riferito allo stesso Supervisore alla Sicurezza. 10.2. Aggiorna lo stato del modulo di accesso. 10.3. Invia opportuna notifica relativa all’avvenuto rigetto del modulo di accesso, sia al Line Manager, sia all’Addetto alla Sicurezza. 10.4. Memorizza il rigetto in un apposito file di log. 10.5 Termina il caso d’uso con successo. Scenario di errore. Nessun modulo di accesso reperito. 3.1. Il sistema notifica l’anomalia. 3.2. Il sistema termina con insuccesso il caso d’uso. Scenario di errore. Il Line Manager decide di terminare anticipatamente la sessione. La stessa sequenza vale per i punti 5 e 10. 6.1. Il sistema chiede conferma della terminazione. 6.2. Line Manager conferma. 6.3. Il sistema termina con insuccesso il caso d’uso. Scenario di errore. Il Supervisore alla Sicurezza tenta di approvare/rifiutare un modulo di accesso riferito a sé stesso. Quanto segue vale anche per il punto 10.1 11.1. Il sistema comunica il tentativo di violazione delle policy di sicurezza. 11.2. Il sistema memorizza il dettagli del tentativo in un apposito file di log. 11.3. Il sistema termina con insuccesso il caso d’uso. Annotazioni 13. In funzione della tipologia del modulo di accesso il sistema genera diverse tipologie di record da inserire nella tabella relativa ai lavori schedulati. In particolare: Nuovo ed aggiornamento profilo: il sistema genera due record, il primo relativo all’inizio validità del profilo ed il secondo relativo alla fine validità. Sospensione utente: generati due record, quello relativo all’inizio della sospensione ed il secondo afferente la fine. Blocco, sblocco e fine sospensione utente: generato un solo record relativo, rispettivamente, alla data ed orario di blocco, sblocco e fine sospensione.

Analizzando lo use case è possibile notare la presenza di una incoerenza (evidenziata anche dal diagramma di fig. 8.39). Il nome dello use case è Approvazione modulo di accesso, pertanto l’obiettivo dello stesso, ossia il successo, è dato appunto dall’approvazione. Ora, gli scenari alternativi sono stati definiti come varianti del flusso degli eventi definiti nello scenario principale

89

UML e ingegneria del software: dalla teoria alla pratica che comunque permettono di ottenere il raggiungimento del successo. Come si può notare esiste lo scenario alternativo relativo al rifiuto del modulo di accesso, questo evidentemente non permetterebbe di raggiungere l’obiettivo dell’approvazione del modulo e quindi dovrebbe essere uno scenario di eccezione. Questa anomalia potrebbe essere risolta attribuendo un nome più consistente al caso d’uso, come per esempio analisi del modulo di accesso. Ciò però risulterebbe meno accattivante e mnemonico. Quindi, utilizzando un approccio pragmatico, si è preferito generare una piccola incoerenza per rendere il modello più chiaro e intuitivo. Per quanto riguarda la selezione dei moduli di accesso validati e quindi eventualmente da approvare, c’è da notare che qualora non ne venga reperito alcuno, ciò non significa necessariamente che non siano presenti. Come per ogni altro utente, anche il profilo del Supervisore alla Sicurezza specifica il ruolo e l’organizzazione in cui il possessore può esercitarlo. Pertanto se presenti nella base dati moduli di accesso validati appartenenti però ad un’organizzazione diversa da quella in cui l’attuale Supervisore alla Sicurezza è autorizzato, queste non passeranno mai il filtro della “riservatezza dei dati”.

Figura 8.40 — Modello di analisi del servizio approvazione moduli di accesso.

RegolaIncompatibilita

ModuloAccessoPage ModuloAccessoHandler

ModuloAccessoStadio

ModuloAccesso Line Manager

ApprovazioneMdAPage ApprovazioneMdAHandler ModuliAccessoManager Organizzazione

SelezioneMdAPage

SelezioneMdAHandler

NotifichePage

NotificheHandler

Addetto alla Sicurezza

UtentiManager

GruppoUtenti

Profilo

Utente Line Manager

90

Capitolo 8. Le classi nei processi

Figura 8.41 — Diagramma di sequenza del modello di analisi relativo al servizio approvazione moduli di accesso.

Supervisore alla Sicurezza

:SelezioneMdAPage :ApprovazioneMdAPage :ApprovazioneMdAHandler :SelezioneMdAHandler

:UtentiManager

:ModuliAccessoManager

(da ModuloAccessoHandler) Richiesta servizio approvazione modulo di accesso

richiede reperimento moduli di accesso validati visualizza la pagina per l'acquisizione dei criteri di ricerca

imposta criteri di ricerca m.d.a.

criteri di ricerca m.d.a. impostati dal Supervisore alla Sicurezza reperimento moduli di accesso validati elenco m.d.a. reperiti

elenco moduli di accesso validati reperiti seleziona m.d.a.

modulo di accesso selezionato m.d.a. selezionato reperisce dati utente a cui si riferisce l'm.d.a. selezionato

verifica rispetto delle regole di incompatibilità pagina con dettagli modulo di accesso da approvare

dati completi m.d.a.

approva modulo di accesso aggiorna lo stato del modulo di accesso

approvazione modulo di accesso eventualmente aggiornamento il profilo

pagina di conferma avvenuta approvazione modulo di accesso

A questo punto il diagramma riportato in fig. 8.40 dovrebbe risultare abbastanza chiaro. In ogni modo in fig. 8.41 ne viene illustrato il comportamento dinamico. Un’osservazione importante è relativa ai vari control denominati manager. Da un’analisi attenta è possibile notare come questi forniscano una prima versione della decomposizione del sistema in componenti. Come si può notare essi offrono una serie di servizi, e per poterli fornire accedono ad un insieme ben definito di entità. Per esempio, ModuliAccessoManager si occupa di fornire tutti i servizi relativi ai moduli di accesso: sottomissione, verifica, reperimento, validazione, approvazione, ecc. Per ottenere tali servizi deve accedere ad un insieme ben definito di entità: RegolaIncompatibilità, ModuloDiAccesso, ModuloDiAccessoStadio, ecc. Quando poi viene la necessità di manipolare dati relativi agli utente e ai relativi profili, ecco che è necessario delegare al relativo manager (UtentiManager) le varie operazioni.

UML e ingegneria del software: dalla teoria alla pratica

91

Pro e contro del modello di analisi Dalla lettura dei paragrafi precedenti i lettori più attenti avranno intuito che il modello di analisi non rientra nella categoria di quelli sempre strettamente necessari che se evitati amplificano notevolmente il fattore di rischio dell’intero progetto. Diversi processi, tra cui lo stesso RUP della Rational, non prevedono una fase distinta di analisi, che invece è integrata con quella di disegno (analysis and design). La decisione se dar luogo o meno ad una fase distinta dipende da molti fattori, non ultimi la dimensione del progetto, il tempo a disposizione, la tipologia, la tecnologia coinvolta, e così via. Qualora però si decida di dar luogo alla fase di analisi, questa dovrebbe aver luogo quasi interamente durante lo stadio di elaborazione (quella successiva alle iterazioni preliminari), e non dovrebbe richiede più di un paio di iterazioni. L’importanza della fase di analisi è stata recentemente attenuata dalla standardizzazione di architetture del tipo Sun EJB e Microsoft .net. Tale standardizzazione, da una parte, ha generato l’esigenza di adeguare i processi di sviluppo del software alle nuove peculiari esigenze dei sistemi basati sui componenti (alcuni esempi sono forniti in [BIB15], [BIB18]); dall’altro ha concorso a ridurre il rischio tecnologico (gran parte dell’infrastruttura del sistema è resa disponibile dai vari tool, i pattern da applicare sono ben definiti, ecc). Logica conseguenza è che molta dell’attenzione è stata spostata su altri manufatti. Per molti autori, uno dei vantaggi offerti dal modellare i requisiti utente attraverso i diagrammi delle classi deriva dalla semplicità e rapidità di aggiornamento degli stessi, caratteristica peraltro intrinseca del formalismo. Logica conseguenza è che tutti i modelli istanza dei diagrammi delle classi si prestano bene ad assorbire i temutissimi change requirements (cambiamento dei requisiti). Il modello di analisi in più, appartenendo ad un livello di astrazione più elevato rispetto a quello di disegno (permette di visualizzare la struttura del sistema nascondendone i dettagli più tecnici), rende più agevole analizzare nuove richieste, studiarne l’impatto, il tempo necessario e quindi i costi, e così via. L’elevato livello di astrazione (rispetto al modello di disegno) è particolarmente apprezzato anche per attività quali la formazione del nuovo personale tecnico coinvolto nello sviluppo e/o manutenzione del sistema. Disponendo di un modello di analisi corretto e aggiornato, si può risparmiare molto tempo: pur trattandosi di un manufatto che offre una vista concettuale e precisa del sistema sottostante, lo fa in modo più agevole e meno dettagliato del corrispettivo al livello di disegno.

I modelli di analisi assumono un ruolo preziosissimo quando si ha a che fare con sistemi legacy datati o con sistemi prodotti da terze parti, magari carenti di documentazione tecnica. In tali circostanze potrebbe rivelarsi molto utile disporre di una fase distinta di analisi: invece di imbattersi in difficili, costose e probabilmente inutili elaborazioni di modelli di disegno, potrebbe risultare con-

92

Capitolo 8. Le classi nei processi veniente realizzare un modello più concettuale. Una scelta in tal senso sarebbe giustificata da diverse considerazioni. Per esempio l’eventuale evoluzione di sistemi come quelli offerti da enti esterni, non è assolutamente controllabile e quindi mantenere aggiornato il diagramma di disegno potrebbe diventare un lavoro estenuante. Viceversa, se il sistema legacy non prevede cambiamenti o è implementato per mezzo di tecnologia a elevato grado di obsolescenza, allora è evidente che il valore aggiunto da un modello di disegno risulterebbe decisamente relativo.

Ulteriori vantaggi sono legati al fatto che la realizzazione del modello di analisi fornisce un’ottima opportunità per il team degli architetti di acquisire rapidamente e formalmente i requisiti del sistema, di rivedere rigorosamente il modello dei casi d’uso e quello a oggetti del dominio, ecc. Lo svantaggio principale del modello è sempre lo stesso: il tempo necessario per la produzione e manutenzione. Se il costo della produzione iniziale a volte è giustificabile per i motivi poc’anzi enunciati, la relativa manutenzione lo sarebbe molto meno. Ciò dovrebbe avvenire ogni qualvolta si aggiorni il modello dei requisiti e/o quello di disegno. Questa necessità di mantenere aggiornati i vari modelli è un problema serio, basti pensare al modello di disegno, che pur di fondamentale importanza, difficilmente è mantenuto allineato con il codice. Per questi motivi spesso il modello di analisi viene considerato da molti tecnici un modello “usa e getta” (alcune volte “getta e getta”). Infatti una volta ottenuti i vantaggi legati alla realizzazione del modello iniziale non ci si preoccupa più di aggiornarlo. Un altro svantaggio del modello di analisi è legato al fatto che nella pratica molto raramente la produzione iniziale del modello di disegno avviene disegnando tutto da zero. In genere si tende a riapplicare strutture, pattern, soluzioni utilizzate in precedenza e dimostratesi soddisfacenti. Si parte quindi cercando di riutilizzare (eventualmente reingegnerizzandole) architetture dimostratesi funzionanti in precedenti progetti. Pertanto i vantaggi offerti dalla produzione del modello di analisi vengono fortemente ridimensionati.

Modello di disegno Introduzione Nel corso dell’esecuzione dei processi di sviluppo del software, l’ultimo modello a oggetti che si incontra è quello relativo alla proiezione statica del modello di disegno. Nei processi di sviluppo di tipo iterativo ed incrementale, tipicamente è prodotto nella relativa fase denominata appunto di disegno, che in molti processi (tra cui il RUP) è integrata con quella di analisi (si parla pertanto di analisi e disegno). Tale fase raggiunge il culmine

UML e ingegneria del software: dalla teoria alla pratica

93

tra la fine dello stadio di elaborazione (Elaboration) e l’inizio di quello di costruzione (Construction). In maniera analoga ai modelli analizzati in precedenza, anche quello di disegno può essere decomposto nei due sottomodelli (proiezioni) relative alla struttura statica ed al comportamento dinamico. Chiaramenti, per la costruzione del sistema non è sufficiente il modello a oggetti (diagrammi delle classi): sebbene di importanza fondamentale, specifica unicamente la struttura statica del sistema, mentre nulla specifica in merito al relativo comportamento dinamico che, in ultima analisi, indica come i vari oggetti (istanze delle classi rappresentate nella proiezione statica) collaborino tra loro al fine di fornire i servizi erogati dal sistema. La descrizione delle dinamiche interne del sistema, tipicamente, è affidata ai diagrammi di interazione (sequenza, sequence e collaborazione, collaboration), e meno frequentemente, ai diagrammi di attività. Questi si rivelano particolarmente utili qualora si voglia enfatizzare l’esecuzione concorrente di determinati processi. Resta poi sempre valido il consiglio di produrre diagrammi degli oggetti (quelli propriamente detti) per esemplificare porzioni di diagrammi delle classi ritenuti poco chiari. Il modello di disegno è indubbiamente uno dei manufatti più importanti del processo di sviluppo del software, sebbene in molti progetti viene prodotto unicamente per scopi documentativi con tecniche di reverse engineering. Verosimilmente è il più noto tra quelli a oggetti tanto che ogni qualvolta si nomina il concetto di diagramma delle classi, l’astrazione che tende a generarsi nella mente degli ascoltatori è relativa ai diagrammi delle classi di disegno: rappresentazioni grafiche dell’organizzazione del sistema in termini implementativi (codice). Il modello di disegno è decisamente orientato alle soluzione tecniche e pertanto si configura come ambiente naturale di architetti e programmatori. Dopo aver lungamente argomentato le funzionalità che il sistema dovrà realizzare, l’attenzione viene (finalmente) focalizzata sul dettaglio di come ottenerle. I risultati dei vari processi di analisi sono tradotti in soluzioni tecniche a bassissimo livello di astrazione, nelle quali sono condensate sia la struttura statica, sia le collaborazioni dinamiche del sistema. Le classi presenti nei precedenti modelli a oggetti vengono “incastrate” in una serie di infrastrutture tecniche che rendono possibile realizzare quanto stabilito nella vista dei casi d’uso. Il modello di disegno concorre a definire la descrizione analitica dell’architettura software del sistema in funzione dei requisiti catturati nelle fasi precedenti e, pertanto, rappresenta il dettaglio delle specifiche per la fase di codifica. Di modelli di disegno (qualora non si passi direttamente dalle specifiche del cliente al codice) ne esistono diverse versioni o, se si preferisce, ne esiste un’unica che evolve durante lo svolgimento delle fasi di elaborazione e costruzione, passando dal livello di specifica via via a quello di implementazione. Le prime versioni del modello presentano un livello di dettaglio non eccessivamente approfondito, in base anche alle competenze dei programmatori a disposizione. In questo modello trovano posto, sicuramente, tutte le classi

94

Capitolo 8. Le classi nei processi

principali ed i metodi più importanti, mentre è buona norma aggiungere classi di marginale importanza in un secondo momento, in funzione delle richieste/suggerimenti provenienti dagli sviluppatori, di eventuali problemi incontrati in corso d’opera, e così via. La stessa sorte conviene al dettaglio di tutti i metodi che tipicamente è ottenuto dall’accoglimento delle decisioni implementative. È invece importante definire fin da subito i metodi pubblici (attributi pubblici non dovrebbero esistere) con la relativa firma in modo da garantire la coerenza del modello, e quindi del sistema, fin dalle primissime fasi evitando di affidarsi eccessivamente all’esperienza dei singoli programmatori. La mancata definizione dell’interfaccia delle varie classi potrebbe causare una perdita di coerenza del sistema, dovuta a scelte operate “localmente” dai singoli sviluppatori che naturalmente non possiedono la visione di insieme del progetto, o quantomeno non sono al corrente delle scelte operate localmente da altri sviluppatori. La formalizzazione dei metodi pubblici è particolarmente utile specie quando si realizzano infrastrutture necessarie per altri sistemi, come per esempio i famosi framework. In questi casi è auspicabile che l’utente dell’infrastruttura non possa cadere in confusione (per esempio perché diversi programmatori utilizzano differenti convenzioni di “disegno”) per via di tecniche non consistenti presenti nel sistema. Il modello di implementazione, invece, nella versione finale è una rappresentazione grafica del codice (tenendo sempre presente che “solo il codice è sincronizzato con sé stesso” [S. Ambler]). Spesso è realizzato “raffinando” il modello di specifica attraverso il reverse engineering del codice, oppure integrando “in linea” le varie parti prodotte dagli sviluppatori attraverso lo sviluppo integrato del codice e del disegno. Uno degli input per la produzione del modello di disegno è costituito dal modello di analisi, che, come visto, fornisce una definizione rigorosa dei requisiti funzionali tralasciando quelli non funzionali, che invece rappresentano una parte fondamentale degli aspetti considerati dal modello di disegno. Sebbene i requisiti non funzionali non siano direttamente considerati nel modello di analisi, c’è da dire che questi fanno sentire la propria influenza fin dalle fasi di analisi dei requisiti. Pertanto, a differenza del modello di analisi, quello di disegno realizza fisicamente i casi d’uso focalizzando l’attenzione su come i requisiti funzionali e non funzionali del sistema e gli altri vincoli si ripercuotono sul sistema in costruzione. Non è infrequente il caso in cui il modello di analisi venga tralasciato per questioni di economia dei tempi. Chiaramente la decisione di trascurare il modello di analisi comporta un certo rischio che però può essere gestito e mitigato dalla presenza di architetti esperti. Qualora non si abbia a disposizione il modello di analisi, è necessario realizzare quello di disegno partendo direttamente dai casi d’uso, accogliendo direttive dell’architettura, cercando di riutilizzare soluzioni mostratesi efficaci in precedenti progetti, sfruttando i pattern previsti dalle particolari tecnologie impiegate, ecc. Tipicamente gli architetti più esperti riescono a produrre, senza grossi problemi, prime versioni del modello di disegno a partire dai casi d’uso e dai modelli a oggetti del dominio e/o del business.

95

UML e ingegneria del software: dalla teoria alla pratica

Figura 8.42 — Esempio di utilizzo dei ruoli associazione. * 1 -utente

Utente

SoggettoTask

* 0..1 -profilo

Profilo

Poiché il modello di disegno è un’astrazione del codice, è necessario che accolga caratteristiche dipendenti dal linguaggio di programmazione. Ciò è utile considerando che spesso il disegno iniziale viene rifinito dopo e/o durante la codifica per mezzo del reverse engineering o produzione sincronizzata. Se per esempio si dovesse decidere utilizzare Java come linguaggio di programmazione, evidentemente avrebbe poco senso organizzare delle classi per mezzo di relazioni di ereditarietà multipla, in quanto il linguaggio non prevede tale tipologia di generalizzazione. Ulteriori vincoli da tenere presenti durante la realizzazione del modello di disegno derivano dall’ambiente di produzione del codice, da proprietà quali riusabilità dei componenti, scalabilità del sistema, tecnologie a disposizione (per esempio database relazionali o OO, sistema operativo, ecc.), e così via. Anche le relazioni con cui le classi sono associate tra loro nel modello di disegno hanno una corrispondenza biunivoca con la relativa rappresentazione in codice. Pertanto, piuttosto che specificare i nomi alle varie associazioni (comunque sempre possibile), è preferibile utilizzare i ruoli che le classi recitano nell’associazione stessa. I nomi di questi ruoli sono i nomi degli attributi utilizzati per realizzare la relazione. La codifica della classe SoggettoTask riportata nella fig. 8.42 genererebbe un codice simile del tipo: Class SoggettoTask { ... private Utente utente = null; private Profilo profilo = null; ... }

Da notare che, poiché la classe SoggettoTask non è navigabile in nessuna delle due relazioni, nelle classi Utente e Profilo non è presente un attributo di tipo SoggettoTask.

96

Capitolo 8. Le classi nei processi

In questa fase anche le definizione precisa delle diverse tipologie di relazioni ha un significato semantico importantissimo. Per esempio specificare un’aggregazione piuttosto che una composizione ha molte implicazioni in termini della gestione del ciclo di vita degli oggetti istanze delle classi coinvolte nell’associazione stessa. In particolare una classe composta dovrà fornire tutti i metodi necessari per creare e distruggere le istanze delle classi che la compongono. Qualora si dia luogo al modello di disegno a partire dal modello di analisi, è necessario tenere presenti alcune differenze. Per esempio, nel modello di analisi tutte le classi evidenziate dovrebbero appartenere ad una delle tipologie viste in precedenza (control, boundary, entity), nel modello di disegno si possono avere molte altre tipologie che dipendono sia dall’applicazione di pattern (per esempio factory, pool, ecc.) sia dal linguaggio e dall’architettura utilizzata. Per esempio se si decidesse di basare il sistema sull’architettura EJB, probabilmente si avrebbero stereotipi del tipo E J B E n t i t y , EJBStatelessSession, ecc. Nel modello di disegno poi, è molto importante definire concetti come le interfacce. Queste sono a tutti gli effetti dei contratti stipulati tra classi server (quelle che offrono i servizi dichiarati nell’interfaccia), e classi client. La loro definizione permette lo sviluppo in parallelo dei vari sottosistemi (l’interazione è definita a priori), aumenta la riusabilità del codice, offre maggiore scalabilità, permette un migliore assorbimento dei cambiamenti di specifiche e così via.

Esempio Come esempio di modello di disegno si è preso in considerazione il processo temporizzato che si occupa di analizzare i lavori presenti nella tabella dei task pendenti (PendingTask) al fine di eseguire quelli la cui data ed orario di esecuzione è stata raggiunta (fig. 8.43).

Figura 8.43 — Caso d’uso relativo all’esecuzione dei lavori pendenti.

Esecuzione lavori pendenti Timer Attivazione nuovo profilo

Sospensione utente

Attuazione modifiche profilo

Rimozione sospensione utente

Disattivazione profilo

Blocco utente Sblocco utente

97

UML e ingegneria del software: dalla teoria alla pratica

Tabella 8.10 — Template 4 CASO D’USO: Esecuzione lavori pendenti SICGSTTSK

Data: 10/VI/2002 Versione: 0.00.001

Il servizio si occupa di reperire tutti i lavori pendenti la cui data ed orario di esecuzione precedono la data ed orario attuale non precedentemente eseguiti e quindi di eseguirli. In particolare, i task possono richiedere uno dei seguenti servizi: rendere operativo un nuovo profilo, attuare gli aggiornamenti di un profilo, disattivare un profilo, sospendere un utente, rimuovere la sospensione di un utente, bloccare e sbloccare un utente. Secondi. Elevata.

Descrizione:

Durata: Priorità: Attore primario:

Timer Fornisce lo stimolo temporizzato per eseguire il presente servizio. L’intervallo tra due stimoli successivi è specificato nella business rule: x.y

Precondizioni:

Il timer fornisca gli stimoli ad intervalli di tempi prestabiliti.

Garanzie di fallimento:

Non viene eseguito alcun lavoro e lo stato del sistema rimane inalterato.

Garanzie di successo:

I lavori richiesti vengono eseguiti correttamente e lo stato del sistema viene aggiornato conseguentemente. Il timer fornisce lo stimolo.

Avvio: Scenario principale. 1.

SISTEMA Riceve la richiesta di esecuzione del servizio.

2.

Reperisce dalla coda dei lavori pendenti (PendingTask), quelli la cui data ed orario di esecuzione è minore della data ed orario attuale e che non siano stati precedentemente eseguiti. (Consultare modello ad oggetti del dominio).

3. 3.1.

Per ogni lavoro reperito: Punto di astrazione: Esegue il lavoro reperito in funzione della relativa tipologia.

3.2. 4.

Elimina il lavoro dalla coda. Termina lo use case correttamente.

Scenario di errore. L’esecuzione del singolo lavoro termina con errore. 3.2. Aggiorna il flag di esecuzione del task. 3.3.

Memorizza l’errore occorso in un opportuno file di log.

3.3.

Continua l’esecuzione considerando il lavoro successivo.

Come al solito, dei vari casi d’uso che specializzano quello astratto (Esecuzione lavori pendenti), per questioni di sintesi, ne viene riportato solo uno. In questo caso si è deciso di illustrare il comportamento relativo all’attivazione di un nuovo profilo.

98

Capitolo 8. Le classi nei processi

Tabella 8.11 — Template 5 CASO D’USO: Attivazione nuovo profilo. SICATTPRF Descrizione:

Durata: Priorità: Precondizioni: Garanzie di fallimento Garanzie di successo: Avvio:

Data: 10/VI/2002 Versione: 0.00.001

Il presente caso d’uso illustra la sequenza necessaria per rendere operativo il profilo specificato. In sostanza dalla coda dei lavori pendenti viene reperito un lavoro relativo a un nuovo profilo da rendere operativo (data e orario di esecuzione del lavoro coincide con i valori di data e orario di inizio validità del profilo) Secondo. Elevata. Siano disponibili i dati del task relativo all’attivazione del nuovo profilo. Non viene cambiato lo stato dell’utente e dell’eventuale profilo corrente a cui il task si riferisce Il nuovo profilo viene correttamente reso operativo Selezionato un lavoro relativo ad un profilo da rendere operativo.

Scenario principale. SISTEMA 1. Reperisce i dettagli dell’utente a cui si riferisce il nuovo profilo. 2. Verifica che l’utente non sia stato disattivato (stato utente DISATTIVATO) 3. Verifica che l’utente non si trovi nello stato di ATTESA 4. Verifica che l’utente non abbia alcun profilo nello stato OPERATIVO. 5. Reperisce il profilo da rendere operativo. 6. Aggiorna lo stato del profilo (da stato di ATTESA ad OPERATIVO) 7. Memorizza l’attività in un apposito file di log. 8. Termina lo use case correttamente.

:

Scenario alternativo. L’utente si trova nello stato di ATTESA 3.1. Aggiorna lo stato dell’utente (da ATTESA a OPERATIVO). 3.2.

Memorizza l’attività in un apposito file di log.

Scenario alternativo. L’utente dispone di un profilo OPERATIVO . 4.1. Reperisce l’attuale profilo operativo dell’utente. 4.2.

Aggiorna lo stato del profilo reperito: - Se la data di fine validità del profilo esistente precede la corrispondente del nuovo profilo (in sostanza il profilo reperito non ha più la possibilità di tornare allo stato operativo), allora ne cambia lo stato da OPERATIVO a STORICO. - Altrimenti lo stato dell’esistente profilo cambia da OPERATIVO a DORMIENTE

3.2.

Memorizza l’attività in un apposito file di log.

Scenario di errore. L’utente è stato disattivato. 2.1. Memorizza l’errore in un apposito file di log. 2.2.

Termina lo use case con insuccesso.

99

UML e ingegneria del software: dalla teoria alla pratica A questo punto, utilizzando un processo formale, sarebbe lecito attendersi il modello di analisi relativo al servizio oggetto di studio. In questo caso lo si è omesso, essenzialmente per due motivi: 1.

nei processi reali non è infrequente il caso in cui si rinunci alla fase di analisi, concentrandosi

direttamente sul disegno del sistema, per questioni di risparmio di tempo; 2.

in questo contesto, il modello di analisi è facilmente ottenibile da quelli relativi ai modelli

riportati in precedenza.

Figura 8.44 — Schema di massima dell’architettura. Business Service Layer «use» PendingTasksServiceBean

«use»

«use»

Business Object Layer

PendingTasksObjectBean

Integration Layer

«use»

PendingTaskDAO ... ...

Data Sources

PendingTaskVO ... ...

«use»

100

Capitolo 8. Le classi nei processi

L’attenzione viene focalizzata sul servizio da realizzare, mentre, per il momento, viene trascurato lo schedulatore (timer) incaricato di fornire gli stimoli (invocazioni) ad intervalli di tempo prestabiliti. L’architettura presa come riferimento è J2EE della Sun, sebbene la comprensione dell’esempio non richieda competenze specifiche. Il modello di disegno realizzato rispetta l’organizzazione a strati sancita dall’architettura stabilita nel SAD (Software Architecture Document). Dall’analisi del diagramma di fig. 8.44, è possibile notare che il modello presenta un livello di astrazione elevato ed un grado di formalità abbastanza contenuto. Ciò è dovuto a diversi fattori. In primo luogo una rappresentazione di maggior dettaglio avrebbe richiesto diverse pagine (per esempio sarebbe stato necessario modellare propriamente i componenti secondo le specifiche). In secondo luogo, nella pratica, non è infrequente ricorrere a modelli iniziali di questo tipo per fissare le idee, magari disegnandoli su una lavagna, per poi sostituirli con versioni più raffinate, in un secondo momento quando ormai si ha una certa fiducia della bontà della soluzione.

In generale, il livello di dettaglio a cui giungere è un problema tipico di quando si realizzano modelli. Spesso si tende a cadere nel tranello che un diagramma dettagliato rappresenti sempre l’alternativa migliore. Fortunatamente le cose non stanno così. Produrre modelli dettagliati non è sempre una buona idea, perché in primo luogo richiede molto tempo per realizzarli e ciò non è opportuno qualora gli obiettivi siano di iniziare a fissare le idee, esplorare diverse alternative, o illustrare delle scelte, poi, corrono il rischio di distogliere l’attenzione dagli aspetti veramente importanti. Quindi, il livello di dettaglio a cui scendere dipende dagli obiettivi che il modello intende raggiungere ed in ultima analisi dal pubblico a cui è destinato.

L’architettura per il servizio prevede due componenti (stateless EJB): quello al livello di service (PendingTasksServiceBean) e quello del livello inferiore: business object (PendingTasksObjectBean). I servizi (metodi) esposti dai due componenti a cui si è interessati nel presente contesto sono e x e c u t e S c h e d u l e d T a s k s (service), r e t r i e v e T a s k s T o E x e c u t e e executeSingleTask (object). Come da direttive architetturali, il compito del primo componente è coordinare la gestione del servizio, mentre il componente di livello inferiore ha la responsabilità di incapsulare una serie di servizi comuni a tutti gli oggetti di tipo Pending Task.

UML e ingegneria del software: dalla teoria alla pratica

101

L’attore Timer e più in generale il servizio di schedulazione si presta ad essere fornito da un opportuno sistema esterno in grado di gestire la programmazione dei vari eventi, il relativo coordinamento, la memorizzazione degli esiti, e così via. Uno stimolo dovrebbe corrispondere all’attivazione di un opportuno oggetto che si configuri come client del servizio e che quindi invochi il metodo executeScheduledTasks del service bean. Una volta ricevuto lo stimolo, il service bean si occupa di caricare in memoria tutti i pending task da eseguire (la cui data e tempo di esecuzione sia minore o uguale a quelli attuali e che non siano già stati eseguiti) e quindi di richiederne l’esecuzione. Entrambi i servizi sono forniti dall’EJB al livello di Object il quale, a sua volta, delega le operazioni ad un opportuno modulo di interfacciamento al database (DAO – Data Access Object, oggetto di accesso ai dati). Brevemente il ricorso a questi oggetti si rende necessario per sopperire ad alcuni inconvenienti (particolarmente pesanti nelle prime versioni dell’architettura EJB) tipici degli Entity EJB. Per la trasmissione dei dati relativi ai lavori pendenti si utilizza un apposita classe (PendingTaskVO) la cui responsabilità primaria è navigare i dati di pertinenza attraverso i vari strati dell’architettura. Oggetti di questo tipo vengono detti Value Object (oggetto valore). Come si può notare esiste una fortissima somiglianza tra la classe PendingTaskVO e la rispettiva al livello di modello a oggetti del dominio. Pertanto, il risultato del reperimento dei lavori pendenti dovrebbe consistere in una lista di PendingTaskVO generata dal PendingTaskDAO, così come il lavoro da eseguire (trasmesso dal componente al livello service a quello di livello inferiore) dovrebbe consistere in un’apposita istanza della classe PendingTaskVO. Nella descrizione di questo scenario, coerentemente con il diagramma precedente, sono stati omessi diversi dettagli. Per esempio, per richiedere l’esecuzione di un determinato servizio offerto da un altro EJB, un EJB deve reperirne l’interfaccia. Ciò si ottiene attraverso una ricerca in un apposito repository di tipo JNDI. Le direttive Sun prevedono di disporre di un’apposita classe atta a fornire tale servizio, denominata tipicamente Locator. Ancora il PendingTaskDAO, come si vedrà successivamente, non potrebbe da solo assolvere a tutte le proprie responsabilità ma abbisogna di comunicare con altri oggetti, e così via. L’architettura organizzata in strati offre la possibilità di effettuare una breve riflessione per quanto attiene alla gestione della transazionalità del servizio. La presenza di due EJB garantisce l’opportunità di gestire ogni pending task in maniera atomica: se l’esecuzione di un particolare task fallisce gli unici effetti da annullare (rollback) sono quelli generati dal task stesso, e non quelli generati dai task eseguiti in precedenza o da eseguire in futuro. In sostanza il service bean può prevedere un funzionamento che non richieda alcuna transazione (o al più che la supporti), mentre quello al livello di object deve prevedere la

102

Capitolo 8. Le classi nei processi

creazione di una nuova transazione ad ogni invocazione dei suoi metodi. Pertanto se l’invocazione del servizio executeSingleTask relativa all’esecuzione dell’ennesimo task fallisce, questa non influenza le precedenti (già concluse) e quelle future ancora da creare (nel caso in cui non si abbia a che fare con un errore di sistema). Tipicamente le tipologie di eccezione che possono generarsi appartengono ad una delle due grandi categorie: • business: si è in presenza di qualche problema o violazione delle regole business; • system: si è verificato un problema di tipo hardware (problemi di connessione, non disponibilità di un server, e così via). Nel caso si verifichi un’eccezione del primo tipo, può avere senso continuare a eseguire i restanti pending task, mentre nel secondo caso potrebbe non aver senso: verosimilmente lo stesso problema tenderebbe a presentarsi durante l’esecuzione degli altri lavori. Un’ultima annotazione che potrebbe valer la pena riportare è relativa alla gestione multithreading del servizio. In un’architettura non component-based, uno scenario del genere potrebbe far considerare l’eventualità di eseguire i vari pending task reperiti concorrentemente. Anche se nella maggior parte dei sistemi si ha unicamente una simulazione di concorrenza, comunque politiche di concorrenza offrono il vantaggio di sfruttare tempi di inattività (come per esempio attesa risposte da periferiche). Questa gestione potrebbe ottenersi associando ciascun pending task ad un apposito thread per la relativa gestione. Ora, tecniche di questo tipo sono tipicamente proibite in architetture component based, in quanto la gestione dei thread è interamente demandata al container. Ciò da un lato è vantaggioso: riduce il sistema da dover disegnare, minimizza il rischio di errori di programmazione (come per esempio errata gestione di situazioni di deadlock), ecc., dall’altro è svantaggioso: limita fortemente il grado di libertà del disegnatore. Una buona alternativa potrebbe ottenersi facendo sì che il componente al livello di service, una volta reperiti i pending task da eseguire, li serializzi e li spedisca a sé stesso. Un opportuno componente si dovrebbe quindi occupare di ricevere questi messaggi e di processarli. In questo modo si otterrebbe una gestione atomica (ogni messaggio ricevuto avvia una transazione a sé stante) ed asincrona dei singoli task. In questo caso si è deciso di non ricorrere a tale soluzione per non rendere incomprensibile un esempio già abbastanza complesso. Dall’analisi dello use case Esecuzione lavori pendenti è possibile notare che ciascun pending task può richiedere una delle seguenti operazioni: rendere operativo un nuovo profilo, attuare gli aggiornamenti relativi a un profilo, disattivare un profilo, sospendere un utente, rimuovere la sospensione di un utente, bloccare e sbloccare un utente. Gli architetti più esperti, analizzando il diagramma del caso d’uso e la relativa descrizione, potranno veder comparire il famoso pattern Command. In sostanza è possibile realizzare

103

UML e ingegneria del software: dalla teoria alla pratica

Figura 8.45 — Porzione di disegno relativa al pattern Command applicato al servizio di esecuzione dei lavori pendenti.

PendingTaskDAO

PendingTaskCommand

+PendingTaskDAO() +retrievePendingTasks() +manageSingleTask() ...

«use»

«create»

+executeTask() #getDataSource() #getConnection() #writeLog() #updateUserStatus() ...

DBUtility «use» «use»

LogManager

«use»

SQLGenerator

«use»

DBMappingTable

«use» «use» PendingTaskVO

«use»

DBMSVendorFactory «create»

ProfileTaskCommands +updateProfileStatus() #getUserAndProfileLocked() ...

ActiveProfileCmd

AmendProfileCmd

DeactivateProfileCmd

+executeTask() ...

+executeTask() ...

+executeTask() ...

UserTaskCommands +updateProfileStatus() #getUserAndProfileLocked() ...

LockUserCmd +executeTask() ...

DBMSVendorSpecific

UnlockUserCmd +executeTask() ...

«create»

OracleSpecific

SybaseSpecific

SuspendUserCmd

UnsuspendUserCmd

+executeTask() ...

+executeTask() ...

una gerarchia di classi, ognuna specializzata nell’eseguire uno specifico task (come riportato in fig. 8.45). Queste sono organizzate secondo una struttura gerarchica che prevede il medesimo progenitore (eventualmente un’interfaccia). Questo permette di comandare i vari oggetti in maniera indipendente dalla relativa implementazione come se fossero tutti istanze della medesima classe. Il package della riflessione di Java permette anche di ottenere una creazione elegante e flessibile dei vari oggetti, è sufficiente creare un mapping tra gli indentificatori delle varie tipologie di task e il nome completo delle classi che li gestiscono. Pertanto la classe che si occupa di eseguire gli specifici task deve reperire l’id della relativa tipologia e con questo ottenere il nome della classe da creare dinamicamente a partire dal nome: Class commandClass = Class.forName(classPath); Object newInstance = commandClass.newInstance();

Il modello di fig. 8.45 prevede che l’esecuzione dei lavori pendenti, assegnati dal componente al livello di service a quello di livello inferiore, sia realizzata da quest’ultimo in collaborazione con un’opportuna istanza della classe PendingTaskDAO. Questo si occupa di prelevare l’identificatore del lavoro e quindi reperire da un’apposita tabella il nome completo della classe da istanziare. Le possibili alternative sono: ActiveProfileCmd, AmendProfileCmd, DeactivateProfileCmd, LockUserCmd, UnlockUserCmd, SuspendUserCmd e UnsuspendUserCmd. Queste istanze, essendo discendenti della classe astratta PendingTaskCommand, possono essere considerate e quindi comandate

104

Capitolo 8. Le classi nei processi

come istanze di tale classe. Quindi l’oggetto di tipo PendingTaskDAO, una volta istanziato lo specifico comando, è in grado di invocarne il metodo executeTask(). Da notare che le specifiche del Command prescrivono che nel costruttore sia fornito l’oggetto che contiene i dati che il comando deve conoscere per poter operare. In questo caso si tratta di un’istanza di PendingTaskVO. Nel modello di fig. 8.45, la presenza della gerarchia intermedia (ProfileTaskCommands e UserTaskCommands) permette di definire una sola volta il codice utilizzato delle varie classi specializzate. Sempre dall’analisi del diagramma è possibile notare la presenza di una classe incaricata di generare dinamicamente i comandi SQL (SQLGenerator) da utilizzarsi per interagire con il data base management system. La maggior parte di questi comandi sono vendorindependent sebbene talune volte può rendersi necessario ricorrere ad alcune funzioni specifiche dei singoli vendor. Al fine di mantenere la portabilità del sistema, queste funzioni vengono incapsulate in apposite classi (OracleSpecific, SybaseSpecific, ecc.). Ancora una volta, il testo riportato nei periodi precedenti si presta a essere rappresentato attraverso un opportuno diagramma di sequenza come quello riportato in fig. 8.46.

Sebbene da un punto di vista teorico un diagramma di interazione (sequenza e collaborazione) sia in grado di illustrare casi/scenari assolutamente generici, nella pratica, non appena l’architettura comincia a superare un certo grado di complessità (presenza di un numero eccessivo di classi, di interfacce/classi astratte…), l’illustrazione del caso generico diviene praticamente impossibile. Volendo si potrebbe anche tentare di ottenere tale risultato; si correrebbe però il rischio di generare un diagramma così complesso da risultare difficilmente leggibile e praticamente inutile. Il problema viene risolto proponendo uno o più casi particolari. Sebbene si perda di genericità, questi rendono il concetto più facilmente comprensibile e con uno sforzo piuttosto marginale è possibile ricavare il caso generico.

Dall’analisi del diagramma di fig. 8.46 è possibile evidenziare alcune anomalie. In primo luogo è presente un oggetto di tipo PendingTasksCommand. Ciò costituisce una macroscopica violazione delle leggi dell’OO (come è possibile avere un’istanza di una classe astratta?). Ciò nonostante si è deciso di riportarla ugualmente al fine di evidenziare il fatto che gli specifici comandi sono visti dal sistema e quindi pilotati come se fossero effettivamente istanza della classe PendingTasksCommand. Anche per quanto concerne gli oggetti relativi agli specifici comandi si è preferito non riportare le singole istanze, bensì semplicemente evidenziare un oggetto generico a mo’ di segnaposto. Per terminare, il livello di dettaglio è ancora molto elevato: i componenti

105

UML e ingegneria del software: dalla teoria alla pratica

sono mostrati come semplici classi, non sono illustrati i meccanismi che permettono di reperire le varie “istanze” di componenti, non sono presentati gli oggetti che cooperano con gli specifici comandi per ottenere i vari servizi, ecc. Ciò è del tutto legittimo qualora ci si trovi nella fase del disegno di specifica, mentre lo sarebbe molto meno nel caso in cui si trattasse della versione di implementazione.

Chiaramente un modello di disegno non è costituito unicamente dalla proiezione statica (diagramma delle classi), bensì è necessario specificare come, a tempo di esecuzione, i vari oggetti dialogano tra loro per fornire i vari servizi. In altre parole è necessario fornire la proiezione dinamica.

CRC Cards Presentazione Una metodologia ampiamente utilizzata (soprattutto in passato) nel processo di progettazione e documentazione di modelli a oggetti è quella nota con il nome Class – Responsibility – Collaborator Cards (carte di classe – responsabilità – collaboratrici) o più semplicemente CRC Cards, il cui dominio principale di applicazione è costituito dai modelli a oggetti di analisi e disegno.

Figura 8.46 — Diagramma di sequenza relativo allo scenario principale del servizio di esecuzione dei lavori pendenti.

:Client

:PendingTasksServiceBean

executeScheduledTasks()

:PendingTasksObjectBean

tasks = retrieveTasksToExecute()

:PendingTasksDAO

tasks = retrieveTasksToExecute()

:PendingTasksCommand

executeQuery() parseResultSet()

«create»

executeSingleTask()

Generates the new transaction to manage the atomicity of the single task

:PendingTasksVO Generates an instance of the specific command capable to manage the particular typology of the task

executeListOfTasks()

executeSingleTask()

getTaskTypeId()

SpecificCommand

«create» executeTask() executeTask()

106

Capitolo 8. Le classi nei processi

Figura 8.47 — Formato delle schede CRC.

Nome della classe

102

responsabilita' ...

collaborazioni ...

152

Il nome della metodologia descrive perfettamente le caratteristiche su cui si focalizza l’attenzione. Brevemente il termine carte si riferisce ai moduli sui quali riportare le varie informazioni. Le direttive classiche ne specificano il formato e addirittura le dimensioni: 6" × 4" (152 × 102 mm). Queste sono costituite da foglietti divisi nelle tre zone: classe, responsabilità e collaborazione. Il termine responsabilità si riferisce alle informazioni che una classe possiede e i servizi che eroga, sebbene, tipicamente, è sufficiente specificare i servizi forniti (in altre parole si fa riferimento alle caratteristiche strutturali e comportamentali delle classi). Non è infrequente che una classe debba farsi carico di specifiche responsabilità per le quali però non possieda parte o tutte le informazioni necessarie. In tal caso nasce l’esigenza di delegare l’espletamento di parte dei propri servizi ad altre schede (necessita di collaborare con altre classi). Per esempio una scheda identificante uno specifico conto corrente potrebbe annoverare tra le proprie responsabilità la fornitura del servizio di generazione dell’elenco dei movimenti mensili. Sebbene la scheda debba avere conoscenza della presenza di tale lista, principalmente per questioni di coesione, non dovrebbe avere conoscenza dei dettagli della relativa gestione. Si genera quindi l’esigenza di creare una collaborazione con una specifica scheda derogata alla gestione della lista. Ciò permetterebbe alla prima scheda di ottenere, dai singoli movimenti, le varie informazioni necessarie (data, destinatario o sorgente, importo, descrizione, ecc.).

UML e ingegneria del software: dalla teoria alla pratica

107

La versione iniziale della metodologia fu congegnata da Ward Cunningham con il nome di HyperCard presso i laboratori della Apple Computer Inc. negli anni Ottanta. Inizialmente fu elaborata per risolvere problematiche legate alla necessità di documentare le decisioni prese nella fase di disegno, con particolare riferimento alle dinamiche delle collaborazioni tra le classi. Nell’arco del tempo, la metodologia CRC Card ha subito profondi cambiamenti specie con l’avvento di linguaggi di modellazione come lo UML. Quantunque quest’ultimo non sia assolutamente una metodologia, gli strumenti forniti (essenzialmente diagrammi delle classi e di interazione) hanno finito per modificare notevolmente l’applicazione della metodologia CRC che comunque mantiene intatta la propria validità sia come tecnica di brainstorming, sia come strumento di insegnamento e documentazione. Da diversi tecnici è considerata come una valida alternativa alla Robustness Analysis. In tal caso, viene utilizzato per produrre modelli di analisi “usa e getta”. Così come nel caso dei modello prodotti con al tecnica della Robustness Analysis, una volta realizzate le versioni iniziali del modello di disegno, tipicamente, non è più giustificabile l’investimento di tempo e denaro necessario per mantenere aggiornare le varie schede. Le CRC Cards si sono dimostrate molto utili anche come strumento di documentazione del codice (per molti autori questo è il ruolo cui sono state relegate dall’avvento delle nuove tecnologie). Tool quali JavaDoc possono agevolare questo tipo di utilizzo. È sufficiente infatti aggiungere, nelle sezioni di intestazione del codice delle classi (header), informazioni supplementari simili a quelle richieste da una scheda CRC (nome della classe, responsabilità, collaborazioni e così via) e quindi esportare tali informazioni in opportuni documenti e/ o pagine HTML. Il modo migliore per applicare la metodologia consiste nel cominciare a stampare tutta una serie di schede vuote (come quelle riportate nella fig. 8.47).

Spesso l’insieme delle informazioni standard viene integrato con dati aggiuntivi, come per esempio se si tratta di una classe attiva o meno, se deve persistere, e così via. Ciò non sempre è una strategia vantaggiosa. Per esempio, qualora si decidesse di utilizzare le CRC Cards per il brainstorming iniziale, probabilmente sarebbe conveniente utilizzarle nella forma essenziale senza aggiungervi ulteriori informazioni: si tratta di una fase altamente “volatile” in cui le modifiche sono molto frequenti e tipicamente di notevole impatto. Non è quindi il caso di perdere tempo a specificare informazioni supplementari che, verosimilmente, subiranno continue variazioni. In altri ambiti, per esempio quando si utilizzano a scopi documentativi, è possibile valutare l’opportunità di aggiungere ulteriori informazioni.

Una volta preparate le varie schede vuote è molto utile disporre di una lavagna bianca magnetica con dei piccoli magneti. Si è quindi pronti a cominciare. Un passo propedeutico

108

Capitolo 8. Le classi nei processi

consiste nel selezionare gli use case/scenari di cui si intende disegnare il sottosistema atto ad erogarne le funzionalità. Tipicamente si preferisce selezionare quelli che intuitivamente coinvolgeranno un numero maggiore di oggetti e/o realizzano funzionalità particolarmente importanti. Il “gioco” inizia scorrendo la lista delle azioni che compongono lo scenario. Qualora si incontrino azioni relative a servizi forniti da “schede” individuate in precedenza, o più semplicemente riferimenti ad esse, si posiziona la relativa card sulla lavagna con un apposito magnete e con un pennarello si tracciano le relazioni con le altre schede. Le schede rappresentano sia entità (e quindi classi appartenenti al modello a oggetti del dominio), sia “controller” di determinate funzionalità. Nel caso in cui nessuna scheda tra quelle individuate in precedenza risulti idonea a gestire il passo dello scenario preso in esame, è necessario iniziare a compilare una scheda vuota relativa alla nuova classe individuata, riportando nella sezione relativa al nome una prima ipotesi di denominazione. Ogni qualvolta si inizia a leggere un nuovo scenario, le prime carte da considerare tendono ad essere le stesse (coerentemente a quanto visto anche per il modello di analisi): la prima rappresenta un’istanza dell’attore che fornisce lo stimolo iniziale al caso d’uso, la seconda rappresenta l’interfaccia, ossia la classe le cui istanze ricevono gli stimoli forniti dagli attori, ne analizzano i dati, invocano i servizi necessari, ecc., la terza la classe rappresenta la gestione dell’interfaccia utente, ecc. Anche in questo caso tenere a mente l’organizzazione dell’architettura del sistema semplifica notevolmente il processo di individuazione delle classi. Il processo è intrinsecamente iterativo ed incrementale: man mano che si procede con l’analizzare nuovi scenari si identificano nuove classi e si perfezionano quelle individuate in precedenza. Gli aggiornamenti delle schede si rendono necessari al verificarsi di diverse situazioni: l’attività considerata, sebbene concettualmente appartenente ai servizi forniti da una specifica classe individuata in precedenza (criterio di coesione), non è ancora contemplata nella relativa lista delle responsabilità, oppure una scheda, al fine di espletare nuove responsabilità, necessita di utilizzare i servizi di altre non ancora inserite nell’elenco delle collaborazioni, e così via.

Terminata l’analisi di uno scenario, è importante effettuare tutti i controlli del caso sia al livello delle singole classi che lo costituiscono, sia a livello più generale del disegno. Il processo di revisione aiuta ad individuare e risolvere possibili anomalie come per esempio presenza di diverse classi con responsabilità molto simili che eventualmente vanno inglobate in una sola più generale, oppure carte poco utili e quindi eventualmente di cui fare a meno, e così via.

UML e ingegneria del software: dalla teoria alla pratica

109

Qualora ci si dovesse rendere conto che alcune schede siano superflue, invece di gettarle via, è consigliabile porle in un apposito contenitore: ciò evita, a seguito di ripensamenti, di dover riscrivere daccapo le card ricordandone tutte le responsabilità e collaborazioni o, peggio ancora, di dover condurre una ricerca nel cestino della spazzatura dell’ufficio (la voce dell’esperienza). Altri controlli sono relativi all’analisi delle responsabilità delle varie schede. Qualora si evidenzi il caso di classi con troppe responsabilità (tipico sintomo di classi a scarsa coesione) è necessario valutare se siano stati inglobati impropriamente diversi concetti ed eventualmente suddividere tali responsabilità tra più classi ad elevato grado di coesione. Altro controllo, esatto contrario del precedente, consiste nel verificare se esistono classi eccessivamente “deresponsabilizzate”. Anche se è tendenza naturale dell’OO creare una miriade di oggetti molto leggeri, non è opportuno distribuire tra vari oggetti responsabilità fortemente correlate tra loro (aumento ingiustificato dell’accoppiamento). Per quanto concerne l’analisi del modello prodotto, andrebbe eseguita sia nel contesto del caso d’uso oggetto di studio, sia in quello più generale dell’intero modello. Per il primo punto un buon approccio di verifica consiste nel variare le assunzioni iniziali dello stesso, magari ipotizzando diversi parametri di input, diversi stati di partenza del sistema, ecc. L’obiettivo consiste nel verificare che il modello individuato mantenga la propria validità con diversi scenari. In caso negativo, chiaramente, è necessario rivedere il flusso delle carte. Una verifica più creativa consiste nel tentare diversi approcci al problema (“tracciare” percorsi differenti). Spesso può capitare che percorsi inizialmente considerati non molto probabili possano poi rivelarsi soluzioni migliori. L’analisi del nuovo diagramma, nel contesto dell’intero modello di disegno, permette di verificare la consistenza, ossia l’applicazione dei principi base, il rispetto della filosofia adottata, ecc. Ogni volta sia necessario prendere delle decisioni (attività molto ricorrente in questa fase) è necessario che queste non siano limitate unicamente a considerazioni locali al caso d’uso oggetto di studio, bensì è opportuno che tengano conto degli aspetti più generali del sistema (in fondo anche per questi motivi si producono i vari modelli). Durante questo processo di revisione spesso si dà luogo ad un refactoring, introducendo opportuni oggetti e/o inglobandone altri in uno solo, al fine di migliorare l’organizzazione del disegno, rendere i modelli più stabili, riflettere la struttura a strati delle moderne architetture, e così via. Una volta raggiunto uno stadio in cui si abbia la confidenza di aver elaborato una buona ipotesi di disegno, è possibile effettuare una verifica formale del comportamento dinamico del modello stesso. Ciò può avvenire tracciando opportuni diagrammi di interazione. Ancora una volta non è necessario essere assolutamente formali; disponendo delle carte, si può utilizzare un approccio decisamente pragmatico, ma non per questo meno rigoroso, per il disegno dei diagrammi di sequenza. Si inizia collocando le carte individuate in alto sulla lavagna lungo un’ipotetica linea orizzontale le une a fianco delle altre, si traccia-

110

Capitolo 8. Le classi nei processi

no le varie linee normali che rappresentano il trascorrere del tempo e quindi si disegnano le varie interazioni (scambi di messaggi tra i vari oggetti). Da notare che l’applicazione di questa metodologia porta ad individuare esclusivamente le classi di cui esista effettiva necessità. Questo è positivo per alcuni aspetti (si disegna solo lo stretto necessario, si risparmia tempo, si evitano problemi di over-engineering, si producono modelli più semplici, ecc.), ma negativo per altri (minore flessibilità, maggiori problemi di espansione dello stesso, ecc).

Indipendentemente dalla tecnologia utilizzata, il processo di disegno del sistema richiede di prendere molte decisioni (probabilmente il numero delle scelte da operare in questa fase è superiore a quello di tutte le altre). È molto importante tenere traccia di quanto stabilito, soprattutto per agevolare la manutenibilità del sistema stesso: quando si dovranno disegnare le soluzioni per i nuovi requisiti, la documentazione delle decisioni permette di ricordare (studiare) le scelte che hanno dato luogo allo specifico disegno evitando di percorrere strade già tentate e dimostratesi non soddisfacenti, di realizzare parti del disegno che violano la filosofia del sistema, ecc.

Un’evoluzione della metodologia, consiste nel disporre di carte del “potere” (power card), ossia carte rappresentanti soluzioni generiche, eleganti, dimostratesi valide nella soluzione di altri problemi, ecc. in poche parole carte rappresentanti pattern. Per esempio si potrebbero preparare un certo numero di carte con i pattern più diffusi (composite, adapter, commander, proxy, factory, ecc.) da estrarre a mo’ di jolly ogni qualvolta se ne ravvisi la necessità. L’applicazione della metodologia classica prevede che al gioco delle carte (come da buona tradizione) prendano parte diverse figure professionali. Per esempio è possibile coinvolgere un business analyst da interpellare qualora i casi d’uso non siano chiari o si individuino delle incoerenze/lacune, uno o più architetti, programmatori, un esperto della metodologia delle CRC Cards (CRC Card Facilitator), ecc. Spesso il team viene corredato da uno o più “scrivani”, il cui obiettivo è di compilare le varie schede man mano che i vari elementi sono evidenziati. Alcuni tecnici poi suggeriscono di annettere anche degli osservatori (come per esempio tecnici junior a scopi formativi, alcuni elementi del team che si dovranno occupare dell’implementazione del sistema per renderli consapevoli, il prima possibile, dell’evoluzione dei requisiti utente, e così via). Sebbene ogni persona possa fornire ottimi spunti alla realizzazione del disegno, è necessario tenere presente che riunioni con troppe persone tendono a generare un elevato grado di entropia. Alcuni tecnici, particolarmente fautori della metodologia delle CRC, si spingono fino al punto di consigliarne l’utilizzo fin dai primi colloqui con gli utenti del sistema. Ciò do-

111

UML e ingegneria del software: dalla teoria alla pratica

Figura 8.48 — Caso d’uso Generazione nuova password.

Reperimento dettagli utente

«include»

Approvazione modulo di accesso Addetto Sicurezza

«include»

Utente

Comunica nuova password

vrebbe permettere di produrre contestualmente i casi d’uso ed il modello di analisi, in altre parole si tenta di prendere i famosi due piccioni con una sola fava… L’esperienza insegna che molto spesso si finisce per prendere due fave con un solo piccione! Qualora si convochino troppe persone in una sola riunione cercando anche di definire i casi d’uso, si finisce molto facilmente per generare una grossa confusione che raramente porta a produrre qualcosa di sensato.

Esempio Come esempio si è deciso di presentare il servizio che permette agli addetti alla sicurezza di far generare al sistema una nuova password per uno specifico utente. Questo servizio si rende necessario poiché gli utenti possono dimenticare la propria password, oppure possono venire bloccati dal sistema a seguito di tre tentativi fallimentari di impostazione della parola chiave durante la fase di log-in nel sistema, e così via. La metodologia delle carte CRC può essere utilizzata per rappresentare diversi manufatti: in tutti i casi in cui si ha a che fare con un modello a oggetti. Nel contesto di questo capitolo si è deciso di mostrarne un utilizzo per un modello di analisi, in quanto considerato un giusto compromesso: mostra diversi dettagli ma non in quantità elevata come quelli richiesti da un modello di disegno. Nelle carte riportate di seguito, spesso sono presenti dei punti di sospensione nella sezione Collaborazioni. È un artificio utilizzato per indicare che, sebbene la card illustrata collabori con altre per espletare ulteriori servizi, questi esulano dall’interesse del presente paragrafo. Le dimensioni delle card non rispettano il formato standard per questioni di impaginazione.

112

Capitolo 8. Le classi nei processi

Card 1 — ResetPasswordPage. ResetPasswordPage

Collaborazioni - ResetPasswordHandler

Responabilità Realizzare l’interfaccia utente necessaria al servizio reimpostazione password. effettuare i controlli preliminari sui dati impostati dall’utente. (Tipologia: boundary.)

Card 2 — ResetPasswordHandler ResetPasswordHandler Responsabilità Coordinare il servizio reimpostazione nuova password. : riceve le richieste dalla UI, invoca i necessari servizi e collabora con ResetPasswordPage per visualizzare i vari risultati. (Tipologia: control.)

Collaborazioni - ResetPasswordPage - SelezioneUtenteHandler - PasswordManager - NotificheManager

Card 3 — SelezioneUtenteHandler. SelezioneUtenteHandler Responsabilità - coordinare il servizio relativo alla selezione di un utente: riceve le richieste dalla UI, invoca i necessari servizi e con la collaborazione di SelezioneUtentePage mostra i risultati. (Tipologia: control.)

Collaborazioni - SelezioneUtentePage - ResetPasswordHandler - UtentiManager

113

UML e ingegneria del software: dalla teoria alla pratica

Card 4 — SelezioneUtentePage. SelezioneUtentePage

Collaborazioni - SelezioneUtenteManager

Responsabilità - Realizzare l’interfaccia utente necesaria alla funzione selezione utenti. - effettuare i controlli preliminari sui dati impostati dall’utente. (Tipologia: boundary.)

Card 5 — UtentiManager. UtentiManager Responsabilità - gestire tutte le operazioni relative ai dati utente (selezione singolo e multiplo utente, inserimento, aggiornamento, eliminazione, ecc.) (Tipologia: control.)

Collaborazioni - SelezioneUtenteHandler - Utente -…

Card 6 — Utente. Utente Responsabilità - memorizzare i dati relativi ad un singolo utente (corrisponde orientativamente ad una tupla della tabella utenti). (Tipologia: entity.)

Collaborazioni

114

Capitolo 8. Le classi nei processi

Card 7 — PasswordManager. PasswordManager Responsabilità - gestire tutte le operazioni relative ai dati delle password (selezione password, inserimento, aggiornamento, eliminazione, ecc.) (Tipologia: control.)

Collaborazioni - ResetPasswordHandler - Password -…

Card 8 — Password. Password

Collaborazioni

Responsabilità - memorizzare i dati relativi ad una singola password. (Corrisponde orientativamente ad una tupla della tabelle password) (Tipologia: entity).

Card 9 — NotificheManager. NotificheManager

Collaborazioni -…

Responsabilità - Fornire il servizio di preparazione e notifica e-mail (eventualmente crittografate) agli utenti. (Tipologia: control.)

Dall’analisi delle varie carte è possibile constatare che il modello realizzato è completamente consistente con i precedenti modelli di analisi rappresentati attraverso lo UML. Le differenze sono che le CRC cards richiedono molto più spazio e i modelli sono decisamente meno intuitivi, però offrono il vantaggio che impongono di specificare un maggior dettaglio di informazioni (dichiarazione esplicita delle responsabilità). Ciò però è ottenibile anche attraverso lo UML sia attraverso la dichiarazione dei metodi delle classi, sia prevedendo una sezione esplicita (magari visualizzabile su richiesta) in cui riportare le responsabilità.

115

UML e ingegneria del software: dalla teoria alla pratica

Figura 8.49 — Modello reimposta password ottenuto attraverso l’utilizzo delle CRC Card. NotificheManager

Password

21. page notifica servizio fornito correttamente 15. re-imposta password utente selezionato

14. Conferma

20. Notifica nuova password

13. page conderma re-imposta password utente selezionato 1. Esegue il servizio

Addetto Sicurezza

18. memorizza nuova password

16. re-imposta password utente selezionato

2. Re-imposta password

ResetPasswordPage

17. storicizza password corrente

19. nuova password utente

ResetPasswordHandler

12. dati utente selezionato

ResetPasswordManager

3. Selezione utente

9. elenco utenti 4. Pagina criteri di ricerca

SelezioneUtentePage

8. elenco utenti

SelezioneUtentiHandler

5. imposta criteri di ricerca

6. ricerca utenti

10. seleziona utente

11. dati utente selezionato

UtentiManager

6. ricerca utenti

7. elenco utenti

ResetPasswordManager

Una volta realizzate le varie carte bisognerebbe mostrarne la relativa collaborazione. Ora supponendo di disporre di una lavagna con i magneti o di una scrivania sufficientemente grande, il risultato che si otterrebbe è simile a quello di fig. 8.49 che, guarda caso, somiglia tantissimo a un diagramma di collaborazione.

Quando un modello di disegno può essere considerato ben progettato? Questo capitolo si conclude con una parte dedicata al tentativo di rispondere a quello che è uno degli interrogativi più controversi e complicati dell’intero processo di sviluppo del software: quando un modello di disegno OO può essere considerato tale e ben progettato? Si tratta di un interrogativo cui, oggettivamente, è difficile fornire una risposta completa ed univoca, probabilmente si tratta di un vero e proprio argomento da salotto o meglio ancora simposio. Ancora una volta, gli architetti esperti sono in grado di rendersi conto del livello di qualità di un modello fin dalle primissime analisi visive dello stesso. Purtroppo, si tratta di capacità soggettive che si sviluppano sia con l’esperienza nel disegno di sistemi che funzionano, sia con lo studio dei testi sacri dell’analisi e disegno OO, ma che difficilmente si prestano ad essere condensate in un trattato organico. Ciò nonostante, in queste pagine si è cercato di fare proprio ciò: fornire una serie di criteri che permettano di evidenziare errori tipici durante il disegno del sistema, di verificare la qualità degli stessi modelli e di produrne versioni di livello qualitativo elevato. Questo però, senza avere assolutamente la presunzione di essere esaustivi.

116

Capitolo 8. Le classi nei processi

Nel processo di produzione e mantenimento del modello di disegno è purtroppo spesso possibile assistere ad uno sviluppo abbastanza tipico. Le prime versioni del modello di disegno rappresentano il paradiso dell’architetto: tutto appare bello, elegante, incontaminato, flessibile, e così via. Questa iniziale visione paradisiaca in genere non riesce a sopravvivere a tutte le fasi dello sviluppo. Durante il processo si assiste ad una sua graduale corruzione che può raggiungere livelli tali da far degenerare catastroficamente l’intera architettura. Uno dei motivi principali della perdita di qualità del modello di disegno è imputabile al cambiamento dei requisiti, che magari avviene in una direzione non prevista dalla logica di funzionamento del sistema. Altre volte invece è generata da motivi piuttosto banali, quali cambiamenti effettuati troppo rapidamente senza un’opportuna mini-iterazione: revisione delle fasi di analisi e disegno, oppure variazioni realizzate da personale non familiare con il disegno del sistema e/o con i principi base (a tal proposito, l’esperienza consiglia di pianificare attentamente le proprie ferie qualora si abbia la responsabilità dell’architettura del sistema), ecc. Puntare l’indice accusatore sui clienti e relativi cambiamenti dei requisiti è la soluzione più facile, il capro espiatorio sempre valido, spiegare poi a persone intelligenti come il cambiamento dei requisiti possa aver fatto irreparabilmente degenerare il disegno è cosa meno facile. Con ciò non si vuole dire che cambiamenti, anche drastici, dei requisiti siano senza effetti, o che non posseggano la potenzialità di compromettere il disegno, né tanto meno che vadano assorbiti in poche ore o giorni di lavoro. Però se il disegno tende a distruggersi a ogni modifica dei requisiti, probabilmente la sua qualità non è eccelsa. Prima di addentrarsi nell’esame di una serie di principi utili e linee guida per la realizzazione di modelli di disegno di buona qualità, è interessante presentare alcuni sintomi che permettono di evidenziare situazioni critiche, in cui bisogna necessariamente intervenire (magari con un refactoring a livello di disegno) prima che la qualità del sistema risulti irreparabilmente compromessa. L’analisi delle situazioni di crisi è molto utile anche perché aiuta a comprendere più intimamente la necessità di realizzare modelli di disegno di qualità. Se da un lato stabilire che un modello di disegno sia ben progettato è un’attività piuttosto difficile, dall’altra verificarne la pessima qualità è molto facile.

Sintomi di una cattiva architettura Uno dei sintomi di malessere dell’architettura più semplici da individuare è la cosiddetta fragilità. Con tale termine si fa riferimento alla tendenza dei sistemi di generare malfunzionamenti di sue parti a seguito della realizzazione di aggiornamenti. Ciò che spesso avviene è che a seguito di alcune modifiche apportate in specifici settori del sistema si assiste alla perdita di funzionalità di altre parti apparentemente non relazionate con i settori modificati. È come dire che in un appartamento si modifica il sistema di antifurto ed improvvisamente non funziona più il sistema di riscaldamento.

UML e ingegneria del software: dalla teoria alla pratica

117

Il livello di qualità del sistema è avvertito intuitivamente dal personale tecnico coinvolto nello sviluppo dello stesso. L’evidenza più palese che il sistema non sia di elevata qualità è fornita proprio dal loro comportamento. Quando il sistema è fragile si assiste ad una rincorsa alle scuse e delle motivazioni tecniche più impensabili pur di evitare l’approvazione di nuove funzionalità o la rielaborazione di quelle esistenti. Il tutto è peggiorato poi da una caratteristica intrinseca della fragilità del sistema ossia auto-rigenerazione: ogni iterazione e ogni ondata di cambiamenti tende a rendere un sistema fragile ancora più fragile, ad introdurre nuovi problemi e a degradarne il controllo.

Una volta individuata una condizione del genere è opportuno inserire nel processo di sviluppo del software una o più iterazioni (a seconda della quantità di entropia generata) volte a rivedere l’architettura senza aggiungere nuove funzionalità. Altamente sconsigliato è invece procedere disseminando artifici temporanei — leggasi toppe — nella speranza che alla fine il tutto magicamente funzioni. Architetture di questo tipo tendono a sprofondare all’occorrenza dei primi problemi e/o richieste di far evolvere il sistema.

Un’anomalia strettamente connessa con la precedente è quella che va sotto il nome di rigidità; con tale termine si indica la resistenza del sistema ai cambiamenti. Chiaramente se ogni cambiamento corre il rischio di distruggere il sistema in parti, questo è fragile e rigido. Come nel caso precedente, ogni aggiornamento apportato al sistema, anche quelli che apparentemente non nascondono alcuna insidia, generano tutta una rincorsa alla stabilizzazione dello stesso. Situazioni del genere, tipicamente, sono evidenziate da periodi prolungati necessari per rendere il sistema stabile. Un’altra anomalia è nota con il nome di immobilità; si tratta del contrario della riusabilità. Evidenzia situazioni in cui risulta difficile riutilizzare parti dello stesso sistema o di altri realizzati in precedenza. Spesso accade di dover realizzare componenti o comunque parti del sistema con funzionalità simili, o addirittura uguali, a quelli implementati per un altro in precedenza e di tentare, legittimamente, di riutilizzare le stesse parti di codice. Tutto ciò, abbastanza naturale dal punto di vista teorico, può risultare molto complicato in pratica. Ciò perché il processo di estrapolazione delle parti da riutilizzare è così lungo, complesso e rischioso (eccessive interdipendenze) che in sostanza, per ridurre il tempo necessario ed il fattore di rischio, diviene più conveniente dar luogo alla riscrittura delle varie parti. Un ulteriore problema è legato all’evoluzione incoerente del disegno che finisce per rendere lo stesso rigido e, frequentemente, fragile. Quando un architetto inizia a disegnare il sistema o sue parti, utilizza determinati principi, fissa delle colonne portanti dell’ar-

118

Capitolo 8. Le classi nei processi

chitettura del sistema, e così via: tutto appare perfetto. A seguito poi di aggiunta di nuove funzionalità e/o correzioni, può accadere che le nuove parti violino i princìpi base e/o le regole stabilite in partenza dall’architetto (è pur vero che ci sono architetti che anche quando costruiscono nella propria mente soluzioni riescono a farlo in maniera errata). Questi problemi sono particolarmente frequenti quando vi è un avvicendamento dell’architetto oppure durante una sua assenza. Il problema di fondo risiede nel fatto che una stessa modifica si presta ad essere ottenuta adottando molteplici approcci. Spesso diversi di questi risultano legittimi, ma non tutti coerenti con le scelte di base, mentre alcuni andrebbero assolutamente evitati (si tratta di soluzioni a corto respiro inserite al volo e bollate come “temporaneamente permanenti”). Questa tipologia di soluzioni può risultare diabolicamente attraente: tende ad apparire più facile e, solo nell’immediato, più veloce da implementare. Ciò fornisce un’attrattiva molto allettante. Sfortunatamente, l’introduzione di soluzioni temporanee di questo tipo, finisce per rendere il sistema rigido e fragile. Si tratta di soluzioni a corto respiro, tipicamente non flessibili, da dover risistemare nell’immediato futuro e pronte ad evidenziare tutte le loro debolezze ai primi cambiamenti. Altre volte può capitare che sia veramente necessario adottare delle soluzioni contrarie alla filosofia di base del sistema o di suoi moduli. In questi casi è forse opportuno riesaminare le scelte di fondo e rivedere il disegno prima di lanciarsi nell’attuazione di soluzioni a corto respiro. Di anomalie se ne potrebbero individuare diverse, sebbene queste siano quelle più ricorrenti. Qualora dovesse capitare di trovarsi di fronte ad un sistema ormai irrimediabilmente corrotto è sempre possibile citare una serie di giustificazioni come: i requisiti continuavano a cambiare, il tempo a disposizione non era sufficiente, ed altre più o meno risibili.

Criteri Buona qualità degli elementi Il primo criterio da considerare scaturisce da un ragionamento logico abbastanza semplice: se le singole parti componenti di un sistema sono di buona qualità, allora verosimilmente è più facile che l’intero sistema sia di qualità. Chiaramente sono possibili deviazioni in entrambe le direzioni: alcuni componenti non perfettamente disegnati e sistemi ancora di buon livello qualitativo e viceversa componenti ben disegnati e sistema scadente. Un buon paragone può essere attinto dalla gastronomia. Alcune volte pur disponendo di ingredienti di primissima qualità è possibile cucinare una pietanza orribile, e caso inverso, pur non disponendo di tutti gli ingredienti migliori si riesce comunque a cucinare portate edibili. Gli elementi base su cui si focalizza l’attenzione nei modelli di disegno sono: le classi, i package ed i componenti. I criteri per verificare la qualità di tali elementi sono stati ab-

UML e ingegneria del software: dalla teoria alla pratica

119

bondantemente analizzati nel paragrafo “Classi ben disegnate” del Capitolo 6 (massima coesione, minimo accoppiamento, riusabilità, ecc.). Non bisogna essere eccessivamente rigidi su questo principio: spesso la qualità del disegno di alcune classi viene intenzionalmente sacrificata a favore della qualità del sistema generale. Un esempio evidente (se ne potrebbero trovare diversi altri) è fornito dall’applicazione del design pattern Command [BIB04]. Si tratta di un disegno molto potente, elegante, versatile (un esempio di applicazione è presentato nella fig. 8.45), ma se si analizzassero, utilizzando criteri prettamente accademici, le classi che specializzano quella generale, si potrebbe notare come queste siano create intorno ad una singola funzione e non ad un vero e proprio “oggetto”. Ciò non dovrebbe costituire di per sé un buon principio OO, eppure si tratta, nel complesso, di un’eccellente soluzione. Chiaramente, qualora si tratti di qualche classe non perfettamente disegnata, è possibile sorvolare, quando invece gran parte o tutte le classi sono disegnate male, o alcune sono veramente pessime, allora è inutile procedere nell’analisi dei rimanenti criteri: il modello non va (per quanto si possa essere maghi dei fornelli, con tanti ingredienti marci ci si fa ben poco). Un altro elemento cui spesso non si attribuisce la dovuta importanza è l’organizzazione in package del sistema. Ciò è molto importante poiché, tipicamente, il riutilizzo delle parti di rado avviene a livello delle singole classi: in genere si cerca di riutilizzare interi package. Errate organizzazione producono svariati problemi: difficoltà di verificare (testare) le singole parti, immobilità del sistema (difficoltà sia di riutilizzarne delle parti, sia di incorporarne altre provenienti da sistemi sviluppati in precedenza), difficoltà di manutenzione, e così via. Un buon criterio da seguire per strutturare i package deriva dai principi classici: massima coesione e minimo accoppiamento. Le classi inserite nello stesso package devo risulta-

Figura 8.50 — Esempio di organizzazione in package di una parte del sistema in cui l’accoppiamento interno non è minimo, però lo è quello tra package.

120

Capitolo 8. Le classi nei processi

re “coese” tra loro ed avere un accoppiamento tale da rendere il package stesso scarsamente accoppiato con i restanti. In altre parole si accetta che le classi presenti in uno stesso package possano presentare un livello non minimo di accoppiamento, purché sia invece minimo quello con le classi appartenenti a un altro package (fig. 8.50). Questo criterio assicura, tra l’altro, che i cambiamenti apportati a un package tendano a essere circoscritti allo stesso. Chiaramente prima di accettare un livello di accoppiamento come quello mostrato in fig. 8.50 bisognerebbe verificare attentamente se ciò sia veramente necessario o se sia esclusivamente conseguenza di un disegno non accurato.

Qualora ci si trovi nella prima ipotesi (forte accoppiamento non riducibile), una buona norma è utilizzare il pattern denominato Façade [BIB04]. Non si tratta di un design pattern con struttura e dinamiche ben definite, è più che altro una norma di buon disegno. L’obiettivo di questo pattern consiste nell’evitare i problemi tipici generati da un forte accoppiamento degli elementi interni ad un package: difficoltà di utilizzo da parte di classi clienti (devono essere consapevoli dell’organizzazione interna), ripetizione del codice necessario all’utilizzo dei servizi esposti dal package nelle varie classi client, e così via. In poche parole il sistema corre il rischio di diventare fragile (è facile commettere errori nell’utilizzo dei servizi, una modifica alla struttura del package si ripercuote su classi clienti, ecc.). Al fine di evitare questi ed altri problemi si ricorre all’utilizzo del pattern Façade, il quale prescrive di introdurre una classe di interfacciamento (una “facciata” appunto, fig. 8.51), che si configuri come unico “punto di accesso” al package, nascondendone i dettagli interni. Ciò permette di evitare che le classi clienti siano consapevoli della struttura interna del package, evita di dover specificare la logica di utilizzo dei relativi servizi in svariate classi clienti e quindi di doverle modificare come conseguenza dell’aggiornamento della struttura del package, ne semplifica l’utilizzo ecc.

Sempre nell’organizzazione del codice in package è molto importante evitare dipendenze cicliche: il PackageC dipende dal PackageB che a sua volta dipende dal PackageA che dipende dal PackageC. Questo scenario è da evitare per una serie di motivi: tende ad aumentare l’area in cui eventuali cambiamenti fanno risentire i propri effetti, rende più difficile verificare il sistema, limita la riusabilità del codice, ecc. Pertanto in tutti i casi in cui sia presente una dipendenza ciclica dei package è necessario andare a interrompere il ciclo vizioso.

121

UML e ingegneria del software: dalla teoria alla pratica

Figura 8.51 — Utilizzo del design pattern Façade.

Façade

Corretto utilizzo della ereditarietà Un problema che spesso si incontra nei modelli OO è legato ad un utilizzo non corretto della relazione di ereditarietà. L’analisi degli equivoci dell’utilizzo della relazione di ereditarietà è stato affrontato dettagliatamente nel paragrafo “I tranelli dell’ereditarietà” presente nel Capitolo 6. In sintesi, non è infrequente che alcuni disegnatori, constatando che alcune classi abbiano qualche parametro o responsabilità in comune, decidano di organizzare queste classi attraverso una struttura gerarchica. A volte si tratta di una soluzione corretta (si vuole effettivamente ereditare il tipo), mentre altre no. Sebbene l’ereditarietà sia un principio base dell’OO, l’utilizzo travisato può generare diversi effetti collaterali (riportati nel Capitolo 6): • la relazione tra le classi è definita a tempo di disegno e non può variare; • vi è un consistente accoppiamento tra classi genitore e classi discendenti, riduzione dell’incapsulamento, ecc.

Presenza e corretta applicazione dei pattern L’applicazione dei pattern nel modello di disegno è un fattore che ne favorisce l’aumento della qualità. Gli stessi disegnatori esperti tendono a sentirsi più a loro agio qualora siano incaricati di revisionare modelli di disegno in cui siano applicati correttamente i design pattern. Ciò perché si tratta di soluzioni ben provate che sicuramente offrono un elevato grado di flessibilità, eleganza, funzionalità. Come ogni cosa però, qualora utilizzata in eccesso, o peggio ancora in modo errato, corre il rischio di generare una serie di problemi. Per esempio, utilizzi ingiustificati tendono a generare modelli over-engineered, ossia inutilmente complessi, difficili da comprendere e mantenere, ecc. I design pattern,

122

Capitolo 8. Le classi nei processi

purtroppo, sono oggetto di una corrente di abuso. Nominarli in un discorso o in un articolo (anche a sproposito) sembrerebbe accrescere il livello di professionalità dei vari soggetti. Ci sono persone che vorrebbero trasformare tutto in un design pattern. Per esempio poiché uno dei principi base dell’OO sancisce che gli attributi delle classi debbano possedere visibilità privata (incapsulamento, information hiding, …), ecco creato il “pattern degli attributi privati”. Pur con questi casi eccezionali, la presenza di design patter nei modelli di disegno tende ad essere un elemento di qualità in grado di calmare le ansie del revisore dello stesso modello.

Interfacce coese Questo principio può essere considerato l’applicazione alle interfacce delle leggi della massima coesione e del minimo accoppiamento. In particolare si vuole evitare una serie di problemi derivanti dal disporre di interfacce gravate da un’eccessiva quantità di responsabilità; in altre parole poco coese. Qualora si riconosca una situazione del genere, è opportuno procedere alla decomposizione dell’interfaccia in una serie di interfacce più semplici e con maggiore grado di coesione. In questo modo, ciascuna interfaccia raggruppa un insieme coeso di servizio utilizzato da un ben definito gruppo di classi client. Chiaramente può capitare l’evenienza di dover disegnare una stessa classe che debba fornire determinati servizi specificati in diverse interfacce. In questo caso è buona norma che le classi client percepiscano le istanze di tale classe attraverso le sue interfacce, piuttosto che per mezzo di una sola interfaccia scarsamente coesa. Ciò migliora la qualità del sistema (è più facile individuare le responsabilità delle varie classi implementanti), permette un riutilizzo più agevole delle parti, diminuisce il livello di fragilità. Un’anomalia generata dalle interfacce poco coese prende il nome di “inquinamento dell’interfaccia”. Questa si genera qualora una specifica classe che implementa una determinata interfaccia richieda di essere comandata o di fornire servizi aggiuntivi rispetto a quelli definiti da tale interfaccia. Spesso, trovandosi di fronte a tale situazione, si procede inserendo nell’interfaccia i metodi aggiuntivi necessari anche se poi questi non possiedono alcuna coesione con i restanti servizi esposti dall’interfaccia stessa. Pertanto, al fine di soddisfare le esigenze di una o poche sottoclassi, si finisce per inquinare l’interfaccia comune. A questo punto tutte le sottoclassi devono necessariamente (a meno di non essere dichiarate astratte) fornire l’implementazione anche dei servizi non utilizzati. Questo causa una serie di problemi come, per esempio, rendere più difficoltosa la riusabilità e la comprensibilità del disegno, aumentare il grado di fragilità del sistema (un’eventuale modifica della definizione dei servizi dell’interfaccia, anche di quelli non implementati dalle varie sottoclassi, comunque genera la necessità di modificare tutte le sottoclassi).

Open-Close: un principio facile facile, ma difficile difficile Uno dei princìpi più famosi del disegno di sistemi OO (e non solo) è attribuibile al solito Bertrand Mayer (l’ideatore del Design By Contract analizzato nel Capitolo 6). Pur

UML e ingegneria del software: dalla teoria alla pratica

123

non essendo di recente coniazione (risale al paleolitico periodo di fine degli anni Ottanta) mantiene intatta la propria validità. Si tratta di un principio che, dopo una riflessione iniziale, appare semplice semplice, quasi lapalissiano, la cui applicazione pratica però non lo è altrettanto. Il principio afferma che: “I componenti del sistema (classi, package, entità, componenti veri e propri, …) devono essere aperti a estensioni, ma chiusi alle modifiche”, da cui il nome Open-Close, aperto-chiuso. Da notare che affinché un componente sia chiuso alle modifiche è necessario che il relativo codice non sia modificabile. Sebbene in prima lettura ciò possa sembrare quasi un controsenso — il comportamento delle componenti deve poter essere modificato senza riscriverle! — un’analisi più attenta ne rivela la raffinatezza. Si proceda con ordine. Per quanto concerne la prima parte, dovrebbe essere ormai chiaro che è insito nel destino dei sistemi cambiare ed evolvere durante l’intero ciclo di vita: non è possibile congelare i requisiti utente e quindi bisogna disegnare il sistema in modo da riuscire, in qualche misura, ad anticipare le variazioni o comunque renderle più indolori possibile. In termini più pratici, si richiede di poter modificare agevolmente il comportamento di determinati componenti al fine di adeguarli a nuovi requisiti, a nuove applicazioni, e così via. Quando poi si ha bisogno di modificare una parte del sistema, si vuole che gli effetti della variazione siano il più possibile circoscritti e non si propaghino in altre parti di codice funzionante. Qualora ciò non avvenga, evidentemente si ha a che fare con un pessimo disegno e probabilmente si è nel caso di una cattiva architettura. Per quanto concerne la seconda parte, essa sancisce che si desidera aggiornare il comportamento del sistema senza dover cambiare le parti esistenti. Questo criterio è anche noto come principio base dei framework. In questi sistemi si desidera avere una parte assolutamente non variabile (il core) e tutta una serie di meccanismi che permettano di adattare il comportamento dell’infrastruttura alle specifiche realtà in cui viene utilizzato. Il punto chiave per ottenere questo comportamento consiste nell’utilizzare un principio base dell’OO: il polimorfismo realizzato per mezzo di superclassi astratte (e/o interfacce). Ciò permette di definire un’astrazione di un concetto (quello della classe padre astratta) ed una serie di diversi comportamenti definiti dalle sottoclassi (come nel caso del design pattern Command). In questo modo il principio dell’apertura/ chiusura è rispettato: la classe genitore è chiusa alle modifiche e la relativa estendibilità è garantita dalla possibilità si aggiungere sottoclassi che realizzino i nuovi comportamenti specializzati desiderati. Ora, ipotizzare di riuscire a chiudere tutto il sistema è impensabile. Per quanto si tentasse di chiudere un componente, esisterebbero sempre tipologie di variazioni contro le quali il componente risulti non chiuso (ciò è consistente con il primo principio di Murphy). Ciò nonostante una chiusura parziale ben mirata può migliorare notevolmente il disegno. Inoltre (come riportato nel paragrafo successivo), i meccanismi dell’astrazione, per divenire pienamente operativi, oltre al polimorfismo ed alle classi astratte, necessitano di co-

124

Capitolo 8. Le classi nei processi

struire un’infrastruttura in grado di selezionare (ad un istante di tempo zero o anche dinamicamente) le specifiche estensione da eseguire (classi specializzanti). A questo scopo si utilizzano design pattern quali il Factory (fig. 8.52). Tutto ciò chiaramente ha un costo, sia in termini di aumento del tempo necessario per il disegno/implementazione, sia di complicazione della configurazione, sia della complessità del sistema stesso. Bisogna anche ricordare che la flessibilità, sebbene sia una proprietà molto ricercata, qualora ecceda determinati limiti, può trasformarsi in un problema. Con riferimento al modello di disegno di fig. 8.45, si può notare che il sistema prevede una serie di comandi SQL specifici per i vari vendor di data base management system. Si vuole chiaramente che il sistema continui a funzionare variando il software di gestione della base dati. A tal scopo è stato applicato una variante del design pattern Factory dato dalle classi DBMSVendorFactory, OracleSpecific, SybaseSpecific, ecc. Ora, quando il generatore di comandi SQL deve generare clausole dipendenti dallo specifico vendor, richiede al factory l’istanza opportuna contenente tali comandi. Il factory può generare una nuova istanza ad ogni richiesta, oppure generarne un’unica all’atto dell’avvio del sistema. Come riesce però a stabilire quale classe specifica costruire? La soluzione tipicamente consiste nel leggere tale informazione da un opportuno file di configurazione all’atto dell’avvio del sistema. Data l’impossibilità/impraticabilità di “chiudere” l’intero sistema diventa di cruciale importanza riuscire ad individuare le porzioni del sistema la cui chiusura porterebbe risultare molto vantaggiosa. A questo punto il pubblico dei lettori odierà a morte lo scrittore, ma ancora una volta, architetti e disegnatori esperti, illuminati dalla relativa esperienza, riescono ad individuare intuitivamente le aree più “sensibili”, basandosi sia sulla rela-

Figura 8.52 — Factory per l’istanziazione dell’ opportuno oggetto DBMS.

SQLGenerator

«use»

DBMSVendorFactory «create»

OracleSpecific

«create» SybaseSpecific

«use»

DBMSVendorSpecific

125

UML e ingegneria del software: dalla teoria alla pratica

Figura 8.53 — Errato utilizzo del meccanismo dell’astrazione.

SQLGenerator

«use»

DBMSVendorSpecific

«create»

OracleSpecific

SybaseSpecific

«create»

tiva conoscenza dello stato dell’arte della tecnologia, sia sulla propria intuizione in merito ai requisiti. Per tutti gli altri non c’è troppo da disperare: esistono una serie di criteri e strategie che possono risultare molto utili. Per esempio, è opportuno cercare di applicare il criterio nei casi in cui un componente utilizzi un software o libreria fornita da altri vendor, oppure presenti delle parti specifiche di una determinata tecnologia (come nel caso delle basi dati) e quando la stessa è soggetta ad evoluzioni. Ancora è utile nei casi in cui si ha la sensazione che i requisiti utente possano variare, oppure nei casi in cui è prevista la possibilità di specializzare il comportamento per casi non ancora contemplati.

Utilizzo delle astrazioni Un altro criterio molto importante da seguire nella realizzazione del modello di disegno, implicitamente presente nei precedenti, è relativo all’utilizzo dei meccanismi dell’astrazione. Si tratta di un buon principio far sì che il disegno (e quindi il codice) dipenda il più possibile da opportune astrazioni piuttosto che da comportamenti concreti. Il criterio quindi suggerisce di affrontare la complessità del sistema conferendo maggiore enfasi alle caratteristiche essenziali dello stesso a spese dei dettagli implementativi. L’idea alla base è abbastanza semplice: la frequenza con cui variano corpi (concreti) è decisamente maggiore della frequenza con cui cambiano le relative astrazioni. Come al solito, in sistemi reali è improponibile tentare di far in modo che ogni cosa dipenda da apposite astrazioni (consultare il paragrafo del principio dell’Open-Close). Inserire classi astratte ed interfacce è la classica condizione necessaria ma non sufficiente per garantire la dipendenza dalle astrazioni. Uno degli errori più classici che l’autore ha visto commettere nei modelli di disegno consiste nell’applicare correttamente le astrazioni, dimenticando però di risolvere il problema della selezione (o creazione) degli oggetti. In

126

Capitolo 8. Le classi nei processi

altre parole, si disegna un’opportuna interfaccia o classe astratta rappresentante l’astrazione, la si implementa/specializza attraverso un opportuno insieme di classi e poi si commette l’errore di far diventare il sistema dipendente dalle classi concrete anziché dalle relative astrazioni annullando tutti i vantaggi offerti dal ricorso a tale tecnica. Ciò è dovuto al fatto che classi astratte e interfacce non possono essere istanziate direttamente. A tal fine si consideri l’esempio di fig. 8.53. La realizzazione dell’interfaccia e delle classi specifiche è corretta, così come è corretta la dipendenza (use) della classe SQLGenerator dall’interfaccia. Il problema è poi che quest’ultima classe genera le istanze delle classi che implementano l’interfaccia (situazione evidenziata dalla presenza degli stereotipi create della relazione di dipendenza). Lo stesso problema si ripropone quando si introduce correttamente un Factory, nel quale però viene codificata (hard-coded) la conoscenza delle classi specializzate. In quest’ultimo caso si tratta comunque di un modello migliore — nel caso in cui si aggiunga, modifichi o elimini una classe specializzata è sufficiente modificare una sola classe — ma facilmente migliorabile. Per esempio è possibile prevedere un file di inizializzazione da caricare in memoria all’atto dell’avvio del sistema che contenga la conoscenza delle varie classi specializzate presenti, oppure si può utilizzare il pattern del Proxy Dinamico. Questa nuova flessibilità — in caso in cui varino le classi specializzate è sufficiente modificare un file di configurazione e nessuna parte del codice — ha un prezzo che consiste nel prevedere e documentare il file di configurazione, realizzare un modulo per il relativo caricamento iniziale, meccanismi astratti di creazione delle specifiche interfacce.

Criteri generali Oltre ai criteri citati in precedenza, ne esistono tutta una serie di carattere generale che probabilmente è opportuno tenere in considerazione. Per esempio è opportuno tendere sempre alla semplicità. Un disegno semplice evidentemente è più facile da comprendere, estendere e riutilizzare. Quindi, anche nella modellazione la semplicità è una virtù che, paradossalmente, è molto difficile da raggiungere. Un altro buon principio da seguire è quello che prende il nome di decentramento. Si tratta di una prescrizione intrinseca nelle moderne architetture component-based. Maggiore è il grado di autonomia dei singoli moduli e maggiore è la probabilità di circoscrivere gli effetti di una variazione. Inoltre risulta più semplice reingegnerizzare le varie componenti, riutilizzarle, ecc.

Conclusioni Nei precedenti paragrafi si è tentato di evidenziare una serie di errori che tipicamente si commettono nella realizzazione dei modelli di disegno, e di fornire un insieme di linee guida da utilizzarsi per verificarne la qualità. Pur nella consapevolezza di non aver fornito un elenco esauriente (probabilmente non

UML e ingegneria del software: dalla teoria alla pratica

127

basterebbe un intero volume dedicato all’argomento), si è cercato di condensare i criteri utilizzati dall’autore del libro sia per realizzare i modelli di disegno, sia per valutarne la relativa qualità. Sebbene l’esperienza giochi ancora un ruolo molto importante, le nuove tecnologie, come per esempio l’architettura EJB, rendono il lavoro meno gravoso: gran parte dell’infrastruttura e dei pattern da utilizzare sono predefiniti e quindi commettere errori gravi è oggettivamente meno facile e richiede un certo impegno.

Caratteristiche di un sistema Come degna e definitiva conclusione del capitolo si è ritenuto opportuno riportare brevemente le caratteristiche che un software deve possedere per poter essere considerato di buona qualità: • correttezza. È la capacità del sistema di eseguire correttamente i servizi specificati nei documenti di analisi dei requisiti (use case). Si tratta del requisito fondamentale: se un sistema non è in grado di fornire i servizi richiesti, tutte le altre qualità perdono di significato. Quantunque di primaria importanza, la correttezza sembrerebbe un requisito quasi impossibile da ottenere; • robustezza. È la capacità del sistema di riconoscere e gestire opportunamente situazioni anomale. La robustezza è il logico completamento della correttezza; • estendibilità. Un sistema è estendibile quando è in grado di assorbire facilmente cambiamenti dei requisiti; • riusabilità. È la capacità di integrare facilmente componenti provenienti da altri sistemi e di permettere il riutilizzo di proprie parti in altri sistemi; • efficienza. È la capacità del sistema di eseguire i servizi richiesti dando luogo ad un utilizzo minimo delle risorse (tempo, memoria, canali di comunicazione, ecc.); • facilità d’uso. Consiste nella facilità di utilizzo del sistema da parte del pubblico degli utenti (pubblico che, tipicamente, contempla persone con diverse qualifiche e diverso livello culturale); • compatibilità. Si tratta della facilità di combinare diversi elementi software tra loro; • facilità di gestione. Consiste nella facilità di installare il sistema, controllarne il funzionamento ed intervenire per renderlo nuovamente operativo qualora si verifichi un’interruzione del funzionamento.

128

Capitolo 8. Le classi nei processi

• portabilità. È la facilità con cui il sistema può essere trasportato in diversi ambienti hardware e software. • sicurezza. Capacità del sistema di neutralizzare tentativi di utilizzo e di acquisizione di informazioni da parte di utenti non autorizzati; • adeguata documentazione. Disponibilità di un buon livello di documentazione del sistema, realizzata con strumenti standard.

Ricapitolando Il presente capitolo è dedicato all’illustrazione dell’utilizzo del formalismo dei diagrammi delle classi nel contesto dei processi di sviluppo del software (modelli a oggetti del dominio, del business, di analisi e di disegno). A tal fine sono stati presi in considerazione i processi più formali. Un primo manufatto di estrema importanza nei processi di sviluppo del software (prodotto durante la fase di analisi dei requisiti utente) è costituito dal modello a oggetti del dominio (Domain Object Model), detto anche modello del dominio. Come suggerito dal nome, rappresenta un modello che ha a che fare con entità “realmente esistenti” nell’area oggetto di studio che il sistema software dovrà automatizzare. Uno degli scopi primari del modello è contribuire alla piena comprensione del contesto in cui il sistema dovrà operare (spazio del problema) focalizzando l’attenzione sulla relativa struttura statica. Non è infrequente riferirsi allo spazio del dominio del problema con l’espressione “mondo concettuale”, in quanto, generalmente, sono presenti “oggetti” che, pur non esistendo apertamente nel mondo reale, da esso “traspirano” oppure possono essere visti come derivati da specifici oggetti attraverso lo studio delle strutture e dei comportamenti. Il modello a oggetti del domino è un modello statico dello spazio del problema che ne rappresenta un’astrazione in termini delle varie entità presenti corredate dalle relative interconnessioni. Le entità individuate sono ottime candidate di classi persistenti: sono le astrazioni dei dati trattati dal sistema. Secondo le conclusioni dei Tres Amigos [BIB08], le entità presenti nel modello del dominio dovrebbero appartenere essenzialmente alle seguenti categorie: a. entità “business”, ossia oggetti rappresentanti entità (più o meno tangibili) manipolati nell’area business oggetto di studio; b. oggetti e concetti appartenenti al mondo concettuale che il sistema necessita di manipolare; c. eventi che possono verificarsi. Una caratteristica tipica dei modelli a oggetti del dominio consiste nello specificare poche o nessuna operazione nelle varie classi. Verosimilmente, è opportuno rimandare la definizione delle proprietà

UML e ingegneria del software: dalla teoria alla pratica

129

comportamentali delle classi a fasi più mature del processo di sviluppo nelle quali si ha una maggiore comprensione del dominio del problema. In questo stadio si è interessati essenzialmente all’organizzazione dei dati e quindi, al livello di classi, alla relativa struttura (attributi e relazioni con altre classi). Il modello a oggetti del dominio, una volta terminato, rappresenta un’asserzione dello spazio del problema o, se si preferisce, il compendio delle regole di business relative all’organizzazione dei dati manipolati. Il DOM è sia un manufatto assolutamente necessario allo sviluppo del sistema, sia un prodotto finale. Si presta ad essere utilizzato per eventuali future reingegnerizzazioni (tipicamente la tecnologia è molto variabile, il business no), per la progettazione di sottosistemi aggiuntivi, per l’illustrazione del business magari a nuovi dipendenti, ecc. La realizzazione del modello a oggetti del dominio è un’attività complessa fortemente dipendente da quelle che sono proprietà soggettive del singolo individuo come cultura, capacità analitiche, esperienza. Ciò determina la difficoltà di formalizzare un metodo rigoroso e meccanico. La tecnica proposta nel testo è il procedimento denominato metodo dell’analisi dei nomi e dei verbi. Esso è composto essenzialmente da tre attività: individuazione dell’elenco delle ipotetiche classi, scorporazione dell’elenco e individuazione delle relazioni. Il primo passo da seguire è un’attività non eccessivamente complessa, quasi meccanica. Più precisamente è necessario eseguire una sorta di analisi grammaticale. Si legge attentamente il documento dei requisiti (use case, appunti presi durante i colloqui con gli utenti, documentazione varia, ecc.), e si evidenziano tutti i nomi e coppie di nomi/aggettivi incontrati. Questi dovrebbero confluire in un’apposita tabella in cui si riportano, per ogni candidata classe, il relativo nome ed una breve descrizione. Il passo successivo consiste nell’operare una cernita dell’elenco al fine di eliminare le classi candidate che sono fuori scope, oppure che non hanno le caratteristiche delle classi, ecc. La prima domanda da porsi è se la classe appartiene o meno al dominio oggetto di studio. Una risposta negativa non significa necessariamente che l’entità che si sta analizzando non sia una classe, ma semplicemente che non è il caso di spendere tempo ad investigare, a studiarne la struttura ed il comportamento giacché comunque non è di interesse per il modello. La seconda domanda è relativa alle presunte istanze della classe, ed in particolare ai valori che queste potrebbero assumere in un ipotetico caso reale. Se ci si dovesse accorgere che le istanze della classe oggetto di verifica possano assumere unicamente valori semplici, verosimilmente non è il caso di considerarla tale, eventualmente si può trattare di un attributo. Il punto successivo, che a dir il vero risulta intimamente legato al precedente, è relativo all’“analisi” dell’eventuale struttura dell’ipotetica classe. In altre parole, è necessario chiedersi se è possibile identificare una qualche struttura in termini di attributi, relazioni, ecc. In caso di risposta affermativa allora potrebbe avere senso considerare la classe come tale, altrimenti si ricade nel caso di aver individuato un potenziale attributo. Dopo essersi interrogati circa la struttura della classe, è possibile spostare l’attenzione sulle eventuali caratteristiche comportamentali. Qualora sia possibile identificare caratteristiche comportamentali, allora sicuramente è possibile proseguire con la valutazione dei criteri successivi, in caso contrario, la situazione nasconde diverse insidie. Quando si elabora un modello a oggetti del dominio, si focalizza l’attenzione sui dati e sulla relativa organizzazione, mentre il comportamento assume un interesse marginale. Un ulteriore elemento da considerare è se l’ipotetica classe possegga una propria indipendenza oppure abbia senso esclusivamente nel contesto di una più ampia. Ciò equivale ad interrogarsi se l’ipotetica “classe” sia un nuovo tipo di dato con un insieme ben definito e separato di operazioni, oppure se queste possono naturalmente

130

Capitolo 8. Le classi nei processi

confluire tra quelle fornite da un’altra classe già accreditata. In caso di risposta affermativa è necessario rivedere la definizione della classe inglobante ed eliminare quella oggetto di verifica. Le classi ottenute dall’esecuzione dei passi precedenti avrebbero ben poco senso se destinate a rimanere isolate. La situazione normale, al contrario, prevede che ciascuna di esse collabori con le altre al fine di erogare i servizi offerti dal sistema. L’obiettivo delle primissime fasi dei processi di sviluppo dovrebbe essere quello di modellare, in modo più semplice possibile, lo spazio del problema e non quello delle soluzioni. In questo ambito, lo scopo fondamentale dei modelli è comunicare e documentare. Pertanto è necessario tenere a mente che molti utenti ed esperti del business dispongono di una limitata cultura informatica, è quindi consigliabile rimandare le ottimizzazioni alle fasi di analisi e disegno. Una delle anomalie che più frequentemente si rischia di generare è di introdurre ottimizzazioni, utilizzare design-pattern e raffinate astrazioni che non sempre hanno molto a che fare con il dominio del problema. Ciò può rendere il modello meno comprensibile ad un pubblico tecnicamente meno preparato come quello degli esperti del dominio e degli utenti/ clienti. La cosa costituisce un grosso limite considerando che proprio questi soggetti sono tra i fruitori di primaria importanza del modello a oggetti del dominio. Inoltre il modello a oggetti del dominio, prima di assumere una forma stabile, tipicamente subisce molte rielaborazioni e quindi il tempo investito nel produrre raffinate astrazioni ben presto può rivelarsi un notevole spreco. Probabilmente non è neppure il caso di spendere un tempo eccessivo nell’investigare le particolari associazioni. In questo stadio affermare che una relazione sia un’aggregazione piuttosto che una composizione non cambia di molto la sostanza, mentre potrebbe creare problemi al solito utente frastornato dalla nuova simbologia. Nella costruzione del modello a oggetti del dominio la parola d’ordine deve essere utilizzare un approccio minimalista. Nella realtà accade raramente di dover realizzare un sistema inedito, molto più frequentemente capita di dover di dover affrontare un dominio per il quale esistano in commercio software che affrontano problematiche simili. In tutti questi casi è possibile tentare di generare il modello del dominio per mezzo di una reingegnerizzazione dello schema della base dati. In questo esercizio è necessario tener ben presenti le regole del particolare database management system. Un’altra accortezza è che nella produzione del modello a oggetti del dominio non interessa rappresentare tutte le tabelle con ogni singolo attributo: molti di questi elementi hanno un significato strettamente legato all’implementazione o alla strutturazione, che quindi hanno poco o nulla a che fare con lo spazio del problema. L’utilizzo di questa tecnica permette di realizzare un modello iniziale che dovrebbe fornire un’ottima piattaforma di dialogo con gli esperti dell’area business. Di fronte ai vantaggi ben evidenti nel ricorrere all’utilizzo dei tracciati del database (riuscire a produrre velocemente una versione iniziale del modello evitando molti lunghi meeting con gli utenti, risolvere a priori eventuali contenziosi sulle diverse percezioni dell’area business da parte degli utenti, disporre di una versione dell’organizzazione dei dati dello spazio del problema ben investigata e collaudata) esistono tutta una serie di rischi, come per esempio: spendere troppo tempo nel tentare di comprendere la struttura del database, produrre una versione del modello ad oggetto a carattere eccessivamente tecnico, utilizzare una struttura eccessivamente “denormalizzata”, e così via. Diverse di queste insidie possono

UML e ingegneria del software: dalla teoria alla pratica

131

essere attenuate considerando il modello prodotto come una versione iniziale da utilizzarsi come piattaforma di colloquio e non come versione finale del modello. “Un modello del business mostra l’ambiente dell’azienda e come questa opera in relazione ad esso…” (Ivar Jacobson). Questo descrive come un gruppo di utilizzatori (tecnicamente denominati worker della business area) utilizzano un insieme di entità di business (le classi già evidenziate nel modello a oggetti del dominio) attraverso opportune unità di lavoro (work unit). In sostanza una work unit definisce la logica che specifica i vari servizi erogabili agli attori. In questo contesto l’attenzione è focalizzata sull’intera area business e non esclusivamente sul sottoinsieme da implementare. È possibile affermare che un business object model è un modello a oggetti del dominio arricchito attraverso l’inserimento di concetti come worker e work unit. Il metodo tipicamente utilizzato per realizzare un modello a oggetti del business prevede di realizzare inizialmente il modello dei casi d’uso di business, da cui prelevare l’elenco degli attori “business” e le work unit. Qualora i casi d’uso posseggano un giusto livello di granularità, si ha quasi una corrispondenza uno ad uno tra singolo caso d’uso e unità di lavoro. Il passo successivo consiste nell’individuare le classi entità (entity). L’ultimo passo consiste nel riorganizzazione il tutto: gli attori interagiscono con le varie unità di lavoro, le quali forniscono i propri servizi accedendo allo stato delle varie entità ed eventualmente modificandolo. La stretta somiglianza esistente tra il modello a oggetti business e del dominio finisce per porli su un piano di alternativa. Se si tentasse di produrre tutti i manufatti previsti dai processi più formali per lo sviluppo del software, verosimilmente sarebbe necessario disporre di un lasso di tempo di qualche ordine di grandezza superiore rispetto a quello effettivamente necessario/disponibile. In questa lotta alla sopravvivenza, tipicamente nei progetti reali, il modello a oggetti del dominio tende ad avere la meglio sul relativo al livello di business. Ciò è dovuto al fatto che è molto utile disporre di una versione dei dati del dominio del problema in quanto agevola la progettazione della base di dati, semplifica la scomposizione del sistema in componenti, agevola la definizione sia delle interfacce utente, sia degli eventuali messaggi utilizzati per far comunicare i vari sottosistemi. Inoltre il modello del business può essere compensato dal modello a oggetti del dominio più la relativa vista dei casi d’uso e la fase successiva prevede il modello di analisi molto simile a quello business. Molti autori preferiscono invece il modello a oggetti business poiché focalizzando l’attenzione sull’intero business si ha un manufatto di valore molto elevato, anche per finalità non strettamente connesse con la costruzione del sistema software, come la materializzazione del quadro generale da utilizzarsi come punto di partenza per generare diversi progetti, addestramento del personale, ottimizzazioni dei flussi lavorativi. Il modello a oggetti di analisi è prodotto durante la relativa fase del processo di sviluppo del software e spesso è incorporata con quella di disegno. Si tratta di uno stadio di “raccordo” tra l’analisi dei requisiti utente ed il disegno del sistema. Le classi del modello di analisi sono ancora focalizzate sui requisiti funzionali del sistema, mentre l’incorporazione di quelli non funzionali è rimandata al modello di disegno. Nella fase di analisi il livello di formalismo è sicuramente superiore rispetto a quello delle fasi precedenti, ma non ancora uniformato a quello delle fasi successive. Gli esperti del dominio tendono ancora ad

132

Capitolo 8. Le classi nei processi

essere coinvolti ma con ruoli molto marginali. Le classi del modello di analisi risultano più vicine allo spazio del dominio, più concettuali e verosimilmente meno granulari delle corrispondenti a livello di disegno. In questa fase non si può ancora pretendere un modello dettagliato e tanto meno un elevato grado di formalità. L’applicazione degli insegnamenti propri dell’OO sovente è sacrificata a favore del risparmio di tempo. Inoltre, come nel caso del modello a oggetti del dominio, ancora non si è molto interessati a definire le operazioni delle varie classi, ciò sia perché probabilmente non si dispone ancora di sufficienti informazioni, sia perché il tempo richiesto probabilmente non sarebbe giustificabile nel contesto dell’intero processo di sviluppo del software. Il modello a oggetti di analisi deve essere realizzato utilizzando esclusivamente tre stereotipi “standard” dell’elemento classe: boundary, entity e control. Una boundary class (classe di confine) rappresenta un “punto di accesso” al sistema, una componente dello strato di interfacciamento con i relativi attori. Si tratta di parti del sistema fortemente dipendenti dagli attori che le utilizzano e permettono di investigare, chiarire e formalizzare i requisiti del sistema a livello di interfacciamento con entità esterne. Considerata la fase di questo modello e l’elevato livello di astrazione non è necessario dettagliare eccessivamente le classi boundary; è sufficiente specificare gli obiettivi dell’interazione, i dati che si intendono scambiare, e così via, senza però descrivere come questa poi venga realizzata. Le classi entity (entità) sono classi atte a modellare informazioni gestite dal sistema che necessitano di essere memorizzate in forma permanente. Sono passive, sebbene possano disporre di comportamenti anche complessi legati alle informazioni contenute. Uno degli scopi delle classi entity consiste nell’isolare i dettagli della memorizzazione delle informazioni dalle classi che le manipolano (control). Le control class (classi di controllo) rappresentano il controllo, nel senso più generale del termine (coordinamento, transizione, successione, ecc.), di altri “oggetti”. Tipicamente sono utilizzate per incapsulare la logica di controllo presente nelle funzionalità definite a livello dei casi d’uso. Spesso rappresentano algoritmi anche complessi di calcolo, gli attuatori delle policy aziendali e regole di business. Una delle caratteristiche peculiari delle classi di controllo consiste nell’incapsulare le logiche del sistema, e quindi nel ridurre l’impatto, sulle restanti parti, dovuto ad eventuali variazioni. Tipicamente tendono ad assumere la fisionomia di “macroclassi” e quindi raramente sopravvivono al modello di disegno, nel quale ciascuna classe di controllo tende a dar luogo a svariate classi. Nella produzione del modello di analisi è necessario tenere presenti quattro regole fondamentali: 1. gli attori possono essere associati unicamente a boundary class; 2. le boundary possono essere associate esclusivamente con attori e control class (questa regola equivale ad affermare che non è possibile associare tra loro due boundary class così come non è possibile collegare direttamente boundary class con entity); 3. le classi entità possono essere associate esclusivamente con quelle di controllo (si vieta di associare tra loro due entity class e di collegarle direttamente o con le boundary o addirittura con gli attori);

UML e ingegneria del software: dalla teoria alla pratica

133

4. le classi control non possono essere associate con gli attori. Il modello di analisi, pur offrendo diversi vantaggi, non rientra nella categoria di quelli estremamente necessari per la buona riuscita dell’intero progetto. Il problema è che il costo di produzione e manutenzione non sempre ne giustifica i vantaggi offerti. La decisione se dar luogo o meno ad una fase distinta dipende da molti fattori, non ultimi la dimensione del progetto, il tempo a disposizione, la tipologia, la tecnologia coinvolta, e così via. Uno dei vantaggi offerti dal modellare i requisiti utente attraverso i diagrammi delle classi deriva dalla semplicità e rapidità di aggiornamento degli stessi. Il modello di analisi, appartenendo ad un livello di astrazione più elevato rispetto a quello di disegno, rende più agevole analizzare nuove richieste, studiarne l’impatto, il tempo necessario e quindi i costi. L’elevato livello di astrazione è particolarmente apprezzato anche per attività quali la formazione del nuovo personale tecnico coinvolto nello sviluppo e manutenzione del sistema: offre una vista concettuale precisa del sistema sottostante in modo più agevole e meno dettagliato del corrispettivo al livello di disegno. I modelli di analisi assumono un ruolo preziosissimo quando si ha a che fare con sistemi legacy datati o con sistemi prodotti da terze parti (in tali casi probabilmente non è il caso di realizzare modelli di disegno e quindi il modello di analisi risulta quello più vicino ai dettagli implementativi). Ulteriori vantaggi sono: rapida e formale acquisizione dei requisiti del sistema da parte del team degli architetti incaricati di realizzare il modello di analisi, possibilità di rivedere rigorosamente il modello dei casi d’uso e quello a oggetti del dominio, ecc. Lo svantaggio principale del modello è sempre lo stesso: il tempo necessario per la produzione e manutenzione. Se il costo della produzione iniziale a volte è giustificabile per i motivi enunciati, la relativa manutenzione lo sarebbe molto meno. Spesso il modello di analisi viene considerato da molti tecnici un modello usa e getta: una volta ottenuti i vantaggi legati alla realizzazione del modello iniziale non ci si preoccupa più di aggiornarlo. Un altro contro del modello di analisi è legato al fatto che, nella pratica, raramente si produce la versione iniziale del modello di disegno disegnando tutto da zero. In genere si tende a riapplicare strutture, pattern, direttive utilizzate in precedenza dimostratisi soddisfacenti e quindi si parte cercando di riutilizzare architetture funzionanti in precedenti progetti. Nel corso dell’esecuzione dei processi di sviluppo del software, l’ultimo modello a oggetti che si incontra è quello relativo alla proiezione statica del modello di disegno. Il modello di disegno può essere decomposto in due sottomodelli relativi alla struttura statica ed al comportamento dinamico. La proiezione statica si presta ad essere rappresentata per mezzo dei diagrammi delle classi, mentre per la descrizione delle dinamiche interne del sistema è opportuno utilizzare diagrammi di interazione (sequenza, sequence e collaborazione, collaboration) e, meno frequentemente, i diagrammi di attività, particolarmente utili qualora si voglia enfatizzare l’esecuzione concorrente di determinati processi. Resta poi sempre valido il consiglio di produrre diagrammi degli oggetti per esemplificare porzioni di diagrammi delle classi ritenuti poco chiari. Il modello di disegno è decisamente orientato alle soluzione tecniche e pertanto si configura come ambiente naturale di architetti e programmatori. Dopo aver lungamente argomentato le funzionalità che il sistema dovrà realizzare, l’attenzione viene focalizzata sul dettaglio di come ottenerle. Le classi presenti

134

Capitolo 8. Le classi nei processi

nei precedenti modelli a oggetti vengono “incastonate” in una serie di infrastrutture tecniche che rendono possibile realizzare quando stabilito nella vista dei casi d’uso. Il modello di disegno concorre a definire la descrizione analitica dell’architettura software del sistema in funzione dei requisiti catturati nelle fasi precedenti e, pertanto, rappresenta il dettaglio delle specifiche per la fase di codifica. Di modelli di disegno ne esistono diverse versioni o, se si preferisce, ne esiste un’unica che evolve durante lo svolgimento delle fasi di elaborazione e costruzione, passando dal livello di specifica via via a quello di implementazione. Le prime versioni del modello presentano un livello di dettaglio non eccessivamente approfondito. In questo modello trovano posto, sicuramente, tutte le classi principali ed i metodi più importanti. È opportuno definire fin da subito i metodi pubblici con la relativa firma in modo da garantire la coerenza del modello fin dalle primissime fasi, evitando ai programmatori l’incombenza di dover operare scelte “locali”. Il modello di implementazione, invece, nella versione finale è una rappresentazione grafica del codice (tenendo sempre presente che “solo il codice è sincronizzato con sé stesso” [S. Ambler]). Uno degli input per la produzione del modello di disegno è costituito dal modello di analisi. Non è infrequente però il caso in cui questo venga tralasciato per questioni di economia dei tempi. Chiaramente la decisione di trascurare il modello di analisi comporta un certo rischio che però può essere gestito e mitigato dalla presenza di architetti esperti. Poiché il modello di disegno è un’astrazione del codice, è necessario che accolga caratteristiche dipendenti dal linguaggio di programmazione. Ulteriori vincoli da tenere presenti durante la realizzazione del modello di disegno derivano dall’ambiente di produzione del codice e da proprietà quali riusabilità dei componenti, scalabilità del sistema, tecnologie a disposizione (per esempio database relazionali o OO, sistema operativo, ecc.). In questa fase anche le definizione precisa delle diverse tipologie di relazioni ha un significato semantico importantissimo. Specificare un’aggregazione piuttosto che una composizione ha, per esempio, molte implicazioni in termini della gestione del ciclo di vita degli oggetti istanze delle classi coinvolte nell’associazione stessa. In particolare una classe composta dovrà fornire tutti metodi necessari per creare e distruggere le istanze delle classi che la compongono. La CRC cards (Class – Responsibility – Collaborator Cards, carte di classe – responsabilità – collaboratrici) è una metodologia ampiamente utilizzata nel processo di progettazione e documentazione dei modelli a oggetti, il cui dominio di applicazione è essenzialmente quello dei modelli a oggetti di analisi e disegno. La versione iniziale fu congegnata da Ward Cunningham con il nome di HyperCard presso i laboratori della Apple Computer Inc. negli anni Ottanta. La metodologia prevede l’utilizzo di una serie di schede vuote (da cui il nome Cards) le cui dimensioni sono definite nelle stesse direttive, ossia: 6" × 4" (152 × 102 mm). Il processo prevede di selezionare gli use case e/o gli scenario di cui si intende disegnare il modello e quindi iniziare a leggere i passi che ne compongono il flusso di azioni. Ogni qualvolta si incontrano riferimenti a oggetti precedentemente individuati (sia oggetti entità e quindi appartenenti al modello a oggetti del dominio, sia “manager” di determinate funzioni) si posiziona la relativa card su un’apposita lavagna. Qualora nessuna delle classi a disposizione risulti idonea per espletare l’azione oggetto d’esame è necessario compilare una nuova scheda: si è individuata una nuova classe. Gli aggiorna-

UML e ingegneria del software: dalla teoria alla pratica

135

menti delle schede sono necessari qualora: 1. l’attività considerata, sebbene concettualmente appartenente ad una classe individuata in precedenza (criterio di coesione), non sia contemplata nella relativa lista delle responsabilità 2. una scheda, per assolvere ad una nuova responsabilità necessiti di utilizzare i servizi offerti da un’altra non menzionata nella propria lista delle collaborazioni. Altri aggiornamenti avvengono durante la revisione del modello qualora esistano classi con troppe responsabilità, diverse molto simili tra loro, altre superflue, ecc. Ogni qualvolta si inizia a leggere un flusso di azioni, le prime carte da considerare tendono ad essere le stesse: la prima rappresenta un’istanza dell’attore che ha fornito lo stimolo iniziale al caso d’uso, la seconda rappresenta l’interfaccia, ossia l’oggetto che riceve lo stimolo fornito e quindi avvia di conseguenza il relativo processo, la terza la classe di gestione dell’interfaccia utente, ecc. Il processo è intrinsecamente iterativo e incrementale: man mano che si procede con l’analizzare nuovi scenari si identificano nuove classi e si perfezionano quelle individuate in precedenza. Terminata l’analisi di uno scenario, è importante effettuare tutti i controlli del caso sia al livello delle singole classi che lo costituiscono, sia a livello più generale del disegno. Durante il processo di revisione spesso si dà luogo ad un refactoring introducendo opportuni oggetti, inglobandone altri in uno solo e così via, al fine di migliorare l’organizzazione del disegno, rendere i modelli più stabili, riflettere la struttura a strati delle moderne architetture. Una volta raggiunto uno stadio in cui si abbia la sensazione di aver elaborato una buona ipotesi di disegno, è possibile effettuare una verifica formale del comportamento dinamico del modello realizzato. Una evoluzione della metodologia consiste nel disporre di carte del “potere”, ossia carte rappresentanti soluzioni generiche, eleganti, dimostratesi valide nella soluzione di altri problemi: in poche parole carte rappresentanti pattern. L’applicazione della metodologia classica prevede che al gioco delle carte prendano parte diverse figure professionali. Per esempio è possibile coinvolgere un business analyst da interpellare qualora gli use case non siano chiari o si individuino delle incoerenze/lacune, uno o più architetti, programmatori, un esperto della metodologia delle CRC Cards (CRC Cards facilitator) ecc. Spesso il team viene corredato dagli “scrivani”, il cui obiettivo è di compilare le varie schede man mano che i vari elementi vengono evidenziati. Da tenere presente che, quantunque ogni persona possa fornire ottimi spunti alla realizzazione del disegno, le riunioni con troppe persone tendono a generare elevato grado di entropia. I vantaggi principali della metodologia delle CRC Cards risiedono indubbiamente nella semplicità e nell’immediatezza di utilizzo. Un altro grande vantaggio consiste nell’economicità con cui è possibile produrre modelli, verificarli, cambiare le fondamenta logiche, e così via. Grazie a questi vantaggi, la metodologia delle CRC Cards si delinea come un ottimo strumento per scopi formativi. In particolare, la “manualità” della tecnica e la semplicità e rapidità con cui è possibile produrre e aggiornare i propri disegni aiutano ad abbattere molti timori. Gli svantaggi della metodologia sono dovuti sia alla difficoltà di organizzare meeting produttivi con le varie parti, specie con gli utenti, sia alla presenza di strumenti più adeguati (lo UML) ed architetture standardizzate. L’ultima parte del capitolo è dedicata all’incosciente tentativo di rispondere a quello che è uno degli interrogativi più controversi e complicati dell’intero processo di sviluppo del software: quando un mo-

136

Capitolo 8. Le classi nei processi

dello di disegno OO può essere considerato tale e ben progettato? In particolare si è cercato di fornire una serie di criteri che permettano di evidenziare errori tipici durante il disegno del sistema, di verificare la qualità degli stessi modelli e di produrne versioni di livello qualitativo elevato, senza avere assolutamente la presunzione di essere esaustivi. Prima di addentrarsi nell’esame di una serie di principi utili e linee guida per la realizzazione di modelli di disegno di buona qualità, è stato ritenuto interessante presentare alcuni sintomi che permettono di evidenziare situazioni critiche, in cui bisogna necessariamente intervenire prima che la qualità del sistema risulti irreparabilmente compromessa. L’analisi delle situazioni di crisi è molto utile anche perché aiuta a comprendere più intimamente la necessità di realizzare modelli di disegno di qualità. Uno dei sintomi di malessere dell’architettura più semplici da individuare è la cosiddetta fragilità. Con tale termine si fa riferimento alla tendenza dei sistemi di generare malfunzionamenti di sue parti a seguito della realizzazione di aggiornamenti. Ciò che spesso avviene e che a seguito di alcune modifiche apportate in specifici settori del sistema si assiste alla perdita di funzionalità di altre parti apparentemente non relazionate con i settori modificati. Altra anomalia, strettamente connessa con la precedente è quella che va sotto il nome di rigidità, con tale termine si indica la resistenza del sistema ai cambiamenti. Chiaramente se ogni cambiamento corre il rischio di distruggere il sistema in parti, questo è fragile e rigido. Un altro problema è quello noto con il nome di immobilità, si tratta del contrario della riusabilità. Evidenzia situazioni in cui è difficile riutilizzare parti dello stesso sistema o di altri realizzati in precedenza. Un ulteriore problema è legato all’evoluzione incoerente del disegno che finisce per rendere lo stesso rigido e, frequentemente, fragile. Quando un architetto inizia a disegnare il sistema, o sue parti, utilizza determinati principi, fissa delle colonne portanti dell’architettura del sistema, e così via: tutto appare perfetto. A seguito poi di aggiunta di nuove funzionalità e/o correzioni, può accadere che le nuove parti violino i principi base, le regole stabilite in partenza dall’architetto. Per quanto concerne la realizzazione di modelli di buona qualità, un primissimo criterio da considerare scaturisce da un ragionamento logico abbastanza semplice: se le singole parti componenti di un sistema sono di buona qualità, allora verosimilmente è più facile che l’intero sistema sia di qualità. Chiaramente sono possibili deviazioni in entrambe le direzioni: alcuni componenti non perfettamente disegnati e sistemi ancora di buon livello qualitativo e viceversa componenti ben disegnati e sistema scadente. Non bisogna essere eccessivamente rigidi su questo principio: spesso la qualità del disegno di alcune classi viene intenzionalmente sacrificata a favore della qualità del sistema generale. Chiaramente, qualora si tratti di qualche classe non perfettamente disegnata, è possibile sorvolare, quando invece gran parte o tutte le classi sono disegnate male, o alcune sono veramente pessime, allora è inutile procedere nell’analisi dei rimanenti criteri: il modello non va. Un altro elemento cui spesso non si attribuisce la dovuta importanza è l’organizzazione in package del sistema. Ciò è molto importante poiché, tipicamente, il riutilizzo delle parti raramente avviene al livello delle singole classi: normalmente si cerca di riutilizzare interi package. Un buon criterio da seguire per strutturare i package deriva dai principi classici: massima coesione e minimo accoppiamento. Le classi

UML e ingegneria del software: dalla teoria alla pratica

137

inserite nello stesso package devono essere “coese” tra loro ed avere un accoppiamento tale da rendere il package stesso scarsamente accoppiato con i restanti. Un problema che spesso si incontra nei modelli OO è legato ad un utilizzo non coretto della relazione di ereditarietà. In sintesi, non è infrequente che alcuni disegnatori, constatando che alcune classi hanno qualche parametro o responsabilità in comune, decidano di organizzare queste classi attraverso una struttura gerarchica. A volte si tratta di una soluzione corretta (si vuole effettivamente ereditare il tipo), altre volte no. Sebbene l’ereditarietà sia un principio base dell’OO, l’utilizzo travisato può generare diversi effetti collaterali: la relazione tra le classi è definita a tempo di disegno e non può variare, vi è un consistente accoppiamento tra classi genitore e classi discendenti, riduzione dell’incapsulamento. L’applicazione dei pattern nel modello di disegno è un fattore che ne favorisce l’aumento della qualità. Si tratta di soluzioni ben provate che sicuramente offrono un elevato grado di flessibilità, eleganza, funzionalità. Come ogni cosa, però, qualora utilizzata in eccesso o peggio ancora, in modo errato, corre il rischio di generare una serie di problemi. Per esempio, utilizzi ingiustificati tendono a generare modelli over-engineered, ossia inutilmente complessi, difficili da comprendere e mantenere. Un altro criterio consiste nel porre attenzione al disegno delle interfacce. In particolare si vuole evitare una serie di problemi derivanti dal disporre di interfacce gravate da un’eccessiva quantità di responsabilità; in altre parole poco coese. Qualora si riconosca una situazione del genere, è opportuno procedere alla decomposizione dell’interfaccia in una serie di interfacce più semplici e con maggiore grado di coesione. In questo modo, ciascuna interfaccia raggruppa un insieme coeso di servizio utilizzato da un ben definito gruppo di classi client. Un’anomalia generata dalle interfacce poco coese prende il nome di “inquinamento dell’interfaccia”. Questa si genera qualora una specifica classe che implementa una determinata interfaccia richieda di essere comandata o di fornire servizi aggiuntivi rispetto a quelli definiti da tale interfaccia. Spesso, trovandosi di fronte a tale situazione, si procede inserendo nell’interfaccia i metodi aggiuntivi necessari anche se poi questi non possiedono alcuna coesione con i restanti servizi esposti dall’interfaccia stessa. Ciò causa una serie di problemi, come per esempio rende più difficoltosa la riusabilità e la comprensibilità del disegno, aumenta il grado di fragilità del sistema (un’eventuale modifica della definizione dei servizi dell’interfaccia, anche di quelli non implementati dalle varie sottoclassi, comunque genera la necessità di modificare tutte le sottoclassi), ecc. Uno dei princìpi più famosi del disegno di sistemi OO e non solo, è quello che prende il nome di OpenClose (aperto-chiuso) coniato da Bertrand Mayer. Il principio afferma che: “Le componenti del sistema (classi, package, entità, componenti veri e propri, …) devono essere aperte a estensioni, ma chiuse alle modifiche”. Da notare che affinché un componente sia chiuso alle modifiche è necessario che il relativo codice non sia modificabile. Per quanto concerne la prima parte (apertura), dovrebbe essere ormai chiaro che è insito nel destino dei sistemi cambiare e quindi bisogna disegnare il sistema in modo da riuscire, in qualche misura, ad anticipare le variazioni o comunque renderle più indolori possibile. In termini più pratici, si richiede di poter modificare agevolmente il comportamento di determinati componenti in modo che gli effetti della variazione siano il più possibile circoscritti. Per quanto concerne la seconda parte, essa sancisce che si desidera aggiornare il comportamento del sistema senza dover cambiare le parti esistenti.

138

Capitolo 8. Le classi nei processi

Questo criterio è anche noto come principio base dei framework. In questi sistemi, si desidera avere una parte assolutamente non variabile (il core) e tutta una serie di meccanismi che permettano di adattare il comportamento dell’infrastruttura alle specifiche realtà in cui viene utilizzato. Chiaramente, ipotizzare di riuscire a chiudere tutto il sistema è impensabile. Per quanto si tentasse di chiudere un componente, esisterebbero sempre tipologie di variazioni contro le quali il componente risulti non chiuso. Ciò nonostante una chiusura parziale ben mirata può migliorare notevolmente il disegno. Un altro criterio molto importante da seguire nella realizzazione del modello di disegno è relativo all’utilizzo dei meccanismi dell’astrazione. Si tratta di una buona norma far in modo che il proprio disegno (e quindi il codice) dipenda il più possibile da opportune astrazioni piuttosto che da comportamenti concreti. Pur nella consapevolezza di non aver fornito un elenco esauriente (probabilmente non basterebbe un intero volume dedicato all’argomento), si è cercato di condensare i criteri utilizzati dall’autore sia per realizzare i modelli di disegno, sia per valutarne la relativa qualità. Sebbene l’esperienza giochi ancora un ruolo molto importante, le nuove tecnologie, come per esempio l’architettura EJB, rendono il lavoro meno gravoso: gran parte dell’infrastruttura e dei pattern da utilizzare sono predefiniti e quindi commettere errori gravi è oggettivamente meno facile e richiede un certo impegno. Come degna e definitiva conclusione del capitolo sono state riportare brevemente le caratteristiche che un software deve possedere per essere considerato di buona qualità: correttezza, robustezza, estendibilità, riusabilità, efficienza, facilità d’uso, compatibilità, facilità di gestione, portabilità, sicurezza e adeguata documentazione.

Appendice

A

UML e i linguaggi di programmazione non OO Introduzione Molte volte si è detto che lo UML è un linguaggio di modellazione che — sebbene ideato per la progettazione di sistemi Object Oriented e component-based — si presta a essere utilizzato per la produzione di sistemi che prevedano linguaggi di programmazioni non basati su tali paradigmi. Non solo, lo UML può essere utilizzato proficuamente per modellare sistemi che addirittura non prevedono implementazione. L’esempio più evidente è l’utilizzo dello stesso UML per modellare il metamodello di cui è istanza. La domanda alla quale si intende rispondere in questa Appendice è come utilizzare i vari strumenti messi a disposizione dallo UML per la modellazione di sistemi non basati sul paradigma OO. A tal fine si è cercato di fornire una risposta inquadrata nel contesto di un processo formale di sviluppo del software.

Analisi dei requisiti Le prime fasi da affrontare in un qualsivoglia processo di sviluppo del software consistono nell’eseguire l’analisi dei requisiti. I modelli da produrre in questa fase, basati sull’utilizzo dello UML, sono essenzialmente di due tipologie: casi d’uso e modelli ad oggetti. In particolare, in funzione di quale fase si stia lavorando, questi modelli prendono il nome di casi d’uso e modello a oggetti business e casi d’uso e modello a oggetti del dominio. I principali strumenti UML da utilizzare sono quindi i diagrammi dei casi d’uso e quelli delle classi. A questi eventualmente possono essere affiancati quelli di sequenza (utili per

2

Appendice A. UML e i linguaggi di programmazione non OO

illustrare gli scenario dei casi d’uso), quelli degli stati (utilizzati per illustrare il ciclo di vita di particolari “entità” e processi presenti nell’area oggetto di studio) e, meno frequentemente, quelli degli oggetti (utilizzati per illustrare particolari istanze di modelli a oggetti o porzioni meno chiare o meno intuitive). Premesso ciò, una prima constatazione da fare è che il “paradigma” utilizzato per condurre l’analisi dei requisiti non ha alcuna influenza diretta su quello adottato per disegnare il sistema. Più volte si è posto l’accento sul fatto che il livello di formalità dei modelli prodotti durante le prime fasi deve essere necessariamente “rilassato” per una serie di validi motivi. Il più importante è consentire a utenti e clienti, che tipicamente non sono esperti di informatica e tanto meno di astrazioni OO, di poter collaborare attivamente al processo di analisi dei requisiti (rivedere i modelli, suggerire modifiche, fornire opinioni, ecc.). Questo rilassamento di certo non pregiudica la qualità del disegno: si possono realizzare perfetti sistemi Object Oriented o component-based a partire da specifiche fornite attraverso documenti in linguaggio naturale (che ben poco hanno di OO) così come, situazione opposta, si possono realizzare sistemi non OO — anche involontariamente — con modelli di analisi dei requisiti perfettamente formali. Il suggerimento pertanto è realizzare l’analisi dei requisiti come se si dovesse realizzare un sistema OO Si tratta di una buona idea anche considerando il ritorno degli investimenti. L’attuale tendenza del settore è costruire sistemi component-based e quindi, prima o poi, specie se il sistema riscuoterà un sufficiente successo, questo dovrà essere reingegnerizzato. In tale evenienza il modello OO dell’analisi dei requisiti realizzato con gli strumenti UML “standard” costituisce un ottimo punto di partenza. Altre considerazioni sono relative alla presenza di tool di supporto che semplificano il lavoro, al rilassamento della formalità delle leggi OO tipico di queste fasi, e via discorrendo. In sintesi, considerando gli strumenti dello UML da utilizzare, si può dire: • i diagrammi dei casi d’uso (eventualmente supportati dai diagrammi di sequenza e quelli di stato) si utilizzano normalmente; • il modello di business (tipo di diagrammi di classi) è molto generale e non strettamente OO; • il modello di dominio è focalizzato sull’organizzazione di dati e relazioni contestuali; si può dire che somiglia molto a un diagramma entity-relationship, poco utile indipendentemente dal paradigma utilizzato per produrre il sistema. Per concludere, questa fase del processo di sviluppo del software è abbastanza indipendente dal paradigma selezionato per l’implementazione del sistema: è pertanto consigliabile utilizzare strumenti moderni.

UML e ingegneria del software: dalla teoria alla pratica

3

Analisi In questa fase le cose cominciano a cambiare. Si tratta del vero punto di raccordo tra analisi dei requisiti e disegno. Da un lato si realizza una versione formale dei requisiti utenti e dall’altro si inizia a disegnare il sistema (a tutti gli effetti si realizza una prima versione del disegno, nel quale gran parte dei requisiti non funzionali sono ancora esclusi). Per la realizzazione dei vari modelli, si presentano due possibilità: 1. comportarsi come se si stesse producendo un sistema OO o component-based. A favore di questo approccio c’è da dire che chiunque possieda una buona esperienza OO tende a disegnare/realizzare sistemi come se fossero basati sul paradigma OO, anche se poi si utilizzano linguaggi non OO. A supporto di questa opinione è sufficiente pensare che le prime versioni dei compilatori C++ erano semplicemente compilatori C forniti di macro evolute. In ogni modo, i vantaggi di questo approccio sono essenzialmente gli stessi riportati nel paragrafo precedente (salvaguardia degli investimenti, supporto di tool commerciali). In generale, si tratta di un approccio ancora possibile in quanto, sebbene il livello di formalità sia sicuramente superiore rispetto a quello delle fasi precedenti, non è ancora elevato come quello richiesto dalle fasi successive. Gli svantaggi sono legati al fatto che si rimanda alla fase successiva, già intrinsecamente complessa, una gran mole di lavoro necessaria per rivedere e adattare il modello a una versione OO a elevato livello di formalità. 2. adattare il formalismo dei diagrammi delle classi alle proprie necessità. Quantunque questa attività possa essere complicata, deve comunque essere realizzata nella fase successiva. In questo stadio, le classi disegnate sono di tre tipi: entità, controllori e “di confine”. Per quanto concerne la prima e la terza, non ci dovrebbero essere troppi problemi, si tratta di classi che incapsulano, rispettivamente, record di dati e moduli di interfacciamento. Pertanto il particolare paradigma selezionato non dovrebbe influenzare tantissimo il disegno di questi elementi. Qualche inconveniente potrebbe invece sorgere con i controllori (classi controller). Dall’analisi dei moduli nei linguaggi di programmazione non Object Oriented basati sul solo paradigma della programmazione strutturata, è possibile notare che comunque esistono variabili “globali” al modulo, eventualmente globali all’intero sistema, e procedure. Le prime possono essere dichiarate nella sezione dedicata agli attributi, mentre le seconde nel dipartimento destinato ai metodi. Restringendo l’insieme delle relazioni utilizzabili e modificando il significato dei vari dipartimenti delle classi, è ancora possibile utilizzare il formalismo dei diagrammi delle classi anche per linguaggi non OO. Per quanto concerne i diagrammi di interazione, questi sono applicabili indipendentemente. Invece di rappresentare oggetti istanze di classi, si

4

Appendice A. UML e i linguaggi di programmazione non OO

visualizzano istanze di moduli. Il vantaggio di questa tecnica è che si comincia a disegnare il sistema spostandosi molto verso la fase di disegno che quindi viene parzialmente alleggerita. Gli svantaggi sono essenzialmente legati all’impossibilità di riutilizzare i modelli prodotti per eventuali successive versioni OO del sistema. In questa fase pertanto si deve affrontare il primo dilemma: continuare con un approccio OO o abbandonarlo per iniziare ad avvicinarsi alla rappresentazione reale del sistema? L’autore è un sostenitore del primo approccio; la scelta però dipende da molti fattori, non ultime le capacità del personale coinvolto nel processo di sviluppo, la riusabilità di modelli realizzati in precedenza, la necessità di preservare gli investimenti.

Disegno Qui purtroppo c’è ben poco da fare: è necessario dar luogo alla seconda alternativa evidenziata nel paragrafo precedente: produrre un modello di disegno considerando che il sistema da sviluppare non sfrutta il paradigma OO. Di versioni del modello di disegno, tipicamente, ne esistono almeno due: specifica e implementazione. In realtà non si tratta di versioni completamente separate, bensì si assiste a un graduale passaggio da un livello di dettaglio elevato a uno sempre più vicino alla codifica. Alla fine si vuole che il modello sia una proiezione grafica del codice. Il consiglio quindi non può che essere quello di adattare i vari formalismi alla rappresentazione di sistemi non OO. Per quanto concerne la proiezione dinamica, nei diagrammi di interazione non cambia molto. Per esempio se nella prima riga dei diagrammi di sequenza sono riportate istanze di moduli o package anziché di oggetti, le cose dovrebbero ancora funzionare bene. Per quanto attiene alla proiezione statica, si possono utilizzare i diagrammi delle classi e degli oggetti con gli avvertimenti specificati nel paragrafo precedente. Probabilmente è opportuno porre attenzione alle relazioni utilizzate. Quella che potrebbe creare maggiori problemi è la relazione di ereditarietà. Sebbene possa essere simulata (consultare Capitolo 6), probabilmente in questa fase è più opportuno evitarla.

Restanti diagrammi Nei precedenti paragrafi sono stati menzionati quasi tutti i diagrammi messi a disposizione dello UML. Restano fuori sono quelli implementativi: diagrammi dei componenti e di dispiegamento. Mentre il primo ha una connotazione ben definita ed è collocabile nella fasi di analisi e disegno, il secondo ha una collocazione più ampia. È opportuno disegnare le prime versioni dell’architettura fisica fin dall’analisi dei requisiti utenti. Ciò per diversi motivi, non ultimo per fornire preziosi riscontri di fattibilità ai requisiti stessi specie quelli non funzionali. La notizia bella è che in questo caso i diagrammi si prestano a essere utilizzati così come sono senza grosse modifiche. In particolare, nei diagrammi di

UML e ingegneria del software: dalla teoria alla pratica

5

dispiegamento (deployment) è necessario illustrare la struttura fisica del sistema in termini di nodi e relative relazioni che, evidentemente, non hanno dipendenze dal particolare paradigma utilizzato per realizzare il sistema. I vari nodi componenti l’architettura sono la sede in cui sono installati i componenti del sistema. Questi ultimi possono essere sia componenti veri e propri (per esempio EJB) oppure parti più generali dell’architettura (web server, application server, file .dll, ecc.). Pertanto i diagrammi dei componenti si prestano a essere utilizzati in maniera molto formale, oppure in modo più pragmatico, come richiesto da un sistema non basato sul paradigma OO.

Conclusioni Quantunque lo UML sia un linguaggio di modellazione studiato per la produzione di sistemi basati sul paradigma OO, è possibile utilizzarlo proficuamente anche qualora il sistema da produrre non sia basato su tale paradigma. In questo scenario, una scelta importante consiste nel selezionare accuratamente il “punto di rottura”, ossia la fase del processo di sviluppo del software in cui interrompere la produzione dei modelli come se si trattasse di un sistema OO e iniziare a produrre modelli più rispondenti al reale paradigma selezionato per lo sviluppo del sistema. Le alternative sono due: iniziare a realizzare i modelli di analisi secondo il paradigma non OO oppure ritardare il passaggio fino al modello di disegno. L’autore è dell’opinione che comunque è conveniente realizzare un maggior numero possibile di modelli OO, non fosse altro per motivi di ritorno degli investimenti. In ogni modo, qualche problema di utilizzo dello UML nasce durante la modellazione della struttura statica del sistema. In particolare, è necessario sia ricorrere ad alcuni artefici, sia limitare le tipologie di relazioni utilizzabili. In ogni modo, anche qualora il sistema da realizzare non sia fondato sul paradigma OO, l’utilizzo dello UML risulta una scelta vincente: migliore ritorno degli investimenti, utilizzo di strumenti “più standard”, maggiore supporto di tool commerciali, preparazione per la reingegnerizzazione.

Appendice

B

UML e la modellazione di basi di dati non OO Introduzione Una delle lacune più serie imputate allo UML consiste nella mancanza di uno strumento formale per il disegno dello schema della base dati. Ora, esistono diverse tipologie di sistemi di gestione delle basi di dati (DBMS, Data Base Management System): relazionali, a oggetti, reticolari, gerarchici e funzionali. Gli unici a non risentire della lacuna sono quelli basati sul paradigma OO. Per questi la rappresentazione dello schema della base dati è ottenibile attraverso il formalismo dei diagrammi delle classi. Lo stesso schema dovrebbe essere derivabile direttamente dal modello a oggetti del dominio e dovrebbe consistere in un opportuno sottoinsieme di package del modello di disegno. Il problema invece si pone per il disegno dello schema di base dati fondati sulle altre tipologie di DBMS. Ma è proprio vero che lo UML non fornisce alcuno strumento per il disegno dello schema di basi di dati non fondate sul paradigma OO? La risposta non è così immediata. Se da un lato è vero che non esiste uno specifico strumento, è altresì vero che lo UML fornisce una serie di strumenti di estensione (stereotipi, valori etichettati e vincoli) che permettono di specializzarne l’utilizzo. Dallo UML 1.4, l’insieme di nuovi elementi introdotti per specializzarne l’utilizzo dovrebbe essere organizzato opportunamente in un apposito profilo di estensione.

Nei seguenti paragrafi, per questioni di praticità e di maggiore diffusione, viene considerata esclusivamente la tipologia di base dati relazionale. Chiaramente

2

Appendice B. UML e la modellazione di basi di dati non OO non si intende affrontare l’argomento in maniera esaustiva, ma si vuole fornire una serie di idee al fine di rendere i lettori interessati all’argomento in grado di definire un profilo atto a risolvere le proprie necessità di disegno di schemi di basi di dati.

L’esempio presentato è tratto dal modello a oggetti del Capitolo 8. Questo dovrebbe favorire il conseguimento dell’obiettivo previsto: fornire elementi per la modellazione di un profilo per il disegno delle basi dati riducendo al minimo le nozioni da presentare relative all’area oggetto di analisi.

Tabelle In primo luogo è necessario selezionare lo strumento dello UML più idoneo per il disegno e la rappresentazione di schemi di basi di dati. In questo caso non vi sono molte possibilità: si tratta di un diagramma statico di struttura focalizzato su astrazioni di tipo logico. Pertanto il formalismo da considerare è quello dei diagrammi delle classi. Logica conseguenza è l’utilizzo di un apposito stereotipo dell’elemento classe (denominato «table») per identificare le singole tabelle. Il dipartimento degli attributi si presta a essere utilizzato come luogo naturale in cui specificare le colonne della tabella (queste, anche se non evidenziato esplicitamente, sono stereotipi dell’elemento UML attribute denominati «column»). Per quanto attiene al dipartimento dedicato ai metodi, per il momento, può essere trascurato (considerare fig. 1).

Figura 1 — Utilizzo dello stereotipo «table» dell’elemento classe.

«table»

USER USR_LOGIN : char[12] USR_NAME : varchar[40] USR_SURNAME : varchar[40] USR_BIRTH : date USR_TITLE : char[15] USR_STATUS_ID : char[1] USR_NOTE : varchar[150]

UML e ingegneria del software: dalla teoria alla pratica

3

È buona norma per il disegno della base dati, attribuire un identificatore univoco alle tabelle, da utilizzarsi come prefisso per i nomi delle colonne. Nel caso in figura si è utilizzato per la tabella USER l’identificatore USR. Questo agevola il compito di riferirsi univocamente ai campi, senza dover necessariamente specificare sempre la tabella di appartenenza.

Relativamente alle colonne, non ha interesse mostrare esclusivamente nome, tipo e dimensione, ma anche tutta una serie di informazioni supplementari. Per esempio, tipicamente, è importante sapere se si tratta o meno di una chiave primaria, se è ammesso il valore null, e così via. Queste informazioni si prestano a essere specificate per mezzo dei meccanismi di estensione forniti dallo UML: stereotipi e valori etichettati (tagged value, cfr. Cap. 2). Si inizi con il considerare le informazioni più semplici. In primo luogo si può considerare che per default nessun campo ammetta valori null e che l’eventualità che ciò sia possibile venga evidenziata attraverso il valore etichettato {null=true}, o più semplicemente {null}, posto a fianco del campo. Per quanto riguarda l’indicazione di chiavi primarie (primaryKey), chiavi importate o straniere (foreignKey) e secondarie (secondaryKey), un buon sistema consiste nel considerare nuovi stereotipi specializzanti l’elemento «column». Il nome di tali stereotipi potrebbe essere, rispettivamente: «primaryKey», «foreignKey» e «secondaryKey». Si ricordi brevemente che la relazione tra due tabelle, nelle basi dati relazionali, si ottiene esportando la chiave primaria di una tabella nell’altra, in cui prende il nome di chiave straniera (foreignKey). Ora un primo problema è relativo al fatto che, spesso, le chiavi di una tabella possono essere composte da più colonne. Questo problema può essere risolto utilizzando in associazione degli stereotipi appositi valori etichettati. Per esempio {xKeyPart=1} , {xKeyPart=2}, … dove il carattere x andrebbe sostituito con le lettere p, f o s a seconda che si tratti, rispettivamente, di chiave primaria, straniera o secondaria. Chiaramente qualora non vi sia specificato alcun valore etichettato, va assunto che la chiave non sia composta, ovvero che sia costituita da un’unica colonna. Un secondo problema potrebbe nascere qualora ci fossero più chiavi.

Nel caso di chiavi primarie, a dire il vero, non dovrebbe mai accadere, altrimenti potrebbe essere il caso di aver prodotto un errato disegno della base dati. Situazioni del genere, tipicamente, sono conseguenza dell’inglobamento in un’unica tabella di diverse entità. Questa anomalia è riscontrabile dalla presenza di dipendenze funzionali (alcuni attributi dipendono da una chiave, un altro grup-

4

Appendice B. UML e la modellazione di basi di dati non OO po da un’altra, ecc.). La 3BCNF (terza forma normale di Boyce e Codd) si occupa proprio di verificare e quindi risolvere dipendenze funzionali. In ogni modo situazioni del genere potrebbero essere risolte strutturando ulteriormente i valori etichettati associate ai vari stereotipi: {pKey, id=A, part=1}, {pKey, id=A, part=2}, {pKey, id= B, part=1}, ecc. La situazione si complica decisamente. La notizia buona è che la necessità di ricorrere a tali livelli di dettaglio è poco frequente.

Quanto riportato per la chiave primaria rimane perfettamente valido per chiavi straniere e per chiavi secondarie di ricerca qualora si decida di evidenziarle (probabilmente è più corretto mostrare queste informazioni attraverso opportuni indici, come riportato successivamente). Per le chiavi straniere potrebbe essere opportuno riportare la tabella di provenienza. Questo risolverebbe il problema di dover specificare l’identificatore per distinguere le diverse chiavi straniere eventualmente presenti in una stessa tabella. Qualora poi una chiave straniera sia presente in diverse tabelle, dovrebbe essere sempre considerata come tabella di provenienza quella in cui recita il ruolo di chiave primaria. Dall’analisi del diagramma di fig. 2 è possibile effettuare alcune considerazioni. Nella tabella USER è stata evidenziata una chiave secondaria composta. Poiché è l’unica, è stato sufficiente riportare l’indicazione delle parti che la costituiscono ({part=1} e {part=2}). Nella modellazione, è sempre opportuno utilizzare un approccio flessibile: qualora non ci sia alcuna possibilità di confusione è appropriato non complicare troppo le cose. Più importante invece è l’anomalia presente nella tabella PROFILE. La prima parte della chiave primaria (PRF_USR_LOGIN) è contemporaneamente chiave straniera, informazione che però non emerge dal diagramma. Ciò è risolvibile adottando uno dei due approcci seguenti: Figura 2 — Utilizzo degli stereotipi primaryKey, foreignKey e secondaryKey. «table»

«table»

USER

PROFILE

«primaryKey»USR_LOGIN : char[12] «secondaryKey»USR_NAME : varchar[40] {part=2}

«primaryKey»PRF_USR_LOGIN : char[12] {part=1} «primaryKey»PRF_PRG : numeric[10] {part=2}

«secondaryKey»USR_SURNAME

PRF_DESCR : varchar[40] PRF_VALID_F : date PRF_VALID_T : char[15] «foreignKey»PRF_STATUS_ID : char[1] {PROFILE_STATUS}

: varchar[40] {part=1} USR_BIRTH : date USR_TITLE : char[15] «foreignKey»USR_STATUS_ID : char[1] {USER_STATUS} USR_NOTE : varchar[150] {null}

5

UML e ingegneria del software: dalla teoria alla pratica

Figura 3 — Introduzione dello stereotipo primaryFKey. «table»

«table»

USER

PROFILE

«primaryKey»USR_LOGIN : char[12] «secondaryKey»USR_NAME : varchar[40] {part=2} «secondaryKey»USR_SURNAME : varchar[40] {part=1}

USR_BIRTH : date USR_TITLE : char[15] «foreignKey»USR_STATUS_ID : char[1] USR_NOTE : varchar[150] {null}

{USER_STATUS}

«primaryFKey»PRF_USR_LOGIN : char[12] {USER, part=1} «primaryKey»PRF_PRG : numeric[10] {part=2}

PRF_DESCR : varchar[40] PRF_VALID_F : date PRF_VALID_T : char[15] «foreignKey»PRF_STATUS_ID : char[1] {PROFILE_STATUS}

«table»

«table»

PASSWORD

SEC_CONTEXT

«primaryFKey»PSW_USR_LOGIN

: char[12]

{USER, part=1}

«primaryKey»SEC_ID

: numeric[15] {seq}

«primaryKey»PSW_PROG_NAME : numeric[1] {part=2} «secondaryKey»PSW_PASSWORD : varchar[50] «foreignKey»PSW_STATUS_ID : char[1] {PASS_STATUS}

«foreignKey»SEC_USR_LOGIN : char[12] {USER, part=1} «foreignKey»SEC_PRF_PRG : numeric[10] {PROFILE, part=2} SEC_DESCR : varchar[50] {null}

PSW_EXP : date

«foreignKey»SEC_DEP_ID : char[10] {DEPARTMENT} «foreignKey»SEC_SEC_GRP_ID : char[10] {SEC_GROUP}

1. utilizzare valori etichettati in associazione con le chiavi primarie per specificare la loro essenza di chiavi straniere; 2. dar luogo a un nuovo stereotipo («primaryFKey») che eredita sia da «primaryKey», sia da «foreignKey» (ereditarietà multipla). La soluzione ritenuta più opportuna è la seconda.

Brevemente, il diagramma di fig. 3 sancisce che un utente può disporre di diverse password (evidentemente solo una nello stato operativo) mentre ciascuna di esse può essere riferita solo a un utente. Questo inoltre può disporre di diversi profiles, di cui solo uno nello stato attivo, e ciascuno di essi è riferito a un solo utente. Infine un profilo è costituito da diversi contesti di sicurezza, in cui ciascuno di essi associa un gruppo di sicurezza a uno specifico dipartimento.

Attraverso l’utilizzo dei valori etichettati è possibile specificare tutta una serie di informazioni aggiuntive. Da tener presente che sebbene dati significativi forniscano importanti

6

Appendice B. UML e la modellazione di basi di dati non OO

informazioni, quando essi sono in eccesso sortiscono l’effetto contrario. Si corre il rischio di rendere il diagramma confuso, poco leggibile, e quindi si risolvono esclusivamente in elementi di disturbo: si passa da informazioni a meri dati. Nel diagramma di fig. 3 si è ritenuto utile evidenziare l’eventualità i cui i valori delle chiavi primarie siano generati dal DBMS oppure fornite (in qualche modo) dagli utenti. Nel primo caso, per esempio con il DBMS Oracle, questo fornisce il meccanismo dei sequence per generare valori sequenziali. Pertanto, qualora una chiave principale sia un numero sequenziale generato dal sistema, potrebbe essere utile specificare un apposito valore etichettato {seq}.

Relazioni Secondo le prescrizioni delle basi di dati relazionali, le relazioni tra tabelle si ottengono attraverso l’esportazione delle chiavi primarie. In particolare, se una tabella è relazionata a un’altra, è necessario inserire la chiave primaria di quest’ultima nella prima, nella quale prende il nome di chiave straniera. Per esempio nella tabella PROFILE è presente l’identificatore univoco dell’utente cui appartiene.

Figura 4 — Relazioni tra oggetti e tra tabelle in database relazionali. Da notare che se nella relazione che unisce la classe User a quella Profile non fosse stato presente il vincolo di navigabilità, anche gli oggetti istanza della classe Profile avrebbero dovuto memorizzare l’indirizzo dell’unico oggetto User di appartenenza. User login : string name : string surname : string dateOfBirth : Date stato : UserStatus ...

Profile 1

*

indirizzo degli oggetti

id : string descr : string status : ProfileStatus validFrom : Date validTo : Date ...

«table»

«table»

USER

PROFILE

«primaryKey»USR_LOGIN : char[12] «secondaryKey»USR_NAME : varchar[40] {part=2} «secondaryKey»USR_SURNAME : varchar[40] {part=1}

USR_BIRTH : date USR_TITLE : char[15] «foreignKey»USR_STATUS_ID : char[1] {USER_STATUS} USR_NOTE : varchar[150] {null}

chiave univoca

«primaryFKey»PRF_USR_LOGIN : char[12] «primaryKey»PRF_PRG : numeric[10]

{USER, part=1}

{part=2}

PRF_DESCR : varchar[40] PRF_VALID_F : date PRF_VALID_T : char[15] «foreignKey»PRF_STATUS_ID : char[1] {PROFILE_STATUS}

7

UML e ingegneria del software: dalla teoria alla pratica

Figura 5 — Utilizzo della relazione di associazione per visualizzare legami tra tabelle. «table»

«table»

USER

PROFILE

«primaryKey»USR_LOGIN

: char[12] : varchar[40] {part=2} : varchar[40] {part=1}

«primaryFKey»PRF_USR_LOGIN

«secondaryKey»USR_NAME

1

«secondaryKey»USR_SURNAME

USR_BIRTH : date USR_TITLE : char[15] «foreignKey»USR_STATUS_ID : char[1] {USER_STATUS} USR_NOTE : varchar[150] {null}

0..n

: char[12] {USER, part=1} : numeric[10] {part=2} PRF_DESCR : varchar[40] PRF_VALID_F : date PRF_VALID_T : char[15] «foreignKey»PRF_STATUS_ID : char[1] {PROFILE_STATUS}

«primaryKey»PRF_PRG

1 1

0..n

1

0..n

«table»

PASSWORD «primaryFKey»PSW_USR_LOGIN

: char[12] {USER, part=1} «primaryKey»PSW_PROG_NAME : numeric[1] {part=2} «secondaryKey»PSW_PASSWORD : varchar[50] «foreignKey»PSW_STATUS_ID : char[1] {PASS_STATUS} PSW_EXP : date

1..n «table»

SEC_CONTEXT «primaryKey»SEC_ID

: numeric[15] {seq} : char[12] {USER, part=1} «foreignKey»SEC_PRF_PRG : numeric[10] {PROFILE, part=2} SEC_DESCR : varchar[50] {null} «foreignKey»SEC_DEP_ID : char[10] {DEPARTMENT} «foreignKey»SEC_SEC_GRP_ID : char[10] {SEC_GROUP}

«foreignKey»SEC_USR_LOGIN

Da notare che il legame si ottiene con un criterio opposto rispetto alla modellazione a oggetti: in questo caso gli indirizzi dei profili appartenenti a uno specifico utente sono presenti nell’oggetto utente. Ora l’impostazione di appositi valori etichettati a fianco dei vari attributi potrebbe essere già di per sé sufficiente per specificare le varie relazioni tra le tabelle. Ciò nonostante si preferisce mostrare più esplicitamente tali legami al fine di aumentare il grado di chiarezza e di immediatezza di fruizione del modello. A tal proposito ci sono alcune alternative circa la tipologia di relazione da utilizzare. In particolare è possibile ricorrere alle relazioni di dipendenza o di associazione. Quantunque esistano valide argomentazioni per entrambe le soluzioni, l’autore preferisce la seconda. A sostegno di questa scelta esistono diversi motivi. In primo luogo si tratta di un legame strutturale, poi è possibile evidenziare eventuali navigabilità in entrambe le direzioni, si può mettere in luce la molteplicità, si può associare un nome. Per quanto concerne la navigabilità, se una tabella esporta la propria chiave primaria in un’altra, evidentemente dai record di quest’ultima è possibile navigare in quelli della prima, ma non viceversa. Eventualmente è possibile specificare sulla relazione i campi che rendono possibile la navigazione. Si tratterebbe però di un’informazione ridondante, già abbondantemente specificata nelle classi.

8

Appendice B. UML e la modellazione di basi di dati non OO

Per concludere il presente paragrafo, vale la pena di ricordare brevemente che nei database relazionali non è possibile rappresentare relazioni (n,n). Ciò è ottenibile per mezzo dell’introduzione di opportune tabelle di legame che decompongano relazioni (n,n) in due relazioni (n,1) e (1,n).

Indici Brevemente, gli indici sono delle strutture create sulle tabelle al fine di migliorare le prestazioni della ricerca. Questo miglioramento delle prestazioni si ripercuote su operazioni di selezione, aggiornamento e cancellazione di record. I DBMS normalmente creano automaticamente indici per la chiave primaria. È opportuno che il disegnatore della base dati dichiari eventuali indici per altri campi utilizzati per la selezione dei record. Chiaramente la creazione e manutenzione degli indici ha un costo e quindi, prima di creare un indice, andrebbero considerati fattori come la dimensione della tabella a cui si riferirebbe il nuovo indice, quanto le performance siano stringenti, quanto frequentemente dovrebbe venir eseguita l’operazione migliorata dall’introduzione dell’indice, ecc. Gli indici possono essere creati per un singolo campo o per un insieme. Eventualmente è possibile specificare funzioni che agiscono su tali campi, come per esempio UPPER() (trasforma i caratteri in maiuscolo). Le informazioni relative agli indici possono essere utili, ma sicuramente non sono di importanza fondamentale. Anche in questo caso è possibile ricorrere a diverse alternative. Le principali consistono nel ricorrere a • ulteriori stereotipi dell’elemento classe (etichettati «index») per mostrare tutti i dettagli degli indici associati alla tabella a cui si riferiscono. Questo offre il vantaggio di mostrare un maggior numero di informazioni. Gli svantaggi invece sono che il diagramma diventa molto confuso (ogni tabella ne prevedrebbe altre per gli indici), non è possibile decidere se visualizzare o meno queste informazioni, ecc.; • utilizzare la sezione dedicata ai metodi, assumendo che si tratti di indici. Questa soluzione presenta l’unico svantaggio di non poter mostrare tutte le informazioni relative agli indici. I vantaggi sono invece che è possibile mantenere il diagramma elegante e chiaro, è possibile decidere se visualizzare o meno le informazioni relative agli indici (escludendo o visualizzando la sezione delle operazioni), e il diagramma è più facile da produrre e manutenere. Chiaramente la soluzione ritenuta migliore è la seconda. Dall’analisi di fig. 6 è possibile notare che la tabella degli USER prevede due indici: uno implicito relativo alla chiave univoca USR_LOGIN e un secondo indice utilizzato per poter reperire gli utenti in base a cognome e nome. Per quanto concerne la tabella PROFILE, si

9

UML e ingegneria del software: dalla teoria alla pratica

Figura 6 — Visualizzazione degli indici.

«table» «table»

USER : char[12] «secondaryKey»USR_NAME : varchar[40] {part=2} «secondaryKey»USR_SURNAME : varchar[40] {part=1} USR_BIRTH : date USR_TITLE : char[15] «foreignKey»USR_STATUS_ID : char[1] {USER_STATUS} USR_NOTE : varchar[150] {null}

PROFILE

«primaryKey»USR_LOGIN

Indexes «index»UPPER(USR_NAME), UPPER(USR_NAME)

«primaryFKey»PRF_USR_LOGIN

1

0..n

: char[12] {USER, part=1} : numeric[10] {part=2} PRF_DESCR : varchar[40] PRF_VALID_F : date PRF_VALID_T : char[15] «foreignKey»PRF_STATUS_ID : char[1] {PROFILE_STATUS}

«primaryKey»PRF_PRG

Indexes «index»PRF_USR_LOGIN,

PRF_PRG, PRF_STATUS_ID

hanno nuovamente due indici: quello relativo alla chiave primaria (PRF_USR_LOGIN e PRF_PRG) e un altro utilizzato per selezionare, per uno specifico utente, il profilo di che si trova in un determinato stato (per esempio ACTIVE).

I trigger I gestori dei database permettono di specificare operazioni da eseguire qualora si verifichino particolari eventi: inserimento, aggiornamento e cancellazione di un record. Pertanto, alcune volte potrebbe essere utile mostrare la presenza di tali procedure nel modello dello schema della base dati. Qui la situazione si complica. In primo luogo bisogna identificare un apposito dipartimento. Sebbene le direttive dello UML prevedano la possibilità di definire dipartimenti supplementari da associare all’elemento classe, raramente i tool supportano tale funzionalità. L’unica alternativa pertanto consiste nel condividere il dipartimento dedicato ai nomi con i vari metodi. Al fine di evitare confusione, è possibile utilizzare gli stereotipi «index» per evidenziare indici e «trigger» per i trigger. Questi ultimi possono essere esclusivamente onInsert, onUpdate e onDelete. Ora, per quanto attiene alla specificazione dei contenuti, si tratterebbe del problema analogo di dettagliare il comportamento dei metodi. Un’alternativa potrebbe essere quella di utilizzare appositi campi note. Se da un lato questa tecnica permetterebbe di ottenere il risultato voluto, dall’altro renderebbe il diagramma veramente complicato da leggere. Le uniche possibilità percorribili si riducono a specificare il contenuto dei trigger in apposite sezioni non visualizzabili, prevederne unicamente la presenza oppure non specificarli del tutto. Probabilmente l’alternativa migliore è la seconda: essere consapevoli della presenza di un trigger è comunque situazione migliore rispetto a non averne alcuna conoscenza.

10

Appendice B. UML e la modellazione di basi di dati non OO

Figura 7 — Visualizzazione trigger.

«table» «table»

USER «primaryKey»USR_LOGIN

: char[12]

«secondaryKey»USR_NAME : varchar[40] {part=2} «secondaryKey»USR_SURNAME : varchar[40] {part=1}

USR_BIRTH : date USR_TITLE : char[15] «foreignKey»USR_STATUS_ID : char[1] {USER_STATUS} USR_NOTE : varchar[150] {null} «index»UPPER(USR_NAME), «trigger»onInsert() «trigger»onUpdate()

PROFILE

UPPER(USR_NAME)

1

0..n

«primaryFKey»PRF_USR_LOGIN : char[12] {USER, part=1} «primaryKey»PRF_PRG : numeric[10] {part=1}

PRF_DESCR : varchar[40] PRF_VALID_F : date PRF_VALID_T : char[15] «foreignKey»PRF_STATUS_ID : char[1] {PROFILE_STATUS} «index»PRF_USR_LOGIN,

PRF_PRG, PRF_STATUS_ID

«trigger»onInsert() «trigger»onUpdate()

Diverse architetture prevedono un funzionamento basato sulla tecnica dell’optmistic locking (blocco ottimistico). In altre parole quando un utente richiede di reperire un record per poterlo modificare, questo non viene bloccato (lock) inibendo (temporaneamente) altre scritture. In sostanza si assume che, nel lasso di tempo tra il momento in cui un utente seleziona un record e quello in cui conferma gli aggiornamenti (oppure il lock viene rimosso per time-out), il record in questione non venga modificato da altri processi. Questa assunzione, con le giuste cautele e i dovuti controlli, è legittima e particolarmente utile, specie quando si realizzano sistemi web-based. Però è necessario realizzare una serie di tecniche atte ad assicurarsi che le modifiche apportate non vadano a ricoprire eventuali altre effettuate nel lasso di tempo di cui sopra. Al fine di poter effettuare tale controllo, una tecnica è quella di inserire una colonna artificiale per ogni tabella aggiornabile concorrentemente atta a memorizzare il timestamp dell’ultimo aggiornamento. Pertanto la selezione di un record comporta la memorizzazione del l’informazione relativa all’ultimo aggiornamento (timestamp). A questo punto, per assicurarsi che il record non sia stato aggiornato tra il momento in cui viene selezionato e quello in cui deve essere modificato, è sufficiente verificare che il timestamp letto non sia cambiato. Volendo sfruttare questa tecnica, risulta una buona idea delegare al DBMS la gestione dei timestamp. A tal fine è sufficiente specificare triggers da attivare all’atto dell’inserimento e dell’aggiornamento dei record.

UML e ingegneria del software: dalla teoria alla pratica

11

Conclusioni In questa Appendice si è mostrato un semplice esempio di profilo utilizzabile per modellare, attraverso lo UML, strutture di base dati. Lungi dal voler considerare la trattazione esaustiva, si è pensato di fornire alcuni spunti utili per realizzare un profilo formale, funzionale alle peculiari necessità dei vari ambienti. Si è anche dimostrato che, se da un lato è vero che lo UML non prevede alcun meccanismo formale per disegnare e rappresentare lo schema delle basi dati di sistemi non OO, dall’altro è possibile aggirare tale lacuna creando appositi profili tramite un intelligente utilizzo dei meccanismi di estensione standard. Da notare che qualora esistesse un profilo di questo tipo, approvato dallo OMG, potrebbe essere possibile, attraverso l’utilizzo di un qualsiasi tool, generare direttamente gli script per la creazione e la gestione della base dati.

Appendice

C

Il profilo EJB Si approfondisce nella presente appendice quanto illustrato per sommi capi al termine del Capitolo 2. Prima di trattare approfonditamente il profilo EJB, è riportata una breve disamina della tecnologia EJB (Enterprise JavaBeans).

Enterprise Java Beans: concetti di base Benché l’illustrazione dell’architettura EJB esuli completamente dagli obiettivi del presente testo e si tratti di una tecnologia più o meno approfonditamente conosciuta da tutti i tecnici appartenenti alla comunità Object Oriented, è doverosa una brevissima descrizione dei concetti citati più frequentemente. L’architettura EJB è una tecnologia Serverside basata su componenti per lo sviluppo e l’installazione di applicazioni business organizzate in componenti distribuiti. L’utilizzo di tale tecnologia permette di realizzare sistemi scalabili, transazionali, multistrato, sicuri, e così via, senza avere l’incombenza di dover progettare e realizzare una complicata infrastruttura. Ciò rende possibile realizzare rapidamente sistemi distribuiti a oggetti, con rischi notevolmente ridotti e con l’enorme vantaggio che il sistema prodotto, qualora le direttive fornite dalla Sun Microsystem siano rispettate, è in grado di essere eseguito virtualmente su qualsiasi piattaforma che supporti le specifiche EJB (write once run anywhere). Un Enterprise Java Bean è un componente software, operante nel lato server, che può essere distribuito in un ambiente multi-tier. Tipicamente risulta costituito da diversi oggetti, sebbene la struttura interna sia “nascosta” all’oggetto client (altro EJB, Servlet, JSP, Applet, ecc.) per mezzo di opportuna interfaccia. Gli oggetti client non possono richiedere direttamente all’EJB l’esecuzione dei metodi esposti, bensì devono necessariamente passare attraverso un apposito oggetto (detto EJB Object, oggetto EJB). Tale oggetto, pertanto, per poter svolgere il proprio ruolo di mediatore, deve clonare tutti i

2

Appendice C. Il profilo EJB

metodi business che il componente EJB fornisce, i quali vengono dichiarati esplicitamente dal componente in un’apposita interfaccia denominata Remote Interface (interfaccia remota). L’oggetto cliente, per ovvi motivi, non può neanche istanziare direttamente gli Oggetti EJB (per esempio potrebbero risiedere in un server diverso da quello in cui è in esecuzione il client) e pertanto necessita di acquisirli. Ciò avviene richiedendo la loro “fornitura” a un altro oggetto — detto Home Object che funge da factory di EJB Object — il quale è gravato da altre responsabilità oltre alla creazione, come la ricerca e la rimozione degli EJB Object. A questo punto entra in gioco un’altra interfaccia detta Home Interface (interfaccia casa), nella quale devono essere dichiarati i metodi per creare, reperire e distruggere gli oggetti EJB. Il tutto poi è soggetto alla gestione dell’EJB Container (contenitore EJB) il quale fornisce i servizi impliciti ai vari componenti EJB; in altre parole fornisce l’ambiente nel quale i componenti EJB vengono eseguiti. Il Container a sua volta dovrebbe funzionare nell’ambiente fornito da un apposito EJB Server. Tipicamente gli oggetti client dialogano sempre con il Container, il quale interagisce con i vari EJB attraverso opportuni metodi dichiarati nelle interfacce di cui sopra. Si tratta di un vero e proprio mediatore “invisibile”, che assolve alle responsabilità di far comunicare i client con gli EJB, coordinare l’esecuzione delle transazioni, provvedere i servizi di persistenza e di sicurezza, gestire il ciclo di vita degli EJB, e svolgere altri compiti ancora. Esistono tre versioni di EJB: Entity, Session e MessageDriven. Gli Entity sono utilizzati per memorizzare informazioni che devono persistere alle sessioni utente, e si specializzano in bean a Container Managed Persistence, detti così poiché la gestione della relativa persistenza è demandata al Container, e bean a Bean Managed Persistence, caratterizzati dal possedere al proprio interno la logica per la gestione della persistenza, implementata “manualmente” dallo sviluppatore. I Session Bean sono utilizzati per modellare la business logic del sistema e quindi realizzano servizi invocabili dai clienti. Esistono due tipi di Session Bean: Stateful e Stateless. I primi sono in grado di memorizzare uno stato tra due invocazioni del client, mentre i secondi non possono farlo e pertanto si prestano a essere utilizzati da più clienti. I Message Driven Bean sono molto simili ai Session Bean e rappresentano una tipologia di consumatore asincrono di messaggi, invocati dal container al fine di notificare la ricezione di un messaggio JMS (Java Messaging System). Per questo motivo, i Message Driven Bean non necessitano né di Home né di Remote Interface. Una volta generate le varie classi che realizzano l’Enterprise Java Bean, le Home e le Remote Interface, il descrittore di dispiegamento (Deployment Descriptor) e le proprietà dell’EJB stesso, è necessario impacchettare tutti questi file in un’unica entità denominata EJB-JAR file, e comprimerlo secondo le direttive del formato ZIP.

3

UML e ingegneria del software: dalla teoria alla pratica

Profilo EJB La formulazione del profilo EJB è il risultato della collaborazione tra Rational, Sun Microsystem e Object Management Group (Java Specification Request JSR-26). Attualmente è disponibile la versione draft (UML Profile for EJB, pubblicata nel maggio 2001), il cui principale fautore è Jack Greenfield coadiuvato da un team formato da James Abbott, Loïc Julien, David Frankel, Scott Rich e molti altri ancora. Sebbene di recente formulazione, si tratta di un profilo già obsoleto sia perché basato sulla versione 1.3 delle specifiche ufficiali dello UML, sia perché la versione dell’architettura EJB presa in considerazione è la 1.1. Ciò nonostante si tratta di un documento molto importante e ben congegnato, il cui adeguamento alle ultime versioni delle specifiche UML ed EJB non dovrebbe richiedere eccessivo lavoro. Obiettivo del profilo è definire un mapping standard tra lo UML e l’architettura Enterprise JavaBeans (EJB) (fig. A.1). Il raggiungimento di tale obiettivo comporta: • la definizione di un approccio standard per la modellazione di sistemi basati sull’architettura EJB e quindi fondati sul linguaggio di programmazione Java; • il supporto delle esigenze pratiche comunemente incontrate nel disegno di sistemi EJB-based; • la definizione di una rappresentazione completa, accurata e non ambigua dei manufatti previsti dall’architettura EJB corredati dalla relativa semantica limitatamente ai fini della modellazione; Figura A.1 — Versione di un diagramma di collaborazione atto a illustrare il principio di funzionamento dell’architettura EJB. (Rielaborazione, da ED ROMAN, Mastering EJB, Wiley).

1. create new EJBObject

Container

HomeInterface

3. EJBObject reference

: HomeObject

aClient : Client

2. create EJBObject 4. invoke method

7. return the method result

RemoteInterface

: EJBObject

: Enterprise Java Bean

6. return the method result 5. acquire EJB and invoke the method

4

Appendice C. Il profilo EJB

• il supporto della costruzione di modelli di “assemblati” EJB frutto della combinazione di package creati utilizzando tool forniti da diversi produttori; • la semplificazione del disegno di sistemi basati sull’architettura EJB, in modo tale che gli “sviluppatori” da un lato non siano costretti a definire per conto proprio tecniche per la modellazione di sistemi EJB e dall’altro possano confidare in tecniche di reverse engineering compatibili con quanto disegnato; • il supporto alle diverse fasi del ciclo di vita del software che implicano manufatti attinenti ai componenti EJB (modello di disegno, di sviluppo, deployment e runtime); • l’attuazione dell’interscambio di modelli di manufatti EJB prodotti per mezzo di tool forniti da diverse ditte produttrici; • la compatibilità con tutte le API e gli standard del linguaggio di programmazione Java; • la compatibilità con i profili UML per le analoghe tecnologie, come CORBA e il modello a componenti CORBA La definizione del profilo EJB, è composto essenzialmente da due parti: il profilo UML vero e proprio e il descrittore UML. Come è lecito attendersi, la prima si occupa della semantica utilizzabile dall’architettura EJB, mentre la seconda introduce un modello UML relativo ai manufatti Java ed EJB che possono essere memorizzati in un file EJB-JAR. In merito alla prima parte è necessaria una breve considerazione. Dalla lettura del capitolo precedente si è appreso che uno degli obiettivi dello UML è supportare specifiche che risultino indipendenti da particolari linguaggi di programmazione o processi di sviluppo. Ciò chiaramente è molto vantaggioso in quanto rende lo UML idoneo a supportare plausibilmente tutti i linguaggi di programmazione. Però, al tempo stesso, in questo ambito si pone qualche problema giacché l’architettura EJB non è stata basata su un linguaggio di programmazione generale, bensì su uno ben preciso: Java. Ciò finisce per causare un primo ostacolo tra la definizione dello UML e il relativo utilizzo in sistemi EJB-based. In altre parole, la definizione del profilo EJB obbliga a definire, come requisito irrinunciabile, il mapping tra UML e costrutti e package del linguaggio Java utilizzati nell’architettura EJB (fig. A.2). Dall’analisi della fig. A.3, è invece possibile evidenziare come il profilo EJB vero e proprio sia costituito da due componenti, il modello di disegno e quello di implementazione. Per quanto riguarda il primo, contiene i diagrammi delle classi che descrivono le interfacce ed eventualmente le classi di implementazione di un Enterprise Java Bean. Il modello di disegno, a sua volta, è suddiviso in due viste: esterna e interna, ciò al fine sia di

5

UML e ingegneria del software: dalla teoria alla pratica

Figura A.2 — Il profilo EJB estende opportune sezioni del profilo Java.

«metamodel»

UML MOF

«profile»

Java

«profile»

EJB

Figura A.3 — Struttura del profilo EJB. La notazione utilizzata in figura (cerchio con inscritto il simbolo + ) rappresenta un’alternativa fornita dallo UML per mostrare relazioni di annidamento tra gli elementi. In questo caso, per esempio, si mostra che il modello EJB Design è incluso nel profilo EJB.

«profile» EJB

«class diagram»

«component diagram»

EJB Design Model

EJB Implementation Model

«class diagram»

«class diagram»

External view

Internal view

6

Appendice C. Il profilo EJB

incoraggiare a pensare il sistema in termini di componenti già durante le prime fasi del ciclo di vita del software, sia di assecondare la netta separazione tra la specifica di un componente EJB e la relativa implementazione. La componente esterna, come logico attendersi, consente di specificare un EJB dall’esterno ossia dal punto di vista dell’oggetto cliente. A tal fine è importante la definizione delle interfacce home e remote istanze dell’apposito stereotipo dell’elemento Class1 (EJB Home Interface ed EJB Remote Interface). Nell’altra vista, l’attenzione viene spostata alla definizione interna dell’EJB o, se si vuole, alla prospettiva dello sviluppatore. L’EJB viene modellato per mezzo di un opportuno stereotipo dell’elemento UML Subsystem contenente sia le interfacce del punto precedente, sia ulteriori stereotipizzazioni dell’elemento Class atte a descrivere l’implementazione dell’EJB. Il modello di implementazione EJB (EJB Implementation Model) contiene diagrammi dei componenti atti a descrivere i manufatti fisici in cui sono memorizzati gli elementi logici di un EJB. In questo ambito viene utilizzato un apposito stereotipo dell’elemento component per rappresentare classi Java, file di risorse e uno stereotipo dell’elemento package per il file EJB-JAR. Riassumendo brevemente, un Enterprise Java Bean viene modellato: • logicamente o come stereotipo dell’elemento Class (nella vista esterna) o dell’elemento Subsystem (nella vista interna); • fisicamente come uno stereotipo dell’elemento Component. Spesso nella definizione formale dei vari profili si incontra il concetto di Virtual MetaModel (VMM, metamodello virtuale), utilizzato per indicare insiemi di estensioni UML. In effetti, ogniqualvolta si introduce un nuovo stereotipo si estende il metamodello base e quindi si finisce per disporre di un nuovo metamodello più potente. In questo contesto quindi ha senso parlare di metamodello virtuale. Ciò, tra l’altro, dovrebbe chiarire anche perché il MOF viene descritto come framework. Nella pratica, a meno di esigenze di elevato grado di formalità, come la definizione dei profili per esempio, legittimamente i vari stereotipi vengono utilizzati all’uopo senza perdere tempo nel fornirne la definizione rigorosa e tanto meno modificando il metamodello di base dello UML. Ad ogni modo, l’organizzazione del VMM relativo al profilo EJB è rappresentata nella fig. A.4. Tra i primissimi elementi definiti nel profilo, necessari nella vista esterna del modello di disegno, compaiono le interfacce EJB corredate dai relativi metodi. La definizione di tali

1

In questo contesto, quando si parla di stereotipo di un elemento UML, si fa riferimento alla relativa metaclasse

appartenente al sottopackage Core del package Foundation del MOF.

7

UML e ingegneria del software: dalla teoria alla pratica

Figura A.4 — Package del metamodello virtuale definito dal profilo EJB.

java

util

jar

lang

javax

ejb ejb

elementi però non sempre può avvenire direttamente, poiché spesso è necessario specificare formalmente gli elementi del linguaggio Java da cui derivano. Pertanto, come già riportato precedentemente, nella definizione formale del profilo EJB è stato necessario specificare tutte, e solo, le parti del linguaggio Java utilizzate dall’architettura EJB. Per esempio è stato necessario definire formalmente la corrispondenza tra il concetto di classe nel linguaggio Java e quello dello UML. In particolare, la sintassi del costrutto Class di Java può essere descritta rigorosamente come segue: [visibility] [modifiers] class Identifier [extends ClassType] [implements TypeList] { {ClassMember} {InterfaceMember} {FieldMember} {MethodMember} } visibility = public/proteced/private modifiers = [abstract][static][final][strictfp] TypeList = InterfaceType/ TypeList, InterfaceType

8

Appendice C. Il profilo EJB

Chiaramente una classe Java corrisponde all’equivalente concetto dello UML, però è necessario dar luogo a tutta una serie di mapping, quali: • la visibilità del linguaggio Java corrisponde all’ElementOwnership presente nel Core Package dello UML. In Java quando la visibilità è omessa si intende a livello di package mentre, con la versione 1.4, in UML viene rappresentata attraverso il carattere tilde (~); • per ciò che concerne i modificatori, si hanno le seguenti corrispondenze: – a b s t r a c t corrisponde alla proprietà i s A b s t r a c t dello UML (GeneralizableElement); – static viene introdotto per mezzo del Tagged Value booleano JavaStatic; final corrisponde alla proprietà i s L e a f dello UML (GeneralizableElement); – s t r i c t f p viene introdotto per mezzo del Tagged Value booleano JavaStrictfp; – Identifier corrisponde ai nomi delle classi UML; – ClassType si collega ai nomi degli elementi specializzati dall’UML; – InterfaceType come al punto precedente; – ClassMember corrisponde alla definizione di classe Java; – InterfaceMember è relativo alla definizione di interfaccia Java (definita nel profilo); – FieldMember corrisponde all’elemento Java Field (definito nel profilo); – MethodMember è associato all’elemento Java Method (definito nel profilo). Ovviamente oltre queste corrispondenze, la definizione formale implica la dichiarazione di tutta una serie di vincoli; per esempio una classe Java può specializzare al massimo un’altra classe Java, una classe Java può realizzare un numero qualsiasi di interfacce Java, una classe non può essere dichiarata final e abstract allo stesso tempo, una classe deve essere definita necessariamente astratta se almeno uno dei suoi metodi è dichiarato astratto, e così via.

UML e ingegneria del software: dalla teoria alla pratica

9

Di seguito, l’esame degli stereotipi definiti ritenuti più interessanti. Si tenga presente che l’organizzazione dei package a cui si fa riferimento, a meno di diverse indicazioni, è quella del profilo EJB e non del linguaggio di programmazione Java. Ciò dovrebbe essere evidente anche dall’utilizzo dei due punti come separatore (standard UML). Nel package j a v a : : l a n g è presente lo stereotipo di interfaccia Java (), che specializza quello denominato 2: stereotipo predefinito dell’elemento classe. Da tener presente che mentre in Java i nomi completamente specificati (fully-qualified) dei package si ottengono separando il nome dei package genitori da quello dei figli per mezzo di un carattere “punto” (per esempio java.lang), nello UML il separatore è dato da due caratteri “due punti” (java::ejb). La definizione formale dei metodi delle interfacce EJB è ottenuta attraverso i seguenti stereotipi della metaclasse Operation appartenenti al package javax::ejb : , , . L’essere stereotipi della metaclasse Operation indica che tali elementi possono essere utilizzati per enfatizzare metodi con particolare significato per gli EJB, rispettivamente metodi di creazione, reperimento e remoti. Per quanto concerne le interfacce, sono definite la , , e .3 La struttura delle precedenti interfacce è fornita nella fig. A.5. Per terminare l’elenco degli stereotipi dell’External View, va menzionato , specializzazione di Usage, stereotipo della relazione di dipendenza. Quest’ultimo viene utilizzato per indicare che un elemento necessita di un altro per la completa implementazione o operazione. Nella versione EJB, indica che il fornitore della relazione Usage è una classe EJB Primary Key necessaria per reperire EJB Entity Bean. Nella vista interna sono presenti diversi stereotipi, il che è abbastanza naturale considerando che il relativo scopo consiste nel descrivere il dettaglio della struttura interna degli EJB. I primi che si incontrano sono gli stereotipi della metaclasse Attributo (Attribute),

2

Si tratta di uno stereotipo che specifica un dominio di oggetti corredati dalle operazioni ad essi applicabili, senza

però definire l’implementazione fisica degli stessi oggetti. In particolare può essere considerato come una particolare versione della classica interfaccia in grado di contenere attributi e associazioni. Obiettivo di queste associazioni è di poter definire il comportamento dei tipi di operazioni senza definire l’implementazione dei dati coinvolti. 3

Non è presente una definizione di Home Interface per i Message Driven Bean, non solo per questioni di

aggiornamento del profilo, ma anche perché tali EJB non dispongono di questa tipologia di interfaccia, così come non dispongono di Remote Interface. Ciò è dovuto al fatto che i Message Driven Bean non possono essere invocati utilizzando un metodo remoto. Essi sono stati introdotti per processare messaggi asincroni e quindi la relativa invocazione deve avvenire da parte di un Messaging Client basato sull’architettura Java Messaging System.

10

Appendice C. Il profilo EJB

Figura A.5 — Interfacce EJB definite nel package javax::ejb (external view). La metaclasse Class appartiene al package Core, sottopackage del Foundation, del metamodello UML. type è uno stereotipo standard. EJBHomeInterface è una metaclasse astratta. «metaclass»

Class

«stereotype»

Type

«stereotype»

«stereotype»

EJBRemoteInterface

EJBHomeInterface

«stereotype»

«stereotype»

EJBSessionHomeInterface

EJBEntityHomeInterface

ejbSessionType : EJBSessionType

«enumeration»

EJBSessionType Stateful Stateless

e in particolare la versione “base” il quale evidenzia che il relativo attributo rappresenta un campo gestito dal Container (Container-Managed Field) di un EJB Entity Bean in un contesto di persistenza gestita dal container. Di questo stereotipo è prevista un’ulteriore estensione rappresentata da con significato piuttosto evidente: indica che il campo è una chiave primaria di un EJB Entity Bean nell’ambito di persistenza gestita dal container. Altri due stereotipi molto interessanti sono quelli etichettati con i nomi , : si tratta di estensioni della relazione di dipendenza denominata Abstraction (Astrazione). Questa relazione è utilizzata per indicare che due o più elementi rappresentano lo stesso concetto a diversi livelli di astrazione. Nel contesto del profilo EJB la relazione è utilizzata per indicare il legame tra interfacce (per esempio EJB Home Interface ) e relativa implementazione (EJB Implementation Class). Quest’ultimo, , rappresenta a

11

UML e ingegneria del software: dalla teoria alla pratica

sua volta uno stereotipo, in questo caso dell’elemento Class, utilizzato per indicare che la relativa classe implementa un’opportuna interfaccia EJB. Altri stereotipi molto importanti per l’Internal View sono quelli relativi all’elemento Subsystem. In particolare sono presenti gli stereotipi , e , come mostrato in fig. A.6. Per terminare l’esame degli stereotipi utilizzati nell’Internal View, si considerino gli elementi , specializzazione dello stereotipo Usage e , stereotipo della metaclasse associazione (Association). Per quanto riguarda il primo non c’è molto da aggiungere, mentre il secondo definisce un ruolo di sicurezza tra un attore UML e un EJB. Terminata l’analisi degli stereotipi del modello di disegno si passa a esaminare quelli legati al modello di implementazione. Il primo che si incontra è (package java::lang) che specializza lo stereotipo predefinito , utilizzato per indicare che un Component è un Java Class File.

Figura A.6 — Stereotipi dell’elemento Subsystem necessari per l’Internal View. Per questioni relative alla versione dell’architettura EJB presa in considerazione (1.1) la specializzazione EJBMessageDrivenBean non è ancora disponibile. Gli attributi evidenziati rappresentano Tagged Value descritti successivamente. Subsystem

«stereotype»

EJBEnterpriseBean ejbEnvEntries ejbNameInJAR : String ejbReferences ejbResources ejbSecurityRoles

«stereotype»

«stereotype»

«stereotype»

EJBSessionBean

EJBEntityBean

EJBMessageDrivenBean

ejbTransType : EJBTransType

ejbPersistenceType : EJBPersistenceType ejbReentrant : Boolean

ejbDestinationType

«enumeration»

«enumeration»

«enumeration»

EJBTransType

EJBPersistenceType

EJBPersistenceType

Bean Container

Bean Container

Topic Queue

12

Appendice C. Il profilo EJB

Un altro stereotipo, questa volta appartenente al package java::util::jar , è che serve per indicare che un package rappresenta un JAR. Ancora, nel package javax::ejb è presente lo stereotipo che specializza , con significato piuttosto intuitivo. Sempre nello stesso package è presente lo stereotipo , atto a indicare che un determinato componente rappresenta appunto un EJB Deployment Descriptor. Infine è presente la specializzazione della relazione Usage, denominata utilizzata per indicare che il “cliente” della relazione rappresenta un ejb-client-jar per l’EJBJAR rappresentato dal fornitore della relazione. Terminata l’analisi degli stereotipi, è necessario analizzare i vari Tagged Value. Una descrizione esaustiva nel contesto del presente paragrafo risulterebbe piuttosto tediosa e non sempre utile, considerando anche che molti possiedono un significato piuttosto intuitivo. Pertanto, di seguito, l’attenzione viene focalizzata esclusivamente sui Tagged Value ritenuti più interessanti e/o ricorrenti. Per esempio nel package java::lang sono definiti i valori etichettati JavaNative e JavaThrows, entrambi applicabili agli stereotipi della metaclasse Operation. Il primo è di tipo booleano e serve ad indicare se un metodo è nativo Java o meno, mentre il secondo è un array di stringhe atto a riportare la lista dei nomi delle eccezioni, separate dal carattere virgola (comma-delimited), scatenabili da un metodo. Un altro esempio di Tagged Value, definito sempre nello stesso package, è JavaFinal il quale con il suo valore booleano è utilizzato per indicare se un parametro è modificabile (false) o meno. Da tener presente che, ogniqualvolta compare un Tagged Value di tipo booleano, la convenzione sancisce che la relativa presenza implica un valore true (riportare JavaFinal = true o semplicemente JavaFinal è completamente equivalente), mentre l’omissione indica un valore false. Nell’analisi della vista esterna del modello di disegno EJB sono stati introdotti alcuni Tagged Value, come per esempio: • E J B S e s s i o n T y p e di tipo enumerato, associato allo stereotipo , il quale indica la tipologia del relativo Session Bean: stateful o stateless; • EJBNameInJar, il quale indica il nome utilizzato dell’EJB quando viene impacchettato nel file EJB-JAR. Tipicamente si utilizza il nome dell’EJB Remote Interface; • EJBEnvEntries, associato a qualsiasi EJB, ne indica, attraverso una lista di tuple (nome, tipo, valore) separate da virgola, le environment entries; • EJBResources, in maniera del tutto simile al Tagged Value precedente, indica le Resource Factory dell’EJB;

UML e ingegneria del software: dalla teoria alla pratica

13

• EJBReferences, associato a qualsiasi EJB, ne specifica, attraverso una lista di tuple (nome, tipo, home, remote) separate da virgola, gli altri EJB referenziati; • EJBSecurityRoles, associato a qualsiasi EJB, ne indica, attraverso una lista di tuple (nome, link) separate da virgola, il nome del ruolo di sicurezza in grado di invocare i metodi; • EJBTransType, dichiara se la gestione delle transazioni di un Session Bean è demandata al Container oppure è eseguita direttamente dal Bean; • EJBPersistenceType, si tratta di un tipo enumerato assolutamente analogo al precedente con l’unica differenza che si applica agli Entity Bean e non ai Session; Un altro Tagged Value molto interessante è denominato EJBTransAttribute , applicabile ai metodi. Esso sancisce la policy utilizzata per la gestione delle transazioni. In particolare i valori previsti dal tipo enumerato sono: NotSupported , Supports , Required, RequiredNew, Manadatory e Never. Brevemente il significato degli attributi transazionali è il seguente: • Non supportata (NotSupported), indica che l’EJB non può essere coinvolto in transazioni. Pertanto quella eventualmente attiva viene sospesa; • Mai (Never), specifica che l’EJB non può essere assolutamente coinvolto in transazioni. Pertanto se all’atto dell’invocazione esiste una transazione attiva, l’EJB stesso genera un’eccezione; • Richiesta (Required), l’EJB deve essere sempre eseguito nel contesto di una transazione. Se all’atto dell’invocazione è presente una transazione attiva, allora l’EJB la utilizza, altrimenti ne inizia una nuova; • Richiesta nuova (RequiredNew), l’EJB deve essere sempre eseguito nel contesto di una nuova transazione; • Supportata (Supports), sancisce che l’EJB è del tutto indifferente alle transazioni. Se ne esiste una attiva, il relativo stato rimane inalterato, altrimenti può proseguire nell’esecuzione senza transazioni attive; • Obbligatoria (Mandatory), richiede che una transazione sia già attiva prima dell’invocazione dell’EJB. In caso contrario un’eccezione viene scatenata.

14

Appendice C. Il profilo EJB

Figura A.7 — Vista esterna del modello di disegno di un Enterprise Session Bean relativo a una sessione utente. «EJBRemoteInterface»

UserSB +«EJBRemoteMethod» authenticate() +«EJBRemoteMethod» deposit() +«EJBRemoteMethod» getAccounts() +«EJBRemoteMethod» getBalance() +«EJBRemoteMethod» getCustomer() +«EJBRemoteMethod» getHistory() +«EJBRemoteMethod» payBill() +«EJBRemoteMethod» transfer() +«EJBRemoteMethod» withdraw()

«instantiate»

«EJBSessionHomeInterface»

UserSBHome +«EJBCreateMethod» create()

Figura A.8 — Vista esterna del modello di disegno di un Enterprise Entity Bean dedicato ai dati dei clienti.

CustomerKey

«EJBRemoteInterface»

Customer +«EJBRemoteMethod» addBankAccount() +«EJBRemoteMethod» getBank() +«EJBRemoteMethod» getBankAccounts() +«EJBRemoteMethod» getCustomerId() +«EJBRemoteMethod» getFirstName() +«EJBRemoteMethod» getLastName() +«EJBRemoteMethod» getState() +«EJBRemoteMethod» getTitle() +«EJBRemoteMethod» removeBankAccount() +«EJBRemoteMethod» setFirstName() +«EJBRemoteMethod» setLastName() +«EJBRemoteMethod» setTitle()

+CustomerKey() +equals( o : Object ) : boolean +hashCode() : int +CustomerKey(customerId : String, bankId : String) «EJBPrimaryKey» «EJBEntityHomeInterface»

PrimaryKey «instantiate»

+«EJBRemoteMethod» create() +«EJBRemoteMethod» findByPrimaryKey() +«EJBRemoteMethod» findByBankId()

A questo punto è finalmente giunta l’ora di riassumere i concetti esposti in chiave operativa attraverso il ricorso ai tanto amati esempi. Il sistema preso in considerazione è relativo ad un’ipotetica banca. Più precisamente si tratta del modello IBM ITSO Bank, riportato nel profilo EJB, utilizzato spesso anche dalla stessa IBM per illustrare ambienti runtime IBM e vari tool. Il primo modello considerato è relativo alla Design View. Si inizi con il focalizzare l’attenzione sulla vista esterna e in particolare su un primo componente dedicato alla sessione utente. Come è lecito attendere si tratta di un Enterprise Session Bean, del quale viene

UML e ingegneria del software: dalla teoria alla pratica

15

fornita la vista esterna, quella a cui sono interessati gli oggetti “cliente” (fig. A.7). Come si può ben notare, l’attenzione viene focalizzata esclusivamente sulla definizione delle interfacce, a tal fine sono utilizzati gli stereotipi e . Per ciò che concerne i metodi, gli stereotipi utilizzati sono e . Nel secondo esempio viene presentata la External View di un Enterprise Entity Bean atto a memorizzare i dati dei clienti della banca (fig. A.8). A differenza del caso precedente, lo stereotipo utilizzato per la Home Interface è la specializzazione proprio per indicare che si tratta di un Enterprise Entity Java Bean. Sono poi presenti gli stereotipi e al fine di enfatizzare la natura dei relativi metodi. Infine è presente lo stereotipo della relazione di dipendenza, per mostrare che l’oggetto CustomerKey fornisce appunto la chiave primaria per il reperimento degli Entity Bean relativi ai clienti (customer). A questo punto si passa all’Internal View. Da questo punto in poi viene preso in considerazione esclusivamente l’ Entity Bean mostrato nella fig. A.8. Come già illustrato precedentemente, sebbene possa sembrare piuttosto singolare, la vista interna degli EJB viene modellata utilizzando l’elemento Subsystem4 anziché Component. Si tratta di un elemento dello UML di utilizzo non frequente, e pertanto non molto conosciuto dai disegnatori. Verosimilmente questa scelta è dovuta alla definizione limitata di componente presente nella versione 1.3 dello UML. Già con la versione 1.4 le cose sono cambiate parecchio, quindi, alla luce delle nuove release dello UML, probabilmente il profilo EJB subirà importanti aggiornamenti.

4

Le metaclasse Subsystem, insieme a Model e Package (genitore degli altri due), rappresenta l’elemento di

maggiore interesse del package Model Management, dipendente dal Foundation, del metamodello UML. In prima analisi, tali elementi sono utilizzati per raggruppare unità, in qualche modo relazionate, necessarie ad altri ModelElements. In particolare ci si avvale dell’elemento Model per rappresentare diverse viste di uno stesso sistema. L’elemento Package è utilizzato, in modo del tutto intuitivo, nel contesto di un modello per raggruppare elementi di modellazione. Infine Subsystem permette di rappresentare ben definite unità comportamentali di un sistema. Come accennato poc’anzi, la metaclasse Subsystem specializza quella Package. Mentre quest’ultima rappresenta un meccanismo generico per organizzare gli elementi del modello, un Subsystem tratteggia un’unità comportamentale di un sistema nel modello. L’elemento Subsystem però non eredita unicamente da Package, ma anche da Classifier. Ciò gli permette di offrire interfacce, operazioni, ecc. Il suo contenuto è partizionato in due sezioni: elementi di specificazione e di realizzazione. Nella prima trovano posto le operazioni nel sottosistema, così come Use Case, State Machines, ecc. ossia tutti elementi che permettono di fornire elementi di specifica. Tipicamente un sottosistema viene rappresentato utilizzando la stessa notazione dell’elemento Package con in più il simbolo di fork. Chiaramente, nulla vieta di rappresentarlo come un Package con lo stereotipo .

16

Appendice C. Il profilo EJB

Figura A.9 — Vista interna del modello di disegno relativa all’Entity Bean Customer. «EJBEntityBean»

Customer specification elements

realization elements

CustomerKey «EJBPrimaryKey»

«EJBEntityHomeInterface»

«EJBRealizeHome»

CustomerHome «instantiate»

«EJBRemoteInterface»

Re JB «E

te » mo Re e z i al

«EJBImplementation»

CustomerBean

Customer

Figura A.10 — Illustrazione dell’utilizzo dello stereotipo .

«EJBEntityBean»

Customer

«EJBReference»

«EJBEntityBean»

BankAccount

Come si può notare dal diagramma di fig. A.9, l’elemento Subsystem si presta a essere utilizzato per descrivere i componenti EJB, in quanto dotato di una sezione relativa alle specifiche (Specification Elements) e di una efferente dalla realizzazione (Realization Elements). Il problema è che, al momento in cui viene scritto questo testo, non tutti i tool commerciali permettono di rappresentare sottosistemi secondo le direttive UML. Lo stereotipo , specializzazione di Usage, stereotipo della relazione di dipendenza, evidenzia la necessità di un EJB di accedere a un altro EJB durante la propria esecuzione. Per esempio la fig. A.10 mostra che l’Entity EJB Customer necessita di accedere all’EJB BankAccount.

17

UML e ingegneria del software: dalla teoria alla pratica

A questo punto è giunto il momento di rappresentare l’implementazione interna dell’Entity Bean. L’elemento introdotto nel diagramma di fig. A.11 è lo stereotipo nella sezione degli attributi della classe CustomerBean la cui descrizione è stata riportata precedentemente. Per concludere l’illustrazione dell’utilizzo dei vari stereotipi presentati nella prima parte, è necessario passare al modello di implementazione. Il primo stereotipo utilizzato è , specializzazione , stereotipo dell’elemento Package, all’interno del quale vengono raggruppati tutti file contenenti le classi Java con in più l’aggiunta del descrittore dell’EJB (Deployment Descriptor). Il tutto viene esemplificato in fig. A.12.

Figura A.11 — Vista interna dell’Entity Bean Customer. CustomerKey

«EJBImplementation»

CustomerBean

-bankId : String -customerId :String +CustomerKey() +CustomerKey(customerId:String, bankId : String) +equals(o : Object) : boolean +hashCode() : Int

-«EJBCmpField» customerId : String -«EJBCmpField» firstName : String -«EJBCmpField» lastName : String -«EJBCmpField» state : String -«EJBCmpField» title : String -«EJBCmpField» bankId : String

«EJBPrimaryKey»

«EJBEntityInterface»

CustomerHome «EJBRealizeHome» +«EJBCreateMethod» create() +«EJBCreateMethod» findByPrimaryKey() +«EJBCreateMethod» findByBankId()

«istantiate»

«EJBRemoteInterface»

Customer «EJBRealizeRemote» +«EJBRemoteMethod» addBankAccount() +«EJBRemoteMethod» getBank() +«EJBRemoteMethod» getBankAccount() +«EJBRemoteMethod» getCustomerId() +«EJBRemoteMethod» getFirstName() +«EJBRemoteMethod» getLarstName() +«EJBRemoteMethod» getTitle() +«EJBRemoteMethod» removeBankAccount() +«EJBRemoteMethod» setFirstName() +«EJBRemoteMethod» setLarstName() +«EJBRemoteMethod» setTitle()

+ejbLoad() +ejbPostCreate() +ejbActivate() +ejbPassivate() +ejbStore() +ejbCreate() +ejbRemove() +addBankAccount() +getBank() +getBankAccount() +getCustomerId() +getFirstName() +getLarstName() +getTitle() +removeBankAccount() +setFirstName() +setLarstName() +setTitle()

18

Appendice C. Il profilo EJB

Figura A.12 — Implementation View dell’Entity Bean Customer.

«EJB-JAR»

Customer «JavaClassFile»

«JavaClassFile»

CustomerBean

CustomerKey

«JavaClassFile»

Customer

META-INF

«JavaClassFile»

«EJBDescription»

CustomerHome

ejb-jar.xml

Da questa breve trattazione emerge che, sebbene il profilo EJB sia ancora in uno stadio piuttosto evolutivo — è necessario un processo di adeguamento sia per incorporare le direttive della versione 1.4 dello UML, sia per allinearlo alla versione 2.0 dell’architettura EJB — si tratta nel suo complesso di un ottimo strumento di supporto alla progettazione di sistemi Component-based utilizzanti l’architettura EJB. Il profilo non solo risolve il problema della rappresentazione dei manufatti (Artifact) previsti dall’architettura EJB, ma fornisce anche direttive su come affrontare il processo di sviluppo di sistemi Component-based. A tal fine il profilo si adegua ai processi di sviluppo dei sistemi, organizzando i vari manufatti previsti dalle diverse fasi, in opportuni modelli; in particolare sono previsti i modelli di disegno e implementazione. Il profilo è focalizzato essenzialmente sulle fasi di disegno e non su quelle di specifica. Il motivo è piuttosto ovvio: queste ultime devono essere generiche e non fondate su specifiche architetture come l’EJB: chiaramente devono assumere che il sistema verrà sviluppato secondo un particolare paradigma, ma non entrare assolutamente nei dettagli tecnici. Lo stesso discorso non può essere applicato al modello di disegno che, per forza di cose, deve essere realizzato in base alle direttive fornite dalla particolare architettura selezionata. Molto apprezzata è la suddivisione del modello di disegno nelle viste esterna e interna. Ciò risulta molto utile nel contesto di processi di sviluppo del software iterativi e incre-

UML e ingegneria del software: dalla teoria alla pratica

19

mentali: nelle prime iterazioni della fase di elaborazione è possibile concentrarsi sulla definizione del comportamento dei vari componenti e sulle relazioni con gli altri, rimandando a successive iterazioni i dettagli interni. La definizione formale di tale profilo permette di mantenere standard l’utilizzo dello UML anche per tecnologie specifiche quali l’architettura EJB. Ciò è molto importante non solo in chiave comunicativa (possibilità di far circolare più agevolmente i vari modelli, semplificazione del processo di inserimento di nuove risorse, ecc.) ma anche come direttiva per le aziende fornitrici dei vari tool (garanzia che il reverse engineering sia consistente con il normale processo, forward engineering, possibilità di comunicazione tra i modelli prodotti con tool diversi ecc.). Qualche perplessità permane per ciò che concerne il dettaglio di vari manufatti. Sebbene le funzioni di reverse engineering potranno fornire un notevole contributo nello snellire il lavoro; la rappresentazione di tutti i vari elementi potrebbe comunque risultare gravosa. Un’altra constatazione è che la stessa definizione del profilo è, se c’è ne fosse ancora bisogno, l’ennesima dimostrazione dell’efficacia, dell’eleganza e della flessibilità del metamodello UML. In effetti è stato possibile specializzarlo, dando luogo al famoso VMM, senza dover assolutamente modificare l’architettura di base. Il profilo EJB rappresenta indubbiamente un primo passo nella direzione della definizione di standard di notevole importanza nella progettazione di sistemi EJB-based.

Appendice

D

Glossario Gran parte del glossario è tratta direttamente dalle specifiche ufficiali OMG dello UML e pertanto la definizione dei termini va inquadrata in tale contesto. Le convenzioni sono le seguenti: Contrario Indica un insieme di concetti la cui semantica è antitetica o comunque utilizzata in modo significativamente diverso da quello del concetto illustrato. Consultare Rimanda il lettore ad altri concetti utili per una maggiore comprensione della parola definita. Sinonimo Il concetto è sostanzialmente equivalente a un altro definito in altra parte. [contesto] Indica il contesto cui si riferisce definizione riportata.

A Aggregazione – Aggregation Forma particolare di associazione che specifica una relazione (tutto-parte, whole-part) tra una classe aggregata (tutto) a una parte componente (parte). Consultare: Composizione.

Aggregazione composta – Composite aggregation Sinonimo: Composizione.

Analisi – Analysis Parte del processo di sviluppo del software il cui obiettivo primario è la costruzione di un modello (detto appunto di analisi) del dominio del problema iniziando a

incorporare direttive provenienti dallo spazio delle soluzioni.

API Interfaccia Programma Applicativo – API Application Programming Interface Interfaccia software che permette alle applicazioni di comunicare tra loro. Un’API è costituita da un insieme di costrutti o di statement specificati in un linguaggio di programmazione che possono essere codificati in un programma eseguibile al fine di ottenere specifiche funzioni e servizi forniti dal sottostante sistema operativo o programma di servizio.

Architettura – Architecture Struttura organizzativa e comportamento associato di un sistema. Un’architettura può essere ricorsivamente de-

composta in parti che interagiscono attraverso apposite interfacce in relazioni che connettono le varie parti e in vincoli sull’assemblaggio delle parti. A loro volta, le parti che interagiscono per mezzo di interfacce includono: classi, componenti e sottosistemi. Consultare: Classe, Componente, Interfaccia, Sottosistema.

Argomento – Argument Istanza di un parametro. Valore a tempo di esecuzione da sostituire a un parametro. Sinonimo: Parametro attuale. Contrario: Parametro.

Aspetto comportamentale del modello – Behavioral model aspect Aspetto del modello che enfatizza il comportamento delle istanze in un sistema, compresi metodi, stati storici e collaborazioni.

Aspetto del modello – Model aspect Dimensione della modellazione che mette in evidenza specifiche qualità. Per esempio, l’aspetto strutturale del modello ne enfatizza le qualità strutturali.

Aspetto strutturale del modello – Structural model aspect Aspetto del modello che enfatizza la struttura degli oggetti in un sistema, considerando tipi, classi, relazioni, attributi e operazioni.

Associazione – Association Relazione semantica tra due o più classificatori che specifica connessioni delle relative istanze.

Associazione binaria – Binary association Associazione tra due classi. Caso particolare dell’associazione n-aria. Consultare: Associazione n-aria.

Associazione n-aria – n-ary association Particolare versione di associazione che coinvolge tre o più classi. Ciascuna istanza dell’associazione consiste in un t-upla di valori delle rispettive classi. Contrario: Associazione binaria.

Astrazione – Abstraction Caratteristiche essenziali di un’entità che la distinguono da tutti gli altri tipi di entità. Un’astrazione definisce un confine relativo al punto di vista del fruitore.

Attivazione – Activation Esecuzione di un’azione. Consultare: Azione.

Attività [processi di sviluppo del software] – Activity Unità tangibile di lavoro eseguita da un lavoratore in un workflow (flusso di lavoro) che: 1. implica un insieme ben definito di responsabilità per il lavoratore stesso; 2. genera uno o più risultati ben definiti detti manufatti (artifacts), a partire da specifici input e 3. rappresenta un’unità di lavoro con confini ben definiti tipicamente riferita in un piano del progetto quando i lavori sono assegnati ai lavoratori.

Attore – Actor Insieme coerente di ruoli che gli utenti dei casi d’uso possono esercitare interagendo con gli stessi. Un attore possiede un ruolo specifico per ogni caso d’uso con il quale interagisce (scambia messaggi). Consultare: Caso d’uso.

Attributo – Attribute Proprietà interna di un classificatore, identificata da un nome, che descrive un insieme di valori e operazioni da esso eseguibili, che le istanze del classificatore potrebbero mantenere. Consultare: Classificatore.

Azione – Action Specifica di un comando (statement) eseguibile che costituisce un’astrazione di una procedura di calcolo. Il risultato di un’azione, tipicamente, comporta la variazione dello stato del sistema e può essere realizzato inviando un opportuno messaggio a un oggetto, modificando una relazione, aggiornando il valore di un attributo.

Azione d’entrata – Entry action Azione da eseguire all’atto dell’entrata in uno stato di una macchina a stati indipendentemente dalla transazione che ha portato a raggiungere lo stato stesso.

Azione d’uscita – Exit action Azione eseguita all’atto dell’uscita da uno stato di una macchina a stati indipendentemente dalla transazione che ne ha generato il passaggio di stato. Consultare: Stato.

B Baseline – Baseline

guite dal “sistema business” per fornire un risultato osservabile al relativo attore business.

Insieme di manufatti rivisti e approvati che rappresentano una base concordata per evoluzioni future e sviluppo e possono essere modificati esclusivamente attraverso un processo formale come quello di gestione della configurazione e variazioni.

Caso d’uso [class] – Use case

Beta testing – Beta testing

Caso di test – Test case

Processo di test che precede il rilascio. Tipicamente viene eseguito da un insieme selezionato di utenti finali. Spesso si ottiene dall’utilizzo di una versione pilota installata direttamente presso l’organizzazione degli utenti finali.

Specificazione di uno scenario di test del sistema in cui si stabilisce cosa verificare, quale situazione iniziale predisporre, quali input introdurre e quali output attendersi.

Binding – Binding

Centrato sull’architettura [processi di sviluppo del software] – Architecture-centric

Creazione di un elemento del modello a partire da un template, specificando i parametri richiesti dal template stesso. Consultare: Template.

Booleano – Boolean Istanza di un tipo enumerato i cui unici valori previsti sono true e false. Consultare: Tipo enumerato.

Build – Build Versione eseguibile del sistema tipicamente focalizzata su una sua parte. I processi di sviluppo iterativi e incrementali prevedono la consegna del sistema finale attraverso la realizzazione di una successione di build.

Specifica di una sequenza di azioni, incluse varianti, che un sistema (o un’altra entità) può eseguire interagendo con gli attori del sistema. Consultare: Istanza di casi d’uso.

Indica una strategia che comporta che l’architettura del sistema sia utilizzata come manufatto primario per concettualizzare, costruire, gestire e far evolvere il sistema in costruzione.

Chiamata – Call Stato di azione che invoca un’operazione di uno specifico classificatore. Consultare: Stato di azione.

Classe – Class

C

Descrizione di un insieme di oggetti che condividono stessi attributi, operazioni, metodi, relazioni e semantica. Una classe può utilizzare una serie di interfacce al fine di specificare insiemi di operazioni esposte al proprio ambiente. Consultare: Interfaccia.

Caratteristica – Feature

Classe aggregata – Aggregate class

Caratteristica, come un’operazione o un attributo, incapsulata in un classificatore, come un’interfaccia, una classe o un tipo di dato. Consultare: Classe, Interfaccia, Tipo di dato

Classe che rappresenta il tutto in una relazione di aggregazione (tutto-parte, whole-part). Consultare: Aggregazione.

Caratteristica strutturale – Structural feature Caratteristica statica di un elemento del modello come un attributo.

Cardinalità – Cardinality Numero di elementi in un insieme. Contrario: Molteplicità.

Casi d’uso business – Business use case Modello dei casi d’uso focalizzato sull’area business, in cui ognuno dei singoli casi d’uso mostra le azioni ese-

Classe associazione – Association class Elemento del modello che possiede sia le proprietà della associazione, sia quelle del classificatore. Una classe associazione può essere immaginata come un’associazione dotata delle proprietà della classe o viceversa (una classe che ha proprietà dell’associazione). Consultare: Associazione, Classe.

Classe astratta – Abstract class Classe che non può essere istanziata direttamente. È presente almeno un metodo astratto da definire in opportune classi specializzanti. È utilizzata per definire un

comportamento comune condiviso da un insieme di sottoclassi. Consultare: Sottoclasse. Contrario: Classe concreta.

Classificatore – Classifier

Classe attiva – Active class

Classificazione – Classification

Classe le cui istanze sono oggetti attivi. Consultare: Oggetto attivo.

Classe ausiliaria – Auxiliary class Stereotipo di classe utilizzata per indicare una classe che ne supporta un’altra più centrale o importante. Le classi ausiliarie, tipicamente, realizzano un flusso o una logica secondaria e tendono a essere utilizzate in associazione a classi focalizzate (focus). Risultano particolarmente utili per specificare la logica business ausiliaria o il controllo ausiliare dei componenti durante il relativo disegno. Consultare: Classe focalizzata.

Classe composta – Composite class Classe associata a una o più classi attraverso la relazione di composizione. Consultare: Composizione.

Classe concreta – Concrete class Classe che può essere istanziata direttamente (non possiede alcuna dichiarazione di metodo astratto). Contrario: Classe astratta.

Classe focalizzata – Focus class Stereotipo di classe utilizzata per indicare una classe che definisce la parte fondamentale della logica o del flusso di esecuzione per una più classi ausiliarie da essa supportate. Le classi focalizzate sono, tipicamente, utilizzate insieme a diverse classi ausiliarie e risultano particolarmente utili per specificare la logica business fondamentale o la parte principale di controllo di componenti durante il relativo disegno. Consultare: Classi ausiliarie.

Classe implementazione – Implementation class Stereotipo dell’elemento class che specifica l’implementazione di una specifica classe per mezzo di un linguaggio di programmazione (per esempio Java, C++, SmallTalk, …) in cui le cui istanze non possono avere più di una classe. Si dice che una classe implementazione realizza un tipo se fornisce tutte le operazioni previste dal tipo stesso e se realizza il comportamento così come definito per le operazioni del tipo. Consultare: Tipo.

Meccanismo che descrive proprietà strutturali e comportamentali. Specializzazioni del classificatore sono classi, interfacce, tipi di dati, componenti, …

Assegnazione di un oggetto al relativo classificatore. Consultare: Classificazione dinamica, Classificazione statica, Classificazione multipla.

Classificazione dinamica – Dynamic classification Variazione semantica di generalizzazione in cui un oggetto può cambiare il classificatore di appartenenza. Contrario: Classificazione statica.

Classificazione multipla – Multiple classification Variazione semantica della generalizzazione nella quale un oggetto può appartenere direttamente a più classificatori. Consultare: Classificazione statica, Classificazione dinamica.

Classificazione statica – Static classification Variazione semantica della generalizzazione nella quale un oggetto non può cambiare il proprio classificatore. Contrario: Classificazione dinamica.

Cliente – Customer Persona o organizzazione, interna o esterna a quella che si occupa di produrre il sistema, che si assume le responsabilità finanziarie del sistema stesso. Alcune volte, ma non sempre, si tratta dell’utente finale.

Cliente – Client Classificatore che richiede i servizi esposti da un altro detto fornitore. Contrario: Fornitore.

Collaborazione – Collaboration Specifica del modo in cui un’operazione o un classificatore, come per esempio un caso d’uso, è realizzato da un insieme di classificatori, associati per mezzo di opportune relazioni, che recitano un determinato ruolo secondo una prestabilita modalità. Consultare: Interazione.

Collegamento – Link Connessione semantica tra un insieme di oggetti. Si tratta di un’istanza di associazione.

Consultare: Associazione.

Collegamento di fine – Link end

componente che esiste per contenere altri componenti. Consultare: Componente.

Istanza di una fine associazione. Consultare: Associazione di fine.

Contesto – Context

Commento – Comment

Vista di un insieme di elementi della modellazione correlati tra loro per un determinato scopo, come specificare un’operazione.

Annotazione attaccata a un elemento o a una collezione. Un commento non ha alcuna semantica associata. Contrario: Vincolo.

Componente – Component Parte modulare, installabile e modificabile del sistema che incapsula implementazione ed espone un insieme di interfacce. Un componente, tipicamente, è specificato attraverso uno o più classificatori (per esempio classi di implementazione) che lo costituiscono e può essere implementato attraverso diversi manufatti (p.e. file compilati, eseguibili, script, …). Contrario: Manufatto.

Comportamento – Behavior Effetti osservabili di un’operazione o un evento, inclusi i relativi risultati.

Composizione – Composition Forma particolare e semanticamente più forte di aggregazione che impone due vincoli: • ogni istanza della parte può partecipare, in ogni istante di tempo, al massimo a una composizione; • la parte “tutto” (whole) deve essere responsabile per la generazione e la distruzione delle parti componenti. La composizione può essere ricorsiva. Sinonimo: Aggregazione composta.

2.

CORBA – CORBA Architettura di un agente comune di richiesta oggetti (Common Object Request Broker Architecture). Specificazione di una architettura a oggetti distribuiti indipendente dal linguaggio di programmazione.

CRC Cards – CRC Cards Classe–Responsabilità–Collaborazione (Class– Responsibility–Collaborators). Tecnica di sviluppo di modelli Object Oriented, inizialmente ideata da Ward Cunningham e Kent Beck al fine di semplificare la modellazione Object Oriented attraverso la definizione formale di cosa una classe dovrebbe fare, ossia le responsabilità, e di quali altre entità sono necessarie per espletare tali responsabilità (collaborazioni).

D DBMS – DBMS Sistema di gestione delle basi dati (DataBase Management System). Software che gestisce dati opportunamente organizzati, fornendo servizi per il controllo centralizzato, l’indipendenza dei dati, rendere l’accesso più efficiente, garantire l’integrità, il ripristino, il controllo della concorrenza, la privatezza e la sicurezza.

Concorrenza – Concurrency

Delega / Delegazione – Delegation

Occorrenza di due o più attività durante lo stesso intervallo di tempo. La concorrenza può essere ottenuta attraverso l’esecuzione simultanea o “interlacciata” di due o più threads (flussi di esecuzione). Consultare: Thread.

Capacità di un oggetto A di inviare un messaggio a un altro oggetto B in risposta della ricezione di un messaggio destinato ad A. La delegazione può essere utilizzata, con opportune cautele e limitazioni, come alternativa all’eredità. Consultare: Ereditarietà.

Condizione di guardia – Guard condition Condizione che deve essere soddisfatta affinché la relativa transizione possa aver luogo. Consultare: Transizione.

Contenitore – Container 1.

istanza che esiste per contenere altre istanze e che fornisce operazioni per accedere o iterare il proprio contenuto (per esempio array, liste, set, …)

Device (Dispositivo) – Device Un tipo di nodo che fornisce proprietà di supporto a un processore. Sebbene abbia le capacità di eseguire programmi embedded (device drivers necessari per poter funzionare) non può eseguire programmi general purpose (normali applicativi). I device vengono introdotti come supporto a particolari processori che eseguono applicazioni general purpose.

Diagramma – Diagram Rappresentazione grafica di una collezione di elementi del modello, tipicamente, riprodotti come grafi connessi da archi (relazioni) e vertici (altri elementi del modello). Lo UML supporta i seguenti diagrammi: dei casi d’uso, di sequenza, di collaborazione, di attività, di stato, delle classi, degli oggetti, dei componenti e di dispiegamento.

Diagramma degli oggetti – Object diagram Diagramma che illustra, in un preciso istante di tempo, un insieme di oggetti opportunamente interconnessi. Può essere considerato un caso particolare di un diagramma delle classi o di uno di collaborazione. Consultare: Diagramma di collaborazione, Diagramma delle classi.

Diagramma dei componenti – Component diagram Diagramma che mostra l’organizzazione e le dipendenze tra componenti. Consultare: Componente.

Diagramma delle classi – Class diagram Diagramma che mostra una collezione di elementi dichiarativi del modello, come classi, tipi, loro contenuti e relazioni. Modella la struttura statica di un sistema.

Diagramma di collaborazione – Collaboration diagram Diagramma che mostra interazioni organizzate attorno alla struttura di un modello, utilizzando o classificatori e associazioni oppure istanze e collegamenti. Al contrario dei diagrammi di sequenza, quelli di collaborazione mostrano le relazioni tra le varie istanze. I diagrammi di sequenza e collaborazione mostrano informazioni molto simili (sono isomorfi, è possibile passare direttamente dall’una all’altra forma), ma conferiscono maggiore attenzione a differenti elementi: i diagrammi di sequenza mettono in evidenza il trascorrere del tempo, mentre quelli di collaborazione conferiscono maggiore importanza alla struttura del modello. Consultare: Diagramma di sequenza.

Diagramma di dispiegamento – Deployment diagram Diagramma che mostra la configurazione a tempo di esecuzione dei nodi di elaborazione e dei componenti corredati dai processi e dagli oggetti in vita su di essi. I

componenti sono manifestazioni di unità di codice a tempo di esecuzione. Consultare: Diagramma dei componenti , Tempo di esecuzione.

Diagramma di sequenza – Sequence diagram Diagramma che mostra l’interazione di oggetti organizzata evidenziando la sequenza temporale. In particolare, mostra lo scambio di messaggi che avviene tra gli oggetti che prendono parte a un’interazione. A differenza del diagramma di collaborazione, quello di sequenza include la sequenza temporale ma non le relazioni tra oggetti. Un diagramma di sequenza può esistere in una forma generica (descrive tutti i possibili scenari) o in una specifica (mostra una particolare istanza di scenario). I diagrammi di sequenza e collaborazione mostrano informazioni molto simili, ma enfatizzando un diverso aspetto (rispettivamente sequenza temporale e relazioni tra oggetti). Consultare: Diagramma di collaborazione.

Diagramma di stato – Statechart diagram Diagramma che modella una macchina a stati. Consultare: Macchina a stati.

Diagramma di interazione – Interaction diagrams Termine generico utilizzato per riferirsi a diversi tipi di diagramma (quelli di sequenza e di collaborazione) impiegati per mostrare interazioni tra oggetti. Consultare: Diagramma di sequenza, Diagramma di collaborazione.

Dipendenza – Dependency Relazione tra due elementi del modello in cui la modifica di uno di essi (quello indipendente) può generare ripercussioni sull’altro (quello dipendente).

Disegno – Design Parte del processo di sviluppo del software il cui obiettivo primario è stabilire come il sistema verrà implementato. Durante la fase di disegno sono prese una serie di decisioni strategiche e tattiche al fine di soddisfare i requisiti, funzionali e non, e i requisiti di qualità del sistema.

Dominio – Domain Area di conoscenza o di attività caratterizzata da un insieme di concetti e terminologie condivisi da addetti ai lavori della specifica area.

E Elaborazione del modello – Model elaboration Il processo di generazione di un tipo repository a partire da un modello pubblicato. Tale processo include la generazione delle interfacce e dell’implementazione che permettono ai repository di essere istanziati e popolati in funzione e in accordo con il modello elaborato. Consultare: Repository.

Elemento – Element Costituente atomico di un modello.

Elemento del modello – Model element [MOF] Elemento che costituisce una specifica astrazione del sistema oggetto di modellazione. Nel contesto del MOF (Meta-Object Facility) gli elementi del modello sono considerati metaoggetti.

Elemento derivato – Derived element Elemento del modello che può essere ottenuto a partire da un altro elemento, ma che viene comunque utilizzato o perché contribuisce ad aumentare il livello di chiarezza o per scopi di disegno, anche se non aggiunge alcuna semantica o ulteriori informazioni.

Elemento generalizzabile – Generalizable element Elemento del modello in grado di partecipare a una relazione di generalizzazione nel ruolo di genitore. Consultare: Generalizzazione.

Elemento parametrizzato – Parameterized element Descrittore di una classe con uno o più parametri cui non è ancora stato attribuito un valore (unbound). Consultare: Template.

Elemento vista – View element Descrizione testuale e/o proiezione grafica di una collezione di elementi del modello.

Enumerato/Enumerazione – Enumeration Sinonimo: Tipo enumerato.

Ereditarietà – Inheritance Meccanismo che permette a elementi più specifici di incorporare struttura e comportamento definiti in ele-

menti più generali ai quali sono correlati per via di aspetti comportamentali. Consultare: Generalizzazione.

Ereditarietà dell’interfaccia – Interface inheritance Ereditarietà dell’interfaccia posseduta da un elemento più generale. Non include l’ereditarietà dell’implementazione. Consultare: Interfaccia. Contrario: Ereditarietà dell’implementazione.

Ereditarietà dell’implementazione – Implementation inheritance Ereditarietà dell’implementazione di un elemento più generale. Questo tipo di eredità include quella dell’interfaccia. Contrario: Ereditarietà dell’interfaccia.

Ereditarietà multipla – Multiple inheritance Variazione semantica della generalizzazione nella quale un tipo può avere più di un supertipo (genitore). Consultare: Supertipo, Tipo. Contrario: Ereditarietà singola.

Ereditarietà singola – Single inheritance Variazione semantica della relazione di generalizzazione in cui un tipo può avere al massimo un supertipo (genitore). Contrario: Ereditarietà multipla.

Espressione – Expression Stringa la cui valutazione genera un valore di un tipo specifico. Per esempio la valutazione dell’espressione (2 * 3 + 7) genera un valore di tipo numerico.

Espressione booleana – Boolean expression Espressione la cui valutazione restituisce un valore booleano. Consultare: Booleano.

Espressione temporale – Time expression Espressione la cui valutazione genera un valore di tipo “tempo”, assoluto o relativo.

EUP Processo Unificato Esteso – EUP Enhanced Unified Process Versione estesa del processo di sviluppo del software RUP. Prevede che il ciclo di vita del software venga suddiviso in cinque fasi: iniziale, elaborazione, costruzione, transizione e produzione; in più vengono conside-

rati workflow aggiuntivi, quali: gestione delle configurazioni e delle modifiche (configuration and change management), gestione del progetto (project management), ambientale e gestione infrastrutture. Consultare: Ciclo di vita del software, RUP, Workflow.

Evento – Event Specificazione di un avvenimento significativa che si manifesta in una ben definita locazione spazio-tempo. Nel contesto dei diagrammi di stato, un evento è un avvenimento in grado di provocare una transizione.

Evento temporale – Time event Evento che rappresenta l’intervallo di tempo trascorso dal momento in cui uno stato concorrente è stato attivato (vi si è transitati). Consultare: Evento.

Export (Esportazione) – Export Nel contesto dei package, permette di rendere un elemento visibile all’esterno del proprio spazio dei nomi (namespace, ambito di denominazione). Consultare: Package, Spazio dei nomi (namespace), Visibilità. Contrario: Import (importazione).

Fase di costruzione – Construction phase Terza fase del processo di sviluppo del software durante la quale il software passa dal punto della baseline di un’architettura eseguibile al quello in cui è pronto a essere sottoposto alla comunità degli utenti. Consultare: Baseline.

Fase di elaborazione – Elaboration phase Seconda fase del processo di sviluppo del software durante la quale viene definita l’architettura del sistema.

Fase di principio – Inception phase Prima fase del processo di sviluppo del software dove le prime concettualizzazioni circa lo sviluppo del sistema vengono fatte evolvere al punto da risultare sufficientemente ben fondate per giustificare il passaggio alla fase successiva (elaborazione).

Fase di produzione [processo di sviluppo del software EUP] – Production phase Si tratta della quinta fase atta a gestire attività necessarie per mantenere il sistema operativo fino a quando sia disponibile una nuova versione o, addirittura, il sistema venga rimpiazzato da uno completamente nuovo. Consultare: EUP.

Extend – Extend

Fase di transizione – Transition phase

Relazione tra un caso d’uso estendente e uno base, che specifica come il comportamento definito dallo use case estendente è incorporato (sotto il controllo di un’espressione booleana definita nella relazione stessa) nel comportamento del caso d’uso base. Il comportamento è inserito in apposite locazioni definite dai punti di estensione presenti nel caso d’uso base. Quest’ultimo non dipende dall’esecuzione del comportamento del caso d’uso estendente. Consultare: Caso d’uso, Include, Punto di estensione.

Quarta fase del processo di sviluppo del software in cui il software è consegnato alla comunità degli utenti.

Figlio – Child Nella relazione di generalizzazione, la specializzazione di un altro elemento detto genitore (parent). Consultare: Sottoclasse, Sottotipo. Contrario: Genitore (Parent).

Fine associazione – End association

F

Punto di fine di una associazione che connette l’associazione stessa a un classificatore. Consultare: Associazione, Classificatore.

Façade – Façade

Firma – Signature

Stereotipo dell’elemento package che contiene esclusivamente riferimenti agli elementi del modello presenti all’interno di un altro package. È utilizzato per fornire una “vista pubblica” di alcuni dei contenuti di un package.

Nome e parametri di una caratteristica comportamentale. La firma può contemplare un eventuale valore di ritorno. Consultare: Caratteristica comportamentale.

Fase [processi di sviluppo del software] – Phase Tempo che intercorre tra due maggiori milestones.

Fornitore – Supplier Classificatore che fornisce un servizio che può essere invocato da altri detti clienti. Contrario: Cliente.

Framework (Struttura) – Framework Stereotipo dell’elemento package contenente elementi del modello che realizzano un’architettura riutilizzabile per un intero sistema o per sue parti. I framework, tipicamente, includono classi, interfacce, pattern e/o template. Quando i framework sono specializzati per uno specifico dominio, spesso ci si riferisce ad essi con il nome di framework applicativi. Consultare: Package, Pattern.

G Generalizzazione – Generalization Relazione tassonomica tra un elemento più generale e uno più specifico. Quest’ultimo è completamente consistente con l’elemento più generale e contiene informazioni supplementari. Ogni istanza dell’elemento più specifico può essere utilizzata in ogni posto in cui è permesso l’utilizzo dell’elemento più generale. Consultare: Ereditarietà.

Genitore – Parent Generalizzazione di un altro elemento, detto figlio. Consultare: Superclasse, Supertipo. Opposto: Figlio.

Gerarchia di contenimento – Containment hierarchy Consiste in elementi del modello corredati dalle relative relazioni di contenimento. Una gerarchia di contenimento costituisce un grafo.

Grafo di attività – Activity graph Caso particolare di una macchina a stati (state machine) utilizzata per modellare processi che coinvolgono uno o più classificatori. Consultare: Macchina a stati. Contrario: Diagramma di stato (statechart diagram).

Guidato dai casi d’uso – Use case-driven Nel contesto dei processi di sviluppo del software, approccio in cui i casi d’uso sono utilizzati come manufatto primario per stabilire il comportamento desiderato del sistema e per comunicare tale comportamento tra i vari soggetti coinvolti nello sviluppo del sistema stesso. Lo stesso approccio comporta che il modello dei casi d’uso diventi l’input primario per le restanti fasi di analisi, disegno, implementazione e test del sistema inclu-

dendo la creazione, la verifica e la validazione dell’architettura del sistema.

Guidato dai fattori di rischio – Risk-driven Nel contesto dei processi di sviluppo del software, strategia che consiste nel focalizzare l’attenzione, in ogni nuova release del sistema, sull’individuazione e sulla riduzione dei rischi contingenti più significativi e pressanti che possono minare il buon successo del progetto.

I ICONIX – ICONIX Esempio di processo di sviluppo del software basato sull’approccio use case-driven (guidato dai casi d’uso) Consultare: Guidato dai casi d’uso.

Implementazione – Implementation Definizione del modo in cui qualcosa viene costruito o elaborato. Per esempio, una classe è l’implementazione di un tipo, un metodo è l’implementazione di un’operazione, e così via. Contrario: Specificazione.

Import (Importazione) – Import Dipendenza tra package che evidenzia i package le cui classi possono essere referenziate all’interno di un altro, e/o ricorsivamente, in tutti i package in esso contenuti. Contrario: Export (Esportazione).

Include – Include Relazione tra un caso d’uso base e uno incluso, che specifica il modo in cui il comportamento definito dal primo contiene il comportamento specificato nel caso d’uso incluso. Il comportamento di quest’ultimo è inserito in apposite locazioni definite nel caso d’uso in base. Quest’ultimo pertanto dipende dall’esecuzione dello use case incluso, ma non dalla sua struttura (attributi e operazioni). Consultare: Caso d’uso, Extend.

Ingegnerizzazione inversa [processi di sviluppo del software] – Reverse engineering La trasformazione del codice in un modello attraverso un’opportuna corrispondenza dallo specifico linguaggio di programmazione utilizzato agli elementi del modello. Consultare: Ingegnerizzazione diretta.

Ingegnerizzazione diretta – Forward engineering Nel contesto dei processi di sviluppo del software indica la trasformazione di un modello nel rispettivo codice attraverso un’opportuna corrispondenza con uno specifico linguaggio di programmazione. Contrario: Ingegnerizzazione inversa.

Innescare – Fire Eseguire una transizione di stato. Consultare: Transizione.

Interazione – Interaction Specificazione di come determinati stimoli vengono scambiati tra particolari istanze al fine di eseguire un compito prestabilito. L’interazione è definita nel contesto di una collaborazione. Consultare: Collaborazione.

Interfaccia – Interface Insieme di operazioni, identificate da un nome, utilizzato per caratterizzare il comportamento di un elemento. Consultare: Operazione. Contrario: Tipo.

Invio di un messaggio – Send [message] Passaggio di uno stimolo da un’istanza mittente a una destinataria. Consultare: Sender (Mittente), Destinatario.

Istanza – Instance Entità, identificata da un’identità univoca, alla quale può essere applicato un insieme ben definito di operazioni, e che possiede uno stato in grado di memorizzare gli effetti dell’esecuzione di queste operazioni. Consultare: Oggetto.

Istanze di casi d’uso – Use case instances Esecuzione di una sequenza di azioni specificate in un caso d’uso. Consultare: Caso d’uso.

Iterativo [contesto dei processi di sviluppo del software] – Iterative Indica la strategia che prevede la gestione di una successione di release via via più complete.

Iterazione – Iteration Insieme distinto di attività eseguite in accordo a uno specifico piano e criteri di valutazione il cui risultato consiste in un nuova release del sistema.

L Linea di vita di un oggetto – object lifeline Linea di un diagramma di sequenza (tipicamente tratteggiata) ortogonale ad un oggetto che rappresenta l’esistenza dello stesso durante uno specifico intervallo di tempo. Consultare: Diagramma di sequenza, oggetto.

Localizzazione del controllo – Focus of control Simbolo appartenente ai diagrammi di sequenza che mostra l’intervallo di tempo durante il quale l’efferente oggetto esegue un’azione, sia direttamente, sia attraverso una procedura subordinata. Consultare: Diagramma di sequenza

M Macchina a stati – State machine Comportamento che specifica le sequenze degli stati, corredati dalle risposte e dalle azioni intraprese, che un oggetto o un’interazione deve percorrere durante la propria esistenza in risposta a determinati eventi.

Manufatto – Artifact Rappresentazione fisica di un’unità di informazione utilizzata dal processo di sviluppo del software. Esempi di manufatti sono i modelli, i file sorgenti, gli script, i file eseguibili, e così via. Un manufatto potrebbe costituire l’implementazione di un componente eseguibile. Sinonimo: Prodotto.

Meccanismo – Mechanism Soluzione comune per un problema o requisito ricorrente. Esempi sono i meccanismi di disegno che provvedono la persistenza degli oggetti o gli strumenti di distribuzione dei modelli di disegno.

Messaggio – Message Specificazione di un conveniente scambio di informazione tra istanze, con l’aspettativa che abbia luogo una determinata attività. Un messaggio può specificare l’invio di un segnale o l’invocazione di una operazione.

Metaclasse – Metaclass Classe le cui istanze sono a loro volta classi. Le metaclassi tipicamente sono utilizzate per costruire metamodelli. Consultare: Metamodello.

Meta-metamodello – Meta-metamodel

Modello libreria – Model library

Modello che definisce il linguaggio necessario per esprimere metamodelli. La relazione tra il meta-metamodello e un metamodello è analoga alla relazione che intercorre tra un metamodello e un modello. Consultare: Metamodello.

Stereotipo dell’elemento package che contiene elementi del modello con l’obiettivo di riutilizzarli in altri package. Un modello di libreria differisce da un profilo in quanto il primo non estende il metamodello attraverso l’utilizzo dei meccanismi propri di estensione come gli stereotipi, i valori etichettati e vincoli. Un modello di libreria è analogo al concetto di libreria di classi proprio dei linguaggi di programmazione. Consultare: Package, Profilo.

Metamodello – Metamodel Modello che definisce il linguaggio necessario per esprimere un modello.

Metaoggetto – Metaobject Termine generico utilizzato per riferirsi a tutte le metaentità di un linguaggio di modellazione. Esempi di metaoggetto sono metatipi, metaclassi, metaattributi e metaassociazioni.

Metodo – Method Implementazione di un’operazione. Specifica l’algoritmo o la procedura associata a una operazione. Consultare: Operazione.

Modello – Model Astrazione di un sistema fisico realizzata per scopi specifici. Consultare: Sistema fisico.

Modello [Contesto della specifica MOF (MetaObject Facility)] – Model Descrive un meta-metamodello, il quale spesso, per questioni di brevità, è riferito semplicemente con il nome di modello. Consultare: Meta-metamodello.

Modello a oggetti del dominio – Business object model Modello a oggetti (insieme di diagrammi delle classi) che descrive la realizzazione dei casi d’uso business.

Modello dei casi d’uso – Use case model Modello che descrive i requisiti funzionali di un sistema in termini di casi d’uso. Consultare: Caso d’uso.

Modello di definizione [MOF] – Defining model [MOF] Modello sul quale è basato il repository. Un qualsiasi numero di repository può far riferimento al medesimo modello di definizione. Consultare: Repository.

Modello pubblicato [MOF] – Published model Modello “congelato” e reso disponibile per istanziare repository e per il supporto nella definizione di altri modelli.Gli elementi di un modello “congelato” non possono essere modificati. Consultare: Repository.

Modificatore di accesso – Access modifier Parola chiave del linguaggio utilizzata per modificare l’accesso a classi, metodi e attributi. Consultare: Visibilità.

Modulo – Module Unità di software di memorizzazione e manipolazione. Sono moduli quelli di codice sorgente, di codice binario, di codice eseguibile, ecc. Consultare: Componente.

MOF Supporto per metaoggetti – MOF MetaObject Facility Specificazione che definisce un insieme di interfacce CORBA (CORBA IDL) che possono essere utilizzate per definire e manipolare un insieme di metamodelli interoperabili (lo UML ne è un esempio) e i relativi modelli.

Molteplicità – Multiplicity Specifica di un intervallo di cardinalità consentite che un insieme può assumere. La molteplicità può essere specificata per un ruolo di un’associazione, per le parti di una composizione, per ripetizioni e così via. Una molteplicità è un sottoinsieme, eventualmente infinito, di numeri naturali (interi non negativi). Contrario: Cardinalità.

Monovalorizzato – Single-valued [MOF] Elemento del modello con molteplicità definita il cui valore superiore dell’attributo di MultiplicityType:: è impostato a un 1. Il termine single-valued pertanto non

si riferisce al numero di valori che un attributo, un parametro, ecc. può assumere in ogni istante di tempo: un attributo single-valued potrebbe non aver alcun valore (per esempio molteplicità uguale a zero). Contrario: Plurivalorizzato.

N Nodo – Node Classificatore che rappresenta, in fase di esecuzione, una risorsa tipicamente dotata almeno di memoria, alla quale frequentemente abbina capacità elaborative, sede di componenti e oggetti eseguibili.

Nome – Name Stringa utilizzata per identificare un elemento del modello.

Non interpretato – Uninterpreted Segnaposto (placeholder) per uno o più tipi la cui implementazione non è specificata dallo UML. Ogni valore non interpretato ha una corrispondente stringa che ne fornisce la rappresentazione.

O Oggetto –object Entità ben delimitata, dotata di nome che incapsula stato e comportamento. Lo stato, in un determinato istante di tempo, è rappresentato dal valore assunto da tutti gli attributi e dalle relazioni instaurate con gli altri oggetti. Il comportamento invece è rappresentato da operazioni, metodi e macchine di stato. Un oggetto è istanza di una specifica classe. Consultare: Classe, Istanza.

Oggetto attivo – Active object Oggetto istanza di una classe attiva che possiede il thread (flusso di esecuzione) e che può avviare un’attività di controllo. Consultare: Classe attiva, Thread (flusso).

Oggetto persistente – Persistent object Un oggetto che continua a esistere dopo il termine del processo o del flusso di esecuzione (thread) che lo ha creato.

Oggetto transiente – Transient object Oggetto che esiste esclusivamente durante l’esecuzione del processo o del flusso di esecuzione (thread) che lo ha creato.

Consultare: Flusso (thread).

OMG Object Management Group Si tratta del gruppo no profit a cui aderisce la quasi totalità dei vendor appartenenti alla comunità Object Oriented. Il fine di tale organizzazione è promuovere standard de facto relativi al mondo OO atti a incoraggiare lo sviluppo di tale paradigma. Attualmente le relative responsabilità includono, tra l’altro, la gestione dell’evoluzione dello UML e dell’architettura CORBA.

Operazione – operation Servizio che può essere richiesta ad un determinato oggetto la cui esecuzione ne influenza il comportamento. Un’operazione dispone di una precisa firma che limita l’insieme dei parametri attuali ammissibili. Consultare: Firma, Parametri attuali.

P Package – Package Meccanismo generalizzato utilizzato per organizzare elementi in gruppi. È possibile dar luogo a package annidati in altri package.

Parametro –parameter Specifica di una variabile che può essere modificata, fornita o restituita in ritorno. Un parametro può includere un nome, tipo e direzione. I parametri sono utilizzati per operazioni, messaggi ed eventi. Sinonimo: Parametro formale. Opposto: Argomento.

Parametro formale – Formal parameter Sinonimo: Parametro

Parametro attuale – Actual parameter Sinonimo: Argomento.

Partecipare – Participate Connessione di un elemento ad una relazione. Per esempio, una classe partecipa in un’associazione, un attore partecipa in un caso d’uso, ecc.

Partizione – Partition 1. grafi di attività: porzione di un grafo di attività che organizza le responsabilità delle azioni. Consultare: Swimlane. 2. architettura: insieme di classificatori correlati presenti allo stesso livello di astrazione appartenenti a diversi strati dell’architettura. Una

partizione rappresenta una frammentazione verticale trasversale all’architettura, mentre uno strato ne à una porzione orizzontale. Contrario: Strato.

Pattern – Pattern Template di collaborazione. Un pattern di disegno fornisce uno schema per perfezionare i sottosistemi o i componenti di un sistema software, e/o le relazioni tra di essi. Descrive una struttura ricorrente di componenti comunicanti che risolvono un problema generale di disegno all’interno di uno specifico contesto.

Pattern architetturali – Architectural patterns Pattern che definiscono determinate strutture o comportamenti, tipicamente utilizzati per la vista architetturale o per uno specifico modello. Esempi di pattern architetturali sono three-tier, multi-tier, client-server, e altri, ognuno dei quali definisce una specifica struttura del modello di dispiegamento e fornisce le linee guida sul modo in cui i componenti devono essere allocati nei vari nodi.

Piano di iterazione – Iteration plan Piano dettagliato per una specifica iterazione. Piano che dichiara il costo previsto in termini di tempo e di impiego di risorse e i risultati attesi in termini di manufatti per una specifica iterazione. Un piano di iterazione deve anche specificare chi dovrebbe fare cosa nel corso dell’iterazione e in quale ordine. Ciò è ottenuto allocando le singole persone ai “lavoratori” (sorta di segnaposto) e descrivendo in dettaglio il workflow dell’iterazione. Consultare: Iterazione, Manufatto, Worker (Lavoratore), Workflow.

Piano di test – Test plan Piano che specifica le strategie di test, le risorse e la relativa schedulazione.

Plurivalorizzato – Multi-valued [MOF] Elemento del modello con molteplicità definita il cui valore superiore dell’attributo MultiplicityType:: è impostato a un numero maggiore di 1. Il termine multi-valued pertanto non si riferisce al numero di valori che un attributo, un parametro, ecc. può memorizzare in ogni istante di tempo. Contrario: Monovalorizzato.

Portabilità – Portability Grado in cui un sistema eseguibile in uno specifico am-

biente di esecuzione può essere facilmente spostato in un altro ambiente di esecuzione mantenendo la proprietà di essere eseguibile.

Postcondizione – Post-condition Vincolo che deve essere soddisfatto al termine dell’esecuzione di un’operazione.

Precondizione – Pre-condition Vincolo che deve essere soddisfatto all’atto dell’invocazione di un’operazione.

Processo – Process Unità di esecuzione pesante che può essere eseguita in concorrenza con altri thread in un sistema operativo. Si contrappone al concetto di thread il quale include processi pesanti e leggeri. Se necessario è possibile distinguere i due concetti attraverso opportuni stereotipi. Spesso indica l’esecuzione di un algoritmo o altrimenti gestione di un’attività dinamica. Contrario: Thread (flusso di esecuzione).

Processo [processo di sviluppo del software] – Process Attività, coordinamento e linee guida da seguire per costruire un sistema.

Procedura di test – Test procedure Specifica sul modo in cui eseguire uno o più casi di test. Consultare: caso di test.

Processo business – Business process Insieme di attività necessarie per produrre come risultato un valore compreso e misurabile per un cliente individuale di un particolare business.

Processo di sviluppo – Development process Insieme di passi ordinati eseguiti per uno scopo prestabilito durante lo sviluppo del software, come costruzione e implementazione di modelli.

Processo di sviluppo del software unificato – Unified Software Development Process Processo di sviluppo del software fondato sullo UML che integra gli approcci iterativo e incrementale, centrato sull’architettura, guidato dai casi d’uso e dai fattori di rischio. Si tratta di un processo organizzato su quattro fasi (inizio, elaborazione, costruzione e transizione) e su cinque workflow principali (cattura dei requisiti, analisi, disegno, implementazione e test). Processo de-

scritto in termini di un modello di business, il quale è strutturato in termini di tre blocchi primitivi (lavoratori, attività e manufatti).

per semplificare l’interpretazione della semantica del metamodello.

Processor (Processore) – Processor

Q

Tipo di nodo che possiede le capacità di eseguire uno o più processi. Queste includono capacità di esecuzione, memoria, dispositivi di input/output e così via. Consultare: Device, Nodo, Processo.

Qualificatore – Qualifier Attributo o t-upla di attributi appartenenti ad una specifica associazione i cui valori partizionano l’insieme degli oggetti relazionati ad un altro attraverso un’associazione.

Profilo – Profile Stereotipo di package che contiene elementi del modello “customizzati” (specializzati) per uno specifico dominio o scopo utilizzando i meccanismi di estensione propri dello UML: stereotipi, valori etichettati e vincoli. Un profilo può eventualmente specificare sia modelli di libreria dal quale dipende, sia il sottoinsieme del metamodello che estende. Consultare: Modello di libreria, Package.

R Raffinamento – Refinement Relazione che rappresenta una più completa specifica di un qualcosa già definito con un determinato livello di astrazione. Per esempio un modello di disegno è un raffinamento del modello di analisi.

Proiezione – Projection

Regola business – Business rule

Corrispondenza (mapping) tra un insieme e un suo sottoinsieme.

Si tratta di principi, policy, leggi, dettagliati algoritmi di calcolo, relazioni tra oggetti, ecc. che influenzano direttamente il dominio del problema che il sistema dovrà, in qualche misura, automatizzare. Pertanto, rappresentano informazioni che, in ultima analisi, dovranno confluire nell’implementazione del sistema.

Proiezione vista – View projection Proiezione di elementi del modello in un elemento vista. La proiezione vista fornisce una locazione e uno stile per ogni elemento vista.

Proprietà – Property Valore identificato da un nome denotante una specifica caratteristica di un elemento. Le proprietà hanno un impatto semantico. Alcune proprietà sono predefinite nello UML, altre possono essere definite dall’utente. Consultare: Valore etichettato.

Proprietà comportamentali – Behavioral features Proprietà dinamica di un elemento del modello, come un’operazione o un metodo.

Pseudostato – Pseudo-state

Relazione – Relationship Connessione semantica tra elementi del modello. Esempi di relazioni sono l’associazione, la generalizzazione, ecc.

Repository – Repository Meccanismo per la memorizzazione dei modelli ad oggetti, interfacce e implementazioni.

Requisito – Requirement Condizione o capacità cui un sistema deve conformarsi.

Requisito funzionale – Functional requirement

Vertice (nodo) in una macchina a stati che ha la forma dello stato, ma che non possiede comportamento da stato. Esempi di pseudostati sono quello iniziale e quello storico. Consultare: Stato iniziale, Stato storico.

Requisito che specifica un’azione che il sistema deve essere in grado di eseguire senza prendere in considerazione vincoli di carattere fisico (provenienti principalmente dall’architettura). In altre parole un requisito che specifica gli output che il sistema deve produrre a fronte di determinati input.

Punto di variazione semantica – Semantic variation point

Requisito non funzionale – Non functional requirement

Punto di variazione nella semantica di un metamodello. Si tratta di un intenzionale grado di libertà utilizzato

Requisito che specifica proprietà richieste al sistema, come vincoli ambientali e di sviluppo, prestazioni, dipendenze

dalla piattaforma, di manutenibilità, estendibilità, sicurezza e affidabilità. Requisito che sancisce vincoli di carattere fisico relativi ai requisiti funzionali. Contrario: Requisito funzionale.

Requisito prestazionale – Performance requirement Requisito che impone condizioni comportamentali a requisiti funzionali, come velocità, numero di lavori eseguiti nell’unità di tempo (throughput), tempo di risposta e utilizzo della memoria. Consultare: Requisito non funzionale.

Responsabilità – Responsability Contratto o obbligazione di un classificatore. Consultare: Classificatore.

Ricevente [oggetto] – Receiver [object] Oggetto che tratta lo uno stimolo fornito da un altro oggetto detto mittente. Opposto: Mittente.

Ricevere [messaggio] – Receive [message] Trattamento di uno stimolo fornito da un oggetto mittente. Consultare: Oggetto mittente.

Riferimento – Reference 1. Denotazione di un elemento del modello. 2. Slot (spazio) di un classificatore, identificato da un nome, che facilita la navigazione verso altri classificatori. Consultare: Classificatore.

Rischio – Risk Variabile del progetto in grado di mettere in pericolo o addirittura annullare il successo dell’intero processo. I rischi possono essere o meno di natura tecnica e possono far sì che il progetto vada incontro a eventi indesiderati come ritardi rispetto alla pianificazione, incremento dei costi e improvvise cancellazioni.

Riuso – Reuse Utilizzo di un manufatto (o opportune porzioni) preesistente.

Ruolo – Role Comportamento specifico, dotato di nome, assunto da un’entità che prende parte a un determinato contesto. Un ruolo può essere statico (come nel caso dell’associazione di fine) o dinamico (come nel ruolo della collaborazione).

RUP Processo unificato della Rational – RUP Rational Unified Process Processo di sviluppo del software fornito dalla Rational, evoluzione dello Unified Software Development Process. Consultare: Processo di sviluppo del software unificato.

S Scenario – Scenario Sequenza specifica di azioni che illustrano un determinato comportamento. Uno scenario può essere utilizzato per mostrare una particolare interazione o l’esecuzione di una specifica istanza di un caso d’uso. Consultare: Interazione.

Schema [MOF] – Schema [MOF] Nel contesto del Meta-Object Facility (MOF), uno schema è analogo a un package contenente elementi del modello; in altre parole uno schema corrisponde a un package del MOF. Contrario: Metamodello, Package.

Sender (Mittente) [oggetto] – Sender [object] Oggetto che passa lo stimolo (invia un messaggio) a un oggetto destinatario. Contrario: Destinatario, Messaggio.

Sequenza di azione – Action sequence Espressione che si risolve in una sequenza di azioni. Consultare: Azione.

Segnale – Signal Specifica della comunicazione di un stimolo asincrono tra istanze. I segnali possono essere dotati di parametri.

Sistema – System Sottosistema di primo livello (top level) in un modello. Consultare: Top level. Contrario: Sistema fisico.

Sistema fisico – Physical system 1. Soggetto di un modello. 2. Collezione di unità fisiche interconnesse, le quali possono includere software, hardware e persone, organizzate al fine di ottenere uno specifico risultato. Un sistema fisico si presta ad essere descritto per mezzo di uno o più modelli, realizzati secondo differenti punti di vista. Contrario: Sistema.

Sistema legacy – Legacy system Sistema preesistente che il nuovo progetto deve “ereditare”. Tipicamente si tratta di sistemi datati, realizzati con tecnologie ormai obsolete, che comunque deve essere incorporato o riutilizzato, totalmente o in parte, dal nuovo sistema da realizzare.

Sottoclasse – Subclass Nella relazione di generalizzazione, si tratta della specializzazione di un’altra classe definita superclasse o genitore. Consultare: Generalizzazione Contrario: Superclasse.

Sottomacchina a stati – Submachine state

Contrario: Supertipo.

Spazio dei nomi – Namespace Parte del modello in cui i nomi possono essere definiti e utilizzati. All’interno di uno spazio dei nomi, ogni nome deve essere univoco. Consultare: Nome.

Specificazione – Specification Descrizione dichiarativa di che cosa un’entità è o del suo comportamento. Contrario: Implementazione.

Stato – State

Stato di una macchina a stati equivalente a uno stato composto il cui comportamento però è definito per mezzo di un’altra macchina a stati.

Condizione o situazione durante il ciclo di vita di un oggetto durante la quale l’oggetto stesso soddisfa particolari condizioni, esegue determinate attività o attende specifici eventi.

Sottopackage – Subpackage

Stato composto – Composite state

Package contenuto in un altro package. Consultare: Package.

Stato che può essere costituito o da altri sottostati concorrenti (ortogonalmente) oppure da sottostati sequenziali (disgiunti). Consultare: Sottostato.

Sottosistema – Subsystem Gruppo di elementi del modello che rappresentano un’unità comportamentale di un sistema fisico. Un sottosistema espone interfacce e possiede delle operazioni. Inoltre, gli elementi del modello di un sottosistema possono essere partizionati in elementi di specifica e elementi di realizzazione. Consultare: Package, Sistema fisico.

Sottostato – Substate Stato parte di uno stato composto. Consultare: Stato concorrente, Stato disgiunto.

Sottostato concorrente – Concurrent substate Sottostato che può essere contenuto simultaneamente con altri sottostati nel medesimo stato composto. Consultare: Stato composto. Contrario: Sottostato disgiunto.

Stato di azione – Action state Uno stato che rappresenta l’esecuzione di un’azione atomica: si tratta tipicamente dell’invocazione di una specifica operazione. Consultare: Stato.

Stato flusso di oggetto – object flow state Stato in un grafo di attività che rappresenta il passaggio di un oggetto dall’output di azioni appartenenti ad uno specifico stato, alle azioni di input di un altro stato. Consultare: Azione, Oggetto.

Stato di sottoattività – Subactivity state Stato di un grafo di attività rappresentante l’esecuzione di una sequenza non atomica di passi avente una qualche durata.

Sottostato disgiunto – Disjoint substate

Stato finale – Final state

Sottostato che non può essere contenuto simultaneamente con altri nel medesimo stato composto. Consultare: Stato composto. Contrario: Sottostato concorrente.

Caso particolare di stato specificante che lo stato composto che lo contiene o l’intera macchina a stati è conclusa. Consultare: Stato.

Sottotipo – Subtype

Stato iniziale – Initial state

In una relazione di generalizzazione, rappresenta la specializzazione di un altro tipo: il supertipo. Consultare: Generalizzazione.

Tipo particolare di stato che specifica la sorgente di una singola transizione o lo stato di default di uno stato composto.

Consultare: Stato composto, Transizione.

Stato di sincronizzazione – Synch state Vertice in una macchina a stati utilizzato per sincronizzare le aree di concorrenza in una macchina a stati. Consultare: Vertice.

Swimlane (Corsia di nuoto) – Swimlane Partizione di un diagramma di attività utilizzata al fine di organizzare le azioni in base alle relative responsabilità. Le swimlane tipicamente corrispondono a unità organizzative di un modello business. Consultare: Partizione.

Stereotipo – Stereotype Nuovo tipo di elemento del modello in grado di estendere la semantica del metamodello. Gli stereotipi devono essere basati su specifici tipi o classi appartenenti al metamodello, dei quali possono estendere la semantica, ma non la struttura. Alcuni stereotipi sono predefiniti nello UML mentre altri possono essere definiti dall’utente. Gli stereotipi rappresentano uno dei tre meccanismi di estensione previsti dallo UML. Consultare: Valore etichettato, Vincolo.

T

Stimolo – Stimulus

Tempo di compilazione – Compile time

Passaggio di informazioni da un’istanza ad un’altra, che può verificarsi inviando un segnale o invocando un’operazione. Un evento consiste nella ricezione di un segnale. Consultare: Evento, Messaggio.

Si riferisce a un’azione che avviene durante la compilazione di un modulo software. Consultare: Tempo di esecuzione, Tempo di modellazione.

Strato – Layer Organizzazione di un insieme di classificatori o di package che si trovano allo stesso livello di astrazione. Uno strato rappresenta una sezione orizzontale di un’architettura, mentre una partizione ne rappresenta una sezione verticale. Contrario: Partizione.

Stringa – String Sequenza di caratteri. I dettagli della rappresentazione delle stringhe dipendono dalla particolare implementazione considerata, la quale può includere insiemi di caratteri internazionali e grafici.

Superclasse – Superclass In una relazione di generalizzazione, rappresenta la generalizzazione di un’altra classe: la sottoclasse o figlia (child). Consultare: Generalizzazione. Contrario: Sottoclasse.

Supertipo – Supertype In una relazione di generalizzazione, rappresenta la generalizzazione di un altro tipo: il sottotipo. Consultare: Generalizzazione. Contrario: Sottotipo.

Template – Template Sinonimo: Elemento parametrizzato.

Tempo di analisi – Analysis time Si riferisce a qualcosa che avviene durante la fase di analisi del processo di sviluppo del software. Consultare: Tempo di disegno, Tempo di modellazione.

Tempo di disegno – Design time Si riferisce a qualcosa che si verifica durante la fase di disegno del processo di sviluppo del software. Consultare: Disegno, Tempo di modellazione. Contrario: Tempo di analisi.

Tempo di esecuzione – Run time Intervallo di tempo durante il quale un programma per computer è in esecuzione. Contrario: Tempo di modellazione.

Tempo di modellazione – Modeling time Si riferisce a qualcosa che avviene durante una fase di modellazione del processo di sviluppo del software. Include tempo di analisi e di disegno. Quando si parla di sistemi implementati utilizzando il paradigma Object Oriented, spesso risulta molto importante distinguere chiaramente problemi relativi al tempo di modellazione da quelli attinenti il tempo di esecuzione. Consultare: Tempo di disegno, Tempo di analisi. Contrario: Tempo di esecuzione.

Test – Test Workflow il cui obiettivo è verificare che un particolare manufatto sia conforme ai relativi requisiti. Il test più importante è quello che consiste nel verificare le caratte-

ristiche e la rispondenza ai requisiti di ogni build del sistema e in particolare la versione consegnata all’utente. Consultare: Build.

Thread (Flusso di esecuzione) – Thread (of control)

(radice) in una gerarchia di contenimento. Questo stereotipo (topLevel) definisce i limiti esterni per la ricerca dei nomi in uno specifico spazio dei nomi. Consultare: Package.

Traccia – Trace

Singolo percorso di esecuzione all’interno di un programma o di un modello o di qualsiasi altra rappresentazione del flusso di esecuzione. Si tratta anche di uno stereotipo (thread) utilizzato per la realizzazione di un oggetto attivo come un processo leggero. Consultare: Processo.

Stereotipo della relazione di dipendenza indicante una relazione di storicità o di processo tra due elementi che rappresentano lo stesso concetto senza indicare le regole specifiche per derivare l’uno dall’altro. Consultare: Dipendenza, Processo.

Tipo – Type

Relazione tra due stati indicante che un oggetto nel primo stato, in risposta della ricezione di un determinato evento e risultando soddisfatte precise condizioni, è in grado di transitare nel secondo stato, eseguendo specifiche azioni. Quando avviene il cambiamento di stato, si dice che la transizione è innescata (fire). Consultare: Innescare (Fire), Stato.

Stereotipo dell’elemento classe che specifica un dominio di oggetti corredati dalle operazioni ad essi applicabili, senza definirne l’implementazione fisica. Un tipo può non contenere alcun metodo, mantenere il proprio flusso di esecuzione (thread), e/o essere annidato. Comunque può possedere attributi e instaurare associazioni con altri tipi. Sebbene un oggetto possa prevedere al più una classe di implementazione, potrebbe essere conforme a molti tipi diversi. Consultare: Classe implementazione. Contrario: Interfaccia.

Tipo di dato – Datatype Descrittore di un insieme di valori privi di identità e di un insieme di operazioni, a questi applicabili, che non prevedono effetti collaterali. I tipi di dato includono quelli predefiniti e quelli definiti dall’utente. Alla prima categoria appartengono numeri, stringhe e tempo. Alla categoria dei tipi di dato definiti dall’utente appartengono i tipi enumerati. Consultare: Tipo enumerato.

Tipo enumerato – Enumeration Lista di valori identificati da un nome, utilizzata come dominio dei valori assegnabili a un particolare tipo di attributo. Per esempio, Visibilità = {public, protected, private, package}.

Tipo espressione – Type expression Tipo risultato della valutazione di un’espressione. Consultare: Tipo.

Tipo primitivo – Primitive type Tipo di dato primitivo e predefinito senza sottostruttura. Alcuni esempi di tipo di dato primitivo: intero, reale, …

Top level (Livello superiore) – Top level Stereotipo di package che denota il package più elevato

Transizione – Transition

Transizione interna – Internal transition Transizione prodotta come risultato di un evento, che non genera il cambiamento di stato di un oggetto.

U UML Linguaggio di modellazione unificato – UML Unified Modeling Language Specifica che definisce un linguaggio grafico per visualizzare, specificare, costruire, e documentare manufatti di un sistema a oggetti distribuito. La specifica include la definizione formale di un metamodello comune di analisi e disegno (OA&D), una notazione grafica e interfacce CORBA IDL che rendono possibile l’interscambio di modelli tra diversi tool e tipi di repository per metadata.

Unità di distribuzione – Distribution unit Insieme di oggetti o componenti allocati in gruppo (unitariamente) ad un processo o processore. Un’unità di distribuzione può essere rappresentata come una composizione o aggregazione a tempo di esecuzione. Consultare: Aggregazione, Composizione, Tempo di esecuzione.

Usage (Utilizzo) – Usage Dipendenza in cui un elemento (cliente) richiede la presenza di un altro (fornitore) per il suo corretto funzionamento e la sua implementazione.

Utilità – Utility Stereotipo che raggruppa variabili globali e procedure nella forma della dichiarazione di una classe. Gli attributi e le operazioni dell’utilità diventano, rispettivamente, variabili e procedure globali. Un’utilità non è un costrutto di modellazione fondamentale, ma un espediente utile alla programmazione.

omettere altre entità che non sono rilevanti per i fini della proiezione stessa.

Vista architetturale – Architectural view Proiezione della struttura e comportamento di uno specifico modello di un sistema, focalizzato sugli aspetti architetturali significativi dello stesso modello.

V

W

Valore – Value

Worker (Lavoratore) – Worker

Elemento di uno specifico tipo di dominio. Consultare: Tipo.

Nei processi di sviluppo del software, rappresenta un “segnaposto” (un attore) destinato a essere sostituito da un individuo del team. Il lavoratore è caratterizzato da particolari responsabilità e capacità come l’essere in grado di eseguire specifiche attività e sviluppare prestabiliti manufatti. Consultare: Attività, Manufatto, Workflow.

Valore etichettato – Tagged value Definizione esplicita di una proprietà attraverso l’accoppiamento del nome a uno specifico valore. Nei valori etichettati, tale nome è denominato etichetta. Specifici valori etichettati sono predefiniti nello UML e altri possono essere definiti dall’utente. I valori etichettati rappresentano uno dei tre meccanismi standard previsti dallo UML per estenderne la semantica. Consultare: Stereotipo, Vincolo.

Versione – Release Insieme di manufatti relativamente completo e consistente, che possibilmente includa anche il relativo build.

Vertice – Vertex Sorgente o destinazione di una transazione in una macchina a stati. Un vertice può essere sia uno stato, sia un pseudostato. Consultare: Pseudostato, Stato.

Vincolo – Constraint Condizione semantica o restrizione. Alcuni vincoli sono predefiniti in UML, altri possono essere definiti dagli utenti. I vincoli costituiscono uno dei tre meccanismi di estensione previsti dallo UML. Consultare: Valore etichettato, Stereotipo.

Visibilità – Visibility Tipo enumerato i cui valori (public, private, protected e package) denotano come l’elemento del modello al quale è riferito può essere visto al di fuori del proprio spazio di denominazione (namespace). Consultare: Spazio di denominazione.

Vista – View Proiezione di un modello, realizzata da una particolare prospettiva al fine di enfatizzare specifici aspetti e di

Workflow (Flusso di lavoro) – Workflow Realizzazione di una parte o di un intero caso d’uso di business. Può essere descritto in termini di diagramma delle attività, in cui si evidenziano i vari lavoratori, le attività che eseguono e i manufatti prodotti. Consultare: Attività, Manufatto, Worker.

X XP eXtreme programming Processo leggero di sviluppo del software nel quale viene conferita particolare importanza al processo di implementazione (code-centric).

Appendice

E

Bibliografia ragionata

[BIB01] GRADY BOOCH – JAMES RUMBAUGH – IVAR JACOBSON, The Unified Modeling Language User Guide. Addison Wesley [BIB02] HANS-ERIK ERIKSSON – MAGNUS PENKER, UML Toolkit. Wiley [BIB03] GRADY BOOCH – JAMES RUMBAUGH – IVAR JACOBSON, The Unified Modeling Language Reference Manual. Addison Wesley [BIB04] ERICH GAMMA – RICHARD HELM – RALPH JOHNSON – JOHN VLISSIDES – GRADY BOOCH, Design Patterns: Elements of Reusable Object-Oriented Software. Addison Wesley. [BIB05] DOUG ROSENBERG WITH KENDALL SCOTT, Use Case Driven Object Modeling with UML. A Pratical Approach. Addison Wesley [BIB07] OMG Unified Modeling Language Specification. OMG (Object Management Group) [BIB08] GRADY BOOCH – J AMES R UMBAUGH – I VAR JACOBSON , The Unified Software Development Process. Addison Wesley [BIB09] KENT BECK (FOREWORD Embrace Change

BY

ERICH GAMMA), Extreme Programming explained.

2

Appendice E. Bibliografia ragionata

[BIB10] ALISTAIR COCKBURN, Writing Effective Use Case. AddisonWesley [BIB11] JOS WARMER – ANNEKE KLEPPE, The Object Constraint Language. Precise Modeling With UML. Addison Wesley [BIB12] SCOTT AMBLER, Process Pattern. [BIB13] GRADY BOOCH, Object-Oriented Analysis And Design. With Application. BenjaminCummings [BIB14] FRANK ARMOUR – GRANVILLE MILLER, Advanced Use Case Modeling. Software Systems. Addison Wesley [BIB15] JOHN CHEESMAN – JOHN DANILES, UML Components. A Simple Process for Specifying Component-Based Software. Addison Wesley [BIB16] STEVE COOK – JOHN DANILES, Design Object Systems. Object-Oriented Modelling with Syntropy. Addison Wesley [BIB17] IVAR JACOBSON, Object-Oriented Software Engineering. A Use Case Driven Approach. Addison Wesley [BIB18] COLIN ATKINSON – JOACHIM BAYER – CHRISTIAN BUNSEE – ERIK KAMSTIES – OLIVER LAITENBERGER – ROLAND LAQUA – DIRK MUTHIG – BARBARE PAECH – JÜRGEN WÜST – JÖRG ZETTEL, Component-based. Product Line Engineering with UML. Addison Wesley [BIB20] BETRAND MEYER, Object Oriented Software Construction. Prentice Hall [BIB21] WILLIAM H. BROWN – RAPHAEL C. MALVEAU – HAYS W. “SKIP” MCCORMICKIII – THOMAS J. MOWBRAY. Anti Patterns. Refactoring Software, Architectures, and Project in Crisis. Addison Wesley [BIB23] PETER COAD – ERIC LEFEBVRE – JEFF DE LUCA, Java Modelling In Color With UML: Enterprise Components and Process. Prentice-Hall [BIB24] MARTIN FOWLER, Analysis Pattern, Part 1. Addison Wesley [BIB25] MICHAEL JACKSON, Software Requirements and Specification. Addison Wesley

UML e ingegneria del software: dalla teoria alla pratica

3

[BIB26] C RIS K OBRYN , Modelling Components and Frameworks with UML. «Communications of ACM», Volume 43, n. 10, October 2000 [BIB27] BERTRAND MEYER, The Significance of Components. «Software Development», November 1999 [BIB28] GEORGE A. MILLER, The Magical Number Seven – Plus or Minus two: Some Limits On Our Capacity For Processing Information. «Psycology Review» n. 63 (1956) [BIB29] JAMES RAMBAUGH – MICHEAL BLAHS – WILLIAM PREMERLANI – FEDERICK EDDY – WILLIAM LORENSEN, Object Oriented Modeling And Design. Prentice-Hall [BIB30] CLEMENS SZYPERSKI, Component Software: Beyond Object Oriented Programming. Addison Wesley [BIB31] CLEMENS SZYPERSKI, Components and Contracts. «Software Development», May 2000 [BIB32] IEEE Recommended Practice for Software Requirements Specifications. IEEE Std 830-1998 [BIB33] JESS GARMS – DANIEL SOMERFIELD, Java Security: JCA – JCE – JAAS – JSSE – SSL and E-Commerce. Wrox

View more...

Comments

Copyright ©2017 KUPDF Inc.
SUPPORT KUPDF