Ho seguito il webcast "Disegno Architetturale: gli idiomi e le linee guida di desing per il .NET Framework" di Giancarlo Sudano relativo alla prima serie del percorso formativo per Aspire Architect. Volevo qui riassumere i concetti principali che ritengo importanti da ricordare.
Per "Design Idiomatico" si intende un tipo di progettazione che tiene conto della particolare tecnologia/piattaforma che si utilizzerà per l'implementazione della soluzione. Si deve sempre ragionare per astrazione ma senza perdere il riferimento a caratteristiche specifiche dell'ambiente di sviluppo. E' fondamentale conoscerne vantaggi e limiti in modo da fare scelte consapevoli durante il design.
Come progettare un tipo ?
Una delle prime scelte importanti è capire se utilizzare un reference type (class) o un value type (struct). Sappiamo tutti quali sono i vantaggi e gli svantaggi di utilizzare questi tipi quindi è possibile enunciare alcune linee guida.
Generalmente la scelta più frequente è utilizzare class, ma conviene considerare struct solo nei casi in cui:
- Il tipo occupa poco spazio in memoria e non deve essere passato frequentemente attraverso metodi e collezioni (ricordare sempre che i value types sono passati per valore e che il garbage collector ha difficoltà nel gestire oggetti di piccole dimensioni)
- Il tipo è Immutable (a riguardo di ciò considerare il post di Marco De Sanctis Regola aurea per chi usa Value Types )
Un'altra importante scelta è se utilizzare properties o methods. L'utilizzo di metodi porta a dei vantaggi in un contesto distribuito (si riducono le operazioni di serializzazione) e in contesti multithreading (è possibile rendili atomici). L'utilizzo delle property è preferibile perchè si ottiene una usabilità migliore rispetto ai metodi, infatti il principale vantaggio delle property è che hanno la sintassi di un campo nonostante lavorano come funzioni. E' importante utilizzare valori di default per ogni property e preservare il valore di un campo se la corrispondente property setter ha scatenato una eccezione. E' consigliato l'utilizzo di un metodo per operazioni lunghe, per conversioni, per operazioni non deterministiche e se l'operazione restituisce una collezione.
Per quanto riguarda il costruttore di istanza è bene non sovraccaricarlo ed evitare l'utilizzo di metodi virtuali al suo interno. E' possibile sollevare eccezioni ma prestando attenzione che l'oggetto non sarà creato e quindi potrà subito essere finalizzato dal garbage collector. Il costruttore statico (se presente) deve essere privato e non deve sollevare eccezioni al suo interno.
E' importante raggruppare logicamente i tipi creati all'interno di namespace. Tuttavia è bene evitare gerarchie di namespace troppo profonde o un numero elevato di namespace. Un consiglio di Richter è posizionare classi specializzate in namespace più profondi rispetto ai namespace delle classi base.
Design for Extensibility
Le possibilità offerte dal framework .NET in merito alla possibilità di realizzare tipi estensibili sono:
- Derivazione singola
- Classi Sealed
- Astrazione (è un "Tipo" che definisce un contratto ma non la sua implementazione)
- Classi astratte (si considera il metodo migliore per realizzare una astrazione perchè l'aggiunta di un metodo non rompe il contratto, di contro ovviamente è che se ne può avere solo una)
- Interfacce (permettono di simulare l'ereditarietà multipla)
- Classe astratta + Interfaccia (utilizzare entrambe per fornire due alternative di estensione)
- Eventi
- Membri virtuali (da utilizzarsi solo quando è strettamente necessario e il contesto di utilizzo è chiaro)
Exception Design
- E' fondamentale non utilizzare le eccezioni per gestire il normale flusso applicativo perchè hanno un costo notevole sulle performance.
- E' bene utilizzare il blocco finally per operazioni di cleanup ma è opportuno evitare di sollevare altre eccezioni all'interno del blocco.
- Una regola semplice e pratica è quella che dice di sollevare una eccezione all'interno di un metodo solo se quel metodo non riesce a svolgere il compito per cui è stato creato.
- Cercare di utilizzare le eccezioni fornite dal framework (es. ArgumentNullException)
Design Pattern Idiomatici
Il pattern idiomatico più conosciuto nella piattaforma .NET è il pattern dispose di cui ho già ampiamente parlato in un mio post: Interfaccia IDisposable e Pattern Dispose.
Collections, Clonazione
E' bene esporre tramite API pubbliche delle interfacce generiche (come IEnumerable<T> o ICollection<T>) ed eventualmente fare controlli dinamici di tipo. E' consigliabile, ma dipende fortemente dal contesto, non implementare il setter per proprietà che restituiscono una collection.
Inoltre conviene non utilizzare mai l'interfaccia ICloneable per motivi spiegati da Brad Abrams in Implementing ICloneable.
Conclusioni
Ovviamente è importante precisare che le linee guida non sono dogmi indiscutibili e quindi sarà sempre compito del progettista/architetto valutare le possibili alternative. Il vantaggio di avere delle linee guida è uniformare il modo di scrivere codice in modo da semplificare la manutenzione e l'evoluzione di un progetto software.