Die meisten Review-Checklisten sind Placebos

Wenn dein Team eine Code-Review-Checkliste hat, gibt es eine gute Chance, dass sie auf einer Wiki-Seite existiert, die niemand öffnet. Sie sagt wahrscheinlich Dinge wie „check for off-by-one errors” und „verify error handling.” Das sind wahre Aussagen. Sie sind aber zu vage, um das Verhalten zu ändern.

Eine Checkliste, die dir sagt, „check for bugs” zu prüfen, ist keine Checkliste. Sie ist eine Erinnerung daran, dass Bugs existieren.

Die Checkliste bei einer Fagan inspection dient einem anderen Zweck. Sie ist keine Liste von Dingen, über die man sich Sorgen machen muss. Sie ist ein strukturiertes Werkzeug, das die Aufmerksamkeit des Reviewers auf spezifische Defektkategorien lenkt, die im tatsächlich zu reviewenden Codebase beobachtet wurden. Sie wird aus Daten gebaut, auf den Artefakttyp zugeschnitten und während der obligatorischen individuellen Vorbereitung verwendet. Bei korrekter Anwendung ist sie einer der Hauptgründe, warum strukturierte Inspections drei- bis viermal so viele Defekte finden wie informelle Reviews.

Was eine Checkliste „strukturiert” macht

Das Wort „strukturiert” ist hier wichtig. Eine strukturierte Checkliste ist keine längere Checkliste. Sie ist eine Checkliste mit spezifischen Designeigenschaften.

Erstens leitet sie sich aus Daten über entkommene Defekte ab. Die Punkte stammen von Bugs, die tatsächlich in Produktion gegangen sind, nicht von einem generischen Best-Practices-Dokument. Wenn deine letzten drei Incidents alle race conditions in async cleanup betrafen, bekommt diese Kategorie einen eigenen Checklistenpunkt. Wenn null dereferences in zwei Jahren kein Problem waren, wird dieser Punkt entfernt.

Zweitens ist sie auf den zu reviewenden Artefakttyp beschränkt. Fagan inspections verwendeten ursprünglich verschiedene Checklisten für Anforderungsdokumente, Designdokumente, Source Code und Testpläne. Jedes Artefakt hat unterschiedliche Defektkategorien. Eine Designdokument-Checkliste fragt nach Konsistenz von Interfaces und Kopplung. Eine Source-Code-Checkliste fragt nach Boundary Conditions und Ressourcenbereinigung. Beides zu mischen verwässert beides.

Drittens wird sie während der individuellen Vorbereitung verwendet, nicht während des Meetings. Jeder inspector liest das Material allein, mit der Checkliste, bevor die Gruppe überhaupt zusammentritt. Die Checkliste prägt, was jede Person sieht, wenn sie den Code isoliert liest. Sie ist kein gemeinsames Referenzdokument. Sie ist eine persönliche Linse.

Viertens ist sie kurz genug, um nutzbar zu sein. Eine Checkliste mit vierzig Punkten ist ein Katalog, kein Werkzeug. Fagan empfahl etwa zehn bis fünfzehn Punkte pro Artefakttyp. Die Einschränkung erzwingt Priorisierung. Du behältst die Kategorien, die wichtig sind, und wirfst den Rest weg.

Wie man eine aus eigenen Daten baut

Der beste Weg, eine strukturierte Checkliste zu bauen, ist zu schauen, was bereits schiefgegangen ist. Hier ist ein praktischer Prozess.

Beginne mit deinem Incident Tracker, deiner Bug-Datenbank oder deinen postmortem-Notizen. Ziehe die letzten zwanzig Defekte heran, die dem Review entkommen sind und es in Produktion oder Test geschafft haben. Kategorisiere jeden nach Root Cause, nicht nach Symptom. „Page crashed” ist ein Symptom. „Missing null check after external API response” ist eine Root Cause.

Gruppiere die Root Causes in Kategorien. Du wirst wahrscheinlich feststellen, dass 80 Prozent deiner entkommenen Defekte in drei bis fünf Kategorien fallen. Das sind deine Checklistenpunkte.

Für jede Kategorie schreibe eine spezifische Trigger-Frage, keine vage Erinnerung. „Check for nulls” ist vage. „For every function that calls an external API, verify the response is validated before use” ist eine Trigger-Frage. Sie sagt dem Reviewer genau, was er suchen soll und wo er suchen soll.

