Aspektorientierte Programmierung mit jQuery-AOP

Schon mal was von Aspektorientierung gehört? Nein? Kein Wunder, denn die aspektorientierte Programmierung führt ein ziemliches Schattendasein, obwohl sie in vielen Fällen äußerst praktisch sein kann. Besonders Aufgaben wie Profiling, Logging und Berechtigungen, die in rein objektorientiertem Code viel Aufwand machen würden, lassen sich aspektorientiert sehr elegant umsetzen.

Objektorientierung++

Glücklicherweise versucht die Aspektorientierung nicht das Rad komplett neu zu erfinden, sondern versteht sich eher als Erweiterung zur normalen Objektorientierung. Es gibt einige Probleme die mit der herkömmlichen Objektorientierung nicht trivial (ohne große Mengen an redundanten Code) lösbar sind, da es beispielsweise keine Möglichkeit gibt von außen in das Verhalten von Objekten einzugreifen. An dieser Stelle führt die aspektorientierte Programmierung das Konzept der Aspekte ein, die bestimmtes Verhalten in den gesamten Programmcode “einweben” können. Die Probleme die damit gelöst werden nennen sich Cross-Cutting Concerns, da sie sich über die gesamte Programmlogik erstrecken und nicht gekapselt werden können. Musterbeispiele für solche Cross-Cutting Concerns sind, wie bereits oben genannt Profiling, Logging oder Berechtigungen.
In der Aspektorientierung benutzt man Point-Cuts um alle Stellen im Programm zu definieren an denen bestimmte Aspekte umgesetzt werden müssen. Ein Aspekt wird in der Regel durch einen Funktionsaufruf realisiert, der an ganz bestimmten Point-Cuts aufgerufen wird. Diese Funktionen nennt man Advice. Um ein Advice aufzurufen, wird der Programmfluss an dem Point-Cut unterbrochen und alle Advices werden ausgeführt. Abschließend lässt man die Advices von einer Hilfsmethode (oder je nach Sprache von einem Compiler) an den von den Point-Cuts definierten Punkten einbinden. Dieser Vorgang nennt sich “Weaving”.

Profiler

Am besten lassen sich die Vorteile der aspektorientierten Programmierung an einem praktischen Beispiel zeigen. Unser Ziel ist es einen Profiler zu schreiben, der bei einem gegebenen Objekt die Laufzeiten aller Funktionsaufrufe misst und auf der Console ausgibt. Diesen Profiler wollen wir dann benutzen um das HTML5-Canvas zu profilen. Als Werkzeug verwende ich jQuery-AOP, eine Bibliothek die Hilfsfunktionen für die aspektorientierte Programmierung bietet.
Nach dem Einbinden von jQuery und jQuery-AOP, definieren wir zuerst einen Point-Cut und einen passenden Advice. Der Point-Cut “zeigt” auf alle Methoden des Objekts das wir überwachen wollen. Der Advice wird bei jedem Funktionsaufruf ausgeführt und führt die eigentliche Zeitmessung durch. Beim Weaving benutzen wir die Funktion around(), um festzulegen dass alle Funktionen in den Advice eingebettet werden sollen.

var ctx = document.getElementById("some-canvas").getContext("2d");
// Point-Cut definieren.
var pointCut = { target: ctx, method: /.*/ };
// Advice definieren.
var advice = function(invocation) { 
    var t1 = Date.now();
    var ret = invocation.proceed();
    console.log(invocation.method + ": "  + (Date.now() - t1) + " ms");
    return ret;
}
// Weave
jQuery.aop.around(pointCut,advice);
// Laufzeiten messen
ctx.fillStyle = "rgb(200,0,0)";             // fillRect: 1 ms
ctx.fillRect (10, 10, 55, 50);              // fillRect: 1 ms
ctx.beginPath();                            // beginPath: 1 ms
ctx.moveTo(30, 30);                         // moveTo: 2 ms
ctx.lineTo(150, 150);                       // lineTo: 0 ms
ctx.bezierCurveTo(60, 70, 60, 70, 70, 150); // bezierCurveTo: 1 ms
ctx.lineTo(30, 30);                         // lineTo: 0 ms
ctx.fill();                                 // fill: 0 ms

JavaScript OO Features: Warum in die Ferne schweifen, das Gute liegt so nahe!

