Sunday, September 10, 2006 

Some notes on AOP

Almost 20 years ago, Peter Wegner identified some defining characteristics of Object Technology, like the difference between an object-based and an object-oriented language ("Dimensions of object-based language design", OOPSLA 1987).
More recently, authors have tried to define similar properties for Aspect-Oriented languages. An interesting work is "Aspect-Oriented Programming Is Quantification and Obliviousness", from Robert Filman and Daniel Friedman (published in "Aspect-Oriented Software Development", Addison Wesley, 2005, but probably available on the net as well). I do not agree on all their conclusions (like "AOP is not for singletons", which is somewhat confusing separation of concerns with separation of crosscutting, pervasive concerns), but the idea of Quantification and Obliviousness as the cornerstones of AOP are quite well-grounded.
The authors discuss different type of Quantification (static and dynamic) but do not dissect Obliviousness any further. I think we have two types of obliviousness as well: caller and callee. This is also somewhat reflected in AOP languages (for instance, in AspectJ we have both call and execution pointcut expressions) and emerges in library-based attempts to bring AOP to traditional OO languages (with explicit factories preventing caller obliviousness).
[As an aside, the paper mentions rule-based systems; I've already said a little, and I would have more to say, about the great inheritance of artificial intelligence in OOP and AOP, but hey, it's getting late :-)].
Obliviousness can be quite limiting if applied to both the caller and the callee. In many cases, good separation of concerns can be obtained by callee obliviousness alone. That means we can also live with library-based AOP in many (but not all) cases. More on this another time; meanwhile, another interesting paper is "Spectators and assistants: Enabling modular aspect-oriented reasoning" by C. Clifton and G. Leavens, Technical Report 02-10, Iowa State University, October 2002. In a sense, only spectators can be considered as totally oblivious aspects (for both caller and callee). Interestingly enough, the strong focus on spectators-like aspects in many introductory papers is giving many the false impression that AOP is only useful for logging. Many papers are also focused on container-like facilities (security, sinchronization, remoting), and we should definitely move beyond that too...
Comments:
Un altro articolo interessante e’ quello di Gregor Kiczales (uno dei primi progettisti di AspectJ e sicuramente uno dei teorici dell’AOP) e Mira Mezini, Aspect-Oriented Programming and Modular Reasoning, ICSE’05. Gli autori rispondono proprio a Clifton e Leavens ma anche ad altri difendendo il principio della Obliviousness. Essi sostengono che, e’ vero, l’AOP ha un costo: “the interface of a module is context dependent. We must know the set of modules with which a given module will be deployed to know its interface.” Tuttavia una volta accettato questo costo sarà possibile ragionare in modo modulare anche per quegli aspetti “crosscutting” i quali nell’alternativa non-AOP non sono per nulla modularizzati, non hanno interfaccia e sono sparpagliati in molti (o tutti i) moduli richiedendo l’analisi dell’implementazione di quei moduli in caso di modifiche.
Credo però che il suo post non vuole mettere in discussione la validità di questo principio bensi’ mitigarlo in vista di una più completa “separation of concerns” (e non solo la modularizzazione dei crosscutting concern). Approcci come quelli tentati da HyperJ (IBM) e FeatureC++. Risulta utile ad esempio avere due gerarchie parallele di classi che modellano i ruoli degli impiegati in un’azienda visti da due punti di vista diversi, quello di chi calcola gli stipendi e quello di chi invece tiene conto degli skills. Secondo le specifiche di un certo progetto si possono mettere insieme i due concern o meno. Ma anche questi approcci sembrano tutti avere un approccio tutto-o-nulla. Cioè callee e caller obliviousness. Si demanda ad un file di configurazione o ad un pezzo di codice la responsabilità di mettere insieme concern trasversali o “paralleli” ma per l’intera applicazione (a meno della possibilità di identificare flussi di esecuzione differenti).
Il suo post “Structural Conformance in C#” mi faceva pensare ad un’applicazione della sua libreria (probabilmente modificata) alla separation (e ricomposition!) dei concerns. Anche se in questo caso si tratterebbe di farla istanza per istanza se capisco bene.
Continuo a chiedermi se sia utile poter distinguere tra istanze diverse di una classe e applicare advice solo ad alcune. Insomma poter affiancare alla AOP con obliviousness totale anche quella con obliviousness solo del callee. Mi pare di si’ anche se dopo una prima analisi di alcuni casi ho sempre trovato che la obliviousness totale aveva maggiori vantaggi. Ma finora non ritengo di aver trovato una risposta che mi sembri sufficiente.
 
