靜態分析#
靜態分析(Static Analysis) 是指在不執行程式的前提下,透過分析原始碼來發現潛在問題——包括 bug、反模式(antipattern)以及其他可在編譯期或提交前診斷的缺陷。「靜態」特指分析原始碼本身,相對的概念是「動態分析(Dynamic Analysis)」,即在程式執行階段進行檢測。靜態分析能在程式碼進入生產環境之前就發現問題,例如:溢位的常數運算式、從未被執行的測試、以及執行時會導致崩潰的無效格式字串。
在 Google,靜態分析的用途遠不止於抓 bug。團隊透過靜態分析來:
- 編纂最佳實踐(codify best practices),確保命名慣例、程式碼風格一致
- 保持程式碼更新,標記已棄用的 API(deprecated API)使用
- 預防與減少技術債(technical debt),指出可簡化的等效表達式
- 輔助 API 遷移,防止在大規模遷移過程中出現退化(backsliding)(詳見 Chapter 22)
研究也顯示,靜態分析檢查能教育開發者,實際上防止反模式進入程式碼庫。靜態分析同時是 API 棄用流程中不可或缺的工具,能在程式碼庫遷移至新 API 的過程中防止退化。
本章將探討有效靜態分析的特質、Google 從中學到的教訓,以及如何在工具與流程中實踐這些最佳實踐。
有效靜態分析的特性#
雖然靜態分析的學術研究已有數十年歷史,但近年來才真正開始關注可擴展性(scalability) 與可用性(usability) 這兩個工程面向。
可擴展性(Scalability)#
分析工具必須在不拖慢開發流程的前提下產出結果。Google 的靜態分析工具面對的是數十億行規模的程式碼庫,為此採用了以下策略:
- 可分片且增量式(shardable and incremental):不分析整個大型專案,而是聚焦於待提交變更所影響的檔案
- 通常只顯示被編輯的檔案或行數的分析結果
- 開放全公司貢獻:廣泛徵集分析器(analyzer)貢獻,擴大分析的數量與多樣性
- 直接將結果呈現給相關工程師,避免瓶頸
可用性(Usability)#
可用性的核心在於成本效益權衡(cost-benefit trade-off)。修復靜態分析警告本身也有成本——甚至可能引入新 bug。例如,修復「死碼(dead code)」警告時若補上呼叫,可能讓未經測試的程式碼意外執行。
因此 Google 採取的原則包括:
- 聚焦新引入的警告:對既有且正常運作的程式碼,通常只在高優先級(如安全性問題)時才標記
- 能自動修復的就自動修復:降低開發者的處理成本
- 只呈現對程式碼品質有實質負面影響的報告,避免開發者浪費時間在無關結果上
- 與開發者工作流程深度整合:將分析融入既有工具鏈,由專責團隊同步更新工具與程式碼
讓靜態分析有效運作的三大關鍵教訓#
教訓一:聚焦開發者的幸福感(Focus on Developer Happiness)#
Google 持續追蹤分析工具的表現——如果不量測,就無法修正問題。Google 只部署低誤報率(low false-positive rate) 的工具,並主動徵求開發者的即時回饋。這個在分析工具使用者與開發者之間的回饋循環,形成了一個良性循環(virtuous cycle),持續建立用戶信任並改善工具品質。用戶信任對靜態分析工具的成功至關重要。
在靜態分析中,假陰性(false negative) 是指分析工具未能發現其設計要找的問題;假陽性(false positive) 則是工具錯誤地標記正常程式碼。傳統研究多聚焦於降低假陰性率,但實務上,低假陽性率才是開發者願意使用工具的關鍵——沒有人想在數百個錯誤報告中翻找少數真正的問題。
Google 定義了有效誤報率(effective false-positive rate) 的概念:如果開發者看到警告後沒有採取任何正向行動,即視為有效誤報。即使分析技術上正確,若訊息令人困惑或議題不重要而被忽略,效果等同於誤報。反之,即使分析判斷有誤,但開發者樂於修正以改善可讀性,則不算有效誤報。
例如,Java 中有一項檢查會標記開發者對 hash table 呼叫 contains(等同 containsValue)而非 containsKey 的情境——即便開發者確實想檢查值,改用 containsValue 語意更清晰。
教訓二:將靜態分析融入核心開發工作流程(Make Static Analysis Part of the Core Developer Workflow)#
Google 將靜態分析整合到 Code Review 工具(Critique) 中。由於幾乎所有程式碼在提交前都經過審查,開發者本身已處於「修改模式」,此時接受靜態分析建議的阻力最小。
Code Review 整合的優勢:
- 開發者送出審查後通常會進行上下文切換(context switch),等待審查者回覆——這段時間正好用來執行分析
- 同儕壓力(peer pressure):審查者會要求修復靜態分析警告
- 靜態分析自動標出常見問題,節省審查者時間,幫助 Code Review 流程規模化
教訓三:賦權使用者貢獻(Empower Users to Contribute)#
Google 鼓勵全公司的領域專家撰寫新的分析器或個別檢查規則。Google 著重建立一個容易接入的靜態分析生態系統,而非僅整合少數現有工具。例如:
- 熟悉特定設定檔格式的專家可以撰寫對應的分析器,檢查那些檔案的屬性
- 發現 bug 的開發者可以撰寫檢查來防止同類 bug 在程式碼庫中其他地方重現
- Google 開發了簡單的 API 供全公司工程師使用——不僅限於分析或語言專家。例如 Refaster 讓工程師只需撰寫轉換前後的程式碼片段即可定義分析器
Tricorder:Google 的靜態分析平台#
Tricorder 是 Google 靜態分析的核心平台,誕生於多次將靜態分析整合進開發流程的失敗嘗試之後。關鍵差異在於:Tricorder 堅持只向使用者交付有價值的結果。
Tricorder 整合於 Code Review 工具 Critique,警告以灰色評論框的形式顯示在 diff 檢視中。