Hier ist ein konkretes Beispiel. Angenommen, dein Team shippt Python-Services und du hast die letzten zwanzig Produktionsissues analysiert. Du findest diese Verteilung:

  • 6 Issues: fehlende Fehlerbehandlung bei externen Aufrufen
  • 5 Issues: falsche Datenbank-transaction boundaries
  • 4 Issues: off-by-one in Paginierungslogik
  • 3 Issues: race conditions in gecachten Daten
  • 2 Issues: Logging sensibler Daten

Deine Checkliste sollte fünf Punkte haben, einen für jede Kategorie. Jeder Punkt sollte eine Trigger-Frage sein, die an deine spezifischen Patterns gebunden ist.

#!/usr/bin/env python3
"""Build a structured review checklist from escaped defect data."""

from collections import Counter
from dataclasses import dataclass
from typing import List


@dataclass
class EscapedDefect:
    id: str
    root_cause: str
    trigger_question: str


def build_checklist(defects: List[EscapedDefect], max_items: int = 10) -> List[str]:
    """Build a Fagan-style checklist from escaped defect data.

    Groups by root cause, sorts by frequency, and returns trigger
    questions for the top categories.
    """
    counts = Counter(d.root_cause for d in defects)
    top_causes = counts.most_common(max_items)

    # Map root causes back to their most representative trigger question
    cause_to_question = {}
    for d in defects:
        if d.root_cause not in cause_to_question:
            cause_to_question[d.root_cause] = d.trigger_question

    checklist = []
    for cause, count in top_causes:
        question = cause_to_question[cause]
        checklist.append(f"[{count}x] {question}")

    return checklist


# Example: escaped defects from a Python web service
DEFECTS = [
    EscapedDefect("BUG-101", "missing-external-error-handling",
                  "For every external API call, is the response validated before use?"),
    EscapedDefect("BUG-102", "missing-external-error-handling",
                  "For every external API call, is the response validated before use?"),
    EscapedDefect("BUG-103", "missing-external-error-handling",
                  "For every external API call, is the response validated before use?"),
    EscapedDefect("BUG-104", "transaction-boundary-error",
                  "Does every database write have the correct transaction scope?"),
    EscapedDefect("BUG-105", "transaction-boundary-error",
                  "Does every database write have the correct transaction scope?"),
    EscapedDefect("BUG-106", "pagination-off-by-one",
                  "For every pagination query, are the limit and offset tested at boundaries?"),
    EscapedDefect("BUG-107", "cache-race-condition",
                  "For every cached value, is there a clear invalidation or TTL strategy?"),
    EscapedDefect("BUG-108", "sensitive-data-in-logs",
                  "Does any log statement include user data, tokens, or PII?"),
]

if __name__ == "__main__":
    checklist = build_checklist(DEFECTS, max_items=5)
    print("Structured Review Checklist")
    print("=" * 40)
    for item in checklist:
        print(f"  [ ] {item}")

Führe dieses Skript gegen deine eigenen Bug-Daten aus und du hast eine Checkliste, die an deine tatsächlichen Failure Modes gebunden ist, nicht an die eines anderen.

Wie die Review-Sitzung tatsächlich aussieht

Mit der Checkliste in der Hand hat die Review-Sitzung einen spezifischen Rhythmus.

Während der individuellen Vorbereitung liest jeder inspector das Material allein mit etwa 100 bis 150 Zeilen pro Stunde. Sie verwenden die Checkliste als Linse, nicht als Skript. Sie arbeiten die Checkliste nicht Punkt für Punkt in Reihenfolge ab. Sie lesen den Code natürlich, und wenn sie auf ein Pattern stoßen, das zu einer Checklistenkategorie passt, halten sie inne und untersuchen es sorgfältig.

Die Checkliste ist kein Ersatz für Lesen. Sie ist ein Pattern-Matcher für Dinge, die dein Gehirn wahrscheinlich überspringt.

Im inspection meeting paraphrasiert der reader den Code laut. Wenn ein inspector einen potenziellen Defekt entdeckt, meldet er ihn sofort. Die Checklistenkategorien liefern ein gemeinsames Vokabular. Statt zu sagen „das sieht falsch aus”, kann ein inspector sagen „das sieht nach einem transaction boundary error aus, Kategorie zwei.” Der moderator protokolliert es. Der author hört zu. Niemand schlägt eine Lösung vor.

