- Projekte driften leicht in zwei Richtungen auseinander: in einen Flow, in dem man einfach losbaut und fertig wird, oder in einen Flow, in dem Recherche und Design immer größer werden und man das ursprüngliche Problem aus den Augen verliert. Für echten Fortschritt ist die Einfach-mal-machen-Seite oft weiter vorn.
- Selbst beim Bau einer Fuzzy-Path-Suche für Emacs führten Zusatzfunktionen einer guten Bibliothek zu neuen Anforderungen und blähten das Design auf; am Ende wurde der gesamte unnötige Anchor-Code verworfen, was YAGNI erneut bestätigte.
- Bei Code-Diffs erfasst ein zeilenbasierter Vergleich höhere Strukturen wie Funktionen oder Typen nicht sauber, und auch treesitter-basierte Tools können bei fehlerhaftem Entity-Matching lange Lösch- und Ergänzungsblöcke anzeigen, die schwer lesbar sind.
- Nötig ist zuerst ein Werkzeug mit minimalem Umfang, das zu einer turnweisen Review von LLM-Ausgaben passt: mit Entity-Extraktion für Rust und einfachem Matching beginnen und einen Workflow priorisieren, mit dem sich Änderungen auf höherer Ebene schnell überblicken lassen.
Zu viel Grübeln und Scope Creep
- Projekte teilen sich leicht in einen Flow, in dem man direkt baut und fertig wird, und einen, in dem man sich so tief in Vorbilder und Vorrecherche eingräbt, dass der Umfang wächst und das eigentliche Problem am Ende ungelöst bleibt.
- Ein am Wochenende gebautes Küchenregal wurde bei Kaffee grob entworfen, mit ein paar Iterationen an 3D-gedruckten Haltern versehen und dann mit Restmaterial und Farbe noch am selben Wochenende fertiggestellt.
- Das CAD für den Ikea-bin-Halter ist als OnShape CAD veröffentlicht.
- Das Material wurde aus Resten der Werkbank wiederverwendet, und die Ecken wurden mit einem palm sander nach Augenmaß geglättet.
- Bei diesem Regal war der eigentliche Erfolg weniger, exakt etwas für die Küche zu bauen, sondern gemeinsam mit einem Freund Spaß an Holzarbeit zu haben; dadurch musste man sich über Detailkriterien nicht übermäßig den Kopf zerbrechen.
- Auf der anderen Seite wurden bei der Suche nach einem strukturellen Diff-Tool vier Stunden in verwandte Tools und Workflows investiert, weil die Ergebnisse von difftastic unbefriedigend waren; am Ende führte das aber wieder zurück zum ursprünglichen Kriterium: ein besserer Diff-Workflow für Emacs.
- Langjährige Interessen wie Interfaces für Hardware-Prototyping, eine Sprache, die Clojure und Rust mischt, oder eine Sprache für CAD haben Hunderte Stunden an Hintergrundrecherche und kleinen Prototypen verschlungen, ohne bislang in Ergebnisse zu münden, die die ursprüngliche Motivation direkt lösen.
- Zum Hardware-Prototyping-Interface siehe September 2023, zur Sprachgestaltung November 2023, und zu CAD-Ideen constraints, bidirectional editing und other dubious ideas.
- Bei den Sprach- und CAD-Projekten bleiben die Erfolgskriterien unscharf: Sollen sie Rust oder Clojure ersetzen, nur Teilprobleme abdecken, als Lernspielplatz ausreichen, kommerzielle CAD-Systeme verdrängen oder auch für andere nützlich sein?
- Solche Fragen zu prüfen ist wertvoll, aber statt immer mehr nur zu prüfen, ist es oft besser, tatsächlich mehr Dinge zu bauen.
- Selbst wenn im Rückblick klar minderwertige Ergebnisse herauskommen, bringt einfach anzufangen einen insgesamt oft weiter.
Das Erhaltungsgesetz des Scope Creep
- Auch Zeit für blindes Drauflosbauen ist begrenzt, und es braucht Balance; nachdem mit einem LLM-Agenten viel Code geschrieben und am Ende wieder komplett verworfen wurde, drängte sich erneut YAGNI auf.
- Gewünscht war eine Finda-artige Fuzzy-Path-Suche über das gesamte Dateisystem für Emacs, und weil eine ähnliche Funktion früher schon einmal von Hand geschrieben worden war, schien es realistisch, sie mit beaufsichtigtem LLM-Einsatz in wenigen Stunden fertigzubekommen.
- In der ersten Planungsunterhaltung wurde Nucleo empfohlen; weil es gut entworfen und dokumentiert ist, wurde es übernommen, um Funktionen wie smart case und Unicode normalization mitzunehmen.
- Zum Beispiel matcht die Query
foosowohlFooals auchfoo, währendFoonicht auffoomatcht. - Auch der Umgang mit
cafeundcaféfällt in diesen Zusammenhang.
- Zum Beispiel matcht die Query
- Das Problem war nicht die gute Bibliothek selbst, sondern dass Nucleo auch Anchor-Funktionalität unterstützt.
- Da in einem Korpus, der nur aus Dateipfaden besteht, ein Zeilenanfangs-Anchor nutzlos schien, sollte er stattdessen als Anchor auf Basis von Path-Segmenten interpretiert werden.
- So sollte etwa
^fooauf/root/foobar/matchen, aber nicht auf/root/barfoo/.
- So sollte etwa
- Um das effizient zu verarbeiten, müsste der Index Segmentgrenzen speichern, und für jedes Segment müsste sich die Query schnell prüfen lassen.
- Dazu kam die Unterstützung für Anchor-Queries mit Slash wie
^foo/bar; mit reiner Segmentprüfung wird es dann schwierig, Pfade wie/root/foo/bar/baz/korrekt zu matchen. - Für dieses Design gingen weitere Stunden drauf: Ideen mit dem LLM hin und her, Code zum Wrappen der Nucleo-Typen schreiben und am Ende feststellen, dass der Code zu groß und unbefriedigend geworden war, sodass schließlich ein kleinerer Wrapper noch einmal selbst neu geschrieben wurde.
- Nach einer Pause fiel auf, dass in Finda eigentlich nie Anchor-Funktionalität vermisst worden war und dass sich bei einem Pfad-Korpus die meisten Anchor-Effekte durch ein
/am Anfang oder Ende der Query ersetzen lassen.- Eine Ausnahme bleibt nur beim Anchor auf das Dateiende.
- Am Ende wurde sämtlicher Anchor-bezogener Code weggeworfen, und es bleibt unklar, ob gegenüber einem direkten Selbstbau ohne Diskussionen mit LLM oder anderen überhaupt noch ein Vorteil geblieben ist.
- Es scheint so etwas wie ein Erhaltungsgesetz zu geben: Je schneller Programmieren wird, desto mehr unnötige Features, Rabbit Holes und Umwege entstehen mit.
Strukturelles Diffing
- Bei Code meint Diff meist eine zeilenbasierte Zusammenfassung von Änderungen zwischen zwei Dateiversionen; in einer Unified View werden Ergänzungen und Löschungen mit
+und-markiert. - Dasselbe Diff kann auch als Links-rechts-Vergleich gerendert werden, und je komplexer die Änderung, desto leichter lässt sich diese Form oft lesen.
- Das Problem zeilenbasierter Diffs ist, dass sie höhere Strukturen wie Funktionen oder Typen nicht erkennen; wenn geschweifte Klammern zufällig irgendwie aufgehen, kann die Markierung sogar dann ausbleiben, wenn Änderungen eigentlich zu verschiedenen Funktionen gehören.
- difftastic versucht dieses Problem mit dem von treesitter gelieferten concrete syntax tree zu verringern, doch das Matching von Entities zwischen Versionen funktioniert nicht immer gut.
- Im konkreten auslösenden Diff wurde
struct PendingClickauf beiden Seiten nicht als Entsprechung erkannt; links erschien es als Löschung, rechts als Ergänzung. - Warum das Matching scheiterte, wurde nicht weiter untersucht, aber selbst wenn das Gesamtdiff dadurch länger wird, erschien es besser,
PendingClickRequestundPendingClickauf beiden Seiten als Entsprechungen zu sehen.
Strukturelle Diff-Tools und Referenzen
- Als am ausgereiftesten und sorgfältigsten ausgearbeitetes Tool für semantic diff gilt semanticdiff.com.
- Es wird von einer kleinen Firma aus Deutschland angeboten und hat ein kostenloses VSCode-Plugin sowie eine Web-App für GitHub-PR-Diffs.
- Allerdings stellt es keine Code-Bibliothek bereit, auf der sich der gewünschte Workflow aufbauen ließe.
- Der Artikel semanticdiff vs. difftastic enthält viele nützliche Details, darunter auch das Problem, dass difftastic in Python nicht einmal bedeutungsvolle Einrückungsänderungen korrekt zeigt.
- Einer der Autoren schrieb in einem HN-Kommentar, dass man treesitter für semantische Verarbeitung letztlich wieder verlassen habe; wegen kontextabhängiger Keywords und Lexer-Verhalten könne das Parsing fehlschlagen, sodass das Tool sogar stoppe, wenn Namen wie
asyncals Parameter verwendet würden.
- diffsitter ist treesitter-basiert und enthält einen MCP server.
- Es hat viele GitHub-Stars, wirkte dokumentationsseitig aber nicht besonders gut, und es war schwer, Material zu finden, das die Funktionsweise erklärt.
- Im difftastic-Wiki steht, dass auf den Leaves des Baums eine longest-common-subsequence berechnet wird.
- gumtree ist ein Tool mit Forschungs- und Wissenschaftshintergrund aus dem Jahr 2014.
- Es benötigt Java und passt damit nicht zum persönlichen Ziel eines schnell in Emacs nutzbaren Werkzeugs.
- mergiraf ist ein treesitter-basierter Merge-Driver in Rust.
- Die architecture overview ist gut aufbereitet, und intern wird der Gumtree-Algorithmus verwendet.
- Dokumentation und Diagramme vermitteln den Eindruck eines sorgfältig gebauten Projekts.
- Ein Autor von semanticdiff.com schrieb in einem HN-Kommentar, GumTree liefere zwar schnell Ergebnisse, aber selbst mit Verbesserungen aus mehreren Folgearbeiten komme es immer wieder zu schlechten Matchings, weshalb man letztlich zu einem Dijkstra-basierten Ansatz zur Minimierung der Mapping-Kosten gewechselt sei.
- weave ist ein weiterer treesitter-basierter Merge-Driver in Rust.
- Die auffällige Landingpage, viele GitHub-Stars und ein MCP server erzeugten insgesamt einen etwas übertriebenen Eindruck.
- Angesehen wurde auch das Entity-Extraction-Crate sem.
- Der Kern des Diff-Codes ist ordentlich, aber etwas weitschweifig, und für das Entity-Matching wird ein greedy Algorithmus verwendet.
- Das Datenmodell erkennt keine Verschiebungen innerhalb einer Datei, obwohl solche Moves semantisch wichtig sein können.
- Außerdem steckt viel heuristikbasierte Impact-Analyse darin, die für Vertrauen eigentlich eine stärkere Sprachintegration bräuchte.
- Beim Ausführen von
sem diff --verbose HEAD~4trat sogar eine fehlerhafte Ausgabe auf, in der unveränderte Zeilen als geändert markiert wurden.
- Beim Ausführen von
- Als Grundlage eignete es sich nicht, weil es zu viele hypothetisch nützliche Features auf etwa 80-%-Fertigstellungsniveau enthält; gleichzeitig ist beeindruckend, dass in drei Monaten schon so viel entstanden ist.
- diffast berechnet auf Basis eines Forschungsalgorithmus von 2008 die tree edit-distance von ASTs.
- Über eigene Parser unterstützt es Python, Java, Verilog, Fortran und C/C++.
- Die example AST differences gallery ist gut aufbereitet.
- Informationen lassen sich als Tupel exportieren und damit in Datalog nutzen.
- autochrome ist ein Diff-Tool nur für Clojure und verwendet dynamic programming.
- Die visuelle Erklärung und das Beispiel-Walkthrough sind sehr gut.
- Tristan Humes Designing a Tree Diff Algorithm Using Dynamic Programming and A* ist als Text zum Entwurf von Tree-Diff-Algorithmen sehr lesenswert.
Gewünschter Workflow und Minimalumfangs-Plan
- Der wichtigste Anwendungsfall ist die turnweise Review von LLM-Ausgaben; der Agent soll nicht einfach auf einmal über 10.000 Zeilen Code ausspucken.
- Der Agent bekommt klar begrenzte Aufgaben, danach möchte man nach ein paar Minuten zurückkommen, sich einen Überblick über die Gesamtänderungen verschaffen und dann in Emacs direkt nachbearbeiten, alles verwerfen und neu versuchen oder gleich alles selbst neu schreiben.
- Der gewünschte Workflow beginnt mit einer Übersicht auf höherer Ebene, welche Typen, Funktionen und Methoden hinzugefügt, entfernt oder verändert wurden.
- Darauf aufbauend sollen sich Text-Diffs pro Entity schnell aufklappen lassen, sodass sich die Zusammenfassung natürlich bis in die Detail-Diffs hinein erweitern lässt.
- Änderungen sollen außerdem direkt an Ort und Stelle bearbeitet werden können, ohne an eine andere Ansicht zu springen; gewünscht ist Inline-Editing, ohne vom Diff-Bildschirm in eine Dateiansicht zu wechseln.
- Das Zielbild ist, den Review- und Staging-Workflow von Magit von Datei- und Zeilenebene auf die Ebene von Entities zu übertragen.
- Entsprechend der wiederentdeckten Lehre vom Minimalumfang ist zunächst geplant, hastig ein treesitter-basiertes Framework zur Entity-Extraktion nur für Rust selbst zu bauen.
- Für das Matching soll zuerst eine einfache greedy Methode reichen, und das Diff soll in der Kommandozeile gerendert werden.
- Wenn das bei einem bestimmten Commit bessere Ergebnisse liefert als difftastic, soll es danach an einen interaktiveren Emacs-Workflow à la Magit angeschlossen werden.
- Wenn möglich, bleibt auch die Option offen, Magit selbst wiederzuverwenden.
- Unterstützung für weitere Sprachen soll jeweils nur bei Bedarf ergänzt werden.
- Später könnte statt einer einfachen greedy Methode auch ein punktbasierter globaler Matching-Ansatz untersucht werden.
- Wenn das Ergebnis zufriedenstellend ist, könnte es veröffentlicht werden, aber GitHub-Stars oder HN-Karma zu sammeln ist nicht das Ziel; vielleicht bleibt es auch einfach ein still privat genutztes Werkzeug.
- Manchmal will man eben einfach nur ein Regal, und mit diesem Satz wird die Haltung noch einmal gebündelt: nicht überdehnen, sondern nur das bauen, was nötig ist.
1 Kommentare
Hacker-News-Kommentare
Ich finde, das zeigt die größte Schwierigkeit einer PhD-Forschung sehr gut
Wenn man sich ein spannendes Thema aussucht und versucht, möglichst alle relevanten Vorarbeiten zu lesen, merkt man schnell, wie viel von dem, was man selbst machen wollte, bereits existiert, und dann entsteht leicht massiver Scope Creep
Nachdem die anfängliche Energie und Begeisterung aufgebraucht sind, muss man die letzten 20–30 % irgendwie mit Gewalt durchdrücken, um den Stand zu erreichen, an dem es publizierbar ist
An Tag 400 hat man fast eine Theorie von allem erklärt und versucht, ein Experimentalsystem im Lagrange-Punkt-Orbit zu bauen, um ein universelles Teilchen nachzuweisen, das alle Kräfte des bekannten Universums vermittelt
Ich frage mich, wie man das abmildern kann
In Wirklichkeit geht es oft eher darum, die Beobachtbarkeit eines Systems von 1 % auf 1,001 % zu erhöhen, und es ist eher ein Zugangstor zu einer akademischen Laufbahn
Deshalb sieht man fast nie Dissertationen, die wirklich spannend, wirklich neu oder direkt wissenschaftlich anwendbar sind
Ich habe kaum jemanden gesehen, der Forschung tatsächlich so betreibt; meistens liest man zwei oder drei Paper und baut von dort aus weiter
Tiefer in die Forschungsliteratur einzusteigen ist sinnvoller, wenn bereits Ergebnisse da sind und man anfängt, sie schriftlich auszuarbeiten
Ich muss immer wieder an den Satz denken: Besser ist gut genug
Kleine Verbesserungen summieren sich mit der Zeit, und nichts ist von Anfang an vollkommen neu, daher wirkt es oft eher kontraproduktiv, im Sitzen ein perfektes Design erzwingen zu wollen
Auch der Gedanke, dass das Hindernis selbst zum Weg wird, passt hier gut
Ein Kollege, mit dem ich gearbeitet habe, sagte bei Kritik an Codeänderungen, wenn er das Gefühl hatte, zu kleinlich zu werden: „Es ist besser als vorher“
Dadurch konnte er Verbesserungsmöglichkeiten ansprechen und zugleich signalisieren, dass man trotz kleiner Mängel weitermachen darf, und ich halte diese Haltung für sehr wertvoll
Früher dachte ich, Perfektionismus bedeute nur, unrealistisch hohe Leistung um jeden Preis anzustreben, aber auch nicht weiterzukommen und aufzugeben, weil etwas nicht perfekt ist, kann Perfektionismus sein
Auch das Aufschieben großer Aufgaben hat oft dieselbe Wurzel
Mir gefiel eine Aussage des CEOs von Rec Room
Teams sagen fast immer, dass Projekte kürzer hätten sein sollen; fast nie sagen sie, man hätte den Release weiter verschieben, alles komplizierter machen und noch mehr polieren sollen
Das gilt nicht in jeder Situation zu 100 %, aber wenn man schon einen Fehler machen will, ist es besser, klein zu bauen und früh zu releasen, statt zu groß aufzuziehen und Zeit zu verschwenden
Menschen kommen von Natur aus leicht auf ähnliche Ideen, deshalb wird ein Projekt, das man ohne Vorwissen fertigstellt, am Ende oft zumindest teilweise eine Neuerfindung sein
Umgekehrt kann man durch vorherige Recherche entmutigt werden, wenn man merkt, dass es teilweise nur eine Wiederholung von etwas bereits Existierendem ist
Trotzdem kann das Wichtigste sein, es allein zum eigenen Lernen bis zum Ende zu bauen
Wenn man allerdings neue Forschungsergebnisse liefern oder mit einem einzigartigen Projekt Geld verdienen muss, ist es schwieriger, aber selbst in solchen Bereichen ist man überraschend oft recht nachsichtig, wenn man Bestehendes nur ein wenig anders dreht
Ich erlebe gerade bei einem Side Project genau diese Situation
Das Gebiet ist Information Retrieval, und ich habe darin wenig Erfahrung, also gibt es natürlich jede Menge prior art, aus der ich lernen oder die ich integrieren könnte
Nachdem ich diesen Text gelesen habe, neige ich deshalb stärker dazu, zuerst mein eigenes Ding zu bauen und nur dann nach Vorbildern zu schauen, wenn ich feststecke oder Ideen brauche
Andererseits wirkte es in der kürzlich erschienenen Clojure-Doku so, als hätte Rich Hickey sich vor seiner Arbeit lange und intensiv mit Vorarbeiten, Papers und anderen Sprachen beschäftigt
Aber auch er hatte vorher schon andere Sprachen gebaut, also begann das größere Bild letztlich ebenfalls damit, durch eigenes Bauen zu lernen
Vielleicht sollte man also nicht zu lange nur nachdenken, sondern erst einmal etwas bauen, in der Praxis Lektionen sammeln, gegen Wände laufen und erst dann tiefer recherchieren, wenn es wirklich nötig wird
Danach habe ich auch noch „Easy made Simple“ und „Hammock Driven Development“ gesehen, und jetzt möchte ich Clojure lernen
Clojure documentary on CultRepo channel: https://www.youtube.com/watch?v=Y24vK_QDLFg
Simple Made Easy: https://www.youtube.com/watch?v=SxdOUGdseq4
Hammock Driven Development: https://www.youtube.com/watch?v=f84n5oFoZBc
Deadlines zu setzen hat bei mir die meisten Scope-Creep-Probleme gelöst
Gefühlt sind Projekte mit harten Deadlines wie bei einem Game Jam oder Programmierwettbewerb leichter abzuschließen, während Projekte mit offenem Ende viel schwerer bis zum Schluss durchzuziehen sind
Das wirkt auf mich ähnlich wie beim C++-Standard, der auch nicht wartet, bis alle gewünschten Features fertig sind, sondern trotzdem alle drei Jahre erscheint
https://news.ycombinator.com/item?id=20428703
Der Text war interessant, aber die Gedanken des Autors wirkten etwas zerfasert und verstreut
Für jemanden, der sagt, er werde von Scope Creep überwältigt, scheint er am Ende des Textes erstaunlich viel zu schaffen; dort hängen ja massenhaft Links zu allen möglichen Themen
Letztlich wirkt er wie jemand, der Lernen und Ausprobieren wirklich liebt, und schon der Prozess, in Rabbit Holes abzutauchen, stimuliert seinen Kopf offenbar angenehm
Als jemand, der allein entwickelt, hatte ich eine Erkenntnis, die mir sehr geholfen hat
Das meiste, was wie eine notwendige Abstraktion aussieht, war in Wahrheit nur Scope Creep mit anderem Namen
Ich habe an neue Features ständig Flags gehängt und irgendwann ein Muster in meinem Code gesehen, also habe ich eine Regel eingeführt
Ein Feature wird nicht ausgerollt, wenn es keinen Test für das Verhalten bei deaktiviertem Flag gibt
Dadurch habe ich Flags nicht mehr als Ausweg betrachtet, sondern als Teil des Produkts, und drei Features aus dem Backlog haben sich ganz von selbst erledigt, sobald ich sie unter diesem Blickwinkel sah
Zu viel Planung und Scope Creep sind sicher ein Problem, aber umgekehrt sollte man auch aufpassen, nicht zu stark in Richtung ad-hoc development zu kippen
Einige meiner erfolgreichsten Projekte waren solche, bei denen ich die Daten modelliert und den Großteil der Funktionen im Voraus geplant und geprüft habe, bevor ich überhaupt funktionierende Software gebaut habe
In dieser Phase weiß man oft noch nicht gut, was übertrieben ist, und wenn man Funktionen weglässt, die ich oder die Nutzer wahrscheinlich wollen, kostet es später viel Zeit, weil man den Kern des Codes massiv umgestalten muss
Wenn man dagegen danebenliegt, wird das Projekt zu groß, und dann nennt man es Scope Creep
Letztlich hängt dieses Urteil davon ab, wie gut man die Domain kennt
Kennt man sie schlechter als gedacht, hat man viel Nacharbeit; kennt man sie besser als gedacht, hätte man eigentlich groß denken können und hat stattdessen mit Baby Steps Zeit verschwendet
Egal in welche Richtung man geht, ein gewisses Bedauern bleibt, daher fühlt sich das am Ende wie eine große Frage des Urteilsvermögens an
Man darf nicht in die Sunk-Cost-Falle geraten, und nur weil man ein paar Stunden zu einem Thema auf PhD-Niveau recherchiert hat, heißt das nicht, dass es zwingend ins Projekt muss
Wenn es nicht genau zum aktuellen Problem passt, sollte man es entschlossen verwerfen