Full Transcript

INGEGNERIA DEL SOFTWARE PARTE I Ciclo di vita di un software - Modello Waterfall Il software subisce uno sviluppo dall’idea iniziale di un possibile prodotto fino all’implementazione ed alla consegna al cliente (ed in seguito durante...

INGEGNERIA DEL SOFTWARE PARTE I Ciclo di vita di un software - Modello Waterfall Il software subisce uno sviluppo dall’idea iniziale di un possibile prodotto fino all’implementazione ed alla consegna al cliente (ed in seguito durante la sua manutenzione). Il ciclo di vita del software e costituito da varie fasi. Nel modello tradizionale, detto “a cascata” (waterfall), ogni fase ha un inizio ed una fine ben definiti. Ogni fase termina con la produzione di un artefatto che viene trasferito alla fase successive. Analisi e specifica dei requisiti In questa fase iniziale vi è un confronto tra cliente e progettista in cui vengono definiti gli elementi chiave del progetto. Questi vengono poi formalizzati e passati alla fase successiva. Progettazione di sistema e sua specifica Una volta definiti i requisiti, occorre progettare un sistema software che li soddisfi. Questa fase e divisa in due sottofasi: > Progettazione architetturale > Progettazione di dettaglio Il progetto architetturale definisce l’organizzazione del sistema in termini di componenti di alto livello e delle loro interazioni I componenti sono quindi scomposti in moduli di basso livello con interfacce definite in modo accurato. Ciascun livello di progettazione e descritto in opportuno documento di specifica in cui sono motivate le scelte progettuali effettuate. Codifica e test di modulo In questa fase si produce il codice che implementa ciò che è stato progettato nella fase precedente. I singoli moduli creati in questa fase sono testati prima di passare a quella successive. Integrazione e test di sistema Tutti i moduli sviluppati nella fase precedente sono assemblati, integrati e testati come un unico Sistema. Consegna e manutenzione Il sistema viene consegnato al cliente ed entra nella fase di manutenzione in cui vengono incorporate le modifiche apportate al sistema dopo la prima consegna. Qualità del software Il software si distingue dagli altri prodotti dell’ingegneria per la sua duttilità: è possibile modificare direttamente il prodotto anziché modificare il progetto. La duttilità del software è spesso utilizzata male: introdurre modifiche sostanziali senza riconsiderare il progetto può essere molto rischioso. Un’altra caratteristica distintiva del software è che il suo costo finale non è determinato dalla fabbricazione ma dalla progettazione e dall’implementazione. Per realizzare un prodotto software si utilizza un processo. Le caratteristiche e le qualità del processo influenzano quelle del prodotto. Il prodotto di un processo di sviluppo software non si riduce solo a quanto viene consegnato al committente (codice eseguibile e documentazione): nel corso del processo di sviluppo vengono realizzati altri artefatti quali i documenti di specifica dei requisiti, i dati dei test etc. Anche questi altri artefatti sono soggetti agli stessi requisiti qualitativi del prodotto finale. Le qualità sono: Esterne → quelle visibili agli utenti (es. usabilità) Interne → la struttura ed il progetto software Correttezza Un prodotto software deve soddisfare i suoi requisiti funzionali (cioè la correttezza dell’output rispetto all’input), ma anche i suoi requisiti non funzionali (quelli che riguardano le sue prestazioni, il livello di scalabilità, etc). La correttezza impone il soddisfacimento sia delle specifiche (funzionali) che dei requisiti non funzionali. La correttezza è una proprietà matematica che stabilisce l’equivalenza tra il software e la sua specifica. La definizione di correttezza presuppone che le specifiche siano chiaramente definite e sia possibile stabilire in modo non ambiguo se siano soddisfatte o meno. Raramente però sono disponibili. Può essere valutata tramite metodi di testing o tramite approcci analitici. Affidabilità Un software è considerato affidabile se opera come ci si attende che esso faccia. L’affidabilità è definita come la probabilità che il software si comporti come atteso per un certo intervallo di tempo. La correttezza è una proprietà assoluta mentre l’affidabilità è un concetto relativo (un software non corretto può essere considerato affidabile). Posto che i requisiti funzionali individuino esattamente le proprietà desiderabili, i software corretti sono un sottoinsieme di quelli affidabili. Robustezza Un sistema software è robusto se si comporta in modo accettabile anche in circostanze non previste dalle specifiche (ad esempio se vengono forniti in input dati non corretti, o in presenza di malfunzionamenti hw). Prestazioni Le prestazioni di un sistema sono influenzate dalla sua efficienza ma non si riducono ad essa. Le prestazioni influiscono sull’usabilità del sistema. Un sistema che utilizza troppe risorse (memoria, spazio su disco) non ha un buon livello di qualità prestazionali. Le prestazioni influenzano la scalabilità del sistema, ovvero la capacità di funzionare con input di dimensioni più grandi. Si può valutare tramite analisi della complessità, simulazioni o misurazioni. Usabilità Un software è considerato usabile (o user friendly) se è facile da utilizzare dagli utenti. Nei sistemi in cui non è prevista un’interfaccia utente (es. sistemi embedded), l’usabilità riguarda l’adattabilità all’ambiente hardware. Verificabilità È molto importante poter verificare la correttezza delle prestazioni di un sistema. La verifica può essere svolta con metodi di analisi (formale) e testing. Per sistemi molto complessi può essere utile suddividerli in moduli e studiarne la verificabilità singolarmente. Manutenibilità La manutenzione del software riguarda l’eliminazione di difetti presenti nel prodotto software, gli interventi atti a migliorare il suo funzionamento ed introdurre nuove funzionalità (evoluzione del software) [oltre il 60% dei costi totali]. Ci sono tre categorie di manutenzione: Correttiva: eliminazione di errori (20%) Adattativa: modifiche per adeguare il software a cambiamenti dell’ambiente in cui esso opera (20%) Perfettiva: interventi migliorativi, introduzione di nuove funzioni/caratteristiche (60%) La Manutenibilità può essere vista come l’insieme di due qualità: la riparabilità e l’evolvibilità. Riparabilità Un sistema è riparabile se i suoi difetti possono essere corretti con una quantità ragionevole di lavoro. Evolvibilità La duttilità intrinseca del software consente di effettuare modifiche direttamente sull’implementazione. Se il software è progettato tenendo in conto la sua evoluzione e se ogni modifica viene progettata attentamente allora esso può evolvere in modo ordinato e controllato. Riusabilità È una qualità simile all’evolvibilità: Un software può essere riusato per dare origine ad un altro prodotto. La riusabilità può coinvolgere l’intera applicazione o specifiche parti di essa (moduli, routine, etc.). Le librerie software (es la libreria standard di Java) sono esempi di software riusabile. La riusabilità è uno degli obiettivi principali della programmazione orientata agli oggetti. Portabilità Il software è portabile se può essere eseguito in ambienti diversi (piattaforme hardware, sistemi operativi). Comprensibilità La comprensibilità di un sistema software dipende principalmente da come è stato progettato. Sistemi software complessi anche se ben progettati sono più difficili da capire. Le tecniche di astrazione e modularità favoriscono la comprensibilità. La comprensibilità gioca un ruolo fondamentale durante la fase di manutenzione. È una qualità interna che aiuta a perseguire altre qualità quali l’evolvibilità e la verificabilità. Interoperabilità L’interoperabilità si riferisce alla capacità di un sistema di coesistere e cooperare con altri sistemi. Può essere raggiunta mediante l’uso di interfacce standardizzate. Qualità del prodotto Produttività La produttività è una qualità del processo di produzione del software. Indica l’efficienza e le prestazioni del processo. Nella gestione di un processo di sviluppo si cerca di organizzare e coordinare i gruppi di lavoro in modo da sfruttare al massimo la produttività dei singoli individui. La riusabilità è una qualità del software che influenza la produttività del processo. Tempestività La tempestività indica la capacità di rendere disponibile un prodotto al momento giusto. La tempestività non sempre è utile: non ha molto senso consegnare un prodotto entro la data prevista ma privo di qualità quali affidabilità e prestazioni. Visibilità Un processo di sviluppo è visibile se lo stato corrente e tutti i passi del processo sono documentati in modo chiaro. La visibilità consente di valutare l’impatto delle azioni sul progetto complessivo. Principi dell’Ingegneria del Software Tali principi possono essere applicati a qualunque fase dello sviluppo del software e sono alla base dei metodi e delle tecniche (la loro unione genera la metodologia) che costituiranno il software. La modularità è il principio fondamentale nella fase di progettazione. Principi fondamentali Rigore e formalità La formalità è il livello più alto di rigore: richiede che il processo di sviluppo sia valutato tramite leggi matematiche. Offre sicuramente maggiori garanzie ma è anche spesso troppo complessa da applicare. Un adeguato livello di rigore è necessario al fine di ottenere prodotti affidabili. Ad ogni modo, parti critiche (come lo scheduler di un sistema real-time) necessitano di una descrizione formale del loro funzionamento e di una dimostrazione formale della loro correttezza. L’utilizzo di criteri per la valutazione della correttezza di un sistema e per la scelta dei dati in input per lo sviluppo di test è un esempio di approccio rigoroso ma non formale: non si ha, cioè, una certezza sulla correttezza del sistema dal momento che non vengono utilizzati strumenti matematici, ma è comunque più affidabile del risultato di un approccio casuale (non rigoroso). Separazione degli Interessi Per dominare la complessità è necessario separare i vari aspetti del problema concentrandosi su ognuno di essi in maniera isolata. Esistono vari modi di separazione: Temporale → si dividono le attività in diversi periodi temporali; Qualitativa → ci si occupa di una qualità per volta secondo un approccio prioritario; Separazione interna → ovvero ci si occupa separatamente di diverse parti del sistema (da qui si arriva alla modularità); Un inconveniente legato alla separazione degli interessi è la minore possibilità di effettuare ottimizzazioni globali. Modularità Un sistema complesso è detto modulare se può essere suddiviso in parti più semplici, detti moduli. Se ci si concentra prima sui moduli e poi sulla loro composizione si ha un approccio bottom-up. Se si procede prima alla scomposizione in moduli e poi sulla progettazione di ciascuno di essi si ha un approccio top-down. La modularità fornisce 4 benefici fondamentali: 1. Capacità di scomporre un sistema complesso in parti più semplici (divide et impera); 2. Capacità di comporre un sistema complesso a partire dai moduli esistenti; 3. Capacità di comprendere un sistema in funzione delle sue parti; 4. Capacità di modificare un sistema modificando solo un insieme delle sue parti; Per ottenere i benefici è necessario che i moduli abbiano: Alta coesione → un modulo è altamente coeso se tutti i suoi elementi sono strettamente connessi; Basso accoppiamento → un accoppiamento definisce la relazione tra i moduli. Un basso accoppiamento consente di analizzare e modificare ciascun modulo separatamente; Astrazione L’astrazione consente di identificare gli aspetti fondamentali del fenomeno e di ignorare i dettagli. È un caso particolare di separazione degli interessi. L’astrazione genera modelli, attraverso i quali è possibile ragionare ed effettuare simulazioni sul sistema prima che esso venga realizzato. Un esempio di astrazione sono i linguaggi di programmazione (scrivere programmi senza implementarli a basso livello). Anticipazione del cambiamento La capacità di supportare l’evoluzione del software richiede l’abilità di anticipare i potenziali cambiamenti futuri. I progettisti devono quindi essere in grado di predire eventuali cambiamenti futuri al fine di predisporre il sistema all’evolvibilità e alla riusabilità. Generalità Ogni volta che si cerca di risolvere un problema, si cerca di scoprire qual è il problema più generale che si nasconde dietro quello specifico. La soluzione generalizzata potrebbe essere più costosa ma favorisce la riusabilità. Occorre bilanciare la generalità rispetto ai costi e all’efficienza della soluzione (es. backtracking). Incrementalità Si parla di incrementalità quando un processo procede per passi che permettono di raggiungere un obiettivo. Nel caso del software si intende che l’applicazione è il risultato di un processo evolutivo. Caso di studio: il compilatore Un compilatore è un prodotto critico: dalla sua correttezza dipende la correttezza delle applicazioni che gestisce. È necessario seguire uno sviluppo rigoroso; sono stati inoltre sviluppati metodi sistematici e formali per la sua progettazione. Separazione degli Modularità Astrazione Anticipazione del interessi cambiamento Efficienza del Il compilatore legge le Sintassi astratta per Si considerano i compilatore e interfaccia sequenze di caratteri ignorare dettagli possibili cambiamenti user-friendly. inseriti (fase 1), le converte sintattici (es. uso di {} nel linguaggio in una certa sintassi e per identificare i sorgente genera token (fase 2) e blocchi) e generazione infine genera il codice di codice intermedio. corrispondente (fase 3). Ad ogni fase è possibile associare un modulo diverso. Processo di produzione del software Serve a definire il ciclo di vita del software al fine di stabilire le attività che devono essere svolte ed il loro ordine. Ha come obiettivo la standardizzazione, la predicibilità e la produttività del software. Modello 1: Code and Fix Il primo modello utilizzato fu il modello code and fix, consistente di due passi: scrittura del codice e correzione degli errori. Dopo una serie di cambiamenti il codice era però disorganizzato e non era in grado di gestire applicazioni complesse. Ciò ha portato alla crisi del software, da cui nascerà poi l’ingegneria del software. Modello 2: Modello di processo di sviluppo Ha come obiettivo determinare l’ordine degli stadi e i criteri di transizione dall’uno all’altro. L’utilizzo di modelli di processo di sviluppo garantisce la qualità del prodotto. Se non si segue un preciso modello, lo sviluppo può essere visto come una black box, e potrebbe non essere sviluppato entro i tempi richiesti né in maniera corretta. Attività principali del processo di sviluppo Sono le attività che devono essere svolte indipendentemente dal modello adottato. Ciò che cambia tra i modelli è solo il flusso di informazioni tra tali attività. 1. Studio di fattibilità Viene eseguita prima di cominciare il processo di produzione per decidere se procedere con lo sviluppo. Il suo risultato è un documento che illustra diversi scenari e soluzioni alternative. Il progettista deve valutare il problema a livello globale. 2. Acquisizione, analisi e specifica dei requisiti Capire gli obiettivi del sistema e documentare i requisiti da soddisfare (non come implementarle). È necessario: Comprendere il dominio applicativo; Individuare i principali stakeholder (coloro i quali sono interessati al sistema, responsabili quindi dell’accettazione dello stesso); In questa fase viene attuata inoltre una modularizzazione orizzontale: il sistema viene strutturato come una collezione di viste allo stesso livello di astrazione. Il risultato è un documento di specifica: definisce le qualità che devono essere raggiunte, ha alcune qualità (come comprensibilità, non essere ambiguo, precisione, etc) e deve essere approvato dagli stakeholder. Contenuto del documento di specifica > Dominio → breve descrizione del dominio applicativo > Requisiti funzionali > Requisiti non funzionali > Requisiti del processo di sviluppo e manutenzione Classificazione dei requisiti ▪ MUST → insieme minimo di requisiti per considerare “accettabile” il comportamento del sistema da realizzare. ▪ SHOULD → requisiti desiderabili in quanto rendono più completo il sistema, ma la cui omissione non compromette nessuna funzionalità di base. La loro implementazione/integrazione non dovrebbe richiedere modifiche profonde alla struttura del progetto. ▪ MAY → sono requisiti la cui presenza nel progetto è facoltativa: in caso di mancanza di risorse (tempo) possono essere tralasciati inizialmente. Tuttavia, la loro integrazione successiva potrebbe comportare modifiche significative al sistema, come il riprogetto o l’aggiunta di nuove classi. 3. Definizione dell’architettura software Il risultato è un documento di specifica del progetto: descrive il sistema in termini di componenti, delle loro interfacce e delle relazioni tra di esse. 4. Produzione di codice e test dei moduli Il risultato di questa attività è la produzione di moduli implementati e testati. La produzione di codice e i test sui moduli possono seguire standard aziendali. 5. Integrazione e test del sistema Consiste nell’assemblare l’applicazione a partire dai singoli componenti sviluppati e testati separatamente. Alcuni modelli di processo (incrementali) includono quest’attività nella produzione del codice: i componenti sono testati ed integrati man mano che vengono realizzati. Allo stadio finale si procede al test dell’intera applicazione (Alpha test). 6. Rilascio, installazione e manutenzione Il software è consegnato ai clienti in due fasi: 1) Ad un piccolo gruppo motivati a rivelare la presenza di eventuali errori (Beta test) 2) Rilascio ufficiale La manutenzione è la fase più costosa. 1) Modello a cascata Questa tipologia di modelli organizza le attività in un flusso sequenziale. Pro → l’implementazione è rimandata finchè non sono chiari gli obiettivi e la pianificazione è ben strutturata. Contro → Lineare, rigido, non flessibile a modifiche. Alcuni modelli a cascata consentono di introdurre del feedback in modo disciplinato. Esso è però limitato tra una fase e quella successiva: 1.1) Modello a cascata con feedback 2) Modelli evolutivi (o incrementali) Questi modelli nascono dall’idea che la prima versione di un prodotto è sicuramente soggetta ad errori, e deve quindi essere rifatto. Un modello di processo evolutivo è “un modello le cui fasi consistono in versioni incrementali di un prodotto software operazionale con una direzione evolutiva determinata dall’esperienza pratica”. Cioè, viene rilasciata una prima versione a cui sicuramente dovranno essere aggiunte altre fasi dipendentemente dall’esperienza pratica. Si basa quindi sul concetto di rilasci incrementali. Le versioni incrementali possono essere rilasciate al cliente man mano che sono sviluppate. Si definisce versione incrementale rilasciabile: un’unità software funzionale autocontenuta che esegue qualche funzione utile al cliente. La strategia evolutiva può essere riassunta come segue: 1) Rilascio di una funzionalità all’utente 2) Misura del valore aggiunto per il cliente 3) Aggiustamento sia del progetto che degli obiettivi 3) Modelli a spirale È un modello ciclico. Ogni ciclo consiste di quattro fasi, ciascuna corrispondente ad un quadrante del diagramma cartesiano. Il raggio della spirale corrisponde al costo accumulato. La dimensione angolare rappresenta il progresso nel progetto. Casi pratici Synchronize and Stabilize (Microsoft): Si basa sulla scomposizione dei progetti in piccole squadre che lavorano in parallelo, ma che si comportano come un unico team di grandi dimensioni. Ciascun team è caratterizzato da sviluppatori e tester. Le specifiche sono in continua evoluzione e giornalmente ogni team inserisce i risultati prodotti all’interno di un database. Sono infine stabilite una serie di date in cui viene valutata la stabilità del prodotto; Software Open-Source: sono modelli gestiti da una regolamentazione delle licenze tale che: Non ci sono limitazioni sull’uso del sw; È possibile effettuare un numero qualsiasi di copie senza costi aggiuntivi; Chiunque può introdurre modifiche; Può essere distribuito ovunque; molti software open-source impongono però una condizione di copyleft, ovvero la nuova versione deve essere rilasciata sotto le stesse condizioni della licenza originale. Il modello di sviluppo si basa su strumenti di collaborazione a partire da un codice messo a disposizione di tutti. L’organizzazione dei progetti open-source è basata su un piccolo gruppo di sviluppatori ed una più grande popolazione di contributori. Il modello a spirale è detto orientato al rischio, dal momento che punta a minimizzare le scelte sbagliate: per ogni fase del ciclo di vita, infatti, vengono attraversati i quattro quadranti rappresentati nella spirale (si analizzano le alternative, i rischi e si fa una scelta). L’intero ciclo può riguardare tutte le fasi del ciclo di vita, oppure solo una parte di esse; Allo stesso modo, può riguardare l’intera applicazione, o solo una parte di essa. Ad ogni iterazione nella spirale si affronta una fase diversa. 4) Unified Software Development Process (UP o RUP) È una metodologia basata sul linguaggio di modellazione UML (insieme di notazioni usate per le varie fasi del ciclo di vita). UP trasforma lo sviluppo di un sistema orientato agli oggetti in un modello iterativo e incrementale. Il principio di base è che un progetto molto grande dovrebbe essere scomposto in iterazioni controllate (mini progetti) che forniscono versioni incrementali del prodotto. Un ciclo di vita UP può quindi essere visto come una sequenza di cicli. Ogni ciclo (mini progetto) è suddiviso in quattro fasi e ognuna termina con una milestone. Una milestone consiste in un insieme di artefatti che possono essere soggetti ad un controllo di qualità. Le fasi del RUP (Rational Unified Process) sono: 1) Concezione → analisi di fattibilità 2) Elaborazione → si identificano i casi d’uso e si specifica l’architettura software (baseline) 3) Costruzione → Si implementa arricchendo la baseline sviluppando e testando il codice fino a supportare un insieme di qualità sufficiente per poterlo rilasciare 4) Transizione → include varie attività finali condotte iterativamente (beta test) Nessuna fase del ciclo è monolitica. Ogni fase è suddivisa in una serie di iterazioni controllate. Le iterazioni producono un incremento che può essere soggetto a valutazione. Un piano di iterazioni specifica il costo previsto, gli artefatti che saranno prodotti, l’allocazione delle risorse umane e l’assegnazione dei compiti. In RUP le attività di un processo sono chiamate workflow. I workflow non sono organizzati in fasi sequenziali. Le varie iterazioni attraversano in genere tutti i workflow principali. RUP è iterativo ed incrementale: è guidato dagli use case e centrato sulle architetture. Gli use case costituiscono l’input principale dei workflow di analisi, progettazione, implementazione e test. L’architettura è l’artefatto principale utilizzato per concettualizzare, gestire e fare evolvere il sistema. RUP best practice 1. Sviluppo iterativo del software. Pianificare gli sviluppi incrementali del sistema basandosi sulle priorità del cliente. Sviluppare le funzionalità più importanti nelle prime fasi del processo di sviluppo. 2. Gestione dei requisiti. Documentare esplicitamente i requisiti e mantenere traccia dei cambiamenti. Analizzare l’impatto dei cambiamenti prima di accettarli. 3. Uso di architetture component-based. Strutturare l’architettura del sistema in componenti riusabili. 4. Modellazione visuale. Usare modelli UML per rappresentare le viste statiche e dinamiche del sistema software. 5. Verifica della qualità. Garantire che il software soddisfi gli standard qualitativi dell’organizzazione. 6. Controllo del cambiamento. Gestire i cambiamenti nel software adottando procedure e strumenti adeguati AGILE SOFTWARE DEVELOPMENT I modelli precedentemente visti sono fortemente orientati alla produzione di documentazione, e sono detti plan- driven. La richiesta di produzione tempestiva di software risulta, però, essere fortemente limitata dai vincoli imposti da tecniche di questo tipo. I metodi di sviluppo agile cercano di andare in contro a questi nuovi requisiti. In questi metodi: > la specifica e l’implementazione sono interfogliate; > Il sistema è visto come una serie di incrementi: frequente rilascio di nuove versioni; > Gli stakeholder devono essere coinvolti (attivamente) in almeno le prime fasi di specifica; > Documentazione minima: ci si concentra sulla scrittura del codice; Se, da un lato, la drastica riduzione di documentazione consente di velocizzare i tempi di rilascio, dall’altro è molto rischioso. Ci deve comunque essere qualcuno che conosca a fondo il sistema, sebbene non totalmente documentato. Inoltre, nei metodi plan-driven, le fasi sono sequenziali ed ognuna termina con una documentazione. I metodi agili, invece, affidano la conoscenza del prodotto alle persone (piuttosto che nella documentazione) e le fasi sono condensate tra loro (→ maggiore flessibilità e possibilità di modifica). Principi dei metodi Agili Coinvolgimento del cliente → clienti strettamente coinvolti nel processo di sviluppo Rilascio incrementale Enfasi sulle persone, non sui processi Accogliere il cambiamento → evolvibilità Mantenere la semplicità → in assenza di documentazione, il mantenimento della semplicità è un requisito essenziale; Una metodologia agile è applicabile quando la dimensione del prodotto è medio/piccola, non sottoposto a molti vincoli e quando il cliente è attivamente coinvolto. Tecniche 1. Extreme Programming L’extreme programming (XP) fu una delle prime tecniche sviluppate. Possono essere rilasciate più versioni nello stesso giorno. Le versioni incrementali devono esser rilasciate al cliente ogni 2 settimane. Per ogni versione (build) si devono effettuare tutti i test ed una versione è rilasciata solo se passa tutti i test. Ciclo di rilascio La pianificazione, quindi, è presente ma non sul lungo periodo. 1.1 Principi e pratiche Pianificazione incrementale → le richieste dei clienti sono memorizzare in story cards. Queste vengono trasformate in task che dovranno essere implementate; > User stories → Le richieste del cliente sono espresse sottoforma di user stories o scenari. Sono trascritte in story cards e poi convertite in task dal team. Queste sono poi inserite nella pianificazione e se ne stimano i costi. Il cliente sceglie poi quali implementare. Piccoli rilasci → vengono implementati e rilasciati gli insiemi minimi di requisiti richiesti; → sviluppo incrementale Design semplice → ogni build deve implementare solo gli elementi necessari per i requisiti attuali; Test-first development → Si usano dei framework automatizzati per scrivere i test. È molto importante scrivere i test prima di implementare cosicché i requisiti siano chiaramente definiti. Ad ogni modifica, il sistema deve essere sottoposto ai test vecchi (test di regressione) e ai nuovi; Refactoring → Ci si deve aspettare che il codice potrebbe dover essere riscritto spesse volte per introdurre nuove funzionalità o per ridurre la complessità, garantendone anche la comprensibilità; → mantenimento della semplicità, accoglimento del cambiamento Pair Programming → gli sviluppatori lavorano in coppia, si controllano a vicenda; → people over process Responsabilità collettiva → ogni membro di una coppia ha pari responsabilità all’interno del sistema: ognuno deve conoscere approfonditamente ogni aspetto del sistema; Integrazione continua → il lavoro completato deve essere subito integrato al sistema e testato; Passo sostenibile → Non si deve esagerare nel lavoro poiché andrebbe a ridurre la qualità; On-site customer → Il cliente deve essere sempre reperibile e coinvolto in ogni momento; → Coinvolgimento del cliente I principi evidenziati sono quelli sempre presenti in qualunque tecnica agile. TEST-FIRST DEVELOPMENT Il test è un aspetto fondamentale di XP. È una tecnica per cercare di valutare la correttezza del sistema o componente. In XP ogni componente del sistema è testato dopo ogni modifica. I test solitamente sono definiti prima di implementare i componenti, in modo tale da avere chiari i requisiti (da qui test-first), e sono sviluppati in maniera incrementale a partire dalle user stories. I test devono essere sviluppati in modo tale da poter essere automatizzati e somministrati ai componenti: sono scritti come programmi e usano framework specifici, come JUnit. I test possono essere: > Positivi → il test notifica la correttezza dell’input > Negativi → il test notifica gli errori nell’input e blocca l’esecuzione Il test lavora su test set, all’interno del quale sono contenuti test case: un insieme di casi che cercano di coprire l’interno dominio di input. PROBLEMI→ Può essere difficile scrivere alcuni test in maniera incrementale o verificare che un test set sia completo. Agile Project Management Un ruolo importante nella gestione dei progetti è quello del project manager: si occupa di consegnare il progetto in tempo e utilizzando le risorse che erano state messe a disposizione. L’approccio standard del project management è quello plan-driven. Tuttavia, nel caso di agile project management, è necessario utilizzare un approccio diverso, in linea con uno sviluppo incrementale e con le pratiche usate nei metodi agili. SCRUM SCRUM è un processo agile che permette di focalizzarsi sulla consegna nel minor tempo possibile. Può essere applicato tramite metodi agili (come XP). Concetti chiave: Il team si auto-organizza per la pianificazione. Ci si incontra ogni giorno (daily scrum); Lo sviluppo del progetto è una progressione (sprint) organizzata in cicli (sprint cycle). Alla fine di ogni ciclo è rilasciato un nuovo build. Il tempo dello sprint cycle dipende dal metodo agile applicato (nel caso di XP, 2 settimane); I requisiti sono definiti in una lista, chiamata product backlog. Da questa lista sono poi selezionati i requisiti che si vogliono implementare nel corrente sprint cycle (la selezione dipende dalla capacità del team e dai tempi dello sprint cycle, i risultati che riescono ad ottenere cosituiscono la velocity), e generano un’altra lista (sprint backlog); Ad ogni sprint cycle si progetta, si codifica e si testa; Durante uno sprint non si accettano cambiamenti nei requisiti; Ruoli: Product Owner → definisce le funzionalità del prodotto, accetta o rifiuta i risultati del team. Idealmente, è chi richiede il prodotto; nella realtà, è una persona scelta dall’azienda che commissiona il prodotto e che si occupa di ricoprire questo ruolo; Scrum Master → ha funzione gestionale nel progetto: è un membro del team che si occupa di migliorare le pratiche in uso, rimuovere eventuali blocchi e facilitare la collaborazione tra ruoli. Diverso dal project manager poiché non si occupa di gestire l’intero progetto ma solo il team; Team → tipicamente 5-9 persone full-time con capacità interfunzionali. Non c’è gerarchia nel team. I membri possono cambiare solo tra uno sprint ed un altro. Scalabilità dei metodi agili I metodi agili sono risultati efficaci in progetti di piccole/medie dimensioni. L’obiettivo è quello di estendere questi metodi per un gruppo più ampio di sviluppo e un progetto più grande. > Scaling up → problemi associati alla crescita della dimensione del progetto. È importante in questo caso mantenere chiari gli elementi fondamentali delle tecniche agili; > Scaling out → problemi associati all’introduzione dei metodi agili all’interno di aziende che non ne hanno mai fatto uso; Problemi pratici dei metodi agili o L’aspetto informale delle tecniche di sviluppo agili sono sicuramente in contrasto con i vincoli di tipo legali (ad es. contratti); o Sono più adatti alla creazione di nuovi software piuttosto che alla revisione di vecchi. I software sviluppati tramite metodi agili, dunque, si prestano poco alla manutenibilità e all’evolvibilità a causa della mancanza di documentazione formale; o Tutta la conoscenza è affidata al team: se il team cambia, i tempi rallentano; o Mantenere alto l’interesse e il coinvolgimento del cliente; Alcune problematiche sono presenti sia nei metodi agili che in quelli plan-based, e sono divisi in tre fattori: Metodi agili per sistemi complessi Problemi: o Grandi sistemi sono solitamente composti da parti separate e gestiti da diversi team, causando problemi di comunicazione. o Possono essere, inoltre, “brownfield systems”: sistemi in cui ci sono porzioni di vecchi sistemi integrati con le nuove implementazioni. o Finanziati da più stakeholders → difficile raggiungere accordi; Ai fini dello scaling up: o Non ci può essere un singolo product owner → ogni team ha il proprio product owner e scrum master; o Nasce la figura del product architect per ogni team → si organizzano per coordinare l’architettura dei diversi prodotti; o Nasce scrum of scrums → incontro giornaliero tra i rappresentati dei singoli team; L’applicazione dei metodi agili per sistemi grandi è molto complessa. Più comunemente, negli sviluppi pratici i metodi utilizzati sono un insieme di metodi agili e plan-based. UML (Unified Modeling Language) È un linguaggio di modellazione, ha quindi una sua sintassi e semantica. È una famiglia di notazioni grafiche, dette diagrammi. È particolarmente adatto a descrivere sistemi realizzati con paradigmi Object Oriented. Può essere usato come bozza, per documentare alcuni aspetti del sistema, sia in fase di costruzione di un sistema (forward engineering) che nella comprensione di alcuni aspetti di un sistema esistente (reverse engineering). In questo caso sono scritti a mano. Può essere usato come progetto, al fine di fornire ai programmatori un modello dettagliato da implementare. I diagrammi devono essere sufficientemente completi per poter descrivere tutte le scelte progettuali e molto dettagliati in modo da descrivere tutti gli aspetti del sistema (ad es. descrivendo i modelli per le interfacce di un sistema). In questo caso sono costruiti tramite strumenti specifici (CASE → Computer Aided Software Engineering), utili anche alla validazione del diagramma. Può essere inoltre usato come linguaggio di programmazione, in cui il diagramma viene convertito in codice (tanto più è dettagliato il diagramma, tanto più accurata sarà la traduzione in codice). La programmazione può essere automatizzata fino ad un certo livello grazie a strumenti specifici. In generale, UML propone due prospettive: Prospettiva software → usato per modellare sistemi: gli elementi del modello corrispondono a modelli del sistema; Prospettiva concettuale → usato per rappresentare la descrizione dei concetti del dominio di interesse; Diagrammi di UML 2 La sintassi dei diagrammi non è molto rigida: si possono usare elementi di un tipo di diagramma in un altro. UML 2 definisce 13 diagrammi ufficiali: UML nel processo di sviluppo ANALISI DEI REQUISITI In questa fase è importante che i diagrammi siano semplici e comprensibili. o I diagrammi dei casi d’uso descrivono le interazioni tra sistema ed entità esterne; o Un diagramma delle classi può essere utilizzato per definire un vocabolario che descriva il dominio di interesse ed esprima le relazioni tra i concetti di tale dominio; o Diagrammi di attività possono essere usati per chiarire il funzionamento del sistema in casi più complessi; o Un diagramma di stato è utile per descrivere entità che hanno comportamenti che possono cambiare in funzione del verificarsi di alcuni eventi; PROGETTAZIONE o I diagrammi delle classi sono usati per illustrare le classi e le loro relazioni; o I diagrammi di sequenza sono usati per documentare gli scenari più comuni; o I diagrammi di package mostrano l’organizzazione del sistema su larga scala; o I diagrammi di stato sono usati per descrivere classi le cui istanze hanno un ciclo di vita complesso; o I diagrammi di deployment illustrano invece la disposizione fisica del sistema; DOCUMENTAZIONE o Un diagramma di package offre una mappa logica del sistema; o Un diagramma di deployment mostra l’aspetto fisico del sistema ad un alto livello di astrazione; o All’interno di ciascun package si può includere un class diagram che evidenzi solo le caratteristiche più importanti. Un certo numero di diagrammi di interazione può accompagnare un class diagram per documentare le interazioni più importanti; o Diagrammi di stato per descrivere comportamenti complessi, frammenti di codice; o Diagrammi di attività per illustrare algoritmi complicati; 1. CLASS DIAGRAM È il tipo di diagramma più utilizzato. Descrive il tipo degli oggetti che fanno parte del sistema e le relazioni statiche che intercorrono tra di essi. Mostra le proprietà e le operazioni (metodi) delle classi. UML usa il termine di caratteristica (feature) per indicare sia le proprietà che le operazioni di una classe. Le classi sono rappresentate tramite rettangoli (box) contenenti all’interno nome della classe, attributi e operazioni. Non c’è una vera distinzione tra classe ed interfaccia. Proprietà Rappresentano le caratteristiche strutturali di una classe (istanza della classe in java). Sono rappresentate tramite attributi o associazioni. Attributi Descrive una proprietà come una stringa secondo il seguente formato: visibilità nome: tipo molteplicità = default {stringa-di-proprietà} > Visibilità → specifica se l’attributo è pubblico (+), privato (-), protetto (#) o ha visibilità di package(); > Nome > Tipo → vincolo sugli oggetti che possono corrispondere all’attributo > Default → valore assunto dall’attributo se non diversamente specificato > Molteplicità → esprime un vincolo sul numero di oggetti che possono essere collegati all’attributo; > {stringa-di-proprietà} →indica caratteristiche aggiuntive di un attributo (ad esempio vincoli). Ad esempio {frozen} indica un attributo che in java è final. Associazioni Un’associazione che modella una proprietà è raffigurata come una linea continua che collega due classi, orientata dalla classe sorgente a quella destinazione. Il nome della proprietà e la molteplicità sono indicati vicino all’estremità dell’associazione relativa alla destinazione. La classe destinazione corrisponde al tipo della proprietà. Le associazioni sono usate per proprietà le cui classi sono più complesse. Molteplicità La molteplicità di una proprietà indica quanti oggetti possono entrare a farvi parte. Nel caso di Ordine → Data, indica che in Ordine possono esserci 0..1 istanze di Data. Le più comuni sono: 1 (Un ordine deve essere fatto da un solo cliente.) 0..1 (Un’azienda cliente può avere un rappresentante o meno.) * (da zero a molti: un cliente può anche non fare un ordine, ma non c’ è limite superiore). * è un’abbreviazione per 0..* Si possono specificare degli intervalli (ad esempio 2..4) fornendo esplicitamente i limiti inferiore e superiore Se i due estremi coincidono si usa un solo numero (1 equivale a 1..1) Terminologia > Opzionale: implica 0 come limite inferiore. > Obbligatorio: implica un limite inferiore di 1 (almeno uno) > Ad un sol valore: implica un limite superiore di 1 (al più uno) > A più valori: implica un limite superiore maggiore di 1 (di solito *) La molteplicità aumenta il livello di dettaglio del modello. Di default gli elementi coinvolti in una molteplicità a più valori formano un set: non è, cioè, definito un ordinamento tra di essi → {unordered} Per specificare diversamente, occorre segnalare {ordered}. La molteplicità di default di un attributo è. Interpretazione della proprietà nella programmazione Non esiste un’unica interpretazione per le proprietà a livello di codice. Un attributo corrisponde a campi privati ed è reso visibile all’esterno tramite getters e setters. Proprietà a più valori Se un attributo ha più valori, i dati corrispondenti formano una collezione. Convertendo in codice: se la collezione è ordinata si deve usare una collezione di oggetti in grado di rappresentarli adeguatamente (List), se invece è unordered, si dovrebbe usare un Set (nella pratica si usano sempre le liste). Le proprietà a più valori hanno un’interfaccia diversa rispetto a quelle a singolo valore (es. metodi per aggiungere o eliminare un elemento dalla collezione). Per questo una collezione non deve mai essere public. Si potrebbero quindi usare copie della collezione reale o un Proxy (interfaccia a sola lettura che disciplina l’accesso ad una risorsa). Associazioni Bidirezionali Un’associazione bidirezionale è una coppia di proprietà collegate. Il doppio collegamento indica che se si segue il valore di una proprietà e poi il valore di quella collegata si deve tornare ad un insieme che contiene l’elemento di partenza. Nell’esempio: Persona ha Automobile[*], e Automobile ha Persona proprietario. Implementare associazioni bidirezionali è difficoltoso dal momento che è necessario assicurarsi che le due proprietà siano sincronizzate → è necessario far sì che che l’associazione sia controllata da una sola delle due parti (quella a valore singolo, in questo caso Persona). La classe subordinata dovrà quindi violare l’incapsulamento al fine di mantenere la sincronizzazione. Dando il controllo a Persona, posso usare metodi con visibilità di package rimuoviProprietario(), aggiungiProprietario() della classe Automobile. Operazioni Sono le azioni che la classe esegue, solitamente corrispondono ai metodi della classe. La sintassi è: visibilità nome (lista-parametri): tipo-di-ritorno {stringa-di-proprietà} > Lista dei parametri → lista ordinata dei parametri delle operazioni, separati da una virgola > Tipo-di-ritorno → specifica il tipo di ritorno Gli elementi della lista dei parametri seguono la seguente sintassi: direzione nome: tipo= default > La direzione indica se il parametro è di input (in), output (out) o entrambi (inout). Se omessa, è predefinito ad in; Spesso è utile distinguere tra le operazioni che cambiano lo stato del sistema e quelle che non lo fanno. UML definisce query quelle che ottengono un valore senza modificare lo stato. Tali operazioni sono etichettate con la stringa di proprietà {query}. UML distingue tra: Operazione → dichiarazione di una procedura Metodo → corpo della procedura Relazione di Generalizzazione (o Specializzazione) Dal punto di vista concettuale esprime la relazione che esiste tra concetti più generici e concetti più specifici. Dal punto di vista software l’interpretazione più comune è quella che implica il meccanismo dell’ereditarietà. Il principio fondamentale da rispettare quando si usa l’ereditarietà è il principio di sostituibilità (Liskov): Ovunque ci si attende un’istanza dell’entità pi generica deve essere possibile utilizzare, senza alterare il corretto funzionamento del sistema, un’istanza di quella più specifica. Note e Commenti Le note sono commenti aggiuntivi che possono apparire in qualunque tipo di diagramma UML. Possono essere collegate con una linea tratteggiata agli elementi cui fanno riferimento o essere disegnate isolate. Un commento breve può essere inserito nel box, preceduto da – Dipendenze Tra due elementi di un diagramma esiste una relazione di dipendenza se una modifica alla definizione di uno (fornitore o supplier) può comportare un cambiamento all’altro (il client). La relazione di generalizzazione rientra tra le dipendenze. La dipendenza non è una relazione transitiva (ovvero se A->B e B->C non è vero che A->C). Esistono diversi tipi di dipendenza, ognuna con una particolare semantica e parole chiave. Operazioni ed attributi statici UML chiama static un’operazione o un attributo che si applica ad una classe anziché ad una sua istanza. Le caratteristiche statiche sono sottolineate. Proprietà derivate Sono quelle che possono essere calcolate a partire da altri valori. “Derivata” (/) vuol dire che o viene calcolata in tempo di esecuzione in funzione di altre proprietà oppure indica una specifica, un vincolo. Aggregazione La relazione di aggregazione indica un’appartenenza (relazione più forte dell’associazione). Non c’è una chiara semantica che distingue l’aggregazione dall’associazione. Nell’esempio: un certo numero di persone fa parte di un club. Composizione La composizione è una forma forte di aggregazione. Sebbene una classe possa essere componente di molte altre, ciascuna sua istanza può essere componente di un solo oggetto. Nella figura, un’istanza di Punto può essere vertice di un Poligono oppure il centro di un Cerchio ma non entrambe le cose. A Poligono appartengono 3 Punti e Cerchio ha al suo interno un’istanza di Punto. La cancellazione di Poligono dovrebbe (dettaglio implementativo) comportare la cancellazione di tutti i suoi Punti. La molteplicità lato oggetto composto è implicitamente 0..1. Quando è specificata pari a 1 la classe componente può appartenere ad una sola altra classe (cioè non può appartenere a relazioni di aggregazione o associazione con altre classi). La composizione può essere anche espressa in UML con il contenimento. Relazione di Realizzazione: Interfacce e classi Astratte Le classi astratte si indicano scrivendone il nome in corsivo. Anche le operazioni astratte sono scritte in corsivo. In alternativa si utilizza l’etichetta {abstract}. Le interfacce sono caratterizzate dalla presenza della parola chiave nel compartimento del nome. Nell’esempio, List è una specializzazione di Collection e ArrayList è una specializzazione di AbstractList. È detta relazione di realizzazione: AbstractList realizza l’interfaccia List. Una classe fornisce (o realizza) un’interfaccia se è sostituibile ad essa. Una classe richiede un’interfaccia se necessita di un oggetto conforme ad essa per funzionare (dipende da essa). Una notazione alternativa, più compatta, fa uso di lollipop, per indicare un’interfaccia fornita, e socket, per indicare un’interfaccia richiesta. Nell’esempio, ArrayList è legata ad una relazione di realizzazione sia a Collection che a List. Associazioni Qualificate Un’associazione qualificata è l’equivalente UML di concetti quali array associativi ( tabelle hash, mappe, dizionari). Il qualificatore indica che per ogni istanza di Prodotto ci può essere una solo linea d’ordine collegata ad un’istanza di Ordine. La molteplicità va considerata nel contesto del qualificatore: un ordine può avere più linee d’ordine ma al più una per ciascun prodotto. Nell’esempio: se l’utente inserisce lo stesso prodotto più volte, ne viene incrementata la quantità nella LineaOrdine, non vengono create nuove istanze. Classi Associative Le classi associative permettono di aggiungere attributi, operazioni ed altre caratteristiche alle associazioni. Nella figura l’associazione tra Autore e Libro specifica il ruolo svolto dall’autore nella stesura del libro: autore principale, autore secondario, traduttore. Una forma alternativa è la seguente: È importante notare che ci può essere una sola istanza della classe associazione per ogni coppia di oggetti associati. In questa forma, infatti, manca il seguente vincolo: può esistere una sola istanza di PartecipazioneStesura per ogni coppia Autore- Libro. Nella forma precedente, questo vincolo è implicito. Classi Parametriche Queste classi vengono solitamente usate per modellare classi che lavorano con dati parametrici. Questi dati sono i generici in java o i template in C++. La relazione tra elemento legato e template (cioè la sostituzione del generico con un tipo specifico) si dice derivazione ed è indicato con. Enumeration Le enumerazioni sono utilizzate per modellare tipi che possono assumere solo un insieme finito di valori prefissati. Sono rappresentate da una classe marcata con la parola chiave che riporta l’elenco dei valori. Classi Attive Ogni istanza di una classe attiva esegue e controlla il proprio thread. 2. OBJECT DIAGRAM Se i class diagram definiscono delle relazioni statiche tra gli oggetti, gli object diagram rappresentano il sistema mentre è in esecuzione ma non la loro evoluzione. È una fotografia degli oggetti che costituiscono il sistema in un determinato momento. La notazione è simile a quella delle classi. Si possono specificare i valori per gli attributi. Gli oggetti sono collegati tra loro tramite link che sono istanze di associazioni. La sintassi è: nomeOggetto:nomeClasse nomeOggetto è opzionale. Volendo rappresentare contemporaneamente il class diagram e object diagram, si può utilizzare la relazione di dipendenza , che indica esplicitamente che gli oggetti sono istanze delle classi ed il link è un’istanza della relazione possiede. Formalmente gli elementi di un diagramma degli oggetti non sono vere istanze, ma specifiche di istanze. Nel diagramma è possibile omettere degli attributi o mostrare “istanze” di classi astratte. 3. SEQUENCE DIAGRAM I diagrammi di sequenza appartengono alla categoria dei diagrammi di interazione, i quali descrivono la collaborazione di un gruppo di oggetti che devono implementare un certo comportamento. Un diagramma di sequenza documenta tipicamente il comportamento di un singolo scenario. Il diagramma include un certo numero di oggetti e i messaggi scambiati tra di essi durante l’esecuzione del caso d’uso. Esempio: calcolo del prezzo di un ordine L’ordine deve calcolare il prezzo di ciascuna linea. Per farlo occorre ottenere la quantità relativa al prodotto ed il suo prezzo unitario. Una volta calcolato il prezzo totale, si calcola lo sconto che dipende dallo specifico cliente. Queste interazioni sono illustrate nel diagramma di sequenza a controllo centralizzato. Ciascun partecipante è rappresentato da un box che contiene il nome (nella notazione degli Object Diagram ma non sottolineato) e da una linea tratteggiata verticale detta “linea di vita” (lifeline). L’ordinamento dei messaggi è determinato scorrendo il diagramma dall’alto verso il basso. Ogni linea di vita ha una barra di attivazione che indica quando il partecipante è attivo nell’interazione. Le frecce indicano i messaggi (invocazioni) e sono etichettate con il nome del messaggio (nome del metodo). Le frecce di ritorno sono opzionali. Quando presenti, possono riportare il valore risultante dall’invocazione (Nell’esempio, l’oggetto prod: rendo esplicito il fatto che esista una relazione a runtime tra il partecipante Ordine e il partecipante Prodotto). Nell’esempio, il primo messaggio non scaturisce da un partecipante ed è detto messaggio trovato (found message), indicato da un pallino pieno. Quando un partecipante invia un messaggio a sé stesso, di solito si sovrappone una barra di attivazione a quella già presente. Diagramma di sequenza a controllo distribuito: Nel caso precedente, un partecipante svolge tutta l’interazione, gli altri forniscono solo i dati (controllo centralizzato). In questo caso, i compiti sono ripartiti tra i partecipanti (controllo distribuito). DIFFERENZE: nei diagrammi a controllo distribuito, si ha la possibilità di modificare il valore dei parametri dell’oggetto in funzione di alcuni vincoli o condizioni. Ad esempio, si potrebbe porre un certo prezzo per un prodotto se la quantità è inferiore ad una soglia ed un prezzo diverso se la quantità supera detta soglia. Ad ogni modo, per limitare gli effetti dei cambiamenti è opportuno che dati e logica che li gestisce siano concentrati nello stesso posto. Creazione e Distruzione dei partecipanti I diagrammi di sequenza prevedono una notazione particolare per indicare la creazione e distruzione dei partecipanti. o Per la creazione si disegna la freccia del messaggio che causa la creazione in modo che punti al box del partecipante creato. Il nome del messaggio è opzionale se si utilizza il costruttore. Per indicare che il partecipante creato fa subito qualcosa, si disegna la barra di attivazione attaccata al box. o La distruzione di un partecipante e indicata con una X posizionata sulla lifeline. Se la freccia di un messaggio termina sulla X, si intende che un partecipante (il mittente del messaggio) ne sta cancellando un altro. Se la X non è raggiunta da nessun messaggio, si intende che il partecipante si autodistrugge ( o che la cancellazione è gestita da un garbage collector). Frame di Interazione Uno dei problemi dei diagrammi di sequenza è la loro inadeguatezza a rappresentare comportamenti ciclici e/o condizionali. Per fare ciò ricorre all’uso di frame di interazione: cornici che includono uno o più frammenti di diagramma. Ogni frame ha un operatore e ogni frammento può avere una guardia. Si noti come i partecipanti siano fuori dal frame: i partecipanti coinvolti sono sempre gli stessi, non dipendono dal ciclo in cui si trovano. Questa notazione non è esattamente corretta dal momento che, sebbene l’Ordine e il Cliente con cui si lavora siano gli stessi per tutto il ciclo, la LineaOrdine e il Prodotto sono variabili. Chiamate Sincrone e Asincrone UML distingue i messaggi sincroni dai messaggi asincroni dal tipo di freccia utilizzata. o Una freccia con punta a forma di triangolo pieno indica un messaggio sincrono (→). o Una freccia la cui punta è costituita da due linee sottili denota un messaggio asincrono (→). Un oggetto che invia un messaggio sincrono deve attendere la risposta prima di poter proseguire. Le chiamate asincrone sono comuni nelle applicazioni multi-thread o nelle applicazioni distribuite. In versioni precedenti di UML, si usa ⇀ (mezza freccia) per indicare i messaggi asincroni e → per i messaggi sincroni. 4. USE CASE DIAGRAM Uno scenario è una sequenza di passi che caratterizzano una particolare interazione tra utente (detti attori) e sistema. Un attore rappresenta un ruolo interpretato da un’entità esterna (essere umano, altro sistema) nei confronti del sistema. Un caso d’uso è un insieme di scenari che hanno in comune lo scopo finale dell’utente. Uno scenario è dunque una possibile esecuzione di un caso d’uso, un’istanza del caso d’uso. Un singolo attore può partecipare a più casi d’uso e un singolo caso d’uso può coinvolgere più attori. Come rappresentare un caso d’uso Si individua lo scenario principale di successo (main scenario), cioè una sequenza di passi che porta al raggiungimento dell’obiettivo dell’utente. Questa sequenza è detta corpo del caso d’uso. Si considerano poi gli scenari che si verificano solo al verificarsi di una precisa condizione e sono dunque svolti da una sequenza alternativa di passi. Sono detti estensioni del main scenario; Ogni use case ha un attore principale; potrebbero comunque essere coinvolti altri attori, detti secondari; Un passo di un caso d’uso è un’interazione tra un attore ed il sistema. È descritto da una frase semplice e non dovrebbe esprimere dettagli tecnici sulle azioni compiute; Estensione Per indicare un’estensione si riporta la condizione che determina il verificarsi di interazioni diverse dallo scenario principale e si indica il passo in cui si può verificare la condizione. Si aggiungono passi numerati che dettagliano le interazioni dell’estensione. Se necessario, si indica il punto di rientro nello scenario principale. Inclusione Se un passo di un caso d’uso risulta complicato è possibile esprimerlo come un altro caso d’uso completo. Il primo caso d’uso include il secondo. Per esprimere l’inclusione nella forma testuale non c’è una notazione standard, di solito si sottolinea il nome del caso d’uso incluso (come un collegamento ipertestuale). Le relazioni di estensione e inclusione sono relazioni tra scenari. Contenuto di un caso d’uso Oltre ai passi che compongono gli scenari, un caso d’uso può comprendere: Una pre-condizione che descrive ciò che il sistema dovrebbe assicurarsi prima che il caso d’uso possa aver inizio. Una garanzia che descrive ciò che il sistema assicura alla fine dello svolgimento del caso d’uso. Uno scenario di successo garantisce il raggiungimento dello scopo del caso d’uso; gli altri scenari possono garantire risultati minori. Un trigger che specifica l’evento che dà origine al caso d’uso. Diagrammi di use case Un diagramma di caso d’uso è una sorta di sommario grafico: illustra i confini del sistema e le sue interazioni con il mondo esterno. Raffigura gli attori, i casi d’uso e le loro relazioni. Gli attori sono rappresentati come degli omini stilizzati. Sono collegati da una linea continua ai casi d’uso cui partecipano. I casi d’uso sono disegnati come delle ellissi ciascuna riportante al proprio interno il nome del rispettivo caso d’uso. A differenza del concetto di caso d’uso, dunque, i diagrammi use case rappresentano i singoli scenari al cui interno sono definiti i passi, che vengono detti casi d’uso. Dipendenza La relazione di inclusione è rappresentata da una linea tratteggiata che termina con una freccia (relazione di dipendenza) etichettata con la parola chiave. Generalizzazione La relazione di generalizzazione tra casi d’uso indica che il caso d’uso figlio, pur essendo simile al padre, ne specializza alcuni aspetti. Il figlio eredita il comportamento del padre e lo può estendere modificando e/o aggiungendo passi elementari. Questa relazione può sussitere anche tra gli attori. La generalizzazione tra attori indica che il ruolo corrispondente all’attore figlio è più specifico di quello dell’attore padre. Il ruolo corrispondente all’attore figlio è compatibile con quello corrispondente al padre (chi ricopre il ruolo del figlio può ricoprire anche il ruolo del padre). Il viceversa non vale. Estensione La relazione di estensione tra casi d’uso si indica con una relazione di dipendenza etichettata dalla parola chiave. La direzione della freccia va dal caso d’uso che rappresenta l’estensione verso quello principale. I punti di estensione sono indicati all’interno del caso d’uso principale. Finalità degli use case Servono per identificare le funzionalità svolte dal sistema software. Tali funzionalità dovranno poi essere assegnate agli oggetti/classi (assegnazione di responsabilità). Possono aiutare ad identificare oggetti, definire i casi di test da effettuare e caratterizzare la dinamica di interazione col sistema. Livelli dei casi d’uso I casi d’uso (di sistema) si focalizzano sull’interazione tra utente e sistema e possono portare a trascurare aspetti importanti che riguardano il processo di business. È utile considerare dei casi d’uso di business che analizzano la risposta del sistema alle richieste di un utente. I casi d’uso possono essere suddivisi in tre livelli (Cockburn), ognuno dei quali rappresenta un diverso livello di astrazione: o Sea-level (livello del mare): rappresentano un’interazione circoscritta tra un attore principale ed il sistema. Si concludono con il conseguimento dello scopo dell’attore; o Fish-level (livello dei pesci): includono i casi d’uso che esistono solo perché inclusi in un caso sea-level; o Kite-level (livello di un aquilone, a volo d’uccello): mostrano il ruolo dei casi d’uso sea-level all’interno di interazioni di business più ampie. I primi due livelli corrispondono a casi di sistema, il terzo a casi di business. 5. ACTIVITY DIAGRAM I diagrammi di attività servono a descrivere: logica procedurale, processi di business e workflow. Non cambiano dinamicamente. Usati ad esempio per descrivere un algoritmo. Sono simili ai diagrammi di flusso (flowchart) ma supportano la rappresentazione di elaborazione parallela (l’ordine di esecuzione non ha cioè importanza). L’esecuzione comincia in corrispondenza del nodo iniziale cui segue l’azione di ricezione dell’ordine. Quindi si incontra un fork. I fork hanno un flusso in ingresso ed un certo numero di flussi di uscita eseguiti in modo concorrente. La sincronizzazione di flussi concorrenti è rappresentata con il costrutto join. (Join → più flussi in ingresso; Fork → un solo flusso in ingresso); Il comportamento condizionale è rappresentato dai costrutti decision (un solo ingresso, più uscite) e merge (più ingressi, una sola uscita). I flussi uscenti da un punto di decisione sono etichettati da guardie. Uno solo di essi verrà eseguito. Scomposizione Le azioni possono essere divise in sottoattività specificate in un altro diagramma. Per indicare una sotto attività si utilizza l’icona a forma di rastrello. Un’azione può essere implementata da un metodo. In tal caso si usa la sintassi nomeClass::nomeMetodo. Partizioni Si può partizionare il diagramma per zone di competenza. Segnali I segnali temporizzati consentono di definire una activity sulla base di eventi esterni. Accept signal →si accetta un segnale da un evento esterno Send signal → si genera un segnale dal mondo esterno Flussi ed Archi UML 2 usa i termini flusso ed arco come sinonimi per indicare una connessione tra due azioni. Per semplificare diagrammi complessi si possono utilizzare coppie di connettori con la stessa etichetta. I flussi possono generare oggetti contenti dati rappresentati come box di classe o mediante pin. Pin e Trasformazioni Regione di Espansione Le azioni contenute all’interno della stessa ragione sono svolte in parallelo per un certo numero di volte non specificato. Più compatto rispetto all’utilizzo di una fork con n uscite. Join Una specifica di join è un’espressione booleana associata ad un join. Ogni volta che un token arriva al join l’espressione viene valutata e si emette un token in uscita solo se essa risulta vera. Nella figura la macchina valuta la specifica di join quando si inserisce una moneta o si sceglie una bevanda. La bevanda verrà distribuita solo se si inserisce un numero congruo di monete. Le etichette A e B sono utilizzate come variabili logiche per specificare che il join deve ricevere token da ogni flusso di ingresso. 6. PACKAGE DIAGRAM Un package è un costrutto che permette di raggruppare un insieme di elementi UML in unità di livello più alto. In genere si utilizzano per raggruppare classi. Ogni classe fa parte di un solo package. Un package può essere membro di un altro package. Ad alto livello corrispondono agli omonimi concetti in Java. Ogni package introduce uno spazio di nomi (namespace): classi differenti membri dello stesso package devono avere nomi distinti. I package sono rappresentati come dei box dotati di linguetta (tab) in alto a sinistra. Il nome è riportato all’interno del box o sul tab se si illustrano i contenuti interni. Nei casi in cui esistono classi con lo stesso nome in package differenti per risolvere l’ambiguità si utilizzano i nomi completamente qualificati che includono i nomi dei package separati da una coppia di due punti (::) (Ad esempio java::util::Date). Si può cioè utilizzare una struttura gerarchica (si può infatti anche utilizzare una rappresentazione ad albero). Le classi contenute in un package UML possono essere pubbliche o private. Le classi pubbliche costituiscono l’interfaccia del package in quanto possono essere utilizzate all’esterno di esso. Per ridurre l’interfaccia di un package si può esportare solo un piccolo sottoinsieme delle operazioni delle classi (pattern Faҫade): o si dichiarano private tutte le classi; o si introducono solo poche classi pubbliche che rendono visibile l’interfaccia desiderata. Principi di suddivisione di classi in package Common Closure: classi dello stesso package dovrebbero condividere le cause di un eventuale cambiamento; Common Reuse: classi dello stesso package dovrebbero essere riusate insieme; Dipendenze Un diagramma dei package documenta i package e le dipendenze tra di essi. Le dipendenze tra package riassumono quelle tra gli elementi contenuti. Un diagramma generale dei package è utile a tenere sotto controllo la complessità strutturale del codice. Valgono due principi: Dependencies principle → Man mano che le dipendenze entranti in un package aumentano, la sua interfaccia deve essere sempre più stabile; Stable abstraction principle → I package più stabili tendono a contenere una percentuale maggiore di classi astratte e interfacce; Nell’esempio le classi di presentazione dipendono da quelle del dominio. Vi sono due tipi di dipendenze specifiche: indica un’importazione di tipo public: gli elementi importati sono aggiunti al namespace e resi visibili anche all’esterno di esso; indica un’importazione di tipo private: gli elementi importati sono aggiunti al namespace ma non sono visibili dall’esterno. Entrambe le parole chiavi sono utilizzate per indicare che un package include nel proprio namespace i nomi definiti nell’altro package. 7. COMMUNICATION DIAGRAM I diagrammi di comunicazione (precedentemente detti diagrammi di collaborazione) sono un tipo speciale di diagramma di interazione che enfatizza lo scambio di dati tra i partecipanti. I partecipanti sono rappresentati come dei box con gli angoli arrotondati. Oltre a relazioni di associazione tra partecipanti e possibile includere anche collegamenti temporanei che esistono solo per la durata dell’interazione (in UML 1 i collegamenti temporanei erano etichettati dalla parola chiave ). I messaggi sono rappresentati con delle frecce etichettate con le azioni ad essi corrispondenti e numerate in modo nidificato per rispecchiare la nidificazione delle chiamate. Oltre ai numeri, nelle etichette dei messaggi possono essere utilizzate delle lettere per indicare che la reazione ad essi avviene in diversi thread di controllo, cioè in parallelo. (Ad esempio i messaggi 1a.1 ed 1b.1 apparterranno a due thread differenti in esecuzione parallela all’interno della gestione del messaggio 1. Per la creazione/distruzione di partecipanti si usano le parole chiave e. La numerazione nidificata indica chiaramente che il metodo getInfoSconti è invocato dall’interno di calcolaSconti. Il collegamento tra un Ordine e un Prodotto è temporaneo. Molto simili ai sequence diagram ma l’evoluzione è rappresentata tramite numeri e lettere piuttosto che con una lifetime. 8. DEPLOYMENT DIAGRAM Definisce come gli artefatti del sistema sono distribuiti sui nodi critici, le macchine. I diagrammi di deployment documentano la distribuzione fisica di un sistema illustrando l’allocazione dei componenti software sulle macchine. Gli elementi principali si chiamano nodi e sono collegati da path di comunicazione. Un nodo è qualsiasi cosa possa mandare in esecuzione del software: dispositivo (device): è sempre hardware (un computer o un componente più semplice); ambiente di esecuzione: è un pezzo di software che può contenere o eseguire altro software (es. un sistema operativo). I nodi contengono elaborati (artifact), manifestazioni fisiche del software: file eseguibili, script, etc. Gli elaborati si illustrano per mezzo dei box di classe (eventualmente specificando la parola chiave ) o elencandone i nomi all’interno dei nodi. E’ possibile associare valori di etichetta (tagged values) ai nodi o agli elaborati per fornire informazioni aggiuntive (marca, sistema operativo, posizione fisica, etc). 9. STRUTTURE COMPOSITE Ha la capacità di scomporre gerarchicamente una classe, mostrandone la struttura interna. La figura seguente mostra una classe TVViewer con le interfacce fornite e quelle richieste. Per rappresentare queste informazioni si può adottare la notazione socket- ball o una semplice lista all’interno del box della classe. La figura rappresenta una scomposizione interna della classe TVViewer in due parti indicando quale delle due supporta o e richiede ciascuna interfaccia. Il nome di ognuna delle due parti ha forma nome:classe (ciascuna delle due parti è opzionale, ma non possono mancare emtrambe). I connettori di delega sono utilizzati per specificare che una parte implementa o richiede un’interfaccia. Anche le parti sono collegate tra di loro da connettori. E’ possibile specificare la molteplicità di ciascuna parte. Si possono aggiungere delle porte alla struttura esterna per raggruppare le interfacce richieste e quelle fornite in base alle interazioni con il mondo esterno. I diagrammi di package rappresentano raggruppamenti a tempo di compilazione, le strutture composite fanno riferimento a quello che accade a tempo di esecuzione. 10. COMPONENT DIAGRAM Uno degli argomenti da sempre dibattuti nella comunità degli oggetti è la differenza tra un componente ed una classe. UML 1 prevedeva un simbolo speciale per i componenti. In UML 2 si utilizza un’icona all’interno di un box di classe oppure la parola chiave. Oltre a tale icona i componenti non introducono elementi nuovi. I collegamenti tra componenti sono basati sulle interfacce implementate e richieste ed utilizzano la stessa notazione socket-ball delle classi. Per rappresentare la composizione interna di un componente si può utilizzare un diagramma di struttura composita. Nella figura un agente di vendita si può collegare ad un componente server usando l’interfaccia dedicata ai messaggi di vendita. Dato che la rete è inaffidabile, è previsto un componente che implementa una coda di messaggi. L’agente parla direttamente con il server quando la rete funziona e con la coda in caso contrario. La coda contatta il server quando la rete torna disponibile. Vuol dire che la coda richiede ed offre contemporaneamente la stessa interfaccia. Dal punto di vista software, un componente può essere visto come una libreria o un’interfaccia; dal punto di vista hardware, come un elemento della macchina (es. batteria). Idealmente, si potrebbe dire che un componente non dovrebbe dipendere dalla tecnologia a cui è associato quanto piuttosto al suo utilizzo (def. Johnson). 11. STATE DIAGRAM Modella il comportamento dinamico di entità che rispondo agli eventi esterni in maniera diversa dipendentemente dalla loro storia passata (simile ad una rete logica sequenziale). Molto spesso il comportamento di un oggetto, ossia il modo con cui esso risponde ai messaggi che riceve (chiamate ai metodi), cambia dinamicamente. La risposta dell’oggetto dipende dal suo passato, dalla sua storia. In questi casi si parla di oggetti provvisti di stati di controllo (inteso come modalità di risposta dell’oggetto), per esprimere il fatto che a seconda del particolare stato di controllo posseduto sussiste una certa modalità di risposta ai messaggi. Lo stato di un oggetto modella la sua storia. È la conseguenza delle azioni eseguite in risposta ai messaggi ricevuti in precedenza. Si dice anche che un oggetto possiede al suo interno un automa a stati finiti, ovvero può ricordare solo un certo numero finito di input. La modellazione del comportamento dell’oggetto, dunque, si accompagna anche alla modellazione del suo automa (o macchina a stati finiti). Negli approcci object-oriented un automa a stati finiti è associato ad una classe e ne modella il comportamento degli oggetti che sono istanze di essa. Automa a stati finiti Un automa a stati finiti (FSM – Finite State Machine) è un grafo orientato in cui i nodi sono gli stati possibili e gli archi corrispondono alle possibili transizioni di stato. Uno stato particolare è lo stato iniziale, in cui l’oggetto-automa viene a trovarsi subito dopo la creazione. Lo stato iniziale è specificato mediante una pseudo-transizione che emana da un cerchietto nero (pseudo-stato). Uno o più stati possono fungere da stati finali o terminatori: l’automa, raggiunto uno di questi stati, non ammette più evoluzione. Una transizione di stato è causata dall’arrivo di un evento (o messaggio) detto trigger della transizione. Un automa risponde ad un evento mediante una transizione di stato e un’azione. L’azione è atomica o non interrompibile. Si parla di: automa di Moore quando l’azione è agganciata allo stato → quale che sia la transizione che porta ad uno stato, si ha sempre l’esecuzione dell’azione corrispondente allo stato; automa di Mealy se l’azione è associata all’arco-transizione → ogni possibile transizione può specificare una diversa azione; I formalismi di Moore e di Mealy sono equivalenti: è sempre possibile trasformare un FSM di Moore in uno di Mealy e viceversa, anche se il numero degli stati non è necessariamente lo stesso nelle due versioni. Esempio: Forno a microonde Si considera un oggetto-forno che risponde ai seguenti eventi (associati ad es. a pulsanti sul frontalino dell’apparecchiatura): apri (apre la porta) chiudi (chiude la porta) start (attiva il tubo a microonde) Il forno è composto, oltre che dal tubo, da una luce e da un timer. L’apertura della porta fa accendere la luce. La chiusura della porta spegne la luce. L’attivazione della cottura (evento start) può avvenire solo a porta chiusa, setta il timer ad 1 min e accende anche la luce. Allo scadere del timer, viene emesso un beep ed il tubo si spegne automaticamente. È possibile aumentare il tempo di cottura re- inviando (anche più volte prima dello spiro del timer) l’evento start che comporta ogni volta l’aggiunta di un ulteriore minuto al tempo di spiro. È possibile interrompere la cottura aprendo bruscamente la porta, in questo caso il timer è resettato ed il tubo disattivato. Nella figura è illustrato l’automa del forno, che rende formale la descrizione del forno. STATECHART I diagrammi di stato di UML si basano sugli statechart. Incorporano una serie di estensioni agli automi “piatti” per modellare situazioni complesse, tra cui: Stati gerarchici o annidati (superstati e sottostati) Transizioni di gruppo → ovvero una transazione che ha origine da uno stato gerarchico Connettori di storia Eventi con guardie Azioni di entrata (entry), uscita (exit), attività interne (do), e risposta ad eventi, in uno stato Transizioni di stato indifferentemente di Mealy o di Moore Transizioni spontanee Stato di uno Statechart Entrando nello stato vengono eseguite (se esistono) le entry action (atomiche) e fatta partire (se esiste) l’attività interna (cioè la do/activity). All’interno dello stato viene eseguita una do/activity la cui durata coinvolge l’intero stato. Uscendo dallo stato vengono eseguite (se esistono) le exit action e fatta terminare (se esiste ed è attiva) l’attività interna. Evento/azione definisce una transizione interna: se si verifica quell’evento, non si va in un altro stato ma si permane nello stesso. La differenza sostanziale con un un arco autoentrante sta nel fatto che, in questo caso, non si esce e poi si rientra nello stato ma si rimane all’interno, non ripetendo quindi l’entry action o la exit action atomica dello stato. Inoltre, in caso di arco autoentrante, la do/attività è interrotta, mentre in questo caso non viene disturbata. Eventi e Guardie Trigger di una transizione: evento(parametri) [guardia] / azione la condizione può mancare (true default) Invio di un evento: send target.evento(parametri) Le guardie sono racchiuse entro [ ] e si basano su dati dell’oggetto. Più in generale possono basarsi anche sui valori dei parametri di un evento ricevuto; Lo statechart della classe Conto mette in evidenza come alcuni eventi possano essere ricevuti e processati in uno stesso stato, senza cioè far cambiare stato; Ci potrebbe essere ambiguità nel caso in cui: o fossero soddisfatte le condizioni per lo svolgimento di più transizioni (es. interna ed esterna) → si definisce un ordine di priorità; o fossero soddisfatte le condizioni per lo svolgimento di più transizioni esterne → si cerca di definire delle condizioni che siano sempre mutuamente esclusive tra di loro; Esempio 2. Controller di un passaggio a livello (slide) Esempio Forno: Statechart FornoTop è un super (o macro) stato in quanto ammette decomposizione. Il più grande macrostato è detto radice. Disattivo e Attivo sono sottostati di FornoTop ma sono a loro volta macro stati. Non ci si può trovare contemporaneamente in più di un sottostato (or-decomposition, più corretto dire xor-decomposition). PortaChiusa e PortaAperta sono stati foglia di Disattivo. La transizione apri che parte dal bordo di Attivo e rientra in Disattivo è una transizione di gruppo: quale che sia lo stato interno di Attivo, l’arrivo di apri comporta sempre il raggiungimento dello stato PortaAperta di Disattivo con le azioni conseguenti. Ovvero è svolta per tutti i sottostati di uno stesso genitore. La transizione start che parte da PortaChiusa di Disattivo e finisce sul bordo di Attivo indica che viene installato lo stato di default (o iniziale) interno ad Attivo (definisce lo pseudo-stato iniziale: il pallino pieno con la freccia). Lo stub state ss indica l’esistenza di uno stato interno in Attivo, non ancora specificato, da cui fuoriesce una transizione che conduce sul bordo di Disattivo (e dunque in PortaChiusa). Il trigger di questa transizione sarà specificato nel raffinamento di Attivo. Gli stub state (barrette) denotano equivalentemente spezzoni di transizioni la cui esatta composizione e materia di sviluppo incrementale dello statechart. Il macrostato Attivo apri, start e timeout sono esempi di transizioni di gruppo per il sottostato Cottura. Quando il controllo è in Cottura, apri sposta sempre in CotturaInterrotta altrimenti conduce in PortaAperta di Disattivo. Di seguito un’aggiunta dello stato Ispezione. È uno stato foglia di FornoTop e si raggiunge quando FornoTop è nello stato Attivo.Cottura e l’utente invia l’evento check. Si nota che check è ridefinito in FineCottura e CotturaInterrotta in modo da essere trascurato (skip). Ripremendo check a partire dallo stato Ispezione, si riassume in Attivo lo stesso (sub)stato ed al suo interno il suo (sub)stato etc ricorsivamente sino ad uno stato foglia, in cui Attivo si trovava al tempo di check. Poiché l’ispezione potrebbe avvenire quando il timeout è imminente e la persona, proprio a seguito dell’ispezione, potrebbe decidere di aprire bruscamente il forno, dallo stato Ispezione sono state considerate esplicitamente le transizioni conseguenti a timeout e ad apri. In previsione della transizione legata al connettore di storia, non sono state introdotte entry action negli stati InizioCottura e CotturaEstesa. Piuttosto, le azioni di gestione della luce, del tubo e del timer sono state “agganciate” all’arco della transizione di default per InizioCottura e all’evento start per quanto riguarda CotturaEstesa. Tutto ciò per impedire che rientrando per storia in uno stato foglia di Cottura venga rieseguita (erroneamente) la corrispondente entry action. NOTA: nel momento in cui da un sottostato si va in (o si esce da) un altro sottostato appartenente ad un genitore diverso tale che esso possiede delle entry (o exit) action, esse dovranno essere eseguite. Inoltre, il passaggio deve essere effettuato risalendo la gerarchia fino al genitore comune di livello più basso (regola dell’antenato comune di livello più basso). Ad esempio, per passare da FornoTop.Attivo.Cottura a FornoTop.Disattivo.PortaAperta: noto che il genitore comune di livello più basso è FornoTop, dunque dovrò fare Cottura → Attivo → Disattivo → PortaAperta (eseguendo le eventuali entry/exit action). ECCEZIONE: quando lo stato da cui si parte è origine o destinazione dell’arco che definisce la transizione. Ad esempio: da FornoTop.Disattivo.PortaChiusa a FornoTop.Disattivo.PortaAperta (nell’ipotesi in cui non ci sia la transizione apri), bisognerà uscire e rientrare, ovvero non si farà riferimento al genitore di livello più basso (Disattivo) ma a quello subito superiore (FornoTop) e si dovranno dunque eseguire le entry/exit action di Disattivo. Connettori di Storia H* e H Il connettore H indica storia superficiale → Ogni stato ha memoria di chi era il figlio corrente quando si è usciti da lui per l’ultima volta. Se non ci si è mai entrati, la storia è impostata sullo stato di default; se si volesse specificare altrimenti, bisognerebbe associare al connettore una freccia verso lo stato che si vuole rendere storia passata. Il connettore H* indica storia profonda → riprende l’esecuzione non solo dall’ultimo stato ma anche nell’ultima configurazione. Per configurazione si intende, ad esempio, FornoTop.Attivo.Cottura invece che solo Attivo H*, come si è detto, fa rientrare nello stato foglia corrente esistente al tempo di ricezione di check. L’uso di H, se lo stato corrente era Cottura, avrebbe comportato il ripristino solo del livello di Cottura (e implicitamente del suo stato di default). Esempio 3: Statechart di una telefonata La decomposizione dello stato Attivo di Telefono mostra in che modo si possono esprimere processi decisionali in uno statechart di UML. Il rombo (o cerchietto) è il punto decisionale o di scelta. Le condizioni testate, che selezionano un cammino di transizione di stato, si basano su dati e/o parametri di evento, e sono racchiuse entro [ ]. Un’alternativa utile nei casi pratici è [else] che consente la scelta un ramo di transizione quando nessuno degli altri ha la condizione true. L’esempio lascia intendere che esistono eventi (non mostrati) la cui ricezione modifica il valore di alcuni attributi dell’oggetto telefono su cui si fondano le condizioni. L’espressione “when situazione” esprime un evento di cambiamento, legato al raggiungimento di una situazione particolare di dati nell’oggetto. Nell’esempio, per semplicità, si considera un numero terminato o quando è stata digitata l’ultima cifra di un numero corretto o quando si è digitata una cifra di un numero scorretto, nel qualcaso si suppone che la linea diventi occupata. Si è ammesso tacitamente che lo stato AttendiRisposta disponga di una attività interna relativa all’attesa di un tempo massimo affinché l’interlocutore risponda. Un’attivita si introduce, in uno stato, con la sintassi: do: attività. Un’attività, diversamente da un’azione, non è atomica. L’arrivo di un evento capace di innescare una transizione ad un differente stato interrompe definitivamente l’esecuzione dell’attivit?

Use Quizgecko on...
Browser
Browser