两位审查员,一份差异,零重叠
两位高级工程师审查同一份拉取请求。一位标记了缺失的空值检查。另一位发现了清理路径中的竞态条件。两人都没有发现全部问题。
如果你只指派了一位审查员,那么其中一个缺陷就会被发布上线。这不是技能差距。这是人类注意力的可预测特性,而 Michael Fagan 于1976年在IBM记录了这一现象。
Fagan 当时正在测量IBM软件流水线中的缺陷检测率。他的数据显示了一个令人不安的事实:即使是经验丰富的检查员,也只能发现团队最终发现的所有缺陷中的一小部分。真正的价值并不在于任何个人的专业知识,而在于多种视角的结构化组合。
如今大多数团队并没有结构化地组合这些视角。一位高级工程师在会议间隙快速浏览差异,注意到一个风格问题,批准通过,然后继续工作。下一位审查员也是如此。两人都遗漏了那个将在下周二破坏生产数据的差一错误。
Fagan 将此称为非结构化审查综合征。团队有眼睛,但没有流程。
Fagan 审查究竟是什么
Fagan 审查不是一群人坐在一起读代码、分享感受的会议。它是一个具有明确角色、准入标准和可衡量产出的正式流程。Fagan 设计它是因为非结构化审查浪费时间,而且泄漏缺陷的比率与完全不审查几乎相同。
核心洞见在于角色分离。每个人恰好负责一项工作:
- 主持人主持会议并执行规则。他们不进行检查。
- 阅读者大声复述代码。这迫使团队面对代码实际做了什么,而不是作者意图做什么。
- 测试者思考执行路径、边界条件和覆盖缺口。
- 作者回答问题,但不捍卫代码。
这种分离防止了集体审查中最常见的失败模式:作者说服所有人放弃他们的顾虑。
当作者同时充当解释者时,他们会淡化模糊性。「哦,那个变量总是由调用方设置的。」团队点头。没有人去核实。阅读者角色的存在就是为了打破这个习惯。如果阅读者无法用一句话复述一个函数,那这个函数就不适合发布。
为什么检查清单胜过直觉
Fagan 还引入了审查检查清单。这些不是从风格指南复制而来的通用编码标准。它们针对正在审查的特定类型工件量身定制。
状态机的检查清单会问:你是否处理了每一次状态转移?资源分配器的检查清单会问:在每条路径上,每一次分配是否都与一次释放配对?
检查清单的存在是因为人类注意力是不均匀的。一位写过一万条数据库查询的专家会在心理上跳过 BEGIN TRANSACTION 代码块。他的大脑会自动将其补全为正确。Fagan 发现,由检查清单驱动的检查员发现了直觉驱动型审查员遗漏的缺陷——不是因为专家粗心,而是因为专业知识会产生盲点。
以下是一份针对单个 Python 函数的轻量检查清单:
CHECKLIST = [
"Can a non-author paraphrase what this function does in one sentence?",
"Does every execution path return or raise predictably?",
"What happens at the minimum and maximum valid inputs?",
"What happens at exactly one step past the boundary?",
"Does the function mutate any argument, closure, or global state?",
"Is every resource acquired also released on the error path?",
"If this raises, can the caller distinguish recoverable from fatal?",
]
这不是官僚主义。它是系统性注意力的强制机制。
Fagan 将审查分为四个阶段,而会议是最短的
真正的 Fagan 审查有四个阶段,而会议本身是最短的一个。
**准备。**每位检查员在团队会面之前,独自带着检查清单审查材料。Fagan 发现,有准备的检查员发现的缺陷数量大约是毫无准备直接参会者的两倍。会议的存在只是为了整合发现,而不是产生发现。
**会议。**阅读者逐行讲解代码。测试者提出假设性问题。主持人将会议控制在两小时内。作者做笔记。会议期间没有人修改代码。缺陷被记录,团队继续推进。
**返工。**作者独自修复已记录的缺陷。
**跟进。**主持人核实每一个缺陷都已处理。大规模的返工可能触发第二次审查。
这种结构对于现代拉取请求来说显得笨重。Fagan 是为大型机软件设计的,当时单个缺陷可能造成数百万美元的损失。但其原则仍然可以适应更小的场景。
何时会小题大做
Fagan 审查并非没有代价。仅准备时间就增加了相当大的开销。对于一个十行的缺陷修复来说,完整的 Fagan 审查是荒谬的。你不需要四个人和一份检查清单来发现一个缺失的导入。
收益曲线是非线性的。Fagan 的数据表明,审查对复杂的高风险模块回报最大:状态机、解析器、资源管理器,以及任何具有非局部状态或微妙顺序约束的模块。对于增删改查处理程序和样板测试,非正式审查就足够了。
真正的错误是将相同的审查策略应用于每一次变更。日志消息中的一个错别字不需要 Fagan 审查。分布式事务协调器很可能需要。
今天就可以使用的轻量版本
你不需要IBM的会议文化就能获得大部分好处。以下是一种适用于现代团队的轻量改编:
-
**要求在集体讨论之前进行独立审查。**每位审查员在任何人开口之前提交书面评论。这可以防止第一个响亮的意见主导一切。
-
**轮换阅读者角色。**请一位审查员在任何人提出批评之前用自己的话总结变更。如果他做不到,说明变更太大或太不清楚。
-
**建立团队检查清单。**从上面的七个问题开始。添加领域特定的项目。每季度回顾一次。
-
**分离作者和辩护者。**作者回答事实性问题。他不争辩代码没问题。如果审查员感到困惑,那是数据,不是辩论。
-
**记录缺陷,稍后修复。**不要在审查期间重写代码。记录问题,结束审查,然后再修复。
以下是一个简单的脚本,可为任何 Python 模块生成审查检查清单:
import ast
import sys
from pathlib import Path
def generate_checklist(source_path: str) -> list[str]:
"""Generate a Fagan-style checklist from a Python module."""
source = Path(source_path).read_text()
tree = ast.parse(source)
checklist = [
f"Module {Path(source_path).name}: {len(tree.body)} top-level statements",
"Can a non-author state the module's responsibility in one sentence?",
]
for node in ast.walk(tree):
if isinstance(node, ast.FunctionDef):
checklist.append(
f"Function '{node.name}': does every path return or raise?"
)
if any(isinstance(n, ast.Try) for n in ast.walk(node)):
checklist.append(
f"Function '{node.name}': is every exception handled explicitly?"
)
return checklist
if __name__ == "__main__":
for item in generate_checklist(sys.argv[1]):
print(f"[ ] {item}")
像这样运行:
python inspection_checklist.py src/transaction.py
它不会帮你发现缺陷。它迫使你在正确的地方仔细查看。
雇佣更聪明的人无法解决流程问题
Fagan 的研究已有近五十年历史,但其发现并未改变。单个审查员是不一致的。团队也是不一致的,除非你对其进行结构化。审查员之间的差异不是需要消除的问题,而是需要组织的资源。
下次当一位审查员发现了另一位遗漏的缺陷时,不要问谁更优秀。要问你的流程是否足够结构化,能够组合两人所看到的东西。Fagan 已经尝试过通过招聘来解决这个问题。它不起作用。
FAQ
什么是 Fagan 审查?
Fagan 审查是由 Michael Fagan 于1976年在IBM开发的正式、结构化的代码审查流程。它使用明确的角色(主持人、阅读者、测试者、作者)、准备要求和检查清单,以最大化软件工件中的缺陷检测。
为什么不同的审查员发现不同的缺陷?
人类注意力是选择性的。专家会发展出让他们快速阅读代码的心智捷径,但这些同样的捷径会造成盲点。不同的审查员有不同的背景和认知模式,因此他们的盲点不会完全重叠。Fagan 的研究表明,审查的价值在于组合多个不完整的视角,而不是找到一个完美的审查员。
Fagan 审查今天还在使用吗?
完整的正式流程在航空航天和医疗设备等安全关键行业之外很少见。然而,其基本原则——独立准备、角色分离和检查清单驱动的审查——正越来越多地被高性能软件团队所采用。核心思想也影响了结构化走查和正式技术审查等现代实践。
完整的 Fagan 审查的开销何时值得?
对于缺陷会造成严重后果的模块:分布式共识逻辑、安全边界、资源生命周期和状态机。对于日常变更,轻量改编通常就足够了。让审查的严格程度与实际风险相匹配,而不是对每一份差异都套用相同的流程。