首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >方法论:在不是太懂源码的情况下,我是怎么定位源码问题的?

方法论:在不是太懂源码的情况下,我是怎么定位源码问题的?

作者头像
CandyTong
发布2023-02-24 15:55:06
5920
发布2023-02-24 15:55:06
举报

在日常开发中,我们多多少少会遇到些问题,有时候是自己的写法有错误,这时候可能就要先检查一遍,看看文档,看看是哪里的问题。

但有时候也有可能是框架/工具的源码错误,虽然一般这种情况很少发生,因为一般框架/工具都会做了比较多的单元测试,经过开源社区的验证,出错的概率比较少,但也不一定所有情况都能测试到。

那么,如果真的认为是源码的 Bug,我们该怎么去定位呢?

本篇文章讲解介绍我最近遇到的一个真实例子,在不是太懂源码的情况下,通过自己的一些经验、调试技巧,去定位问题

发现问题

在我的某个项目中,当我使用 pnpm i --fix-lockfile 时,一定会报如下错误:

运行 pnpm i 的时候,不会报错,只有运行 pnpm i --fix-lockfile 会报错。**在一些业务场景下,我们偏向于使用 pnpm i --fix-lockfile**,当然我也可以改为用 pnpm i,那故事就结束了,全剧终 hhh。

当然我还是稍微努力了一下下,准备提个 issue 看看。

既然要提 issue,那就得首先觉得它是 pnpm 自身的问题,不是我写的代码有问题。我个人主要是有以下原因:

  • • 我就是安装个依赖,这能有什么错哦。。。而且它 pnpm i 是能安装的
  • --fix-lockfile 这个选项,肯定比仅仅使用 pnpm i 的场景少,那在极端场景下,可能 pnpm 的单元测试没覆盖到,有问题也是正常的
  • • 我是学过英文的,错误信息很明显就说,vite@4.0.4_@types+node@17.0.45 这个版本解析不出来,个人感觉应该是要解析成 vite 4.0.4,我 package.json 也是这么写的,pnpm 自己加的其他东西,那肯定不关我的事情呀。而且 pnpm 的 lock 文件也是用 vite@4.0.4_@types+node@17.0.45,那是你的问题没错了
  • • 错误信息中出现 @vitejs/plugin-basic-ssl,有可能是这个包不行,但 pnpm i 既然能正常安装,就证明人家本身没问题,是 pnpm 的问题。

因此,我提了个 issue[1],就贴了个截图,然后写 pnpm i --fix-lockfile 安装失败,是解析版本失败了,还贴了 pnpm 的锁文件。

我觉得我已经写得很明白了,这么一个 package 的版本解析错误问题,作者应该一看就懂。。。了吧

结果不出所料,作者也看不懂,让我提供一个最小的复现 Demo

这里补充一个知识点,一般提 issue 的时候,都要带上最小的复现 Demo,不然人家作者也没办法复现你的问题。

但是鸭,很多时候,开发者可能遇到问题了,却提供不出来,主要有以下原因:

  • 项目非常大,不知道哪里有问题,因此不知道怎么做一个最小复现的 Demo
  • • 是公司的项目,不能将代码提供出去

我是两个原因都有,因此不是我不想提供 Demo,而是我也搞不出来。。。因此想碰碰运气,说不定作者一看就知道呢 hhh,结果不出所料,还是得提供 Demo。

很多人提供不了最小复现 Demo,开源库作者也没办法知道问题,然后问题就不了了之。

因此,很多人也只能走到这一步,然后故事就结束了。

但其实不是完全不可能提供一个 Demo,看要不要再努力一下下。这时候人和人的差别就会显现出来了

  • • 有的人可能觉得换一种方式就行了
  • • 有的人可能觉得没多大影响,不折腾了
  • • 有的人可能觉得,我就是要搞出来。

当我第一次遇到这个问题的时候,我也是抱着,算了不管了

