Warum Clojure?

18.01.2013 Permalink

Die letzten sechs Monate habe ich mich in meiner spärlichen, abendlichen Freizeit mit der JVM basierten Programmiersprache Clojure beschäftigt. Wer nicht lange suchen will, um einen vernünftigen Abriss zu erhalten, kann die Einführung von Mark Volkmann lesen.

Ich hatte mich in den letzten paar Jahren schon mit Groovy und Scala beschäftigt, und mir anhand der Bücher von Dierk König bzw. Martin Odersky die Sprach-Features angesehen, alles allerdings ohne Versuche zu unternehmen, irgendetwas anderes als Beispiele zu probieren. Im letzten Jahr habe dann ich von zwei völlig unterschiedlichen Personen Berichte über vielversprechende Features von Clojure erhalten. Die Fremdartigkeit von Clojure Programmen verglichen mit C-Syntax-basierten OO-Sprachen wie Java, C# oder Scala gab dann den Ausschlag: das will ich richtig lernen.

Inzwischen habe ich nicht-triviale Teile eines Rich Client Frameworks implementiert (darunter eine GUI DSL, Databinding, Validierung, unittestbare Controller), eine Domäne also, die für Clojure auf den ersten Blick ungeeignet erscheint, da ein Rich Client Unmengen veränderlichen Zustands enthält. Doch es geht ohne besondere Kniffe und mit weit weniger Gehirnschmalz als die vergleichbare Java Implementierung, die ich in den letzten 18 Monaten für einen unserer Kunden geschrieben habe.

Das hervorragende Buch Clojure Programming versucht zu Beginn, den Leser vorzubereiten: 'Clojure demands that you raise your game, and pays you back for doing so.' Das trifft den Nagel auf den Kopf. Es ist eine Herausforderung wie der Marsch auf einen Gipfel. Und dann sieht man die Welt von 'da oben' auf eine andere Weise. Es verändert die Perspektive. Und lässt mich vieles, was ich in den letzten Jahren für 'Okay' hielt, in Zweifel ziehen.

OOP als Hinderniss
Ich sehe OOP mittlerweile kritisch und halte es nicht mehr für einen Teil der Lösung... sondern einen Teil des Problems:

Wir erschaffen in OO Programmen am laufenden Band neue Datenstrukturen mit dazugehörigem Verhalten. Die Instanzen der resultierenden Klassen sind i.a. nicht in dem Sinne einheitlich, dass ich generelle Funktionen auf sie anwenden kann. Um Probleme in Java generell zu lösen, muss ich z.B. folgendes tun:

Wenn ich diesen Aufwand nicht treiben möchte, bleibt mir nur das endlose händische Ausprogrammieren von immer ähnlichen aber nie gleichen Verfahren, die am Ende die fachliche Funktionalität ergeben.

Klassen enthalten umfangreiche Mengen veränderlicher, meist privater Attribute, die allerdings überwiegend durch entsprechende Setter wieder beschreibbar werden. Es gibt hier keine Garantien bzgl. des Zustands eines Objekts. Jeder Systemteil kann prinziell den Zustand ändern und das, zum Leidwesen nebenläufiger Programme, zu jedem Zeitpunkt. Die Konzepte von Identität und Zustand sind vermischt. Es ist nicht umsonst guter Programmierstil in Java und Scala, unveränderliche Objekte zu verwenden, wenn es irgendwie geht.

In Backends typischer Enterprise-Systeme findet man überwiegend keine OO Programme im Sinne der Erfinder, sondern zustandslose Funktionen, die auf Datenstrukturen operieren, welche in einer OO Sprache geschrieben wurden. OOP ist hier im wesentlichen nutzlos.

