两次修复同样的缺陷是流程失败
每个团队都有一个反复出现的缺陷。分页中的差一错误。认证中间件中缺失的空值检查。三个冲刺前有人「修复过」的结账竞态条件。
你并不是三次写了同一个缺陷。你写了三个具有相同原因的不同缺陷。修复处理的是症状。原因依然隐藏。
费根审查的设计初衷是在缺陷逃逸之前发现它们。大多数团队止步于返工阶段。作者修复记录的问题,主持人验证修复,然后所有人继续前进。这是错误的。跟进阶段才是因果分析该在的地方。跳过它,你就等于在为同样的缺陷类型安排下一次审查。
因果分析的实际含义
因果分析不是根本原因分析。根本原因分析问的是「哪一行代码失败了以及为什么」。因果分析问的是「我们的流程中有什么让这类缺陷得以存在」。
空指针异常的根本原因是「我们忘了检查空值」。因果分析的发现是「我们的审查检查清单没有包含空值安全性,而且我们的lint规则允许未检查的解引用」。一个修复了缺陷。另一个修复了工厂。
在费根审查中,因果分析发生在返工之后。主持人按类别对缺陷进行分组,并主持一个简短的会议来识别流程层面的原因。输出不是代码变更。而是流程变更:更新的检查清单、新的lint规则、培训缺口或修改后的准入条件。
操作机制:如何运行
从审查日志开始。每个缺陷应该已经有四个字段:位置、严重度、类型和描述。在因果分析期间添加第五个:流程原因。
主持人按类型对缺陷分组。如果十二个缺陷中有三个是边界条件错误,那就是一个模式。如果两个是来自同一模块的API误用错误,那也是一个模式。模式是信号。单个缺陷是噪音。
对每个模式问三个问题:
- 审查之前我们本可以预防这个类别吗?
- 为什么现有的预防机制漏掉了它?
- 下次预防这个类别的最廉价变更是什么?
第三个问题是大多数团队出错的地方。他们建议重写架构。这是不切实际的幻想。目标是最小的流程变更,消除整个类别。
边界条件模式可能意味着在审查检查清单中添加「差一和边界检查」。API误用模式可能意味着添加一条静态分析规则。缺失错误处理的模式可能意味着更新完成的定义,要求包含错误路径测试。
可用的因果分析追踪器
以下是一个Python脚本,它接收审查日志,按类型对缺陷分组,并提示输入流程层面的原因。
#!/usr/bin/env python3
"""
Run causal analysis on a Fagan inspection log.
Reads a JSON log, groups defects by type, and emits a causal analysis report.
"""
import json
import argparse
from collections import defaultdict
from dataclasses import dataclass, field
from typing import List, Dict
@dataclass
class Defect:
location: str
severity: str
defect_type: str
description: str
@dataclass
class CausalPattern:
defect_type: str
count: int
locations: List[str] = field(default_factory=list)
process_cause: str = ""
proposed_fix: str = ""
def load_log(path: str) -> List[Defect]:
with open(path, "r") as f:
raw = json.load(f)
return [Defect(**item) for item in raw]
def analyze_patterns(defects: List[Defect]) -> List[CausalPattern]:
groups: Dict[str, List[Defect]] = defaultdict(list)
for d in defects:
groups[d.defect_type].append(d)
patterns = []
for dtype, items in groups.items():
patterns.append(CausalPattern(
defect_type=dtype,
count=len(items),
locations=[d.location for d in items],
))
return sorted(patterns, key=lambda p: p.count, reverse=True)
def prompt_causal_input(patterns: List[CausalPattern]) -> List[CausalPattern]:
print("=== CAUSAL ANALYSIS SESSION ===")
print("For each pattern, identify the process cause and the cheapest fix.\n")
for p in patterns:
print(f"Pattern: {p.defect_type} ({p.count} occurrence(s))")
print(f"Locations: {', '.join(p.locations)}")
p.process_cause = input("Process cause: ").strip()
p.proposed_fix = input("Cheapest prevention fix: ").strip()
print()
return patterns
def emit_report(patterns: List[CausalPattern], output_path: str):
report = {
"summary": {
"total_patterns": len(patterns),
"total_defects": sum(p.count for p in patterns),
},
"patterns": [
{
"type": p.defect_type,
"count": p.count,
"locations": p.locations,
"process_cause": p.process_cause,
"proposed_fix": p.proposed_fix,
}
for p in patterns
],
}
with open(output_path, "w") as f:
json.dump(report, f, indent=2)
print(f"Report written to {output_path}")
def main():
parser = argparse.ArgumentParser(description="Causal analysis for Fagan inspections")
parser.add_argument("log", help="Path to inspection log JSON")
parser.add_argument("--output", default="causal_report.json", help="Output report path")
args = parser.parse_args()
defects = load_log(args.log)
patterns = analyze_patterns(defects)
if not patterns:
print("No defects found. Nothing to analyze.")
return
patterns = prompt_causal_input(patterns)
emit_report(patterns, args.output)
if __name__ == "__main__":
main()
将审查日志保存为 inspection_log.json:
[
{"location": "src/auth.py:42", "severity": "major", "defect_type": "null-safety", "description": "Missing null check on user object"},
{"location": "src/orders.py:88", "severity": "minor", "defect_type": "boundary", "description": "Off-by-one in pagination limit"},
{"location": "src/auth.py:67", "severity": "major", "defect_type": "null-safety", "description": "Unchecked token decode result"}
]
运行 python causal_analysis.py inspection_log.json,脚本会引导你识别流程原因。它生成的报告将成为你下次回顾或流程改进周期的输入。
为什么大多数团队跳过这一步
因果分析为已经昂贵的流程增加了时间。250行的标准费根审查需要8到12人时。因果分析再增加30到60分钟。
当你有积压工作时,这额外的时间感觉像是浪费。但如果因果分析哪怕只阻止了一种类别的缺陷复发,在下次该类别不再出现时它就回本了。
更难的问题是诚实。因果分析常常揭示缺陷之所以存在,是因为团队跳过了一个步骤。代码在审查前没有经过测试。审查者没有使用检查清单。检查清单本身就不完整。
这些发现可能令人不适。把因果分析当作追责的团队将再也得不到诚实的答案。主持人必须将其框定为流程改进,而不是问责表演。
真正的权衡:速度对学习
费根审查后你有两个选择。通过验证修复然后继续前进来闭环。或者通过验证修复并了解缺陷为什么存在来闭环。
第一个选择今天更快。第二个选择在未来六个月内更快。
从费根审查中获得最大价值的团队把审查日志当作数据集。缺陷模式是反馈。忽视它们就像运行测试套件却从不看失败结果。
并非每个缺陷类别都值得做因果分析。一次性的错别字不需要流程变更。但如果某个类别在连续两次审查中出现,你面对的就是一个伪装成代码问题的流程问题。
从影响最大的模块开始
你不需要对每次审查都做因果分析。选择产生最多生产事故或最多逃逸缺陷的模块。运行完整的费根审查,收集日志,花三十分钟做因果分析。
第一次做的时候,提出的修复方案会显而易见。更新检查清单。添加一条lint规则。写一段关于这个模式的简短团队备忘。到第三次审查时,你应该能在已经分析过的类别中看到更少的缺陷。
如果数量没有下降,说明你提出的修复方案太模糊了。「更小心一点」不是流程变更。「在CI中运行空值安全性linter」才是。
随着时间推移追踪每次审查的缺陷类别。如果因果分析起到了作用,反复出现的类别就会消失。如果它们没有消失,你要么错误地识别了原因,要么没能实施修复。
FAQ
费根审查中的因果分析是什么?
因果分析是一个审查后步骤,团队在其中检查发现的缺陷,按类别分组,并识别流程层面的原因。目标是通过改变检查清单、工具或实践来防止复发,而不仅仅是修复单个缺陷。
因果分析与根本原因分析有何不同?
根本原因分析确定单个缺陷发生的具体原因,例如缺失的空值检查。因果分析确定为什么流程允许写下整个缺陷类别,例如缺失的检查清单项或未执行的lint规则。
什么时候应该运行因果分析?
在费根审查的返工和跟进阶段之后运行,趁缺陷还新鲜的时候。关注出现在多个缺陷中或已在之前的审查中出现过的模式。一次性缺陷很少能证明流程变更的合理性。
如何判断因果分析是否有效?
随着时间推移追踪跨审查的缺陷类别。如果你的因果分析有效,反复出现的类别频率应该下降。如果同样的类别持续出现,你提出的修复方案要么不正确,要么没有被实施。
在下一个审查日志上运行它
找到你最近的审查日志。按类型对缺陷分组。对于排名最高的类别,问问下次预防它的最廉价流程变更是什么。
把那个变更写下来。指定负责人。在下次审查中验证它。这就是因果分析。其他一切都只是修缺陷。