后来再遇上,真烦,不如提个 issue 碰碰运气吧

再后来多遇上几次,实在不想忍了,晚上调试一下看看,就花一个晚上,不行拉倒

因此才有了接下来的一些努力。

有时候,你离开源贡献,就只有一念之差。只是,有些人选择放弃,有的人选择再努力一下。

调试代码

光有决心还是没有的,得实际行动。

但一个巨大的问题摆在面前,pnpm 的代码我也没看过鸭,调个啥玩意???

因此,第一个问题,是怎么把 pnpm 源码跑起来调试呢?

pnpm 源码调试

之前看了**神光大佬的调试小册[2]**,学到了很多调试相关的知识,感兴趣的可以学习一下

一般情况下,如何知道一个开源仓库要怎么进行调试呢?

  1. 1. 看仓库的 CONTRIBUTING.md[3] 文档,按道理比较常见的开源仓库都会有
  2. 2. 找别人总结过调试文章

我随便在掘金,找了一遍文章[4],毕竟能调试,能打断点就行。因此如何调试的问题就解决了。

这里总结一下:

  • • pnpm i 先安装 pnpm 源码的依赖
  • • pnpm run compile,执行源码所有包的构建(pnpm 是 monorepo 仓库)
  • • 用 node 执行 pnpm 的入口脚本

下图是我在 webstorm 的调试配置,qf-tds-vue-plugins 是我的项目文件夹,下面配置的意思是,我要在这个文件夹运行以下命令(因为是在项目目录安装依赖):

# 实际上 pnpm i,也是运行全局安装的 pnpm 目录下的 bin/pnpm.cjs
node /candy/app/pnpm/pnpm/bin/pnpm.cjs i --fix-lockfile

找个地方打个断点,代码能停住(停不住可能是根本没运行这行代码,换个别的),就代表这一步已经成功了

定位问题

这一步才是最核心、但又最麻烦的步骤

如何在茫茫源码中定位问题?下面是我的一些个人经验:

从错误信息出发,找到报错的代码

我们全局搜索关键字isn't supported by any available resolver,找到是哪一行报错的,找到之后,打个断点。

这就找到了报错源头了。因为 resolution 不为真值,所以报错了,那我们的问题就变成了,为什么 resolution 不为真值。这就将很大很抽象的问题,转化成了一个更小更明确的问题

resolution 是由 resolveFromNpm 返回的,那我们就修改一下断点位置

这里有一个小经验,断点位置要改到哪里比较好?有两种方式:

  • • 找到 resolveFromNpm 的函数源码实现,在函数实现里面打断点
  • • 直接在 resolveFromNpm 函数调用的位置打断点。

我个人更偏向与在调用的位置打断点,因为更方便。可以看上图的例子,resolveFromNpm 是另一个函数返回的,如果你想要找到它的实现,还得进去 createNpmResolver 函数里面找,说不定里面函数比较复杂,就比较麻烦,需要找到 resolveFromNpm 函数真正的内部实现,才能打断点 。

如果是在调用位置打断点,就会在 resolveFromNpm 函数调用前停住,此时,我们按进入函数,就能直接找到源码了

因此断点会改到这里,但我们运行后会发现,每个 package 都会在这里暂停,一个项目这么多包,不行啊。

这时候就要用到条件断点如何设置条件断点呢?可以先观察一下一些变量的值

可以看到 wantedDependency.pref 的值为 4.0.4_@types+node@17.0.45,那就用这个了。断点的条件就设置为

wantedDependency.pref === '4.0.4_@types+node@17.0.45'

这就能在出错前将代码定住了,然后我们进入函数

进入 resolveFromNpm 调试,然后发现 spec 为 null,所以函数 return null 了,因此又可以将问题转化:为什么 spec 会使 null?

那就要排查 parsePref 函数了,还是用上述的思路,打断点,进入函数,

同样的,按照上述思路就是 parsePref 函数的问题了,这里就不重复了。

