你的代码审查可能已经失效

大多数代码审查只能发现本应找到的缺陷的15%到30%。这不是猜测。IBM在20世纪70年代就测量过,AT&T、惠普和微软的研究也在数十年间反复确认了这一范围。

非正式审查成本低廉、异步进行、 socially acceptable。但它在发现缺陷方面基本上是无效的。工程师读得太快,跳过错误路径,而且为了避免成为阻塞合并的那个人,他们不愿指出真正的问题。

有一种替代方法能够持续报告60%到90%的缺陷移除率。它由迈克尔·法根于1976年在IBM发明。不需要工具,不需要AI,不需要预算。它需要的是大多数工程团队不愿给予的东西:结构。

什么是Fagan Inspection

Fagan Inspection是一种正式定义的多步骤审查流程,具有特定角色、时间限制、准入标准和检查清单。与典型的拉取请求审查不同,它不是作者和审查者之间的对话。而是一个有主持人、阅读者、检查员和作者参与的结构性会议。

作者基本保持沉默。会议有严格的时间限制。唯一的目标是发现缺陷。

该流程遵循六个步骤:

  1. 规划。 主持人选择材料,验证准入标准,分配角色,并安排会议。准入标准的存在是有原因的。不审查草稿。文档必须完整、可编译且经过测试,才有资格占用四个人的时间。

  2. 概述。 可选。如果检查员不熟悉该领域,作者解释背景。

  3. 准备。 每位检查员在会议前单独审查材料。这一点不可协商。不能毫无准备地到场。检查员使用针对常见缺陷类型定制的检查清单,并私下标注问题。

  4. 检查。 会议本身。没有写过代码的阅读者逐行浏览代码并大声复述。检查员发现差异时提出问题。作者倾听。没有人提出修复方案。主持人执行时间限制,并确保会议仅专注于缺陷识别。

  5. 返工。 作者修复缺陷。

  6. 跟进。 主持人验证每个缺陷都已得到处理。如果发现太多缺陷,则进行重新检查。

四个角色确保流程的公正性。主持人负责规划和控制。作者创建了作品,仅在有人询问时才回答问题。阅读者在会议期间复述代码,迫使大家以更慢、更仔细的速度理解。检查员通常为二至四人,负责发现缺陷。

为什么非正式审查失败的地方,Fagan Inspection能成功

区别不在于天赋。在于流程设计。

在典型的拉取请求审查中,审查者在浏览器中阅读差异,浏览正常路径,留下几条评论,然后批准。没有准备时间。没有检查清单。没有强制审查者检查错误处理或边界条件的机制。社交动态奖励的是速度和礼貌,而不是彻底性。

Fagan Inspection反转了这些激励。

个人准备意味着每位检查员在会议开始前确实已经阅读了代码。阅读者的复述迫使团队以理解速度而非浏览速度来处理代码。检查清单将注意力引向已知的缺陷类别,而不是什么吸引了眼球。时间压力防止会议偏离到设计辩论中。将缺陷发现与缺陷修复分开,防止团队锚定在某人提出的第一个解决方案上。

结果是,Fagan Inspection在缺陷到达测试或生产环境之前就能发现大部分缺陷。

成本前置体现在人时上。

真正的权衡:人时与缺陷逃逸

这就是大多数团队不使用Fagan Inspection的原因。

一次检查需要四到六个人在房间里待上最多两个小时,才能审查大约250行代码。这是一项小变更需要8到12人时。在现代CI/CD工作流中,团队每天部署多次,这看起来荒谬。

该流程也让人感觉官僚化。准入标准、正式角色、打印的检查清单、跟进验证。大多数工程师原则上就会讨厌它。而且它无法扩展到大型差异。一个两千行的重构需要八次单独的检查会议。

但当你看总成本而非会议成本时,计算就变了。

IBM的原始数据显示,在检查期间发现和修复缺陷的成本约为在测试期间发现和修复同一缺陷成本的十分之一。当它逃逸到生产环境时,比例增长到二十或三十比一。

所以是的,检查很昂贵。但它仍然比调试生产事故、将冲刺容量消耗在被动修复上以及失去客户信任要便宜。

问题是节省是看不见的。你无法衡量你预防的缺陷。会议的成本是即时且明显的。这就是为什么非正式审查在大多数组织中获胜。它优化的是可见速度,而非隐形质量。

在2026年运行轻量级Fagan Inspection

你不需要采纳完整的仪式。大多数团队可以通过保留核心机制、去掉文书工作,以20%的开销获得70%的收益。

