Prof. Dr. Detlef Kreuz

(Zu-) Stand der objektorientierten Programmierung

Oscar Nierstrasz schrieb im Journal of Object Technology ein recht interessantes Editorial für die Ausgabe September 2010: Ten Things I Hate About Object-Oriented Programming. Was auch immer seine Beweggründe sein mögen, nachdenkenswert sind seine Gründe allemal. Auf jeden Fall kommen sie zu einer Zeit, in der sich die Welt der Programmiersprachen (wieder einmal) im Umbruch befindet.

Ich selbst bemerke beim Nachdenken über seine Gründe an mir die eine oder andere Tendenz, andere Konsequenzen aus der vermeintlichen Krise der objektorientierten Programmierung (OOP) zu ziehen. Aber soweit bin ich noch nicht. Trotzdem möchte ich hier über seine Gründe ein wenig reflektieren.

Wenn ich über OOP sinniere, dann meine ich dabei die Programmiersprachen des Mainstreams mitsamt deren verwandten Technologien. Es mag für jedes Konzept eine OO-Programmiersprache geben, die ein Konzept optimal umsetzt. Aber was hilft es, wenn diese Sprache kaum benutzt wird, oder kaum benutzt werden kann?

10. The Next New Thing

Das ist kein echter Grund, sondern ein altes Leiden unserer Zunft. Wir hoffen mit einer neuen Technologie, der berühmten Silver Bullet, alle unsere technischen Probleme zu lösen. Aber so, wie es (hoffentlich) keine Werwölfe gibt, die nur mit einer silbernen Kugel zu töten sind, kann es auch keine entsprechende Technologie geben: No silver bullet.

IT-Probleme sind entweder trivial oder komplex. Für komplexe Probleme kann es keine einfachen Lösungen geben, keine Silberkugel. Technologien können höchstens helfen, komplexe Probleme etwas weniger komplex zu machen. Aber komplex bleiben sie. Und da hilft auch das nächste neue Ding nicht.

9. UML

Meine Meinung zu UML ist sehr zwiespältig. Zum einen sehe ich es als eine nette Möglichkeit an, Systeme zu beschreiben. Für manche unter uns sagt ein Bild mehr als 1000 Worte. Solange man sich über die Abstraktionsebene im Klaren ist. Zum anderen liegt die "Wahrheit" immer im Programmcode. Dieser wird nämlich von der CPU ausgeführt, nicht ein hübsches UML-Bildchen.

So wie es einen Impedance mismatch zwischen (relationalen) Datenbanken und OO-Systemen gibt, gibt es auch einen zwischen UML und Programmiersprachen. Der Sprung von der UML zum Programmcode, etwa in Java, wird meines Erachtens nur ungenügend von Werkzeugen aus dem MDA-Umfeld geleistet.

Damit bleibt UML eine nette Beschreibungsmöglichkeit, aber relativ losgelöst vom tatsächlichen Programmcode.

8. Methodik

Eine Methodik (Methodology, gerne verwechselt mit Methodologie) ist nichts anderes als eine Sammlung von Methoden zur Problemlösung. Betrachtet man diese so unvoreingenommen, ist nichts an ihr auszusetzen. Leider werden Methodiken gerne unreflektiert eingesetzt. So nach dem Motto: "Der Hammer hat schon beim Nagel geholfen, für die Schraube wird er auch passen".

Unzählige Flame wars wurden und werden genau aus diesem Grund ausgefochten. Mein [X] ist besser als deins. Für [X] können Sie beliebig aus Programmiersprache, Betriebssystem, Architektur, Gadget, … wählen. Dabei vergessen die Kontrahenten gerne, sich den konkreten Anwendungsfall anzusehen. Nur dafür kann eine bestimmte Methodik (vielleicht) geeigneter sein, als eine andere.

7. Design Patterns

Auf Ebene der Programmierung sind Entwurfsmuster nichts anderes, als der Versuch, Defizite der Programmiersprache über Konventionen auszugleichen. Punkt.

6. Change

