关于调试的方法论

Debug,作为程序员每个人都不陌生. 在实际过程中,却常常成为梦魇,往往调试的时间是实际写代码时间的数倍.其实调试也是有很多技巧的, 有一本书 thinking in debug,令人意外的是,它所讲的很多都是软件开发之外的东西,包括侦破学,心理学,医学, 以及从福尔摩斯们那里借鉴来的一些方法. 其实在软件中找bug, 改bug确实有点象侦探们的破案工作. 联系自己的曾经的痛苦调试经历, 有必要总结一些心得方法,免得总象没头的苍蝇在代码的海洋里乱飞

Contents

[hide]

 

[ edit]

工欲善其事,必先利其器

  • 资料查询: Google, BaiDu , MSDN, CSDN, ITPUB, ChinaUnix 帮助文档等.
  • 调试工具: SoftIce, GDB, Log4j, Log4cpp 等
  • 单元测试: JUnit, CppUnit
  • 检测工具: memCheck,Valgrind 等
  • 集成环境: IDE: VC, Eclipse,TOAD 等
  • 抓图工具: snapshot 等
  • 比较工具: diff, BeyondCompare
[ edit]

隐患险于明火,防范胜于救灾

虽然每个程序员都知道,debug是难以避免的,但是如果我们在设计编程时多注意一些防范措施,那么花在Debug上的时间会大大减少.

  • .遵守一些基本的代码标准:每种语言几乎都有它自己的code convention,打印一份,放在你的手边或贴在案头.
  • .前事不忘,后事之师: 从别人和自己做过的项目中吸取经验教训,多看一些Effective C++,Effective Java之类的书,自己曾犯下的错误要及时总结.
  • .提高编译器的的检查标准,不放过一个警告错误, 例如g++中加上-Wall 编译开关,java中加上-Xlint:deprecation,unchecked
  • .多做一些Code Review, 在检查代码的同时可以准备一些Check List加以参照对比,例如常用的 Cpp Check List ,看看有没有未初始化变量,野指针,内存泄漏等等,嗅一嗅你的代码中有没有Martin Fowler所总结的那些代码的坏味道.最好请同行一起检查一下你写过的代码,发现问题的机率大大超过你自己的复查
  • 利用一些工具来做静态的代码检查,例如Java的CheckStyle, FindBug,C++Test/JavaTest, Insure等
[ edit]

明察秋毫,顺藤摸瓜

It is a capital mistake to theorize before one has data. Insensibly one begins to twist facts to suit theories, instead of theories to suit facts. -- Sherlock Holmes
福尔摩斯:在没有事实作为参考以前妄下猜测是很可怕的错误.感觉不正确的人总是用事实去套自己固有的猜测,而不是按正确的方法根据得到的事实来推导结论,看它能否吻合已得到的事实.
  • 模拟现场: 首先一定要重现bug, 找出错误的发生的规律和条件. 找出产生错误的最小化测试用例,如果无法重现,通常可能是与时间有关的问题,或是由于内存管理,野指针引起的问题,抑或是由偶而发生的特殊事件或输入数据引起的.
  • 实地勘察: 不放过一个有价值的蛛丝马迹,出错的具体表现是什么,是否要经过什么特定步骤或输入才能发生,发生的机率是多少,有没有错误信息,有的话,“录下口供”, 以备详查.要仔细观察研究错误产生的步骤,当时的系统环境(disk/memory free space),log,或由crash产生的core dump文件.
  • 作案时间: 这个问题是什么时候出现的,是最近才有的,还是一直就有,只是过去没发现. 如果是前者,用CVS等版本控制工具察看最近哪位老兄动了代码,最近改动的代码有没有问题.
  • 分析原因: 判断最有可能的嫌疑代码,由80/20原则,经常出问题的地方往往老是有问题,review那些经常出错的codes. 如果错误是依赖于特定时间,输入或环境,根据其特征,查找出错原因.
[ edit]

大胆假设,小心求证

One should always look for a possible alternative and provide against it. It is the first rule of criminal investigation. -- Sherlock Holmes
福尔摩斯:犯罪调查的第一法则就是: 你必须寻找各种可能解释事情的方法,然后想办法看看能否试图推翻它
  • 借鉴福尔摩斯的演绎法(从普遍性结论或一般性事理推导出个别性结论),根据以上所做的勘查和分析,假设bug产生的几种可能原因,模拟错误输入或条件,执行重现bug的testcase,在可疑之处留下眼线(日志/断言),逐一进行验证排除.
