Alle Beiträge

Wenn das Lesen von KI-Code kein Review ist

Artikel anhören (experimentell) · 11:20

00:00 / 11:20

Ein dichtes Feld aus Codezeilen strömt ins Dunkel vorbei, mit einem hellen Kontrollpunkt, an dem wenige Zeilen ins Licht gehoben und gelesen werden, während der Rest ungelesen vorbeifließt

Ein Entwickler, den ich mir deutlich vorstellen kann, weil es eine Version von ihm gerade überall gibt, hat einen Arbeitsablauf, der verantwortungsvoll aussieht. Er lässt das Modell den Code schreiben und liest den Diff, bevor er ihn annimmt. Er macht ein Review. Nur werden die Diffs immer länger, das Lesen immer schneller, und eines Nachmittags merkt er, dass er Code angenommen hat, den er jetzt nicht mehr ändern könnte, wenn er müsste. Die Projekte, die die Maschine größtenteils gebaut hat, sind die, in denen er sich kaum zurechtfindet. Ein Projekt, das er vor drei Jahren von Hand geschrieben hat, fühlt sich noch immer an wie sein eigenes Haus. Irgendwo dazwischen hörte Lesen auf, Review zu sein, und nichts hat es angekündigt.

Manchmal ist Lesen die ganze Arbeit

Hier ist, was die starke Version dieser Warnung falsch macht. Für einen großen Teil dessen, was du an einem Tag annimmst, ist das Lesen des Diffs die gesamte Arbeit, und es selbst laufen zu lassen wäre reines Theater. Ein Rename, das der Compiler bereits abgesichert hat. Ein Dependency-Bump. Ein Config-Wert, eine Textänderung, ein Kommentar. Boilerplate, den du hundertmal geschrieben hast und an seiner Form erkennst. Ein Block generierter Tests, bei dem Lesen ohnehin der wertvollere Durchgang ist, weil Lesen genau den Test erwischt, der nichts prüft und trotzdem grün wird.

Sorgfältiges Lesen ist kein schwaches Instrument. Lange vor all dem fand Code-Inspektion mehr Defekte pro Stunde als Testen, und das tut sie noch immer. Der Punkt ist also nicht "lass den Code immer laufen". Der Punkt ist, dass Lesen und Ausführen für unterschiedliche Bugs blind sind, und der meiste Streit, den Leute führen, ist in Wahrheit ein Streit darüber, in welcher Blindheit sie gerade leben.

Der wahre Test ist, ob du es vorhersagen kannst

Die Frage war nie Lesen gegen Ausführen. Sie lautet, ob du vorhersagen kannst, was der Code tut, ohne ihn auszuführen.

Bei der einen Kategorie von Änderung kannst du es, und Lesen genügt. Bei einer anderen steckt die Korrektheit nicht in dem Text vor dir. Sie lebt zwischen den Zeilen, in der Verschränkung zweier Threads, die der Diff dir immer nur einzeln zeigen kann. Sie lebt unter Last, in der Query, die bei zehn Zeilen in Ordnung und bei zehn Millionen tödlich ist. Sie lebt an der Grenze, im Off-by-one und im Fehlerpfad, an dem der Normalfall glatt vorbeigeht. Sie lebt über die Leitung hinweg, in der Frage, ob das neue Stück überhaupt mit dem verdrahtet ist, das es aufruft. Sie lebt im Negativraum, in der Autorisierungsprüfung, die nicht da ist, und der Exception, die still verschluckt wird, also in einer Zeile, die nie im Diff auftaucht, weil sie nie geschrieben wurde. Du kannst dich nicht zu einem Bug lesen, der nur zur Laufzeit existiert.

KI lässt "sieht richtig aus" weniger bedeuten

KI verschärft das auf eine ganz bestimmte Weise. Wer Code freigibt, tut es anhand von Signalen, die für Korrektheit einstehen. Er liest sich idiomatisch. Er wirkt selbstsicher. Er ähnelt Code, der früher funktioniert hat. Ein Sprachmodell ist eine Maschine, die genau darauf getrimmt ist, diese Signale zu erzeugen. Es ist sehr gut darin, richtig auszusehen.

So war die älteste Abkürzung im Review, "das sieht in Ordnung aus", noch nie ein schwächerer Beleg dafür, dass die Sache in Ordnung ist. Die Politur ist keine Beruhigung. Sie ist das Risiko, weil sie genau auf den Teil von dir zielt, der die Änderung ohnehin durchwinken wollte. Es gibt Forschung, die zeigt, dass Leute mit einem fähigen Assistenten weniger sicheren Code schrieben und sich sicherer waren, dass er sicher sei. Die Zuversicht bewegt sich in die entgegengesetzte Richtung zur Gefahr, und sauberer generierter Code ist die entwaffnendste Eingabe, die man einem Reviewer reichen kann.

Die Maschine kann ihn auch laufen lassen

