Kürzlich kam in einer Diskussion das Thema Code Reviews auf. Mich überraschte, wie entschieden selbst erfahrene Entwicklerinnen und Entwickler ihre Positionen vertraten. Beim genaueren Hinhören fiel mir auf: Viele dieser Überzeugungen spiegelten weniger ein systematisches Verständnis wider als vielmehr die Gewohnheiten der eigenen Toolchain. Das hat mich neugierig gemacht.
In diesem Artikel untersuche ich die Dimensionen, die Code-Review-Ansätze voneinander unterscheiden – synchron oder asynchron, blockierend oder nicht blockierend – und ordne ihre jeweiligen Vor- und Nachteile ein. Ziel ist es, Teams eine Orientierung zu geben, welcher Ansatz zu ihrer Projektkritikalität, Teamstruktur und gewünschten Entwicklungsgeschwindigkeit passt.
Code Review-Dimensionen
Code Review-Prozesse lassen sich durch zwei unabhängige Faktoren charakterisieren: Timing (synchron vs. asynchron) und Auswirkung (blockierend vs. nicht blockierend).
| Synchron | Asynchron | |
|---|---|---|
| Blockierend | Pair / Ensemble Programming | Pull / Merge Requests |
| Nicht blockierend | Ad-hoc-Konsultationen | Post-Push |
Bei einem synchronen Code Review sind Reviewer und Autorin beziehungsweise Autor gleichzeitig beteiligt und kommunizieren in Echtzeit. Dieser Ansatz ermöglicht sofortiges Feedback, direkte Kommunikation und die schnelle Klärung von Fragen oder Bedenken. Da alle Beteiligten während des Überprüfungsprozesses aktiv anwesend sind, können interaktive Diskussionen und Klärungen stattfinden.
Bei einem asynchronen Code Review arbeiten Reviewer und verfassende Person nach ihrem eigenen Zeitplan. Der Code wird zur Überprüfung eingereicht und die Reviewer untersuchen ihn, wann immer sie Zeit haben.
Ein blockierendes Code Review verhindert, dass der Code gemerged wird, bis die Überprüfung abgeschlossen und genehmigt ist. Dies fungiert als Kontrollinstanz und stellt sicher, dass keine Änderung ohne ausdrückliche Validierung in die Produktion gelangt.
Bei einem nicht blockierenden Code Review kann der Code auch während der ausstehenden Überprüfung weiterentwickelt werden. Feedback wird anschließend gegeben und Korrekturen nachträglich vorgenommen. Dieser Ansatz priorisiert die Entwicklungsgeschwindigkeit gegenüber einer Validierung im Vorfeld der Bereitstellung.
Synchron und blockierend
Pair Programming und Ensemble Programming sind die deutlichsten Beispiele für synchrone, blockierende Code Reviews. Bei diesen Praktiken wird der Code gemeinsam geschrieben, wobei die Reviewer von Anfang an dabei sind.
Beim Pair Programming arbeiten zwei Personen gemeinsam an einem Arbeitsplatz und schreiben sowie überprüfen gleichzeitig Code. Ein „Driver“ schreibt den Code, während ein „Navigator“ ihn in Echtzeit überprüft, Fehler identifiziert und Verbesserungen unter Berücksichtigung der architektonischen Auswirkungen vorschlägt. Die Rollen werden häufig gewechselt.
Das früher auch als „Mob Programming“ bekannte Ensemble Programming erweitert dieses Konzept auf größere Teams. Mehrere Personen arbeiten gleichzeitig an demselben Code: Eine programmiert aktiv, die anderen beobachten, machen Vorschläge und überlegen, was als Nächstes zu tun ist.
Pair Programming und Ensemble Programming sind in bestimmten Szenarien besonders wertvoll:
Kritische Änderungen: Bei Änderungen an der Kerninfrastruktur, an sicherheitsrelevantem Code oder an Systemen mit hoher geschäftlicher Auswirkung sorgt die Anwesenheit von prüfenden Personen während der Entwicklung dafür, dass Probleme sofort erkannt und behoben werden können. So wird die Einführung kritischer Fehler verhindert.
Wissensverteilung: Diese Praktiken eignen sich hervorragend, um Fachwissen innerhalb von Teams zu verbreiten. Weniger erfahrene Entwicklerinnen und Entwickler, die mit erfahrenen Kolleginnen und Kollegen zusammenarbeiten, können durch Beobachtung und aktive Teilnahme architektonisches Denken, bewährte Verfahren und Fachwissen erwerben. Komplexe Subsysteme profitieren von einem kollektiven Verständnis.
Risikoreiche Refactorings: Bei groß angelegten Refactoring-Maßnahmen, die mehrere Komponenten betreffen, ist synchrone Zusammenarbeit von Vorteil: So lassen sich Änderungen besser koordinieren und architektonische Probleme frühzeitig erkennen.
Onboarding: Neue Teammitglieder können sich schnell mit den Coding Standards des Teams vertraut machen, indem sie mit erfahrenen Kolleginnen und Kollegen zusammenarbeiten.
Synchron und nicht blockierend
Ad-hoc-Konsultationen sind informelle, synchrone Austausche, bei denen eine entwickelnde Person eine Kollegin oder einen Kollegen bittet, sich ein Stück Code anzusehen: am Schreibtisch, per Videocall oder im Chat. Anders als beim Pair Programming sind diese Gespräche nicht im Voraus geplant und blockieren nicht das Mergen des Codes. Sie eignen sich besonders für schnelle Plausibilitätsprüfungen, die Klärung von Designentscheidungen oder eine zweite Meinung zu einem kniffligen Grenzfall. Sie sind leichtgewichtig und verursachen kaum Prozessaufwand. Da sie jedoch nicht dokumentiert werden, tragen sie weder zur Dokumentation noch zum institutionellen Wissen bei.
Asynchron und blockierend
Pull Requests, auch Merge Requests genannt, haben sich zum dominierenden Mechanismus für die Überprüfung von Code in verteilten Teams entwickelt. Sie bieten einen asynchronen, aber dennoch blockierenden Ansatz für Code Reviews.
Zunächst erstellt eine Entwicklerin oder ein Entwickler einen Feature Branch und implementiert die erforderlichen Änderungen. Anschließend öffnet sie oder er einen Pull Request, um diese Änderungen zu beschreiben. Die Reviewer überprüfen den Code dann asynchron und geben ihr Feedback in Form von Kommentaren. Der Code kann erst dann gemerged werden, wenn er genehmigt wurde und die erforderlichen Prüfungen bestanden hat, beispielsweise statische Code-Analysen und automatisierte Tests.
Pull Requests bieten mehrere Vorteile:
Asynchronität: Die Reviewer können zu einem für sie passenden Zeitpunkt teilnehmen, was ideal für Remote-Teams und unterschiedliche Zeitzonen ist.
Dokumentation: Die Code Review-Diskussion wird Teil der Projektgeschichte und liefert Kontext für zukünftige Entwicklerinnen und Entwickler, die mit dem Code in Berührung kommen.
Skalierbarkeit: Die asynchrone Natur des Prozesses ermöglicht es, Projekte zu skalieren, ohne dass alle Beteiligten zur gleichen Zeit verfügbar sein müssen.
Qualitätskontrollen: Durch das Blockieren bis zur Freigabe wird sichergestellt, dass kein ungeprüfter Code in die Produktion gelangt.
Allerdings können Pull Requests auch Reibung erzeugen:
Latenz bei der Überprüfung: Das Warten auf Reviewer kann den Entwicklungsprozess verlangsamen, insbesondere wenn die Überprüfungskapazitäten begrenzt sind oder sich die Reviewer in verschiedenen Zeitzonen befinden.
Kontextwechsel: Sowohl die verfassende Person als auch die Reviewer müssen den Kontext wechseln – die eine, wenn Rückmeldungen eingehen, die anderen, wenn sie um eine Überprüfung gebeten werden.
Verfügbarkeit von Reviewern: Kritische Änderungen können blockiert werden, während auf bestimmte Reviewer mit dem erforderlichen Fachwissen gewartet wird.
Langlebige Branches: Feature Branches können erheblich vom Main Branch abweichen, was zu komplexen Merges und potenziellen Integrationsproblemen führt.
Asynchron und nicht blockierend
Code Reviews können auch asynchron und nicht blockierend durchgeführt werden. Das bedeutet, dass der Code erst nach dem Push in den Main Branch überprüft wird. Dafür sind hohe technische Disziplin, geeignete Werkzeuge und ein entsprechender Entwicklungsprozess erforderlich.
Ein solcher Prozess ist Trunk-Based Development: Entwicklerinnen und Entwickler machen häufige, kleine Commits in einem einzigen Branch, dem sogenannten „Trunk“. Die Code Reviews finden nach dem Push der Commits statt. Fehler werden durch automatisierte Tests und Monitoring erkannt, nicht durch Pre-Commit-Gates.
Damit dieser Ansatz funktioniert, müssen alle wesentlichen Funktionsentwicklungen entweder hinter Feature Flags versteckt oder mithilfe von Branch-by-Abstraction Patterns implementiert werden. So kann unvollständiger oder experimenteller Code im Trunk vorhanden sein, ohne dass dies Auswirkungen auf andere Nutzende oder Entwickelnde hat.
Mithilfe von Feature Flags können Codepfade ohne erneutes Deployment ein- oder ausgeschaltet werden. Dadurch ist eine schrittweise Einführung sowie eine sofortige Rücknahme bei auftretenden Problemen möglich.
Bei Branch-by-Abstraction werden Abstraktionsschichten eingeführt, die eine Koexistenz alter und neuer Implementierungen ermöglichen. Anschließend wird die Abstraktion schrittweise von der alten auf die neue Implementierung migriert. Danach kann die alte Implementierung entfernt werden.
Alle Änderungen, ob bedeutend oder unbedeutend, müssen leicht rückgängig gemacht werden können. Das ist eine sowohl technische als auch organisatorische Anforderung:
Technische Reversibilität bedeutet, dass automatisierte Rollback-Funktionen, Feature Flag-Umschaltungen und Datenbankmigrationsstrategien Rollback-Vorgänge unterstützen.
Organisatorische Reversibilität bedeutet, eine Teamkultur zu etablieren, in der Rollbacks nicht als Fehler, sondern als normale operative Sicherheitsmechanismen betrachtet werden.
Ohne einfache Reversibilität wird Trunk-Based Development riskant. Wenn eine problematische Änderung nicht schnell rückgängig gemacht werden kann, führt dies zu langwierigen Incidents, die das Geschäft beeinträchtigen.
Trunk-Based Development beschreibt primär die Branching-Strategie, bei der alle Entwicklerinnen und Entwickler häufig und mit kleinen Commits in einem einzigen Branch arbeiten. Ob das Code Review vor oder nach dem Push stattfindet, ist eine davon unabhängige Prozessfrage. Der Kern von „nicht blockierend“ liegt im Optimistic Merging: Änderungen werden zunächst integriert, und das System vertraut darauf, dass automatisierte Tests, Monitoring und nachgelagerte Reviews Probleme rechtzeitig und zuverlässig entdecken.
Fazit
Code wird überprüft, um Fehler zu identifizieren, Wissen auszutauschen, Standards durchzusetzen und letztlich Risiken zu minimieren. Einen universell optimalen Ansatz gibt es jedoch nicht. Synchrone blockierende Methoden wie Pair Programming oder Ensemble Programming maximieren das unmittelbare Feedback und den Wissensaustausch, können jedoch die Entwicklungsgeschwindigkeit einschränken. Asynchrone blockierende Code Reviews mit Pull Requests oder Merge Requests schaffen für viele Teams ein Gleichgewicht zwischen Geschwindigkeit und Sicherheit. Asynchrone, nicht blockierende Code Reviews maximieren die Geschwindigkeit, erfordern aber erhebliche technische Disziplin, umfassende Tests und Reversibilitätsmechanismen.
Die effektivsten Teams passen ihre Strategie für Code Reviews sorgfältig an ihren spezifischen Kontext, ihre Anforderungen und Fähigkeiten an. Viele hervorragende Teams entwickeln ihren Ansatz im Laufe der Zeit weiter und wenden mit zunehmender Reife ihrer technischen Praktiken fortschrittlichere Strategien an. Der Schlüssel liegt darin, die Kompromisse zu verstehen und bewusste Entscheidungen zu treffen, die mit den Geschäftsprioritäten und den Fähigkeiten des Teams im Einklang stehen.
Ausblick
Für die Wirksamkeit von Code Reviews ist neben einem gut gestalteten Review-Prozess die Kultur im Team mindestens genauso wichtig. Eine konstruktive Feedbackkultur konzentriert sich auf das Verhalten und den Code statt auf die Personen, formuliert konkrete Verbesserungsvorschläge und würdigt auch positive Aspekte. Anstatt sich in Nitpicking über Formatierungen zu verlieren, sollten Reviews bewusst Architektur, Designentscheidungen und Verständlichkeit in den Vordergrund stellen.
Dabei spielt psychologische Sicherheit eine zentrale Rolle: Nur wenn sich alle sicher fühlen, Fragen zu stellen, Unsicherheiten zuzugeben und Kritik zu äußern, ohne negative Konsequenzen befürchten zu müssen, können Code Reviews ihr volles Potenzial als Lern- und Qualitätsinstrument entfalten.