这时候,建议将每个假设与推理过程,验证步骤逐条记录下来,对于理清思路,及以后的review很有好处
  • 为准确诊断某些杂症,可以使用二分法,把应用程序拦腰斩断,或掐头去尾,或屏敝可疑代码,或在关键代码行设置断点,缩小嫌疑者的范围,使错误代码无处藏身.
[ edit]

三思而后改之

When you have aliminated the imporsible,whatever remains,however improbable,must be the truth.    -- Sherlock Holmes
福尔摩斯:除去不可能的剩下的即使再不可能,那也是真相
  • 找到错误所在之处,接下来自然是如何修改它了. 在修改错误之前,先问问自己,那是不是真正的错误原因,抑或只是错误的表相?会不会牵一发而动全身,搞出其他毛病来?如何修改才能斩草除根,根本解决这种问题?
  • 针对每一个bug,编写与其对应的单元测试代码,针对修正前和修正后的代码进行测试,以验证推理修正的结果.
  • 用版本管理工具保存原有代码的快照,一次修改一处代码,改正错误后,首先执行相应的单元测试以验证结果,再比较前后所做的修改(推荐BeyondCompare),最后写下修改原因,提交代码.
  • 在修改错误之后,再问问自己.这种错误怎么会出现?其他模块中是否可能存在相类似的错误?今后如何杜绝这类错误?


[ edit]

调试的常用手段

  • 输出Print:
    • C++: printf("%s,line%d: %s",__FILE__,__LINE__,variable);
    • Java: System.out.println("Info:"+variable),Log4j--logger.debug();
    • PHP: echo " line".__LINE__.": ".$variable;
    • PLSQL: DBMS_OUTPUT.PUT_LINE(' variable:'||v_variable);
  • IDE内置的或专用的调试工具(VC,Eclipse,GDB,DDD,etc.)
    • 设置断点break(Add/remove/enable/disable breakpoint/resume/continue)
其中断点的设置很有讲究,在有疑问的行,函数方法或异常都可设置断点,也可针对某种情况设置条件断点: 
如在gdb中设置在某处当nConfId=540 时中断
break <args> if <cond> :  (gdb)break if nConfId=5400
condtion <breakpoint number> cond : (gdb)cond 1 nConfId==5400
    • 单步Step(step into/step over/step return/step with filter)
如在gdb中 step运行一步(会进入函数内), next运行一步(不进入函数里) finish(运行到函数返回), until(运行到某一行)
  • 编写易于调试的程序
    • 尽量降低类,模块之间的耦合性,将依赖倒置,以利于故障隔离,从而分段,分模块进行故障隔离和调试
    • 在命令行参数或配置文件中设置调试开关,或使用调试宏,编译时加入-DMYAPP_DEBUG
    • 利用配置文件,配置表或诸如Spring 的BeanFactory等方法将耦合及依赖从程序代码中移出
[ edit]

绝招

常规方法都搞不定,就只有使用绝招了

  • 把可能有问题的代码模块隔离或抽出,另写一段测试代码, 使用Mock对象或设定可能引起问题的输入,分支条件或循环次数,再结合以上招数进行分析.如果单元测试写得好的话,就会帮你节省很多力气,直接把相关的单元测试加以假设的条件就好了.
  • 安放钩子函数:
void donothing(){return;}
if(++num==18400) donothing();

之后可将断点设置在donothing函数中

  • .对于C++来说,最头痛的就是内存泄漏,溢出之类的问题,有不少好工具可以加以利用,如memcheck,valgrind等
1)dbug库( ftp://ftp.ninemoons.com/pub.dbug)
2)Electric Fence: Linux自带,要加入-lefence编译参数
3)dmalloc:
4)Valgrind: valgrind --tool=memcheck --leak-check=yes ./yourprogram
[ edit]

无招

  • 到网上(Google, BBS, newsgroup))找找是否有人碰到类似情况, 或向身边的高手请教,有时当局者迷,旁观者清,往往可能只是一些低级的错误,比如源代码版本搞错了, 链接的库版本不兼容等等.
  • 总也搞不定,多半是头已经晕了,出去喝杯茶,抽根烟,再回来说不定豁然开朗
[ edit]

Referred links and books

  • 0
    点赞
  • 0
    收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
评论

打赏作者

fanyamin

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值