Vai alla Home Page About me Courseware Federica Living Library Federica Federica Podstudio Virtual Campus 3D La Corte in Rete
 
Il Corso Le lezioni del Corso La Cattedra
 
Materiali di approfondimento Risorse Web Il Podcast di questa lezione

Sergio Di Martino » 9.La Progettazione Software


Obiettivi della lezione

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

Software Lifecycle Activities


Progettazione di un Sistema Software

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:

  • Definire gli Obiettivi di Design del progetto;
  • Decomporre il sistema in sottosistemi più piccoli che possono essere realizzati da team individuali, eventualmente in parallelo;
  • Selezionare le strategie per costruire il sistema, quali:
    • Tecnologie HW/SW;
    • Gestione dei dati persistenti;
    • Flusso di controllo globale;
    • Politiche di controllo degli accessi;

L’output del System Design è un documento contenente:

  • Una chiara descrizione di ognuna delle strategie elencate sopra;
  • Un insieme di modelli statici/dinamici del sistema che includano la decomposizione del sistema in sottosistemi, formalizzati con Class Diagram, Sequence Diagrams, StateChart, etc… di UML.

System Design

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:

  • “Stiamo facendo la cosa giusta?” (abbiamo compreso bene cosa vuole il cliente?)

Design: si focalizza sul dominio di implementazione:

  • “Stiamo facendo la cosa bene?”  (stiamo facendo le migliori scelte tecnologiche?)
  • Dobbiamo raggiungere dei compromessi fra i vari obiettivi di design che sono spesso in conflitto gli uni con gli altri.
  • Dobbiamo anticipare molte decisioni relative alla progettazione pur non avendo una chiara idea del dominio della soluzione.

Attività del system design

Il System Design è costituito da tre macro-attività:

  1. Identificare gli obiettivi di design: gli analisti identificano e definiscono le priorità delle qualità del sistema
  2. Progettare la decomposizione del sistema in sottosistemi. Utilizziamo linee guida e stili architetturali standard.
  3. Raffinare la decomposizione in sottosistemi per rispettare gli obiettivi di design: la decomposizione iniziale di solito non soddisfa gli obiettivi di design. Gli sviluppatori la raffinano in maniera iterativa finché gli obiettivi non sono soddisfatti.

Identificare gli obiettivi qualitativi del sistema

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:

  1. Performance
  2. Affidabilità
  3. Costi
  4. Mantenimento
  5. Criteri End user

Criteri di Performance

Includono i requisiti imposti sul sistema in termini di spazio e velocità

  • Tempo di risposta: con quali tempi una richiesta di un utente deve essere soddisfatta dopo che la richiesta è stata immessa?
  • Throughput: quanti task il sistema deve portare a compimento in un periodo di tempo prefissato?
  • Memoria: quanto spazio è richiesto al sistema per funzionare?

Criteri di affidabilità

Quanto sforzo deve essere speso per minimizzare i crash del sistema e le loro conseguenze?
Rispondono alle seguenti domande:

  • Robustezza. Capacità di sopravvivere ad input non validi immessi dall’utente
  • Attendibilità. Differenza fra comportamento specificato e osservato
  • Disponibilità. Percentuale di tempo in cui il sistema può essere utilizzato per compiere normali attività
  • Tolleranza ai fault. Capacità di operare sotto condizioni di errore
  • Sicurezza. Capacità di resistere ad attacchi di malintenzionati
  • Fidatezza. Capacità di evitare di danneggiare vite umane, anche in presenza di errori e di fallimenti.

Criteri di costi

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:

  • Costo di sviluppo del sistema iniziale.
  • Costo relativo all’installazione del sistema e training degli utenti.
  • Costo per convertire i dati del sistema precedente. Questo criterio viene applicato quando nei requisiti è richiesta la compatibilità con il sistema precedente (backward compatibility).
  • Costo di manutenzione. Costo richiesto per correggere errori sw o hw.
  • Costo di amministrazione. Costo richiesto per amministrare il sistema.

