1 Punkte von GN⁺ 2 시간 전 | 1 Kommentare | Auf WhatsApp teilen
  • k10s war ein GPU-aware Kubernetes-TUI, das per Vibe-Coding mit Claude schnell gebaut wurde, aber nach dem Hinzufügen der Fleet-Ansicht mehrere Bildschirmzustände beschädigte
  • model.go wuchs auf ein einzelnes Model mit 1690 Zeilen und einer Update()-Funktion mit 500 Zeilen an und trug dadurch gleichzeitig UI, Client, Cache, Navigation und View-Status
  • Die KI fügte Features zwar schnell hinzu, vergrößerte aber zugleich ein god object und einen globalen Key-Handler, sodass mit jeder neuen View weitere Branches in bestehenden Handlern entstanden
  • Positionsbasierte []string-Daten und direkte Mutation in Hintergrund-tea.Cmd konnten Spaltenfehler und offensichtliche Data Races verursachen
  • Das neue k10s wird in Rust neu geschrieben; noch vor dem ersten Prompt sollen Interface, Message-Typen, Ownership-Regeln und Scope in CLAUDE.md festgeschrieben werden

Warum k10s neu geschrieben wird

  • k10s begann als GPU-aware Kubernetes-Dashboard und war als TUI-Tool dafür gedacht, dass Betreiber von NVIDIA-Clustern Informationen wie GPU-Auslastung, DCGM-Metriken, idle Nodes und Kosten von $32/hr sofort sehen können
  • Es wurde mit Go und Bubble Tea geschrieben und in etwa 7 Monaten, 234 Commits und ungefähr 30 Wochenenden in Vibe-Coding-Sessions mit Claude erstellt
  • Anfangs funktionierten grundlegende k9s-Klon-Features wie Pods, Nodes, Deployments, Services, Command Palette, Watch-basierte Live-Updates und Vim-Keybindings schon nach etwa 3 Wochenenden
  • Das Kernfeature, die GPU-Fleet-Ansicht, war ein Bildschirm, der pro Node GPU-Zuweisung, Auslastung, DCGM-basierte Metriken, Temperatur, Stromverbrauch, Speicher und farbbasierte Zustände zeigte; Claude erzeugte dabei in einem Schritt die FleetView-Struktur, GPU-/CPU-/All-Tab-Filterung und sogar das Rendering der Allocation Bars
  • Nach dem Hinzufügen der Fleet-Ansicht war beim Zurückkehren zur Pods-Ansicht mit :rs pods die Tabelle leer, Live-Updates stoppten, in der Nodes-Ansicht erschienen veraltete Daten aus dem Fleet-Filter und auch die Fleet-Tab-Zählung war falsch
  • Beim Nachverfolgen des Problems wurde erstmals die gesamte von Claude erzeugte model.go mit 1690 Zeilen gelesen; eine einzelne Model-Struktur hielt UI-Widgets, Kubernetes-Client, Logs-/Describe-/Fleet-Status, Navigationshistorie, Cache und Mouse-Handling zugleich
  • Die Methode Update() war eine msg.(type)-Dispatch-Funktion mit 500 Zeilen und 110 switch/case-Branches
  • KI kann Features schnell bauen, aber wenn man ihr ohne Einschränkungen immer weiter Aufgaben überlässt, kollabiert die Architektur, und das Geschwindigkeitsgefühl sieht bis kurz vor dem gleichzeitigen Zusammenbruch des Ganzen wie Erfolg aus

