一个内置 5.5 MB 运行时的浏览器 Python Playground 开始在慢速连接的用户端发生静默失败。罪魁祸首是误用的 Network Information API 以及一个错误分类错误的错误分组仪表板。这个 Bug 隐藏了数周,浪费了开发人员的时间,并导致一部分用户无法运行代码。

问题是如何浮现的

该 Playground 的错误追踪器弹出了一个显眼的单一消息:“undefined is not an object.” 标题暗示这是一个简单的 JavaScript 拼写错误,因此团队一直在追踪一条并不存在的代码路径。当他们检查原始元数据时,发现其中 89% 的事件实际上是网络超时。仪表板获取了到达的第一条错误,并用它来命名整个批次,从而掩盖了真实的失败类型。

教训 1 – 仪表板标题可能具有误导性

只有当聚合逻辑反映了每个事件的真实原因时,聚合事件的仪表板才会有所帮助。在这里,按位置而非按错误原因进行分组,营造了一种客户端 Bug 的假象。经验教训:永远不要仅根据仪表板的标题来修复问题。在分配资源之前,请提取底层事件样本并验证实际发生的情况。

教训 2 – 占位符值不是测量值

为了避免为慢速链路上的用户加载沉重的运行时,代码咨询了 Network Information API 并读取了 downlink 属性(该属性报告每秒兆比特数)。在首次访问时,Chrome 通常会返回一个占位符,而不是实际的测量值。逻辑将该占位符视为快速连接并跳过了优化,实际上阻碍了它本应帮助的用户。

将任何默认值或哨兵值视为“无数据”。占位符应该触发回退策略,而不是被解释为真实的速率读数。

教训 3 – 网络状况瞬息万变,因此单一快照是不可靠的

在解决 downlink 问题后,团队转向检查 effectiveType,它将连接分类为 “4g”、“3g” 等。快速的实验室测试通过了,但几分钟后重新运行相同的测试却失败了。移动连接是波动的;用户可能在前一秒还在快速的 4G 链路上,下一秒就掉到了较慢的 3G。仅在页面加载时检查连接无异于一场赌博。

正确的方法是订阅 Network Information 对象上的 change 事件,并对带宽的任何变化做出反应,而不是做出一次性的决定。

团队做了哪些改变

  • 两阶段下载 – 运行时现在从一个微小的 bootstrap 文件开始。如果识别到连接较慢,bootstrap 会以小块的形式获取剩余的运行时,从而降低完全中断的可能性。
  • 实时监控 – 代码不再进行单一的 downlink 读取,而是监听 change 事件并即时调整下载策略。
  • 稳定的源选择 – 此前,系统在下载过程中如果发现更快的端点会切换 CDNs。在慢速链路上,这会导致下载从零开始,从而加剧问题。新逻辑在下载期间锁定数据源。
  • 延迟缓存写入 – 以前在应用可用前运行的繁重缓存操作,现在被推迟到运行时启动之后,从而为关键下载释放带宽。

更广泛的影响

对于构建基于 Web 工具的开发人员来说,网络的可变性是一个首要关注的问题。慢速链路上的静默失败会挫伤用户体验并扭曲遥测数据,导致团队走上错误的调试路径。在这种情况下,对数据的误读导致了数周徒劳无功的调查。

下一步需要注意什么

总结: 当数据看起来过于“干净”时,它可能只是个占位符;当仪表板标题指向单一 Bug 时,请深入挖掘;当你基于一次性的网络读取做出决定时,你是在赌一个移动的目标。适应这些现实情况,可以将静默失败转变为可预测、可恢复的事件。