Programming Languages Hacks

Importanti regole per linguaggi di programmazione rilevanti come Java, C, C++, C#…

  • Subscribe

  • Lettori

    I miei lettori abituali

  • Twitter

Come salvare la buona progettazione da morte certa…

Posted by Ricibald on January 15th, 2012

Ormai è tantissimo che non pubblico più nulla su questo blog, e con un buon motivo… Ero alla ricerca di me stesso

Può sembrare uno scherzo, ma limitatamente all aspetto tecnologico è proprio così.

Dagli ultimi articoli che ho letto in questo periodo ho approfondito tantissime tecniche di progettazione e implementazione:

  • TDD (Nunit), utilizzabile anche con semplici js tramite divisione MVC
  • Mock e Stub (Moq, Moles)
  • IoC e DI (Unity, NInject)
  • AOP e Decorator/Proxy (PostSharp)
  • DDD (l’unico vero modo per non ottenere codice procedurale ma vero OOP)
  • Code-First: il codice regna, il database è solo un’implementazione della persistenza (Persistence Ignorance)
  • Principio “Tell Don’t Ask”
  • ALM (VS Team System, tracking, branching, …)
  • CQRS: separa comandi (cambiano stato) da query (leggono stato) per non avere effetti collaterali. Questo porta all’uso di eventi a livello di sistema e a nuovi scenari di scalabilità (vedi Azure e db NoSql)
  • Generic Specialization: Double-Dispatch, Visitor pattern
  • Design: confine tra utility (semplice da usare) ed entertainment (evocativa e figa)
  • AHP e Reti neurali per supportare la Teoria delle decisioni
  • Progressive Enhancement, unobtrusive javascript, preemptive fetching
  • Reactive programming: solo eventi, nessun multithreading
  • Agile programming: XP, Scrum, Pair programming
  • Design By Contract
  • Convention over Configuration

Ora, questi sono “solo” puntatori a tutto ciò che ho imparato. Sono tutti concetti importantissimi, ma più o meno tutti si basano sul fatto che il team sia estremamente affiatato e competente nella tecnologia e nella progettazione.

E da qui sono arrivato al punto contraddittorio:

non riuscirò, probabilmente mai, a mettere in pratica questi concetti in un sistema omogeneo dove i membri del team abbiano la piena coscienza di cosa si sta facendo.

Ora, immaginiamo anche di aver trovato 2-3 persone molto competenti che sanno applicare queste tematiche: questo non basterebbe comunque! Nuove persone arriveranno nei progetti, le competenze cambieranno, le persone stesse cambieranno e considereranno “meno rilevante” fare bene le cose, “basta che funziona“. Quindi bisogna chiedere a sé stessi:

vale veramente la pena approfondire in questo modo le proprie conoscenze?

La filosofia “basta che funziona” è in realtà molto più nobile di come la si dipinge, basti pensare allo slogan “it just works” dell’Apple: porta all’efficacia di un progetto.

La buona analisi/progettazione può essere vista come un “cruccio” dell’analista/tecnico, quando poi alla fine comanda sempre e solo il risultato finale, il “just works”. Allora perché preoccuparsi tanto, perché dedicare tempo allo studio di pattern di analisi e progettazione? Beh, molto semplice, in tutti questi discorsi del “just works” non abbiamo considerato la dimensione risorse:

con risorse infinite… tutto funziona!

Una buona progettazione porta a tre benefici fondamentali: la predicibilità, il riuso e la separazione delle responsabilità. Il riuso consente di non reinventare funzionalità già scritte e “mature”, evitando di sprecare tempo inutile in cose già fatte e rifatte, analizzate e testate; la predicibilità è un requisito fondamentale del riuso. La separazione delle responsabilità consente di ottenere alta coesione e basso accoppiamento, consente di limitare gli impatti di una modifica e di implementare funzionalità trasversali, come caching o security abilitandone il riuso e l’attivazione o disattivazione su richiesta.

Questi benefici agiscono sulle risorse necessarie: comportano un beneficio concreto in termini di tempo, persone, soldi richiesti. Il caching se implementato correttamente consente di acquistare meno server, il riuso consente di diminuire i tempi di sviluppo e i bug riscontrati. La predicibilità consente di commettere meno errori in quanto il codice è privo di effetti collaterali.

Quindi questo porta a dire una cosa fondamentale:

la progettazione non ha lo scopo di produrre un sistema che funziona, ma serve a realizzarlo con meno impiego di risorse (tempo, persone, soldi).

La buona progettazione consente quindi di ottenere l’efficienza di un progetto.

Quindi, dopo aver prima messo in discussione e in seguito ricostituito il concetto di progettazione conferendogli quindi una rinnovata dignità di esistere, torniamo alla domanda iniziale:

