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

Valeria Vittorini » 9.Programmazione modulare: concetti base


Il problema

Lo sviluppo di sistemi software complessi (programmazione in grande o “in the large”) richiede di:

  • poter suddividere il lavoro tra diversi gruppi di lavoro e di contenere i tempi e i costi di sviluppo che sono in generale molto alti.
  • Mantenere alta la qualità del software in termini di:
    • affidabilità;
    • prestazioni;
    • modificabilità;
    • estensibilità;
    • riutilizzo.

La programmazione modulare è un necessario approccio alla programmazione in grande, in quanto consente gestire la complessità del sistema da realizzare suddividendolo in parti tra loro correlate.

Modularizzazione

La modularizzazione è la suddivisione in parti (moduli) di un sistema, in modo che esso risulti più semplice da comprendere e manipolare.
Essa determina la definizione di una “architettura”, che descrive l’organizzazione del sistema in parti e le interconnessioni tra di esse
Gran parte dei sistemi complessi sono modulari. Ad esempio, un elaboratore composto da diversi sotto-sistemi:

  • sottosistema di elaborazione;
  • sottosistema di memorizzazione;
  • sottosistema di comunicazione;
  • rete di interconnessione.

Modularizzazione e Astrazione

La suddivisione di un sistema in parti richiede che ciascuna di esse realizzi un aspetto o un comportamento ben definito all’interno del sistema.

E’ necessario a questo scopo che venga effettuato un processo di astrazione.

Il processo di astrazione

L’astrazione è il processo che porta ad individuare e considerare le proprietà rilevanti di un’entità, ignorando i dettagli inessenziali.

Le proprietà prese in considerazione definiscono una particolare vista dell’entità (che può in generale essere considerata secondo viste diverse)

Esempio: una persona

  • vista “anagrafica”:
    • Nome, cognome, data di nascita, luogo di nascita, residenza …
  • vista “clinica”:
    • Temperatura corporea, peso, pressione arteriosa, …

L’importanza delle astrazioni

Problemi complessi possono essere gestiti e risolti facendo uso di astrazioni, ad esempio sviluppando un modello della realtà/sistema.

Il modello deve essere sviluppato ad un (appropriato) livello di astrazione, ciò significa che della realtà devono essere rappresentati nel modello tutti e soli gli aspetti ritenuti essenziali.

Il livello di astrazione infatti deve essere:

  • sufficientemente dettagliato da renderne significativa l’analisi;
  • non tanto dettagliato da rendere troppo onerosa l’analisi;
  • ilinguaggi di programmazione fanno uso di diverse astrazioni, ad esempio i TIPI di dato, i sottoprogrammi, etc…

Meccanismi di astrazione

I meccanismi di astrazione più diffusi sono:

  • astrazione sul controllo;
  • astrazione sui dati.

L’astrazione fondamentale dei linguaggi procedurali è l’astrazione sul controllo che consiste nell’astrarre una data funzionalità dai dettagli della sua implementazione.

L’astrazione fondamentale dei linguaggi a oggetti è l’astrazione sui dati che consiste nell’astrarre le entità (oggetti) costituenti il sistema, descritte in termini di una struttura dati e delle operazioni possibili su di essa.

Il concetto di modulo

Un modulo in un sistema software è un componente che può essere utilizzato per realizzare una astrazione.

In generale può esportare all’esterno:

  • servizi/funzioni (“astrazione sul controllo”);
  • dati (“astrazione sui dati”);
  • identificativi.

A sua volta può importare dati, funzionalità e identificativi da altri moduli.

E’ definito come una unità di compilazione.

Definisce un AMBIENTE DI VISIBILITA’.

Realizzazione di un modulo

È dotato di una chiara separazione tra:

  • interfaccia;
  • corpo.

L’interfaccia specifica “cosa” fa il modulo (l’astrazione realizzata) e “come” si utilizza. Deve essere visibile all’esterno del modulo per poter essere utilizzata dall’utente del modulo al fine di usufruire dei servizi/dati esportati dal modulo.
Il corpo descrive “come” l’astrazione è realizzata, è contiene l’implementazione delle funzionalità/strutture dati esportate dal modulo che sono nascoste e protette all’interno del modulo. L’utente può accedere ad esse solo attraverso l’interfaccia.

