解决bug以及程序调试, 是我们经常要面对的事情, 有句话说, 程序员不仅在生产软件, 也在生产bug. 如何快速高效地进行调试, 也是程序员一项重要的技能. 结合最近解决的一些问题, 我和大家分享几则调试心得, 希望能给大家一些帮助.
合理的收集与利用调试信息
-
分析错误信息:
错误信息通常包括错误类型, 消息和调用栈, 这些信息通常是我们面对bug能掌握的最直接信息, 了解常见的error类型代表的含义, 会帮助你快速定位; 另外也可人工增加一些调用栈辅助调试.
错误信息是你定位问题最重要的线索, 前端web程序如果运行结果与期望不符, 首先看控制台有无报错, rails/node程序也是一样, 需要时刻关注web server的错误输出.
-
实例1: 一个node项目有一个报错, 分析错误栈对应的代码也没有发现明显的逻辑错误, 但是忽视了错误消息, 后来才留意到错误信息: “RangeError: Invalid array length”, 这个错误常见于把负数赋值给了数组的length, 了解到这点就可以很快定位到了错误源头.
-
实例2: 在一个rails项目中, 启动时总是出现2条异常的sql, 但是不太清楚是怎么触发的, 后来通过在sql对应的Model中添加”puts caller”输出调用栈, 很快找到了错误的调用处.
-
-
增加调试信息:
通过puts或者console.log输出调试信息是我们经常做的事情, 有几点tip可以帮助大家:
- 输出明显的分割线, 可以快速看到你的调试信息
- 添加固定的调试前缀, 方便搜索
- 除了前缀, 可以输出递增的数字标识, 用以确定执行顺序
- 注意不要遗留调试信息到生产环境, 这在node和ruby代码规范中都有规避方案
-
利用源码:
不要畏惧源码,要善于从源码中寻求答案, ruby/javascript/nodejs作为动态语言, 阅读和调试源码本来就具有先天优势.源码也是程序, 所有报错信息都是程序员写的, 如果你发现一个报错不明其意, 可以尝试到对应的源码的上下文中去寻找答案
- 实例: 之前一个node项目频繁出现400错误, 多方排查无解, 最后我们决定到源码中去排查, 在expressjs源码中发现有2处地方可能返回400, 我们在这2处代码中添加了调试信息, 很快发现此问题的触发条件, 最后通过google得知这是node0.12.7的一个bug. 之后我们进行了手动修复.
二分调试法:
如果没有显而易见的地方让你着手查看, 你总是可以依靠好用的老式二分法查找, 看症状是否出现在代码的两个远端之一, 然后看中间. 如果问题出现, 则bug就位于起点与中点之间, 否则就在中点与终点之间, 以这种方式, 你可以让范围越来越小, 直到最终确定问题所在 (摘自<程序员修炼之道>)程序员修炼之道>
-
实例: 最近2个月, 一个node项目一直受到内存泄露的影响, 我们从问题暴露伊始就开始积极排查, 学习node内存模型和GC原理, 在社区中寻找通用的内存泄露排查工具进行尝试, 但是苦于工具兼容性问题以及复杂的输出结果, 在接近2个月的排查过程中, 进展缓慢. 上周五, 我们决定用二分法进行排查, 我们反复删除部分代码, 配合压力测试进行重现, 对app.js 删除到为数不多的几行时, 内存泄露现象消失了, 在1小时左右, 我们定位到了问题出在我们之前添加的一个第三方包connect-timeout, 然后我们去github上发现, 该包已经就内存泄露进行了升级. 周一我们进行了代码升级, 内存问题就得到了很大的改善.
这件事除了让我们反思引入第三方包的严谨性外, 更大体会就是方向比努力更重要, 合理的调试方法能极大的提高效率.
先学习内存模型和GC原理以及分析工具, 是通过因果关系调试 采用二分法, 是通过相关关系调试, 寻找相关关系也是当今大数据分析的基本原理, 找到影响因素和被影响因素, 虽然不一定知道原因, 但是问题就是解决了.
小黄鸭调试法:
这种调试法源于一个大学计算机中心,在这里,学生们遇到神秘bug的时候就可以先把问题解释给这只摆在桌子上的泰迪熊听,然后才能向老师或助教求助。所以,有的时候只跟熊聊天也能解决问题。这一调试方法真的很管用,以至于风靡了整个软件工程行业, 后来演变成小黄鸭调试法, 参考: http://blog.jobbole.com/85719/ 究其原因, 我个人觉得, 在我们大脑的思考过程中, 会跳过一些我们认为理所当然的假设, 有时候这种假设是错误的, 但是我们自己在思考的时候会自动跳过, 也就是进入”我不知道我不知道”这个认知误区, 而如果能把问题以文字和语言的形式进行描述, 就很容易发现这类问题.
- 实例: 我也有过几次这样的经历, 某个同事遇到一个问题, 百思不解, 然后拉着我一起分析解决, 往往在问题还没有描述完, 他忽然就顿悟了: 噢, 我知道了, 原来…(某个地方做了错误的假设)…, 这个时候其实我还没有明白是什么问题, 但是问题已经解决了, 我只能微微一笑, 深藏功与名.