Den Code laufen zu lassen ist inzwischen nicht mehr nur etwas, das du von Hand tust. Der Agent kann ihn für dich laufen lassen und dir die Logs zurückgeben, einen Screenshot, einen Absatz, der erklärt, was er geändert hat und warum er sich sicher ist. Das ist echt, und für viele Änderungen ist es das Ganze: die Tests wurden grün, während du zusahst, der Screenshot zeigt den Button da, wo er hingehört, und du bist früher fertig, als du es allein gewesen wärst.

Aber sieh dir an, was da passiert ist. Dieser Beleg wurde von derselben Art System erzeugt, die den Code geschrieben hat, ausgerichtet auf dasselbe Ziel, nämlich richtig auszusehen. Ein grünes Log, dessen Lauf du nicht selbst gesehen hast, ist eine Behauptung, keine Tatsache. Du kannst den Agenten sogar auf die Logs ansetzen, die Datenbank, die ausgehenden Aufrufe, und ihn fragen, ob alles zusammenpasste, und das ist ein echter Hebel. Es ist auch die Stelle, an der ich ein Modell dabei ertappt habe, wie es nicht wirklich hinsah und mit dem antwortete, was es zu finden erwartete. Den Lauf an den Agenten zu übergeben, macht das Review also nicht überflüssig. Es verschiebt es eine Ebene nach oben, vom Diff zum Beleg. Wenn er dir sagt, die Logs zeigen X, dann öffne die Logs und finde X. Behandle "es hat funktioniert, hier ist der Beweis" als eine Behauptung, die es zu bestätigen gilt, genauso wie du eine Zahl prüfst, bevor du sie im Absatz eines Modells stehen lässt. Es ist dieselbe Prüfung, die ein Forscher auslässt, wenn ein erfundenes Zitat in einer veröffentlichten Arbeit landet.

Und das führt zu dem Teil, der fast schon ironisch ist. Bei vielen kleinen Änderungen ist das Schnellste und Verlässlichste, was du tun kannst, es selbst laufen zu lassen. Eine Wand aus Logs und eine selbstsichere Zusammenfassung zu lesen kann dich mehr kosten, als die Sache zu öffnen und mit eigenen Augen hinzusehen. Es laufen zu lassen ist nicht die langsame, pflichtbewusste Option, auf die du zurückfällst, wenn das Lesen versagt. Die Hälfte der Zeit ist es die schnelle.

Das Versagen, das die meisten trifft, ist die Menge

Das Versagen, in das die meisten tatsächlich geraten, ist ganz und gar nicht subtil. Es ist die Menge.

Jenseits von ein paar hundert Zeilen verfällt jedes menschliche Review zum Überfliegen, und ein Agent gibt dir ein paar hundert Zeilen, sooft du fragst. Reine Review-Arbeit ist die ermüdendste Form, in der dieser Beruf daherkommt, denn du rekonstruierst die Absicht eines anderen, in einem Stil, der nicht deiner ist, in einem Tempo, das du nicht gesetzt hast. Also kippt sie, leise, ins Annehmen. Niemand entscheidet, mit dem Reviewen aufzuhören. Du fängst an, den Code zu sehen, statt ihn zu verstehen, und von innen fühlen sich die beiden gleich an. Die Grenze, auf die es ankommt, ist nicht, wie viel das Modell schreiben kann. Sie ist, wie viel du tatsächlich halten kannst. Lass es darüber hinaus schreiben, und du machst kein Review, was auch immer der Bildschirm behauptet, dass du tust.

Passe die Tiefe an die Tragweite an

Das macht aus der Disziplin etwas Nützlicheres als "lies gründlicher". Passe die Tiefe des Reviews an die Tragweite an und daran, wie umkehrbar die Änderung ist.

Eine kleine, abgegrenzte Änderung, die sich leicht zurückrollen lässt, verdient ein schnelles Lesen und einen Merge. Eine Migration, eine öffentliche Schnittstelle, eine Löschung, alles, was Geld oder Zugriff berührt, verdient die langsame Art von Review: es laufen lassen, es nachverfolgen, versuchen, es kaputtzumachen, ob du das selbst tust oder den Agenten seine Arbeit zeigen lässt und dann prüfst, was er tatsächlich getan hat, egal wer oder was es geschrieben hat. Lesen genügt genau dann, wenn die Weisen, auf die die Änderung scheitern kann, die sind, die deine anderen Gates ohnehin abfangen: der Compiler, die Typen, die Tests, die in der CI laufen, ob du sie von Hand ausführst oder nicht. Es genügt nicht mehr in dem Moment, in dem das Versagen, das dir wirklich wehtäte, eines ist, das nur das Ausführen offenbaren würde. Auf jeden Diff dieselbe Sorgfalt zu verwenden ist keine Gründlichkeit. Es ist die Weigerung zu entscheiden, wo das Risiko liegt.

Was du weggibst

Unter den technischen Kosten liegt eine leisere. Code anzunehmen, den du nicht verstanden hast, verschiebt dich, Klick für Klick, vom Autor deines Produkts zu seinem Konsumenten. Du behältst den Ruhm und du behältst die Verantwortung, der Pager klingelt weiterhin für dich, aber du hast das Verständnis weggegeben, auf dem beide ruhen. Leute beschreiben, wie sie an einem Nachmittag eine App bauen und sich seltsam leer fühlen, und dann zwei Wochen damit verbringen, sie wirklich zum Laufen zu bringen, und sich wieder wie sie selbst fühlen. Das ist keine Nostalgie. Das ist der Unterschied zwischen dem Besitz der Sache, die deinen Namen trägt, und dem Halten einer Kopie, die du nicht öffnen kannst.