Es stimmt, OOP kann schlecht mit Änderungen umgehen. Aber da tun sich alle Programmierungen schwer. Änderungen sind unausweichlich. Das Dumme ist nur, dass man meistens nicht weiß, was sich ändert. Ob ich nun die Inkonsistenzen dieser Welt in sog. Kontexte abbilden möchte, ist mir nicht klar.

Die Schwierigkeit besteht meiner Meinung darin, dass wir bei IT-Systemen immer beides wollen: absolute Zuverlässigkeit und einfachste Anpassung an neue Situationen. Die Software soll das tun, was wir ihr gesagt haben, aber auf beliebige Änderungen ihrer Umgebung angemessen (automatisch) reagieren. Man zeige mir ein System, dass diesen Widerspruch für alle zufriedenstellend auflöst.

Nein, wir können höchstens versuchen, unsere Software auf eine bestimmte Art von Änderungen vorzubereiten. Aber dann bleibt immer noch die Entscheidung, ob man in eine mehr oder minder ungewisse Zukunft investieren möchte.

5. Types

Für viele ist es ein heißes Eisen, ob die Überprüfung der Datentypen statisch zur Übersetzungszeit oder dynamisch zur Laufzeit stattfindet. Aus meiner Sicht ist die Entscheidung längst gefallen, auch wenn sie wenig mit OOP an sich zu tun hat.

Als vor über 15 Jahren durch Java die Konzepte Garbage Collection und Bytecode breit eingeführt wurden, diskutierte man über deren mögliche und tatsächliche Effizienzprobleme. Heute ist klar, dass genau diese Konzepte für die Effizienz vieler Anwendungen sorgen, von der Effektivität der Programmierer ganz zu schweigen (Ich sehe hier einmal von vielen eigebetteten Systemen o.ä. ab, die haben andere Anforderungen.)

Weil es diese beiden Konzepte in Plattformen, wie Java oder .NET geschafft haben, können automatisierte Subsysteme dafür sorgen, dass der Hauptspeicher gut genutzt wird und dass die an die jeweilige Umgebung angepassten Maschinenbefehle verwendet werden. Vorher mussten diese Entscheidungen von Programmierern getroffen werden und die lagen häufig genug daneben. Ein optimales C-Programm mag schneller sein, als ein entsprechendes Java- oder C#-Programm. Aber wer hat die Ressourcen, immer die optimale Lösung zu finden? Da ist es wirtschaftlicher, einmal (oder zwei- bis zehnmal) eine passende Strategie zu realisieren, die gut genug ist. Davon profitieren auch die durchschnittlichen und weniger guten Entwickler. Die gibt es nämlich auch.

Aus diesem Grund ist die dynamische Typprüfung der statischen langfristig unterlegen. Projekte wie PyPy zeigen, was alles mit dynamischer Typprüfung möglich ist. Viele vergessen, dass die meisten Programmiersprachen, die eine statische Prüfung verlangen, auch eine dynamische Prüfung benötigen. Zum Beispiel bei Typumwandlungen oder bei Zugriffen auf Sequenzen. Die Sprachen, die eine fast vollständige Prüfung erlauben, sind kaum zu benutzen. Ich erinnere mich an die Sprache zum System Tycoon, bei der ein Freund drei Tage versucht hat, die Fehlermeldung des Übersetzers zu einem Typfehler zu verstehen und zu korrigieren.

Es gibt immer noch Entwickler, die glauben, eine 32-Bit-Zahl reiche für fast alles aus. Die Zahl zwei Billionen mag für viele Anwendungen groß genug sein, aber für Dateien ist sie inzwischen viel zu klein. Auch die Anzahl der Tweets ist inzwischen jenseits dieser Grenze. Die Regeln für 32-Bit-Zahlen entsprechen denen der 64-Bit-Zahlen und denen der 16-Bit-Zahlen. Bis auf die erlaubten Grenzwerte. Warum soll ich als Entwickler mich auf den Datentyp int, long oder short festlegen, wenn sich die Welt der Anforderungen noch ändern kann?