Fünf Prinzipien aus den Trümmern

  • Prinzip 1: KI baut Features, aber keine Architektur

    • Claude konnte einzelne Features wie Fleet-Ansicht, Log-Streaming und Mouse-Support gut bauen, aber jedes Feature wurde im Kontext „es muss jetzt funktionieren“ umgesetzt und ohne die Beziehungen zu anderen Features zu berücksichtigen, die denselben Zustand teilen
    • Im Handler für resourcesLoadedMsg standen Bedingungen wie msg.gvr.Resource == k8s.ResourceNodes && m.fleetView != nil, wodurch Fleet-spezifische Logik in den generischen Pfad zum Laden von Ressourcen gemischt wurde
    • Immer wenn eine neue View spezielles Verhalten brauchte, kam ein weiterer Branch in denselben Handler, und damit keine Daten aus alten Views in neue Views ausliefen, mussten mehrere Felder manuell geleert werden
    • In model.go waren 9 manuelle Cleanups wie m.logLines = nil, m.allResources = nil, m.resources = nil verstreut; wenn nur eines fehlte, blieben Ghost-Daten der vorherigen View zurück
    • Die Alternative ist, vor dem Schreiben von Code konkrete Interfaces, Message-Typen und Ownership-Regeln selbst zu definieren und als Architektur-Invarianten in CLAUDE.md festzuhalten
    • Beispielregeln wären etwa: Jede View implementiert ein View-Trait/Interface, keine View greift auf den State einer anderen View zu, asynchrone Daten kommen nur über AppMsg-Varianten herein, und die App-Struktur ist nur für Navigation und Message-Dispatch zuständig
  • Prinzip 2: Das god object ist das Standardprodukt von KI

    • KI neigte zu einer Struktur, in der ein einziges Struct alles enthält, weil damit der unmittelbare Prompt mit dem geringsten Zeremoniell erfüllt werden konnte
    • Auch das Key-Handling war nicht pro View getrennt; dieselbe Taste s bedeutete in der Logs-View Autoscroll, in der Pods-View Shell und in der Containers-View Container-Shell
    • Die Anfrage „Shell-Support zu Pods hinzufügen“ wurde umgesetzt, indem in der Nähe des bestehenden globalen Key-Handlers einfach ein Branch eingefügt wurde
    • Auch die Enter-Taste verzweigte zwischen Contexts-View, Namespaces-View, Logs-View und generischer Drill-down-Logik innerhalb eines einzigen flachen Dispatches über String-Vergleiche mit m.currentGVR.Resource
    • Innerhalb der einen Datei model.go wurde m.currentGVR.Resource == mehr als 20 Mal wie ein Typ-Diskriminator verwendet, und jedes Hinzufügen einer neuen View machte Änderungen in mehreren Handlern nötig
    • Die Alternative ist, keine view-spezifischen State-Felder in App/Model hinzuzufügen, jede View als eigenes Struct zu bauen und auch die Keybindings in der Keymap der aktiven View zu halten; diese Regel gehört in CLAUDE.md
    • Es braucht Guardrails wie „Eine neue View muss ein neues File sein; wenn dafür bestehende Views geändert werden müssen, stoppen und nachfragen“, damit die KI nicht einfach den kürzesten Weg über weitere Branches nimmt
  • Prinzip 3: Die Illusion von Geschwindigkeit erweitert den Scope

    • k10s war ursprünglich ein Tool für eine enge Zielgruppe, nämlich Betreiber von GPU-Training-Clustern, aber Vibe-Coding ließ Features wie Pods, Deployments, Services, Command Palette, Mouse-Support, Contexts und Namespaces wie „kostenlos“ wirken
    • Dadurch weitete sich das Projekt von einem GPU-fokussierten Tool zu einem General-Purpose-TUI für alle Kubernetes-Nutzer aus, also faktisch in Richtung eines erneuten k9s-Nachbaus
    • In einer flachen keyMap waren viele View-spezifische Bindings wie Fullscreen, Autoscroll, ToggleTime, WrapText, CopyLogs, ToggleLineNums, Describe, YamlView, Edit, Shell, FilterLogs, FleetTabNext, FleetTabPrev in einer einzigen Struktur vermischt
    • Autoscroll und Shell lagen beide auf s; weil der Dispatch die aktuelle Resource prüfte, „funktionierte“ es zwar, aber Keybindings ließen sich nicht mehr lokal verstehen
    • Die Geschwindigkeit beim Schreiben des Codes sah wie „Shipping“ aus, aber jedes Feature verursachte die Kosten eines weiteren Branches im god object
    • Die Alternative ist, in CLAUDE.md eine Scope-Grenze festzulegen: k10s ist für GPU-Cluster-Operatoren gedacht, unterstützte Views sind auf Fleet, Node-Detail, GPU-Detail und Workload begrenzt, und generische Resource-Views oder k9s-duplizierende Funktionen werden nicht hinzugefügt
    • KI kann ein unendliches Line-Budget liefern, aber das Complexity-Budget bleibt endlich, daher muss Scope vorab aktiv abgelehnt werden
  • Prinzip 4: Positionsbasierte Daten sind eine Zeitbombe

    • k10s flachte Resources aus der Kubernetes-API sofort in die Form type OrderedResourceFields []string ab
    • Die Sortierfunktion der Fleet-Ansicht behandelte ra[3] als Alloc, ra[2] als Compute und ra[0] als Name; die Identität der Spalten hing damit nur an Kommentaren und an der Spaltenreihenfolge in resource.views.json
    • Wenn in resource.views.json zwischen Instance und Compute eine Spalte eingefügt wurde, konnten Sortierung, bedingtes Rendering und Drill-Targets, die auf ra[2] und ra[3] zugreifen, stillschweigend falsch werden
    • Der Compiler kennt die Bedeutung von []string nicht, und auch die JSON-Konfiguration konnte weder Sortierverhalten noch bedingtes Rendering oder benutzerdefinierte Drill-Targets ausdrücken, sodass der Go-Code positionsbasierte Annahmen hart codierte
    • KI wählt leicht []string oder Vec<String>, weil sie sich direkt in Table-Widgets einspeisen lassen; typisierte Structs haben anfangs mehr Zeremoniell und fallen deshalb auf dem schnellen Pfad oft weg
    • Die Alternative ist, strukturierte Daten bis unmittelbar vor dem Rendering als typisierte Structs wie FleetNode oder PodInfo zu behalten und Sortierung nicht über positionsbasierten Zugriff wie row[3], sondern über benannte Felder auszuführen
    • Eine Beispielstruktur wie FleetNode { name, instance_type, compute_class, alloc } drückt Spaltenidentität im Typ aus, sodass unmögliche Zustände wie eine falsche Spaltensortierung gar nicht erst erzeugt werden können
    • „Making impossible states impossible“ ist ein Ausdruck aus der Elm-/Rust-Community und bedeutet, Typen so zu entwerfen, dass ungültige Zustände gar nicht konstruiert werden können, statt sie erst zur Laufzeit zu prüfen
  • Prinzip 5: KI besitzt keine State-Transitions

    • Der Kern des Bubble-Tea-Modells ist, dass State nur in einem message-getriebenen Update() verändert wird, aber k10s verletzte genau das
    • Der Handler updateTableMsg gab eine tea.Cmd-Closure zurück, und innerhalb dieser Closure wurden mit Aufrufen wie m.updateColumns(m.viewWidth), m.updateTableData() und m.table.SetCursor(savedCursor) Felder des Model verändert
    • Bubble Tea führt tea.Cmd in einer separaten Goroutine aus, daher konnte die Closure m.resources, m.table und m.viewWidth lesen und schreiben, während die Haupt-Goroutine in View() dieselben Felder las
    • Es gab weder Lock noch Mutex, und <-m.updateTableChan wartete nur auf ein Update-Signal, verhinderte aber nicht, dass View() halb geschriebene Zustände las
    • Diese Struktur war ein offensichtliches Data Race und zeigte sich meist dadurch, dass zwar „meistens alles funktioniert“, aber gelegentlich die Darstellung kaputtgeht
    • Die Alternative ist, dass Background-Worker den UI-State nicht direkt mutieren, sondern typisierte Messages über einen Channel senden und der Main Event Loop nach Empfang dieser Messages die State-Mutationen anwendet
    • Die Concurrency-Regel lautet: Background-Tasks dürfen UI-State nicht direkt ändern, sondern senden Ergebnisse als typisierte Message; außerdem muss render()/view() eine pure Funktion ohne Side Effects, I/O oder Channel-Operationen sein

