Nel mondo dello sviluppo software la programmazione orientata agli oggetti è un paradigma di programmazione introdotto alla fine degli anni ‘60 con il Simula e seguito negli anni ‘70 da Smalltalk. In seguito è diventato il paradigma dominante ed oggi i linguaggi più usati che supportano l’Object Oriented sono C++, Java, Delphi, Python, C#, Perl.
I due più conosciuti ambienti di sviluppo, Java e .NET, si fondano proprio sul paradigma ad oggetti.
Tutto ciò però non significa che non sia possibile andare oltre l’oggetto e le sue regole di vita, dall’incapsulamento all’ereditarietà, dal polimorfismo all’implementazione di interfacce. In questo articolo voglio infatti parlare di un altro tipo di paradigma che in realtà non è propriamente “altro” rispetto alla Programmazione Object Oriented, diciamo che ne estende alcuni concetti osservando la problematica dello sviluppo software da un altro punto di vista.
Bisogna ora focalizzare l’attenzione su due termini piuttosto generali: “aspetti” e “questioni”. Soffermiamoci sul secondo termine: le questioni. Ora come termine italiano dice poco, in realtà in inglese si parla di “concerns” che è una parola difficilmente traducibile mantenendo inalterato il concetto proprio. Il verbo inglese “to concern” (concernere) significa – appunto – “qualcosa che riguarda qualcos’altro”, se vogliamo riportare tutto nell’ambito dello sviluppo software, possiamo pensare al codice che stiamo scrivendo come a quel qualcosa che riguarda l’esecuzione di alcune funzionalità richieste.
Ci stiamo appunto avvicinando alla questione. Il nostro programma ha quindi dei requisiti funzionali, deve fare qualcosa, in realtà spesso deve fare molte cose; tutte queste cose, se le si guardano bene, possono però essere raggruppate in varie categorie, ognuna di queste categorie può essere vista come una “macro-questione”, cioè una macro-funzionalità.
Questo è il primo passo da cui è nato tutto: cercare di vedere un programma come un contenitore di questioni (o caratteristiche, funzionalità, comportamenti) ognuna delle quali ben definibile e quindi ben separate l’una dall’altra. Questa separazione delle questioni (SOC, Separation of Concerns in inglese) è nata come risposta alla manutenibilità di un programma: se fosse possibile separare nettamente i vari comportamenti di un programma, diventa più facile poi intervenire su un singolo comportamento.
Banale tutto ciò: sono almeno 30 anni che si sviluppa software in questo modo. In realtà non è così banale in certi particolari ambiti: non sempre suddividere i comportamenti di un programma tramite l’uso di oggetti è possibile; ci sono alcuni di questi comportamenti che hanno bisogno di particolari accorgimenti per potere essere definiti e separati da tutto il resto.
Prima di procedere ad un esempio vediamo il secondo termine: aspetto. In realtà bisognerebbe ripetere quanto detto sopra per spiegare anche questo termine. Il motivo di averlo usato deriva dal fatto che il paradigma di sviluppo di cui stiamo parlando si chiama AOP: Aspect Oriented Programming (Programmazione Orientata agli Aspetti) ed è quel paradigma di sviluppo che tende a separare le “questioni” non tramite oggetti, bensì tramite aspetti.
Facciamo un esempio: classicamente l’esempio che si fa quando si parla di AOP è la transazione. Una delle questioni che spesso devono risolvere i programmi che scriviamo è quello di tenere traccia delle transazioni (ad esempio su base di dati) ognuna composta da una serie di operazioni atomiche che però devono essere annullate tornando allo stato precedente se solo una di queste fallisce facendo fallire a sua volta la transazione stessa. Se ci fate caso spesso si parla di aspetto transazionale, indicando con questo il fatto che esiste un ben preciso ambito di operatività del programma che deve gestire le transazioni.
Se l’obiettivo è quello di separare questa funzionalità da tutto il resto in modo che sia facilmente mantenibile ci si accorge che non è poi così immediato. In un’architettura classica in cui abbiamo tutta una serie di oggetti di business che si occupano di tutte le operazioni a livello di DB, facilmente potrebbero essere coinvolti decine di questi oggetti, ognuno tramite la sua operazione particolare. La tanto decantata manutenibilità dell’aspetto transazionale va a farsi benedire: la transazione è spalmata su tutti questi oggetti e verificare dove ad esempio si celasse un bug diventa un’impresa veramente ardua.
Ma si potrebbe fare un altro classico esempio, quello del logging: durante la vita di un programma c’è quasi sempre bisogno di fare un log, ma le fonti di log sono gli oggetti stessi, quindi la funzionalità di logging delle nostra applicazione viene spalmata su tutte le classi che abbiamo realizzato. Modificare in un secondo momento le modalità di log di un’applicazione significherebbe modificare molte classi. Esistono oggi vari framework che facilitano lo sviluppo delle sopracitate funzionalità (facendo riferimento al logging per esempio come non citare Log4j).
Questi esempi servono ad illustrare come alcune questioni (ed ecco che ritorna questo termine) centrali delle nostre applicazioni, spesso per loro natura sono spalmate su tutta l’applicazione, e cercare di separarle in un ben preciso ambito non è così facile: ognuno degli oggetti coinvolti deve, nei propri metodi, contenere codice in grado di gestire il suddetto problema; si viene così a creare, nella stesura del codice, una ridondanza non necessaria. Inoltre gli oggetti modificati a tale scopo diventano più complessi e quindi diminuisce la manutenibilità del programma.
Quindi parliamo di AOP. L’Aspect Oriented Programming nasce con lo scopo di risolvere problemi di questo tipo. Gli aspetti modellano le problematiche trasversali agli oggetti stessi, ossia compiti che nell’ OOP tradizionale sono difficilmente modellabili. Ma AOP non deve essere utilizzato come il prezzemolo una volta che se ne diventa pratici, come in tutte le cose il troppo stroppia e va sempre valutato bene quando usarlo o meno.
AOP cerca di risolvere le problematiche di cui sopra tramite appunto gli aspetti (l’aspetto della gestione delle transazioni, l’aspetto del logging, ma anche l’aspetto della gestione della sicurezza, ecc.): il modello utilizzato è quello dei “punti di connessione” (o JPM: Join Point Model). Questo modello definisce tre concetti:
i punti di connessione (Join Points) ossia i punti del codice dove l’aspetto in oggetto interagisce con il resto del programma, cioè dove l’aspetto viene richiamato ed applicato;
una metodologia per selezionare una serie di punti di connessione: in generale una sorta di motore di query che permetta di specificare dei criteri per selezionare solo una parte di tutti i punti di connessione specifici dell’aspetto in oggetto; l’insieme dei punti di connessione risultanti dalla query viene chiamato “pointcut” (difficilmente traducibile, letteralmente sarebbe “punto di taglio”);
un modo per incidere sul comportamento del programma una volta richiamato l’aspetto: questo è il fine ultimo di AOP e cioè poter influenzare il programma di base a seconda delle condizioni. In genere questa interazione dell’aspetto sul programma viene detto “advice”, traducibile con “notifica” o più letteralmente “avviso”.
Il fine ultimo di AOP (quindi di ogni framework che offre AOP) è quello di offrire un metodo per iniettare i vari advice, propri di ogni aspetto, nel programma di base tramite i punti di connessione; tutto ciò ovviamente al fine di poter influire sul comportamento del programma di base (questo metodo viene detto “weaving” che potremmo tradurre come “intrecciatura”, quindi AOP offre il metodo di intrecciare gli aspetti con il programma di base). I vari framework che offrono AOP si differenziano soprattutto sulle modalità di implementazione pratica del weaving sollevando a volte dubbi sul fatto che sia veramente AOP e non piuttosto un surrogato.
Il risultato di AOP, se prendiamo gli esempi proposti prima, è quello di poter definire dei “moduli” (gli aspetti) di logging o di gestione della security, e di iniettarli nel programma di base tramite alcuni punti di connessione, in modo da poter influenzare l’esecuzione di quest’ultimo. Poter definire dei moduli in qualche modo separati aumenta notevolmente la manutenibilità di questi perché evita che questi vengano spalmati sull’intera applicazione, ma va anche nella direzione di una maggiore separazione in componenti.
Un programma aspect-oriented quindi, è costituito essenzialmente da due insiemi di costrutti: gli aspetti e gli oggetti. Gli aspetti sono delle entità esterne agli oggetti che osservano il flusso del programma generato dalle interazioni tra oggetti, modificandolo quando opportuno o aggiungendo nuove funzionalità. Se paragonassimo gli oggetti a degli attori, potremmo dire che gli aspetti sono degli spettatori, cioè delle entità che osservano le azioni in corso nel programma senza esservi direttamente coinvolti, ma degli spettatori un pò particolari, visto che in determinate circostanze salgono sul palcoscenico e partecipano alla rappresentazione.
In definitiva l’AOP complementa l’OOP iniettando codice in punti ben precisi di un’applicazione per aggiungere comportamenti addizionali ad un oggetto in modo dinamico: gli advice possono essere applicati prima, dopo e intorno al joinpoint, e a volte è anche possibile “decorare” un oggetto con azioni a run-time senza impatti per l’oggetto decorato. Il bello di tutto questo è che il sistema alterato con gli Aspect è totalmente inconsapevole della loro esistenza: un comportamento del genere ricorda molto ciò che fanno i virus: inseriscono tra le istruzioni di una applicazione un’istruzione di salto alla prima linea di se stessi, ed alla fine mettono un altro salto al punto del codice che avevano interrotto; in questo modo nessuno può accorgersi di nulla ma il comportamento del programma è così alterato. Ovviamente tutto ciò fa capire quanto sia potente la programmazione Aspect Oriented, e come bisogna saperla dosare ed utilizzare per non combinare disastri nelle applicazioni.
Ma non è tutto oro quel che luccica: se AOP rende più semplice l’architettura e la strutturazione di un’applicazione, di contro può rendere il debug un vero inferno in quanto a run-time può diventare molto difficile seguire la sequenza delle istruzioni quando i punti di connessione diventano molto numerosi.