一、前言
在大型 C++ 工程(例如 Chrome 瀏覽器內核)中,開發者經常會遇到這樣的選擇:
到底應該在關鍵點使用 CHECK
直接崩潰,還是使用 return
、LOG
記錄錯誤然后繼續執行?
這看似只是一個代碼風格問題,實則背后牽扯到 線上崩潰率、穩定性、用戶體驗、以及問題定位效率。
本文結合瀏覽器內核的真實代碼案例,從源碼設計出發,分析 CHECK
與 return
的差異,探討它們在 穩定性優化 和 線上質量保障 中的取舍,并給出一些落地的優化實踐經驗。
二、背景知識:什么是 CHECK
在 Chromium 基礎庫(base/check.h
)中,CHECK
是一個類似于斷言的宏,用法非常常見:
CHECK(pointer); CHECK_EQ(size, expected_size);
和 DCHECK
不同的是:
CHECK
永遠生效(無論 Debug 還是 Release),失敗時會直接觸發 致命崩潰(調用ImmediateCrash()
)。DCHECK
只在 Debug 或啟用了--enable-dcheck
的模式下才有效,Release 默認不生效。
也就是說:
CHECK = 防御型編程 + 線上崩潰
DCHECK = 調試模式下的保護網
因此,使用 CHECK
的地方,意味著開發者明確認為 “如果條件不滿足,繼續運行程序只會帶來更大風險,還不如直接崩潰并上報”。
三、典型代碼案例分析
以瀏覽器 UI 初始化的一個場景為例:
ViewBuilder buildui; const ui::ThemeProvider* tp = GetThemeProvider(); if (tp) { base::RefCountedMemory* memory = tp->GetRawData("browser.xml", ui::k100Percent); CHECK(memory); if (memory) { buildui.BuilderUiUtf8((const char*)(memory->front()), this); } }
3.1 為什么這里要用 CHECK?
browser.xml
是瀏覽器 UI 布局的核心資源,缺失后界面幾乎無法正常渲染。如果
memory == nullptr
,繼續運行只會導致后續邏輯 不可控,甚至觸發 更隱蔽的崩潰。與其后面某處 隨機崩潰,不如在這里立刻崩潰并生成明確的堆棧。
這就是 “快速失敗(fail fast)” 的思想。
3.2 如果換成 return,會發生什么?
假設我們改成:
if (!memory) { LOG(ERROR) << "browser.xml not found!"; return; }
那么結果可能是:
瀏覽器啟動后黑屏、空白 UI,但進程沒崩潰。
用戶上報 “瀏覽器打不開界面”,但 crash dump 無法收集到。
問題變成 用戶可見 bug,而不是 研發可復現的崩潰。
這對于穩定性指標(crash 率下降),看似有好處,但實際上可能導致 用戶體驗更差,研發也更難排查問題。
四、崩潰率 vs 穩定性:Return 和 CHECK 的權衡
在工程實踐中,我們必須明確:
崩潰率降低 ≠ 穩定性提升
關鍵在于 是否能繼續安全執行
我們來對比:
場景 | 使用 CHECK | 使用 return |
---|---|---|
必要資源缺失(如 ICU 數據、核心 UI XML) | 立即崩潰,易于定位問題 | 可能黑屏、功能殘缺,用戶可見異常 |
可選功能缺失(如某個實驗性模塊文件) | 不建議 CHECK,影響整體穩定性 | return 更合理,降級執行 |
開發/調試階段 | CHECK 更利于暴露問題 | return 可能掩蓋 bug |
線上用戶體驗 | 崩潰率升高,定位快 | 崩潰率降低,但潛在 bug 難發現 |
所以,CHECK
和 return
的取舍,本質是 在開發效率和用戶體驗之間做平衡。
五、實戰案例:ICU 數據初始化
再看一個瀏覽器啟動過程的例子:
bool InitializeICUFromDataFile() { LazyInitIcuDataFile(); bool result = InitializeICUWithFileDescriptorInternal(g_icudtl_pf, g_icudtl_region); // 省略修復邏輯... #if !BUILDFLAG(IS_CHROMEOS) CHECK(result); #endif return result; }
5.1 為什么要 CHECK(result)
ICU(International Components for Unicode)庫是瀏覽器處理多語言文本的底層依賴:
URL 編解碼
字符串歸一化
字體渲染
如果 result == false
,意味著 ICU 數據加載失敗,瀏覽器將 無法正常顯示文字,后續所有邏輯都是“廢的”。
這種情況不可能通過 return
優雅降級,因此必須 硬崩潰。
5.2 線上優化的方式
有些廠商會加 修復邏輯:
如果文件損壞,嘗試復制備份文件或重新下載。
只有在修復仍失敗時,才觸發
CHECK
。
這樣既保留了問題定位能力,也避免了因文件損壞導致的大規模崩潰。
六、Return vs CHECK 的取舍標準
結合瀏覽器內核開發經驗,我總結了一套實踐標準:
核心依賴缺失(必須)
比如 ICU、核心 DLL、主進程資源
必須
CHECK
,否則繼續執行毫無意義
關鍵路徑但可恢復(嘗試修復后 CHECK)
比如 UI skin、配置文件
可以先嘗試 fallback/修復,最后再
CHECK
非關鍵路徑(直接 return)
比如實驗性模塊、日志收集、輔助功能
用
return
避免線上崩潰率增加
Debug 階段優先 CHECK,Release 階段視情況降級
開發時暴露問題
發布時保障用戶體驗
七、穩定性優化的工程思考
7.1 崩潰率指標要結合 “可用性”
單純追求 crash 率下降 并不科學
更重要的是用戶是否能 完成主要功能
如果 return 導致瀏覽器無法啟動,雖然 crash 率下降,但用戶留存反而下降
7.2 崩潰收斂要和監控系統結合
通過 Crashpad 或 Breakpad 收集崩潰堆棧
配合日志系統判斷是 偶發 bug 還是 環境問題
對于外部文件缺失類問題,可以考慮 自愈機制(repair)
7.3 CHECK 的位置很關鍵
CHECK 應該放在 錯誤首次被發現的位置,而不是后面調用處
否則會讓堆棧看起來和實際問題無關,增加排查難度
八、總結
本文通過分析 CHECK
和 return
的區別,結合瀏覽器內核中的 UI 資源加載 和 ICU 初始化 兩個案例,說明了它們在 線上崩潰率優化 中的不同作用:
CHECK:快速失敗,便于定位核心問題,但可能提高 crash 率
return:避免崩潰,提升穩定性,但可能掩蓋嚴重問題
最佳實踐是:
核心依賴:必須 CHECK
可修復:修復失敗后 CHECK
非關鍵路徑:return
崩潰率優化的目標不只是數字下降,而是 既能快速定位問題,又能保障用戶體驗。
這正是工程實踐中的智慧所在。