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.
Schlagworte: object, programmieren.