Criteri di mantenimento

Determinano quanto deve essere difficile modificare il sistema dopo il suo rilascio

  • Estendibilità. Quanto è facile aggiungere funzionalità o nuove classi al sistema?
  • Modificabilità. Quanto facilmente devono essere cambiate le funzionalità del sistema?
  • Adattabilità. Quanto facilmente può essere portato il sistema su differenti domini di applicazione?
  • Portabilità. Quanto è facile portare il sistema su differenti piattaforme?
  • Leggibilità. Quanto facile deve essere capire il sistema dalla lettura del codice?
  • Tracciabilità dei requisiti. Quanto è facile mappare il codice nei requisiti specifici?

Criteri di End User

Includono qualità che sono desiderabili dal punto di vista dell’utente, ma che non sono state coperte dai criteri di performance e affidabilità.
Criteri:

  • Utilità: quanto bene dovrà il sistema supportare il lavoro dell’utente?
  • Usabilità: quanto dovrà essere facile per l’utente l’utilizzo del sistema?

Design Trade-offs

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.

Pensare in avanti

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.

Decomposizione del sistema

Non è pensabile progettare un sistema software come un unico modulo.

  • Per ridurre la complessità della soluzione, decomponiamo il sistema in parti più piccole, chiamate sottosistemi. Un sottosistema tipicamente corrisponde alla parte di lavoro che può essere svolta autonomamente da un singolo sviluppatore o da un team di sviluppatori.
  • Decomponendo il sistema in sottosistemi relativamente indipendenti, i team di progetto possono lavorare sui sottosistemi individuali con un minimo overhead di comunicazione.
  • Nel caso di sottosistemi complessi, applichiamo ulteriormente questo principio e li decomponiamo in sottosistemi più semplici.

Decomposizione del sistema

L’obiettivo della fase più importante del System Design è definire la decomposizione del sistema in termini di sottosistemi, cioè:

  1. Definire l’elenco dei sosttosistemi (o moduli) che comporranno il nostro software.
  2. Definire le interfacce dei sottosistemi. I vari sottosistemi come forniscono dei servizi agli altri sottosistemi?
  3. Definire le classi in ogni sottosistema.

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.

Qualità del Design

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:

  • Accoppiamento (coupling);
  • Coesione (cohesion).

L’obiettivo è l’indipendenza funzionale dei sottosistemi.

Accoppiamento

L’accoppiamento è una misura su quanto fortemente una classe è connessa con /ha conoscenza di/ si basa su altre classi.

  • Due o più classi si dicono accoppiate quando è impossibile modificare una senza dover modificare anche la/le altra/e
  • Se due classi dipendono strettamente e per molti dettagli l’una dall’altra, diciamo che sono fortemente accoppiate. In caso contrario diciamo che sono debolmente accoppiate

Per un codice di qualità dobbiamo puntare ad un basso accoppiamento (low coupling).

Un basso accoppiamento è una proprietà desiderabile del codice, poiché permette di:

  1. Capire il codice di una classe senza leggere i dettagli delle altre;
  2. Modificare una classe senza che le modifiche comportino conseguenze sulle altre classi;
  3. Riusare facilmente una classe senza dover importare anche altre classi.

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.

Tipi di Accoppiamento in O-O

Cosa si intende per coupling in un sistema Object-Oriented?

Date 2 classi X e Y:

  1. X ha un attributo di tipo Y, oppure
  2. X ha un metodo che possiede un elemento (es: parametro, variabile locale, etc…) di tipo Y, oppure
  3. X è una sottoclasse (eventualmente indiretta) di Y, oppure
  4. X implementa un’interfaccia di tipo Y.

Lo scenario 3 è quello che porta al massimo accoppiamento in assoluto.

Coesione

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:

  1. Di comprendere meglio i ruoli di una classe;
  2. Di riusare una classe;
  3. Di manutenere una classse;
  4. Di limitare e focalizzare i cambiamenti nel codice;
  5. Utilizzare nomi appropriati, efficaci, comunicativi.

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