Ich baue Werkzeuge, die ganz auf Generierung setzen. Eines macht aus einem Satz einen Plan und öffnet seine eigenen Pull Requests. Ein anderes steuert ganze Flotten von Agenten. Ich bin nicht hier, um irgendjemandem zu sagen, weniger KI-Code zu schreiben. Aber jedes dieser Werkzeuge setzt am Ende einen Menschen voraus, der in der Tiefe reviewt, die die Änderung verdient, und ein Gate, durch das du nur hindurchblickst, ist kein Gate. Die Maschine kann den Diff erzeugen. Den Teil, in dem du ihn verstanden hast, kann sie nicht erzeugen.

Also prüfe per Augenschein, so viel du willst, bei den Änderungen, bei denen der Augenschein alles ist. Zieh nur eine klare Linie zwischen dem Diff, den du verstanden hast, und dem Diff, den du nur gesehen hast. Der erste gehört dir. Der zweite wird deiner in dem Moment, in dem er bricht, und es wird ihn nicht kümmern, dass er in Ordnung aussah.

Fragen, die dieser Artikel beantwortet

  • Reicht es, den Diff zu lesen, um KI-Code zu reviewen?

    Für einen großen Teil dessen, was du an einem Tag annimmst, ja, und es selbst laufen zu lassen wäre reines Theater: ein Rename, das der Compiler bereits abgesichert hat, ein Dependency-Bump, ein Config-Wert, eine Textänderung, Boilerplate, den du an seiner Form erkennst. Generierte Tests sind der klarste Fall, denn Lesen erwischt den Test, der nichts prüft und trotzdem grün wird. Es reicht nicht mehr, sobald die Korrektheit nicht in dem Text vor dir steckt: die Verschränkung zweier Threads, die Query, die bei zehn Zeilen in Ordnung und bei zehn Millionen tödlich ist, der Fehlerpfad, an dem der Normalfall glatt vorbeigeht, oder die Autorisierungsprüfung, die nie im Diff auftaucht, weil sie nie geschrieben wurde.

  • Der Agent hat die Tests laufen lassen und liefert grüne Logs und einen Screenshot, warum soll ich noch weiter prüfen?

    Weil dieser Beleg von derselben Art System erzeugt wurde, die den Code geschrieben hat, ausgerichtet auf dasselbe Ziel, nämlich richtig auszusehen. Ein grünes Log, dessen Lauf du nicht selbst gesehen hast, ist eine Behauptung, keine Tatsache. Den Lauf an den Agenten zu übergeben macht das Review nicht überflüssig, es verschiebt es eine Ebene nach oben, vom Diff zum Beleg: wenn er dir sagt, die Logs zeigen etwas, dann öffne die Logs und finde es. Ich habe ein Modell dabei ertappt, wie es nicht wirklich hinsah und mit dem antwortete, was es zu finden erwartete, und das ist dieselbe Prüfung, die ein Forscher auslässt, wenn ein erfundenes Zitat in einer veröffentlichten Arbeit landet.

  • Wie viel KI-Code kann ich tatsächlich reviewen, bevor es kein Review mehr ist?

    Weniger, als ein Agent dir geben wird, denn er liefert ein paar hundert Zeilen, sooft du fragst, und jenseits davon verfällt jedes menschliche Review zum Überfliegen. Reine Review-Arbeit ist die ermüdendste Form, in der dieser Beruf daherkommt, weil du die Absicht eines anderen rekonstruierst, in einem Stil, der nicht deiner ist, in einem Tempo, das du nicht gesetzt hast. Niemand entscheidet, mit dem Reviewen aufzuhören. Du fängst an, den Code zu sehen, statt ihn zu verstehen, und von innen fühlen sich die beiden gleich an.

  • Ist es nicht sicherer, jede Änderung in voller Tiefe zu reviewen?

    Nein. Auf jeden Diff dieselbe Sorgfalt zu verwenden ist keine Gründlichkeit, sondern die Weigerung zu entscheiden, wo das Risiko liegt. Passe die Tiefe an die Tragweite an und daran, wie umkehrbar die Änderung ist: eine kleine, abgegrenzte Änderung, die sich leicht zurückrollen lässt, verdient ein schnelles Lesen und einen Merge, während eine Migration, eine öffentliche Schnittstelle, eine Löschung oder alles, was Geld oder Zugriff berührt, die langsame Art verdient, bei der du sie laufen lässt, nachverfolgst und zu brechen versuchst. Es selbst laufen zu lassen ist auch nicht der pflichtbewusste Notnagel, denn die Sache zu öffnen und hinzusehen ist oft schneller, als eine Wand aus Logs und eine selbstsichere Zusammenfassung zu lesen.