UGIdotNET Home UGIdotNET Home
UGIdotNET Blogs UGIdotNET Blogs
UGIdotNET Forum UGIdotNET Forum
MSDN Architetti MSDN Architetti
Visualizza Modifiche Visualizza Modifiche
Modifica Modifica
Stampa Stampa
Modifiche Recenti Modifiche Recenti
Sottoscrizioni Sottoscrizioni
Ufficio Oggetti Smarriti Ufficio Oggetti Smarriti
Cerca Riferimenti Cerca Riferimenti
Rinomina Rinomina
Cerca

Versioni

19/12/2006 13.11.49
RiccardoGolia-83.225.217.238
15/12/2006 14.00.11
-82.193.6.180
15/12/2006 1.13.06
-84.222.9.124
14/12/2006 10.59.26
-84.222.9.124
14/12/2006 8.46.38
-84.222.9.124
Elenco completo versioni Elenco completo versioni
Managing Complexity
.
Summary Gestire la complessità di un software è un tema fondamentale

Pubblicato nella Rubriki Numero 4 - 14 Dic 2006 da IgorDamiani

Proliferazione del numero delle classi

Gestire la complessità di un software è un tema fondamentale che entra in gioco quando si intende procedere alla realizzazione di un buon software. Quando si accenna al concetto di “complessità di un software”, non si parla di quanto sia più o meno complicato per l’utente utilizzarlo, ma si parla della sua complessità interna a livello di organizzazione architetturale dell’intero sistema, delle singole classi e di codice. Si parla quindi della complessità che l’architetto e il developer hanno inserito durante tutto il ciclo di sviluppo del software, dal design dell’intero sistema fino allo sviluppo vero e proprio del codice. E’ importante gestire tale complessità? Certo che sì, altrimenti non saremmo qui a parlarne! Perchè è importante? Dirlo con una sola frase è praticamente impossibile,a ma ci proviamo lo stesso: ogni software parte da una manciata di requisiti chiari e semplici, ma via via che passa il tempo tende a diventare più grosso (nuove richieste, nuove features, testing e bug-fixing, etc): gestire la complessità significa anche far evolvere un software in modo coerente, omogeneo e chiaro.

Relazioni tra una classe e l'altra

Tutto il tema del 'managing complexity' ruota attorno ad un semplice, ma non banale, presupposto: per poter lavorare su una certa parte di programma - sia essa un intero layer, o una classe o una singola routine - dobbiamo conoscere il meno possibile delle altre parti dello stesso programma. Meno dobbiamo conoscere, meno siamo confusi, più riusciamo a focalizzare la nostra attenzione su quello che stiamo facendo. Più la nostra attenzione è alta, più il nostro codice è efficiente. Ma non è solo una questione di produttività ed efficienza. Se la modifica di codice contenuto in una classe ci obbliga a pensare a tutte le classi dipendenti, allora le classi sono strettamente correlate fra loro (instaurando un alto grado di accoppiamento): il risultato è che per gestire una sola classe, in realtà devo tenere in considerazione anche il design interno delle altre. Se si modifica la firma di un costruttore di una classe, è evidente che tutto il codice che istanzia questa classe sarà dipendente da tale modifica e dovrà essere corretto. Questo rende il codice difficilmente manutenibile, proprio perchè potenzialmente una piccola implementazione in un punto potrebbe impattare su tutto il resto dell'applicazione. Ma è anche scarsamente leggibile, perchè per capire davvero cosa fa una classe attraverso il suo codice sorgente, in realtà è necessario leggere anche la classe da cui dipende, continuando a rimbalzare l'una dell'altra.

Forte dipendenza tra le classi

Gestire la complessità a livello architetturale consiste nel suddividere il sistema - che inizialmente disegniamo come un blocco unico - in più sottosistemi separati e che devono collaborare fra loro. Ogni sottosistema è un layer diverso, e diverse sono quindi le responsabilità e le logiche che le classi contenute nel layer stesso devono risolvere. Possiamo immaginare diversi livelli dei nostri layers: in una classica applicazione, quello a più alto livello è l'interfaccia utente, ovvero quello che è a contatto con l'utente finale, mentre il layer più basso è l'object model. Quest'ultimo, in particolare, definisce le classi che appartengono al dominio applicativo. Ogni layer può comunicare solo con altri layers adiacenti. Layers tipici di un sistema sono la user-interface, la business logic, il data access layer, etc: quando si sviluppa un sottosistema, ci si concentra su quello e si dovrebbe conoscere il meno possibile degli altri layers. L'astrazione di ogni layer dall'altro si ottiene tramite l'utilizzo di interfacce. Tramite interfaccia, è possibile utilizzare gli oggetti senza conoscerne il tipo concreto: è sufficiente sapere che una certa interfaccia ci fornisce un contratto grazie al quale possiamo usare proprietà e metodi. Non c'è più un forte accoppiamento, perchè non si lavora più con una classe determinata scritta nel codice, ma abbiamo semplicemente un riferimento ad un'interfaccia.