Die Checkliste wird während des Meetings nicht herangezogen. Zu diesem Zeitpunkt haben die inspectors sie bereits internalisiert. Das Meeting dient dem Abgleich dessen, was jeder einzeln gefunden hat.

Warum generische Checklisten scheitern

Die meisten Teams, die Checklisten ausprobieren, geben auf, weil sie die falsche Art verwenden.

Eine generische Checkliste, die aus einem Blogpost kopiert wurde, hat kein emotionales Gewicht. Sie bittet den Reviewer, nach Dingen zu suchen, die in seinem Codebase vielleicht nie passiert sind. Der Reviewer überfliegt sie, sieht nichts Vertrautes und liest den diff weiter, wie er es immer getan hat.

Eine strukturierte Checkliste, die aus entkommenen Defekten gebaut wurde, ist anders. Jeder Punkt repräsentiert einen realen Incident, der echte Zeit gekostet hat. Der Reviewer kennt diese Bugs, weil er die postmortems gesehen hat. Die Checkliste verbindet Review-Verhalten mit spezifischen, einprägsamen Fehlern.

Der andere häufige Fehler ist, die Checkliste als Meeting-Werkzeug statt als Vorbereitungswerkzeug zu behandeln. Wenn du die Checkliste während des Review-Meetings herausholst, wird sie zu einem Skript für gemeinsames Überfliegen. Alle lesen denselben Punkt, schauen auf denselben Code und konvergieren auf denselben offensichtlichen Beobachtungen. Die Kraft der Checkliste liegt in der individuellen Vorbereitung, wo sechs Personen dieselben Kategorien unabhängig anwenden und verschiedene Dinge finden.

Die Wartungslast, die die meisten Teams ignorieren

Eine Checkliste ist kein Monument. Sie ist ein lebendiges Dokument, das schneller verrottet als Code.

Wenn du jedes Mal einen Checklistenpunkt hinzufügst, wenn etwas schiefgeht, aber nie einen entfernst, hast du in sechs Monaten vierzig Punkte. An diesem Punkt fangen Reviewer an, sie wie Tapete zu behandeln.

Fagan empfahl, die Checkliste selbst nach jeder wenigen Inspections zu überprüfen. Entferne Punkte, die in den letzten fünf Reviews keinen Fund ausgelöst haben. Füge Punkte nur für neue Defektkategorien hinzu, die in Produktion entkommen sind. Halte die Gesamtzahl unter fünfzehn. Wenn du sie nicht kurz halten kannst, priorisierst du nicht.

Hier ist ein leichtgewichtiges Skript, um eine Checkliste basierend auf historischen Funddaten zu beschneiden:

#!/usr/bin/env python3
"""Prune a review checklist: remove items that no longer trigger findings."""

from dataclasses import dataclass
from typing import List, Dict


@dataclass
class ChecklistItem:
    category: str
    trigger_question: str
    findings_last_5_reviews: int


def prune_checklist(items: List[ChecklistItem], min_findings: int = 1) -> List[ChecklistItem]:
    """Remove checklist items that have not produced findings recently.

    A Fagan-style checklist should be short enough to be usable.
    Items that sit idle waste attention.
    """
    kept = [item for item in items if item.findings_last_5_reviews >= min_findings]
    removed = [item for item in items if item.findings_last_5_reviews < min_findings]

    print(f"Kept {len(kept)} items, removed {len(removed)} items")
    for item in removed:
        print(f"  REMOVED: [{item.category}] {item.trigger_question}")

    return kept


# Example: current checklist with finding counts from the last 5 reviews
CURRENT_CHECKLIST = [
    ChecklistItem("missing-external-error-handling",
                  "For every external API call, is the response validated before use?", 4),
    ChecklistItem("transaction-boundary-error",
                  "Does every database write have the correct transaction scope?", 3),
    ChecklistItem("pagination-off-by-one",
                  "For every pagination query, are the limit and offset tested at boundaries?", 0),
    ChecklistItem("cache-race-condition",
                  "For every cached value, is there a clear invalidation or TTL strategy?", 1),
    ChecklistItem("sensitive-data-in-logs",
                  "Does any log statement include user data, tokens, or PII?", 0),
]