Figure 20.1: Critique's diff viewing, showing a static analysis warning from Tricorder
架構與規模#
Tricorder 採用微服務架構(microservices architecture):
- 系統向分析伺服器發送分析請求,附帶程式碼變更的 metadata
- 伺服器透過 FUSE 檔案系統讀取原始碼版本,並存取快取的建置輸入與輸出
- 各分析器獨立執行,結果寫入儲存層
- 最新結果顯示於 Critique;分析進行中會發送狀態更新
Tricorder 每天分析超過 50,000 個 Code Review 變更,經常達到每秒執行數個分析的速率。
新增檢查的四項準則#
Google 全公司的開發者皆可撰寫 Tricorder 分析器或貢獻個別檢查,但新檢查必須滿足:
- 可理解(Understandable):任何工程師都能看懂輸出
- 可操作且易於修復(Actionable and easy to fix):結果應包含修復指引
- 有效誤報率低於 10%:開發者至少 90% 的情況下認為指出了真正問題
- 對程式碼品質有顯著潛在影響:即使不影響正確性,開發者也應認真看待
Tricorder 支援超過 30 種語言、包含超過 100 個分析器(大部分由 Tricorder 團隊以外的人貢獻)。其中 7 個分析器本身就是外掛系統,擁有數百個額外檢查。整體有效誤報率低於 5%。
整合工具#
Tricorder 整合了多種靜態分析工具:
- Error Prone(Java)與 clang-tidy(C++):擴充編譯器以識別 AST 反模式。例如,對
long型別欄位f做雜湊運算(int)(f ^ (f >>> 32))時,若f實際為int,右移 32 位元是空操作(no-op),f會與自身 XOR 而失去作用。Google 啟用此檢查為編譯錯誤時,修復了 31 處此類 bug。 - Deleted Artifact Analyzer:當被其他非程式碼引用(如文件)參照的原始檔被刪除時發出警告
- IfThisThenThat:指定兩個不同檔案中必須同步修改的部分
- Chrome Finch Analyzer:針對 Chrome A/B 實驗設定檔,檢查審批、與其他實驗的交叉干擾等問題;會透過 RPC 查詢其他服務
- Binary Size Checker:當變更顯著影響二進位檔大小時發出警告
幾乎所有分析器都是過程內分析(intraprocedural),即基於單一函式內的程式碼進行分析。組合式或增量式的跨過程分析(interprocedural analysis)技術上可行,但需要額外基礎設施投資。
整合回饋機制#
Tricorder 在每個分析結果旁顯示 “Not useful” 按鈕,點擊後可直接向分析器作者提交 bug,並預填分析結果資訊。審查者也可點擊 “Please fix” 要求變更作者修復問題。
Tricorder 團隊追蹤各分析器的「Not useful」點擊率(特別是相對於「Please fix」的比率),若分析器未能改善問題並降低「Not useful」率,將被停用。
建立並調校這個回饋循環花了大量工夫,但回報豐厚——在建立明確的回饋管道之前,許多開發者會直接忽略他們不理解的分析結果。
有時候修正非常簡單——例如改善分析器輸出的訊息文字。曾有一個 Error Prone 檢查標記 Guava 的 printf 類函式(只接受
%s)參數過多的問題,但因訊息不清楚,每週收到「Not useful」回報。團隊將訊息改為明確說明「此函式只接受%s」後,回報立即停止。
建議修正(Suggested Fixes)#
Tricorder 檢查會盡可能提供自動修正(automated fix)。自動修正同時也是訊息不夠清楚時的額外說明來源,讓開發者能直接看到預期的修正方式。

