最有效却无人使用的质量流程

1976年,Michael Fagan 在 IBM Systems Journal 上发表了一篇论文,描述了一种审查流程,其有效性使之成为软件质量的金标准。Fagan Inspections 在尚未运行任何测试之前,就能捕获全部缺陷的60%到90%。NASA 2002年的一项研究发现,每投入1小时审查,平均可避免后续33小时的维护工作。

按任何可衡量的标准,这都是软件工程史上产出的最佳审查流程。

如今几乎无人使用。

问题不在于 Fagan Inspections 是否有效。它有效得近乎过头。问题在于,一个拥有如此业绩的流程为何从主流开发中消失,而当我们取代它时,是否失去了某些重要的东西。

Fagan Inspections 的实际样貌

Fagan 并非发明了 code review。他发明了一种特定的、高度结构化的发现缺陷的仪式。

该流程包含六个严格阶段:

  1. Planning: 主持人选定参与者,并确认材料符合准入条件。
  2. Overview: 作者解释背景。这是建立上下文,而非审查。
  3. Preparation: 每位参与者独立审查材料,速度约为每小时150行,并列出个人怀疑的缺陷清单。
  4. Inspection Meeting: 团队集会不超过两小时。Reader 大声朗读逻辑。Recorder 记录缺陷。主持人确保会议聚焦发现缺陷,而非解决缺陷。
  5. Rework: 作者修复所有已记录的缺陷。
  6. Follow-up: 主持人验证已做修正,且未引入新缺陷。

角色明确且不重叠。主持人负责推进流程,但不是技术权威。Reader 朗读代码,但不为其辩护。作者在场,但会议期间禁止解释意图。目的不是协作,而是冷静、系统化的缺陷侦测。

这正是它奏效的原因。普通工程会议的社会动力学被刻意排除在外。

为何数据如此出色

缺陷检出率并非偶然。它源于若干深思熟虑的设计选择。

Independent preparation 意味着六个人在群体讨论可能对他们产生偏见之前,各自隔离地审查同一段代码。他们清单之间的重叠部分告诉你缺陷有多明显。只有一个人发现的条目往往最有价值。

严格的两小时上限防止疲劳摧毁判断力。Fagan 知道审查有效性在两小时后急剧下滑。每小时150行的速率同样是刻意设定。速度再快,你开始看到的就不是实际存在的东西,而是你预期看到的东西。

No-solutions 规则保持会议聚焦。没有什么比满屋子工程师为尚未完全界定的缺陷设计修复方案更能摧毁审查。

这些约束不是官僚负担,而是机制本身。移除它们,你会得到更友好但也更无效的东西。

是什么扼杀了 Fagan Inspections

如果流程如此有效,它为何消失?

简短的答案是:它的昂贵方式正是现代软件开发拒绝容忍的那种。

一次 Fagan Inspection 消耗被审代码编写工时的15%到20%。在一个记录在案的案例中,348行代码需要27.3人时的审查。当团队每天多次发布时,这一比例不可想象。

光是日程安排就是一份全职工作。你需要五到六个人在房间里待上两小时,加上 preparation,加上 follow-up。在大型组织中,找到一个主持人、Reader、两名 Reviewer、Recorder 和作者都有空的两小时时段可能要花好几天。

僵化的角色结构同样无法扩展。Fagan Inspections 假定团队稳定且人数足以填满所有角色。在创业公司,五人团队可能没有专人能担任专职主持人而不摧毁 velocity。

最大的因素是文化。Fagan Inspections 刻意制造不适。作者静坐一旁,由同事朗读其代码并记录缺陷。没有”这只是初稿”的余地。该流程假定缺陷代价高昂,而社会摩擦成本低廉。现代工程运行的前提恰恰相反。

我们用什么取代了它

行业并未放弃结构化审查。它用 pull request 取而代之。

Pull request review 是异步的、低仪式感的,直接嵌入开发 workflow。Reviewer 可以在会议间隙、手机上或等待 CI 完成时查看 diff。没有指定角色。作者和 Reviewer 往往是同一个人,只差一个 approval 就能 merge。

这在可及性、速度和 developer experience 上是巨大进步。在缺陷侦测上则是巨大退步。

多项研究发现,非正式审查大约只能捕获结构化审查所捕获缺陷的一半。2009年 Basili 等人的实验对比了 Fagan 式审查与 lightweight review,发现更轻量的流程在同一材料上显著检出了更少的缺陷。

问题不在于 Reviewer 懒惰。流程的设计目的就不是发现缺陷。它的设计目的是让代码在”多一双眼睛看过”的情况下发货,这不是一回事。

我们真正失去了什么

Pull request review 优化吞吐率。Fagan Inspection 优化彻底性。这是截然不同的目标,两者都没错。错误在于假设更轻量的流程是更重量流程的严格超集。

以下是消失的东西:

Independent preparation。 在 pull request 中,Reviewer 是 cold reading diff。他们没有花一小时阅读周边上下文、追踪数据流、构建心智模型。他们只是在响应通知。审查深度不可同日而语。

