5 Punkte von GN⁺ 2025-06-18 | 1 Kommentare | Auf WhatsApp teilen
  • Komplexität ist der gefährlichste Faktor in der Entwicklung
  • Echte Effizienz entsteht aus einem pragmatischen Ansatz, der Komplexität vermeidet, etwa durch eine „80/20-Lösung“
  • Es ist wichtig, eine ausgewogene und flexible Haltung zu Tests und Refactoring zu bewahren
  • Betont wird die Nutzung von Tools sowie die Gewohnheit, leicht lesbaren und wartbaren Code zu schreiben
  • Vor übermäßiger Abstraktion und Trends wird gewarnt, stattdessen wird eine Haltung empfohlen, die auf Einfachheit setzt

Einleitung

  • Dieser Text ist eine Sammlung von Gedanken eines Grug-Brain-Entwicklers, der aus Erfahrung gelernte Erkenntnisse aus vielen Jahren Softwareentwicklung zusammenfasst
  • Der Grug-Brain-Entwickler hält sich selbst nicht für besonders klug, hat aber durch langjähriges Programmieren vieles gelernt
  • In der Hoffnung, dass andere aus Fehlern lernen, teilt er seine Einsichten auf einfache und witzige Weise
  • Komplexität ist der größte Feind im Entwicklerleben
  • Komplexität schleicht sich heimlich in die Codebasis ein und bringt selbst anfangs leicht verständlichen Code nach und nach an einen Punkt, an dem Änderungen kaum noch möglich sind

Den Dämon der Komplexität bändigen

  • Komplexität dringt lautlos ein wie ein unsichtbarer Geist, und Projektmanager sowie Nicht-Grug-Entwickler bemerken sie oft nicht
  • Der beste Weg, Komplexität zu verhindern, ist, „Nein“ zu sagen
    • „Wir bauen dieses Feature nicht“
    • „Wir führen diese Abstraktion nicht ein“
    • Natürlich kann es der Karriere eher nützen, „Ja“ zu rufen, aber dem Grug-Brain-Entwickler ist eine ehrliche Entscheidung sich selbst gegenüber wichtiger
  • Je nach Situation sind auch Kompromisse („ok“) nötig; in solchen Fällen wird bevorzugt ein Problem mit einer 80/20-Lösung (nach dem Pareto-Prinzip) einfach gelöst
  • Dem Projektmanager nicht alles im Detail zu sagen und es in Wirklichkeit per 80/20-Ansatz zu erledigen, kann ebenfalls eine kluge Strategie sein

Codestruktur und Abstraktion

  • Die passende Einheit im Code (Cut-Point) zeigt sich mit der Zeit ganz natürlich; deshalb ist es besser, frühe Abstraktion zu vermeiden
  • Ein guter Cut-Point hat idealerweise nur eine schmale Schnittstelle zum Rest des Systems
  • Frühe Versuche der Abstraktion scheitern leicht, und erfahrene Entwickler versuchen erst dann langsam zu strukturieren, wenn die Form des Codes sich einigermaßen gesetzt hat
  • Weniger erfahrene oder „Big-Brain“-Entwickler neigen dazu, zu Beginn eines Projekts übermäßig zu abstrahieren und hinterlassen damit Wartungslast

Teststrategie

  • Bei Tests sind Maß und Ausgewogenheit wichtig
  • Bevorzugt wird, Tests erst nach dem Prototyping zu schreiben, wenn sich der Code einigermaßen stabilisiert hat
  • Unit-Tests werden anfangs genutzt, in der Praxis zeigen aber Tests auf mittlerer Ebene (Integrationstests) den größten Effekt
  • End-to-End-Tests sind ebenfalls nötig, aber zu viele davon führen schnell zu unwartbaren Zuständen; daher sollten nur wenige wirklich notwendige Pfade abgedeckt werden
  • Bei Bug-Reports sollte immer zuerst ein reproduzierender Test ergänzt und der Bug danach behoben werden

Prozesse, Agile und Refactoring

  • Agile ist für Grug-Entwickler nicht schlecht und auch nicht das Schlimmste, aber überzogene Erwartungen an „Agile-Schamanen“ sind riskant
  • Prototyping, Tools und gute Kollegen sind in Wirklichkeit wichtigere Erfolgsfaktoren
  • Refactoring ist ebenfalls eine gute Gewohnheit, aber großes und erzwungenes Refactoring ist riskant
  • Das gewaltsame Einführen komplexer Abstraktionen führt eher zum Scheitern eines Projekts