Michelangelo, ci diamo del tu? Inizio io :-).
Il tuo commento e' ricco di spunti molto interessanti, ci sarebbe da parlarne per ore. Mi limito pero' ad approfondire due punti:

- proprio nell'esempio che fai di due gerarchie parallele (che si sposano meglio, come indichi, con un approccio ad iperspazi o a mixin layers, rispetto ad uno basato su pointcuts ed advices) porta in modo naturale ad avere gran parte del codice interessato alla sola gerarchia degli stipendi XOR :-) alla gerarchia degli skills. Questo e' alla base della separation of concerns anche dei caller, e della caller obliviousness sulla composizione dei concern.
Tuttavia e' piu' che plausibile che ad un certo punto alcune parti di codice (ovviamente non riusabili in altri contesti, ma il codice custom esiste sempre!) siano interessate ad entrambi gli aspetti, all'interno di un unico caller. A quel punto entrambi gli aspetti devono essere visibili, espliciti, e non impliciti. Le -ilities sono facilmente implicite, ma i concern non sono solo le -ilities (altrimenti, come alcuni sostengono, diventa solo una questione di fare una virtual machine piu' furba, e lasciamo perdere il nuovo paradigma).

- l'articolo di Kiczales che citi e' molto interessante ma, per me, totalmente deprimente :-(. Mi fa pensare ad una delle classiche situazioni in cui si difende la lettera anziche' la sostanza. In fondo quel che dice e' che cambiando la definizione di modular reasoning, possiamo dire che l'AOP permette il modular reasoning.
Purtroppo la sua tesi fa acqua da tutte le parti, ed anche l'esempio e' scelto in modo piuttosto mirato a far notare solo i lati positivi della scelta. Propongo un banale scenario alternativo tra poco.
La falla principale dell'approccio proposto e' che dimentica il perche' delle tecniche. L'AOP e' solo una tecnica, cosi' come l'OOP e' solo una tecnica. Se dimentichiamo i principi che le guidano, e prima ancora di questi i valori che li informano, siamo fregati.
In concreto, la separation of concern e' un principio sostenuto da diversi valori. Uno di questi valori e' che sarebbe bello creare un componente X, usarlo nei sistemi S1 ed S2, ma poter ragionare su X in modo largamente indipendente da S1 ed S2. Anche perche' chi sviluppa X puo' non conoscere chi sviluppa S2 ed S2.
Ecco perche' e' fallace pensare che si possa ritornare al ragionamento modulare solo dopo aver considerato X nel contesto di S1, poi X nel contesto di S2, ecc. In effetti, in tutto il suo articolo il concetto di riuso si e' perso, perche' la sua strategia rende difficoltoso, se non impossibile, un riuso ed una manutenzione separata dei componenti.
Nello specifico, basta pensare a cosa succede se qualcuno che usa la classe Point in un sistema S1 privo di pointcuts esegue modifiche basate sull'interfaccia aspect-aware di Point in S1 (che e' l'unico sistema che conosce). In questo caso l'interfaccia aspect-aware coincide con quella tradizionale. Quindi posso allegramente rompere il mio invisibile contratto con l'interfaccia aspect-aware di Point in S2, di cui non sono a conoscenza.
Questa sarebbe la versione AOP del problema della Fragile Base Class, esteso pero' a tutte le classi. Direi proprio che non ci siamo :-)).

Come ho fatto notare in altre occasioni, l'AOP e' decisamente immatura sotto tanti aspetti. Uno dei motivi e' che ci si avvita su tecniche sempre piu' complesse (ed espressive) al fine di far vedere cose mirabolanti. Ma manca, e si vede, un occhio al design dei sistemi, una visione che ritorna ai principi ed ai valori complessivi del buon design, senza spingere in una sola direzione sbilanciando equilibri delicati (cosa che capita invece di frequente quando la ricerca si fa troppo di nicchia e perde di vista il resto del mondo). Manca, a mio avviso, una eleganza di fondo cui non si rimedia aggiungendo costrutti.
Il buon Dijkstra diceva: About the use of language: it is impossible to sharpen a pencil with a blunt axe. It is equally vain to try to do it with ten blunt axes instead.

L'AOP e' un paradigma di estremo interesse, e piu' volte nell'arco del 2005 mi sono detto (presuntuosamente :-)) che dovrei dare il mio contributo alla causa :-). Ma se non si esce dagli esempi giocattolo e non si pensa ad un AOD che guidi l'AOP, a mio avviso non si andra' in una gran bella direzione...