Figure 20.2: View of an example static analysis fix in Critique
修正可直接在 Critique 中套用,或透過命令列工具批次套用於整個變更。雖然並非所有分析器都提供修正,但許多都有。Google 的原則是:風格問題應自動修復,例如格式化工具(formatter)自動重新排版原始碼。Google 為每種語言都有風格指南來規範格式問題——指出格式錯誤不應浪費人類審查者的時間。
數據顯示:審查者每天點擊「Please Fix」數千次,作者每天套用自動修正約 3,000 次,而「Not useful」點擊約 250 次/天。
專案層級客製化(Per-Project Customization)#
在建立用戶信任的基礎上,Tricorder 新增了為特定專案啟用「可選分析器」的功能。例如:
- Proto Best Practices Analyzer:標記可能破壞 Protocol Buffers 資料格式的變更——只有在專案存儲序列化資料時才需啟用
- 部分分析器從「可選」起步,根據回饋改善後,逐步升級為預設啟用。例如,有一個建議 Java 程式碼可讀性改善的分析器(通常不改變程式碼行為),起初使用者擔心太「吵」,但最終反而希望看到更多分析結果
Google 刻意選擇專案層級(project-level) 而非使用者層級(user-level) 的客製化。使用者層級客製化會導致團隊成員看到不一致的結果,甚至出現一人修 bug、另一人引入同類 bug 的情況。早期 Critique 曾提供使用者自訂功能,移除後反而發現了大量隱藏的 bug 與誤報問題(例如 C++ linter 錯誤地分析 Objective-C 檔案、HTML linter 誤報率過高等)。
預提交檢查(Presubmits)#
除了 Code Review,Google 還有預提交檢查(presubmit check) 機制可阻擋提交:
- 簡單的內建檢查:例如確保 commit message 不包含 “DO NOT SUBMIT”、測試檔案與程式碼檔案一併提交
- 團隊可指定必須通過的測試套件或無 Tricorder 特定類別問題
- 檢查程式碼格式是否正確
- 通常在送出審查時與提交時各執行一次
各團隊也可撰寫自訂預提交檢查,為自己的專案設定比公司整體更嚴格的標準。但團隊特定預提交可能增加大規模變更(LSC,見 Chapter 22)的難度,因此部分檢查對描述中包含 “CLEANUP=” 的變更會跳過。
編譯器整合(Compiler Integration)#
雖然以預提交阻擋提交已經很好,但在工作流程中更早通知開發者效果更佳。Google 盡可能將靜態分析推進到編譯器中。中斷建置(break the build)是無法忽視的警告,但並非所有分析都適合。適合整合進編譯器的條件是:
- 高度機械化且無有效誤報
- 可操作且易於修復(盡量包含自動修正建議)
- 只回報影響正確性的問題,而非風格或最佳實踐
此外,編譯器檢查必須足夠快速,不能拖慢建置速度。Error Prone 的 “ERROR” 等級檢查就是一個範例——這些檢查已全數在 Google 的 Java 編譯器中啟用,從根本上防止該類錯誤再次進入程式碼庫。
啟用新編譯器檢查的流程:
- 先透過叢集以 MapReduce 方式在整個程式碼庫上平行執行編譯器(如 clang、javac)與分析
- 分析必須產出修正以自動化清理
- 準備並測試一個涵蓋整個程式碼庫的變更,修復所有現有問題
- 提交變更後,開啟編譯器檢查,防止新問題被引入
- 建置中斷由持續整合(CI)系統在提交後捕捉,或由預提交檢查在提交前攔截
Google 的策略是不發出編譯器警告(compiler warning)。經驗顯示開發者會忽略警告。檢查要嘛設為錯誤(中斷建置),要嘛不顯示。Java 與 C++ 編譯器皆已設定為不顯示警告。Go 語言更極端——其他語言視為警告的項目(如未使用的變數或套件匯入)在 Go 中直接是錯誤。
編輯與瀏覽程式碼時的分析#
IDE 是另一個潛在整合點,但有限制:
- 分析需要極快速(理想情況下 < 100 ms,最多不超過 1 秒)
- 需確保同一分析在不同 IDE 中行為一致
- IDE 的流行度會起落,整合相對混亂
Code Review 相較 IDE 有特定優勢:分析可以考量整個變更的完整上下文(例如死碼分析在函式實作但尚未加入呼叫端時可能不準確),且作者若要忽略結果需說服審查者。
不過,雖然 Google 主要聚焦於顯示新引入的警告,對於部分分析(特別是安全性分析),開發者確實需要在瀏覽程式碼時查看整個程式碼庫的分析結果。特定安全團隊需要所有問題實例的全局視圖,開發者在規劃清理工作時也有類似需求。換言之,在程式碼瀏覽時顯示結果有時是正確的選擇。
結論#
靜態分析是改善程式碼庫、早期發現 bug、讓昂貴流程(如人工審查與測試)專注於無法機械驗證之問題的利器。透過改善可擴展性與可用性,Google 已將靜態分析打造為軟體開發中的有效組成部分。
TL;DRs#
- 聚焦開發者幸福感:投入大量心力建立分析使用者與作者之間的回饋機制,積極調校分析以降低誤報數量
- 讓靜態分析成為核心工作流程的一部分:主要整合點是 Code Review(提供修正、納入審查者),同時也整合至編譯器檢查、提交閘門、IDE 與程式碼瀏覽
- 賦權使用者貢獻:藉由領域專家的知識來擴展分析工具與平台的建置與維護規模,開發者持續新增分析與檢查,讓工作更輕鬆、程式碼庫更好