Dr. Carlo Pescio Il Design Architetturale |
Pubblicato su Computer Programming No. 39
Introduzione
Riprendiamo, dopo la pausa estiva, la nostra discussione sull'approccio
object oriented allo sviluppo del software; da questa puntata,
iniziermo a parlare di design, e vedremo come i risultati dell'analisi
possano evolvere in un prodotto piu' dettagliato e preciso, che
indichi come devono essere svolte le diverse attivita'
del sistema. Ricordiamo infatti che compito dell'analisi e' definire
cosa il sistema sia chiamato a fare, e compito del design
e' stabilire come cio' debba essere realizzato.
Chi ha seguito sin dall'inizio questa serie di articoli, ricordera'
che la prima puntata ha dedicato ben poco spazio agli aspetti
specifici dell'object oriented, presentando invece una panoramica
del processo di sviluppo; in questa puntata, sara' nuovamente
necessario staccarsi per un momento dal singolo paradigma, al
fine chiarire alcuni punti fondamentali del design architetturale.
Non me ne vogliano i lettori "affamati di oggetti",
poiche' affrontare temi di piu' ampio spettro consentira' a tutti
di considerare l'approccio object oriented per cio' che e' realmente,
ovvero un buon metodo per risolvere i problemi, non la panacea
di tutti i mali.
Design Architetturale
Se consideriamo la realta' dei processi di sviluppo del software,
non limitandoci quindi a discuture le regole auree della progettazione,
ma osservando cio' che accade in casi concreti, vedremo che in
progetti medio/piccoli, l'analisi tende ad essere poco piu' di
una rapida raccolta dei requisiti: in pratica, ci si ferma al
documento informale di specifica, e talvolta si individuano le
classi principali, sostanzialmente "ad occhio". Come
ben sappiamo, spesso poi il design si fonde con l'implementazione,
risolvendo i problemi a mano a mano che si incontrano, spesso
seguendo un cammino dominato dall'interfaccia utente, o dal flusso
dei dati, piu' raramente dagli algoritmi di calcolo. Tutti (o
quasi) sappiamo che non e' il modo migliore di procedere nello
sviluppo, ma e' altrettanto vero che in piccoli progetti, con
budget e tempi di conseguenza limitati, il risultato finale potra'
essere comunque accettabile, in funzione ovviamente delle capacita'
dello sviluppatore.
Su progetti piu' grandi, un simile metodo non funziona affatto;
per quanto molti programmatori, cresciuti su piccoli progetti,
siano restii ad accettarlo, lo sforzo necessario allo sviluppo
non cresce in maniera lineare con la dimensione del progetto.
Chi ha sviluppato programmi da 10.000 linee non puo' direttamente
trasferire le sue tecniche di sviluppo ad un progetto da 500.000
linee di codice: la diversa dimensione richiede un approccio piu'
sistematico e preciso. La convinzione diffusa di essere in grado
di portare a termine un qualunque progetto e' peraltro tipica
dell'ambiente software: difficilmente chi ha costruito una passerella
su un torrente si sentirebbe autorizzato a progettare il nuovo
Golden Gate, ma raramente un programmatore ritiene un progetto
troppo ambizioso per le proprie capacita', almeno all'inizio;
un ulteriore segno, nel caso servisse, dell'immaturita' dell'ingegneria
del software, o perlomeno dello scarso assorbimento dei suoi messaggi.
Un componente fondamentale nei grandi progetti, indipendentemente
dal metodo usato per lo sviluppo (analisi strutturata, metodi
object oriented, metodo di Jackson, ecc) e' l'architettura.
Mentre la passerella (il piccolo progetto) funzionera' a dovere
una volta che i suoi elementi fondamentali siano stati costruiti
ed incastrati insieme, un grande ponte deve necessariamente essere
progettato alternando continuamente due o piu' livelli di astrazione:
da una parte avremo "la visione globale", che consente
di trattare problemi come le vibrazioni dell'intera struttura,
o la distribuzione del carico globale, e cosi' via, includendo
anche decisioni strategiche e gestionali. Dall'altra parte dovremo
comunque prestare attenzione al singolo dettaglio, al bullone
che non deve cedere sotto lo sforzo.
L'architettura del sistema, ovvero una visione ad alto livello
delle componenti statiche e dinamiche fondamentali, rappresenta
il tratto d'unione tra un vasto elenco di requisiti, frutto dell'analisi,
ed un mare di particolari, di minuzie, frutto del design dettagliato.
L'architettura guidera' le scelte strategiche a lungo termine,
e definira' l'approccio globale del sistema alla soluzione dei
problemi.
Un piccolo esempio potra' forse chiarire meglio il concetto di
architettura del sistema: consideriamo un programma di elaborazione
testi abbastanza sofisticato. Tale programma dovra' essere in
grado di leggere e scrivere files in una varieta' di formati,
oltre naturalmente a consentirne l'editing. Stabiliti i termini
del problema, esistono molte soluzioni; chi di voi ricorda i programmi
di parecchi anni fa, ricordera' anche le utility di corredo per
la conversione di formato; in quel caso, il design architetturale
aveva individuato due componenti, un programma di elaborazione
testi con un unico formato di lettura e scrittura, ed uno o piu'
programmi separati, in grado di effettuare la conversione dei
dati. Programmi piu' moderni sono invece costruiti intorno ad
una architettura differente, con moduli piu' piccoli e disaccoppiati:
di norma, un componente di editing viene collegato a diversi filtri,
in grado di leggere o scrivere un particolare formato; il collegamento
tra componente di editing e filtri e' minimo, ed infatti puo'
essere in molti casi modificato anche a run-time. Il componente
di editing e' a sua volta suddiviso in componenti di alto livello.
Ovviamente, la diversa architettura ha anche requisiti di hardware
e software di base differenti, rispetto alla soluzione monolitica
piu' datata: ecco quindi che considerazioni di tipo strategico
e di management fanno la loro comparsa nel design architetturale.
Ritornando per un istante a considerare alcuni paradigmi di sviluppo
del software, e' stato osservato come il difetto piu' frequente
nei prodotti sviluppati con la tecnica dei prototipi, o con metodi
RAD (Rapid Application Development) sia proprio la carenza di
struttura, se analizzati ad un livello di astrazione piu' alto.
Cio' non dovrebbe stupire piu' di tanto, considerando che nell'approccio
a prototipi si finisce spesso per concentrare la propria attenzione
su un elemento alla volta, rischiando facilmente di perdere di
vista la "big picture", il sistema visto come insieme
globale. Un simile rischio e' presente anche nello sviluppo troppo
customer-oriented, ovvero troppo incline ad introdurre ogni caratteristica
che i singoli utenti desiderano: persi in una marea di opzioni,
si perdono di vista le scelte strategiche piu' opportune a lungo
termine. La tentazione opposta, ovvero di porre troppa enfasi
nel definire la struttura del sistema, arrivando si' ad una architettura
estremamente modulare, estendibile, portabile ed elegante, ma
con tempi di sviluppo abissali e talvolta con richieste di risorse
poco rassicuranti, e' invece l'altra faccia della medaglia. Uno
sguardo alle recenti vicissitudini nello sviluppo dei sistemi
operativi potra' confermare la difficolta' di operare la scelta
corretta, rimanendo in equilibrio tra due tentazioni molto forti.
Proprio per tali ragioni il design in generale, ed il design architetturale
in particolare, possono certamente essere considerati come le
fasi piu' complesse e critiche nello sviluppo del software. Affermazioni
di questo tipo sono abbastanza impopolari, e tendono a far storgere
il naso agli analisti, o ai programmatori -specialmente a coloro
che si considerano "artisti"- poiche' sembrano sminuire
l'importanza del loro lavoro. Vi e' comunque un buon fondamento
alla base di tale convincimento: mentre l'analisi e la codifica
richiedono, di norma, di operare ad un unico livello di astrazione,
nella fase di design si devono continuamente considerare fattori
di alto livello, vicini all'analisi, e fattori di basso livello,
vicini all'implementazione. Non considerare i primi porterebbe
direttamente ad una architettura limitativa (forse il termine
piu' espressivo per descriverne il risultato e': "under-architectured"),
mentre trascurare i secondi potrebbe facilmente portare a soluzioni
difficili da implementare (un problema sentito non di rado dai
programmatori, che talvolta devono rifare buona parte del lavoro
svolto da progettisti inadeguati).
Purtroppo, molti programmatori e progettisti trovano difficile
bilanciare costantemente considerazioni di livello architetturale
e considerazioni di dettaglio quasi implementativo. Tuttavia,
la capacita' di mantenere la visione globale del sistema, ed allo
stesso tempo di concentrarsi sui dettagli, e' forse una delle
caratteristiche che meglio identificato il buon progettista, e
cio' vale anche per chi progetta l'architettura di un sistema
- che dovrebbe essere affidata ai migliori tra i progettisti.
Fondamentale e' anche la profonda capacita' critica ed autocritica:
i design peggiori che ho incontrato, erano sempre frutto di persone
magari brillanti, ma incapaci di identificare i difetti nei propri
elaborati. Individuare con chiarezza i problemi ed i vantaggi
di ogni soluzione e' uno dei fondamenti del design: nessuna
soluzione e' esente da difetti; tuttavia, se li conosciamo
possiamo valutarne la rilevanza rispetto ai nostri obiettivi.
Una soluzione limitativa, ma con tempi di sviluppo brevi, puo'
essere migliore o peggiore di una architettura totalmente aperta
ma con richieste di tempi e costi ben piu' consistenti. In questo
senso, occorre sempre dubitare di chi non sa, o non vuole, trovare
zone d'ombra nella soluzione prospettata: vi e' una elevata probabilita'
che non abbia considerato i lati negativi della proposta, o analizzato
possibili alternative. Tra l'altro, anche se non pochi pensano
che per ogni problema esista una sola soluzione, e' invece
abbastanza ragionevole trovarsi in situazioni che consentono diversi
risultati di design, tutti completi e consistenti, ognuno dei
quali probabilmente sbilanciato verso una caratteristica che il
progettista ha ritenuto particolarmente rilevante. Solo con un
onesto confronto dei pro e dei contro delle diverse soluzioni
si puo' operare la scelta corretta.
Chi desiderasse approfondire alcune delle tecniche di "pensiero
creativo" che fanno parte del processo di design, puo' consultare
[1], utilizzato all'universita' di Stanford nel corso di design,
oppure [2], sempre valido seppure un po' datato, e che dedica
una parte consistente della trattazione alla "scienza del
design".
Il riuso: "design patterns"
Il design architetturale e' ovviamente piu' semplice da capire
che da ideare partendo da zero; affermazione di per se' banale,
ma nel campo del software si ha una tendenza quasi spasmodica
a reinventare la ruota. Nuovamente, cio' non accade in molti altri
settori, dall'architettura all'ingegneria meccanica, dove il riutilizzo
di elementi di design e' pratica comune e consolidata. Come avevo
accennato nella seconda puntata della serie, alla quale rimando
anche per alcuni cenni bibliografici, una tendenza relativamente
nuova nel campo del software e' di cercare di identificare, catalogare
e distribuire una serie di strutture o scelte architetturali che
si sono dimostrate "vincenti" in numerosi contesti applicativi.
Tali strutture vengono definite "design patterns", seguendo
la terminologia introdotta da Cristopher Alexander, che negli
anni 70 ha cominciato a sperimentare l'uso di un linguaggio per
esprimere i pattern ricorrenti nell'architettura (degli edifici,
non del software!). L'idea di base del riuso e' sempre la stessa:
nel contesto del design, significa cercare tra un catalogo di
pattern di provata efficacia la soluzione per un problema, o sottoproblema,
simile ai nostri, ed adattare tale schema al nostro caso concreto.
Alcuni pattern molto interessanti, ad esempio su sistemi distribuiti
e sull'I/O sincrono/asincrono, possono essere trovati in [4],
che costituisce comunque un'interessante lettura per ogni progettista.
Ovviamente, non dobbiamo limitarci al ruolo di consumatori nel
gioco del riuso: se abbiamo applicato con successo una strategia
di attacco per una categoria di problemi, potrebbe essere interessante
condensare tale esperienza in un sintetico pattern di design,
a beneficio dell'organizzazione o del team di sviluppo; in senso
piu' esteso, il "design per il riuso", ovvero la definizione
dei particolari del design architetturale con un'attenzione particolare
alla creazione di pattern flessibili, ma ben definiti, per la
soluzione dei problemi, puo' essere una impostazione molto vantaggiosa,
se le risorse e la politica dell'ambiente di lavoro la consentono.
Una buona architettura
Scendiamo ora in dettagli piu' tecnici, ed analizziamo le qualita'
che caratterizzano una "buona architettura". In generale,
potremmo dire che una buona architettura e' allo stesso tempo
semplice da capire, da estendere, da implementare, da riutilizzare,
e cattura l'essenza statica e dinamica del sistema: ognuna di
queste proprieta' gioca un ruolo essenziale, che distingue una
architettura elegante da una approssimativa. Ovviamente, il termine
"semplicita'" va inteso come "assenza di complessita'
artificiosa": l'architettura di un sistema operativo moderno
non sara' mai "semplice" in assoluto e tantomeno la
sua implementazione; tuttavia vi e' una linea abbastanza netta
tra la difficolta' intrinseca del problema e le complicazioni
introdotte dallo "sviluppo selvaggio". Tornando al semplice
esempio del programma di elaborazione testi, una possibile soluzione
artificiosa potrebbe essere quella di avere un interprete centralizzato,
in grado di riconoscere un sopra-insieme dei vari formati. Oltre
a complicare in modo terribile la realizzazione, un simile approccio
costituirebbe l'incubo del team di manutenzione ed estensione.
Ciononostante, ho conosciuto sviluppatori che la considerano come
"soluzione ideale".
Riprendiamo ora le singole qualita' di una buona architettura:
la "semplicita' di comprensione", che potremmo meglio
definire "maneggevolezza intellettuale", e' un elemento
fondamentale poiche' rendera' piu' agevoli i continui spostamenti
dell'attenzione dal particolare al generale. Quando ci si rende
conto che la struttura interna di un sistema e' tale per cui ad
ogni modifica, aggiunta, variazione dei requisiti, e' necessario
riconsiderare una moltitudine di fattori, l'architettura del sistema
manca di maneggevolezza intellettuale. Tipicamente, una rappresentazione
ad alto livello della struttura statica del sistema dovrebbe rispettare
le regole del "magico numero 7" viste in precedenza,
e cosi' anche la struttura dinamica (sulla quale torneremo diffusamente
in seguito).
La semplicita' di estensione e' uno degli indici primari dell'esistenza
di una vera e propria architettura di sistema, che non deve essere
composto di moduli che "accidentalmente" operano di
concerto.Ritornando all'esempio degli elaboratori di testi, mentre
entrambe le soluzioni, al livello di dettaglio dato, sono intellettualmente
maneggevoli, la soluzione basata su filtri dinamici e' intrinsecamente
piu' flessibile e semplice da estendere rispetto alla soluzione
del "programma di import/export monolitico".
La semplicita' di implementazione deriva dalla completezza del
design e dall'attenzione posta in tale fase, rispetto ai vincoli
di prestazioni, ambiente, e piattaforma target. Un design architetturale
che richieda implicitamente la presenza di un database ad oggetti
sara' tutt'altro che semplice da implementare su un microcontroller
per applicazioni embedded; d'altro canto, un design che imponga
vincoli di timing estremamente rigorosi sara' piuttosto complesso
da implementare su un sistema con multitasking cooperativo. Chi
ritiene che un buon design sia completamente indipendente dalla
piattaforma e dal sistema, corre seriamente il rischio di essere
odiato dai programmatori, e di vedere il suo lavoro completamente
stravolto nella fase di codifica.
La possibilita' di riutilizzo, oltre ad essere uno degli elementi
di maggiore interesse economico, e' anche un chiaro indice della
generalita' delle scelte compiute, che non sono state ritagliate
intorno ad un problema specifico, ma che tengono in considerazione
una classe piu' astratta di esigenze. Tornando per l'ennesima
volta all'esempio dell'elaboratore di testi, la soluzione dei
filtri dinamici e' intrinsecamente piu' riutilizzabile, poiche'
piu' generale, di una soluzione che preveda la lettura e la scrittura
di un numero fisso di formati "di cui l'utente ha bisogno
subito". Gli stessi filtri, se progettati adeguatamente,
potrebbero ad esempio essere riutilizzati per importare testo
in un foglio elettronico, o in un programma di impaginazione o
di grafica.
Maggiori dettagli sull'aspetto architetturale del design possono
essere trovati in [5] e [6].
Verso la definizione dell'architettura
Al termine della fase di analisi, abbiamo identificato un certo
numero di classi, relazioni, ed eventi tipici del sistema; ci
si potrebbe chiedere se tale struttura non identifichi immediatamente
l'architettura del sistema stesso, proprio perche' ne cattura
gli elementi essenziali. Cio' sarebbe auspicabile, e di fatto
costituirebbe un enorme punto a favore dell'approccio object oriented,
ma purtroppo in genere non corrisponde a verita'. Se il sistema
e' relativamente semplice, come nel caso degli esempi necessariamente
ridotti che si trovano in libri ed articoli, si ha in effetti
una stretta corrispondenza tra il risultato dell'analisi e l'architettura
del sistema; non appena si esce dai confini ridotti della divulgazione,
tuttavia, la complessita' dei sistemi richiede metodi di attacco
piu' completi.
Esistono fondamentalmente quattro fattori che dovremo bilanciare
nel definire l'architettura del sistema:
Elementi essenziali dell'architettura object oriented
Il design architetturale object oriented contempla di norma quattro
elementi, che verranno modellati secondo i principi di astrazione
del paradigma ad oggetti (classi, attributi, metodi, strutture
tutto/parti e generalizzazione/specializzazione, e soggetti o
categorie di classi); tali elementi possono essere identificati
come:
Bibliografia
[1] James Adams: "Conceptual Blockbusting: A Guide to Better
Ideas, 2nd edition", Norton press, 1980.
[2] Herbert Simon: "The Science of Artificial", MIT
Press, 1969.
[3] Mike Tudball: "Architecture-Driven Development",
Objects In Europe, Vol 2 No 1, 1995.
[4] Coplien, Schmidt: "Pattern Languages of Program Design",
Addison-Wesley, 1995.
[5] Lawrence Peters: "Handbook of Software Design: Methods
and Techniques", Yourdon Press, 1981.
[6] Michael Jackson: "Principles of Program Design",
Academic Press, 1975.
[7] Frederick Brooks: "The Mythical Man-Month", Addison-Wesley,
1975.
[8] Steve Maguire: "Debugging the Development Process",
Microsoft Press, 1994.
[9] Parnas, Clements: "A Rational Design Process: How and
Why to Fake it", IEEE Transactions on Software Engineering,
Febbraio 1986.
Biografia
Carlo Pescio (pescio@acm.org) svolge attività di consulenza in ambito internazionale nel campo
delle tecnologie Object Oriented. Ha svolto la funzione di Software Architect in grandi progetti per
importanti aziende europee e statunitensi. È incaricato della valutazione dei progetti dal
Direttorato Generale della Comunità Europea come Esperto nei settori di Telematica e Biomedicina.
È laureato in Scienze dell'Informazione ed è membro dell'ACM, dell'IEEE e della New York Academy
of Sciences.