Aspectgeoriënteerd programmeren

Aspectgeoriënteerd programmeren (AOP, of Aspect Oriented Development, AOD) is een uitbreiding op de bekende manieren van programmeren, waarbij het mogelijk wordt om een stuk code A "in te lassen" in een ander stuk code B zonder dat B een zichtbare verwijzing heeft naar A. Hoewel deze uitbreiding toegepast kan worden op alle bekende vormen (paradigma's of paradigmata) van programmeertalen, past het concept qua structuur het beste bij het objectgeoriënteerde paradigma.

Het is belangrijk op te merken dat aspectgeoriënteerd programmeren niet een paradigma is dat andere paradigmata vervangt. AOP is bedoeld als een strikte uitbreiding op bestaande programmeertechnieken.

Overzicht

Aspectoriëntatie is bedoeld als antwoord op een probleem waar alle "klassieke" paradigma's mee kampen, namelijk de vraag hoe om te gaan met de zogeheten crosscutting concerns: handelingen die door het hele programma heen uitgevoerd moeten worden. Typische voorbeelden hiervan zijn logging en beveiliging: op vrijwel ieder punt in een gemiddeld programma bestaat de behoefte aan de mogelijkheid om zaken naar een log weg te schrijven om fouten te traceren en zeer veel programma's moeten op verschillende punten controleren of de gebruiker van het programma wel gerechtigd is om de opgevraagde handeling uit te voeren. De "klassieke" paradigma's hebben hiervoor geen makkelijke oplossing en vallen daarom terug op het meerdere malen opnemen van precies dezelfde code op iedere plaats waar dezelfde handeling uitgevoerd wordt. Met als resultaat dat een verandering aan de uitvoering van een dergelijk crosscutting concern betekent dat door het hele programma heen code moet worden aangepast.

Als oplossing hiervoor biedt AOD (Aspect Oriented Development) de mogelijkheid een stukje code te schrijven dat op een groot aantal, door de programmeur te definiëren punten, ingevoegd wordt. Dit invoegen gebeurt zonder dat de geschreven code waarin ingevoegd wordt, aangepast wordt op de invoeging (dat er op een gegeven plaats iets ingevoegd wordt, kan men dus niet zien aan de programmacode). Om dit voor elkaar te krijgen, geeft de programmeur extern aan de programmacode een aantal punten op waarin bepaalde code ingevoegd dient te worden. Een apart programma neemt de code van de programmeur en zijn aanwijzingen over invoegen en stelt daarmee een nieuw programma samen waarin de juiste code op de juiste plaats ingevoegd is.

Er zijn in principe twee tijdstippen waarop het weven (het samenvoegen van code) kan plaatsvinden: tijdens de compilatie van code naar programma (dit wordt statisch weven genoemd) en tijdens het uitvoeren van het gecompileerde programma (dynamisch weven). In het eerste geval leeft de AOD-uitbreiding op de bestaande techniek in de compiler, in het tweede geval wordt de uitbreiding extern aangebracht via een preprocessor.

Geschiedenis

Om aspectoriëntatie mogelijk te maken, moet men beschikken over een aantal verschillende concepten (die verderop behandeld zullen worden). Deze concepten zijn allemaal los van elkaar ontwikkeld en bestudeerd vanaf de jaren 1980. De samenvoeging van deze concepten tot "aspectgeoriënteerd programmeren" gebeurde waarschijnlijk voor het eerst midden jaren 1990 aan het Xerox PARC. Uit die hoek komt ook de term aspect, voor het eerst gehoord in 1996. Vanuit het PARC komt ook AspectJ (een project van Gregor Kiczales), een van de bekendste programmeertalen (een uitbreiding op Java) waarin statisch weven verwerkt zit.

Sinds het jaar 2000 heeft AOD een grotere bekendheid gekregen, mede door het opnemen van AspectJ in de opensourcegemeenschap als onderdeel van Eclipse. Sinds 2003 is het echt 'hot' en wordt AOD op meerdere manieren geïmplementeerd, bijvoorbeeld als onderdeel van het Spring-framework door middel van dynamisch weven.

De werking

Terminologie

De introductie van aspectoriëntatie brengt zijn eigen terminologie met zich mee. Hoewel er nog niet echt "vaste regels" zijn, worden de volgende termen wel vrij algemeen gebruikt in de literatuur aangaande AOD:

Crosscutting concern
Het concept dat een bepaalde taak of handeling uitgevoerd moet worden op verscheidene plekken die verspreid liggen door de hele programmacode heen. Het idee is het beste te begrijpen als men het vergelijkt met objectoriëntatie. Binnen de objectoriëntatie worden klassen gedefinieerd die elk de verantwoordelijkheid dragen voor één klein stukje van wat het programma als geheel moet doen. Een crosscutting concern is een verantwoordelijkheid die in heel veel van dergelijke klassen speelt en dus moeilijk in een eigen klasse te vangen is.
De crosscutting concern is de bestaansreden van AOD.
Advies (en: advice)
Een stukje code dat ingevoegd kan worden in andere code. Het advies behelst normaal gesproken de statements die nodig zijn om een crosscutting concern uit te voeren.
Invoegpunt (en: joinpoint)
Een plaats in de "normale" programmacode waar een advies ingevoegd kan worden.
Puntsnede (en: pointcut)
Een verzameling invoegpunten. Aspectgeoriënteerde talen bieden altijd een mechanisme om uit de vele invoegpunten die in een programma bestaan een aantal deelverzamelingen te kiezen. Iedere deelverzameling wordt vervolgens verbonden aan een advies, met de betekenis dat dat advies ingevoegd moet worden op alle invoegpunten in die deelverzameling.
Aspect
Een analogie van de klasse. Waar een klasse echter een samenstelling is van programmatoestand en uitvoerbare code is een aspect een samenstelling van puntsneden en de adviezen die bij die puntsneden horen. Het aspect is het stuk van het programma waarin de programmeur aangeeft welke adviezen bij welke puntsneden horen.
Weven (en: weaving)
Het proces waarbij adviezen ingevoegd worden in andere programmacode, op de aangegeven invoegpunten.
Introductie (en: introduction)
Een specifieke toepassing van weven, waarbij het ingewoven advies niet zomaar een bestaand stuk code wijzigt, maar de hele code structureel uitbreidt.

Invoegpunten en puntsnede-selectie

Zoals elders opgemerkt, werkt aspectoriëntatie door het invoegen van stukken code (adviezen) in andere stukken code. De plaatsen waar adviezen ingevoegd kunnen worden, worden invoegpunten genoemd. In principe is er een invoegpunt direct voor en direct na ieder statement in een gegeven programma:

 :
 {invoegpunt 0}
 statement_0;
 {invoegpunt 1}
 statement_1;
 {invoegpunt 2}
 :

Formalisten zullen opmerken dat de locaties van invoegpunten overeenkomen met de plaatsen waar in een Hoare triple de asserties staan. Dit is uiteraard geheel niet toevallig; de assertie in het Hoare triple geeft de toestand van een programma weer tussen statements en komt dus overeen met de plaats waar een voorgaand statement geheel afgehandeld is. Vanaf dat punt kan het volgende statement beginnen en dat is dus ook precies het punt waar een alternatief statement tussengevoegd kan worden.

Het is wel van belang om op te merken dat invoegpunten aangeduid worden in termen van het statement waar ze bij staan en niet in termen van asserties in de Hoarelogica; in het voorgaande voorbeeld is de aanduiding van invoegpunt 0 dus "het invoegpunt voor statement_0" en van invoegpunt 2 "het invoegpunt na statement_1".

Het zal duidelijk zijn dat het aantal invoegpunten in een gegeven programma erg groot is: het aantal statements plus 1 (en het aantal statements in een klein programma is al gauw 10.000). Als er een advies is dat op meerdere invoegpunten ingevoegd moet worden, is het al gauw ondoenlijk om dat voor ieder invoegpunt individueel op te geven. Om die reden is het concept van een puntsnede ingevoerd: een verzameling van invoegpunten die allemaal tegelijk van een advies kunnen worden voorzien.

Puntsneden zelf kunnen gedefinieerd worden met een opsomming van invoegpunten, maar dat is meestal om dezelfde reden ondoenlijk. Daarom voorziet ieder AOD-systeem in een manier om snel hele verzamelingen invoegpunten aan te duiden (dit wordt meestal filteren genoemd). Daarbij wordt meestal gekeken naar een aantal typische soorten invoegpunten die vaak interessant gevonden worden – bijvoorbeeld bepaalde methode-aanroepen of alle statements die een bepaalde variabele van waarde laten veranderen. Het is bijvoorbeeld gemeengoed in alle AOD-systemen om een puntsnede te definiëren die bestaat uit "alle invoegpunten voor aanroepen van alle methoden wier namen met set beginnen". Dergelijke selecties kunnen heel uitgebreid worden: "alle methoden die beginnen met setA, maar alleen in klassen wier naam zelf begint met Security of Timing maar niet als die klasse overerft van klasse Simpel of als de methode aangeroepen wordt vanuit methode moeilijk()" bijvoorbeeld.

De meeste AOD-systemen implementeren dergelijke filtratie-systemen met behulp van reguliere expressies. Overigens wordt de selectie meestal gesplitst: een puntsnede wordt gedefinieerd in termen van statements, daarna wordt gezegd dat het advies ingevoegd moet worden voor al die statements. Het aanduiden van statements wordt namelijk makkelijker gevonden dan het aanduiden van de plaatsen tussen statements.

Als eenmaal een puntsnede gedefinieerd is, kan aan die puntsnede een advies worden toegekend. Dat advies wordt dan ingevoegd op alle invoegpunten in de puntsnede. Merk daarbij op dat het meestal niet verboden is dat een invoegpunt in meerdere puntsneden is opgenomen, of dat aan een puntsnede meerdere adviezen worden toegekend. Het is dan ook vaak nodig zorgvuldig op te passen dat de uiteindelijke volgorde van het uitvoeren van alle adviezen goed is (of ten minste om ervoor te zorgen dat individuele adviezen elkaar niet beïnvloeden).

Een bijzondere toepassing van AOD is om de definities van klassen en dergelijke in een werkend programma aan te passen. Dit is een toepassing waarbij niet alleen enkele, losse statements ingevoegd worden bij een invoegpunt, maar waarbij extra variabelen of zelfs hele extra methodes ingevoegd worden. Deze toepassing van AOP wordt doorgaans een introductie genoemd.

Het weven

Nadat vastligt welke invoegpunten interessant zijn (door gebruik van puntsneden) en welke adviezen erin ingevoegd moeten worden, volgt de toepassing van een weefproces om "normale" code en adviezen samen te voegen tot een werkend programma. Voor dit weven bestaan verschillende modellen die destijds opgesomd zijn door Gregor Kiczales en zijn team:

  1. Adviezen worden ingeweven in de broncode door een preprocessor, waarna het resultaat door een normale compiler gecompileerd wordt.
  2. Een normale compiler compileert alle code, waarna een apart programma het gecompileerde resultaat aanpast om het weven uit te voeren.
  3. Een aangepaste compiler compileert alle code en voert tegelijkertijd het weven uit.
  4. Alle code wordt normaal gecompileerd tot losse brokken, maar niet in elkaar gezet tot een geheel programma. Deze stap gebeurt bij uitvoering van het programma en bij het inladen van ieder blok wordt meteen het weven uitgevoerd.
  5. Alle code wordt normaal gecompileerd, geladen en opgestart. Tijdens het uitvoeren wordt bij ieder invoegpunt gecontroleerd of er een advies bijhoort en eventueel wordt dat advies uitgevoerd.

De eerste drie varianten zijn vormen van statisch weven, waarbij de weving uitgevoerd wordt als het programma vertaald en samengesteld wordt tot uitvoerbare code. De eindgebruiker merkt niets van het weven. Deze methode van weven levert over het algemeen een generieker systeem op, omdat fijnmazige weving (bijvoorbeeld op alle statements die een variabele aanpassen) moeilijker door te voeren is en veel tijd kost. Om die reden wordt dergelijke weving het liefst uitgevoerd op een moment dat de eindgebruiker het niet merkt.

De eerste methode lijkt op het macromechanisme van de programmeertaal C en past de leesbare broncode aan. Om die reden zijn er groepen die vinden dat deze methode niet "echt AOD" is.

De laatste twee methoden zijn vormen van dynamisch weven, waarbij het weven plaatsvindt als de eindgebruiker het programma gebruikt. Het nadeel van deze methode is dat het opstarten of uitvoeren van het programma langzamer wordt. Om die reden voorzien dynamische weefsystemen vaak alleen in invoegpunten waarvan maar een beperkt aantal aanwezig is en die ook nog makkelijke aanpassingen aan het programma opleveren: rond de aanroep van een methode bijvoorbeeld. Daarbij komt ook nog eens dat de meeste dynamische methoden een aangepaste omgeving vereisen; in het geval van Java een aparte JVM bijvoorbeeld om de vierde methode toe te kunnen passen. Het grote voordeel is echter dat de weving aangepast kan worden zonder het hele programma opnieuw te hoeven compileren.

De laatste paar jaren is er echter nog een methode in opkomst, bedacht door Cohen en Gil, een soort variant van de vijfde methode, die goed werkt op populaire systemen als de JVM en de .NET-omgeving. Deze methode maakt gebruik van een dynamische proxyklasse, een kindklasse van een klasse waarin advies ingevoegd moet worden, die tijdens de uitvoering van het programma gegenereerd kan worden. De proxy beschikt uiteraard over dezelfde methoden als de klasse die van advies voorzien moet worden, door overerving. Advies bij de aanroep van de methoden kan echter als volgt ingevoegd worden:

  • De te adviseren methoden worden overschreven in de proxy.
  • De overschrijvende methode roept de adviescode aan en delegeert vervolgens naar de oorspronkelijke methode in de ouderklasse.
  • De proxy wordt ingezet in plaats van instanties van de ouderklasse, gewoon volgens het principe van polymorfisme. De adviezen worden "op magische wijze" in het programma ingevoegd.

Het resultaat is een soort combinatie van de vierde en vijfde methode. Het nadeel is dat er maar een gelimiteerde selectie van invoegpunten mogelijk is, omdat de bestaande code niet aangepast kan worden anders dan door overerving. Maar het is niet erg duur qua benodigde tijd en redelijk makkelijk in te passen in bestaande systemen. Het Spring Framework gebruikt deze methode bijvoorbeeld.

Toepassing

Aspectoriëntatie kan worden toegepast om crosscutting concerns te implementeren in code zonder dezelfde code op meerdere plaatsen te moeten herhalen. Daarmee maakt AOP het mogelijk om de structurering van een programma te verbeteren en daarmee ook de onderhoudbaarheid ervan. Typische voorbeelden van dergelijke crosscutting concerns zijn logging, toegangscontrole, transactionaliteit en databasetoegang.

Voor diverse objectgeoriënteerde programmeertalen zijn één of meerdere AOD-preprocessoren gebouwd:

  • Voor C#/VB.NET:
    • .NET
    • AspectDNG
    • Aspect#
    • Encase
    • Compose*
    • Seasar.NET
    • DotSpect (.SPECT)
    • Fody
    • PostSharp
  • Voor Java:
    • AspectJ
    • AspectWerkz (tegenwoordig opgenomen in AspectJ)
    • Byte Code Engineering Library
    • Dynaop
    • JAC
    • Jakarta Hivemind
    • Javassist Home Page
    • JAsCo
    • JBoss AOP
    • Object Teams
    • PROSE
    • Reflex
    • The AspectBench Compiler for AspectJ (abc)
    • Het Spring Framework
    • Seasar
    • Het JMangler Project
    • InjectJ
  • Voor C/C++:
    • AspectC++
    • XWeaver project
    • FeatureC++
  • Voor Python:
    • Aspyct
    • Lightweight Python AOP
    • Logilabs aspect module
    • PEAK
    • Pythius
  • Voor PHP:
    • Go! AOP PHP
    • PHPaspect
    • Aspect-Oriented PHP
    • Seasar.PHP
    • AOP API for PHP
    • PHP AOP
  • Voor Perl:
    • The Aspect Module
  • Voor XML:
    • AspectXML
  • Voor COBOL:
    • zelf te implementeren

Afwegingen

Alhoewel AOD bepaalde voordelen heeft zijn er ook nadelen aan te wijzen. Of het in de toekomst een standaard feature van elke taal wordt valt derhalve nog af te wachten. Wellicht kunnen de problemen die het oplost straks op een andere manier aangepakt worden die de voorkeur geniet.

Voordelen

Reductie van het aantal coderegels
Omdat aspectoriëntatie dubbele code tegengaat, kan de grootte van de code gereduceerd worden. Reductie van het aantal regels reduceert daarmee ook het aantal mogelijke fouten en de kosten van het onderhoud. Her en der worden op dit gebied spectaculaire claims gedaan, met name voor applicaties waar veel logging in zit.
Leesbaardere broncode
Doordat de code van twee totaal verschillende onderwerpen (bedrijfslogica en infrastructuur) niet meer door elkaar loopt is de broncode veel mooier en leesbaarder.
Verbeterde herbruikbaarheid van de broncode
Doordat logging per applicatie weer anders kan zijn wordt de herbruikbaarheid van zowel de bedrijfsobjecten als de technische objecten beter.
Het vergemakkelijken van (tijdelijke) aanpassingen
Omdat aanpassingen gemaakt kunnen worden zonder de oorspronkelijke code te wijzigen, kan het makkelijker worden om aanpassingen te maken. Ook is het makkelijker om aanpassingen te maken en te verwijderen, wat een voordeel kan zijn bij stukken code die van een tijdelijke aard zijn (of vaak met de tijd mee moeten wijzigen). Zeker bij dynamisch weven kan dit voordeel aanzienlijk zijn.

Nadelen

Het is schadelijk voor de structuur van de code
Dit is een vergelijkbaar bezwaar met de bezwaren die bestaan tegen de goto-statements. In plaats van een structuur te hebben die uit de code zelf af te lezen is, kan het geheel een soort onnavolgbare spaghetti worden. Dit kan vooral ook lastig zijn bij het debuggen van code. Hetzelfde bezwaar is van toepassing op templates in C++ en annotations in java. Dit komt voort uit het feit dat alle drie de mechanismen onzichtbare code genereren.
Er is een gevaar van onbedoelde neveneffecten
Dit gevaar komt voort uit het gebruik van reguliere expressies om puntsneden te definiëren. Het is mogelijk dat een reguliere expressie zo opgesteld wordt dat hij meer invoegpunten meeneemt dan de bedoeling is (bijvoorbeeld methoden uit gebruikte bibliotheken, of methoden in de eigen code als die plotseling van naam veranderen).
Er is een gevaar van onvoorspelbaar gedrag door interacties tussen aspecten
Hieraan werd al eerder gerefereerd: omdat het meestal niet verboden is om meerdere adviezen in te voegen in een invoegpunt, moet men oppassen met de uiteindelijke volgorde en mogelijke interacties tussen de ingevoegde stukken code. Het feit dat de invoeging niet direct af te lezen is uit de code en stukken code "door magie" opeens in het programma verschijnen, kan dit erg lastig maken. Vaak is het dan ook een goed idee om minstens te werken met een tool dat de invoegingen zichtbaar maakt.
Het is een kanon om een vlieg van de muur te schieten
Verschillende groepen beweren dat er andere middelen moeten zijn om de problemen op te lossen (alhoewel niet direct duidelijk is welke). Een gerelateerd bezwaar in dit opzicht is dat AOD vergelijkbaar is met het template mechanisme dat buitengewoon krachtig is maar voornamelijk gebruikt wordt om containers te maken.