Wartung, Perfektionismus und Bescheidenheit

  • Bestehende Systeme ohne Grund auseinanderzunehmen ist riskant, und Strukturen zu entfernen, deren Zweck man nicht versteht, ist keine gute Gewohnheit
  • Idealismus, der von perfektem Code träumt, verursacht in der Realität meist Probleme
  • Mit wachsender Erfahrung spürt man immer stärker, dass man funktionierenden Code respektieren sollte

Tools und Produktivität

  • Gute Entwickler-Tools (IDE-Autovervollständigung, Debugger usw.) steigern die Produktivität erheblich, und es ist wichtig, sie gründlich zu verstehen
  • Der tatsächliche Wert eines Typsystems liegt in Autovervollständigung und Fehlervermeidung; übermäßige Abstraktion und Generics sind dagegen eher riskant

Code-Stil und Wiederholung

  • Empfohlen wird ein Stil, bei dem etwa Bedingungen auf mehrere Zeilen aufgeteilt werden, damit Code leichter lesbar und einfacher zu debuggen ist
  • Das DRY- (Don’t Repeat Yourself-)Prinzip wird respektiert, aber wichtiger als das erzwungene Entfernen von Duplikaten ist ein ausgewogenes Verhältnis
  • In vielen Situationen ist einfache Wiederholung besser als eine komplexe DRY-Implementierung

Prinzipien des Softwaredesigns

  • Gegenüber dem SoC- (Separation of Concerns-)Prinzip wird Lokalität des Verhaltens bevorzugt: „Code, der ein bestimmtes Verhalten umsetzt, sollte in dem Objekt liegen, damit Wartung leichter fällt“
  • Vor Callback-/Closure-Konstrukten, Typsystemen, Generics und Abstraktion wird gewarnt, wenn sie nur in kleinen Mengen sinnvoll eingesetzt werden sollten
  • Der Missbrauch von Closures kann in JavaScript sogar zur „Callback-Hölle“ führen

Logging, Betrieb

  • Logging ist sehr wichtig: Es sollte an wichtigen Verzweigungen vorhanden sein, und in Cloud-Umgebungen sollte Nachverfolgbarkeit etwa über Request-IDs möglich sein
  • Wenn dynamische Log-Level und nutzerspezifische Logs genutzt werden können, hilft das enorm bei der Fehlersuche im Betrieb

Nebenläufigkeit, Optimierung

  • Bei Nebenläufigkeit wird nur den möglichst einfachen Modellen vertraut (zustandslose Web-Requests, getrennte Worker-Queues usw.)
  • Optimierung sollte nur dann wirklich erfolgen, wenn reale Performance-Profildaten vorliegen
  • Auf versteckte Kosten wie Netzwerk-I/O muss geachtet werden; nur auf CPU-Komplexität zu schauen, ist riskant

API-Design

  • Eine gute API muss leicht zu benutzen sein; zu komplexes Design oder zu viel Abstraktion verschlechtern die Developer Experience
  • Empfohlen wird eine Struktur aus „einfacher API für den konkreten Use Case“ und „geschichteter API, mit der sich auch komplexe Fälle umsetzen lassen“

Parser-Entwicklung

  • Rekursiver Abstieg wird in der akademischen Welt unterschätzt, ist aber für produktiven Code die geeignetste und am leichtesten verständliche Methode
  • Nach den meisten Erfahrungen in der Parser-Entwicklung sind mit Tools generierte Parser in ihren Ergebnissen zu komplex und bei der Problemlösung eher ein Nachteil
  • Als bestes empfohlenes Buch wird „Crafting Interpreters“ genannt, weil es viele praxisnahe Ratschläge enthält

Frontend und Trends

  • Modernes Frontend (React, SPA, GraphQL usw.) ruft eher noch mehr Komplexitätsdämonen herbei und ist oft unnötig
  • Grug selbst bevorzugt einfache Tools wie htmx und hyperscript, um Komplexität zu reduzieren
  • Im Frontend gibt es zwar ständig neue Versuche, doch man sollte beachten, dass vieles nur Wiederholung bestehender Ideen ist

Psychologische Faktoren, Impostor-Syndrom

  • Die meisten Entwickler haben oft das Gefühl, „nicht zu wissen, was sie tun“, und sollten sich von FOLD (Fear Of Looking Dumb) lösen
  • Wenn ein Senior-Entwickler offen sagt: „Das ist auch für mich schwierig, das ist zu komplex“, können auch Junior-Entwickler den Druck loslassen
  • Das Impostor-Syndrom ist ein verbreitetes Gefühl, und es wird dazu ermutigt, weiter zu lernen und daran zu wachsen

