兩位審查員,一份差異,零重疊
兩位高級工程師審查同一份拉取請求。一位標記了缺失的空值檢查。另一位發現了清理路徑中的競態條件。兩人都沒有發現全部問題。
如果你只指派了一位審查員,那麼其中一個缺陷就會被發布上線。這不是技能差距。這是人類注意力的可預測特性,而 Michael Fagan 於1976年在IBM記錄了這一現象。
Fagan 當時正在測量IBM軟體流水線中的缺陷檢測率。他的數據顯示了一個令人不安的事實:即使是經驗豐富的檢查員,也只能發現團隊最終發現的所有缺陷中的一小部分。真正的價值並不在於任何個人的專業知識,而在於多種視角的結構化組合。
如今大多數團隊並沒有結構化地組合這些視角。一位高級工程師在會議間隙快速瀏覽差異,注意到一個風格問題,批准通過,然後繼續工作。下一位審查員也是如此。兩人都遺漏了那個將在下週二破壞生產資料的差一錯誤。
Fagan 將此稱為非結構化審查症候群。團隊有眼睛,但沒有流程。
Fagan 審查究竟是什麼
Fagan 審查不是一群人坐在一起讀程式碼、分享感受的會議。它是一個具有明確角色、准入標準和可衡量產出的正式流程。Fagan 設計它是因為非結構化審查浪費時間,而且洩漏缺陷的比率與完全不審查幾乎相同。
核心洞見在於角色分離。每個人恰好負責一項工作:
- 主持人主持會議並執行規則。他們不進行檢查。
- 閱讀者大聲覆述程式碼。這迫使團隊面對程式碼實際做了什麼,而不是作者意圖做什麼。
- 測試者思考執行路徑、邊界條件和覆蓋缺口。
- 作者回答問題,但不捍衛程式碼。
這種分離防止了集體審查中最常見的失敗模式:作者說服所有人放棄他們的顧慮。
當作者同時充當解釋者時,他們會淡化模糊性。「哦,那個變數總是由呼叫方設定的。」團隊點頭。沒有人去核實。閱讀者角色的存在就是為了打破這個習慣。如果閱讀者無法用一句話覆述一個函式,那這個函式就不適合發布。
為什麼檢查清單勝過直覺
Fagan 還引入了審查檢查清單。這些不是從風格指南複製而來的通用編碼標準。它們針對正在審查的特定類型工件量身訂製。
state machine的檢查清單會問:你是否處理了每一次狀態轉移?資源分配器的檢查清單會問:在每條路徑上,每一次分配是否都與一次釋放配對?
檢查清單的存在是因為人類注意力是不均勻的。一位寫過一萬條資料庫查詢的專家會在心理上跳過 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 的數據表明,審查對複雜的高風險模組回報最大:state machine、parser、資源管理器,以及任何具有非局部狀態或微妙順序約束的模組。對於增刪改查處理常式和樣板測試,非正式審查就足夠了。
真正的錯誤是將相同的審查策略應用於每一次變更。日誌訊息中的一個錯別字不需要 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 審查的開銷何時值得?
對於缺陷會造成嚴重後果的模組:分散式共識邏輯、安全邊界、資源生命週期和state machine。對於日常變更,輕量改編通常就足夠了。讓審查的嚴格程度與實際風險相匹配,而不是對每一份差異都套用相同的流程。