Insofern sind Angaben des Datentyps nichts anderes als eine (manchmal voreilige) Optimierung. So, wie ich früher entschieden habe, auf welche Speicherstellen meine Datenstruktur abgelegt wird oder für welche CPU ich durch den Übersetzer Code generieren lasse. Wenn dagegen, wie bei der Garbage Collection, zur Laufzeit geprüft wird, welche Datentypen tatsächlich verwendet oder benötigt werden, dann wird mir als Entwickler eine gehörige Last genommen.

Mir ist klar, dass diese Argumentation nur für eine bestimmte Art von Systemen gültig ist. Bei bestimmten Systemen ist es gut, wenn ich beweisen kann, dass keine Typfehler im Programm vorkommen. Bei diesen Systemen sollte ich auch beweisen, dass die Anforderungen korrekt implementiert wurden. Für die Vielzahl der Business-Anwendungen ist dies so nicht nötig. Ebenso hilft die dynamische Typprüfung auch den vielen durchschnittlichen und weniger guten Entwicklern.

4. Methods

Methoden sind der Teil eines Programms, der Aktionen ausführt, wenn ein Objekt eines Nachricht empfangen hat. Diese Aktionen sind üblicherweise selbst das Senden von Nachrichten an Objekte. Soweit die Theorie. In Sprachen wie Smalltalk auch die Praxis. In Java, C# und Co. gibt es auch noch andere Befehle, die z.B. die for-Schleife. Die sind aber weder Objekt noch Nachricht.

Egal, ob in Smalltalk, Java, C# oder einer anderen OO-Sprache, in der Praxis sind Methoden eher kurz. Lange Methoden haben den Nachteil, dass deren Aktionen schlecht wiederverwendet werden können. Also schreibt der gute OO-Programmierer kurze Methoden und nutzt darüber hinaus intensiv das Konzept der Vererbung. Alle hoffen so wiederverwendbaren Code zu erstellen und wiederverwendbaren Programmcode zu nutzen.

Im Endeffekt ist die eigentliche Funktionalität des Systems nicht explizit in den Methoden, sondern implizit in deren Zusammenspiel zu finden.

3. Klassen

Die meisten Softwerker stellen sich unter einem Objekt etwas ähnliches vor. Dies erkennt man spätestens dann, wenn man sich das Whiteboard nach einer Diskussion über Objekte in einem System anschaut. Objekte sind meistens die Kreise oder Rechtecke, Methoden kleine Kringel, Referenzen sind Pfleile, Nachrichten sind als Linien quer darüber gemalt.

Bei Klassen ist das anders. Die sind auf dem Whiteboard zwar immer Rechtecke, aber was sie bedeuten bleibt unklar. Sicher liegt es daran, dass Objekte eher etwas konkretes sind. Klassen sind abstrakt. Fragt man herum, was Klassen sind, so eiert sich fast jeder Befragte um eine Definition herum. Heraus kommen Begriffe wie Blaupause, Fabrik, gemeinsame Eigenschaften, Objekttyp oder Muster. Aber spätestens bei der Reflexion sind alle diese Definitionen vergessen.

Nicht umsonst verzichtet eine der erfolgreicheren Sprachen, JavaScript, auf Klassen. Dort sind alles Objekte, auch die Klassen.

2. Object-Oriented Programming Languages

Es gibt eine Reihe von Programmiersprachen, die das OOP-Konzept mehr oder minder gut umsetzen. Die sind aber nicht das Problem, höchstens das Symptom. Der Designer einer (Mainstream-) Programmiersprache muss auch darauf achten, dass die Sprache benutzbar bleibt. Andernfalls verkommt sie zur Nischensprache. So gesehen bildet eine allgemein verwendbare Programmiersprache nicht nur die Fähigkeiten der Top-Entwickler, sondern auch die der durchschnittlichen und unterdurchschnittlichen Programmierer ab.

Sprich, wir haben die Programmiersprachen, die wir verdienen.

1. Paradigm

