


Systemcode von der KI sorgfältig schreiben lassen, bis zu Typen und Fehlerbehandlung
Für KI-Hilfe an einer strengen Systemcodebasis. Die KI prüft ihren Code mindestens dreimal so lange, wie das Schreiben dauerte, deckt alle Fehlerfälle ab, legt Regeln in Typen fest, hält alte und neue Formate kompatibel und macht kleine Commits, die alle bauen.
Diese KI-Regeln verwenden
## Von der KI geschriebenen Code mindestens dreimal so lange prüfen, wie das Schreiben dauerte
Behandle die Assistenz als Qualitätsverstärker, nicht als Geschwindigkeitsverstärker. Investiere die gewonnene Zeit in eine Sorgfalt, die Arbeit ohne Hilfe nicht erreichen würde.
- Strebe Spitzenqualität an: decke mehr Randfälle ab, überarbeite den Code zugunsten der Verständlichkeit, räume die offenen TODO-Markierungen ab, erledige die mühsamen Dinge und ziele auf null Fehler.
- Prüfe weit länger, als du erzeugst. Verbringe mindestens dreimal so viel Zeit mit dem Prüfen erzeugten Codes, wie seine Erstellung gedauert hat, wäge jede Zeile und jede Entwurfsentscheidung ab und suche aktiv nach Wegen, den Code zu brechen.
- Die Autorin oder der Autor trägt die Verantwortung für das Ergebnis. Gib einen ersten Entwurf nicht als fertige Arbeit weiter, es sei denn, du bittest ausdrücklich nur um Rückmeldung zu einer Richtung.
- Arbeit, die Anzeichen mangelnder Sorgfalt trägt, etwa fehlende Fälle, die eine aufmerksame menschliche Prüfung erkannt hätte, darf rundheraus abgelehnt statt überarbeitet werden.
- Nenne diesen Maßstab zu Beginn jedes Gesprächs über Codeänderungen und noch einmal, bevor ein Pull Request eröffnet wird, damit Autorenseite und Prüfseite dieselbe Messlatte teilen.
## Korrektheit in die Typen verlagern und alle Randfälle abdecken
Bevorzuge die korrekte Umsetzung gegenüber der bequemen und verlagere so viel Korrektheit wie möglich in das Typsystem, damit Fehler bereits beim Übersetzen auffallen und nicht erst im Produktivbetrieb.
- Modelliere den gesamten Fehlerraum. Keine Abkürzungen und keine vereinfachte Fehlerbehandlung.
- Behandle alle Randfälle, einschließlich Wettlaufsituationen, Signalzeitpunkten und Verhaltensunterschieden zwischen Betriebssystemen.
- Verankere Korrektheitsvorgaben in Typen: Hüllentypen für Domänenwerte, Erbauer-Muster für komplexe Konstruktion, Typzustände in generischen Typen, wenn Zustandsübergänge eine Rolle spielen, und Ausleihen statt Klonen.
- Bevorzuge Garantien zur Übersetzungszeit gegenüber Prüfungen zur Laufzeit, wo immer die Wahl besteht.
- Halte die Sichtbarkeit so eng wie möglich. Erweitere die Sichtbarkeit eines Elements nur dann, wenn etwas außerhalb seines Moduls es wirklich benötigt.
- Kennzeichne öffentliche Typen in stabilen, extern genutzten Paketen als erweiterbar, um Vorwärtskompatibilität zu wahren. Interne Pakete, die keine Stabilitätszusage machen, brauchen das nicht, auch wenn es bei Fehlertypen dennoch sinnvoll sein kann.
- Verwende Nachrichtenaustausch oder das Aktorenmodell statt gemeinsam veränderbaren Zustands, damit Datenwettläufe gar nicht erst entstehen können.
- Die Details richtig hinzubekommen ist wichtiger, als schnell fertig zu werden.
## Hilfreiche Fehlermeldungen geben und auf jedem Betriebssystem gleich reagieren
Das für Nutzende sichtbare Verhalten ist eine Entwurfsvorgabe und kein abschließender Feinschliff.
- Erzeuge strukturierte, hilfreiche Diagnosemeldungen statt nackter Fehlerzeichenketten und setze überall dort, wo die Meldung einen Menschen erreicht, eine leistungsfähige Diagnosebibliothek ein.
- Gestalte die Fortschrittsanzeige reaktionsschnell und aussagekräftig.
- Halte das Verhalten über alle Plattformen hinweg einheitlich, selbst wenn die zugrunde liegenden Fähigkeiten sich unterscheiden, und setze jede Plattform mit ihren eigenen nativen Mechanismen um, statt eine Plattform auf einer anderen nachzubilden.
- Schreibe Meldungen für Nutzende in klarer Gegenwartsform: "the tool now supports", nicht "the tool now supported".
- Bündle die Pluralbildung in einem gemeinsamen Hilfsmodul, dessen Funktionen zu einer Anzahl die Einzahl- oder Mehrzahlform zurückgeben. Ergänze neue Hilfsfunktionen dort, statt die Logik der Pluralbildung an den Aufrufstellen einzustreuen.
## Das Design schrittweise wachsen lassen und die Gründe festhalten
Halte den Entwurf konkret und zusammensetzbar statt abstrakt und übermäßig allgemein.
- Entwickle den Entwurf schrittweise weiter, statt vorab eine perfekte Architektur anzustreben.
- Halte Entwurfsentscheidungen und ihre Abwägungen in Entwurfsdokumenten fest, die beim Projekt liegen, damit spätere Lesende erkennen können, warum eine bestimmte Form gewählt wurde.
- Erkunde und iteriere bei Unsicherheit, statt eine Vermutung in einer Abstraktion festzuschreiben.
## Nutzerfehler von internen Fehlern trennen und genauen Kontext mitgeben
Fehler sind Teil dessen, was Nutzende zu sehen bekommen, und verdienen dieselbe entwerferische Aufmerksamkeit wie der erfolgreiche Pfad.
- Verwende per Ableitung erzeugte Fehlertypen statt zeichenkettenbasierter Fehler und fasse verwandte Fehler unter einer Aufzählung von Fehlerarten zusammen, sobald die Menge wächst.
- Führe reichhaltigen, strukturierten Kontext an den Fehlern mit, statt die Einzelheiten zu einer einzigen Meldung einzuebnen.
- Verwende ein zweistufiges Modell. Von Nutzenden verursachte und externe Fehler bilden einen eigenen Typ, der auf bedeutungstragende Beendigungscodes abgebildet wird; interne Programmierfehler sind davon getrennt und dürfen das Programm mit einer Panik abbrechen.
- Schreibe den angezeigten Fehlertext als kleingeschriebenes Satzfragment, das sich richtig liest, wenn es in eine umschließende Wendung wie "failed to ..." eingebettet wird.
## Bei unbekannten Konfigurationsschlüsseln warnen und Konfigurationsfehler klar melden
Entscheidungen bei der Deserialisierung bestimmen die Qualität jeder Konfigurationsfehlermeldung, die Nutzende je zu sehen bekommen.
- Verfolge und melde ignorierte oder unbekannte Pfade in der Konfiguration, damit ein Tippfehler als Warnung sichtbar wird, statt stillschweigend wirkungslos zu bleiben.
- Ebne verschachtelte Strukturen bei der Deserialisierung niemals ein. Die dafür nötige interne Pufferung verschlechtert die Warnungen zu unbekannten Feldern; kopiere die Felder stattdessen ausdrücklich in die Struktur.
- Verlasse dich niemals auf die Deserialisierung von Varianten ohne Kennzeichnung, denn sie erzeugt schlechte Fehlermeldungen. Schreibe stattdessen einen eigenen Besucher mit einer präzisen Erwartungszeichenkette.
## Vor einer Formatänderung prüfen, ob alte und neue Versionen die Daten der anderen lesen können
Jede Änderung an einer Struktur, die auf die Festplatte geschrieben oder über die Leitung gesendet wird, muss vor dem Einfließen durch die vollständige Kompatibilitätsmatrix verfolgt werden.
- Alter Leser mit neuen Daten: kann er sie noch deserialisieren, und gehen dabei Informationen verloren?
- Neuer Leser mit alten Daten: erzeugen die deklarierten Standardwerte korrekte und nicht bloß gültige Werte?
- Alter Schreiber mit neuen Daten: kann er sie verlustfrei zurückschreiben, ohne Felder fallen zu lassen? Das ist der am häufigsten übersehene Fall.
- Erhöhe die Formatversion vorausschauend. Wenn ein neues Feld bedeutungsmäßig wichtig sein wird, erhöhe die Version bereits beim Hinzufügen des Feldes und nicht erst dann, wenn zum ersten Mal ein vom Standard abweichender Wert geschrieben wird; sonst beschädigen ältere Programmstände die Daten stillschweigend, sobald sie sie zurückschreiben.
- Betrachte Standardwerte als notwendig, aber nicht hinreichend. Sie erlauben es einem alten Leser, neue Daten zu lesen, aber ein alter Schreiber lässt die ihm unbekannten Felder weiterhin fallen.
## Async nur wo nötig einsetzen und Arbeit nicht unbegrenzt starten
Asynchronität ist ein Werkzeug für Ein- und Ausgabe sowie für Nebenläufigkeit, kein voreingestellter Stil.
- Verwende eine mehrfädige asynchrone Laufzeitumgebung, beschränke Asynchronität aber auf den Code, der sie braucht, und halte alles andere synchron.
- Verwende strukturierte Nebenläufigkeit, damit nebenläufiger Arbeit keine künstlich lange Lebensdauer gegeben werden muss.
- Plane in die Warteschlange gestellte Arbeit mit Blick auf den Gegendruck ein, statt unbegrenzt neue Aufgaben zu starten.
- Stelle anhaltbare Zeitgeber-Grundbausteine dort bereit, wo die Auftragssteuerung den Prozess anhalten und fortsetzen kann, damit die angehaltene Zeit nicht auf Zeitüberschreitungen angerechnet wird.
## Keine Logik in Sammelmodule legen und Importe ganz oben schreiben
Modulgrenzen werden durch Struktur und Sichtbarkeit durchgesetzt, nicht allein durch Übereinkunft.
- Verwende sammelnde Moduldateien ausschließlich dazu, öffentliche Elemente erneut zu exportieren. Nicht triviale Logik gehört in ein Untermodul der Umsetzung oder in ein spezifischeres Modul.
- Halte plattformabhängigen Code in getrennten Dateien je Plattform hinter bedingter Kompilierung, mit einer gemeinsamen Schnittstelle zwischen ihnen.
- Halte Testhilfen in eigenen Modulen oder Dateien.
- Importiere Typen und Funktionen ganz oben im Modul. Importiere niemals innerhalb eines Funktionsrumpfes; die einzige Ausnahme ist eine bedingt übersetzte Funktion, die einen ebenso bedingten Import benötigt.
- Bevorzuge es, den Typ selbst zu importieren, und importiere nur dort stattdessen ein Modul, wo das die übliche Schreibweise ist. Vermeide voll qualifizierte Pfade in Signaturen: importiere das übergeordnete Modul und qualifiziere nur mit diesem.
- Aufzählungsvarianten für den Musterabgleich zu importieren ist in Ordnung.
## Daten ausleihen oder teilen statt kopieren
Wähle die Darstellung, die Arbeit vermeidet, statt nachträglich zu optimieren.
- Teile unveränderliche Daten über Referenzzählung oder Ausleihen, statt sie zu kopieren.
- Verwende einen für kurze Zeichenketten optimierten Typ, wo der Code viele kurze Zeichenketten verarbeitet.
- Achte auf Klonen und Referenzieren. Vermeide Klonen dort, wo die Daten eine natürliche Baum- oder Besitzstruktur haben, die das Ausleihen bereits ausdrückt.
- Lass Daten nach Möglichkeit über Iteratoren strömen, statt sie vollständig im Speicher zu puffern.
## Vor jedem Commit formatieren und Abhängigkeitsversionen an einer Stelle verwalten
Mechanische Einheitlichkeit ist billig durchzusetzen und teuer nachzurüsten.
- Beginne jede Quelldatei mit der Copyright-Zeile und der Lizenzkennung des Projekts.
- Lege die Sprachedition fest und führe vor jedem Commit den Formatierungsbefehl des Projekts aus. Die Formatierung wird in der kontinuierlichen Integration erzwungen, ein unformatierter Commit scheitert dort also.
- Unterdrücke eine Lint-Regel mit der erwartenden statt mit der erlaubenden Form, damit die Unterdrückung selbst eine Warnung auslöst, sobald die Regel nicht mehr greift, und tote Unterdrückungen sich nicht ansammeln.
- Wo eine Lint-Regel das Anhängen formatierten Textes an eine Zeichenkette verbietet, verwende das Schreibmakro des Projekts, statt eine formatierte Zeichenkette anzuhängen.
- Verwalte alle Versionen von Abhängigkeiten zentral in der Wurzel des Arbeitsbereichs und lege interne Pakete auf genaue Versionen fest.
- Kommentiere nicht offensichtliche Entscheidungen zu Abhängigkeiten und Funktionsschaltern, besonders dort, wo ein Funktionsschalter aus Sicherheits- oder Größengründen abgeschaltet ist.
## In Kommentaren erklären, warum etwas so ist, nicht nur was
- Kommentare erklären das Warum, nicht bloß das Was.
- Erzähle nicht innerhalb von Funktionsrümpfen mit. Setze einen Kommentar nur dort, wo etwas nicht offensichtlich oder besonders ist oder wo die Absicht eine tiefergehende Erklärung braucht.
- Die Dokumentation auf Modulebene nennt Zweck und Zuständigkeiten des Moduls.
- Beende Codekommentare immer mit einem Punkt.
- Verwende in Überschriften und Titeln niemals die durchgängige Großschreibung der Wörter; schreibe sie immer wie einen Satz.
- Verwende immer das Oxford-Komma.
- Lass keine Artikel weg. Schreibe "the file has a newer version", nicht "file has newer version".
## Tests mit dem Test-Runner des Projekts ausführen und vorhandene Fixtures nutzen
Teste so, wie das Projekt seine Tests ausführt, und nutze das bereits vorhandene Testmodell weiter.
- Verwende für Einheits- und Integrationstests das im Projekt vorgesehene Testwerkzeug. Weiche nicht auf das Standardwerkzeug der Sprache aus, wenn die Testsammlung vom Ausführungsmodell des vorgesehenen Werkzeugs abhängt.
- Verwende eine lokal gebaute Kopie des Werkzeugs nur dann, wenn Änderungen am Verhalten des Werkzeugs selbst überprüft werden, etwa ein neuer Schalter oder ein neues Ausgabeformat. Führe die Testsammlung selbst mit dem Standardwerkzeug aus, da Integrationstests ihre eigenen inneren Prozesse aus den erzeugten Artefakten starten.
- Führe Dokumentationstests mit dem Befehl aus, der sie unterstützt, wenn das Hauptwerkzeug das nicht tut.
- Halte Einheitstests neben dem Code, den sie testen, Integrationstests in einem eigenen Paket, gemeinsame Testvorrichtungen in einem Paket für Testvorrichtungen und Testhilfsmittel in einem Unterstützungspaket.
- Bevorzuge das Modell erwarteter Ergebnisse aus dem Paket für Testvorrichtungen gegenüber handgeschriebenen Stichprobenprüfungen.
- Greife zu den bereits vorhandenen Werkzeugen: parametrisierte Testfälle, eigenschaftsbasiertes Testen, Schnappschuss-Zusicherungen, eigene Testgerüste und verbesserte Ausgaben von Zusicherungen. Prüfe, was verfügbar ist, bevor du einen neuen Mechanismus hinzufügst.
## Commit-Betreffe mit dem Bereich beginnen und jeden Commit baubar halten
Die Historie ist ein prüfbares Erzeugnis, behandle deshalb jeden Commit als eine Einheit der Prüfung.
- Stelle jeder Betreffzeile eines Commits den berührten Bereich in Klammern voran, gefolgt von einer kurzen Beschreibung. Verwende einen Meta-Bereich für übergreifende Arbeiten wie Mindestversionen der Werkzeugkette, Freigaben und Änderungen an der kontinuierlichen Integration.
- Commits zur Versionserhöhung und zur Vorbereitung einer Freigabe folgen ihren eigenen festen Betreffzeilen und werden normalerweise vom Freigabewerkzeug erzeugt.
- Schreibe die Nachrichtentexte im einfachen Präteritum und im Präsens: was früher geschah, was jetzt geschieht und welche Tests ergänzt wurden.
- Commit-Nachrichten sind Markdown. Verwende in der Betreffzeile keine Backticks, im Nachrichtentext dagegen schon.
- Halte Commits atomar. Jeder Commit ist eine logische Änderungseinheit.
- Halte die Historie bisektionsfähig. Jeder Commit muss bauen und alle Prüfungen bestehen.
- Trenne die Anliegen. Formatierungskorrekturen und Umbauten gehören in eigene Commits, getrennt von Verhaltensänderungen.
## Einen nebenläufigen Runner in Dispatcher und Executor teilen, die über Nachrichten reden
Über einen nebenläufigen Ausführer lässt sich am leichtesten nachdenken, wenn eine Komponente die Außenwelt besitzt und eine andere die Ausführung.
- Ein Verteiler steht mit der Außenwelt in Verbindung, bringt die Ereignisse aller Quellen in eine Reihenfolge, multiplext diese Quellen über ein Auswahlkonstrukt und hält den maßgeblichen Zustand darüber, was gerade läuft. Synchronisationspunkte halten die Berichterstattung frei von Wettlaufsituationen.
- Ein Ausführer plant und betreibt Arbeitseinheiten als asynchrone Zustandsmaschinen und kümmert sich um Wiederholungen, Zeitüberschreitungen, das Starten von Prozessen und das Erfassen der Ausgabe. Jede Einheit ist die maßgebliche Quelle für ihren eigenen Zustand und besitzt einen eigenen Kanal zum Verteiler statt eines Rundrufs.
- Teile keinen Zustand direkt; alles wandert als Nachricht zwischen den Komponenten.
- Modelliere den gesamten Fehlerraum und verwende anhaltbare Zeitgeber, damit Signale zum Anhalten und Fortsetzen die Zeitmessung nicht verfälschen.
- Verwende die nativen Möglichkeiten jeder Plattform zur Prozesseinschließung und zur Signalbehandlung, sichere sie mit bedingter Kompilierung und plattformeigenen Modulen hinter einer gemeinsamen Schnittstelle ab und dokumentiere die Plattformunterschiede und Abwägungen in Kommentaren.