Reader 角色。 让人大声朗读代码,迫使团队以最慢成员能跟上的节奏推进。它暴露出默读时隐藏的假设。屏幕上的 diff 让眼睛跳过无聊的部分。Reader 不会跳过。

Defect-only focus。 Pull request 评论容易滑向风格和架构意见。这些也有价值,但不是缺陷侦测。花在争论缩进上的每一分钟,都是没有花在寻找 null dereference 上的一分钟。

可测量的流程数据。 Fagan Inspections 产出硬数字:每小时缺陷数、preparation 时间、rework 时间、模块级 defect density。现代审查工具只统计评论数和 approval 数,几乎无法说明审查质量。

一种务实的中间路线

你不会在现代 continuous deployment 环境中运行完整的 Fagan Inspections。但你可以借用它重要的部分。

最重要的可迁移思想是结构化的 independent preparation。在深度异步审查之前,要求 Reviewer 独自花时间在材料上。不是快速扫读。是真正的 preparation。

不要再用泛泛的 “LGTM”,强制推行一份 lightweight checklist,模仿 Fagan 内建在角色和规则中的纪律:

from dataclasses import dataclass, field
from typing import List, Optional
from enum import Enum

class DefectSeverity(Enum):
    MINOR = "minor"
    MAJOR = "major"
    CRITICAL = "critical"

@dataclass
class ReviewEntry:
    line_number: Optional[int]
    category: str
    severity: DefectSeverity
    description: str

@dataclass
class InspectionReport:
    reviewer: str
    prep_time_minutes: int
    entries: List[ReviewEntry] = field(default_factory=list)

    def defect_count(self) -> int:
        return len(self.entries)

def run_inspection_checklist(
    code: str,
    reviewer: str,
    prep_time_minutes: int
) -> InspectionReport:
    """Structured prep produces structured output.

    Mimics the Fagan prep phase: reviewer spends focused
    time with the material, then logs findings against a
    consistent taxonomy instead of ad hoc comments.
    """
    report = InspectionReport(
        reviewer=reviewer,
        prep_time_minutes=prep_time_minutes
    )

    # Example: check for missing null handling
    if "->" in code and "null" not in code.lower():
        report.entries.append(ReviewEntry(
            line_number=None,
            category="null-safety",
            severity=DefectSeverity.MAJOR,
            description="No explicit null handling in pointer function"
        ))

    return report

这不是 Fagan Inspection。它是恢复其最重要特性之一的方式:审查产出应当是结构化的、可测量的,并聚焦于缺陷类别而非个人意见。

另一个可迁移的思想是 time-boxed deep review。每个 sprint 挑选一个关键模块。安排一次90分钟的聚焦审查,附带 independent preparation。只记录缺陷。不讨论解决方案,不争论风格,不涉及设计争议。

成本是真实的。但如果 NASA 的比例哪怕大致成立,现在专注一小时就能省去日后数十小时的调试。

令人不安的真相

Fagan Inspections 并未失败。它们被拒绝,是因为其要求的严谨性与行业优先追求的速度不兼容。

这一权衡对大量软件而言是合理的。landing page 按钮上的拼写错误不需要五人正式审查。但取代 Fagan Inspections 的文化对所有代码一视同仁,而成本就隐藏在这里。

最昂贵的缺陷存在于那些看起来正确、通过测试、却在生产环境中以真金白银为代价的方式失败的代码里。正是这类代码,最能从”旨在发现缺陷”的流程中受益,而不是从”旨在 approve diff”的流程中受益。

Pull request review 将会继续存在,这本身没问题。但假装它能取代结构化审查则有问题。它是为不同工作准备的不同工具,只拥有其一的团队将继续发现那些更优流程本可在发货前捕获的昂贵 bug。

FAQ

什么是 Fagan Inspection?

一种由 Michael Fagan 于1970年代在 IBM 开发的、用于发现软件制品缺陷的结构化多阶段审查流程。包含六个阶段(Planning、Overview、Preparation、Inspection Meeting、Rework、Follow-up),参与者有特定角色。会议只聚焦记录缺陷,不解决缺陷。

Fagan Inspections 有多有效?

IBM 报告的缺陷移除率超过90%。NASA 2002年研究发现每1小时审查平均避免33小时维护。独立研究一致表明,结构化审查捕获的缺陷约为非正式审查的两倍。

为什么团队不再使用 Fagan Inspections?

该流程消耗项目总工时的15%到20%,需要协调多名参与者的时间,且文化上过于僵化。随着软件团队转向更快的发布周期,开销变得不可持续。Pull request review 取代它成为默认方案,因为它更快、更易融入日常工作流,尽管捕获的缺陷更少。

现代团队还能从 Fagan Inspections 中受益吗?

不能以原始形式受益。带指定角色的完整六阶段流程不适合 continuous deployment。但核心思想——independent preparation、time-boxed focused review、structured defect logging、以及将缺陷发现与方案设计分离——可以改编。选择性地将这些应用于关键代码的团队,能在不产生开销的情况下获得大部分收益。