Schutzregeln für CLAUDE.md und agents.md

  • Architektur-Invarianten

    • Jede View muss ein View-Trait/Interface implementieren und darf nicht auf den State anderer Views zugreifen
    • Alle asynchronen Daten müssen über AppMsg-Varianten hereinkommen, und Background-Tasks dürfen keine Felder direkt mutieren
    • Das Hinzufügen einer neuen View darf keine Änderungen an bestehenden Views erfordern
    • Die App-Struktur soll ein schlanker Router sein, der nur Navigation und Message-Dispatch übernimmt
  • Regeln zur State-Ownership

    • View-spezifische State-Felder dürfen nicht zur App/Model-Struktur hinzugefügt werden
    • Jede View soll als eigenes Struct existieren und ihre eigenen Keybindings deklarieren
    • Die App soll Keys an die aktive View dispatchen, und neue Keybindings gehören in die Keymap der jeweiligen View statt in einen globalen Handler
    • Wenn das Hinzufügen einer View Änderungen an bestehenden Views verlangt, muss angehalten und nach Bestätigung gefragt werden
  • Scope

    • k10s soll ein Tool für GPU-Cluster-Operatoren sein, nicht für alle Kubernetes-Nutzer
    • Unterstützte Views sollen auf Fleet, Node-Detail, GPU-Detail und Workload begrenzt sein
    • Generische Resource-Views wie Pods, Deployments oder Services sollen nicht hinzugefügt werden
    • Features, die Funktionen von k9s duplizieren, sollen nicht hinzugefügt werden
    • Feature-Requests, die Betreibern von GPU-Training-Jobs nicht helfen, sollen abgelehnt werden
  • Datenrepräsentation

    • Strukturierte Daten dürfen nicht in []string, Vec<String> oder positionsbasierte Arrays abgeflacht werden
    • Daten sollen bis unmittelbar vor dem Render-Aufruf als typisierte Structs fließen
    • Spaltenidentität soll aus Struct-Feldnamen kommen, nicht aus Array-Indizes
    • Sortierfunktionen sollen auf typisierten Feldern arbeiten, nicht über positionsbasierten Zugriff wie row[3]
    • Strings für die Anzeige sollen nur in render()/view()-Funktionen erzeugt werden
  • Nebenläufigkeitsregeln

    • Background-Tasks wie Watcher, Scraper oder API-Calls dürfen den UI-State nicht direkt mutieren
    • Background-Tasks sollen ihre Ergebnisse als typisierte Messages über einen Channel senden
    • Nur der Main Event Loop darf State-Mutationen auf Basis empfangener Messages anwenden
    • render()/view() muss eine pure Funktion ohne Side Effects, I/O oder Channel-Operationen sein
    • Wenn asynchrone Arbeit den State ändern soll, muss eine neue AppMsg-Variante definiert werden

Wie der Neuaufbau erfolgen soll

  • k10s soll in Rust neu geschrieben werden, nicht weil Rust objektiv besser wäre, sondern weil es sich wie eine Sprache anfühlt, die direkt steuerbar ist
  • In einer Sprache, mit der man ausreichend vertraut ist, spürt man oft schon vor einer verbalen Erklärung, dass etwas falsch läuft, und dieses Gespür kann Vibe-Coding nicht ersetzen
  • Wenn KI plausibel wirkenden Code ausgibt, braucht man die Fähigkeit zu erkennen, ob er in Wahrheit Müll ist
  • In der neuen Version wird die gestalterische Arbeit wie konkrete Interfaces, Message-Typen und Ownership-Regeln von Menschen zuerst von Hand erledigt, bevor überhaupt Code geschrieben wird
  • Statt Architekturentscheidungen wieder von der KI falsch treffen zu lassen, werden sie künftig noch vor dem ersten Prompt als Dokument festgelegt
  • Links zum bisherigen TUI und zum Projekt finden sich auf k10s Github und K10S.DEV

Nachtrag

  • Bubble Tea ist ein Go-TUI-Framework auf Basis der The Elm Architecture; die Architekturprobleme von k10s entstanden nicht durch Bubble Tea, sondern in der k10s-Implementierung
  • „Making impossible states impossible“ ist ein Ausdruck aus der Elm-/Rust-Community und meint, ungültige Zustände nicht zur Laufzeit zu prüfen, sondern sie durch Typdesign gar nicht erst konstruierbar zu machen
  • So wie bei KI-Texten der „em-dash“ als Geruchsspur auftaucht, kann beim KI-Coding das „god object“ ein solcher Geruch sein, und Vibe-Coding lässt Implementierung billig erscheinen, was zu Fokusverlust und Bloat führen kann

