Perche‘ un concetto sostanzialmente corretto di framework viene spesso implementato nel modo sbagliato? Una riflessione sui framework nei progetti IT sulla base di molte esperienze dirette “sul campo”.
Introduzione
In quanto consulente IT, “visito” molte ditte come professione: be’, praticamente tutti progetti ai quali ho partecipato vertevano il loro sviluppo su un framework. Spesso il framework era fatto in casa e altre volte acquistato in blocco da qualche fornitore.
Come architetto ho sempre trovato che il concetto di framework era sostanzialmente corretto, ma la realizzazione purtroppo non è mai stata all’altezza della responsabilità affidata a tale elemento. In questo articolo vorrei quindi spiegare quali sono secondo me gli errori legati a una interpretazione sbagliata di tale concetto.
Che cosa è un framework?
Prima di tutto diamo una definizione di framework in modo tale da ragionare su alcuni elementi oggettivi in un articolo che è essenzialmente una riflessione puramente soggettiva e quindi facilmente criticabile.
In inglese “framework” significa “telaio” o “struttura”.
In informatica ci si aspetta quindi che un framework sia uno dei pilastri su cui la propria applicazione viene costruita. Ma quali compiti o responsabilità vengono affidate a questo elemento?
Se pensiamo ai framework che normalmente usiamo, possiamo dire che di solito questi nascondono al loro utilizzatore alcune complessità e riducono la “pila delle conoscenze” necessarie per implementare un determinato requisito; per esempio Hibernate per il Mapping O/R, il supporto cross database, …
Questi framework quindi forniscono un reale vantaggio a tutti gli sviluppatori che possono “dimenticarsi” di alcuni aspetti tecnici “complicati” e possono, di conseguenza, indirizzare la loro e la nostra formazione verso altri problemi (o per meglio dire verso nuovi problemi).
Questo infatti è uno degli aspetti cruciali: un framework di successo implementa un requisito comune in modo soddisfacente e automatico (o semiautomatico).
Per fare questo il requisito deve essere stato studiato e discusso dalla comunità dei tecnici e devono essere state formulate delle strategie vincenti e convincenti per la sua risoluzione.
Quando il requisito non è comune, ma legato al dominio della propria applicazione, allora il problema è diverso poiche’ difficilmente si potrà trovare una soluzione già implementata e qui noi architetti software abbiamo la possibilità di creare la nostra opera suprema… o il male assoluto…
Critica e apologia al concetto di framework
La critica non è tanto al concetto di framework quanto alle sue “incarnazioni”. Se fatti bene possiamo pure usarli, ma in generale non mi è mai capitato di vedere un framework “fatto in casa” che fosse anche “fatto bene”.
Piuttosto ho sempre trovato qualcosa di estremamente esotico … più orientato a nascondere dettagli di poco conto piuttosto che attività complesse e quindi ne risulta che le normali attività vengono svolte in un tempo maggiore e con maggiore frustrazione poiche’ non è possibile trovare documentazione a proposito dei problemi che siamo stati noi stessi a creare e che “in natura” non erano presenti.
Al contrario, invece, le attività complesse diventano ancora più difficili poiche’ si ripercuotono su tutti gli sviluppatori e ognuno di loro troverà delle soluzioni indipendenti piuttosto che essere guidati verso un unico design: la qualità quindi si abbassa e i costi di manutenzione del prodotto invece aumentano.
Ma allora cosa fare ? I progetti di certo non aspettano e noi siamo tenuti a dare risposte. Cosa possiamo inventarci al posto del framework? Prendendola in maniera ironica possiamo dire che esistono le seguenti alternative:
- Adottare la filosofia “Wait and Download”
- Scrivere un cattivo framework
- Scrivere un buon framework
La prima alternativa mi è stata illustrata da un cliente il quale sostiene che, generalmente, nel giro di poco tempo salta fuori qualcuno nel mondo che risolve quel problema al posto tuo. Tenuto conto che in definitiva è molto più costoso pagare qualcuno per fare danni che pagarlo per non lavorare (cioè per non far danni), i manager dovrebbero sempre tenere presente questa opzione…
La seconda alternativa rappresenta ciò che succede normalmente: ed è anche la ragione di questo articolo.
La terza alternativa invece è quello che si dovrebbe fare. Ma come si scrive un buon framework? La risposta è “non lo so”; tuttavia ci sono alcuni indicatori che ci possono aiutare a capire se quello che stiamo facendo è un prodotto utile oppure no e nel corso dell’articolo illustrerò alcuni “anti pattern” che ho visto più volte realizzati.
Uno sguardo agli antipattern più diffusi
Diamo di seguito uno sguardo a una serie di antipattern relativi ai framework che si riscontrano solitamente nell’analisi dell’architettura dei vari sistemi. Probabilmente alcuni di questi esempi rappresenteranno situazioni che i nostri lettori hanno riscontrato qualche volta.
1 – New Entry
Sintomo
È stato deciso di creare un nuovo framework applicativo perche’ quelli presenti sul mercato non hanno le funzionalità che ci servono (tutte o in parte).
Problema
Creare un nuovo framework di per se’ non è sbagliato, ma è molto difficile non trovare nulla sul mercato che faccia al caso vostro. In generale questa decisione è ben vista da tutti i fornitori perche’ questo porta a loro molto lavoro ma le persone “illuminate” sanno che lavorare è fare qualcosa di utile; il resto appartiene alla categoria dei commercianti che devono vendere a prescindere dalla motivazione.
In ogni caso, quando scegliamo di scrivere un nuovo framework, ci troveremo di fronte a uno dei seguenti problemi:
- sarà molto costoso farlo
- sarà molto costoso mantenerlo
- sarà obsoleto nel giro di pochi anni
Soluzione
La ovvia soluzione a questo antipattern è che non dobbiamo scrivere un nuovo framework! Tuttavia dobbiamo risolvere i problemi che ci avevano condotto all’idea di scriverne uno e quindi le strade maestre sono le seguenti:
- ridiscutere i requisiti per comprendere meglio lo scenario e identificare soluzioni alternative a quelle ideate in precedenza;
- rivedere la propria architettura e il proprio design per capire se il problema è scomponibile in problemi più piccoli o se addirittura possiamo eliminarlo in modo definitivo (spesso è possibile);
- usare per quanto possibile i prodotti sul mercato per ridurre l’effetto dei nostri problemi sapendo che non li abbiamo eliminati del tutto.
L’effetto di queste soluzioni sarà una sostanziale riduzione del budget previsto per ottenere il risultato atteso (o un risultato analogo). Tuttavia la decisione finale potrebbe comunque essere quella di scrivere il framework e quindi i prossimi antipattern ci aiuteranno a tenere sotto controllo tale elemento.
2 – My Way
Sintomo
È stata deciso di creare un nuovo framework applicativo e la sua impostazione di base non fa riferimento a Pattern o Standard riconosciuti.
Problema
Diciamo pure che sul mercato non c’è nulla di pronto che faccia al caso nostro, ma la probabilità che non esistano pattern o standard che riguardano le tematiche che ci si presentano è davvero bassa. In particolare in informatica ognuno tende a creare il “proprio standard” e quindi alla fine abbiamo solo soluzioni proprietarie che sono difficili da integrare.
A questo punto abbiamo, oltre al problema di scrivere e mantenere un framework, anche il problema di far capire a coloro che ci lavorano sopra come funziona, perche’ non abbiamo adottato nessuna delle soluzioni descritte “in letteratura” con conseguente aumento dei costi di formazione e documentazione che verranno presto tagliati causando dei “lock-in” verso le persone che per prime hanno lavorato al nostro framework.
Soluzione
La soluzione a questo anti pattern è che dobbiamo in ogni caso fare riferimento a uno standard. Tale standard inoltre deve essere sufficientemente diffuso e studiato in modo tale da non dover produrre documentazione al riguardo, ma solo a proposito dell’implementazione che abbiamo deciso di farne.
L’uso di questi standard però non è sufficiente se non si sottopone alla comunità il proprio prodotto. Questo non deve far pensare al solito discorso relativo all’open source che tutti conosciamo. Ma è anche vero che, se sul mercato non c’era nulla di buono, allora altri useranno il nostro prodotto dandoci feedback importanti (e forse anche delle patch) poiche’ è sempre più conveniente evolvere qualcosa piuttosto che riscrivere “from scratch”.
In ogni caso almeno si creerà un minimo di comunità verso qualcosa che prima non esisteva e fornirà valore aggiunto al proprio lavoro… in fondo l’obiettivo non era vendere il framework, ma completare il progetto in modo efficace ed efficiente.
3 – Complexity over Complexity
Sintomo
Un framework nasce essenzialmente per ridurre la complessità di un’operazione. Il nostro software, invece, non aiuta il lavoro di tutti i giorni e quindi stiamo andando nella direzione sbagliata.
I tipici sintomi di questo antipattern sono:
- la crescita esponenziale dei file XML o del loro contenuto (maneggiati da persone)
- la generazione automatica di codice fatta esplicitamente dal programmatore
- sistemi di build particolarmente complicati
Problema
Scambiare un tipo di complessità (per esempio dovuta a fattori tecnologici) con un altro tipo di complessità (dovuta al nostro framework) peggiora la situazione generale. Nel primo caso, infatti, troveremo aiuto nella “comunità” nel secondo invece saranno solo problemi nostri perche’ il nostro framework lo abbiamo solo noi.
Soluzione
I programmatori devono avere a disposizione strumenti e API facili da usare cosicche’ si possano concentrare sui requisiti di business e non sui dettagli tecnici o tecnologici (che invece sono compito di architetti e designer).
La cosa più facile da usare è quella che si usa senza saperlo ma agisce dietro le quinte e non impone regole particolarmente restrittive al lavoro di tutti i giorni. Un framework non deve imporre particolari sessioni di formazione per usarlo, estenderlo o mantenerlo.
Alcune “raccomandazioni” possono venire incontro nella costruzione del nostro framework:
- è ammesso al più un file di configurazione (ma sarebbe meglio nessuno);
- i file XML non devono essere usati da persone ma solo da altri programmi;
- la generazione di codice deve essere fatta senza che il programmatore debba svolgere attività esplicite;
- i sistemi di build (e in particolare le procedure di rilascio) non devono diventare complesse e il release manager non deve avere compiti “manuali” lunghi e articolati;
- scrivere il framework usando una metodologia TDD aiuta a mantenere le interfacce pulite e facili da utilizzare;
- il codice di framework deve rimanere piccolo e isolato.
4 – Helper Class
Sintomo
Inseriamo nel codice di framework delle classi di utilità (o helper class) che aiutano gli utilizzatori del framework a scrivere codice più compatto e leggibile.
Problema
Sento già un’orda di persone che si chiedono cosa ci sia di male… la risposta è “niente”; tuttavia vorrei spiegare meglio perche’ questo è un sintomo che il nostro framework sta andando nella direzione sbagliata.
Spesso deleghiamo al framework una serie di compiti che non dovrebbero appartenergli. Ho affermato che un framework è una struttura (portante) della nostra applicazione. Se questo è vero vuol dire che svolge compiti particolarmente importanti e lascia dettagli di poco conto ai programmatori. Le classi di utilità, invece, dovrebbero nascondere solo dettagli di poco conto o attività che altrimenti sarebbero ripetitive (ma non complesse o strategiche).
In questo senso le API di framework hanno una dignità ben maggiore e non devono essere “inquinate” da compiti minori. Inoltre per questo tipo di API (le helper class) esistono concetti ben più antichi come le “shared library” (o “common library”) che possono essere distribuite per essere usate da tutti gli sviluppatori ma non fanno parte del codice di framework.
D’altro canto invece il framework può fare pesantemente uso di helper class che svolgono compiti comuni.
Soluzione
La soluzione in questo caso è abbastanza semplice e deve essere applicata a livello di design. Si deve fare molta attenzione quando si suddividono le funzionalità offerte dal framework e quelle più comuni che potrebbero essere offerte da una normale libreria neutra rispetto al problema che il framework intende risolvere.
Facendo attenzione a questa suddivisione nasceranno delle librerie di utilità usate sia dal framework che dagli altri elementi della nostra applicazione. Il grande vantaggio è che, se un elemento dell’applicazione vuole usare le helper class, non deve dipendere dall’intero framework e quindi il nostro design risulta più pulito e sarà anche più semplice “impacchettare” e distribuire il nostro software.
5 – Evolution
Sintomo
Il codice del nostro framework evolve ogni volta che vengono inserite nuove funzionalità nella nostra applicazione.
Problema
Il codice di un framework risolve un problema (complesso) specifico o una categoria di problemi (comuni o generici), per cui non è lecito aspettarsi che evolva ad ogni evoluzione dell’applicazione.
Se invece assistiamo ad una evoluzione del framework ad ogni evoluzione dei requisiti dell’applicazione, vuol dire che il framework si sta occupando di molti aspetti eterogenei e probabilmente legati al business del nostro software. Questo è sbagliato perche’ i framework sono elementi di infrastruttura e aiutano gli sviluppatori a implementare i requisiti di business,ma ne sono completamente slegati.
Soluzione
La soluzione in questo caso non è semplicissima, perche’ probabilmente architettura e design dell’applicazione andrebbero rivisti per separare il codice di infrastruttura da quello di business. Un refactoring su così vasta scala non è facilmente realizzabile e quindi quello che si può fare per contenere i danni è prevedere una “involuzione” del framework in modo da affidargli compiti di volta in volta meno numerosi e più specializzati.
6 – I’m a tier
Sintomo
Il framework rappresenta un layer della nostra applicazione.
Problema
Questo scenario è sbagliato ed è più corretto dire che possiamo costruire un layer usando un framework. Anche se a prima vista sembra solo una distinzione sofistica, non è vero e si tratta di una differenza sostanziale.
Infatti pensiamo a un framework molto diffuso come Hibernate. Esso è usato per implementare il layer di persistenza delle nostre applicazioni delegandogli compiti molto particolari come il Mapping O/R, e così via. Tuttavia Hibernate non è legato a un’applicazione specifica e quindi ognuna di esse implementerà il proprio layer di persistenza fornendo classi e interfacce specifiche.
Soluzione
In un layer ben progettato dovrebbe essere possibile sostituire il framework usato con un altro, senza stravolgere gli altri layer, che invece continuano a vedere classi e interfacce che noi abbiamo messo a disposizione.
7 – “I’m everywhere”
Sintomo
Un framework che lavora su tutti i livelli dell’applicazione.
Problema
Se un framework lavora su tutti i livelli della nostra applicazione, vuol dire che sta affrontando problemi eterogenei e quindi non va bene.
Per mantenere il codice di infrastruttura piccolo, efficace ed efficiente è necessario che questo sia anche specializzato. Inoltre un framework che lavora “cross” su tutti i livelli diventa estremamente invasivo e la sua sostituzione, evoluzione e manutenzione sarà problematica.
Soluzione
La soluzione a questo antipattern è mantenere gli elementi di infrastruttura all’interno di un singolo layer, anche a costo di dover scrivere più framework (uno per ogni layer). Questo lascia la possibilità di fare refactoring sui singoli layer senza stravolgere il design dell’intera applicazione.
Conclusioni
Al termine di questa riflessione posso affermare che non sono contrario al concetto di framework, ma che sono contrario al ruolo architetturale che normalmente gli viene affidato. Un framework risolve problemi complessi o categorie di problemi che poi non devono essere più affrontati e in generale hanno scarso bisogno di nuove funzionalità quando il problema è stato evaso completamente.
I framework non forniscono componenti riusabili o funzionalità di business, ma piuttosto sono in grado di gestire componenti creati in base a uno standard: sarà poi il componente a occuparsi della funzionalità specifica del nostro dominio.
Se costruiti in base a questi principi, i framework saranno generalmente piccoli e isolati nel loro ruolo e aiuteranno gli sviluppatori in modo concreto: quindi costerà poco produrli in casa (quando non ci sia altra scelta) e sarà più facile sostituirli con quelli standard quando si presenterà l’occasione (anche se l’esigenza a quel punto sarà decisamente meno sentita).