Goodbye, Clean Code (2020)
(overreacted.io)- Zeigt anhand der Erfahrung, Resize-Code in einem Grafikeditor mit Fokus auf Deduplizierung neu geschrieben und später zurückgerollt zu haben, dass Besessenheit von Clean Code die tatsächliche Änderbarkeit beeinträchtigen kann
- Die bisherige Implementierung wiederholte Formeln für die einzelnen Handles von Rectangle, Oval, Header und TextBlock; die neue Implementierung versuchte, Duplikate zu entfernen, indem sie Richtung und Form trennte und kombinierte
- Direkt nach dem Refactoring war der Code halb so groß und die Änderungsstellen waren an einer Stelle gebündelt, doch sobald später Sonderverhalten je Form und Handle nötig wurde, hätte die Abstraktion eher im Weg stehen können
- Das größere Problem als die technische Entscheidung war, die Änderung ohne Rücksprache mit dem Kollegen, der den ursprünglichen Code geschrieben hatte, direkt in master einzuchecken; das kann den Vertrauensaufbau im Team beschädigen
- „Sauberkeit“ ist kein Ziel, sondern nur ein Werkzeug zum Umgang mit komplexen Systemen; wichtiger als die Form des Codes ist, ob das Team gemeinsam auf Veränderungen reagieren kann
Wiederholungen im Resize-Code eines Grafikeditors
- Auf der Canvas eines Grafikeditors gab es Code, um Formen wie Rechtecke und Ovale über kleine Handles an den Rändern zu resizen
- Die Implementierung funktionierte, aber für jede Form und Handle-Richtung wiederholten sich die Formeln zur Berechnung von Position und Größe
Rectanglehatte Methoden wieresizeTopLeft,resizeTopRight,resizeBottomLeft,resizeBottomRightOvalhatte Methoden wieresizeLeft,resizeRight,resizeTop,resizeBottom- Auch
HeaderundTextBlockhatten jeweils eigene Resize-Methoden
- Wenn Nutzer Shift gedrückt hielten, musste während des Resizings auch das Seitenverhältnis beibehalten werden, wodurch noch mehr Formeln nötig wurden
Abstraktion zur Entfernung von Duplikaten
- Die Wiederholung zeigte sich auf zwei Achsen
- Wiederholung zwischen Handles derselben Richtung:
Oval.resizeLeft()undHeader.resizeLeft()sind beide Aktionen, bei denen das linke Handle gezogen wird - Wiederholung zwischen Methoden innerhalb derselben Form: Mehrere Methoden von
Ovalbehandeln alle ein Oval
- Wiederholung zwischen Handles derselben Richtung:
- Die neue Struktur teilte den Code in
DirectionsundShapesauf und versuchte, richtungsbezogenes Verhalten mit formspezifischem Verhalten zu kombinieren- In
Directionsgab estop,left,bottom,right - In
Shapesgab esOval,Rectangle - Mit
createHandleundcreateBoxwurden Handles und Formen kombiniert
- In
- Auf diese Weise wurden Handle-Konfigurationen wie
fourCorners,fourSides,twoSideserstellt und damitRectangle,Oval,HeaderundTextBlockerzeugt
Die Illusion direkt nach dem Refactoring
- Nach dem Refactoring war die gesamte Codegröße halbiert, und die Duplikate schienen verschwunden zu sein
- Man glaubte, beim Ändern des Verhaltens einer bestimmten Richtung oder Form statt mehrerer Methoden nur noch eine Stelle anpassen zu müssen
- Spät in der Nacht wurde die Änderung in master eingecheckt, in der Annahme, den unordentlichen Code eines Kollegen sauber aufgelöst zu haben
Die Probleme, die am nächsten Tag sichtbar wurden
- Am nächsten Tag bat der Vorgesetzte darum, die Änderung zurückzusetzen; damals war das schwer nachvollziehbar, weil der neue Code für sauberer als der alte gehalten wurde
- Mit zeitlichem Abstand zeigte sich, dass diese Einschätzung aus zwei Gründen richtig war
- Der Code wurde neu geschrieben und eingecheckt, ohne mit der Person zu sprechen, die den ursprünglichen Code geschrieben hatte
- Im Gegenzug für weniger Duplikation wurde einfache Änderbarkeit bei neuen Anforderungen geopfert
- Gesunde Engineering-Teams müssen kontinuierlich Vertrauen aufbauen, und großflächiges Neuschreiben von Kollegencode kann der Zusammenarbeit erheblich schaden
Die Kosten der falschen Abstraktion
- Später wurden für viele Handles mehrerer Formen zahlreiche Sonderfälle und Verhaltensweisen nötig
- Um solche Anforderungen in der neuen Abstraktionsstruktur unterzubringen, hätte der Code um ein Vielfaches komplexer werden können
- Umgekehrt war die ursprüngliche „unordentliche“ Struktur dafür vergleichsweise leicht zu ändern, weil die einzelnen Methoden getrennt waren
- Das Entfernen von Code-Duplikation ist nicht kostenlos; man kann eine Fähigkeit gewinnen und dafür eine andere verlieren
„Sauberkeit“ neu denken
- Eine Phase, in der man von „Clean Code“ und der Entfernung von Duplikaten besessen ist, können viele Entwickler durchlaufen
- Wenn man unsicher gegenüber dem eigenen Code ist, ist es leicht, Stolz an messbare Dinge zu knüpfen: strenge Lint-Regeln, Namenskonventionen, Dateistrukturen oder keine Duplikate
- Sobald man gelernt hat, Abstraktionen zu bauen, kann man bei jedem wiederholten Code glauben, Abstraktion sei eine Tugend, und sie anwenden wollen
- Man sollte gründlich darüber nachdenken, mit welchen tatsächlichen Engineering-Ergebnissen das Gefühl von „sauber“ oder „schmutzig“ verbunden ist
- Es geht nicht nur darum, wie Code aussieht, sondern auch darum, wie Code gemeinsam mit den Menschen im Team verändert und angepasst wird
Wann man Clean Code loslassen sollte
- Clean Code ist kein Ziel, sondern ein Versuch, komplexe Systeme zu verstehen
- Er kann ein Abwehrmechanismus sein, der Orientierung gibt, wenn man nicht sicher ist, welche Auswirkungen eine Änderung in der Codebase haben wird
- Anfangs kann es befriedigend sein, komplexen Code durch das Extrahieren von Funktionen oder das Refactoring von Klassen zu vereinfachen
- Man sollte dort jedoch nicht stehen bleiben und zum „Clean-Code-Fanatiker“ werden
- Clean Code sollte als Wegweiser dienen, aber irgendwann muss man ihn loslassen
1 Kommentare
Hacker-News-Kommentare
Codeduplizierung ist manchmal in Ordnung, aber das ist noch kein Beweis dafür, dass Clean Code schlecht ist
Es wirkt, als sei man beim Refactoring etwas übers Ziel hinausgeschossen; wenn es nur darum gegangen wäre, „10 Zeilen wiederholter Mathematik“ in eine Funktion auszulagern, wäre es wohl sauberer gewesen
Das Teammitglied, das diese 10 Zeilen nicht in eine Funktion gepackt hat, hat es auch nicht gut gemacht; ich hätte den PR daher wohl abgelehnt, aber nicht selbst neu geschrieben
Den PR eines anderen einfach neu zu schreiben, ist eine schnelle Beleidigung, die jeden Raum für Diskussion schließt, und oft kennt man den Kontext nicht, in dem der Code so geschrieben wurde
Wenn es keine Coding-Standards gibt, sollte man den PR reviewen und ablehnen; wenn man stattdessen mit der Haltung committet, „Clean Code ist nicht wichtig“, hat man in ein paar Monaten eine Codebasis, an der niemand mehr arbeiten möchte
Was man hier lernen sollte, ist nicht Clean Code, sondern Kommunikation und Rücksichtnahme
Damit nimmt man der ursprünglichen Autorin oder dem ursprünglichen Autor auch die Chance, zu lernen, wie Code von geringer Qualität in der Praxis Zeit verschwendet
Selbst wenn der Clean-Code-Ansatz besser wäre, geht es hier im Kern nicht um den Code, sondern darum, dass jemand seine Zeit ohne nützliches Feedback vergeudet hat
Am besten wäre es gewesen, wenn die ursprüngliche Autorin oder der ursprüngliche Autor den Maßstab anhebt und man selbst an anderen Dingen arbeitet, die tatsächlich Aufmerksamkeit brauchen. Die richtige Antwort war also Code Review
Solche Leute lösen oft lieber Probleme, die keine sind, statt die echten Probleme anzugehen, und schaffen dabei sogar noch zukünftige Probleme
In einer ersten Implementierung ist es oft richtig, solch kleinen Code von vielleicht 10 Zeilen nicht zu abstrahieren. Code, der genau nur diese eine Sache tut, lässt sich später leicht ändern, und 10 Zeilen für einen unbewiesenen Vorteil zu abstrahieren, bringt wenig
Clean Code ist überhaupt nicht wichtig, und die meisten ähnlichen Regeln oder „Prinzipien“ sind es ebenfalls nicht. Vielmehr klammern sich daran oft Leute, die im Lauf der Zeit eine wabenartige Struktur in der Codebasis aufschichten und das rechtfertigen wollen
Das Ergebnis ist oft übermäßig abstrahierter, schwer zu ändernder, langsamer und schwer verständlicher Code
Wenn man überhaupt ein Prinzip braucht, dann nur dieses: Das, was das Programm tatsächlich tut — Datentransformation — klar auszudrücken und leicht änderbar zu machen
Die meisten SOLID-Prinzipien helfen nicht dabei zu erkennen, was die Datentransformation ist und wie sie abläuft, oder stehen dem sogar im Weg; zum Verständnis realer Programme taugen sie daher meist wenig
Wenn man sich das vorgeschlagene Refactoring ansieht, sind diese 10 Zeilen in Wahrheit nicht identisch, sondern nur ähnlich
Die Formel zum Skalieren der linken oberen Ecke eines Rechtecks und die Formel zum Verändern der rechten unteren Ecke einer Ellipse sind unterschiedlich. Oberflächlich sehen sie ähnlich aus, aber an manchen Stellen steht eben
+statt-In diesem Fall steckt die Komplexität in der Geometrie selbst, und auch wenn man das „abstrahiert“, verschiebt man die Unordnung nur an eine andere Stelle, statt sie grundsätzlich zu beseitigen
Vielleicht gäbe es eine clevere mathematische Abstraktion wie ein spezielles Zahlensystem, aber wenn das nicht in die Sprache eingebaut ist, sind die Kosten, spezielle Operationen auf Basis normaler Ganzzahlen zu implementieren, vermutlich höher als der Nutzen
„Clean Code“ braucht ein Rebranding
In mehr als 25 Jahren Berufserfahrung habe ich immer wieder endlosen Widerstand gegen Design Patterns, Abstraktion, das Entfernen von Duplikaten und Ähnliches gesehen
Die Begründungen sind immer dieselben: Abstraktion mache den Code komplexer, man habe keine Zeit für Clean Code, oder alles sei ohnehin nur Geschmackssache und Meinung
Das Ziel von Clean Code ist jedoch, Code bei sich ändernden Anforderungen einfacher und wartbarer zu machen
Der Wert von Software liegt gerade darin, dass sie sich im Lauf der Zeit ändern kann. Wäre das nicht so, könnte man fest verdrahtete Schaltungen verwenden, die viel einfacher und billiger zu implementieren und zu warten sind
Wenn das Refactoring dieses Ziel nicht erreicht hat und die Führungskraft überzeugend sagen kann, dass der Code bei künftigen Anforderungsänderungen schwerer wartbar geworden ist, dann war dieser Code eben nicht „clean“
Dass es keine Diskussion mit der ursprünglichen Autorin oder dem ursprünglichen Autor gab, ist eine Frage von Prozess und Umgangsformen außerhalb des Bereichs von Clean Code. Nur weil jemand unhöflich und eigenmächtig gehandelt hat, verschwindet der Wert von Clean Code nicht
Das bekannte Buch Clean Code nennt „keine Duplikate“ als Bedingung für ein gut entworfenes System und bezeichnet Duplikate als Hauptfeind zusätzlicher Arbeit, zusätzlichen Risikos und unnötiger Komplexität
Nach dieser Definition wird das Entfernen von Duplikaten zum Synonym für Vereinfachung, und das ist falsch
Das Entfernen von Duplikaten bedeutet Einführung von Abhängigkeiten. Wenn diese Abhängigkeit das Problem gut modelliert, kann sie eine gute Abstraktion und Vereinfachung sein; wenn nicht, ist sie nur Kompression im Gewand einer Abstraktion
Zur Einordnung: Dieses Zitat bezieht sich auf die Erwähnung von Kent Becks Simple Design in Clean Code
Die eine ist das subjektive Gefühl, dass irgendein Code gut sei, die andere ist das Befolgen des Buchs Clean Code und der damit verbundenen Richtlinien
Immer mehr Menschen haben das Gefühl, dass sich beides deutlich unterscheidet. Wenn man manche Richtlinien aus dem Buch befolgt, entsteht am Ende womöglich Code, der überhaupt nicht sauber ist
In einer idealen Welt würden alle Clean Code schreiben wollen, aber immer weniger Leute halten Clean Code für einen guten Weg, dieses Ziel zu erreichen
Besonders übermäßige Abstraktion und das Entfernen von Duplikaten gelten für viele inzwischen als Quelle unsauberen Codes
Manchmal ist technische Schuld bereits in die Produktanforderungen selbst eincodiert; dann kann man sie weder durch Refactoring noch durch Best Practices beheben, sondern nur den Schmerz etwas lindern
Ziel und tatsächliche Resultate von Clean Code gehen weit auseinander, weil Menschen selbst mit vorliegenden Anforderungen den zukünftigen Zustand des Codes meist nicht zuverlässig vorhersagen können
Clean Code ruft die schreckliche Frage „Was wäre, wenn?“ hervor, und das wird leicht zu einer Büchse der Pandora, die Releases endlos verzögert
Fast immer ist es besser, auf das hinzuarbeiten, was man weiß, und die Architektur von jemandem mit Domänenerfahrung treiben zu lassen
Viele gute Abstraktionen stecken bereits in Bibliotheken. Leider ist das natürlich nicht immer der Fall
Schlechte Abstraktionen fressen Zeit, und da Code auch ein Mittel zur Kommunikation mit anderen im Team ist, ist viel wichtiger, wie Teammitglieder diese Kommunikation aufnehmen, als wie ich selbst am liebsten denke
Ein Kollege hatte per Copy-and-paste viel Code geschrieben, und du hast ihn nach dem Commit refaktoriert.
Der Kollege hat sich beim Vorgesetzten beschwert, der Vorgesetzte hat dich gerügt, und damit lief es letztlich darauf hinaus, beim nächsten Mal Unordnung in der Codebasis zu lassen.
Daraus kann man zwar etwas lernen, aber das heißt nicht, dass Copy-and-paste besser ist als Funktionen zu schreiben.
Die Refaktorierung hätte als separate Änderung mit Erwähnung des ursprünglichen Autors eingereicht und reviewt werden sollen.
Dass der Kollege allerdings nicht direkt mit dir gesprochen, sondern es an den Vorgesetzten eskaliert hat, und dass der Vorgesetzte das Zurückdrehen angeordnet hat, ist ein ziemlich schlechtes Zeichen.
Mögliche Antworten wären „Nein, weil …“ oder in seltenen Fällen „Stimmt, ich mache das morgen“.
So vermeiden alle ohne Aufsehen das Problem, und vielleicht lernt sogar jemand etwas.
Der Autor hat aber einen wichtigen Kontext ergänzt: Er hat die Möglichkeit künftiger Anforderungsänderungen gegen weniger Duplikation abgewogen, und später brauchte man tatsächlich viele Sonderfälle und spezielles Verhalten pro Handle jeder Form.
Das zeigt den Unterschied zwischen zufälligem DRY und wesentlichem DRY.
Bibliothek A und B wirken oft konsistent zueinander, sodass Code gleich aussieht, aber das kann in Wirklichkeit Zufall sein.
Selbst wenn ein Wrapper um lib A und ein Wrapper um lib B denselben Code enthalten, führt eine Abstraktion zu einem schlechten Ergebnis, wenn sie in Wahrheit völlig unterschiedliche Dinge kapseln.
Mit „15“ ist es genauso. Wenn „alle Listen immer 15 Einträge anzeigen sollen“, dann sollte man die 15 abstrahieren; wenn es aber einmal „Top 15 Songs auf der Startseite“ und einmal „standardmäßig 15 Kommentare unter einem Song“ sind, dann ist es die schlechtere Wahl, diese beiden 15er zusammenzufassen.
Das zweite Warnsignal ist, dass auch die Refaktorierung selbst keine gute Refaktorierung war. Es fehlt an Modularität, es bleibt weiterhin gekoppelt, und es wurde nur ein wenig Duplikation reduziert.
Es kann klingen wie: „Dein Code war nicht gut, also habe ich ihn neu geschrieben. Hast du ein Problem damit?“
Die beste Lösung wäre wohl eine Form von „spätem Review“ gewesen: um eine Erklärung bitten, eine Lösung vorschlagen und hoffen, dass der ursprüngliche Autor es nicht einfach ignoriert.
Entscheidend ist, zu erkennen, dass sich Code in Zukunft ändert. Anforderungen ändern sich, und der zugehörige Code ändert sich ebenfalls.
Wenn man Abstraktionen schafft, die diese Änderbarkeit einschränken, schadet man dem zukünftigen Ich dabei, die Codebasis anzupassen.
Gerade an Abstraktionen, in die viel Zeit und Mühe geflossen sind, hängt man emotional oft zu sehr, sodass man noch mehr Zeit verschwendet, Veränderungen in eine elegante Lösung hineinzuzwingen.
KISS ist richtig.
Dieser Fall wirkt wie das Problem „Niemand mag Klugscheißer“.
Das passiert besonders oft, wenn Junior-Entwickler noch nicht gelernt haben, dass die Arbeit nicht nur zwischen ihnen und dem Computer stattfindet, sondern zwischen ihnen, dem Computer und anderen Menschen.
Eine sauberere Version wäre vermutlich akzeptiert worden, wenn man mit dem ursprünglichen Autor tatsächlich gesprochen und sich auf einen besseren Weg geeinigt hätte.
Statt „Mach’s gut, Clean Code“ wäre als Titel wohl Ich habe gelernt, wie man im Team arbeitet treffender.
Komplexerer Code wurde womöglich als Bedrohung wahrgenommen und derjenige, der über die Führungskette in Schach gehalten werden musste.
Gerade als Junior sollte man wissen, dass das stark vom Kontext abhängt. An vielen Arbeitsplätzen ist Einfachheit wichtiger als Flexibilität, verwirrende Sprachfeatures werden vermieden, und es werden Arbeitsweisen und Sprachen bevorzugt, bei denen man viele Stunden vor der Tastatur verbringt.
Dagegen steht eine Philosophie, die lebenslanges Lernen, Software-Handwerk und Reflexion über Prozesse betont — und die kollidiert mit solchen Umgebungen vollständig.
Der Versuch, besser zu werden, kommt bei Menschen, die nicht wachsen wollen, nicht gut an.
Beides sind legitime Lebensentscheidungen. Software kann ein Ort sein, an dem man sieben Stunden arbeitet und dann bequem nach Hause geht, oder eine Welt der Erkundung, deren Grenzen nur Fantasie und Intellekt setzen.
Wenn du Neues entdecken willst, solltest du vorsichtig damit sein, Jahrzehnte mit Menschen zu verbringen, die Angst vor dem Unbekannten haben.
Kurz gesagt: Wenn die Anreize zwischen dir und deinen Kollegen nicht übereinstimmen, solltest du über einen Wechsel nachdenken.
Dieser Text hat einen guten Grund, so lange geschätzt zu werden, und hat vielen Entwicklern, die in dieselbe Falle tappen, die Augen geöffnet.
Selbst wenn man allein arbeitet, kann man saubere Abstraktionen überkonstruieren und später an genau diesen Abstraktionen scheitern.
Das Problem ist, dass Änderungen ohne Review direkt in den main-Branch committet werden.
Jede Codeänderung sollte ein Peer-Review bekommen, sei es durch Pair Programming oder einen klassischen PR, damit so etwas nicht eskaliert.
Der Vorgesetzte war ein schlechter Vorgesetzter. Statt zu schimpfen hätte er erkennen müssen, dass ein guter Prozess fehlt.
In einem Team mit Code-Review-Kultur hätte ein übereifriger Neuzugang, selbst wenn er so etwas versucht, von Kollegen wohl ein „Danke, aber so nicht“ oder „Gute Idee, aber lass es uns so machen“ gehört.
Unabhängig davon finde ich, dass man Code nicht ohne guten Grund refaktorieren sollte. Auch schlechter Code kann Code sein, den man kaum je wieder anfassen muss.
Der beste Zeitpunkt für Refaktorierung ist, wenn man merkt, dass man das Verhalten ändern muss, die bestehende Struktur es aber unnötig schwer macht.
Leider scheint hier die falsche Lehre gezogen worden zu sein.
Ich verstehe nicht, warum alle nur darüber reden, was er stattdessen hätte tun sollen. Das ist eine völlig berechtigte Lehre.
Im Finanzwesen arbeitet man ständig mit Produkten, die in gewisser Weise ähnlich, aber nicht völlig gleich sind. Aktienoptionen und Devisenoptionen sind beide Optionen, haben einen Ausübungspreis und werden mit irgendeiner Version von Black-Scholes bepreist, sind in der Praxis aber extrem unterschiedlich.
Die Versuchung ist groß, dass jemand Neues, der dazukommt, viel Verhalten vereinheitlichen will, und wir sagen jungen Entwicklern oft aus der Perspektive des Vorgesetzten im Originalbeitrag, sie sollen einen Gang runterschalten und die Dinge getrennt halten.
Es geht nicht darum, sich von Clean Code zu verabschieden, sondern darum, dass man übermäßige Abstraktion vermeiden muss, um Clean Code tatsächlich beibehalten zu können.
Was ein allgemeiner Handle tut, ist eine affine Transformation, also Verschiebung, Rotation, Skalierung und Spiegelung; bei gleichmäßiger Skalierung ist es eine Ähnlichkeitstransformation. Das funktioniert garantiert bei jeder Form.
Das eigentliche Problem ist, dass man nichts abstrahieren sollte, das man nicht gut genug versteht. Das gilt besonders, wenn man nicht weiß, welche Möglichkeiten es in Zukunft geben wird.
Wenn man zum Beispiel denkt, Ellipsen seien nur Kreise und Ellipsen, ist man ziemlich überrascht, wenn das erste „echte“ Oval auftaucht.
Weil der Autor unordentlichen Code und „nicht-sauberen“ Code nicht aktiv voneinander trennt, versuchen andere, diese Unterscheidung zu treffen.
DRY ist wertvoll, aber Abstraktion hat ihren Preis. Es ist immer ein Trade-off.
Einer der Gründe, warum ich Sprachen wie Haskell mag, ist, dass der Abstraktionsmaximalismus nicht auf der Ebene des Codings, sondern auf der Sprachebene stattfindet.
Darüber, was ein Applicative tun soll, sind sich alle einig. Die Herausforderung verschiebt sich weiter nach rechts in der Pipeline von der Idee zum Code, nämlich hin zur Frage, das zum Problem passende Applicative zu finden.
Die meisten objektorientierten Design Patterns haben in so einer Umgebung natürliche und wohldefinierte Entsprechungen, was hilft, projektspezifische Abstraktionen zu reduzieren.
Für Einsteiger sind die anfänglichen Kosten, die Sprache zu lernen, hoch, und oft öffnet man fremden Haskell-Code und muss erst einmal etwa 12 unbekannte Compiler-Erweiterungen verstehen.
Trotzdem sehe ich das als Nettogewinn, weil man diese Compiler-Erweiterungen danach in jedes weitere Projekt mitnehmen kann.
Für Experten gibt es den Nachteil, dass man fast einen Doktortitel braucht, um ein Gefühl dafür zu bekommen, wie viel RAM cats verbrauchen wird.
Wenn ein Stück Code Applicative oder Monad verwendet, kann man sein Verhalten dank dieser Gesetze kennen.
Objektorientierte Design Patterns haben solche Gesetze nicht. Es sind lose definierte Konzepte oder Muster, offen für individuelle Interpretation und Anpassung, aber ohne konkrete Gesetze, die genau sagen, was sie sind und welches Verhalten oder welche Ausgabe man erwarten sollte.
Aber beim Beispiel aus dem Blogpost kann ich nur sagen, dass ich sehr oft dem Pfad von Clean Code gefolgt bin und mir damit später selbst ins Bein geschossen habe.
Wenn ich später erst die Abstraktion studieren muss, um es wieder zu verstehen, dann ist diese Abstraktion nicht nützlich.
Objektorientierung passt am natürlichsten zu GUI-Widgets und Editor-Implementierungen.
Die Regel, die die anderen Regeln der Objektorientierung zusammenführt, lautet Don’t ask, tell.
In diesem Beispiel würden Handles auf bekannte Weise gemeinsam genutzt, und es gäbe Kontrollpunkte je nach Form. Wenn sich ein Box-Handle ändert, könnte man
updateControlPoints(changedHandle)aufrufen, und wenn sich ein Kontrollpunkt ändert,updateBoxHandles(changedControlPoint).Es gibt sicher auch andere saubere Trennungen, aber ich würde es so machen.
Aber darüber, welche Parsing-Bibliothek man verwenden sollte, ob
Data.Text.StrictoderData.Text.Lazy, wie stark man Point-free-Style einsetzen sollte, ob.oder>>>oder andere Kombinatoren, und welche Sprach-Erweiterungen man nutzen sollte, herrscht weit weniger Einigkeit.Ich mag Haskell, aber es hat ähnliche Probleme wie andere Sprachen. Die Leute können sich wirklich nicht auf die beste Art einigen, Dinge zu tun.
Dann bekommt man das Haskell-Typsystem und vorhersagbare Performance.
Man hätte nur die wiederkehrenden mathematischen Teile in eine separate Funktion auslagern und sie aus den Resize-Funktionen aufrufen müssen.
Dass die Resize-Funktionen selbst wiederholt wirkten, lag daran, dass sie ein Interface bildeten; deshalb war das Refactoring, das sie entfernte, falsch.
Aber die Mathematik, die diese Funktionen ausführten, war ein völlig legitimer Kandidat, um den Code aufzuräumen.
Am Ende bekommt man viele kleine, pure Funktionen, aber das ist kein Problem, weil sie leicht zu testen und zu refaktorieren sind.
Es gibt viele Wege und Muster, um dasselbe Problem zu lösen, aber bestehende Interfaces, die andere verwenden, auseinanderzureißen, ist kein guter Start.
Der Vorteil von Abstraktion besteht darin, dass man das Innere flexibler verändern kann, ohne den Rest zu beschädigen.
Ein guter erster Schritt ist, wie gesagt, unten flexiblere mathematische Funktionen zu bauen und den Rest unverändert zu lassen.
Danach kann man im Team tiefer darüber sprechen, wie man kommunizieren will, während das Interface eine klare Fassade bietet und die Implementierung abstrahiert wird.
Was hier beschrieben wird, wirkt wie schlechte Abstraktion
Das eigentliche Problem ist, dass Leute mit nur zwei oder drei Beispielen im Kopf etwas entwerfen, ohne zu prüfen, ob es wirklich allgemein genug ist, um künftige Anwendungsfälle abzudecken
Man sollte von unten nach oben abstrahieren, nicht von oben nach unten
Es gibt keinen Grund, einen einzigen Punkt zu schaffen, an dem alles passiert und der
2^nKombinationen behandeln mussTrotzdem kann man Wiederholungen reduzieren, Änderungen an einer Stelle vornehmen und sich zugleich die Möglichkeit für maßgeschneiderte Anpassungen erhalten, wenn sie nötig sind
Deshalb reicht es nicht, einfach zu sagen: „Vermeide schlechte Abstraktion“ oder „Gestalte es so, dass künftige Nutzung abgedeckt ist“
Im Allgemeinen kann niemand die Zukunft vorhersagen
Selbst wenn sich eine Abstraktion richtig anfühlt, sollte man sie nur mit äußerster Vorsicht verwenden. Es ist besser, ein extremer Skeptiker zu sein
Wenn man in mathematischem Code eine Sache anfasst, wirkt sich das auf alles aus
Ab einem gewissen Punkt ist es einfacher, etwas doppelten Code stehen zu lassen, die Ähnlichkeit durch Hinweise in der Code-Struktur oder Kommentare zu markieren und mit der Zeit die entscheidenden Unterschiede in Kommentaren hervorzuheben
Der Clean-Code-Ansatz für diese Art von Code geht mit sich ändernden Anforderungen nicht gut um
Am Ende wird daraus leicht ein Haufen Hilfsfunktionen mit mehreren Code-Pfaden je nach Aufrufstelle
Manchmal kann man das abstrahieren, und wie manche vielleicht schon bemerkt haben, beschreibt der vorherige Satz objektorientierten Polymorphismus
Aber woher soll man die genauen Anforderungen im Voraus kennen? Wenn man in so einer frühen Phase „Clean Code“ versucht, endet das nur in schlechter Abstraktion oder Overengineering
Der richtige Ansatz ist, erst einmal nichts zu tun, mögliche Probleme zu notieren und zu warten, bis man mehr über die Problem-Domäne weiß
Code, der wie im ursprünglichen Beispiel etwas doppelt ist, aber leicht zu verstehen bleibt, bereitet mir keine großen Sorgen
Wenn ich aber dasselbe Problem an drei Stellen beheben oder Code für ein neues Feature an drei Stellen ergänzen muss, dann werde ich unruhig
Ab dann fange ich an zu überlegen, ob sich der zu ändernde oder zu ergänzende Code irgendwie an eine gemeinsame Stelle verschieben lässt
Leute lesen über Design Patterns oder DRY, wenden es beim Lernen meist falsch an und schieben die Verantwortung dann dem Originalwerk zu
Rob Pike sagte einmal, einer der Gründe, warum Go so geworden ist, sei: „Ein kleines Copy ist besser als eine kleine Abhängigkeit“
Je länger ich programmiere, desto besser verstehe ich, was damit gemeint ist
Wiederholung an sich ist nicht das Problem. Entscheidend ist, die Einheit zu verstehen, die man gerade liest und vielleicht auch ändern wird
DRY bedeutet oft, noch eine weitere Abstraktion einzuführen, und wenn diese Abstraktion nicht ausreichend orthogonal ist, muss man nun nicht mehr eine, sondern zwei Einheiten berücksichtigen, was die Sache nur schlimmer macht