Die Kosten, von denen YAGNI nie sprach
(newsletter.kentbeck.com)- YAGNI ist keine einfache Sparregel im Sinne von „Schreibe keinen Code, den du noch nicht brauchst“, sondern ein Prinzip, das die Kosten behandelt, wenn man auf Basis von Vermutungen Struktur vorwegnimmt, bevor der Bedarf feststeht
- Im Zentrum des Problems steht nicht Design an sich, sondern wann man designt; zu frühe Strukturierung kann genauso riskant sein wie zu späte Strukturierung
- Vorab geschaffene Struktur erzeugt zugleich Optionskosten, weil sie Wahlmöglichkeiten schließt, bevor Informationen vorliegen, und NPV-Kosten, weil sie Kosten vorzieht und Erträge verzögert
- Selbst wenn die Kosten der Codegenerierung nahezu gegen null gehen, verschwindet YAGNI nicht; im Gegenteil kann billige Generierung vermutungsbasierte Frameworks noch leichter entstehen lassen
- Die Schlussfolgerung „Baue es, wenn du es brauchst“ liegt nicht an den Kosten des Codeschreibens, sondern daran, dass ungenutzte Optionen und ungenutztes Geld weiterhin Wert haben
YAGNI verbietet Design nicht
- YAGNI steht für „You Aren’t Gonna Need It“ und ist keine Ausrede dafür, niemals etwas zu entwerfen, das man gerade nicht braucht
- Wenn etwas gebraucht wird, kann man es bauen; entscheidend ist jedoch das Timing
- Ausgangspunkt ist die oft erzählte Situation in einem Projekt: „In drei Wochen wird die einfache Implementierung nicht mehr reichen, also möchte ich jetzt etwas Komplexeres bauen“ – worauf wiederholt die Antwort „You aren’t going to need it“ kam
- Dieses Prinzip betrachtet sowohl zu frühes als auch zu spätes Schaffen von Struktur als riskant
Das Problem sind nicht die Kosten des Codeschreibens, sondern vermutungsbasierte Struktur
- Eine verbreitete Interpretation sieht YAGNI als Sparregel: „Noch nicht benötigter Code ist teuer, also schreibe ihn nicht“
- Doch YAGNI zielt nicht auf die Kosten der Codeproduktion, sondern auf spekulative Struktur (speculative structure), die geschaffen wird, bevor echte Features sie benötigen
- Eine solche Struktur verursacht zu unterschiedlichen Zeiten und aus unterschiedlichen Gründen zwei Arten von Kosten
Erste Kostenart: Optionen
- Wenn man Struktur baut, bevor das Feature eintrifft, committet man sich auf Grundlage von Vermutungen gegenüber Anforderungen, die man noch nicht kennt
- Die im Voraus vorbereitete Funktion unterscheidet sich meist von der Funktion, die tatsächlich kommt; dadurch zahlt man doppelt
- die Kosten, um eine Struktur in der falschen Form zu umgehen
- die Kosten, um diese Struktur wieder herauszureißen
- Dieses Problem erschöpft sich nicht in der Aussage „Vorhersagen sind schwierig“
- Selbst wenn die Vermutung stimmt, bleibt ein Verlust, weil man die Option verliert, sich nicht vorab festzulegen und später die richtige Struktur zu bauen
- Warten ist keine Faulheit, sondern das Halten eines Vermögenswerts namens Option
Zweite Kostenart: NPV
- So wie Geld einen Zeitwert hat, haben auch Features einen Zeitwert
- Wenn man jetzt Struktur für ein Feature baut, das erst in drei Monaten gebraucht wird, werden Kosten vorgezogen und der Launch des Features, das tatsächlich Geld verdient, verzögert
- Diese Kosten entstehen selbst dann, wenn die Vermutung stimmt
- Auch perfekte Vorhersage kann die Reihenfolge von Kosten und Erträgen nicht ändern; der Verlust entsteht aus dem Abstand, in dem Kosten vor Erträge gestellt werden
- Optionskosten betreffen das Problem „Committe dich nicht, bevor die Information vorliegt“, NPV-Kosten das Problem „Zahle nicht, bevor es nötig ist“
- Selbst beim Einwand „Späteres Ändern ist zu teuer“ kann genau dieser teure Umbau wiederum eine Vorhersage sein
Auch wenn Codegenerierung billig wird, bleibt YAGNI
- In keiner der beiden Kostenarten sind die Kosten für das Tippen von Code enthalten
- Wenn die Kosten des Codeschreibens nahezu gegen null gehen, bricht die sparorientierte Interpretation von YAGNI – „Code ist billig geworden, also kann man ihn vorab bauen“ – zusammen
- Da YAGNI jedoch keine Sparregel ist, macht billige Codegenerierung YAGNI nicht überflüssig
- Optionskosten entstehen nicht durch die Menge an Aufwand, sondern durch das Commitment, das künftige Wahlmöglichkeiten schließt
- NPV-Kosten entstehen nicht durch den Produktionspreis, sondern durch das Timing der Cashflows
- Kostenlose Generierung schwächt YAGNI nicht, sondern erleichtert eher den Bau vermutungsbasierter Frameworks
- Die generierte Struktur verursacht weiterhin beide Kosten, und weil man sie nicht selbst geschrieben hat, kann das Verständnis dafür sogar geringer sein
- Die Schlussfolgerung lautet nicht „Warte, weil Code teuer ist“, sondern: Baue es, wenn du es brauchst, weil Optionen und Geld wertvoller sind, solange sie nicht ausgegeben wurden
1 Kommentare
Hacker-News-Meinungen
Ich denke, auch die Kosten für Strukturänderungen sind gesunken
Dank AI sind die Kosten gesunken, vor einer Strukturänderung das Verhalten durch Tests abzusichern, und auch die Kosten für die Umsetzung von Migrationen ohne Downtime sind niedriger geworden
Einer der großen Gründe, warum Rust schon vor AI Aufmerksamkeit bekam, war ebenfalls, dass die Kosten für Änderungen an der internen Struktur einer Anwendung niedrig sind, und jetzt gilt das umso mehr
Die Opportunitätskosten, wenn man Strukturen nicht sicher ändern kann, sind stark gestiegen, und aktuell optimiere ich vor allem auf die Fähigkeit, große Teile von Code und Produkt schnell und sicher ändern zu können
Allerdings dauerten Strukturänderungen vor AI viel länger, daher könnte man auch sagen, dass der Wert dessen, worauf man jetzt optimiert, eher gesunken ist
Es ist weiterhin wertvoll, aber vielleicht etwas weniger als früher
Mit der Zunahme von fragilen, AI-generierten Tests sind die Kosten für Strukturänderungen höher geworden als früher
Eine Testsuite aufzuräumen, damit sie den Kern des Problems prüft und nicht zufällige Designentscheidungen, ist etwas, worin AI noch nicht gut ist
Aber es ist auch viel zu einfach, stattdessen eine ungefähr zu 75 % ausgereifte fragile Testsuite zu bekommen
Viele Leute sind zufrieden damit und betrachten den Schritt von „ein paar mittelmäßigen, fragilen Tests, die Menschen geschrieben haben“ zu „vielen mittelmäßigen, fragilen Tests, die AI geschrieben hat“ als objektive Verbesserung
Ich stimme völlig zu, dass man Tools so nutzen sollte, aber daraus folgt schwerlich, dass man sich keine Sorgen machen muss, zu früh ein falsches Luftschloss zu bauen
Einen perfekten Testvertrag zu entwerfen, der Refactorings standhält, bleibt ziemlich schwierig
Altes kommt also wieder wie neu zurück
Von der kontextuellen Effizienz von Ansätzen wie DDD oder Clean Architecture bis hin zu solchen Punkten: AI schafft keine neuen Kompromisse, sondern wirkt wie ein Verstärker
Sie steigert die Produktivität von Teams, die es richtig machen, und vergrößert zugleich die Schulden von Teams mit niedrigen Qualitätsmaßstäben bei Design und Architektur
Der einzige Gewinn ist, dass man nicht tief nachdenken muss
Tiefes Nachdenken kostet aber gar nicht so viel Zeit oder Mühe; man wird also von Leuten überholt werden, die AI genauso einsetzen, aber genug nachdenken, damit es nicht zu Herumgepfusche wird
Kent Beck vergleicht Code, den man noch nicht geschrieben hat, mit einer Finanzoption, die man zu einem bestimmten Preis kaufen kann
Aber das ist eben nur eine Metapher, und wenn man sie zu weit treibt, wird es seltsam
Hat man unendlich viele Optionen, wenn man noch gar keinen Code geschrieben hat? Auch wenn man noch keine Zeit investiert hat, scheint das nicht zu stimmen
Es könnte auch als Begründung dienen, in der Planungsphase zu bleiben und das Schreiben von Code unbegrenzt aufzuschieben, um nichts festzulegen
Wenn die Metapher dennoch funktioniert, könnten die Kosten im Lesen von Code liegen
Nicht geschriebener Code muss nicht gelesen werden, und wenn man Coding Agents einsetzt, verschmutzt er den Kontext auch nicht mit irrelevanten Details
Code, den man noch nicht geschrieben hat, muss man auch nicht testen, und Tests, die man noch nicht geschrieben hat, kosten auch keine Laufzeit
Deshalb ist es gut, ein Projekt so klein wie möglich zu halten; wenn man Features aufschiebt, kann man das Wachstum der Codebasis maximal verlangsamen
Das bedeutet auch, dass man viele Kosten vermeiden kann, wenn man fremden Code ausführt
Wenn man eine Standard-API nutzen kann, muss man die Implementierung nicht im Detail verstehen oder deren Tests ausführen, aber das Hinzufügen von Abhängigkeiten birgt Risiken
Nicht geschriebener Code hat keinen Wert
Damit Code, der heute geschrieben wird, Wert schafft, muss er die heutige Anfrage oder das heutige Issue lösen oder darauf ausgerichtet sein, morgen etwas leichter zu machen
Wenn man mit einer hackigen Lösung technische Schulden aufnimmt oder Zeit auf etwas verschwendet, das YAGNI widerspricht, schafft man keinen Wert
Entscheidend ist nicht der nicht geschriebene Code, sondern der Code, den man künftig schreiben wird, und dessen Zweck
Man muss den richtigen Kompromiss finden zwischen dem Erledigen des heutigen Tickets bzw. To-dos und dem Vermeiden, sich künftig selbst ins Bein zu schießen
Code zu schreiben ist eine Verpflichtung; der heutige Wert ist sichtbar, der morgige Wert aber eher eine Schätzung
Trotzdem fallen später immer Kosten an, also schätzt man, was gebraucht werden könnte, um die Kosten zu minimieren
Ich finde, dass Best Practices im AI-Zeitalter nicht genug betont werden, aber Kent übersieht hier etwas vollständig
Es hat erheblichen Wert, schneller herauszufinden, welche Funktionalität benötigt wird
Eine spekulative Struktur zu bauen, kann ein Zwangsmechanismus sein, der Anforderungen konkretisiert, und zumindest beginnt er, Fehlermuster sichtbar zu machen
Das kann teurer sein als abzuwarten, daher sollte man das bei den meisten Anforderungen nicht tun, aber manchmal kann es die beste Wahl sein
Die Kosten, etwas Falsches zu bauen, sind jetzt viel niedriger, und dadurch ändert sich auch die Rechnung rund um YAGNI
Trotzdem muss man weiterhin rechnen, und derzeit muss jedes Team selbst herausfinden, wie sich das für es verändert hat
Wenn man sie nicht wegwirft, wird sie zu einem Zwangsmechanismus für ein schmutziges Ergebnis
Bei YAGNI geht es um das Problem, Dinge zu bauen, die nicht in den aktuellen Anforderungen enthalten sind, weil man erwartet, dass sich die Anforderungen später ändern
Das ist etwas anderes, als aktuelle Anforderungen und Einschränkungen zu konkretisieren
Solche Dinge entstehen vor allem aus Gesprächen mit Stakeholdern, Nutzern und Kunden sowie aus Ressourcen, technischen Einschränkungen und Fähigkeiten
Prototypen sind wertvoll, wenn man mit Stakeholdern spricht, ein Projektmanagement-Modell erstellt oder Engineering-Forschung betreibt
Alles andere stellt Ursache und Wirkung auf den Kopf
Der Ansatz, laufende Software als Asset zu betrachten, ist richtig
Allerdings sind die Kosten für das Ausführen und erneute Erstellen deutlich gesunken
Nicht gesunken sind die Kosten dafür, die Vertrauenskette in vorhersagbare Ergebnisse zu brechen
Eine bestimmte Version laufender Software hat Vertrauen aufgebaut; wenn man sie von Grund auf neu schreibt, wird dieses Kapital beim Release zurückgesetzt
Irgendwann hat sich meine Sichtweise geändert
Ich verschiebe Konkretes mit YAGNI und schreibe, soweit möglich, eine abstrakte Version
Soll ich einen UserStore bauen? Das wirkt am einfachsten, aber vielleicht brauche ich gar keine konkrete Form namens User
Also baue ich einen Store, der alles aufnehmen kann, was speicherbar ist
Wenn man das nicht gewohnt ist, sieht es nach Overengineering und Generics-Wildwuchs aus, aber paradoxerweise ist es die Art, die sich am wenigsten auf irgendeine konkrete Implementierung festlegt
Man hat nicht nur ein UserStore-Interface gebaut, das vermutlich nicht nötig war, sondern auch noch eine verallgemeinerte Store-Abstraktion, die ganz sicher nicht nötig war
Um sich nicht auf eine konkrete Implementierung festzulegen, hat man klebrige Schichten implementiert, die nicht nötig sind und sehr wahrscheinlich auch künftig nicht nötig sein werden
Wenn eine Abstraktion nicht auf einem tatsächlichen Bedarf basiert, ist sie, selbst wenn sie später nötig wird, höchstwahrscheinlich fast falsch gebaut
Am Ende wird man UserStore ohnehin brauchen, also hätte man damit anfangen sollen
Ich stimme nicht zu, dass „das Argument nicht lautet, Vorhersagen seien schwierig, als ob ein schärferer Architekt das vermeiden könnte“
Dieses Argument funktioniert nur unter der Prämisse, dass Vorhersagen schwierig sind
Wenn man eine sehr wahrscheinliche Funktion im Voraus vorbereitet und alles zusammenpasst, liefert man schneller aus
Es gibt auch keine Garantie, dass das Team zwangsläufig wächst oder die Besetzung gleich bleibt; kurz vor der Deadline hektisch YAGNI nachzuholen fühlt sich schlimmer an, als Selbstbeschränkung zu feiern
Kürzlich musste ich funktional aus einer Codebase herauskommen, in der sich sehr viel YAGNI angesammelt hatte, und selbst mit Agents war das eine riesige Arbeit
Wie weiß man in einem verteilten System, was wirklich in Benutzung ist? Ich habe Dinge übersehen, der Agent hat Dinge übersehen, und insgesamt dauerte es viel länger als nötig
Es war auch kein bloßes 1:1-Porting, sondern sollte eine Gelegenheit zur Vereinfachung sein, deshalb musste ich vollständig verstehen, wie das alte System funktioniert
Auch Dinge, die tatsächlich überhaupt nicht genutzt wurden, gehörten zum Verständnisumfang, wenn man sie nicht als solche identifizieren konnte
Am Ende läuft es meiner Ansicht nach darauf hinaus, Probleme zu erkunden und Lösungen zu implementieren
Das falsche Problem zu lösen hat immer Kosten, und es kostet auch, eine schlechte Lösung für etwas zu implementieren, das gar nicht nötig ist
Softwareentwicklung kann manchmal zu bloßem Trial and Error werden, statt über Strategien zur Erkundung und über die Menge der Probleme nachzudenken
Es gibt Fälle, in denen es langfristig hilft, ein Problem in eine bestimmte Richtung tiefer zu erkunden, als unmittelbar nötig wäre; aber ziellos Lösungen zu implementieren ist nie gut
Ich denke, was Kent Beck wirklich kritisiert, ist die Haltung, „für den Fall der Fälle“ etwas zu implementieren, nur weil es in Zukunft vielleicht nötig sein könnte
Es hieß: „Wenn man Struktur baut, bevor die Funktion kommt, legt man sich auf eine Vermutung fest“, aber ich denke, in beiden Fällen stellt man Vermutungen an
Die Wahrscheinlichkeit, dass die Funktion kommt, kann hoch sein, aber sicher ist sie nicht
Wenn man die Struktur jetzt nicht baut, entstehen Refactoring-Kosten; baut man sie zu früh und die Funktion kommt nicht, verschwendet man Aufwand
Wie sehen Kosten, Wahrscheinlichkeiten und Trade-offs zwischen diesen Möglichkeiten aus? Das hängt natürlich von der Situation ab
YAGNI als Ganzes ist absichtlich eine starke Verallgemeinerung
Letztlich kommt es auf die Umstände an
In beiden Richtungen gibt es oft viele Vermutungen und vage Handwedelei, ähnlich wie bei verlässlichen Aufwandsschätzungen
Manche Entwickler, die mit einer unsicheren Welt schlecht zurechtkommen, suchen für alles nach Schwarz-Weiß-Regeln
https://www.sebastiansylvan.com/post/the-perils-of-future-co...
Kurz gesagt schlägt er sich auf die Seite von YAGNI
Ich habe in Kent Becks Texten noch nie etwas gesehen, das für eine Chipfirma nützlich wäre
In Chipfirmen müssen viele Menschen über lange Zeit arbeiten, Kunden können bis zur Fertigstellung nichts sehen, und um Geld zu verdienen, muss man in Stückzahlen von Millionen verkaufen
Hardware hat starke Einschränkungen
Software wurde ursprünglich genau deshalb erfunden, um solchen Einschränkungen zu entkommen
Es stimmt wohl, dass viele Chipfirmen ihre laufende Arbeit nicht teilen, aber Simulationen, Prototypen und Engineering Samples zu teilen ist möglich und passiert auch tatsächlich
Natürlich muss man dafür normalerweise ein großer Kunde sein
Erkenntnisse aus einer Branche, in der Änderungskosten vergleichsweise niedrig sind, lassen sich nicht einfach auf Branchen mit hohen Änderungskosten übertragen, und umgekehrt gilt das oft genauso
Wie planen Chipfirmen solche Projekte? Agile, Wasserfall, oder nutzen sie ein anderes Framework als die Softwarebranche?