Refaktorisieren ist überbewertet – zu der Überzeugung gelange ich immer mehr. Refaktorisieren ist eine Symptomkur, die das System, das Paradigma, das zu dem Zustand geführt hat, den sie auflösen soll, nicht verlässt.
Vielleicht ist das auch der Refaktorisierung selbst nicht anzukreiden. Das mag sein. Refaktorisierung bedeutet dann nicht nur eine funktionserhaltende Umstrukturierung, sondern genauer eine paradigmatische funktionserhaltende Umstrukturierung.
Dann ist es denen anzukreiden, die die Refaktorisierung – speziell die objektorientierte Refaktorisierung - so vertreten, als sei sie der Schlüssel zum Glück. “Schaffe dir die Refaktorisierungsmuster drauf, schaffe dir ein Tool wie ReSharper an – und schon ist der Weg aus dem Brownfield abgesteckt.” Oder auch: “Folge treu dem Dreischritt red-green-refactor und ein Platz auf der ewig grünen Wiese ist dir gewiss.”
Bullshit!
Selbstverständlich hat Refaktorisierung einen Wert. Sie ist ein Tool, das man zur rechten Zeit einsetzen kann und soll. Mein Eindruck ist nur, dass die jedem Tool eigene Begrenzung beim Lob der Refaktorisierung vergessen wird. Wo hat je ein Artikel gesagt: “Stopp! Ab hier hilft Refaktorisierung nicht weiter, um Evolvierbarkeit zu erreichen.” oder “Refaktorisierung macht nur in diesem oder jenem Rahmen Sinn.”
Klar, es wird schonmal erwähnt, dass man vorsichtig sein solle und Refaktorisierung nicht zum Selbstzweck werden darf. Refaktorisierung solle vor allem helfen, einen anderen Zweck zu erfüllen; refaktorisiere dann, wenn eine Veränderung es gebietet und nicht “auf Vorrat”.
Damit ist aber keine Grenze des Werkzeugs Refaktorisierung aufgezeigt, sondern nur die Intensität seiner Anwendung.
Mir tritt nun aber immer deutlicher eine solche Grenze ins Auge. Die üblicherweise beschriebene Refaktorisierung, die ReSharper-Refaktorisierung, hat ihre Grenze in der Objektorientierung. Refaktorisierung kann nur mit den Mitteln der Objektorientierung arbeiten. Der Code nach der Refaktorisierung ist immer noch objektorientiert.
Wie gesagt: Der Refaktorisierung selbst kann man das nicht anlasten, aber ihren Proponenten, die diese Grenze nicht aufzeigen – und sei es aus den löblichsten Beweggründen.
Eigentlich müsste ich deshalb wohl sagen: Objektorientierung ist überbewertet. Entzündet haben sich meine folgenden Gedanken aber an der Refaktorisierung. Deshalb habe ich es eingangs anders formuliert.
Und was stört mich nun an der Refaktorisierung bzw. am OO-Paradigma?
Mich stören die Abhängigkeiten.
Man mag lesen, Objektorientierung sei die Summe aus Kapselung plus Polymorphie plus Vererbung. Das ist natürlich nicht falsch, selbst wenn man sich trefflich darüber streiten kann, ob denn nun Vererbung ein unverbrüchliches Element der Objektorientierung sei.
Das Problem solcher Beschreibung von Objektorientierung ist nicht, was sie als Eigenschaften nennt, sondern das, was sie auslässt. Sie lässt – ganz natürlicherweise, übrigens – aus, das was der Objektorientierung zugrunde liegt. Sie lässt ihr Erbe aus. Das liegt in der prozeduralen Programmierung. Und noch tiefer liegt es in der von-Neumann-Maschine.
Es gibt keine wirkliche Einheit von Daten und Funktionen in unseren Rechnern, wie Objekte sie vorgaukeln. Zwar liegen Daten und Funktionen im grundsätzlich selben Speicher (Prozessadressraum), doch darin sind sie sauber getrennt. Das ist heute immer noch so wie zu Zeiten von Modula, C, Fortran, Assembler.
Objekte sind daher nur moderne Formen von C-Strukturen oder Pascal-Records. Und Methoden rufen sich immer noch mit einer call-Maschinencodeanweisung auf. Ob die zu einer virtuellen oder realen Maschine gehört, ob der Sprung direkt oder über eine Dispatch-Tabelle geht… das ist einerlei.
Objektorientierung abstrahiert von der notwendigen Trennung von Daten und Funktionen im Speicher. Das ist eine gute Sache. Aber das ist eben nicht alles. Sie bleibt dem Abhängigkeitsdenken, dem Callstack-Denken verhaftet. Wie Methoden einander nutzen, unterscheidet sich nicht davon, wie weiland C-Funktionen sich genutzt haben.
Das halte ich für ein Problem. Hier sehe ich die Grenze für die Nützlichkeit der Objektorientierung (wie der prozeduralen Programmierung oder auch Anteile der Funktionalen Programmierung).
Man mag dagegen halten, dass so die Welt sei und doch auch soviel machbar sei und funktioniere. Klar. Das ist unzweifelhaft.
Bis zum Beginn des 19. Jahrhunderts war aber auch viel machbar und hat funktioniert. Stonehenge, Pyramiden, Petra, Chinesische Mauer, Petersdom… all das wurde vor allem mit “menschlicher und tierischer Kraft” geschaffen. Die Werkzeuge haben sich in 5000 Jahren natürlich verändert. Was den Erbauern von Stonehenge noch sehr schwer gefallen war und Jahrzehnte gedauert hat, war für die Baumeister des Kölner Doms kein wirkliches Thema mehr.
Doch was ist dieser Fortschritt angesichts dessen, was dann ab dem 19. Jahrhundert passiert ist? Dampfmaschine, Explosionsmotor und Elektromotor haben das Bauen revolutioniert - genauso wie die Fortschritte in den Materialien Stahl, Beton, Glas, Kunststoff.
Alle Verbesserungen innerhalb des Paradigmas (oder Denkhorizonts) “menschliche und tierische Kraft” (oder “Materialien aus der Natur”) konnten nicht vorausahnen lassen, was passierte, als man dieses Paradigma sprengte.
Innerhalb der Grenzen von “menschliche und tierische Kraft” war viel möglich, sehr viel – arrogant wäre jedoch der gewesen, der behauptet hätte, damit sei die Grenze des Machbaren erreicht, der Gipfel der Baukunst erklommen.
So sehe ich es mit der Objektorientierung. Mit ihr im Speziellen und im Rahmen des zugrundeliegenden “Abhängigkeitsparadigmas” für Funktionalitätsnutzung ist viel möglich, sehr viel. Arrogant wäre jedoch der, der behauptete, damit sei die Grenze des Machbaren (oder Wünschenswerten), der Gipfel der Programmierkunst erreicht. Man müsse nur lernen, Objektorientierung sauber zu betreiben und schon sei nur noch der Himmel die Grenze.
I beg to disagree.
Menschliche und tierische Kraft und natürliche Baumaterialien waren und sind genauso beschränkend wie das “Abhängigkeitsparadigma” der prozeduralen und objektorientierten Programmierung. Und beim hochgelobten Refaktorisieren zeigt sich das für mich sehr deutlich. Hier ein Beispiel mit einer Folge von Anweisungen in einer Methode in einer Klasse:
Was würde daraus durch Refaktorisierung? Mit “Extract Method” würden Anweisungsgruppen ausgelagert in eigene Methoden:
Und mit “Extract Class” würden Methoden dann nochmal in separate Klassen rausgezogen:
Für einen Freund der Objektorientierung und des Refactoring sieht das alles normal aus. Prinzipien wie SPR und SLA werden in Anschlag gebracht und treiben eine differenzierte Struktur aus. Statt alles in einer Methode abzufrühstücken, wird auf kleinere Funktionseinheiten verteilt. Die LOC pro Methode sinken, die LOC pro Klasse sinken, die Cyclomatische Komplexität sinkt. Alles scheint in Butter.
Ich sehe das anders. Denn ich sehe eine Vervielfältigung von Abhängigkeiten. Vor der Refaktorisierung war der Code schlecht zu verstehen, weil er Anweisungen auf verschiedenen Abstraktionsebenen enthielt und verschachtelt war; dafür stand er “auf einem Haufen”. Nach der Refaktorisierung ist der Code aber immer noch schlecht zu verstehen. Denn erstens sind die algorithmischen Verschachtelungen nicht aufgehoben, sondern nur verschleiert worden. Und zweitens sind zu ihnen Indirektionen in Form von Abhängigkeiten getreten. Der vorherige Haufen ist jetzt nur kein Haufen mehr, sondern in alle Winde zerstreut.
Der refaktorisierte Code ist selbstverständlich im OO-Paradigma geblieben. Die Abhängigkeiten sprießen. Und weil sie sprießen, muss nun auch noch über Praktiken wie IoC und Tools wie DI Container nachgedacht werden.
Etwas übertrieben gesagt: Die Objektorientierung ist eine so komplizierte Technologie, dass man sie nicht ohne eine Vielzahl von begrenzenden und stützenden Prinzipien, Praktiken und Tools einsetzen kann. Damit ist sie mehr ein Problem denn eine Lösung.
Mit so einem Statement mache ich mich natürlich unbeliebt. Das ist mir klar. Aber ich habe lange drüber nachgedacht und spreche es daher nicht leichtfertig aus. Die wahren Gewinne, die nur oder zumindest vor allem durch Objektorientierung erzielt wurden in der Softwareentwicklung, sind mir zu gering, als dass ich sagen wollte, sie sei eine einzige Erfolgsgeschichte. Und das sage ich, obwohl (oder weil?) ich seit 1990 objektorientiert programmiere.
Natürlich gibt es Gewinn durch Objektorientierung. Aber ich sage mal provokant: Wer hat je dagegen die Verluste durch Objektorientierung aufgerechnet? Ist die Bilanz wirklich positiv. Ich habe Zweifel.
Wie gesagt: Irgendwie funktioniert ja die OO-Software. Keine Frage. Aber wenn es heute unwartbare Systeme gibt, dann sind das Anwendungen auf OO-Basis. Man mag einwänden, das läge daran, dass ja vor allem mit OO-Sprachen entwickelt würde, deshalb müssten auch die unwartbaren System OO-Anwendungen sein. Haha. Ja, klar. Dagegen halte ich: Die Objektorientierung ist damit angetreten, genau das zu verhindern. Sie hatte versprochen, Softwareentwicklung “natürlich” zu machen – halt objektorientiert wie die Welt scheinbar ist und damit viel einfacher zu beherrschen als alles was vorher war – und damit eben auch evolvierbarer und korrekter. Und was ist rausgekommen?
Objektorientierung ist einfach kein Selbstgänger. Sie ist vielmehr so schwierig, dass sie deutlich in der Anwendung begrenzt werden muss, meine ich. Sie hat ihren Wert. Ich möchte sie nicht missen. Doch den sehe ich vor allem bei der “Programmierung im Kleinen”. Überschaubare Probleme, die in wenigen Hundert Zeilen Code zu lösen sind, die kann man objektorientiert angehen. Super! Volle Kraft voraus. Da entstehen dann Codegebilde, die man mit SOLID-Prinzipien und Refaktorisierung im Griff haben kann, deren Korrektheit man zeigen kann, die für sich evolvierbar sind.
Aber oberhalb dieser “Taschen der Objektorientierung”…? Was da? Helfen da Komponenten- oder Pakete wie sie die UML bietet? Nein. Die sind nur Objektorientierung in Verkleidung. Denn was Klassen/Objekten, Komponenten, Paketen gemeinsam ist, ist die Sicht auf die Welt durch eine Abhängigkeitsbrille. Die ist das Problem. Große Systeme lassen sich – so meine immer fester werdende Meinung – nicht wirklich agil und evolvierbar innerhalb eines fundamentalen Abhängigkeitsparadigmas realisieren.
Irgendwie geht das. Aber irgendwie geht irgendwie alles. Man kann auch mit Streichhölzern ein Formel 1 Auto bauen oder Kleidung aus Papier fertigen. Das sind hübsche Zeitvertreibe – nur wirklich praktischen Wert haben sie am Ende nicht. Oder wollte man sie praktisch einsetzen, wären große Probleme gleich zur Stelle.
Noch eine andere Analogie: Wir können auf der Basis Newtonscher Physik eine Menge Energie erzeugen mit Wind-, Kohle-, Wasserkraftwerken. Richtig, richtig viel Energie braucht dann aber ein anderes Paradigma: die relativistische Physik Einsteins und die Quantenphysik Heisenbergs. Nur damit sind Kernkraftwerke oder dermaleinst Fusionskraftwerke möglich.
Für mich bedeutet das zusammenfassend: Objektorientierung ist eine gute Sache. Ihr muss allerdings ein Platz und eine Reichweite zugewiesen werden, damit ihr Nutzen ihre Nachteile aufwiegt.
Im Bild sind das die “Taschen der Objektorientierung”. Sie sind überschaubar, innerhalb ihrer sind Abhängigkeiten in übliche OO-Manier erlaubt.
Außerhalb dieser “Taschen” jedoch… die “Taschen” selbst… da soll keine Objektorientierung im Spiel sein. Das Denken in Abhängigkeitsbeziehungen ist dort zu verrmeiden. Wie die “Taschen” nach außen beschaffen sind, woher sie kommen, das will ich an dieser Stelle offen lassen.
In der Natur finden sie keine Abhängigkeiten wie sie in objektorientierten Strukturen existieren. Die wahrhaft komplexen Systeme der Welt kommen ohne aus. Und auch die Teile menschengemachter Maschinen sind nicht abhängig voneinander wie es objektorientierte Funktionseinheiten sind.
Woher kommt da nur der Glaube, virtuelle, aber dennoch (oder gerade deshalb) riesige Softwaregebilde könnten geeignet auf einem Paradigma aufgebaut werden, in dessen Kern Abhängigkeiten stehen? Ich weiß nicht, ob ich diesen Glauben naiv oder arrogant nennen soll. In jedem Fall scheint es mir nun offensichtlich, dass er verfehlt ist.
Sobald ein Problem eine Größe hat, die sich mit OO-Mitteln in Hunderten LOC umsetzen lässt, finde ich Objektorientierung geeignet. Solange ein Problem jedoch umfangreicher ist, ist für Entwurf und Umsetzung die Objektorientierung und jedes andere Paradigma das auf “Abhängigkeitsdenken” basiert ungeeignet.
Hierarchie ist natürlich weiterhin ein wichtiges Strukturierungsmittel. Keine Frage. Doch das, was da in einer Hierarchie aufgespannt ist – die Funktionseinheiten, die zusammen die Lösung ergeben –, darf nicht über Abhängigkeiten verbunden sein. Das ist schlicht nicht überblickbar, nicht evolvierbar. Objektorierung bzw. OO-Denke verbietet sich also sogar doppelt: erstens, weil sie sich innerhalb des Abhängigkeitsparadigmas bewegt, zweitens weil sie kein Mittel zu natürlicher Hierarchisierung bietet.
Die üblichweise gemeinte Refaktorisierung ist mithin überbewertet, weil sie Objektorientierung perpetuiert. So wichtig sie in “Taschen” der OO nötig sein mag, so hilfreich dort Prinzipien wie SOLID oder Tools wie DI Container sein mögen… Ich bin der Meinung, dass man daraus nicht ableiten soll, Objektorientierung sei dazu geeignet, einfach/natürlich/intuitiv Größeres zu leisten.