云隙随笔

学习笔记 | 记录一个自己没有修改代码却出现bug的场景

发布于 # 学习笔记

bug的问题定位在开发中是尤为重要的一环,尤其是当项目庞大架构复杂之后,能否剪枝快速定位问题场景并针对性调试与修复会很大程度地影响开发效率与个人心情。之前在twitter上看过一位微软开发者如何使用条理清晰的方法论快速定位并修复问题,令我颇为佩服。另外也不时能看到有博主提出bug的场景,然后底下评论区的用户猜测是什么原因导致的bug,饶有海龟汤和本格推理的意味,也深感自己在这方面的不足。这篇文章仅记录一个非常愚蠢的bug,但由于个人的经验不足花费了大量时间,也算自我检讨与反思,共勉。

问题描述

周四下班前,一位同事告诉我,春节前我负责开发的一个功能出现了问题,无法使用。这个功能基于plate.js的 drag-and-drop 库(plate-dnd)进行二次开发,实现了跨编辑器的组件拖拽功能。而plate.js 这个库是基于slateReact-DnD的封装库,同时提供了大量插件与频繁的版本更新,以至于不容易定位源码中的具体问题。

我开发的这个功能属于一个独立的package,是monorepo架构的一部分,通过yarn workspaces实现。另外,由于项目由多人同时开发,并不清楚其它同事是否有什么部分影响了我现有的代码。同时由于项目没有单元测试,并不能获知是什么时候导致的bug。

目前能明确确认的信息是:在春节前我的代码正常运行,春节后在我的代码没有做任何修改的情况下,程序出现了意外的bug。不但我二次修改的代码无法正常运行,这个包原有的dnd功能也无法使用。

排查流程

步骤1

在遇到上面的场景后,我的第一反应就是检查近几天的代码提交,看看是否有其他人的代码修改影响了我原有功能。在检查过每个提交修改的文件和依赖后,没有什么特殊发现。

步骤2

在第一步无果后,我在本地运行了春节前我个人本地开发的环境,发现代码可以正常运行,并没有出现bug,说明bug是出现在春节之后。

步骤3

为了明确到底是什么原因导致了bug,我fork了最新的分支,运行后通过控制台发现其中一个本应该有值的变量现在变成了undefined。至此,我认为问题是出在外部的变量传递有问题,导致了我封装的包无法获取正确的值。因此将关注点转向了调用这个包的父级文件的代码。

花费大量时间排查后没有发现问题。

步骤4

因为现在这个包原有的功能无法使用,我尝试将这个包迁移回原有的版本而非我二次开发的版本,发现可以正常使用。

至此,问题似乎又定位回了plate-dnd自身,因此我尝试直接使用plate-dnd 源码,将源码移动到/src目录中并转译,发现居然也无法正常工作。

也就是说,直接通过yarn下载的编译后版本可以正常运行,而从源码自行build的版本无法运行。

步骤5

至此,我人已经晕了。问题基本定位为工程上导致的bug,而非我个人代码的问题。因此关注点放在了yarn, node_modules, cache, package.json, workspace 这些工程化细节上。

另外,我尝试回滚到2周前的代码并重新build,发现也出现了bug(和步骤2不同的地方在于我这一步重新build了)。

结论

现在信息已经全部给出,我从事后来看如果有经验或者敏锐的人可能在一开始看到问题描述就能知道是什么原因,而自己在步骤5之后都想了比较长时间才明白,实属惭愧。

结论就是: 没有锁版本。

直到很晚我才发现这个项目居然不知道什么时候把yarn.lock 放到了.gitignore(因为之前很长一段时间内都有,所以我完全没有考虑版本变动的问题)。而其他同事移除lock的原因是,一个需要付费的动画库gsap需要没有yarn.lock的时候才能正常安装。

另外,在导入plate相关的几十个包的时候,都是统一使用了"plate": "^30.1.2" 这种锁定主要版本的形式。原则上来说不是主要版本也不会出现破坏性更新,但是因为plate是一个代码高频更新的比较新的开源库,出现不兼容的情况也在所难免。

在修复bug后我光速提交代码并开始写这篇总结性质的文章,因此尚未定位到具体是哪个依赖的什么代码导致了这个奇怪的异常问题。虽然说知其所以然很重要,但是我还是觉得先记录下“知其然”的过程,确保以后不要在大方向上失之千里。

总结

在发现这个本不应该出现,并且也应该快速想到的问题时,我脑子突然浮现出了去年底OpenAI Python SDK的破坏性更新以及LangChain版本不兼容导致的诸多程序崩溃,也愈发感受到了锁版本的重要性,尤其是对于社区不够活跃的新项目来说。

并且正如同windows的“重启大法”一样,对于前端项目就应该从最开始就cache clean + npm install 二连,从源头排查经典的“我本地可以运行的问题”。同时,对于依赖的版本控制需要有更敏锐的认知,避免毫无意义的心智负担。

而在回顾我自己的排查路径中,我能明显感觉到我思维上的滑坡,也造成了大量时间的浪费。当然,也不得不吐槽万恶的node_modules,也许使用bun可以提升很多效率,但是既然项目从初期就是yarn那也没什么办法,只能每次安装时苦苦等待漫长的几分钟。

如下是我对于“自己代码没变动但是导致程序bug”的后验方法论:

  1. 依赖检查(版本检查),即需要确保当前的bug不是由于版本导致的,也要确认回滚后的代码和过去那个时刻是完全一致的。
  2. 回滚至没有bug与有bug的临界版本。在确定不是外部依赖导致的问题后,才需要关注项目自身的代码实现。
  3. 定位是自己的问题还是其它人代码修改导致的问题。
  4. 如果是自己的问题或者问题本身很容易解决可以尝试解决,否则就先甩锅嘿嘿,除非自己时间精力非常充足。