读书、编程,广泛了解各领域的知识和思想, 不定期邀请人一起去爬山、逛风景、参加研讨会。其实生活也可以很飘逸的。 从来没有枯燥无趣的生活,只有不去主动寻找生活、发现乐趣的心。
关于软件调试,在学习和工作中积累了一点心得,提出来以供参考。
1. 初期: 重点内功修炼, 训练问题分析能力
建议初学者在初学编程的时候,不要用任何调试器, 只用 print 打印语句即可。 这样做的调试效率可能有点慢, 但可以训练一个人的问题分析能力。 暂时不花精力去折腾工具, 而是分析程序状态, 推测是哪里出了问题。在可能的地方放置打印语句,然后运行,验证猜测是否正确, 逐渐排查缩小范围直至找到问题所在。
2. 初中期: 敏感捕捉线索
对于较大规模的应用程序来说,因为使用了很多开源或闭源框架, 出错时通常会有大量的异常栈,令人不知所措。 此时, 可以查看异常栈中与业务代码相关的异常,因为框架代码通常是不会出错的,可能是业务代码本身的问题,也可能是不正确理解 框架 API 导致调用不正确的问题。
3. 中期: 使用调试工具
进入真正的软件开发环境,你会发现很多人使用工具的效率很高,这让人很眼红。 使用工具提升开发效率可一点错都没有, 大胆使用吧!
对于调试, 首先要学会的是使用断点。 断点很类似于前面所说的打印语句, 只不过打印语句需要插入到程序中,而断点不需要插入多余的打印语句,程序运行到这里会停止。 这时, 可以查看各个变量的状态值是否正确; 可能会使用单步调试, 遇到可能出问题的函数时需要 SETP INTO, 而不会出问题的函数则最好直接 STEP RETURN。 基本的使用大抵是这些。
4. 调试信念
无论多么奇怪的现象, 背后总有合理的解释。只不过要你去发现。有些是框架交互的问题,会令人百思不得其解。比如, 在 Flex as函数中传入字符串参数并且可以打印值 , 在 Java 端却获得为 null , 中间没有任何操作, 完全是 flex 与 Java 交互的问题。 令人奇怪的是, 其他的类似调用是成功传递参数的。 唯一值得怀疑的是, 传入的参数是从 ComBobox 的选中项, 其值是 XML 字符串的子节点。 肯定是 ComBobox , XML 和 Java 这三者交互的时候出了问题。
var selItem = statusData.selectedItem;
Alert.show(selItem.book_id); // 可以在AS端打印出 selItem.book_id 的值
vmFlex.doSomething(selItem.book_id) ; // 但传入到 Java 端获取 null
var book_id:String = statusData.selectedItm.book_id as String ; // 想了一个办法绕过去了
vmFlex.doSomething(book_id) ; // Right
5. 热调试
很多事情需要预先部署好热调试。所谓“热调试”是指做一些修改能够很快得到反馈,而不需要重新编译和部署整个项目。 比如像 Flex 这种技术。 我初接触的一个项目,flex, java, spring 等集成, 由于偷懒而不去配置热调试,结果哪怕改一个小地方都要重新编译所有模块,虽然后来写了个自动部署脚本使得可以并发做点其他事情,但 Flex 调试的低效率耗费了不少时间。 这也说明了, 工欲善其事, 必先利其器。
6. 详细的日志输出
详细的日志输出也是软件调试中必不可少的有力工具。 可以分为四级:
(1)错误日志: 记录程序运行出错的情况; 必不可少的。
(2)信息日志: 记录一些重要操作的情况,比如导致状态变更的操作, API 调用返回信息等, 做到有据可查; 也是很重要的。
(3)SQL日志: 记录执行的SQL语句, 方便日后查询
(4)DEBUG 日志: 通常不会用到, 在极少数情况下可能会找到一些有价值的线索。 可有可无。如果影响程序性能,可以去掉。
日志错误输出不仅要有技术性的栈信息,最好有包含业务方面的信息。在排查问题时, 不仅知道是哪个方法出错了, 而且知道是针对哪个具体的业务和所涉及的业务对象出错了,方便对比和排查问题。此外,日志中的时间信息有时会成为重要的线索。
7. 如有可能,从数据库入手
如果有访问数据库的权限, 可以从数据入手, 查看数据库中的数据是否是正确的; 这样可以排除数据方面导致的问题,确定问题出在程序上; 如果数据有问题,那么要先追查导致数据不正确的原因,再定位程序问题。
8. 单步调试
对于那些隐藏较深的问题, 单步调试的结果会让人大吃一惊: 会是想不到的原因导致的。比如,我在调试一个调用API 的代码时, 之前都工作的好好的, 但做了些修改后,报 NullPointException 异常。 仔细阅读程序, 发现并没有明显的逻辑错误。 最后只好采用单步调试, 很快就找到了问题所在: 因为一个空字符的问题导致的;另外一个例子时,向数据库插入数据,要先验证在数据库中已经存在。总是报“不存在”错误。仔细阅读代码,以及在数据库中直接执行SQL 都表明是存在的。百思不得其解, 后来采用单步调试,发现在应用程序通过Spring自动注入的 service 访问数据库时并没有查询到相应数据。
9. 对比实验
有时候, 同一个业务,有的报错, 有的不报错。 此时,可以做一些对比实验, 分析差异,寻找原因。 比如,最近在排查数据库连接被 proxool 关闭的情况。经过详细的日志输出,发现所有的集群中, 集群 ABC值得关注,AB报错,而C 不报错。 A,B,C 都是集群中数据库表的记录数最多的,百万级。后来的实验表明: 执行同一个SQL, A,B需要10分钟左右,而 C 只要 9s 左右。 进一步咨询 DBA, 了解到 A,B 由于内存大小受限而导致查询速度慢, 数据库连接很可能是因为查询超时而被关闭。
10. 特定开发领域的调试
不同开发领域的调试是有一些差异的。比如前端与后台, PC、服务器和移动设备编程,系统编程与应用编程。 需要针对自己所在开发领域去专门学习一些特定的调试技巧。
11. 反思,找出弱项
如果有些 BUG 非常难以找出问题之源, 最好记录下来, 过一段时间来回顾一下, 究竟是哪些类型的问题对你来说很难发现和解决? 这类BUG 有无特别的方法来排查 ? 对于我来说,涉及到框架交互的问题是比较棘手的,必须深入框架源码去查找问题。
12. 框架交互的问题
涉及框架交互的问题往往比较棘手, 初步的方法是: 源码 + 关键断点 + 单步调试 + 大胆猜测和定位 + 验证 + 耐心。 详见 《多数据源的动态配置与加载使用兼框架交互的问题调试》。
做到前面这些, 基本上大部分的问题都可以很快找到源头。 进一步地, 对于那些非常刁钻的问题, 恐怕只有深入框架源码或者请教高手指点一二了。这一步我尚且没有做到。待有经验后再说吧。