Modularizzazione in C/C++

Essendo una unità di compilazione, il modulo è un file sorgente
L’uso disciplinato di alcuni meccanismi del linguaggio C/C++ consente una corretta strutturazione di un programma in moduli.
Tra i principali meccanismi vi sono:

  • la compilazione separata,
  • l’inclusione testuale,
  • le dichiarazioni extern,
  • l’uso dei prototipi di funzioni.

Interfaccia di un modulo in C/C++

Un modulo dunque è un file sorgente contenente una interfaccia (specifica) ed un corpo (implementazione).
L’interfaccia deve fornire all’utente indicazione di cosa il modulo esporta e come usare le funzionalità esportate, ad esempio le dichiarazioni dei tipi definiti dal modulo e i prototipi delle funzioni.
Poiché l’utente del modulo deve conoscerne l’interfaccia ma ignorarne l’implementazione, è buona norma tenere separate la specifica del modulo dalla sua implementazione.
L’interfaccia è pertanto realizzata mediante un HEADER file ed utilizzata dagli utenti del modulo includendola mediante i meccanismi di inclusione testuale forniti dal linguaggio (direttiva #include).

Realizzazione di un modulo in C/C++

  • Nell’esempio in figura il modulo A (in A.cpp) esporta funzionalità che devono essere utilizzate dal modulo utente Main (in Main.cpp).
  • Ciò può essere realizzato scrivendo un file di intestazione o header file (con l’estensione .h) separato dall’implementazione di A e contenente le dichiarazioni che costituiscono l’interfaccia di A.
  • Siccome ogni modulo deve essere autoconsistente, ovvero deve contenere tutte le informazioni necessarie per la compilazione, l’header file deve essere incluso (mediante la direttiva al preprocessore #include):
  • nella implementazione di ogni modulo utente;
  • nel file contenente l’implementazione del modulo (in modo da “ricostruire” il modulo nella sua integrità).

Specifica e implementazione

  • Si osservi che, fintanto che l’interfaccia resta inalterata, l’implementazione può essere modificata senza dover ricompilare il modulo utente (ma naturalmente occorre ricollegare i moduli oggetto).
  • La specifica, contenuta nel file di intestazione, può essere riguardata come una sorta di contratto sottoscritto tra l’implementatore e l’utente.
  • Quando più programmatori lavorano simultaneamente ad un progetto di grandi dimensioni, una volta accordatisi sulla specifica dei vari moduli, possono procedere all’implementazione dei rispettivi moduli indipendentemente l’uno dagli altri.

Costruire un progetto

  • La compilazione separata consente dunque di compilare i moduli singolarmente rimandando alla fase di collegamento la risoluzione dei riferimenti esterni.
  • Le modifiche effettuate su un modulo richiedono la ricompilazione del modulo e hanno un impatto sui suoi utenti, che a loro volta devono essere ricompilati.
  • Gli strumenti di sviluppo attualmente disponibili consentono ovviamente di automatizzare il processo di costruzione dell’applicazione.
  • Il filmato mostra come costruire l’applicazione utilizzando l’ambiente DEV_CPP  attraverso la costruzione di un progetto: si supponga di voler realizzare una applicazione costituita da un modulo (modulo.cpp e modulo.h) utilizzata da un modulo utente (main.cpp).
  • Si opera nel modo seguente …

Costruire un progetto

Di seguito è riportata la sequenza di azioni effettuata nel filmato:

  • Eseguire il programma Dev-CPP
  • Dal menù file scegliere Nuovo->progetto
  • Definire il tipo di file eseguibile (target) che si vuole ottenere: nel nonstro caso una “console application”
  • Definire il linguaggio (C++)
  • Dare un nome al progetto (sarà il nome dell’eseguibile)
  • Confermare le scelte
  • Salvare i file di progetto nella cartella desiderata
  • Il Dev genera automaticamente uno schema per il modulo utente main.cpp, che però deve essere salvato su disco fisso: scegliere dal menù file l’opzione “salva con nome ” (“save as”) e dare al modulo utente il nome desiderato
  • Dal menù file scegliere Nuovo->file sorgente
  • Rispondere sì alla domanda “si vuole aggiungere il nuovo file al progetto”
  • Scegliere dal menù file l’opzione “salva con nome ” e dare al nuovo modulo il nome desiderato (ad esempio modulo.cpp)
  • Ripetere le azioni da 9 a 11 per creare il file header, nel salvare il file però si scelga come tipo di file “file header” dal menù a tendina e specificare nel nome l’estensione .h (ad esempio modulo.h)
  • A questo punto l’applicazione è costituita da tre file (main.cpp, modulo,cpp e modulo.h) e due moduli (main e modulo), si può iniziare a scrivere il codice ….

Costruire un progetto: esempio


Modularizzazione

  • L’organizzazione in moduli di un sistema software è un problema trattato dall’ingegneria del software.
  • E’ possibile organizzare in moduli secondo diverse metodologie di sviluppo (Top-down, Bottom-up).
  • Possono essere adottati diversi criteri che tengano conto di alcuni elementi, tra i quali:
    • Information hiding;
    • coesione;
    • accoppiamento.

Metodologie di sviluppo

  • Top down: si parte dall’alto, considerando il problema nella sua interezza e si procede verso il basso per raffinamenti successivi fino a ridurlo ad un insieme di sottoproblemi elementari.
  • Bottom up: si risolvono singole parti del problema, senza averne necessariamente una visione d’insieme, per poi risalire procedendo per aggiustamenti successivi fino ad ottenere la soluzione globale.

Information hiding

  • Consiste nel nascondere e proteggere (incapsulare) alcune informazioni di una entità all’interno del modulo.
  • Alle informazioni protette l’accesso da parte degli utilizzatori del modulo è fornito in maniera controllata.
  • L’accesso ad esse è possibile solo attraverso l’interfaccia.

Information hiding: esempio

  • Un modulo realizza l’astrazione dati fornendo un tipo “persona”.
  • Una persona è caratterizzata al livello di astrazione scelto da Codice Fiscale, Nome, Cognome, Data e Luogo di nascita, Residenza…
  • Il modulo esporta attraverso la sua interfaccia il tipo “persona” ed alcune operazioni che è possibile effettuare su variabili di quel tipo, ad esempio inizializzazione dei dati della persona, stampa dei dati etc…
  • Non è possibile per l’utente del modulo modificare o leggere i dati di una (variabile) persona se non utilizzando le funzionalità esportate dal modulo

Information hiding: criteri

Valutare con attenzione cosa esportare e cosa nascondere.

Le interfacce devono essere:

  • minimali, cioè devono esporre solo ciò che è strettamente necessario;
  • stabili, cioè possibilmente non devono subire cambiamenti nel tempo: se l’interfaccia non cambia, le informazioni nascoste possono essere modificate senza che questo influisca sulle altre parti del sistema di cui l’entità fa parte.

Accoppiamento

  •  Due moduli si dicono debolmente accoppiati se le dipendenze tra di essi sono minimizzate (non si sono create indipendenze non volute o non necessarie);
  • Ad esempio: limitare al massimo l’uso di variabili globali, visibili e utilizzabili da più moduli poiché creano dipendenze non facilmente controllabili.

Coesione

  • Un modulo è fortemente coeso se incapsula un insieme di caratteristiche omogenee, sufficientemente indipendenti da altri moduli.
  • Un esempio di modulo fortemente coeso è un modulo che realizza un’ unica astrazione (Persona, Ordina).

Coesione e Accoppiamento: criteri

Nello sviluppo software coesione e accoppiamento suggeriscono criteri contrastanti, tra i quali è opportuno valutare un tradeoff.

L’ideale sarebbe riuscire ad ottenere una alta coesione tra i moduli ed un basso accoppiamento, ma:

  • un livello molto alto di coesione dei moduli genera in generale una eccessiva crescita delle dipendenze tra di essi.
  • un livello molto basso di accoppiamento può determinare uno scadere della qualità delle astrazioni realizzate dai moduli.

Un esempio in pseudo codice

  • Il prossimo esempio mostra una possibile realizzazione di un modulo. Per la definizione del modulo è stato usato uno pseudo-codice.
  • La prima parte definisce l’interfaccia: il modulo esporta (parola “chiave”: export) il tipo Persona e le funzioni CreaPersona, StampaDatiPersona e ModificaResidenza.
  • Mediante l’interfaccia vengono esportati il nome del tipo ed i prototipi delle funzioni.
  • Il modulo a sua volta importa da un altro modulo (parola “chiave”: import) gli oggetti cin e cout (dalla libreria).
  • La seconda parte contiene il corpo del modulo: nell’implementazione è nascosta la rappresentazione del tipo di dato
  • Persona. (in questo caso un record contenente i campi Nome, Cognome, Residenza) e la definizione delle funzioni.

Un esempio in pseudo-codice


Osservazioni sull’esempio

E’ esplicitamente definito un modulo PERSONA.
Sono esplicitate la sezione di interfaccia e il corpo.
E’ esplicitamente definito cosa viene esportato e cosa viene importato.

E’ realizzato l’information hiding:

  • sui tipi: il tipo persona definito dal modulo è esplicitamente incapsulato nel modulo, l’utente potrà dichiarare variabili del tipo Persona ma non ha la visibilità della dichiarazione del tipo nè ha necessità di sapere come è realizzato.
  • sulle funzioni: l’implementazione delle operazioni è esplicitamente incapsulata nel corpo del modulo, l’utente ha visibilità dei prototipi esportati dall’interfaccia e attraverso di essi potrà invocare le funzioni.

Vantaggi

Seguendo i criteri a cui abbiamo accennato si riesce ad ottenere che:

  • L’implementazione del modulo dipenda in modo minimo dall’”esterno”.
  • Le modifiche apportate ad un modulo impattino in modo minimo sull’”esterno”.

Ciò comporta i seguenti vantaggi:

  • Riuso;
  • Modificabilità;
  • Estensibilità;
  • Manutenibilità;
  • Leggibilità;
  • Protezione;
  • Sviluppo separato.

Meccanismi e Linguaggi

La strutturazione di un programma in moduli deve essere supportata da opportuni meccanismi offerti dal linguaggio di programmazione.
Non tutti i linguaggi sono “modulari”, nel senso di offrire costrutti espliciti per la strutturazione dei programmi in moduli. Alcuni linguaggi modulari sono: ADA, Mesa, Modula2.
Il C non è un linguaggio modulare perchè non offre costrutti espliciti, una programmazione modulare è possibile mediante un uso auto-disciplinato degli strumenti del linguaggio.
Il C++ offre meccanismi espliciti a supporto dell’information hiding, ma non prevede costrutti per la definizione esplicita dei moduli.

Librerie di moduli software

Queste tecniche di sviluppo modulare consentono lo sviluppo su base professionale di librerie di moduli software.
Il produttore di una libreria distribuisce:

  • i file di intestazione (che devono essere inclusi dall’utilizzatore nel codice sorgente) dei moduli che fanno parte della libreria;
  • i moduli di libreria in formato oggetto (già compilati), che l’utilizzatore deve collegare assieme ai propri moduli oggetto.

Tale scelta è tipicamente motivata da esigenze di tutela della proprietà, ed inoltre evita di dover ricompilare i moduli di libreria.

In conclusione…

La modularizzazione è necessaria:

  • Per realizzare LIBRERIE software: per offrire collezioni coese di procedure e funzioni.
  • Per realizzare astrazioni di dato, utilizzando in maniera disciplinata gli strumenti offerti dal linguaggio.
  • Per implementare Tipi di Dati Astratti, avendo a disposizione meccanismi forniti dal linguaggio per l’information hiding.
  • 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