- Code Reviews sind weniger ein Verfahren, um Bugs aufzuspüren oder Integrität zu garantieren, sondern eher ein Prozess, der später schwer wartbaren Code frühzeitig sichtbar macht
- Wenn Reviewer den Code lesen und ihn nur schwer verstehen können, ist die Wahrscheinlichkeit hoch, dass künftige Maintainer auf dasselbe Problem stoßen
- Änderungen sollten besser jetzt vorgenommen werden, solange die ursprünglichen Autorinnen und Autoren den Kontext noch im Kopf haben; die Erwartung, Bugs allein durch Code-Inspektion zuverlässig zu finden, ist unrealistisch
- Statt „findet Bugs“ ist „versucht, es zu verstehen, und markiert die Stellen, die ihr nicht versteht“ eine für Reviewer besser umsetzbare Aufgabe
- Ein gutes Review besteht nicht darin, alles perfekt zu beweisen, sondern beginnt damit, an unverständlichen Stellen Notizen zu hinterlassen und Verbesserungen einzufordern
Den Fokus von Code Reviews ändern
- Der zentrale Zweck eines Code Reviews ist nicht, dass Reviewer Bugs finden, und auch nicht, zu garantieren, dass der Code bugfrei ist
- Die Erwartung, durch bloßes Durchsehen von Code generell Bugs finden zu können, ist in der Praxis schwach
- Deshalb liegt der Schwerpunkt des Reviews weniger auf „ist der Code korrekt?“ als auf „können andere ihn später lesen und ändern?“
Schwer verständlicher Code ist ein Warnsignal für die Wartbarkeit
- Reviewer lesen Code mit dem Ziel zu verstehen, was er tut und wie er funktioniert
- Unverständliche Stellen werden zu einem Warnsignal dafür, dass künftige Maintainer dort hängen bleiben könnten
- Solchen Code sollte man besser sofort korrigieren, solange die ursprünglichen Autorinnen und Autoren den Kontext noch im Gedächtnis haben
Eine praktikablere Review-Aufgabe als Bugsuche
- Die Aufforderung „finde Bugs in diesem Code“ ist eine Aufgabe, deren Erfolg schwer zu beurteilen ist
- Selbst wenn man einige Bugs findet, könnten weitere versteckte Bugs übersehen worden sein; aus Sicht der Reviewer wird oft nur ein Scheitern klar sichtbar
- Umgekehrt hat die Aufforderung „versuche, diesen Code zu verstehen, und weise darauf hin, wenn du etwas nicht verstehen kannst“ klarere Kriterien
- Es ist nicht nötig, alles perfekt zu verstehen
- Es reicht, die nicht verstandenen Stellen festzuhalten
- Wer versucht, das Ganze zu verstehen, und an den Stellen, an denen er hängen bleibt, Notizen hinterlässt, erfüllt die Rolle des Reviews
Review-Kriterien in der Praxis
- Code, den Reviewer nicht verstehen, kann schon für sich genommen ein Änderungskandidat sein
- Review-Kommentare dienen nicht nur als Bugreports, sondern machen auch fehlende Erklärungen, strukturelle Probleme und schwer lesbare Abläufe sichtbar
- Nach diesem Maßstab ist es wichtiger, den Code in einen Zustand zu bringen, in dem spätere Teammitglieder ihn lesen und bearbeiten können, als seine Korrektheit zu beweisen
Bugfunde sind ein Nebeneffekt
- Das bedeutet nicht, dass Code Reviews gar keine Bugs finden
- Bugs können während eines Reviews entdeckt werden, aber als Methode, um alle oder die meisten Bugs zu finden, sollte man sie kaum erwarten
- Eine realistischere Erfolgsbedingung ist, die Verständlichkeit zu prüfen und schwer wartbare Stellen gemeinsam mit den ursprünglichen Autorinnen und Autoren sofort zu verbessern
1 Kommentare
Meinungen auf Hacker News
Code Reviews haben nicht nur einen einzigen Zweck. Schwer wartbaren Code zu finden, ist wichtig, aber es ist weder der einzige Zweck noch vielleicht der wichtigste.
Sie dienen als Sicherheitsmechanismus, der es erschwert, schädlichen Code zu mergen, selbst wenn Entwickler oder KI in eine seltsame Richtung laufen, und jemand, der dem Problem nicht zu nahe ist, kann bessere Ansätze oder übersehene Probleme erkennen.
Jemand, der andere Teile des Systems besser kennt, kann auch Interaktionsprobleme aufdecken, mindestens eine weitere Person wird mit dem Code vertraut, und sowohl Autor als auch Reviewer bekommen eine Lerngelegenheit.
Das ist besonders wichtig, wenn es Erfahrungsunterschiede gibt: Beim Mentoring neuer Mitarbeiter füge ich sie als Reviewer zu all meinen PRs hinzu, damit sie sehen, wie ich arbeite, und reviewe auch ihre PRs, um ihnen Orientierung zu geben. Manchmal lerne auch ich von ihnen.
Bugs zu finden gehört ebenfalls dazu, sollte aber nicht der Hauptmechanismus sein; besonders wichtig ist es bei Security- und Performance-Bugs, die sich mit automatisierten Tests nur schwer finden lassen.
Das gilt besonders bei Modularisierung und Zerlegung: Wenn man einen riesigen PR als Ganzes verstanden hat, entsteht im Kopf ein Modell, und man beginnt zu sehen, ob er wartbar sein wird, irgendwann zum absoluten Albtraum wird oder irgendwo dazwischen liegt.
Der vielleicht wichtigste Teil von Code Reviews ist meiner Meinung nach Wissenstransfer.
In unserem kleinen Team geben, außer in dringenden Fällen, alle Teammitglieder vor dem Merge ihre Zustimmung zu einem PR. Dadurch weiß das ganze Team grob, in welchem Zustand die Codebase gerade ist. So vermeiden wir Überraschungen wie „das gesamte System, von dem ich abhängig war, ist verschwunden“, wie ich sie früher in stärker siloisierten Unternehmen erlebt habe.
Außerdem ist es ein Ort, um Fragen zur Funktionsweise zu stellen und Verständnis aufzubauen. In einem gut funktionierenden Team sollte jeder Entwickler das Gesamtsystem bis zu einem gewissen Grad verstehen, einschließlich der Teile, die er nicht direkt anfasst.
Eine weitere wichtige Funktion ist die Überprüfung von Organisationswissen. Kürzlich habe ich eine Tabelle etwas geändert, und ein Kollege wies mich darauf hin, dass ein Microservice, den ich nicht berücksichtigt hatte, in diese Tabelle schreibt und dadurch kaputtgehen würde. Ich wusste weder, dass dieser Microservice existiert, noch dass er auf diese Tabelle zugreift, aber diese Überprüfung hat ein größeres Problem und eine Datenbereinigungssituation verhindert.
Am Ende sind alle mit ihrer eigenen Arbeit beschäftigt und wollen nicht die Person sein, die einen wichtigen PR blockiert, also klicken sie einfach auf Approve, während tatsächlich niemand den Code reviewed.
Bei Problemen wie „das gesamte System, von dem ich abhängig war, ist verschwunden“ halte ich automatisierte Tests für sehr wichtig, die das auch dann erkennen können, wenn die Person, die von diesem System abhängt, gerade nicht dabei ist.
Ich bin außerdem ein starker Befürworter von geteilter Verantwortung für alles. Es ist natürlich, dass Menschen bestimmte Teile der Codebase, besonders von ihnen erstellte Komponenten, faktisch besitzen, aber das führt zu Silos und einem niedrigen Bus-Faktor. Es sollte nicht dazu kommen, dass eine Person ein System besitzt und dieses System wiederum von einer einzelnen anderen Komponente abhängt.
Wenn es zu viel zu lesen gibt, kann niemand mehr folgen; deshalb delegiert man, schreibt Dokumentation und hält Übersichtssessions ab.
Ich habe Code Reviews immer am besten als das Tor verstanden, durch das Code vom Besitz des Autors in den Besitz des Teams oder Projekts übergeht.
Der Code, den ich reviewe, ist nicht mehr dein Code, sondern wird bald unser Code sein. Wartbarkeit ist dabei natürlich ein großer Faktor.
Unser Team hat angefangen, KI zu nutzen, also habe ich auf einen simplen Modus umgestellt: Ich hinterlasse keine Kommentare mehr und entscheide nur noch binär, ob „das völlig verrückt ist oder ob es durchgehen kann“.
Ich schone damit meine Zeit und meine mentale Gesundheit.
Dadurch werden Reviewer und Autor nur noch bequemer
Der Zweck von Code-Reviews ist vielschichtig. Geht es um schwer wartbaren Code, mögliche Bugs, darum, ob es einfacher und sauberer geht, ob es zum Code-Style des Projekts passt, ob auch andere den Code verstehen, ob Junior-Teammitglieder onboarded werden, ob Designentscheidungen einem Sanity Check unterzogen werden – all das gehört dazu.
Solche oberflächlichen Texte wirken meist eher wie eine Selbstrechtfertigung fauler Code-Reviewer.
Man sollte prüfen, ob die Funktionalität gemäß Issue oder PR-Beschreibung das Ziel erreicht, ob unnötiger Code wie übrig gebliebene Debug-Ausgaben oder private API-Keys vorhanden ist, und ob offensichtliche Mängel wie Memory Leaks, unbehandelte Edge Cases, Sicherheitslücken oder veraltete API-Aufrufe vorliegen.
Man sollte auch prüfen, ob sich der Code verständlicher machen lässt, ob Abstraktionen hinzugefügt oder entfernt werden sollten, ob Variablen- und Methodennamen besser sein könnten und ob mehr oder weniger funktionaler Stil sinnvoll wäre.
Ebenso sollte man prüfen, ob er mit der Codebase oder dem Style Guide konsistent ist, ob es offensichtliche Performance-Verbesserungen gibt – etwa ein Hash-Set statt einer Liste zu verwenden oder Lazy Evaluation anzuwenden – und ob die Tests ausreichend sind.
Der Behauptung, dass unverständlicher Code nicht durchgewunken werden sollte, stimme ich auch nicht vollständig zu. Manche Codes sind einfach wirklich schwer zu verstehen; das Ziel ist, sie funktional korrekt und zugleich so verständlich wie möglich zu machen.
Dieser Text ist aber eher bloß Bait und vergleichbar damit zu sagen: „Die Leute denken, beim Abendessen gehe es darum, Essen zu essen, aber eigentlich geht es nicht ums Essen, sondern darum, mit Familie und Freunden in Verbindung zu treten.“ Das ist eine bestimmte Art von schlampiger reduktionistischer Argumentation, die auf HN gut ankommt.
Ich hatte das Gefühl, dass Review und Debugging viel mehr Zeit kosten als das Schreiben und Produzieren von Code, und einfach „beten, dass es funktioniert“ geht nie gut aus.
Die Aussage „Durch Anschauen von Code findet man im Allgemeinen keine Bugs“ stimmt überhaupt nicht. Auf jeder Abstraktionsebene kann man durchaus welche finden, und so etwas nennt man Code Smell.
Nicht geschlossene File Descriptors, nicht awaited Coroutines, riesige try/catch-Blöcke, die Fehler nicht protokollieren und irgendeinen Wert zurückgeben, falsche Type Casts usw. gehören alle dazu.
Als allgemeines Prinzip sollte man Type Checker, Compiler und Runtime nicht bloß als Hürden betrachten, die man irgendwie passieren muss. Man sollte mit diesen Stufen zusammenarbeiten, anerkennen, dass sie wertvolle Werkzeuge sind, und nicht gegen sie arbeiten.
Man kann „im Allgemeinen“ sicher irgendwie so definieren, dass es technisch wahr wird, aber dann verliert die Aussage ziemlich ihren Sinn.
Wenn man weitreichende Aussagen über Code-Reviews treffen will, ist es wichtig zu definieren, welche Art von Code-Review gemeint ist.
Heute sind asynchrone PR-Reviews im GitHub-Stil mit Inline-Kommentaren der Standard, aber Reviews bestehen nicht nur daraus. Früher gab es auch persönliche Reviews mit Prozessen, die eher an eine Dissertationsprüfung oder einen Konferenzvortrag erinnerten.
Die Literatur, die Code-Reviews als nützliche Qualitätspraktik beschreibt – genauer gesagt als eine der wenigen tatsächlich nützlichen Qualitätspraktiken –, stammt überwiegend aus deutlich stärker strukturierten Review-Prozessen als den heutigen.
Persönlich empfand ich PR-Reviews im GitHub-Stil vor LLMs eher als etwas, das einem das Gefühl gibt, der Prozess sei in Ordnung, oder als Mittel, um Governance-Checkboxen abzuhaken; in der LLM-Ära scheint mir ihr Kosten-Nutzen-Verhältnis deutlich schlechter geworden zu sein, sodass sie wohl verschwinden werden.
Um eine etwas erzwungene technische Analogie zu bemühen: Asynchrone Textkommunikation ist im Hinblick auf erfolgreich codierbare Information verlustreicher als Sprache und hat auch geringeren Durchsatz. Wenn man also viele Informationen austauschen muss, lohnt es sich, die Synchronisierungskosten zu zahlen.
Das war näher an traditionellem Engineering, und alle mussten Software als etwas Dauerhafteres betrachten.
Wartbarkeit sicherzustellen ist sicherlich einer der Vorteile von Code-Reviews, aber zu sagen, das sei ihr einziger Zweck, ist eine ziemlich gewagte Behauptung.
Code-Reviews sind auch ein Werkzeug, mit dem das Team Codeänderungen nachvollziehen und gemeinsame Verantwortung für die gesamte Codebase übernehmen kann.
Code-Review ist nichts Einheitliches. Es gibt viele Gründe dafür: Wissensaustausch, Verantwortungswäsche, Codequalität, Compliance und so weiter; und wie immer hängt der jeweilige Zweck vom Nutzungskontext ab.
Wenn der Autor Mathematiker ist, bedeutet „Durch Anschauen von Code findet man im Allgemeinen keine Bugs“ vermutlich nicht, dass es völlig unmöglich ist, Bugs zu finden.
Gemeint ist eher, dass es nicht möglich ist, alle Bugs oder einen bestimmten Bug zu finden.
Um es mit dem Wartbarkeitsargument zu verbinden: Ein Ziel von Reviews ist auch, Code so einfach wie möglich zu machen, damit die Wahrscheinlichkeit steigt, dass er per Review debugbar ist. Im absoluten Sinn verhindert das keine Bugs, aber es erhöht die Wahrscheinlichkeit.
Die erste Interpretation ist relevant, aber falsch; die zweite ist wahr, aber nicht relevant.
Wahrscheinlich wollte der Autor sagen: „Einen gegebenen Bug kann man im Allgemeinen nicht durch Anschauen des Codes finden“, also „Für jeden Bug B gilt nicht, dass man B finden kann“; aber auch das ist zwar wahr, geht jedoch am Kern vorbei.
Ich habe sowohl mit Leuten zusammengearbeitet, die PR-Vorschläge häufig ablehnen, als auch mit Leuten, die Vorschläge annehmen.
Bei Menschen, die Vorschläge annehmen, frage ich mich, ob sie die Ownership bis zu einem gewissen Grad mit mir teilen wollen. Es fühlt sich so an, als würden wir beide den Code warten und verstehen und in dieselbe Richtung schauen.
Umgekehrt sinkt meine Bereitschaft, mich an PRs von Leuten zu beteiligen, die PR-Vorschläge ablehnen. Wenn es am Ende ohnehin abgelehnt wird, warum sollte man dann Zeit investieren und gründlich reviewen?
thought,change,nit,fixoderchatvoran.thoughtbedeutet zum Beispiel: „Vielleicht wird foo später häufiger, dann refactoren wir es“,change: „Das ist eine leaky abstraction; ich würde es gern wie bar modelliert sehen“,nit: „Der Name ist nicht ganz intuitiv, lass uns Baz oder Boo in Betracht ziehen“,fix: „Dieser Unit-Test prüft das falsche Feld“, undchat: „Das ist eine größere Entscheidung darüber, wie Lösungen dieser Kategorie künftig aussehen sollen; lass uns das zuerst im Team besprechen.“Manche Präfixe bedeuten, dass der PR bis zur Änderung blockiert ist, andere, dass es ein Kommentar ist, den man annehmen kann oder auch nicht. Für die Autorin oder den Autor wird klarer, was „wir müssen zu einem gemeinsamen Verständnis kommen“ ist und was eher „Ausdruck einer Präferenz“ oder „Beobachtung“ ist.
Allerdings sollte man nicht gekränkt sein, wenn man ein
nithinterlässt und die andere Person nicht zustimmt und es ignoriert. Wenn man stark dabei empfunden hat, hätte es keinnitsein dürfen.