come fare in modo che le tecniche di buona progettazione siano usate nel tempo?

Non possiamo pretendere che tutti siano competenti, ma possiamo sfruttare a nostro vantaggio molte debolezze umane, tra cui l’estrema conservatività con cui lavora la maggior parte delle persone, che non si domanda i perché delle cose, “basta che funzionano”. Immaginiamo un framework perfetto, che consente di suddividere il progetto in blocchi funzionali ideali, come potrebbe essere (a meno di qualche problema) ASP.NET MVC 3. In questo caso anche il programmatore più inesperto non può esimersi dal realizzare codice scritto in quella forma, e qualunque altra forzatura richiedere troppo sforzo per essere implementata, da cui potremmo sfruttare la “debolezza dell’incompetente”.

Quindi, secondo il sottoscritto, la strategia vincente non è fare in modo che tutti abbiano competenze a questo livello, ma costruire un team dove un “senior architect” sia incaricato della stesura della progettazione e della separazione dei componenti. La “novità” dell’approccio sta nel fatto che i componenti orchestrati devono avere caratteristiche tali da impedire utilizzi impropri alterandone l’architettura. Ad esempio c’è una bella differenza tra esporre questo metodo:

void setUserStatus(string status);

e questo metodo:

void setUserStatus(UserStatus status);

Il primo è suscettibile a utilizzi impropri (potrei scrivere uno stato valido “Active” ma anche uno non valido “Ready”), il secondo non ha possibilità di utilizzi impropri (l’enumerato ha solo valori discreti). Se i metodi rispettono queste regole, se le classi che lo contengono hanno i membri incapsulati correttamente, se i metodi non facade sono visibili solo nell’ambito dei moduli che lo contengono, se si segue l’approccio Design-By-Contract e si validano precondizioni e postcondizioni, non sarà possibile dall’esterno ottenere comportamenti che violano i vincoli architetturali.

Se inoltre abbiniamo controlli di Code Analysis e di complessità ciclomatica minima possiamo fare in modo che anche l’implementazione del singolo metodo non faccia uso di funzioni sbagliate definite altrove. Ma il principio deve sempre essere lo stesso:

permettere anche a un tecnico non evoluto di integrarsi in un sistema complesso nel modo corretto, senza che questo possa compromettere l’architettura.

Diventa quindi fondamentale che il ruolo dell’architetto sia ben delineato e confinato e che tecnici meno competenti non si occupino di concetti architetturali.

Oltre a questo è fondamentale che i singoli tecnici meno esperti abbiano al tempo stesso la giusta motivazione (non sono macchine!) e quindi il ruolo dell’architetto è da una parte schermare da usi impropri, dall’altra quello di concedere la giusta autonomia di implementazione poiché ognuno ha il proprio stile. Questo serve a motivare dal punto di vista tecnico, mentre dal punto di vista degli obiettivi è importante che ogni persona si senta responsabile del proprio blocco funzionale in tempi in cui lui stesso si senta impegnato a rispettare. Ad esempio c’è una bella differenza tra il dire: “fai questo task prima possibile” o dire: “se sviluppi tutto questo use case dall’inizio alla fine, mi dici quando credi di finire?“. Questo responsabilizza le persone poiché le fa sentire parte di qualcosa e se qualcosa va male nel loro use case o sono in ritardo nella consegna stai tranquillo che faranno anche mezzanotte poiché si sentiranno responsabili della loro parte. Ovviamente il loro use case deve essere comunque sempre protetto da implementazioni improprie che alterano l’architettura, e qui torniamo al punto precedente!

E per ultimo, le funzioni di utilità: aldilà del framework che implementiamo, ci sono funzioni utility di uso comune che spesso mettiamo a fattor comune, ma la cui memoria si perde nel tempo. Come fare per forzarne l’uso? Inanzitutto l’uso si può “suggerire” mediante gli extension method, sarà l’IDE che ne suggerirà l’autocomplete. Ma per forzare realmente si dovranno implementare dei custom Code Analysis mediante strumenti come FxCop.

Sintetizzando, design, progettazione e tecniche di programmazione sono fondamentali per minimizzare le risorse necessarie a realizzare un progetto e ottenere efficienza oltre a efficacia (“it just works”). Ma tali caratteristiche non devono appartenere a tutti, come suggeriscono tutti i modelli agili, ma possono purtroppo appartenere solo a poche persone “sagge”, che avranno il compito di costruire un’infrastruttura atta a garantirne il corretto uso nel tempo, anche quando loro stessi avranno (finalmente) cambiato o costruito la loro società ideale.

Leave a Reply

You must be logged in to post a comment.