前面幾頁講的都是「小顆 CL、一位 Reviewer、一天內回饋」的常規審查——那套流程建立在一個前提上:變更小到一個人審得完。但當一筆變更要在整個 monorepo 裡同步觸及數十萬、甚至數百萬個引用時,這個前提就崩了。大規模變更(Large-Scale Change, LSC) 是一組邏輯相關、卻無法以單一原子單元提交的變更;要讓它們安全落地,靠的不是「更用力地審一顆巨大 CL」,而是把它拆片、自動化、並用 OWNERS 結構重組「誰該核准」。
🧠 為什麼常規審查撐不起大規模變更
常規流程(見 為什麼需要 Code Review、審查實務準則、Critique 實戰流程)把「小顆變更 + 一位 Reviewer」當成地基。LSC 恰恰打破這個地基:一筆變更可能觸及數百萬個原始檔中的引用,沒有任何一位 Reviewer 有能力、也沒有任何一套 VCS 有記憶體,去一次吞下它。
Google 很早就放棄了「用大型原子變更橫掃程式碼庫」的想法。原因反直覺卻關鍵——隨著程式碼庫與工程師人數增長,可能的最大原子變更規模反而會縮小:要讓變更中的每個檔案在提交前都是最新的、要跑完所有受影響的 presubmit 與測試,都變得越來越難。於是問題不再是「怎麼把巨大變更審好」,而是「怎麼讓巨大變更根本不必以巨大的形式存在」。
- 絕大多數 LSC 幾乎不改變功能:它們傾向是廣泛的文字更新——清理反模式、替換已棄用的函式庫功能、推動編譯器等底層基礎設施升級、把使用者從舊系統遷移到新系統。
- 它們由自動化工具產生:人力投入應與程式碼庫規模呈次線性關係——無論 repo 多大,產生所有必要變更所需的人力時間應大致相同。經驗法則:若變更需要超過 500 次編輯,學會用自動化工具通常比手動更划算。
- 所有權(ownership)決定誰要核准:一顆小 CL 只要一位 Reviewer;一筆跨越數百個目錄的 LSC,會撞上數十個不同團隊的擁有者,「找誰審、誰有否決權」本身就成了要解的問題。
NOTE
LSC 的定義往往由儲存庫拓撲決定。若組織用的是分散式或聯合式儲存庫(federated repositories),跨儲存庫的原子變更在技術上可能根本不可行——這時即使變更規模不大,也天生是一筆 LSC。
⚖️ 巨大變更為何無法走常規審查——四道障礙
一筆理想的邏輯變更本該打包成單一原子提交,獨立地被測試、審查、提交。以下四道障礙解釋了為什麼在規模化下這條路走不通,也解釋了為什麼答案是「拆片」。
① 技術限制 + 合併衝突:原子提交可能根本不可能
- 大多數 VCS 的操作成本與變更規模呈線性:處理數十個檔案的提交毫無問題,但缺乏記憶體或算力去一次原子提交數千個檔案;集中式 VCS 在處理期間甚至可能阻塞其他寫入者。
- 變更越大,撞上合併衝突的機率越高。在 Google 這種全球公司,永遠有人正在改動 repo——沒辦法「趁週末沒人開發時偷偷提交觸及所有檔案的變更」。
- 把大型變更拆成較小的獨立區塊(chunk),就能繞過這兩道限制;代價是執行過程更複雜。
// 想像一個要在 50 萬處把 scoped_ptr 換成 std::unique_ptr 的變更。
// ❌ 一顆巨大原子 CL:VCS 吞不下、幾乎必然合併衝突、沒人審得完。
// ✅ 拆成上千個各自獨立、可原子提交的分片(shard),逐片跑管線。② 測試與審查:巨大 diff 讓「找根因」和「審得完」都失效
- Google 的 CI 不只跑直接受影響的測試,還會遞移地跑所有依賴被改檔案的測試。在 25 個檔案的變更裡找到失敗根因很直接;在 10,000 個檔案的變更裡找一個,像大海撈針。
- 小型獨立的變更,影響的測試集較小、失敗更好診斷——這正是拆片的核心動機之一。取捨是相同測試會被多次重跑,但工程師追蹤失敗的時間遠比多跑測試的算力昂貴,Google 有意識地選了後者。
- 所有變更提交前都需審查,這條政策同樣適用於 LSC。但手動審一顆大型提交既繁瑣又易錯。把 LSC 拆分為獨立分片(shard),才讓審查重新變得可行。
③ 由誰負責:基礎設施團隊,而非「叫大家自己遷移」
為什麼不直接發布新 API,然後要求所有人自行遷移?因為那無法擴展,理由有三:
- 領域知識集中化:基礎設施團隊最懂底層系統,也最有能力備好處置手冊(playbook)快速修錯;讓每個消費端團隊各自重學一遍,全局極度低效。
- 避免無資金支持的強制命令(unfunded mandate):好處分散在整個組織、個別團隊缺乏動機主動遷移。集中遷移並統一核算成本,幾乎總是更快更便宜。
- 激勵機制對齊:由「有切身利益移除舊系統」的團隊來負責,才能確保遷移真的被做完——自發性遷移(organic migration)很少能完全成功。
④ LSC 的四階段流程
有了基礎設施後,LSC 大致分四個階段(邊界模糊):
- 授權(Authorization):作者填一份簡短文件,說明變更原因、預估影響範圍(會拆出多少分片)、FAQ 與變更描述,並向被重構 API 的擁有者取得「領域審查」。提案交給約十二人的監督委員會——一群熟悉各語言細節的資深工程師。委員會的目標不是禁止 LSC,而是幫作者產出最高品質的變更,並作為衝突的升級裁決機構。真正被徹底拒絕的變更很少,只有危險的(如把所有
NULL轉成nullptr)或極低價值的(如英式↔美式拼寫互換)。 - 變更產生(Change Creation):盡可能自動化地產生實際編輯,好在使用者回退到舊用法、或發生文字合併衝突時能重新產生。產出的變更應盡量像人寫的——這是需要風格指南與自動格式化工具的原因。
- 分片與提交(Sharding & Submitting):見下一折頁。
- 清理(Cleanup):建立機制防止已被移除的符號被重新引入。Google 用 Tricorder 在 code review 時自動標記工程師引入新的已棄用物件。
⑤ 分片審查:Rosie、TAP、與「全域審批者」
分片與提交是 LSC 基礎設施最核心的一環。Google 的工具叫 Rosie:它把一筆巨大的綜合變更,依專案邊界與所有權規則拆成可原子提交的分片,讓每片獨立走「測試—寄送—審查—提交」管線。Rosie 會限制未完成分片數量、以較低優先級執行,避免壓垮共享的測試基礎設施。
每個分片經過:
- 測試:透過 TAP 遞移地跑所有依賴該片檔案的測試;絕大多數分片影響的測試少於一千個。
- 寄送審查者:Rosie 用一個理解 OWNERS 檔案的所有者偵測服務,依審查能力加權挑選 Reviewer;若某擁有者無回應,自動補上額外審查者。
- 審查:實務上,當地擁有者常過度信任 LSC 工程師、不會用審一般變更的嚴謹度來審。Google 因此只在需要上下文(而非僅需審批權限)時才發給當地擁有者,其餘交給全域審批者(global approver)——他們通常精通該語言/函式庫,不逐一審每筆變更,而是用獨立的模式比對工具自動批准符合預期的變更,只手動檢查因合併衝突或工具故障而異常的少數,讓流程能極好地擴展。
- 提交:通過各專案 precommit 檢查後正式提交。
// 全域審批者的心智模型(示意):
// 不是逐一讀每筆 diff,而是驗證「這筆分片是否符合預期模式」。
function autoApprove(shard, expectedPattern) {
if (matches(shard.diff, expectedPattern)) return "LGTM"; // 自動批准
return "manual-review"; // 只有異常分片才進人工
}透過 Rosie,Google 能每天有效地產生、測試、審查、提交數千個變更。
⑥ OWNERS 檔案:規模化下「誰能核准」的結構
常規審查裡,一筆變更要提交需要三種面向的核准:同儕 LGTM(正確性與可理解性)、Code Owner 核准(適不適合進這塊 codebase)、以及 Readability 核准(符不符合語言風格)。分片審查改寫的,主要是中間那一項的「找誰」機制。
Google 的 codebase 是階層式目錄結構,每個目錄可放一個 OWNERS 檔案,列出對該目錄及其子目錄有 stewardship 的人。運作方式:
- 關係是階層累加的:一個檔案的 Owner,通常是其所有上層目錄 OWNERS 成員的聯集。
- OWNERS 檔案可引用其他 OWNERS 或外部存取控制清單,但最終都解析為個別人員名單。
- 根目錄的 OWNERS 可作為大規模變更的「全域核准者」——這正是 LSC 得以繞過各地方團隊、把審查導向單一專家的制度基礎。
- 它同時充當一種文件:人和工具都能藉由往上走訪目錄樹來找到負責人(Rosie 的所有者偵測服務就靠它)。
- 新專案不需向中央註冊,一個新的 OWNERS 檔案就夠了。
IMPORTANT
許多首次做 LSC 的作者,直覺認為「當地專案擁有者應該審每一樣東西」。但對大多數機械式 LSC,讓單一全域審批者理解變更性質、建立自動化審查,遠比騷擾數十個團隊更經濟。委員會最常見的調整之一,就是把 LSC 的所有 code review 導向這位全域審批者。
WARNING
沒有防止**回退(backsliding)**的機制,LSC 的成果會隨時間被侵蝕——工程師會無意間重新引入已被移除的模式,讓辛苦的遷移功虧一簣。清理階段的 Tricorder 標記正是為此而存在。
🔑 Takeaways
- LSC = 一組邏輯相關、卻無法以單一原子單元提交的變更;成因是檔案太多、必然合併衝突、或分散式/聯合式 repo 使跨庫原子提交不可行。
- 常規「小 CL + 一位 Reviewer」的地基在此崩解:規模越大,最大可行原子變更反而越小,測試根因越難找、審查越不可能。
- 解法不是更用力審巨大 CL,而是拆片:Rosie 依專案邊界與所有權規則把主變更拆成可原子提交的分片,逐片跑測試—寄送—審查—提交。
- 由基礎設施團隊集中負責(領域知識、避免無資金強制命令、激勵對齊),並經約十二人的輕量級監督委員會授權。
- OWNERS 檔案用階層累加的方式界定誰能核准;根 OWNERS = 全域審批者,讓 LSC 的審查導向單一專家、以模式比對自動批准符合預期的分片,只人工處理異常。
- 清理階段用工具(Tricorder)防止回退,否則遷移成果會被逐漸侵蝕。
No notes yet — jot your takeaways or Q&A here.