Suche/In einer großen Codebasis die Ursache beheben und die öffentliche API kompatibel halten

In einer großen Codebasis die Ursache beheben und die öffentliche API kompatibel halten

Wenn eine KI eine große, geschichtete Codebasis ändert. Hält Build-Logs kurz, verfolgt Fehler bis zur Ursache weiter oben, prüft den eigenen Diff auf Helfer, die nur Probleme verdecken, und schreibt einen klaren Pull Request.

Diese KI-Regeln verwenden
## Build-Logs kurz halten und vor jedem Commit Formatter und Tests ausführen - Beim Bauen die gesamte Build-Ausgabe ins Nullgerät umleiten und denselben Befehl nur bei einem Fehlschlag erneut ausführlich ausführen, damit ein erfolgreicher Build keine Tokens für Logs verbraucht. Beispiel: `build-command >/dev/null 2>&1 || build-command`. - Lokal zwischengespeicherte Abhängigkeiten gegenüber Netzwerkabrufen bevorzugen und, falls verfügbar, einen Build- oder Compiler-Cache aktivieren, um inkrementelle Neubauten zu beschleunigen. - Bei langen unbeaufsichtigten oder agentengesteuerten Builds modale Abbruch- oder Assertion-Dialoge bereits zur Konfigurationszeit deaktivieren, damit der Lauf nicht wartend auf einen Menschen blockiert. - Den Formatter vor jedem Commit ausführen und dessen reinen Prüfmodus nutzen, um ohne Dateiänderung zu verifizieren; Code, der nicht dem Projektstil entspricht, erzwingt vermeidbare Review-Schleifen. - Die Testsuite vom Repository-Wurzelverzeichnis aus ausführen und Regressionstests als Dateien im Tests-Verzeichnis des Projekts ablegen. Ist keine GPU oder Spezialhardware verfügbar, CPU- oder Interpreter-Testmodi verwenden, statt die Abdeckung auszulassen. - In Cross-Shell-Umgebungen, etwa wenn eine Unix-Shell Host-Tools aufruft, die korrekte ausführbare Dateiendung anhängen, damit die vorgesehene Host-Binärdatei läuft und nicht eine für die falsche Plattform. ## Die Ursache weiter oben beheben, statt Schutzabfragen einzubauen - Auf eine Darstellung hinarbeiten, die durch ihre Konstruktion korrekt und robust ist, auch wenn das eine größere Überarbeitung bedeutet als den kleinstmöglichen Diff. - Ursachen beheben, nicht Symptome. Tritt ein Fehler in einer späten Phase wie der Ausgabeerzeugung auf, liegt die Ursache meist vorgelagert: in einer früheren Transformation, Legalisierung, Spezialisierung, Lowering-Stufe oder in der Datendarstellung selbst. Dorthin zurückverfolgen und dort beheben. - Jede Änderung hinterfragen, bevor man sie beibehält: Warum ist sie nötig, welcher Test schlägt ohne sie fehl, und ist das die richtige Lösung oder signalisiert das Problem, dass die Darstellung fehlerhaft ist? Lässt sich kein Test benennen, der ohne die Änderung fehlschlägt, sollte sie vermutlich gar nicht existieren. - Nicht kaschieren. Eine Schutzabfrage, Null-Prüfung oder Sonderbehandlung, die eine fehlerhafte Struktur überdeckt, verbirgt einen Darstellungsfehler, und eine Schutzabfrage, die bei korrekter Eingabe nie ausgelöst wird, ist toter Code. Die Darstellung korrekt machen, damit nachgelagerte Konsumenten einfach bleiben. - Die Form der Eingabe hinterfragen. Wird eine bestimmte Eingabeform behandelt, immer fragen, ob diese Form selbst korrekt ist oder ob stattdessen der vorgelagerte Erzeuger korrigiert werden sollte; lokal nur behandeln, wenn die Form tatsächlich gültige Eingabe ist. - Korrekte Darstellung vor Editierdistanz bevorzugen. Sollen zwei Oberflächenformen äquivalent sein, sie identisch modellieren, und konzeptionell ungeordnete Schlüssel-Wert-Daten über Schlüssel oder Rolle statt über Position oder Index ansprechen. - Während der gesamten Aufgabe ein Arbeitsprotokoll führen: das Problem mit einem anschaulichen Beispiel, Blockaden, wie sich eine Lösung in die nächste fortsetzt, die gewählte Lösung und warum sie prinzipientreu ist, sowie verworfene Alternativen. Daraus die Änderungsbeschreibung destillieren und aus dem committeten Code heraushalten. ## Den eigenen Diff auf Helfer prüfen, die nur schlechtes Design verdecken - Vor dem Abschluss einer nicht-trivialen Änderung den Diff auf Anzeichen prüfen, dass er eine schlechte Darstellung kompensiert, und diese Muster als Warnsignale behandeln, bis belegt ist, dass sie auf dieser Ebene hingehören. - Eigene Hilfsfunktionen für semantische Äquivalenz, etwa neue rekursive „ist-äquivalent"- oder „passt-zusammen"-Funktionen, bedeuten oft, dass zwei alternative Darstellungen nebeneinander bestehen bleiben durften; zuerst fragen, warum ein bestehender Kanonisierungs-, Substitutions- oder Gleichheitspfad sie nicht bereits identisch macht. - Ungeprüftes Wachstum an Hilfsfunktionen: jede neue Hilfsfunktion, jeden Fallback und jede Try-artige Funktion als Prüfziel behandeln, besonders wenn sie nur existiert, um einen einzelnen fehlschlagenden Test zu bestehen, oder wenn sie Teile eines bestehenden Mechanismus neu implementiert. - Die Rekonstruktion von Syntax aus Semantik, also das Wiederaufbauen von Oberflächensyntax aus bereits geprüften semantischen Daten, bedeutet meist, dass der Erzeuger die falsche Darstellung bewahrt hat; der geprüfte semantische Wert sollte die Quelle der Wahrheit sein. - Die Wiedergewinnung von Kontext durch Graphdurchlauf, also das Durchlaufen von Operanden-, Referenz- oder Besitzketten, um Argumente, Schlüssel oder übergeordnete Elemente zu rekonstruieren, ist meist eine nachgelagerte Reparatur; stattdessen die kanonische Form beim Erzeuger aufbauen. - Patches auf Konsumentenseite: späte Phasen sollten fehlerhafte Formen aus früheren Phasen nicht flicken, und fest codierte Darstellungsdetails (Sonderfälle für bestimmte Unterklassen, magische Namen, Argumentindizes oder Reihenfolge von Einträgen) brauchen eine starke Invariante und gehören meist an eine kanonische Konstruktionsgrenze. - Stilles Abfangen unmöglicher Formen: eine Schutzabfrage, die für eine vertragswidrige Form einen Standardwert zurückgibt, verbirgt Fehler; die unmögliche Form per Assertion abfangen, statt sie stillschweigend zu akzeptieren. - Die Prüfung mit einer Inventarliste verdächtiger Hilfsfunktionen beginnen: jede neue Hilfsfunktion, mit welchem bestehenden Mechanismus sie sich überschneidet, und welcher Test ohne sie fehlschlägt. Für jede markierte Änderung die genaue Eingabeform und ihren Erzeuger benennen, entscheiden, ob die Form kanonisch oder eine zufällige Alternative ist, zuerst eine Lösung beim Erzeuger versuchen, die bestehende Quelle der Wahrheit benennen und die Rückbau-Probe machen: die Änderung entfernen, den kleinsten fehlschlagenden Test ausführen und den tatsächlichen Bruch zurückverfolgen. Eine markierte Änderung nicht allein deshalb behalten, weil sie Tests bestehen lässt. ## Kommentare mit Beispielen schreiben und jede Information an nur einer Stelle halten - Funktionskommentare als vollständige Sätze schreiben: zuerst festhalten, was die Funktion tut, dann warum sie existiert, falls das nicht offensichtlich ist, und bei nicht-trivialem Verhalten ein konkretes Beispiel statt eines knappen Fragments einfügen. - In Kommentaren und Erklärungen anschauliche Beispiele verwenden. „Betrachten wir folgendes Beispiel:" mit echtem Code bevorzugen und dann Schritt für Schritt beschreiben, was passiert, welcher Durchlauf die Form erzeugt, welche Invariante der lokale Code bewahrt und welcher Konsument davon abhängt, statt abstrakte Bezeichnungen als Ersatz für eine Erklärung zu nutzen. - Vor dem Schreiben zuerst nach Wiederverwendbarem suchen und dann nicht-triviale Logik in eine benannte, dokumentierte Hilfsfunktion auslagern. Zuerst gemeinsam genutzte Utilities durchsuchen; ist die Logik wirklich neu, ihr einen aussagekräftigen Namen und einen Dokumentationskommentar geben, statt eine mehrstufige Berechnung in einem Inline-Block zu verstecken. - Eine einzige Quelle der Wahrheit bewahren und toten Code nach einem Refactoring löschen: eine gegebene Sache an genau einer Stelle abbilden oder klassifizieren, und wenn eine Änderung einen Zweig oder eine Hilfsfunktion unerreichbar macht, ihn entfernen, statt ihn zurückzulassen. - Pro Wert eine einzige kanonische Darstellung bewahren und die Invariante per Assertion absichern. Eine zweite Darstellung desselben logischen Werts bricht Gleichheit, Identität und Deduplizierung; an der Konstruktionsstelle eine Assertion setzen, wenn eine Invariante garantiert, dass eine Form für bestimmte Eingaben nie erzeugt wird. - Bei vertragswidriger Eingabe laut scheitern. Ist eine Hilfsfunktion nur für eine eingeschränkte Menge an Eingaben gültig, bei allem außerhalb dieser Menge eine Assertion auslösen, statt still einen Standardwert zurückzugeben. ## Den Pull Request in fünf Teilen schreiben und Regressionstests beilegen - Den Code formatieren und die Änderung standardmäßig als „non-breaking" kennzeichnen, oder als „breaking", wenn sie eine Binärschnittstelle oder das Sprachverhalten verändert. - Regressionstests einschließen, die vor der Korrektur fehlgeschlagen wären. - Die Beschreibung in einer festen fünfteiligen Struktur verfassen: Motivation (das Problem mit einem konkreten, anschaulichen Beispiel), vorgeschlagene Lösung (der Ansatz und warum er prinzipientreu ist), Änderungsübersicht (eine Liste der betroffenen Dateien oder Bereiche und was jede davon bewirkt), Konzepte und Begriffe (ein kurzes Glossar nur der codebasis-spezifischen oder subtilen Begriffe, auf die sich der Bericht stützt, nicht bekannte Grundlagen) und ein Prozessbericht. - Im Prozessbericht jede Änderung mit einem logischen Grund erklären. Bei einem sich fortsetzenden Problem den auslösenden Test und eine Code-Nachverfolgung nennen, die die genauen beteiligten Funktionen benennt, nicht nur eine Beschreibung. Bei jeder Änderung, die eine Eingabeform absichert oder als Sonderfall behandelt, angeben, ob diese Form gültige Eingabe ist oder ob stattdessen ihr Erzeuger hätte korrigiert werden sollen, damit ein Reviewer bestätigen kann, dass die Lösung auf der richtigen Ebene sitzt. - Für einen Reviewer schreiben, dem der eigene Kontext fehlt: mit einem konkreten Beispiel beginnen, den relevanten Codeausschnitt einfügen statt nur einen Typ oder eine Funktion zu nennen, und die Schritte der Reihe nach erklären. ## Enums und Schnittstellen nur am Ende erweitern, damit öffentliche API und ABI intakt bleiben - Jede Änderung an einem öffentlichen API-Header so behandeln, als müsse sie Binär- und Quellkompatibilität für Aufrufer bewahren, die bereits gegen eine ältere Version kompiliert wurden. - Enums: nie einen neuen Enumerator mitten in ein bestehendes Enum einfügen, da das Einfügen jeden späteren Wert verschiebt und Aufrufer, die ihn gespeichert oder verglichen haben, stillschweigend bricht. Neue Enumeratoren unmittelbar vor dem abschließenden Zähler- oder Sentinel-Element mit einem expliziten, fortlaufenden Wert anhängen, und um einen zu entfernen, ihn in eine Markierung für „entfernt" umbenennen, dabei aber seinen ursprünglichen Integer-Wert behalten und einen ausgemusterten Wert nie wiederverwenden. - Virtuelle Methodentabellen in COM-artigen Schnittstellen haben ein durch die Deklarationsreihenfolge festgelegtes Layout: virtuelle Methoden nie umordnen, nie die Signatur oder Aufrufkonvention einer Methode ändern, nie eine neue Methode in der Mitte einfügen (am Ende vor der schließenden Klammer anhängen), und nie eine Methode entfernen: ihren Rumpf durch einen „nicht implementiert"-Stub ersetzen und die Deklaration beibehalten. - Vermeiden, eine bestehende öffentliche Schnittstelle direkt zu erweitern, wenn Clients sie implementieren oder per Bezeichner abfragen könnten; stattdessen eine neue abgeleitete oder versionierte Schnittstelle mit eigenem Bezeichner hinzufügen und dabei die ursprüngliche für bestehende Aufrufer unterstützt lassen.