Stellen wir uns vor, Webentwickler würden anstatt Programmiersprachen Fremdsprachen sprechen: Englisch könnten wir, aber Französisch brauchen wir jetzt leider für unser neues Projekt.
Ganz logisch: Das mit den lateinischen Buchstaben kennen wir ja bereits, syntaktische Schnörksel wie Gravis sind unnötiger Codesmell und allgemein ist die Grammatik ja ziemlich komisch (bestes Beispiel “Grand Prix Eurovision de la Chanson Européenne” – das ist doch viel mehr Tipparbeit als Eurovision Song Contest und sagt das gleiche). Also nimmt man doch lieber alles was man nicht kennt aus der Sprache raus und erfindet seine eigenes, smarteres Französisch, das halt französische Wörter nimmt, aber vom Aufbau an Englisch angelehnt ist. Englisch ist ziemlich bekannt und ausserdem genauso feature complete, das macht die Sache für andere auch einfacher. Und für die Franzosen (die nicht in der Lage sind, unser besseres Französisch zu sprechen) schreibt man dann bei der eigentlichen Kommunikation einfach das schnieke Französisch 2.0 in das bloated Französisch 1.0 um, damit die auch was verstehen.
Klingt komisch? Willkommen in der Welt von JavaScript. Nach CoffeeScript hat jetzt Microsoft seinen Entwurf für ein ‘besseres’ JavaScript herausgebracht. Hauptargument: Vererbung und Objekte in JavaScript sind ja schwer zu handeln. Leider stimmt das seit ECMAScript 5 dank Object.create nicht mehr ganz (und das wird mittlerweile von einer ganzen Reihe Browser unterstützt), nur ist es da anders als man es gewohnt ist. Daher ein kleiner Exkurs in ECMAScripts ‘neue’ Objektmethoden:
Die Methode:

Object.create(parent,properties)

Erstellt in ECMAScript 5 ein neues Objekt,d das von parent erbt und die Eigenschaften aus properties besitzt. Z.b. würde:

 var Hund = Object.create(Animal,{ name: { value: 'Hund', writable: false } }) 

Ein Objekt Hund erstellen, das von Animal erbt und “Hund.name” würde einfach “Hund” zurückgeben. Was jetzt wirklich neu ist: Durch das writable: false werden sämtliche Versuche, Hund.name zu überschreiben ignoriert. Es gibt neben value und writable aber noch weitere praktische Attribute, nämlich:

  • enumerable: Gibt an ob die Eigenschaft bei einer for..in Schleife über das Objekt erscheint
  • get: Sehr praktisch: Ruft eine Funktion beim lesenden Zugriff auf die Eigenschaft auf, anstatt einfach nur einen wert zurückzugeben.
  • set: Ebenfalls praktisch: Ruft beim schreibenden Zugriff auf die Eigenschaft eine Funktion auf, anstatt einfach den Wert in die Eigenschaft zu schreiben
  • configurable: Definiert, dass die Konfiguration der Eigenschaft nicht verändert werden darf
Mit Object.defineProperty(objekt,name,properties) kann man auch nachträglich Eigenschaften an ein Objekt hinzufügen (z.B. für komplexere Methoden).

Man sieht also gleich, dass es hier ganz neue Sprachfeatures und nicht nur syntaktische Neuanordnungen gibt. Die sind zwar nicht ganz das sind, was man von der Objektorientierung her kennt (wo ist das ‘class’ Schlüsselwort?), aber JavaScript ist halt auch kein Java.
Zusätzlich dazu gibt es noch zwei praktische Methoden um Änderungen an Objekten zu verhindern:

  • Object.preventExtensions(Hund): Verhindert, dass man mit defineProperty Eigenschaften nachträglich an Hund hinzufügt
  • Object.seal(Hund) : Hier könnte man die Objektstruktur von Hund nicht mehr verändern
  • Object.freeze(Hund) : Hier kann man auch vorhandene (und auf writable gesetzte) Eigenschaften des Objektes nicht mehr ändern

Die Features werden von allen neueren Browsern (IE9+,FF4+, Chrome 5+, Opera 11.60+) unterstützt, für ältere gibt es bei Modernizr einen Polyfill.
Ich finde zwar z.B. Typescript eine ganz nette Sache und wirklich gut gemacht (gerade für JS Neulinge aus der Java/C# Welt). Allerdings würde ich persönlich mir wünschen dass mehr Energie in die bessere Unterstützung von ‘echtem’ JavaScript in IDEs gesteckt wird, anstatt andauernd das Rad neu zu erfinden. Am Ende interpretieren die Browser dann doch JavaScript – und das ist jetzt nicht so komplex oder aufwändig als dass man auf Zwischeninterpreter angewiesen ist um ein Projekt In-Time durchzubekommen. Immerhin gibt es auch gute (und kleine!) Bibliotheken, die einem das Leben erleichtern, wenn die Sprache einem doch Probleme bereitet.
Wer mehr zu den Objekt.* Methoden lesen will: