Comprendere le attività del System Design
Strutturazione in sottosistemi
Rispettando gli obiettivi del sistema
Linee guida per la definizione di artefatti
Concetti di Coesione ed Accoppiamento tra artefatti
La progettazione di un sistema software (o System Design) è l’insieme dei task svolti dall’ingegnere del software per trasformare il modello di analisi nel modello di design del sistema.
Obiettivo ultimo è definire un nuovo documento, scritto in linguaggio tecnico/formale, da dare ai programmatori, per guidarli nell’implementazione del software descritto nel Documento dei Requisiti Software.
Scopi del System Design:
L’output del System Design è un documento contenente:
Cambiamo radicalmente punto di vista.
Finora abbiamo lavorato per interagire col cliente.
Linguaggio poco formale, adatto a chiarire i requisiti.
Nel system design i nostri interlocutori sono le squadre di programmatori che dovranno implementare il sistema.
Chi leggerà i nostri documenti sarà un esperto di informatica → Linguaggio per esperti tecnici.
Cambiamento di obiettivo.
Da “Are we doing the right thing” a “Are we doing things right”?
L’Analisi si focalizza sul dominio di applicazione:
Design: si focalizza sul dominio di implementazione:
Il System Design è costituito da tre macro-attività:
E’ il primo passo del system design.
Identifica le qualità su cui deve essere focalizzato il sistema.
Molti design goal possono essere ricavati dai requisiti non funzionali o dal dominio di applicazione, altri sono forniti dal cliente.
E’ importante formalizzarli esplicitamente poiché ogni importante decisione di design deve essere fatta seguendo lo stesso insieme di criteri.
Possiamo selezionare gli obiettivi di design da una lunga lista di qualità desiderabili.
I criteri sono organizzati in cinque gruppi:
Includono i requisiti imposti sul sistema in termini di spazio e velocità
Quanto sforzo deve essere speso per minimizzare i crash del sistema e le loro conseguenze?
Rispondono alle seguenti domande:
Other Placeholder: Includono i costi per sviluppare il sistema, per metterlo in funzione e per amministrarlo.
Quando il sistema sostituisce un sistema vecchio, è necessario considerare il costo per assicurare la compatibilità con il vecchio o per transitare verso il nuovo sistema.
I criteri di costo:
Determinano quanto deve essere difficile modificare il sistema dopo il suo rilascio
Includono qualità che sono desiderabili dal punto di vista dell’utente, ma che non sono state coperte dai criteri di performance e affidabilità.
Criteri:
Quando definiamo gli obiettivi di design, spesso solo un piccolo sottoinsieme di questi criteri può essere tenuto in considerazione.
Es: non è realistico sviluppare software che sia simultaneamente sicuro e costi poco.
Gli sviluppatori devono dare delle priorità agli obiettivi di design, tenendo anche conto di aspetti manageriali, quali il rispetto dello schedule, del budget, etc…
Esempi:
Spazio vs. velocità.
Se il software non rispetta i requisiti di tempo di risposta e di throughput, è necessario utilizzare più memoria per velocizzare il sistema (es. Caching, più ridondanza). Se il software non rispetta i requisiti di memoria, può essere compresso a discapito della velocità.
Tempo di rilascio vs. funzionalità.
Se i tempi di rilascio sono stringenti, possono essere rilasciate meno funzionalità di quelle richieste, ma nei tempi giusti.
Tempo di rilascio vs. qualità.
Se i tempi di rilascio sono stretti, il project manager può rilasciare il software nei tempi prefissati con dei bug e, in tempi successivi, correggerli, o rilasciare il software in ritardo, ma con meno bug.
Quando progettiamo una classe dovremmo sforzarci di pensare a quali cambiamenti potranno esserle richiesti in futuro, e quali scelte possiamo fare oggi per facilitarne la modifica in futuro.
“Progettare per ANTICIPARE IL CAMBIAMENTO”
Le scelte fatte in questa fase avranno un profondo impatto sul successo complessivo del software che si sta sviluppando.
Poiché la manutenzione di un software è normalmente la fase più lunga (e costosa per un’azienda), sarà fondamentale fare qualunque scelta in fase di design che faciliti la successiva manutenzione del codice.
Non è pensabile progettare un sistema software come un unico modulo.
L’obiettivo della fase più importante del System Design è definire la decomposizione del sistema in termini di sottosistemi, cioè:
Il tutto mirando a rispettare i criteri di design imposti dal dominio applicativo
Come modelliamo i sottosistemi?
UML: Package, cioè Collezioni di classi, associazioni, operazioni e vincoli che sono correlati.
JAVA: fornisce i Package che sono costrutti per modellare i sottosistemi.
C++ / C# hanno il costrutto di Namespace per indicare lo stesso concetto.
Esistono dei criteri di Design generici, che valgono per qualunque software O-O, di fondamentale importanza nella definizione dei sottosistemi.
Questi criteri, se ben applicati, migliorano notevolmente la qualità interna del codice, senza introdurre particolari svantaggi.
La qualità del Design dipende da due fattori qualitativi fondamentali:
L’obiettivo è l’indipendenza funzionale dei sottosistemi.
L’accoppiamento è una misura su quanto fortemente una classe è connessa con /ha conoscenza di/ si basa su altre classi.
Per un codice di qualità dobbiamo puntare ad un basso accoppiamento (low coupling).
Un basso accoppiamento è una proprietà desiderabile del codice, poiché permette di:
Un basso accoppiamento migliora la manutenibilità del software.
Un certo livello di accoppiamento è comunque insito nel concetto di scambio di messaggio del paradigma O-O.
Linea Guida: definire le responsabilità delle classi in modo da ottenere un basso accoppiamento.
Cosa si intende per coupling in un sistema Object-Oriented?
Date 2 classi X e Y:
Lo scenario 3 è quello che porta al massimo accoppiamento in assoluto.
La Coesione è una misura di quanto siano fortemente relate e mirate le responsabilità (o servizi offerti) di una classe.
Se ciascuna unità è responsabile di un singolo compito, diciamo che tale unità ha una alta coesione.
Un’alta coesione è una proprietà desiderabile del codice, poiché permette:
Coesione dei metodi
Un metodo dovrebbe essere responsabile di un solo compito ben definito
Coesione delle classi
Ogni classe dovrebbe rappresentare un singolo concetto ben definito
Livelli di Coesione:
Indicatori di un’alta coesione (è il nostro obiettivo):
Decomposizione in moduli
Non riesco a specificare chiaramente le responsabilità di ogni modulo (Gestisce, Salva? Quindi bassa coesione).
Tutti i sottosistemi accedono al database direttamente, rendendoli vulnerabili ai cambiamenti dell’interfaccia del sottosistema Database.
In futuro, se cambiasse il DBMS, dovrei modificare il codice in tutte le classi che accedono al DB. (Alto accoppiamento tra classi e DB, da evitare!)
Chiariamo le responsabilità
Introduco il sottosistema Storage che gestisce l’I/O col DB (disaccoppia tutte le classi dal db).
Conseguenze di questa scelta:
Il progettista deve scegliere tra queste due soluzioni quale rispetta maggiormente i Criteri di Design per il sistema da sviluppare.
“Capire le responsabilità è fondamentale per una buona programmazione ad oggetti”
Martin Fowler
Pensare alla progettazione di un sistema in termini di:
Ciascuna classe dovrebbe avere delle responsabilità e dei ruoli ben precisi → Alta Coesione
La classe che possiede i dati dovrebbe essere responsabile di processarli → Basso Accoppiamento
Ciascuna classe dovrebbe essere responsabile di gestire i propri dati → Incapsulamento
E’ facilitata dall’uso di CRC Cards.
Introdotte Kent Beck e Ward Cunningham per insegnare progettazione O-O
Una card CRC è una scheda da associare ad ogni classe, per rappresentare i seguenti concetti:
Responsabilità: conoscenza che la classe mantiene o servizi che la classe fornisce.
Collaboratori: altre classi di cui è necessario sfruttare la conoscenza o i servizi, per ottemperare alle responsabilità sopra indicate.
Ne va specificata una per ogni classe del class diagram di design.
Conseguenza dell’utilizzo:
E’ una linea guida chiave per avere Basso Accoppiamento, utilizzata nel progetto Demetra, uno dei primi grossi sistemi O-O, sviluppato all’univ. di Boston.
Forza l’information hiding, evitando situazioni tipo Foo.getA().getB().getC()…..
Più è lungo il percorso di chiamate attraversato dal programma, più esso è fragile a cambiamenti.
Traduzione formale della legge di Demetra.
Un metodo deve mandare messaggi solo a:
1. Introduzione all'Ingegneria del Software – La qualità del Software
2. Il Ciclo di vita del Software: i modelli di Processo
3. Concetti e strumenti di base per il Project Management
5. Introduzione ad UML: Gli Use Case Diagrams
6. Il documento dei Requisiti Software
7. UML: I Sequence Diagrams ed i Class Diagrams