Coesione

Livelli di Coesione:

  1. Coesione molto bassa: una classe è sola responsabile di molte cose in aree funzionali molto diverse → più sottosistemi.
  2. Coesione bassa: una classe è sola responsabile di molte cose in una sola area funzionale → più classi, piu’ leggere.
  3. Coesione alta: classe con responsabilità leggere in una sola area che collabora con altre classi (di solito pochi metodi mirati).

Indicatori di un’alta coesione (è il nostro obiettivo):

  1. Una classe ha delle responsabilità moderate, limitate ad una singola area funzionale.
  2. Collabora con altre classi per completare dei tasks.
  3. Ha un numero limitato di metodi, cioè di funzionalità altamente legate tra loro.
  4. Tutti i metodi sembrano appartenere ad uno stesso insieme, con un obiettivo globale simile.
  5. La classe è facile da comprendere.

Esempio

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!)

Modellazione con accesso diretto al DB.

Modellazione con accesso diretto al DB.

Modellazione con inserimento di un sottosistema responsabile di interagire col DB.

Modellazione con inserimento di un sottosistema responsabile di interagire col DB.


Esempio

Chiariamo le responsabilità
Introduco il sottosistema Storage che gestisce l’I/O col DB (disaccoppia tutte le classi dal db).

Conseguenze di questa scelta:

  • Se cambia il DBMS, devo modificare solo il sottosistema Storage, quindi facilito la manutenzione.
  • Abbiamo aumentato la complessità di progettazione (c’è un modulo in più) ed abbiamo rallentato le prestazioni.

Il progettista deve scegliere tra queste due soluzioni quale rispetta maggiormente i Criteri di Design per il sistema da sviluppare.

Modellazione con accesso diretto al DB.

Modellazione con accesso diretto al DB.

Modellazione con inserimento di un sottosistema responsabile di interagire col DB.

Modellazione con inserimento di un sottosistema responsabile di interagire col DB.


Responsibility-driven design

“Capire le responsabilità è fondamentale per una buona programmazione ad oggetti”

Martin Fowler

Pensare alla progettazione di un sistema in termini di:

  • Classe;
  • Responsabilità;
  • Collaborazioni.

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.

Le 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:

  1. Nome della Classe;
  2. Responsabilità della Classe;
  3. Collaboratori della Classe;

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.

Un esempio di CRC Card.

Un esempio di CRC Card.


Le CRC Cards

Ne va specificata una per ogni classe del class diagram di design.

Conseguenza dell’utilizzo:

  1. Le CRC cards spingono ad ottenere un design con chiare responsabilità e controllo sul numero di classi associate
  2. Se non riesco a specificare chiaramente le responsabilità di una classe, c’è un errore di design
  3. Se una classe ha bisogno di troppe classi collaboratrici, c’è un potenziale errore di design

La Legge di Demetra

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. L’oggetto contenente (this)
  2. Un parametro del metodo
  3. Una variabile di istanza dell’oggetto contenente
  4. Un oggetto creato all’interno del metodo

Esempio di applicazione della Legge di Demetra

Esempio di tre diverse soluzioni di progettazione. Le prime due violano la Legge di Demetra, l’ultima no.

Esempio di tre diverse soluzioni di progettazione. Le prime due violano la Legge di Demetra, l'ultima no.


  • Contenuti protetti da Creative Commons
  • Feed RSS
  • Condividi su FriendFeed
  • Condividi su Facebook
  • Segnala su Twitter
  • Condividi su LinkedIn
Progetto "Campus Virtuale" dell'Università degli Studi di Napoli Federico II, realizzato con il cofinanziamento dell'Unione europea. Asse V - Società dell'informazione - Obiettivo Operativo 5.1 e-Government ed e-Inclusion

Fatal error: Call to undefined function federicaDebug() in /usr/local/apache/htdocs/html/footer.php on line 93