1 Kommentare

 
GN⁺ 2 시간 전
Hacker-News-Kommentare
  • Diejenigen, die sagen, generierter Code sei okay, waren meist Leute, die diesen Code nicht lesen
    Auch die im Artikel vorgeschlagenen Gegenmaßnahmen halten wohl nicht lange. Wenn man Systeme oder Komponenten entwirft, entstehen Invarianten wie „Eine View greift nicht auf den Zustand einer anderen View zu“, aber irgendwann muss man eine Funktion hinzufügen, die mit dieser Bedingung kollidiert
    Dann gibt man die Funktion meist entweder auf, setzt sie unbeholfen und ineffizient auf die Invariante drauf oder man ändert die Invariante selbst. Diese Entscheidung ist nicht bloß eine Frage des Kontexts, sondern des Urteilsvermögens, und aktuelle Modelle liegen dabei viel zu oft falsch
    Wenn man Architektur-Constraints explizit macht, verbiegt sich der Agent auch dann daran entlang und produziert komplexen, nicht wartbaren Code, wenn diese Constraints eigentlich geändert werden müssten. Wenn man ihn nicht noch genauer liest als von Menschen geschriebenen Code, entsteht am Ende „Code, der sich selbst auffrisst“, und man merkt es erst viel zu spät

    • Wenn man guten Code schreiben kann, kann man mit verschiedenen Techniken auch die AI dazu bringen, guten Code zu schreiben; das ist absolut möglich
      Der Schlüssel ist, die Stellen zu erkennen, an denen AI Probleme hat, und sie einfacher zu machen. Dafür braucht man zum Beispiel extrem kleinen Kontext, klar abgegrenzte Modularisierung, pure Module, die von Ein- und Ausgabe getrennt sind, Verstecken hinter Interfaces, 100 Tests, die in unter einer Sekunde laufen, Benchmarks usw.
      AI funktioniert gut, wenn es Grenzen und kleinen Kontext gibt. Gibt man ihr das nicht, wird sie schlechter, und die Verantwortung dafür liegt bei der Person, die das Tool benutzt
    • Dass man „irgendwann Funktionen hinzufügen muss, die mit Invarianten kollidieren“, ist meiner Meinung nach ein großes Problem von spezifikationsgetriebener Entwicklung
      Keine Spezifikation hält der Realität stand, und selbst nach gründlicher Untersuchung und Planung stellt sich irgendeine Invariante in der Spezifikation am Ende als falsch heraus
      Wenn Menschen in der Entwicklung auf so eine Situation stoßen, können sie einen Schritt zurücktreten und neu überlegen, ob die Invariante falsch ist und welche Auswirkungen eine Änderung hätte. AI dagegen bastelt unter falschen Annahmen oder Designs oft irgendwie eine gehackte Lösung zusammen und hat zu wenig Einsicht, um das Ganze neu zu bewerten
      Mit gutem Workflow und Validierung kann es besser werden, aber es ist kein Bereich, den Tools wie Claude Code standardmäßig gut beherrschen; da gibt es Grenzen
    • Ich habe bei uns im Unternehmen etwas Ähnliches erlebt, als wir ein neues internes Framework gebaut und bestehende Nutzungen des alten Frameworks migriert haben
      Anfangs haben wir starke Prinzipien aufgestellt und ein paar Einsatzfälle von Hand migriert, um Vertrauen zu gewinnen. Die komplette Migration war so groß und teuer, dass sie fast 10 Jahre lang verschoben wurde, also wollten wir sie mit AI beschleunigen, um die Kosten zu senken
      Für die mechanischen, einfachen 80 % war AI okay. Bei den restlichen 20 % musste das Framework geändert werden; meistens waren das kleine Änderungen wie zusätzliche API-Felder, aber ein oder zwei Fälle erforderten ein konzeptionelles Redesign
      Das Backend eines bestimmten Systems konnte in 99 % der Fälle bestimmte Daten erzeugen, in einigen wichtigen Fällen aber logisch nicht, sodass sie extern gemeldet werden mussten. Eine wichtige Optimierung beruhte jedoch auf der Annahme „das ist unmöglich“
      Das AI-Tool erkannte diese Situation nicht und fügte Migrationslogik hinzu, die so aussah, als würde sie korrekt funktionieren. Dank unserer Deployment-Methode war es noch kein Produktionsbug, aber als wir dem Partnerteam die richtigen Fragen stellten, stellten wir fest, dass derselbe Bedarf auch anderswo bestand
      Am Ende wurde es nur deshalb kein großes Problem, weil ein Mensch tief genug drinsteckte. Mit Validierungswerkzeugen und schlaueren Modellen könnten solche Migrationen in Zukunft leichter werden, aber im Moment ist generierter Code zwar manchmal schön, aber auch brüchig, sodass man ihn ständig eng begleiten muss
    • Es reicht nicht, den ausgegebenen Code nur zu lesen; zumindest meiner Erfahrung nach muss man Code auch selbst schreiben
      Ich hatte ein seltsames Architektur-Pattern etwa zwei Monate lang benutzt, und jedes Mal fühlte es sich ein wenig unbequem an. Erst gestern Abend wurde mir klar, dass es keine gute Abstraktion war und dass man es besser aufteilen könnte
      Wenn ich den Code von einem LLM generieren ließ, spürte ich dieses Unbehagen viel weniger deutlich, wodurch ich das Problem später erkannte und länger brauchte, um eine Lösung zu finden. Den Randbereich kann man ruhig generieren lassen, aber die Kernfunktionalität sollte man weiterhin größtenteils selbst schreiben
    • Informell formulierte Invarianten sind selbst mit menschlichen Reviewern schwer auf ihre Verletzung hin zu beweisen, und natürliche Sprache ist dafür nicht präzise genug
      Selbst wenn man sie in einer präzisen formalen Sprache ausdrückt, fehlt dem LLM unter dem Agenten die Fähigkeit zu verstehen, warum diese Invarianten nötig sind und warum sie wichtig sind. Es mag irgendwann LLMs geben, die Tokens mit formalen Spezifikationen verknüpfen und sogar Beweise schreiben, aber seltsamer Code aus den informellen Teilen des Prompts wird weiter auftauchen
      Man kann das nicht verhindern, indem man Constraints und Prompts nur als technische Liste oder Spezifikation ergänzt. Auch mit besseren Fallen entkommen die Lebewesen
      Das Problem ist Code-Aufblähung, also Code, der hinzugefügt wird, um Prompt oder Aufgabe irgendwie zu erfüllen. Oft ist weniger Code besser, und man braucht Menschen, die vorhersagen können, was andere wollen und erwarten. Generatoren sind gut, aber man sollte sie etwas zurückhaltender einsetzen, eher nicht wie einen Feuerwehrschlauch
  • Als Copilot noch nur eine Zeile automatisch vervollständigte, hieß es: „Trotzdem musst du noch die ganze Funktion schreiben“, und als die Funktion vervollständigt wurde: „Die Logik um die Funktion herum musst du selbst schreiben“, und als auch diese Logik vervollständigt war: „Das Feature musst du selbst schreiben“
    Jetzt, wo sogar Features fertig werden, heißt es: „Aber die Architektur musst du immer noch selbst schreiben.“ Ich weiß nicht, ob diese Modelle Architektur lösen können, aber es ist interessant zu sehen, wie sich die Erwartungen ständig verschieben

    • Diese hypothetischen „Leute“ lagen von Anfang an durchgehend falsch
      Auch wenn AI eine Zeile fertigstellt, eine ganze Funktion oder ein Feature samt Ticket, muss man den Code immer noch lesen und verstehen
    • Modelle können Architektur schon machen, aber im Moment meist erst, wenn man sie stark in diese Richtung lenkt; sonst sind sie sehr schlecht darin
      Ich benutze AI ständig, und sie wird besser, aber ich reviewe immer noch jede einzelne Zeile. Selbst auf Zeilenebene ist sie heute meiner Meinung nach nicht klar besser als die Tab-Autovervollständigung vom letzten Jahr; manchmal ist sie sehr gut, manchmal wirklich schlecht
    • Die Lösung steht meiner Meinung nach zwischen den Zeilen des Artikels
      LLMs sind großartig für Softwareentwicklung, aber nur, wenn man sie nicht die Architektur schreiben lässt. Module, Structs und Enums sollte man selbst anlegen, und möglichst auch Felder und Varianten selbst hinzufügen
      Es ist sinnvoll, jedes Struct, Enum, Feld und Modul mit Doc-Comments zu versehen und dem LLM dann diese Module und Datenstrukturen zu zeigen, damit es die nötigen Funktionskörper usw. ausfüllt
    • In heutigen Sprachen ist die Codebasis global komplex, und die gewünschten Invarianten sind nicht sichtbar, daher skaliert das schlecht
      Selbst wenn man mehrfach sagt „Auf dem kritischen Pfad niemals blockieren“, baut das LLM Blockierung in den kritischen Pfad ein, und wenn man sagt „Wenn du X machst, brauchst du Tests vom Typ Y“, macht es X und vergisst die Tests
      Menschen befolgen Anweisungen auch nicht zu 100 %, aber LLMs sind zufälliger. Menschliche Fehler bestehen relativ selten darin, exakt das Gegenteil des Gewünschten zu tun
      LLMs sehen wichtige Invarianten im Code und bauen trotzdem Umgehungen, schreiben Tests, die Fehlschläge wie Erfolge aussehen lassen, behaupten dann, sie hätten genau das Geforderte getan, und vergraben es in einem 5000-Zeilen-Commit
      Ich halte LLMs für großartig und bin sicher, dass sie die Zukunft sind. Deshalb baue ich auch eine Sprache namens https://GitHub.com/Cuzzo/clear, damit wir über Sprachprobleme hinauskommen, bei denen globaler Kontext dort nötig ist, wo er nicht nötig sein sollte. Dann wird Zusammenarbeit leichter
      Es gab Erfolge, aber manchmal war es so frustrierend, dass ich mich gefragt habe, ob es den Verstand wert war, den ich hineingesteckt habe
    • Ich nenne das Wegwerf-Architektur
      Nicht, weil Architektur unwichtig wäre, sondern weil eine Architektur, die gestern gut gepasst hat, heute nicht zwingend immer noch passen muss
  • Ich habe mir beim Einsatz von Coding-Agenten ein paar Regeln gesetzt
    Erstens: Wenn ich mit einem Agenten Code generieren lasse, dann nur für etwas, bei dem ich absolut sicher bin, dass ich es mit genug Zeit selbst korrekt schreiben könnte
    Zweitens: Wenn das nicht der Fall ist, gehe ich nicht weiter, bis ich das Generierte vollständig verstehe und selbst reproduzieren könnte
    Drittens: Wenn ich Regel zwei breche, kann ich kognitive Schulden aufbauen, aber ich muss sie vollständig zurückzahlen, bevor ich ein Projekt für abgeschlossen erkläre
    Je mehr Schulden sich ansammeln, desto wahrscheinlicher wird es, dass die Qualität später generierten Codes sinkt, und es fühlt sich an, als würde das mit Zinseszins wachsen. Für private Projekte ist das angenehm, man lernt viel, und am Ende bleibt eine Codebasis, die man entspannt verstehen kann

    • Das sind vernünftige Regeln, wenn man ein solides mentales Modell von Code-Sanity und Wachstum der Codebasis behalten will, aber im Job, wo sich die Erwartungen an Liefergeschwindigkeit seit AI stark verändert haben, ist das schwer einzuhalten
      Man braucht einen Gleichgewichtspunkt, an dem man mit der Codebasis verbunden bleibt, ohne zum Bottleneck des Teams zu werden
    • Ich habe versucht, ähnliche Regeln zu befolgen, bin dann aber auf ein schwieriges Mathematikproblem gestoßen
      Claude ist ein Mathematiker auf PhD-Niveau, ich nicht, aber ich wusste genau, welche Eigenschaften die gewünschte Lösung haben sollte und wie man testet, ob sie korrekt ist. Also habe ich Claudes Lösung statt meiner einfachen, naiven Lösung übernommen, das im Pull Request vermerkt, und alle hielten das für die richtige Entscheidung
      Ich frage mich, ob man für solche Fälle Ausnahmen machen sollte. Noch interessanter ist die Frage, ob man ganz mit dem Handschreiben von Code aufhören würde, falls AI nicht nur in höherer Mathematik, sondern auch beim Programmieren viel besser wäre als man selbst, unter der Annahme, dass man dann vielleicht die Tests noch beurteilen kann, auch wenn man den Code selbst nicht mehr beurteilen kann
    • Ich mag den Ausdruck Verständnisschulden mehr als „kognitive Schulden“
      Denn die auflaufenden Schulden bestehen ganz konkret in mangelndem Verständnis des Codes; das ist präziser
    • Für private Projekte ist es okay, wenn die angenehmere Arbeitsweise wichtiger ist, aber im Job versteht man auch nicht jede einzelne Schicht vollständig — Abhängigkeiten, Arbeit von Kollegen, externe Services bis hinunter zum Silizium
      Ich verstehe nicht, warum ausgerechnet AI plötzlich völlig anders behandelt werden sollte
      Letztlich muss man es als Risiko-Ertrags-Abwägung betrachten. Was verliert man, wenn es falsch ist, wie wahrscheinlich ist es, dass Tests und Reviews es finden, und was gewinnt man, wenn es klappt? Das gilt genauso für Libraries und externe Services
      Komplexe Finanzregeln in einem nicht testbaren Krypto-Vertrag ohne Updates sind nicht annähernd dasselbe Risiko wie ein Viewer zur Visualisierung interner Logdaten
    • Ich hatte einen ähnlichen Ansatz, glaube aber letztlich, dass es praktisch kaum möglich ist, Regel zwei wirklich ausreichend einzuhalten
      Theoretisch klingt sie gut, aber in der Praxis nimmt man immer mentale Abkürzungen, ohne es zu merken
      Wenn ich in einer unbekannten Codebasis einen Fehler selbst behebe und das mit einem Fall vergleiche, in dem ich glaubte, die Arbeit des Agenten „vollständig verstanden“ zu haben, bleibt eine Woche später unterschiedlich viel im Kopf hängen. Wenn ich es selbst mache, wird es zu allgemeinem Wissen, und die wichtigen Teile bleiben meist erhalten; wenn ich versuche, das vom Agenten Geschriebene als mein eigenes zu übernehmen, fühlt es sich in dem Moment zwar verstanden an, aber ich vergesse es sehr schnell wieder
      Deshalb bin ich zu dem Schluss gekommen, dass LLM-Hilfe in solchen Fällen meinen Zielen meistens schadet, selbst wenn man andere Sorgen wie Zeit- und Business-Druck außen vor lässt
  • Mir ist genau dasselbe passiert
    Der Betrug läuft so ab: In einer guten Codebasis kann AI viele Features bauen, und es wirkt schneller, sicherer und präziser. Besonders in Bereichen, die man selbst nicht gut kennt, fühlt es sich noch stärker so an
    Mit der Zeit wächst die Codebasis, die Navigationszeit wird länger und die Fehlerrate steigt. Man will es nicht wahrhaben und drückt noch stärker aufs Gas, bis Änderungen praktisch unmöglich werden und man erst dann aufhört
    Wenn man den Code wieder ansieht, reicht „Spaghetti“ als Beschreibung nicht mehr aus; es ist eher die Chinesische Mauer
    Am Ende habe ich 75.000 von 140.000 Zeilen gelöscht, und die drei Monate, in denen ich mich intensiv auf Agent-Coding eingelassen hatte, fühlten sich wie verschwendet an. Ich habe nutzlose Features gebaut, mehr Bugs produziert, mein mentales Modell des Codes verloren, schwierige Entscheidungen übersehen, die man nur erkennt, wenn man im Code steckt, und letztlich auch die Nutzer enttäuscht

    • Interessant ist, dass dieses Ergebnis für manche überraschend ist
      Nicht spöttisch gemeint, aber ich frage mich wirklich, was die ursprüngliche Erwartung war und woher sie kam
      An LLMs scheint man andere Erwartungen zu haben. Wenn man einem zufälligen „Entwickler“, den man nur online getroffen hat, eine zusammengefasste Feature-Beschreibung gibt und dann einen halb kaputten Haufen Implementierung zurückbekommt, wäre niemand überrascht
      Trotzdem erwarten Menschen von Maschinen, die gelegentlich wortreiche Halluzinationen produzieren, manchmal Wunder, die sie nicht einmal von Menschen erwarten würden. Ich frage mich, woher dieses Vertrauen kommt
    • Eine große Codebasis sollte aus kleinen Codebasen bestehen
      So wie eine große Stadt aus kleinen Städten besteht, hat man eine Karte, zoomt in einen lokalen Bereich hinein und arbeitet dort. Man muss nicht jedes Detail von New York kennen, nur um einen Kaffee zu trinken
      Eine wartbare gesunde Architektur zu schaffen, ist die Verantwortung der Person, die das Tool benutzt. AI verhindert das nicht, und wenn man das Tool richtig einsetzt, kann sie sogar helfen
    • Es dürfte Workflow-Lösungen geben, die nicht darin bestehen, AI ganz aufzugeben
      Zum Beispiel könnte man den von AI generierten Code sofort als Legacy-Code behandeln, starke Kapselungsgrenzen und klar definierte Interfaces ziehen und ihn dann in einen passiveren Workflow integrieren
      Es gibt ein Spektrum vom Einmal-Prompt bis zur Inline-Codegenerierung, und je nach Problem und Position im Codebase ist eine andere Form passend
      Einmalige Generierung eignet sich eher für Prototypenphasen, in denen man die Spezifikation oft wiederholt; sobald der Prototyp Form annimmt, geht man auf Modul- oder Dateiebene herunter und arbeitet systematischer, während man auf dieser Ebene weiterhin ein brauchbares mentales Modell behält
    • Ich frage mich, ob der generierte Code überhaupt nicht gelesen wurde und einfach alles automatisch committed wurde
      Wenn er gelesen, aber nicht verstanden wurde, hätte man sich zu jedem Output detaillierte Kommentare geben lassen können. Wenn man weiß, dass Modelle mit wachsender Codebasis Probleme bekommen, muss man den Output umso strenger prüfen, je höher die Komplexität wird
    • Ich habe zwar keine großen Codebasen betreut, aber vielleicht könnte man einen Workflow im Stil von Working Effectively with Legacy Code anwenden
      Also Inseln mit höherer Codequalität schaffen, AI helfen lassen, Entwicklerintentionen und Business-Regeln zu rekonstruieren, und im Zielmodul Seams und Unit-Tests anlegen
      AI muss nicht zwingend nur zur Steigerung des Durchsatzes dienen; sie kann auch ein flexibles Werkzeug für Exploration und Refactoring sein, das späteres Handschreiben oder Agent-Implementierungen unterstützt
  • Jedes Mal, wenn ich solche Texte sehe, vergleiche ich die Geschwindigkeit, die Leute mit AI angeblich erreichen, mit der Geschwindigkeit, die ich erreiche, wenn ich einfach selbst programmiere
    Zufällig arbeite ich seit 7 Monaten an einem 3D-MMO-Projekt, das inzwischen spielbar ist, Leuten Spaß macht, okay aussieht und problemlos Hunderte Nutzer auf den Server bringt. Die Architektur ist ziemlich gut, Features lassen sich leicht erweitern, und nach etwa einem Jahr Entwicklung könnte man es veröffentlichen
    Im Originalartikel hat man dagegen nach 7 Monaten Vibe-Coding nicht einmal ein grundlegendes TUI gebaut. Die Feature-Geschwindigkeit kann sich hoch anfühlen, aber für so ein Basis-UI ist das unglaublich langsam. Es gibt viele gute TUI-Libraries, und wenn man nur Tabellen mit den nötigen Daten füllen muss, kann man das von Hand in ein paar Wochen bauen
    Mit AI hat man stark das Gefühl, schnell viel voranzukommen, aber in Wirklichkeit scheint es oft viel langsamer zu sein als manuelles Coden. Produktivitätsdaten scheinen ebenfalls eher zu stützen, dass AI-Nutzer sich schneller fühlen, tatsächlich aber weniger Output haben

    • Diese Kennzahl hängt stark davon ab, wer AI wofür benutzt
      Der größte Zeitfresser in der Softwareentwicklung sind Meetings, in denen Erwartungen von Stakeholdern und Lösungsansätze abgeglichen werden. Aus dieser Perspektive hilft AI fast gar nicht; wenn man also Personenstunden vom Vorschlag bis zum Einstieg in die Testschleife vergleicht, bekommt man ernüchternde Ergebnisse
      Aber bei Problemlösung, Bugfixing und der Umsetzung bereits freigegebener Lösungen bin ich im Vergleich zu früher mindestens um den Faktor 10 besser geworden. Es geht nicht nur um reine Zeit, sondern auch um die Fähigkeit, beobachtetes Verhalten zu interpretieren und Probleme zu untersuchen
      Allerdings gibt es auch Leute, die mit AI keine wertvollen präzisen Ergebnisse erzeugen können. Wenn man genau weiß, was man will und wie man es will, ist AI eine große Hilfe. Wenn ich ihr etwas gebe, das ich ohnehin gemacht hätte, macht sie es schneller. Wenn ich aber selbst nicht genau weiß, was ich will, schadet AI dem Fortschritt
    • Ich bin kürzlich zum selben Schluss gekommen
      Wenn Leute zeigen, was sie mit LLMs gebaut haben, beeindruckt mich das selten, weil es meist Dinge sind, die man auch von Hand in sehr kurzer Zeit bauen könnte
      Ich beobachte auch nicht, dass wirklich beeindruckende Software häufiger wird, was dazu passt, dass LLMs derzeit eher einfache als wichtige Probleme lösen
    • Die Menschen, die den größten Gewinn aus LLMs ziehen, sind vermutlich oft diejenigen, die ursprünglich gar nicht gut darin waren, gute Software zu bauen, oder denen die Fähigkeit dazu fehlte
    • Ich fand die 7 Monate auch merkwürdig. Selbst in einer neuen Sprache sollte das nicht so lange dauern
      Außerdem wird Codequalität zu wenig erwähnt
      Eine per Vibe-Coding entstandene Codebasis ist ein großartiges Beispiel dafür, dass LLMs im Code-Schreiben gar nicht so gut sind. Sie beheben eigene Fehler und bauen sie sofort wieder ein, und selbst die Musterverwendung ist inkonsistent
      In letzter Zeit trifft Claude auch „interessante“ Stilentscheidungen, die nicht zum Stil der bestehenden Codebasis passen
    • Systeme der GPT-Familie sind architektonisch darauf ausgelegt, Text — also Sprache und Code — zu erzeugen; das ist ihr Zweck und ihr Lebenselixier. Deshalb scheinen sie intern in Richtung alles selbst erzeugen verzerrt zu sein
      Solche Wiederholungen muss man mit Sprache im Stil eines „Senior Developers“ unterbinden
  • Der Teil „Bevor ich Code schreibe, entwerfe ich konkrete Interfaces, Nachrichtentypen und Ownership-Regeln selbst“ ist genau der schwierige Teil am Programmieren
    Wenn die Architektur da ist, ist das Schreiben von Code sehr leicht. Wenn man den Code nicht selbst schreibt, merkt man schwerer, dass man etwa eine API mit nullable Werten entworfen hat, die Datenbank das aber nicht erlaubt — oder dass sie es zwar erlaubt, man aber andere kleine Probleme übersehen hat
    Ich verstehe nicht, wie man diesen Text schreiben und trotzdem nicht merken kann, dass das Problem AI ist. Nicht nur, weil man AI die Architektur überlassen hat, sondern weil man nicht genau darauf geachtet hat, was AI überhaupt macht
    AI ist ein verherrlichter Codegenerator, und man muss alles kontrollieren, was sie tut. Der schwierige Teil der Softwaretechnik war nie das Tippen von Code, sondern alles drum herum

    • Ich glaube, es gibt zwei Arten von Entwicklern: diejenigen, die denken, dass der schwierige Teil der Code ist, und diejenigen, die das nicht denken
      Entwickler, die Coden für schwierig halten, lieben AI-Coding wirklich, weil etwas, das früher schwer war, jetzt leicht geworden ist
      Für diejenigen, die Coden für leicht halten, geht es beim Coden um Abstraktion, Wartbarkeit und Erweiterbarkeit. Schwierig ist, ein sinnvolles Fundament zu legen, auf dem Software wachsen kann, und wenn man die richtige Abstraktion gefunden hat, ist der Rest vergleichsweise einfach
      Für diese Leute ist AI-Coding ein nützliches Werkzeug, aber kein magisches. Der Autor des Originalposts hat die Grenzen von AI erkannt und gehört deshalb zur zweiten Gruppe; er hat den schwierigen Teil gesehen, den AI nicht kann
    • Im Moment gibt es auch ein Problem mit unscharfen Definitionen
      Auf der einen Seite gibt es Leute, die starke Tab-Autovervollständigung oder Chatbots im Nebefenster nutzen und trotzdem alles gründlich reviewen, und auf der anderen Seite neue Editoren, die damit werben, Dutzende Agenten zu orchestrieren, sodass man den Großteil des Codes angeblich gar nicht mehr lesen werde, wie Steve Yegge hier: https://steve-yegge.medium.com/welcome-to-gas-town-4f25ee16d...
      Die erste Gruppe denkt weiterhin tief über Design, Interfaces und Datenstrukturen nach und reviewt streng. Die zweite Gruppe tut das nicht, und das macht mir mehr Sorgen
    • Ich glaube, Agenten scheitern fast immer zwischen Planung und Ausführung
      Ich folge dem Ansatz plan → red/green/refactor, und die Planung selbst wirkt oft ziemlich plausibel und fundiert, weil alle Dokus und Forendiskussionen eingesaugt werden
      Das Problem ist, dass beim tatsächlichen Arbeiten zwangsläufig Stellen auftauchen, an denen Doku und Implementierung voneinander abweichen. Vielleicht wurde eine Tool-Kombination nie so verwendet, vielleicht ist die Doku veraltet, vielleicht ist es einfach ein Bug
      Wenn Projekt- oder Featureziel aber klar genug sind und man lokal ausführen und testen kann, kann sich ein Agent aus architektonischen Sackgassen iterativ wieder herausarbeiten. Er schaut sogar in Dependencies und Library-Code und schlägt Upstream-Fixes vor; das ähnelt dem, was ich in tiefen Debugging-Sessions selbst tue
      Deshalb bin ich ziemlich zufrieden damit, langweilige Arbeit lieber zu delegieren und zu überwachen, statt sie selbst zu machen. Allerdings graben viele Teammitglieder Architekturprobleme nicht so tief auf und „eskalieren an den Architekten“, und das scheint langfristig nicht gut zu sein
      Das Zeitfenster, in dem man alles ausführen und verstehen kann, schließt sich schnell. Vielleicht passen wir uns aber an, indem wir neue Tools und Frameworks bauen — so wie wir auch Compiler benutzen, ohne jeden Schritt bis zum Maschinencode oder Branch Prediction und Caching moderner CPUs vollständig zu verstehen
    • Viele scheinen zu übersehen, dass man alles überprüfen muss, was AI tut
      Als jemand mit nicht allzu viel Code-Erfahrung lerne ich mehr als je zuvor, indem ich Ergebnisse überprüfe und sehe, was richtig und falsch ist
      Deshalb glaube ich auch nicht, dass es bald dramatisch besser wird. Wenn Leute mich fragen: „Wie bekommst du aus Claude so gute Ergebnisse?“, lautet meine Antwort immer: „Ich habe genau hingeschaut, Probleme gefunden und Claude gebeten, sie zu beheben.“ Das ist wirklich alles, aber schon bei dieser Antwort wird der Blick oft glasig
      Das ist ähnlich wie bei Google: Es hat das Finden von Informationen erleichtert, aber den menschlichen Anteil nicht entfernt, der gute von schlechten Informationen unterscheiden muss
    • Wenn ich Agenten benutze und sie nicht komplett hassen oder scheitern will, bleibt mir nur dieser Weg
      Erst denke ich über das Problem nach und entwerfe Struktur und APIs, und erst dann lasse ich AI die Implementierung übernehmen
  • Der Titel lautet zwar „Ich bin zum Handschreiben von Code zurückgekehrt“, aber tatsächlich macht die Person eher „die Design-Arbeit von Hand, bevor Code geschrieben wird“
    Dann scheint der Code immer noch von Claude generiert zu werden
    Noch gravierender ist, dass man offenbar 7 Monate lang glaubte, das Vibe-Coding-Projekt funktioniere gut, ohne den generierten Quellcode überhaupt anzuschauen, und sogar schon eine Domain dafür gekauft hatte

    • Kurz gesagt: ein Clickbait-Titel, und das eigentliche Ziel des Textes scheint zu sein, Aufmerksamkeit auf das eigene Projekt zu lenken
    • Ich habe auch schon Domains für Projektideen gekauft, nur wenige Minuten nachdem sie mir eingefallen waren
      Wenn es ein Side-Project ist und man Diffs verfolgt und schrittweise prüft, ist es nicht völlig absurd, den Code nicht bis in die Tiefe zu lesen. Sicher eine andere Arbeitsweise, aber nicht völlig verrückt
  • Es fühlt sich an, als würde man Entwicklern dabei zusehen, wie sie die Lektionen aus Projektmanagement und Produktmanagement im Speedrun nachholen
    Jetzt sehen sie, dass Spezifikationen nützlich sind und dass ein Projekt nicht schneller wird, nur weil man viel falschen Code schreiben lässt. Entwickler ärgern sich oft über Meetings und Diskussionen als Hindernis fürs Coden, aber genau solche Prozesse existieren häufig, um zu verhindern, dass alle noch mehr falsche Dinge bauen
    Man hat auch erkannt, dass Aufgabenmanagement nützlich ist, und jetzt, wo ständig davon die Rede ist, dass man das gesamte Design im Voraus machen müsse, bewegt man sich in Richtung Wasserfall
    Als Nächstes wird man Prototyping benennen, über inkrementelle Features sprechen, bei denen alte und neue Anforderungen gleichzeitig verwaltet werden, und irgendwann sagen, dass Kunden stärker eingebunden werden müssen
    Es lohnt sich, sich anzusehen, was Projektmanager und Produktmanager tatsächlich tun. Sie führen ein Produkt namens Code, ohne dass von ihnen erwartet wird, Code zu lesen, und müssen das alles allein in natürlicher Sprache erreichen

    • Stimmt schon. Diese Leute scheinen nie Manager gewesen zu sein
      Glauben sie, Menschen würden nichts Kaputtes schreiben? Glauben sie, Teams würden nie eine Woche oder mehrere Monate auf dem falschen Weg verbringen? Jetzt kann man all das mit Vibe-Coding in 30 Minuten erleben. Als ehemaliger technischer Produktmanager fühlt sich das exakt gleich an
  • Ich bin verwirrt von der Lücke zwischen Titel und Fazit, weil es in Wirklichkeit gar nicht so aussieht, als würde hier jemand wieder von Hand Code schreiben

    • Ich denke, der sensationelle Titel war absichtlich so gewählt, damit HN anbeißt und der Beitrag auf der Startseite landet
    • Der Text selbst wirkt auch nicht so, als sei er von Hand geschrieben. Was es auf die HN-Startseite geschafft hat, war eher der Titel als der Inhalt