最后发现,是 wantedDependency.pref 这个属性,应该为 4.0.4,才能使后面的代码不报错,而不是 4.0.4_@types+node@17.0.45

那接下来的问题就转化成了: wantedDependency.pref 为什么不为 4.0.4?我们需要找到 wantedDependency.pref 被赋值的地方

下面又是一些经验:

  • • 全局搜索 .pref =,是为了所有出 wantedDependency.pref = xxx 的这些代码
  • • 全局搜索 pref:(注意前面有空格),这个是为了搜索 { pref: xxx } 的代码

不过很可惜,在 pnpm 中都搜不到太多有用的信息,那就只能通过调试找了。接下来该怎么办呢?

我们可以利用函数的调用栈,逐级往上找,调试方法跟之前一样,目标是,找到 ``wantedDependency.pref 被赋值的地方。

有较多调试经验的开发者,也可以不逐级网上找,如果觉得肯定不会在当前函数层级被赋值,可以直接跳到更深的函数调用层级中

最终,我找到了整个 wantedDependency 初始化的地方:resolveDependency 函数。

这里我直接回顾一下整个错误的相关信息:

  1. 1. @vitejs/plugin-basic-ssl 在安装 vite 的时候,遇到了版本解析错误,4.0.4_@types+node@17.0.45
  2. 2. 在 resolveDependency 函数中,会解析 @vitejs/plugin-basic-ssl 的 package.json。直接注意的是,它的 package.json 没有 dependencies 字段
  3. 3. pkg 对象根据 package.json 生成,这一句代码中,由于 pkg.dependencies 不存在,因此会导致使用了锁文件的 dependencies 字段,这是不应该的,导致取了锁文件的 vite 版本号4.0.4_@types+node@17.0.45

既然知道了这个,我们就知道了这个错误出现的场景:

  1. 1. 装了多个 Vite,有的 Vite 版本号是 4.0.4,有的是 4.0.4_@types+node@17.0.45 ,出现多个 Vite 的原因,是因为 peerDependencies,感兴趣可以查看官网的说明文档[5]
  2. 2. @vitejs/plugin-basic-ssldependencies 字段不存在(不是为空,是不存在)

只有同时满足以上条件才会报错,因此很多非 monorepo 仓库,都不会有这个问题,因为它们只装了一个 Vite。

当我知道了以上信息之后,我就可以提供一个最小的可复现 Demo 了

不过,我觉得既然都看到这里了,不如尝试一下自己修复

直觉告诉我,只要加一点代码就行了,判断 pkg.dependencies是否为空,为空就设置为 {}

if (!pkg.dependencies) {
    pkg.dependencies = {}
}

然后我把出错原因写到了 issue 中,顺便提了个 pull request 给开源作者,然后被告知需要补一下单元测试(这也的确是正常且稳妥的做法),至于后续单元测试怎么补,就不是本文该关心的问题了,以后有机会再聊。

总结

本文用我个人的例子,从发现问题,到调试代码,一步步地深入,直到最终找到问题。里面用到了很多调试相关的技巧,这些技巧可以帮助我们,即使在不熟悉源码的情况下,也能深入源码进行定位问题

这些技巧主要包括以下这些:

  • 全局搜索查找关键词/错误信息,找到相关的源码
  • 转化问题,将大的抽象问题,变小变具体
  • 在合理的位置打断点
  • • 巧用条件断点,巧妙的设置断点条件
  • • 利用函数调用栈

当然,仅仅有技巧也不行,你需要有解决问题的决心。那么,当你遇到问题时,是选择避开它,还是选择解决它呢?

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-01-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Candy 的修仙秘籍 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 发现问题
  • 调试代码
    • pnpm 源码调试
      • 定位问题
      • 总结
      相关产品与服务
      SSL 证书
      腾讯云 SSL 证书(SSL Certificates)为您提供 SSL 证书的申请、管理、部署等服务,为您提供一站式 HTTPS 解决方案。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档