Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
SLIDES 1: INTRODUZIONE
Ciclo di vita del software:
- Raccolta dei requisiti
- Analisi/specifica dei requisiti
- System Design
- Object Design
- Implementazione
- Testing
Il software engineering si occupa di problem solving. Precisamente, risoluzione dei problemi di un cliente
mediante i mezzi della computer science.
Un software ha costi elevati. Il costo è espresso in manpower (mesi/uomo).
La manutenzione costa più dello sviluppo del software in se: in un ciclo di vita di un software, il 75% è
dedicato alla manutenzione, il 25% restante allo sviluppo (il cui tempo è diviso 50% per analisi,
progettazione e coding, l’altro 50% in testing).
La qualità del software può essere misurata
(da federica unina..)
Metodologie di sviluppo: modelli processo
L’approccio Tayloristico è stato per decenni il fondamento dell’industria mondiale. Prevedeva:
- gestione gerarchica
- passi fissi, non fluidi
- separare nettamente le postazioni di lavoro
- il lavoro una volta suddiviso, diventa un singolo task
- processo orientato al prodotto e non al cliente
- processo ripetibile
Una prima applicazione di tale approccio fu il modello a cascata.
- progressione lineare di fasi, senza ricicli
- nessuna overlap tra fasi
- uscite intermedie
- consente un controllo del processo
contro: interazione con l’utente solo all’inizio e alla fine del processo. Il sistema diventa installabile solo a
fine processo.
pro: facilmente comprensibile ed applicabile.
Così si è pensato di introdurre prototipi usa e getta ai fini di avere una prima implementazione da
considerare come prova con lo scopo di verificare l’accettabilità del prodotto. Prototipi usa e getta: mockup,
breadboards.
Quindi passa ad un modello di prototipazione evolutiva, costituito da poche fasi che si ripetono:
- realizzazione di un artefatto
- consegna al cliente
- feedback
- modificare il progetto in base a tali feedback
Si passa al modello a spirale (applicazione del modello evolutivo al modello a cascata).
Ogni fase inizia con un obiettivo di design e termina con un’interazione con il cliente.
Ogni ciclo di spirale si compone di quattro fasi. Il raggio rappresenta il costo accumulato e la dimensione
angolare rappresenta il progresso nel processo.
Obiettivo dello UC: ciò che il sistema dovrebbe fare secondo gli utenti.
Passi per la costruzione di uno Use Case Diagram:
- identificazione degli attori
- identificazione dei casi d’uso
- definizione delle associazioni tra attori e casi d’uso
- descrizione dei casi d’uso
- strutturazione dei casi d’uso
Per ogni Use case è necessaria una descrizione testuale dettagliata, presente successivamente nel documento
dei requisiti software. L’obiettivo è di specificare in ogni aspetto l’interazione attore/i sistema. Una di queste
rappresentazioni è il template di Cockburn.
NB: LO USE CASE DIAGRAM è UN DIAGRAMMA SEMPLICE. UN UC COMPLESSO è INDICE DI
UN CATTIVO ANALISTA.
SLIDES 7: STATECHARTS
Diagramma utilizzato per descrivere il comportamento di un artefatto composto da un numero finito di stati.
E’ composto da stati rilevanti dell’entità e transizioni che determinano passaggi di stati.
Vantaggio principale: grande impatto visivo e grande leggibilità.
Le transizioni sono generalmente lanciate da eventi.
Vengono usati principalmente per descrivere il comportamento di:
- Use Cases
- Classi
Ogni oggetto ha interazioni, durante il suo ciclo di vita, con oggetti vicini. Deve quindi rispondere sempre a
stimoli asincroni. Esistono inoltre oggetti il cui comportamento è determinato dallo stato dei suoi attributi.
Per descrivere queste situazioni, viene utilizzato lo Statechart: è un diagramma chiave per guidare i
programmatori nell’implementazione di sistema.
Un evento è un avvenimento significativo per un artefatto.
Uno stato è una condizione di un oggetto compreso in un intervallo di tempo compreso tra due eventi.
Una transizione è una relazione che lega uno stato di partenza ad uno di arrivo.
Uno stato è rappresentato da un rettangolo arrotondato.
Le transizioni sono etichettate con tre elementi, tutte opzionali: Evento [condizione] / Azione.
Evento: trigger. Stimolo per innescare una transizione tra stati.
Condizione (guard): espressione booleana.
Azione: è associata alla transizione, è considerata un processo rapido, atomico.
Uno stato può opzionalmente avere le seguenti azioni:
- Entry: azione che viene eseguita ogni volta che si entra nello stato.
- Exit: azione che viene eseguita ogni volta che si esce dallo stato.
- Do: associato allo stato, non atomica.
Esistono transizioni senza trigger, chiamate appunto transizioni triggerless. Al termine dell’azione del primo
stato, si passa automaticamente al secondo stato.
Uno stato può contenere sottostati.
Uno stato può essere decomposto ortogonalmente (in sottostati mutualmente esclusivi), concorrentemente (in
sottostati concorrenti). Sono ammesse anche transizioni che entrano in uno stato interno.
Quando si entra in uno stato composito, la macchina a stati inizia dal suo stato iniziale. Esistono situazioni in
cui vorremmo poter memorizzare quale sottostato era attivo nel momento in cui si abbandona uno stato
composito, così da poter riprendere le attività da quel punto (una volta rientrati nello stato composito).
Si parla di History states.
Esistono due stati, H e H*.
Lo stato H si chiama history state superficiale. Nel caso di macchina a più livelli, H ricorda soltanto lo stato
del primo livello.
Lo stato H* è detto history state profondo, utile per ricordare lo stato di ogni livello.
Linee guida:
- il nome dello stato deve riflettere un intervallo di tempo reale.
- dev’essere possibili uscire da ogni stato.
- non confondere evento (causa) con azione (effetto).
- nomi non ambigui.
- condizioni sugli eventi hanno valore booleano.
- azioni e condizioni sono opzionali: da usare solo se necessari.
- check finale: abbiamo descritto tutti i possibili comportamenti del sistema?
ARCHITETTURE SOFTWARE
Un’architettura software è la struttura del sistema.
Definire un’architettura significa mappare funzionalità su moduli (es. modulo di interfaccia utente, modulo
di accesso a db, modulo di gestione della sicurezza etc…).
La coesione è una misura di quanto siano fortemente relate e mirate le responsabilità di un modulo.
Se ciascuna unità è responsabile di un singolo compito, parleremo di alta coesione.
Un’alta coesione è una proprietà desiderabile del codice poiché permette:
- di comprendere meglio i ruoli di una classe
- di riutilizzare una classe
- di manutenere una classe
- di limitare e focalizzare i cambiamenti del codice
- utilizzare nomi efficaci.
Coesione dei metodi: un metodo dovrebbe essere responsabile di un solo compito.
Coesione delle classi: ogni classe dovrebbe rappresentare un solo concetto ben definito.
Indicatori di alta coesione di una classe:
- ha delle responsabilità moderate, limitate ad una singola area funzionale.
- collabora con altre classe per completare dei tasks.
- ha un numero limitato di metodi, cioè di funzionalità altamente legate tra loro.
- tutti i metodi sembrano appartenere ad uno stesso insieme, con un obiettivo globale simile.
- la classe è facile da comprendere.
L’accoppiamento è una misura di quanto fortemente una classe è connessa con altre classi e di quanto si
basa su di esse.
Due o più classi si dicono accoppiate quando è impossibile modificare una senza dover modificare l’altra.
Se due classi dipendono strettamente l’una dall’altra, si parlerà di forte accoppiamento.
Per un codice di qualità dobbiamo puntare ad un basso accoppiamento.
Un basso accoppiamento è una proprietà desiderabile del codice poiché permette di:
- capire il codice di una classe senza dover leggere i dettagli delle altre.
- modificare una classe senza che le modifiche comportino conseguenze sulle altre classi.
- riutilizzare una classe senza dover importare necessariamente anche altre classi.
Un basso accoppiamento migliora la manutenibilità del software.
Linea guida: legge di Demetra.
Concetto base: spingere al massimo l’information hiding.
Un metodo M di un oggetto O dovrebbe soltanto mandare messaggi a:
- il proprio oggetto
- un parametro del metodo
- una variabile di istanza dell’oggetto contenente
- un oggetto creato all’interno del metodo
Come ottenere alta coesione e basso accoppiamento?
Basta pensare alla progettazione di un sistema in termini di classe, responsabilità e collaborazioni.
Ogni classe deve avere delle responsibility e dei ruoli ben precisi (alta coesione).
La classe che possiede i dati dev’essere responsabile di processarli (basso accoppiamento).
NB:
- se non riesco a definire chiaramente le responsabilità di una classe, c’è un errore di design.
- se una classe ha bisogno di troppe classi collaboratrici, c’è un errore di design.
- una classe è troppo complessa se rappresenta più di un concetto.
- un metodo è troppo complesso se è responsabile di più di un compito logico.
Le responsabilità delle attività vengono attribuite mediante uno strumento chiamato OBS (Organizational
Breakdown Structure).
Permette di identificare le competenze richieste nelle attività di progetto.
TIPOLOGIE DI TEAM:
Democratico decentralizzato:
- assenza di un leader permanente.
- consenso di gruppo sulle soluzioni e sulla organizzazione del lavoro.
- comunicazione orizzontale.
Vantaggi: attitudine positiva a ricercare presto gli errori.
funziona bene per problemi “difficili”.
Svantaggi: è difficile da imporre.
non è scalabile.
Controllato decentralizzato:
- un leader riconosciuto che coordina il lavoro
- la risoluzione dei problemi è di gruppo, ma l’implementazione delle soluzioni è assegnata a sottogruppi da
parte del leader.
- comunicazione orizzontale con i sottogruppi e verticale con i leader.
Controllato centralizzato:
- il team leader decide sulle soluzioni e sull’organizzazione.
- comunicazione verticale tra team leader e altri membri.
FORMALISMI PER IL PROJECT MANAGEMENT.
Grafo delle attività (PERT) e diagramma di Gantt (diverse rappresentazioni grafiche dello scheduling del
progetto).
PERT evidenzia le dipendenze e il cammino critico (percorso costituite da fasi di lavoro che possono essere
compiute soltanto con l’ultima azione della fase precedente. Consente di determinare la reale durata
dell’intera attività lavorativa.).
Gantt mostra il succedersi temporale delle fasi di un progetto.
Un grafico PERT è un grafo i cui nodi sono istanti di un processo (inizi di fasi) e gli archi rappresentano
fasi, stabilendo durata e relazioni di dipendenza temporale tra fasi.
C’è sempre un nodo iniziale “inizio del processo” e un nodo finale “fine del processo”. Gli altri nodi
formano una rete di attività che procedono in parallelo o in sequenza.
Cronogramma di Gantt è una rappresentazione su scala temporale dell’evoluzione del progetto.
Ogni barra rappresenta un’attività, la lunghezza di ognuna di esse è proporzionale alla durata dell’attività che
rappresenta e viene collocata sulla scala temporale in prossimità dell’attività stessa.
Limite: non sono evidenziati i legami tra le attività, né le risorse deputate al loro svolgimento.
Un diagramma di Gantt permette di rappresentare graficamente un calendario di attività.
Il diagramma è costruito da:
- un asse orizzontale (tempo).
- un asse verticale (mansioni).
Composite:
Obiettivo: trattare in maniera omogenea oggetti singoli e multipli, definiti in maniera ricorsiva.
Observer:
Obiettivo: definire una relazione di dipendenza uno-a-molti tra oggetti, in modo che quando un oggetto
cambia stato, tutti quelli che ne sono dipendenti vengono notificati e si aggiornano di conseguenza.
E’ possibile associare un nome ed un tipo ad una sottoespressione quando è utile usarla più volte.
es.
context Person inv:
let income : Integer = self.job.salary->sum() in
if isUnemployed then
income < 100
else
income >= 100
endif
I tipi in OCL sono organizzati in una gerarchia. Un’espressione OCL è valida se i tipi coinvolti sono
conformi.
- Un tipo type1 è conforme ad un tipo type2 quando un’istanza di type1 può essere sostituita al posto di
un’occorrenza di type2.
- Ogni tipo è conforme al suo supertipo.
- La conformità è transitiva.
- l’operazione di casting ad un sottotipo può servire ad accedere alla proprietà di un’istanza quando sia certo
che essa è associata a un sottotipo type2 del tipo corrente type1.
- l’oggetto corrente può essere ri-tipato con l’operazione oclAsType(OclType)
- avendo un oggetto di tipo type1 ed un suo sottotipo type2 si può scrivere object.oclAsType(type2)
La valutazione di un’espressione può portare a valori indefiniti.
In generale:
- True OR Undef valuta True
- False AND Undef valuta False
- False IMPLIES Undef valuta True
- Undef IMPLIES True valuta True
Esiste un’operazione per testare l’indefinitezza di un’espressione: ocllsUndefined().
Oggetti e proprietà:
- se l'associazione ha cardinalità m .. * (molti)
- object.role1 è una espressione di tipo Set (Class1)
- ha come valore una collezione di oggetti di tipo Class1
- se l'associazione ha cardinalità m .. * (molti) ed è qualificata come {ordered}
- object.role1 è una espressione di tipo Sequence(Class1)
- ha come valore una collezione ordinata di oggetti di tipo Class1
Es.
Invariante (il manager di una compagnia non è disoccupato; la compagnia non ha insieme vuoto di
dipendenti)
Alla proprietà di una collezione si accede mediante ->
context Company
inv: self.manager.isUnemployed = false
inv: self.employee->notEmpty()
In caso di ruolo con cardinalità 0..1 o 1 è possibile considerare il valore
dell'espressione object.role come un insieme singoletto (contenente un unico
oggetto) anziché un oggetto.