W03-高阶问题排查心得体会

我们有一个困扰了近 3 年的 Webpack 幽灵问题,上周有了重大进展。借机想总结一下前端里古怪艰深问题的特点,以及处理这类难搞问题的心得体会。


古怪艰深的问题可以按照代码有没有问题切为两类

  • 第一类是代码有很隐蔽的设计缺陷。 这类问题源于代码设计上潜伏的逻辑缺陷,往往在特殊条件下才暴露出来。比如代码对异步、并发或状态假设不当,在低性能、特定执行顺序才会暴露。实际遇到最多的就是时序竞态问题,后边会提到具体例子。

  • 第二类往往根因不在代码本身,需要放在一个充满不确定性的系统里寻找真相。 这个恰恰是前端问题排查的高阶所在,不光要写对代码,还能理解代码将运行在什么样的世界里。这个世界可以称之为“不确定的运行时”,其不确定的程度经常会让观测和复现都充满挑战。


不确定的运行时

可以理解为是一种技术假设,假设除了代码,都是不可靠的。目的是做好防御工作。

从代码被 build 开始,就可以算作不确定运行时的起点。

Build(人为选型) → Container(强定制) → OS & Hardware(高度碎片化)→ User & Physical Context(不可控)。

1. Build:基建与构建产物差异

Polyfill、运行时库、构建配置版本不一致,可能导致新语法在老环境直接报错,或重复/错误注入引发异常。

前段时间月付遇到的问题,从 Webpack 切换到 Rspack 构建后,在低端设备上出现了 Promise/thenable 执行顺序异常的情况。构建工具本身可能并没有 bug,而构建产物的 runtime 行为差异会触发问题。因为 Webpack 有一套复杂的 runtime,用 Promise/thenable 协调模块加载顺序。

商家收银台在快驴端发现过 set cookie 失效的问题,是客户端内不同线程之间的执行时序导致的。

2. Container:宿主 App / WebView 内核差异

不同 WebView 在 JS 执行、CSS 渲染、安全与接口行为上不完全一致,壳层还可能注入或限制 API,导致同一 H5 在不同容器中表现不一致。

很典型的像 iOS 的拦截器,容器对网络请求、路由跳转等设有拦截机制,错误或重复的拦截实现就会造成问题。比如在慧销遇到过 set cookie 失败导致 token 失效。在外卖商家端踩过离线化的坑。

在外卖商家端,曾经还发现过会注入一个hook,如果没有 knb.js 会自主引入1.9.1版本,导致在安卓偶发订阅 appear 成功,但是没有执行回调。

3. OS & Hardware:OS 与设备碎片化

不同厂商 ROM、机型、分辨率与系统版本差异,可能带来特定机型崩溃、布局异常或性能问题。

在外卖商家 iOS 端出现过单用户大量重复调用 yoda 问题,在第二年解决另一个问题的时候,发现可以并案处理,最终定位是低端机下可用内存不足导致。当内存告警后,由于保活机制的存在,WebView 持续多次调用 reload 方法,进而出现单用户短时间内频繁进入页面。

4. User & Physical Context:用户真实运行环境差异

用户千差万别的使用方式和所处环境,比如弱网、低电量、存储空间不足等,都会形成诸多不可控变量,造成边缘情况下的异常现象。这些问题由于缺乏可重复性,往往给排查带来很大挑战,需要结合埋点日志和用户操作记录才能分析出线索。

在三方支付场景遇到过微信双开导致支付失败的问题。部分用户会使用系统或第三方提供的双开功能,在一部手机上运行同一 App 的两个实例。这种机制会破坏应用单例假设或 IPC 链路,会导致意想不到的问题。


我栽过很多坑,谈一些心得体会,并分享两个案例

一、破我执,不是所有的艰深问题都需要立刻“完美解释”。 如果已经引起事故,第一优先级是释放周围对你的压力。先找一个可控的 workaround 把风险封住,让大家的关注点快速转移出去。绝大多数人并不在意那个“终极根因”,先稳住局势,才有资格慢慢追真相。

二、有恒心,勇于直面挑战,才能练就技术深度。 很多问题是在长期等待中突然被解决的。当走入死穴后,把问题放在那里,等机会或者灵感,缺什么补什么,但不要放弃。


案例一:Webpack 构建产物不稳定问题

开头提到的 Webpack 构建产物不稳定问题,就是一个用了近三年时间、由浅入深不断逼近真相的过程。

  1. 问题现象 问题现象是 Webpack 构建产物有时会不可用,线上偶发白屏。最初接触这个问题,发现找不到根因,但由此优化了一个流水线的发包机制问题,保证了线上白名单验证和全量发布的是同一个包。

  2. 配置分析 后来洪泽专门做了一次 Webpack 配置分析,定位到三方依赖里 ESM 与 CJS 混用导致构建异常,针对配置做了优化,但依旧会存在异常产物。

  3. 社会实验与折中 去年剑钻在基本功传帮带上,对构建异常做了一场主题分享。带着大家现场复现了构建产物不稳定的问题,通过这次社会实验,提升了团队对构建的认知深度。同时找到了一个更有效的配置开关,如果把异步打包构建关掉,异常产物就不再出现了,代价是构建速度。

  4. 最终解决 上周,剑钻提供了一个新的解决方案。找到了构建物中 n.n() 包装这个重要特征,通过检查构建产物内的 CJS 压缩特征是否符合预期,保证线上打包的正确性。可以完全封堵质量风险的漏洞。

至此这个问题我觉得就可以了结了,再有余力的话,就是能帮 Webpack 彻底解决异步构建 CJS 模块的问题,提个 PR 过去。


案例二:Alpha项目营销重构

问题可以被解决,但不是所有问题都能得到圆满的技术解释。应对问题的过程更重要。

Alpha项目曾经在一次营销重构中,引起开户转化率下滑。持续了两个多月才解决,暴露了一系列问题。最终也没有找到技术根因,也没有条件再去复现了,永远不可能有圆满的技术解释了。

但在这个过程,我们对质量有了深切的认识。那段时间补齐了业务指标的实时观测能力,AB 实验的能力,线下质量的常态化运营,以及对回滚策略和决策的深入反思。这个事件也直接促成次年Alpha项目的整体重构,为团队梳理了很好的可维护性标杆。

最后更新于