Fazit

  • In der Programmierung muss Komplexität immer kritisch beobachtet werden, und das Bewahren von Einfachheit ist der Schlüssel zu erfolgreicher Entwicklung
  • Erfahrung, der effektive Einsatz von Tools, Bescheidenheit und Respekt vor tatsächlich funktionierendem Code führen langfristig zu effizienterer und wertvollerer Entwicklung
  • „Komplexität sehr, sehr schlecht“ — dieser Satz sollte immer im Gedächtnis bleiben

1 Kommentare

 
GN⁺ 2025-06-18
Hacker-News-Kommentare
  • Ich schätze den Wert eines guten Debuggers so hoch ein, dass er kaum in Gold aufzuwiegen ist, eigentlich sogar noch höher. Ob in einem kleinen Startup oder in einem bekannten Big-Tech-Team, oft war ich im Team der Einzige, der überhaupt einen Debugger benutzt hat. Tatsächlich sieht man immer noch viele Leute, die weiterhin mit print-Statements debuggen. Selbst wenn ich versuche, meinen Workflow Kollegen zu zeigen, kommt kaum Reaktion. Ich stimme zu, dass der beste Ausgangspunkt, um ein System zu verstehen, direkt der Debugger ist. Bei Tests an einer interessanten Codezeile anzuhalten und sich den Stack anzusehen, ist viel einfacher, als den Code nur im Kopf nachzuverfolgen. Wenn man lernt, einen Debugger zu benutzen, bekommt man wirklich eine kleine Superkraft. Wenn es irgendwie geht, sollte man das unbedingt einmal ausprobieren
    • Ich würde wirklich gern einen echten Debugger benutzen, aber aus der Perspektive von jemandem, der nur in Großunternehmen gearbeitet hat, war das in der Praxis oft unmöglich. In einer Microservices-Mesh-Architektur kann man lokal nichts wirklich laufen lassen, und selbst in Testumgebungen ist es meistens so eingerichtet, dass man keinen Step-Debugger anhängen kann. Deshalb ist print-Debugging die einzige realistische Option. Und wenn sogar noch das Logging-System Probleme hat oder das Programm abstürzt, bevor es Logs ausgibt, dann kann man nicht einmal print benutzen
    • Zu diesem Thema gab es vor einigen Jahren eine gute Diskussion. Es gibt ein berühmtes Zitat von Brian Kernighan und Rob Pike, und beide sind nun wirklich keine jungen Entwickler: "Wir benutzen Debugger nicht für viel mehr, als uns einen Stack Trace oder ein paar Variablenwerte anzusehen. Bei komplexen Datenstrukturen und Kontrollflüssen gerät man leicht in Details fest. Es ist produktiver, selbst mehr über das Programm nachzudenken und zwischendurch Ausgaben mit print sowie selbstprüfenden Code einzubauen. print einzufügen ist viel schneller, als sich Schritt für Schritt mit dem Debugger hineinzubewegen. Außerdem bleibt print-Code im Programm erhalten, während eine Debugging-Sitzung verschwindet." Ich stimme dieser Sicht ebenfalls zu. Während des größten Teils der Entwicklung liefert die Schleife aus print-Hypothese-Ausführen eine viel schnellere Problemlösung. Es geht nicht darum, den Code im Kopf zu "auszuführen"; vielmehr hat man bereits ein Funktionsmodell des Codeflusses, und wenn print eine falsche Ausgabe zeigt, erkennt man in den meisten Fällen schnell, was wirklich los ist. Passender Link: The unreasonable effectiveness of print debugging
    • Dass printf-Debugging unter Linux-artigen Systemen immer verbreitet war, lag auch daran, dass GUI-basierte Debugger in dieser Umgebung nicht vertrauenswürdig waren. Linux-GUIs sind oft instabil und daher schwer zu verlässlich zu nutzen. Bei mir begann der ernsthafte Einsatz von Debuggern erst, als (1) unter Windows die GUI gut funktionierte, aber die CLI oft kaputtging, und (2) ich mehrfach Probleme hatte, weil print-Debugging-Code versehentlich in eine Version übernommen wurde. Danach habe ich einige Abenteuer mit CLI-Debuggern erlebt, und der Prozess aus JUnit + Debugger (IDE-basiert, etwa in Eclipse), bei dem man experimentellen Code direkt ausprobiert und zugleich als Test stehen lässt, fühlte sich fast so bequem an wie ein Python-REPL. Allerdings braucht man eine Anfangsinvestition, um den Debugger passend zur Umgebung einzurichten
    • In meinem eigenen Code ist es leicht, einen Debugger zu benutzen, und ich mag das wirklich sehr. Aber sobald der Debugger tief in Bibliotheken oder Frameworks abtaucht statt in meinen eigenen Code, verliere ich sofort die Orientierung und hasse es. Solche Frameworks und Bibliotheken wurden über Hunderttausende von Stunden entwickelt, deshalb überschreiten sie auf meinem Niveau schnell den Bereich dessen, was ich noch verstehen kann
  • Falls Professor Carson diesen Text hier sieht, möchte ich mich aufrichtig bedanken. Damals an der Uni habe ich nicht verstanden, warum wir HTMX lernen sollten und warum Sie so leidenschaftlich dafür waren, aber ein paar Jahre später habe ich es wirklich begriffen. HTML over the wire ist tatsächlich alles. Als Staff Ruby on Rails Engineer bin ich Ihrer Arbeit in Hotwire mehrfach begegnet, und wenn ich Sie gelegentlich auf GitHub oder Hacker News aktiv sehe, bin ich immer wieder beeindruckt. Sie sind wirklich ein Licht in der Programmier-Community. Tiefen Respekt und vielen Dank
    • Ich bin offenbar nicht der Einzige, den das emotional trifft, das ist bewegend
    • War HTMX nicht einfach nur ein Meme? Wegen Poe’s Law bin ich unsicher, ob das ernst gemeint ist
  • Dieser Text enthält wirklich viele großartige Sätze, aber am besten gefiel mir die Stelle über Microservices: "grug versteht nicht, warum großes Gehirn ein System kaum sauber zerlegen kann und dann trotzdem noch Netzwerkaufrufe hinzufügt"
    • Manche Leute kennen als Methode, ein System in Teile zu zerlegen, nichts außer daraus APIs zu machen. Wenn etwas nicht über eine API exponiert ist, halten sie es einfach für undurchsichtigen Code, den man weder verstehen noch wiederverwenden kann
    • Es ist auch etwas schade, dass Microservices aus verschiedenen Gründen in manchen Fällen tatsächlich praktisch sind und deshalb eingesetzt werden
    • Ich sehe immer wieder, wie kleine Dev-Teams mit zwei Leuten selbst eine banale Web-App mit nur fünf Formularen zu einer komplizierten "Microservices"-Struktur aufblasen — mit gemeinsamer Datenbank, API-Management, Batch-Jobs über Queues, E-Mail-Benachrichtigungen, eigener Observability-Plattform und mehr. Und am Ende bauen sie sogar gewöhnliche Formulare als SPA, weil es "einfacher" sei. Inzwischen verstehe ich, dass "Architektur" und "Patterns" oft nur der Beschäftigungserzeugung für nutzlose Entwickler dienen. Wenn es das alles nicht gäbe, stünden sie wahrscheinlich mit einem Schild auf der Straße: "Ich benutze JavaScript für ein halbes Sandwich"
    • Meine persönliche Verschwörungstheorie ist, dass Cloud-Anbieter das Microservices-Pattern so stark gepusht haben, dass wir deshalb hier gelandet sind. - Ohne Orchestratoren wie K8S läuft es praktisch nicht, wodurch sich Managed Cloud leichter verkaufen lässt - Mehr Netzwerkverkehr und CPU-Nutzung bedeuten höhere Rechnungen - Großflächig geteilten Zustand sauber zu verwalten ist schwer, also braucht man Managed Databases und Event-Queues - Lokales Ausführen wird schwierig, sodass selbst die Entwicklungsumgebung zu Cloud-Kosten führt - Man wird an Cloud-spezifische Vorgehensweisen gebunden und kommt schwer wieder heraus. Früher wurde beworben, die Cloud würde IT-Kosten senken, was völlig lächerlich ist. Schon seit den 2000ern war klar, dass das eine Illusion ist, und am Ende wird einfach alles teurer
  • Der Satz "Komplexität vs. Tyrannosaurus von Angesicht zu Angesicht, grug nimmt Tyrannosaurus statt Komplexität: wenigstens kann grug Tyrannosaurus sehen" ist so einprägsam, dass ich ungefähr einmal pro Woche daran denken muss
    • Zitat: "Selbst beim Sturz hatte Leyster die Schaufel nicht losgelassen. In der Panik hatte er das vergessen. Also schwang er verzweifelt die Schaufel gegen das Bein des jungen Tyrannosaurus ...". Eine eindringliche Szene, die den extremen Überlebenskampf mit dem Tyrannosaurus lebhaft beschreibt. Letztlich wurde die Krise überwunden, als seine Gefährtin Tamara mutig mit einem Speer mitten ins Gesicht des Tyrannosaurus stach. Der Kampf, die Spannung und auch die Momente der Stille sind eindrucksvoll
    • grug hat ganz sicher noch nie gegen einen "unsichtbaren" Tyrannosaurus gekämpft. Ich stecke gerade selbst in einem Eins-gegen-eins mit einem unsichtbaren Tyrannosaurus, und es ist wirklich hart
  • Bemerkenswert an diesem Artikel ist, dass der Autor zwar in der Lage wäre, "kompliziertere Dinge" zu tun, sich aber aus Erfahrung bewusst dagegen entscheidet. Natürlich gibt es Zeiten und Orte, an denen Abstraktion und Komplexität nötig sind, aber die grug-Philosophie sagt, dass ihnen an sich kein inhärenter Wert zukommt. Das erscheint mir sehr plausibel. Auch bei KI habe ich den Eindruck, dass sie bei konsistentem und datenbasiertem Code wirksamer ist
    • Komplexität und Abstraktion sollte man dann einsetzen, wenn der Code dadurch leichter zu verstehen wird als vorher. Wichtig ist die Voraussetzung: "wenn man dafür keinen zusätzlichen Spezialkurs braucht, um es zu verstehen". (Je nach Situation verschieden)
    • "Alles sollte so einfach wie möglich gemacht werden, aber nicht einfacher"
  • Kaum zu glauben, dass dieser Text von 2022 ist. Es fühlt sich an, als hätte ich ihn schon vor zehn Jahren gelesen und als wäre er längst ein Klassiker
  • Dieser Essay ist mein absoluter Lieblingstext darüber, wie man Software baut. Auch der Stil hat Charme, selbst wenn er manche vielleicht eher abstößt, und der Kern bleibt immer gültig
  • Das Codeschnipsel "traurig, aber wahr: 'ja' lernen und lernen, im Fehlerfall anderen grugs die Schuld zu geben, ist die beste Karrierestrategie" ist pure Realität. Ich dachte anfangs auch einfach, die Ursache in Unternehmen sei ein Kommunikationsproblem im Technikteam, aber mit der Zeit habe ich wie grug gelernt, dass es tatsächlich so ist
  • Das ist die beste Erklärung des Visitor Pattern, die ich bisher gesehen habe
    • Ich arbeite nicht in einer typischen OO-Codebasis und wusste deshalb nie so richtig, was das Visitor Pattern eigentlich ist, aber ich möchte das Buch "Crafting Interpreters" empfehlen, in dem es um den Bau von Interpretern und VMs geht. Dort sieht man, wie das Visitor Pattern in der Praxis eingesetzt wird. Ich habe beim Lesen versucht zu verstehen, warum diese Komplexität existiert, es am Ende aber durch eine Tagged Union ersetzt. Vielleicht bin ich einfach nicht besonders stark in OO, aber genau darum geht es auch im grug-Artikel: Wenn man sich Komplexität und Indirektion nicht selbst antun muss, gibt es oft einen direkteren Ansatz
    • Ich bin ziemlich empfindlich, was Benennungen angeht, und finde den Namen Visitor Pattern zu vage. Ich habe tatsächlich nie etwas mit dem Namen Visitor gebaut. Wenn es zum Beispiel um Übungen mit einem Syntaxbaum (AST) geht, dann sind Namen wie AstWalker, AstItem::dispatch(AstWalker) oder AstWalker::process(AstItem) viel aussagekräftiger als Visitor. „Visitor“ im Sinn von "besucht" ist einfach zu abstrakt und bedeutungsarm. Je nach Kontext sieht das natürlich anders aus, und man kann einfach im Kommentar "visitor pattern" dazuschreiben, dann gibt es kein Erkennungsproblem. Ich hatte früher einmal die Aufgabe, zwei Objekttrees gegeneinander abzugleichen, um Daten zu vergleichen oder zu importieren, und habe dafür den Namen AbstractImporter verwendet. Das war konkreter, der Prozess und die Rolle waren klarer. Mit einem typischen Visitor Pattern war das nicht ganz identisch
    • Ich habe tatsächlich nachgeschaut, und die Bewertung war "Bad". Haha
  • Ich teile hier noch verwandte Beiträge. Hat jemand weitere Meinungen oder ergänzende Artikel?<br/><i>The Grug Brained Developer (2022)</i> - https://news.ycombinator.com/item?id=38076886 - Oktober 2023 (192 Kommentare)<br/><i>The Grug Brained Developer</i> - https://news.ycombinator.com/item?id=31840331 - Juni 2022 (374 Kommentare)