Programming Languages Hacks

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

  • Subscribe

  • Lettori

    I miei lettori abituali

  • Twitter

Riuso o Automazione – come ottenere un riuso al 100% personalizzabile

Posted by Ricibald on June 26th, 2013

Riuso con libertà assoluta di customizzazione… Connubio possibile?

Ricordiamo la metafora per i design pattern:

a * b + a * d = a * (b + d)

Significa che l’essenza dei design pattern e della buona programmazione sta nel riuso: più si riesce a centralizzare maggiore è la consistenza e l’efficienza che si ottiene.
L’efficienza è ovvia: centralizzando riusciamo a non riscrivere codice e quindi a ottimizzare il nostro lavoro. Centralizzare significa ottenere consistenza: se check di errori o log esiste nel metodo centralizzato, sarà propagata ovunque tra i chiamanti.

Esistono però ambiti dove il riuso è necessario ma molto arduo a causa delle esigenze specifiche del contesto. Un esempio tipico sono le UI (User Interface).

Immaginiamo un modello complesso da visualizzare, con 100 proprietà di tipi diversi. Nella visualizzazione o edit del modello dobbiamo visualizzare o modificare in modo consistente tutte le proprietà per conferire un look & feel autorevole. D’altra parte la visualizzazione non può nemmeno essere troppo rigida poiché si perderebbe quella flessibilità per visualizzare o modificare in modo speciale particolari proprietà. Il discorso potrebbe non essere limitato alle singole proprietà ma anche al layout stesso: potrebbe richiedere raggruppamenti speciali opzionalmente collassabili, funzionalità di drag & drop, autocompletamento, dialog ajax, popup di conferma, validazione ajax, …

Come ottenere il riuso con la giusta flessibilità?

Ci sono diverse possibilità in questo caso per ottenere il riuso con la flessibilità desiderata:

  • copia e incolla: più che di riuso si parla di “riciclo”. È una soluzione svantaggiosa? Da moltissimi punti di vista sì, ma la replicazione del codice ha anche vantaggi come la diminuzione del rischio globale (se fallisce una libreria base non fallisce tutto) e libertà assoluta di customizzazione. Ovviamente avremo bassissima consistenza ed efficienza
  • micro funzionalità fornite tramite helper: che siano classi di check, design pattern complessi, risorse (css/js), user control, html helper, aspetti, interceptor o extension method tali componenti di appoggio consentono di scrivere codice di più alto livello disinteressandoci di dettagli tecnici come log, caching, verifica errori, generazione html, transazionalità, undo metodi, … Passando opportuni parametri o implementazioni specifiche (Pattern Strategy) possiamo personalizzarne il comportamento (es. se loggare o meno) rendendole scelte comunque flessibili. Normalmente agiscono però a un basso livello: l’orchestrazione dobbiamo sempre implementarla
  • intera funzionalità autogenerata: ci troviamo nel caso più estremo, rendendo il tutto molto più consistente ma anche molto più rigido. Utilizzando metadati (da file, attributi, reflection, convenzioni, codice tramite model specializzati…) o generando codice che abilita il pattern Template Method (es. partial method) possiamo avere dei gradi di customizzazione sulle singole micro funzionalità e sull’orchestrazione stessa. Questa tecnica funziona bene e ha differenti gradi di libertà ma richiede lo sforzo di costruire un framework la cui definizione di metadati potrebbe essere non banale per ottenere il comportamento “ipotizzato” (il risultato finale potrebbe essere difficile da prevedere e non immediato). Infine, la generazione ha comunque un costo computazionale a ogni pagina generata da valutare.
  • intera funzionalità pregenerata: sembra lo stesso caso precedente, in realtà è una generazione su file che sfrutta internamente gli helper. È come se avessimo scritto noi il codice “a mano” ma in realtà è stato generato su file basandosi su metadati. Lo scopo è quello di ricondursi al secondo caso nella lista, in cui costruiamo ogni micro funzionalità tramite helper, ma risparmiando l’onere di riscrivere l’intera orchestrazione. Questo consente che l’orchestrazione sia liberamente editabile: non si otterrà la consistenza nell’orchestrazione (le modifiche in un flusso condiviso devono essere propagate a mano) ma si guadagna in efficienza. Inoltre non inficia in tempi di calcolo. Le tecniche possono essere applicazioni standalone o integrate nell’IDE come i Text Template (T4) che consentono di definire MVC Template.
  • intera funzionalità pregenerata solo su richiesta, altrimenti autogenerata: è un ibrido tra le due. Di base viene autogenerata la funzionalità magari ottimizzandone i tempi tramite un precaching e solo in caso di esigenze particolari di customizzazione si richiede esplicitamente di creare la funzionalità pregenerata su file su cui si avrà completa libertà di azione, ma partendo da uno scheletro già completamente funzionante. Un ottimo esempio in questo senso è ASP.NET Dynamic Data

Il concetto in questo post è stato generalizzato (guarda caso…) ma si può leggere “funzionalità” come “proprietà da visualizzare in input (html)” e “orchestrazione” come “layout della pagina web”: i principi si applicano quindi tranquillamente anche in un contesto web.

Non esiste solo il riuso classico…

Questo post voleva evidenziare i diversi livelli per generalizzare e abilitare il riuso: spesso ci si basa solo sulla seconda soluzione vedendo il riuso solo come la costruzione di componenti e l’utilizzo sapiente di design pattern. In realtà esiste un livello spesso non esplorato: quello della generazione automatica del codice, che elimina gran parte del lavoro pur conservando una libertà assoluta di customizzazione (come nell’ultimo caso).

Arrivare alla generazione automatica del codice ha un costo e non è detto che paghi sempre. Questi i contesti dove secondo me la generazione automatica paga:

  • UI che gestiscono varie entità in modo per lo più omogeneo
  • workflow che gestiscono varie entità in modo per lo più omogeneo
  • mapping tra entità (invece di utilizzare automapper)
  • codice collante strutturale (flusso application-domain-repository per una certa entity)
  • unit test che seguono un certo standard e certi input limite noti
  • entità e metodi derivati da sorgenti esterne (database, wsdl, xsd, api REST)
  • documentazione dei metodi per soddisfare determinati standard
  • refactoring complessi automatizzabili

Altri casi sono facilmente individuabili: ogni qualvolta ci troviamo a lavorare “come macchine” dovremmo far fare il lavoro “alle macchine” per lasciare a noi i lavori più interessanti.

Buon riuso! Anzi, buon riuso+automazione…!

Leave a Reply

You must be logged in to post a comment.