浏览器稳定性提升之路:线上崩溃率优化中的 Return 与 CHECK 之争

一、前言

在大型 C++ 工程(例如 Chrome 浏览器内核)中,开发者经常会遇到这样的选择:
到底应该在关键点使用 CHECK 直接崩溃,还是使用 returnLOG 记录错误然后继续执行?

这看似只是一个代码风格问题,实则背后牵扯到 线上崩溃率、稳定性、用户体验、以及问题定位效率

本文结合浏览器内核的真实代码案例,从源码设计出发,分析 CHECKreturn 的差异,探讨它们在 稳定性优化线上质量保障 中的取舍,并给出一些落地的优化实践经验。


二、背景知识:什么是 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; } 

那么结果可能是:

  1. 浏览器启动后黑屏、空白 UI,但进程没崩溃。

  2. 用户上报 "浏览器打不开界面",但 crash dump 无法收集到。

  3. 问题变成 用户可见 bug ,而不是 研发可复现的崩溃

这对于稳定性指标(crash 率下降),看似有好处,但实际上可能导致 用户体验更差,研发也更难排查问题。


四、崩溃率 vs 稳定性:Return 和 CHECK 的权衡

在工程实践中,我们必须明确:

  • 崩溃率降低 ≠ 稳定性提升

  • 关键在于 是否能继续安全执行

我们来对比:

场景 使用 CHECK 使用 return
必要资源缺失(如 ICU 数据、核心 UI XML) 立即崩溃,易于定位问题 可能黑屏、功能残缺,用户可见异常
可选功能缺失(如某个实验性模块文件) 不建议 CHECK,影响整体稳定性 return 更合理,降级执行
开发/调试阶段 CHECK 更利于暴露问题 return 可能掩盖 bug
线上用户体验 崩溃率升高,定位快 崩溃率降低,但潜在 bug 难发现

所以,CHECKreturn 的取舍,本质是 在开发效率和用户体验之间做平衡


五、实战案例: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 的取舍标准

结合浏览器内核开发经验,我总结了一套实践标准:

  1. 核心依赖缺失(必须)

    • 比如 ICU、核心 DLL、主进程资源

    • 必须 CHECK,否则继续执行毫无意义

  2. 关键路径但可恢复(尝试修复后 CHECK)

    • 比如 UI skin、配置文件

    • 可以先尝试 fallback/修复,最后再 CHECK

  3. 非关键路径(直接 return)

    • 比如实验性模块、日志收集、辅助功能

    • return 避免线上崩溃率增加

  4. Debug 阶段优先 CHECK,Release 阶段视情况降级

    • 开发时暴露问题

    • 发布时保障用户体验


七、稳定性优化的工程思考

7.1 崩溃率指标要结合 "可用性"

  • 单纯追求 crash 率下降 并不科学

  • 更重要的是用户是否能 完成主要功能

  • 如果 return 导致浏览器无法启动,虽然 crash 率下降,但用户留存反而下降

7.2 崩溃收敛要和监控系统结合

  • 通过 CrashpadBreakpad 收集崩溃堆栈

  • 配合日志系统判断是 偶发 bug 还是 环境问题

  • 对于外部文件缺失类问题,可以考虑 自愈机制(repair)

7.3 CHECK 的位置很关键

  • CHECK 应该放在 错误首次被发现的位置,而不是后面调用处

  • 否则会让堆栈看起来和实际问题无关,增加排查难度


八、总结

本文通过分析 CHECKreturn 的区别,结合浏览器内核中的 UI 资源加载ICU 初始化 两个案例,说明了它们在 线上崩溃率优化 中的不同作用:

  • CHECK:快速失败,便于定位核心问题,但可能提高 crash 率

  • return:避免崩溃,提升稳定性,但可能掩盖严重问题

最佳实践是:

  • 核心依赖:必须 CHECK

  • 可修复:修复失败后 CHECK

  • 非关键路径:return

崩溃率优化的目标不只是数字下降,而是 既能快速定位问题,又能保障用户体验

这正是工程实践中的智慧所在。

相关推荐
dragoooon342 小时前
[优选算法专题二——NO.16最小覆盖子串]
c++·算法·leetcode·学习方法
汉克老师2 小时前
第十四届蓝桥杯青少组C++选拔赛[2023.1.15]第二部分编程题(4 、移动石子)
c++·算法·蓝桥杯·蓝桥杯c++·c++蓝桥杯
qq_433554543 小时前
C++ Dijkstra堆优化算法
开发语言·c++·算法
UrSpecial4 小时前
Linux线程同步与互斥
linux·开发语言·c++
楼田莉子4 小时前
C++动态规划算法:斐波那契数列模型
c++·学习·算法·动态规划
Madison-No75 小时前
【C++】日期类运算符重载实战
c++·算法
椰子今天很可爱5 小时前
线程分离和线程同步互斥
linux·c++
小柯J桑_5 小时前
Linux:线程控制
linux·c++·算法
1白天的黑夜15 小时前
栈-1047.删除字符串中的所有相邻重复项-力扣(LeetCode)
c++·leetcode·