Concetti da tenere a mente simultaneamente

La complessità va controllata non solo a livello architetturale, ma anche quando si progetta l'applicazione e si comincia a pensare alle classi che dovremo implementare. E' evidente che all'aumentare del numero di classi, aumenta anche la complessità del progetto. Ma è anche vero il contrario: una sola classe che incorpora tutta la logica è anch'essa un errore di progettazione. Qual'è il numero giusto di classi? La risposta ovviamente non c'è, o almeno non è così semplice. E' opportuno disegnare le classi in modo tale che operino al giusto livello di astrazione e che siano consistenti fra loro. Ciò implica tra le altre cose avere un nome chiaro, che non lasci dubbi sullo scopo della classe stessa. O ancora, che ci sia un buon riutilizzo di codice, per esempio. Classi astratte, ereditarietà, composizione di interfacce sono tutte tecniche valide per organizzare meglio le classi ed il loro codice. Organizzare gerarchicamente la struttura delle relazioni fra classi ci aiuta a elaborarla molto meglio, e per questo possiamo raggrupparle con strutture come le liste.

Lunghezza delle routine

All’inizio di questo articolo abbiamo detto che gestire la complessità non è un concetto astratto. Questo è talmente vero che anche all’interno della singola routine è opportuno prenderla in considerazione mentre sviluppiamo. La lunghezza di una routine, per esempio, è una metrica (un po’ superficiale) che possiamo usare per misurare la complessità del nostro codice. O ancora, contare il numero di "decision point" contenuti in una routine (punti in cui compaiono istruzioni if, while, loop, for, e simili): più questo numero è alto, più la routine dovrebbe subìre del refactoring, spezzandola in più routines separate. Tutto questo per migliorare la leggibilità del codice di fronte alla lettura di uno sviluppatore esterno, oppure a noi stessi quando dovremo tornare a modificare la stessa routine. Questo approccio permette di avere routines brevi, efficaci, semplici da leggere e da modificare: il miglior modo di mantenere bassa la complessità in una routine è quello di pensare che il codice deve essere scritto una volta sola, e bene, e letto molte volte.

Numero di decision point nel codice

Commentare il codice è un'altra best practice consigliata: grazie ai nostri commenti nel codice, rendiamo più chiaro l'obiettivo che vogliamo raggiungere, possiamo documentare parti che possono risultare oscure. Ma si può fare di più. Una cosa migliore è lasciare che il codice parli da sé, che sia autoesplicativo e chiaro, usando nomi di variabili forti ed attinenti. I commenti hanno il difetto che devono essere mantenuti, per fare in modo che siano costantemente allineati con il codice reale della routine, e quindi richiede un’attività in più che porta via tempo e risorse. Da un certo punto di vista, è molto meglio fare in modo che il codice sia abbastanza chiaro da essere leggibile senza bisogno di commenti. Questi ultimi andrebbero utilizzati solo per evidenziare aspetti particolari, e non immediatamente chiari a chi legge.

Span e life time delle variabili

Lo scopo di questo articolo è stato quello di mostrare come la complessità di un software dovrebbe essere un tema da tenere a mente in qualsiasi punto del ciclo di sviluppo del software stesso. Sebbene si parli della complessità interna di un software – e quindi un concetto che magari non arriva all’utente finale del nostro prodotto – è comunque fondamentale, perchè se un software è modellato coerentemente e tutte le sue parti collaborano e lavorano assieme, il software ne guadagna in affidabilità (un sistema progettato e realizzato in modo opportuno è anche più facilmente testabile) e modularità (un layer può essere sostituito con un altro, mantenendo invariata la struttura del sistema stesso, aggiungendone e modificandone funzionalità). Anche se non direttamente, anche l’utente finale beneficierà di un sistema software progettato meglio. Gestire la complessità del software è tanto compito dell’architetto, sia dello sviluppatore: tutte le persone coinvolte nel progetto hanno il compito di far nascere ed evolvere il sistema software in modo uniforme, per garantire un prodotto finale di maggiore qualità ed efficienza.

UGIdotNETWiki

UGIdotNETWiki è il WikiWiki italiano dedicato a .NET

Se è la prima volta che senti parlare di Wiki, leggi il BenvenutoAiVisitatori e WikiInUnMinuto, oppure il ManualePassoPassoDelWiki.

Argomenti Recenti

  • ManagingComplexity
© 2008 User Group Italiano UGIdotNET. Tutti i diritti riservati. Note legali