Die objektorientierte Denkweise hat einiges für sich und einiges gegen sich. Man kann in der Tat den interessierenden Ausschnitt der Welt als Ansammlung von Objekten modellieren, die sich gegenseitig Nachrichten senden. Dann sind auch Katzen objektorientiert. Diese Denkweise hat den Vorteil, dass sie nicht nur dem Namen nach zur Objektivität passt, dem vorherrschenden Wissenschaftsparadigma. Darin wurden wir (westliche) Menschen von klein auf an geschult.

Ohne mich in der Philosophie verlieren zu wollen, wissen wir allen, dass Objektivität ein fragwürdiges Konzept bleibt. Nicht nur bei Prüfungen ...

Vieles lässt sich auch nur schwer objektorientiert modellieren, etwa die Erkennung von Gegenständen auf Bildern. Die wird meistens als eine Reihe von Transformationen beschrieben. Geschäftsregeln sind meist in Form von logischen Formeln beschrieben, Abläufe in Form von Prozeduren.

Oscar Nierstrasz beschreibt, was OOP alles nicht ist. Weder Alles ist ein Objekt, noch Objekte+Klassen+Vererbung, Kapselung+Datenabstraktion+Verbergen von Informationen oder Objekte+Nachrichten. Da stimme ich überein.

Nicht folgen kann ich bei der Denkweise, OOP sei kein Paradigma, wie funktionale oder prozedurale Programmierung, sondern sei die Aussage: "Für jedes Problem entwerfe dein eigenes Paradigma". Natürlich soll man für jedes Problem den eigenen Lösungsansatz entwickeln. Aber dies objektorientiert zu nennen, halte ich für übertrieben. Dann könnte ich diesen Ansatz auch "Tessa" nennen ;-).

So oder so bleibt die Frage, was objektorientierte Programmierung ist.

0. (Zu-) Stand der objektorientierten Programmierung

Die Frage, was objektorientierte Programmierung sei, ist nicht nur philosophisch zu sehen. Sie wird zu einer sehr praktischen Frage, wenn man z.B. OO-Sprachen lernt oder lehrt. Warum soll ich Java, C# oder Smalltalk lernen? Was ist das besondere gegenüber Sprachen wie PL/1, Modula-2, Perl oder PHP? Die Antwort ist meistens, dass durch OO-Sprachen und deren Frameworks Arbeitserleichterungen versprochen werden. Oder, dass Java ein Industriestandard sei, den man lernen müsse.

Das mit dem Industriestandard mag so sein, aber auch Standards ändern sich. Früher hieß der mal COBOL, dann C bzw. C++. Und selbst wenn sehr viele Unternehmen eine Infrastruktur haben die auf Java und/oder .net basiert (und ansonsten virtualisiert ist), so spricht aus meiner Erfahrung wenig dagegen, andere Java/JVM- oder .net-Sprachen einzusetzen. Clojure oder JRuby finden gerade ihren Platz in der Java-Welt, F#, Python.net in der .NET-Welt. Alles keine Sprachen, die als reinrassig OOP anzusehen sind.

Jeder erfahrene Entwickler weiß, dass die versprochenen Arbeitserleichterungen höchstens relativ zu anderen anzusehen sind. Auch OOP ist keine Silberkugel. Für GUI- oder Web-Anwendungen ist teilweise Programmcode notwendig, bei dem ich an "Von hinten durch die Brust ins Auge" denken muss. Dieser Programmcode kann nicht gesund sein.

Je mehr ich darüber nachdenke, desto mehr komme ich zu dem Schluss, dass OOP mindestens für den durchschnittlichen Programmierer eine frag-würdige Angelegenheit ist. Objektorientierung ist eine Denkweise, die man kennen sollte, sobald man mit der Entwicklung von (Software-) Systemen zu tun hat. Aber sie ist nicht die einzig mögliche Denkweise, geschweige die am häufigsten zu verwendende Denkweise.

Ich selbst bevorzuge Programmiersprachen, bei denen ich unterschiedliche Paradigmen anwenden kann, die aber trotzdem eine reichhaltige Infrastruktur bzw. Plattform bieten. Aktuell sind dies Python oder Clojure. Vielleicht schaue ich mir auch Scala oder JRuby vertieft an. Oder F#. Change will show.

Denn: Programmieren ist Modellieren.