Grazie per gli spunti, anche su FeatureC++ che e' meno noto di AspectC++. Prima o poi torno sull'argomento!
 
Certo che possiamo darci del tu!!! E sono contento che i miei commenti ti siano stati utili.

La questione della obliviousness.

Dici: "Tuttavia e' piu' che plausibile che ad un certo punto alcune parti di codice (ovviamente non riusabili in altri contesti, ma il codice custom esiste sempre!) siano interessate ad entrambi gli aspetti, all'interno di un unico caller. A quel punto entrambi gli aspetti devono essere visibili, espliciti, e non impliciti."

Sembra plausibile anche a me. A meno che anche i caller siano divisi (o si possano dividere) su iperpiani secondo i concern. Se come tu sostieni, “program design is language design” (e l’idea a me piace parecchio) allora posso pensare ad ogni iperpiano come ad un sotto-dominio applicativo con il suo sotto-linguaggio. E per ciascun iperpiano risolvo quella parte del problema applicativo. Ad es. il calcolo degli stipendi e la creazione del report degli skill sono due sotto-problemi diversi che possono essere affrontati ciascuno sul suo iperpiano. Non sono sicuro che questo ragionamento scali in tutte le direzioni. Al top-level avro’ bisogno di “cucire” i piani. Ma cosa succede su un iperpiano? C’e’ la necessità di cucire aspetti a grana più fine?

Un’altra obiezione al mio ragionamento potrebbe derivare dalla decisione del manager di pagare gli stipendi sulla base degli skill! Mi viene in mente il mio prof. di analisi matematica, e penso che potrebbe esistere “un rimescolamento delle carte”, come lui diceva, che riporta anche in questo caso la situazione a due iperpiani che non si secano. Ma qui la strada mi pare diventi lunga.

Sulla questione della modular reasoning non posso che riconoscere che l’AOP crea delle difficolta’. Parecchi, tra cui lo stesso Kiczales, sostengono che ci siano da fare ancora molti passi avanti riguardo la granularita’ dei joint point: adesso si fa riferimento a dei metodi domani si potrebbe poter fare riferimento ad elementi più complessi, relativi al dominio applicativo.

Non ho dato nessuna risposta ai quesiti che hai posto, perche’ non ne ho una. Attendo un tuo intervento futuro più esteso sulla questione! Soprattutto la tua “eleganza di fondo cui non si rimedia aggiungendo costrutti” mi pare molto interessante.
 
Direi che e' sempre possibile trovare esempi misti (come il tuo, o tutte le variazioni sul tema) in cui mi servono elementi da entrambe le gerarchie. In effetti nella teoria degli iperspazi non si richiede che non ci siano overlapping concerns (da cui il concetto di merge class che manca invece in Aspect/J), ma non per questo le cose mi pare che diventino improvvisamente facili...

Prima o poi torno su questi argomenti, ci sono molte cose su cui devo ancora pensare a fondo ;-)
 
This post has been removed by a blog administrator.
 
Post a Comment

<< Home