if __name__ == "__main__":
    pruned = prune_checklist(CURRENT_CHECKLIST, min_findings=1)
    print("\nActive checklist:")
    for item in pruned:
        print(f"  [ ] [{item.findings_last_5_reviews}x] {item.trigger_question}")

Plane diese Überprüfung vierteljährlich. Eine veraltete Checkliste ist schlimmer als keine, weil sie Reviewer trainiert, das Werkzeug zu ignorieren.

Der Trade-Off: Daten vs. Geschwindigkeit

Eine strukturierte Checkliste aus Defektdaten zu bauen, kostet Zeit. Du brauchst eine genaue Incident-Kategorisierung. Du brauchst Disziplin, die Liste kurz zu halten. Du brauchst einen Prozess, um sie aktuell zu halten.

Eine generische Checkliste dauert fünf Minuten zu schreiben und fünf Wochen, um irrelevant zu werden.

Die strukturierte Version ist langsamer zu erstellen, aber schneller zu verwenden. Ein Reviewer mit fünfzehn gezielten Trigger-Fragen scannt Code effizienter als ein Reviewer mit vierzig generischen Erinnerungen. Die Spezifität spart Zeit.

Die wirklichen Kosten sind organisatorisch. Du brauchst eine Aufzeichnung entkommener Defekte, die detailliert genug ist, um Root Causes zu extrahieren. Viele Teams haben das nicht. Ihr Bug-Tracker hat Titel wie „user report: page broken” und keine Folgeanalyse. Ohne Root-Cause-Daten kannst du keine Checkliste aus Beweisen bauen. Du kannst nur die eines anderen kopieren.

Häufig gestellte Fragen

What is a structured checklist-based review?

Ein Review-Prozess, bei dem jeder inspector während der obligatorischen individuellen Vorbereitung vor einem Gruppen-inspection meeting eine Checkliste verwendet, die aus tatsächlichen entkommenen Defektdaten gebaut wurde. Die Checkliste enthält gezielte Trigger-Fragen statt generischer Erinnerungen.

How is this different from a normal code review checklist?

Eine normale Checkliste ist oft generisch, aus einer Vorlage kopiert und wird während des Reviews beiläufig herangezogen. Eine strukturierte Checkliste leitet sich aus deiner spezifischen Defektgeschichte ab, ist auf den Artefakttyp beschränkt und wird während der fokussierten individuellen Vorbereitung mit einer definierten Lesegeschwindigkeit verwendet.

How many items should a structured checklist have?

Zehn bis fünfzehn Punkte pro Artefakttyp. Mehr als das, und Reviewer fangen an zu überfliegen. Weniger als fünf, und du vermutlich Kategorien.

How often should the checklist be updated?

Nach jeder drei bis fünf Inspections oder vierteljährlich. Entferne Punkte, die keine Funde produziert haben. Füge Punkte nur für neue entkommene Defektkategorien hinzu.

Can this work without a full Fagan inspection process?

Ja. Die Checkliste selbst ist der portabelste Teil der Fagan-Methode. Verlange individuelle Vorbereitung, gib Reviewern eine strukturierte Checkliste, die aus deinen Daten gebaut wurde, und führe ein time-boxed Meeting mit einem reader durch. Du wirst mehr Defekte finden als mit informellem Review, selbst ohne die volle Six-Phase-Ceremony.

Starte mit einem Modul und einem Quartal

Du musst nicht deinen gesamten Review-Prozess überholen. Wähle ein Modul, das in den letzten sechs Monaten Produktionsdefekte hatte. Sammle die Root Causes. Baue eine Fünf-Punkte-Checkliste. Verlange zwei Reviewer, dreißig Minuten mit dem Code und der Checkliste zu verbringen, bevor es eine Gruppendiskussion gibt.

Protokolliere, was sie finden. Vergleiche es mit dem, was dein normales Pull-Request-Review am selben Code gefunden hat. Dieser einzelne Vergleich wird dir sagen, ob eine strukturierte Checkliste den Aufwand für dein Team wert ist.

Wenn die Daten ja sagen, erweitere auf ein weiteres Modul. Wenn die Daten nein sagen, ist dein informelles Review bereits gut genug, oder deine Defektdaten sind nicht detailliert genug, um eine nützliche Checkliste zu bauen. In jedem Fall hast du eine echte Messung statt einer geliehenen Meinung.