Heutige Unternehmensanwendungen lassen sich nur noch wirtschaftlich erstellen, indem möglichst viel aus dem Ökosystem einer Programmiersprache wiederverwendet wird. Mit anderen Worten: niemand würde heute ein Enterprise-Java-System bauen, das nicht mindestens ein Dutzend Fremdbibliotheken verwendet. OOP geht allerdings davon aus, dass eine Klasse eine nützliche, abgeschlossene Implementierung einer Verantwortlichkeit ist. Wenn ich nicht Eigner der Klasse bin, z.B. weil sie aus einer Fremdbibliothek stammt, dann kann ich heute in Java Vererbung oder das Decorator-Muster nutzen, um eine erweiterte Variante der Klasse zu bekommen. Beides erfordert, dass ich kontrollieren kann, wie Instanzen entstehen, was nicht immer gegeben ist. Natürlich kann ich alternativ statische Methoden oder 'Service'-Klassen erstellen, die auf den öffentlich zugänglichen Teilen eine fremden Klasse zusätzliche Funktionalität schaffen. Um dann den syntaktischen Bruch bei der Benutzung zu eliminieren, bieten Sprachen wie Xtend oder C# Extensions an. Extensions erscheinen mir wie ein implizites Eingeständnis, dass Klassen i.a. vom Autor nicht abschliessend mit genügend Funktionalität versehen werden können. Und ich schließe daraus: Klassen als wichtigstes Sprachmittel zur Erreichung des hohen Guts 'Wiederverwendung' sind offensichtlich nicht ausreichend, sondern erzwingen nicht selten Umwege, die man dann als Design-Patterns bezeichnet.

Macht Clojure alles richtig?
Weiß ich nicht. Aber viele Features der Sprache beseitigen gezielt die prinzipiellen Schwächen von OOP. Das äußert sich dann dadurch, das Clojure Programme nur einen Bruchteil der Zeilenzahl von funktionsgleichen Java Programmen besitzen. Viele Probleme, die wir in OO Sprachen mithilfe von Dependency-Injection, AOP, Reflection, Annotationen und anonymen inneren Klassen lösen, verschwinden fast vollständig.

Wo für die sichere Umsetzung von Enterprise-Architekturen heute mit wachsender Begeisterung MDSD Verfahren zum Einsatz kommen, kann mit vertretbaren Abstrichen in Komfort und Tooling das Clojure (und allen Lisp-Sprachen) eigene Makro-System verwendet werden. Da Makros Teil von Clojure sind, ist keine Anpassung des Builds, kein expliziter Generatorbau mit fremdartigen Werkzeugen, keine Konfiguration von Compiler-Plugins und keine Bindung an eine IDE wie Eclipse nötig. Das wirkt sich vor allem auf den Kopf aus: Mini-Softwaregeneratoren, die das Schreiben von Boilerplate-Code vermeiden helfen, sind nur fünf bis zehn Zeilen entfernt. Als Entwickler denke ich daher nicht über 'Generatorbau' mit allen Konsequenzen nach, sondern erschaffe mir die erforderliche Abkürzung sofort.

Ich könnte mit einem Dutzend von Features (Destructuring, persistente Datenstrukturen, Pre- und Post-Conditions, Software-Transactional-Memory, Funktionen höherer Ordnung, bestehende Typen um neue Protokolle erweitern, Multi-Methoden, zyklenfreie Namespaces, REPL und interaktive Programmierung, Homoiconicity, Metadaten, Keywords) weiterschwärmen, doch ich will die mir ersichtlichen Nachteile, die heute bei Clojure bestehen, auch aufzählen:

Ich halte Clojure für einen Produktivitäts-Booster in Projekt-Teams von erfahrenen, qualitätsbewussten Entwicklern, wenn die ersten Hürden erstmal genommen sind.

Entwickler, die nicht in der Lage sind, im Kern zu verstehen, was sie gerade beackern, werden mit Clojure allerdings wenig Glück haben. Frameworks wie Hibernate oder JSF, die die tatsächliche Welt (DB Relationen bzw. HTTP/HTML/CSS) zu verbergen versuchen, passen nicht so recht in das Clojure Weltbild. Es geht also nicht ohne profundes Know-How über das Einsatzgebiet. Clojure wird vermutlich nie eine Programmiersprache sein, die irgendwie der Klickibunti-Programmierung in die Hände spielt.

Clojure ist auf jeden Fall eine sichere Wette, wenn man die eigenen Fähigkeiten verbessern und eine wesentlich weitere Perspektive auf Programmierung gewinnen will.