以下是一个实用的流程:

在任何同步审查之前要求个人准备。如果你没有读过代码,你就不参加。

指派一个没有写过代码的阅读者大声讲解逻辑。不要让作者主导。复述迫使团队处理每一个分支。

使用针对团队最常见缺陷类型定制的检查清单。从下面的清单开始,并从逃逸的缺陷中学习,不断添加条目。

时间限制为九十分钟。即使没完成也要准时结束。与其让疲劳破坏质量,不如安排第二次会议。

让作者保持被动。他们只回答澄清问题。不要为设计选择辩护。

记录缺陷,而不是解决方案。会议后再修复缺陷。

为了具体说明,这里有一个小型Python脚本,用于规划检查、估算时间并打印角色专属检查清单:

#!/usr/bin/env python3
"""
Plan a Fagan-style inspection: estimate time and emit checklists.
"""

import argparse


def count_lines(filepath):
    """Count non-blank lines."""
    try:
        with open(filepath, "r") as f:
            return sum(1 for line in f if line.strip())
    except Exception:
        return 0


def plan_inspection(files, rate=125):
    """
    rate: reviewable lines per hour. Fagan recommended 100-125 for code.
    """
    total = sum(count_lines(f) for f in files)
    hours = total / rate
    minutes = hours * 60

    print(f"Total reviewable lines: {total}")
    print(f"At {rate} lines/hour, schedule: {hours:.1f} hours ({minutes:.0f} min)")

    if hours > 2:
        sessions = int(hours / 2) + 1
        print(f"WARNING: exceeds 2-hour limit. Split into {sessions} sessions.")

    print("\n--- ROLE: Reader ---")
    print("Paraphrase each block aloud. Do not just read variable names.")
    print("Pause at every conditional, loop boundary, and API call.")

    print("\n--- ROLE: Inspector ---")
    checklist = [
        "Off-by-one in loops and array or slice access",
        "Null, None, or undefined dereferences",
        "Resource leaks: files, connections, locks, contexts",
        "Error paths: are they handled, returned, and tested?",
        "Return values: are they checked before use?",
        "Shared mutable state and thread safety",
        "Boundary conditions and input validation",
        "Invariant violations: what should stay true, and does it?",
    ]
    for item in checklist:
        print(f"  [ ] {item}")

    print("\n--- ROLE: Moderator ---")
    print("Start on time. End on time.")
    print("No design debates. No solution proposals.")
    print("Record each defect with: file, line, severity, type.")


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Plan a Fagan inspection")
    parser.add_argument("files", nargs="+", help="Source files to inspect")
    parser.add_argument("--rate", type=int, default=125, help="Lines per hour")
    args = parser.parse_args()

    plan_inspection(args.files, args.rate)

将其保存为 inspect.py,运行 python inspect.py src/auth.py src/orders.py,你就得到了时间估算和检查清单。

常见问题

什么是Fagan Inspection?

Fagan Inspection是一种具有定义角色、准入标准和检查清单的结构化六步审查流程。它由迈克尔·法根于1976年在IBM开发,用于在测试之前发现软件工作产品中的缺陷。

Fagan Inspection与拉取请求审查有何不同?

拉取请求审查通常是异步的、非正式的,由作者主导。Fagan Inspection是一个同步会议,具有分配的角色、强制性的个人准备、严格的时间限制,以及作者保持沉默而其他人发现缺陷的规则。

为什么Fagan Inspection没有更普及?

它们在人时上代价高昂,对现代团队来说感觉官僚化,而且无法很好地扩展到大型、频繁的变更。成本是可见且即时的。被预防的缺陷是不可见的。

Fagan Inspection能在敏捷或CI/CD环境中运作吗?

可以,但需要修改。大多数团队使用轻量级版本:强制个人准备、复述代码的阅读者、检查清单和严格的时间限制。完整的正式流程通常仅保留给关键或高风险的模块。

在一个模块上尝试

你不需要重写你的流程。选择一个在过去一个月中有过逃逸缺陷的模块。召集三位没有写过它的工程师。提前二十四小时给他们代码和检查清单。安排九十分钟。指派一位阅读者。让作者倾听。

测量你的发现。然后决定会议的成本是否高于缺陷可能造成的成本。

如果你想要原始数据,法根1976年的论文《Design and Code Inspections to Reduce Errors in Program Development》仍然是最佳参考。它已有五十年历史,而大多数团队仍未赶上。