文死谏,武死战,程序员死BUG
◣
新帆新闻组上一个网友签名
|
Debugging
原文出处:
The Pragmatic Programmer – From Journeyman to Master
这是多么的痛苦
你必须面对这个麻烦
并且这个麻烦是你自己创造的
◣
Sophocles
。
Ajax
早在
14
世纪,人们就使用
Bug
这个单词来描述
"
可怕和可恶的东西
"
。相传,
COBOL
语言的发明人
Grace Hopper
是第一个在计算机中发现臭虫的人-这个虫子在一台计算机的继电器里面住了一个月。有人问出了什么问题,一个技术人员报告说:“计算机里面有个臭虫。”并且如实地把这句话记录到了工作日志。
我们现在仍然必须面对系统中的“臭虫”,虽然不是会飞的那一种。也许一个真正的虫子更容易让人接受。软件的
Bug
以各种难以预料的方式出现,从对需求的误解到编码时的错误。很不幸,当代的计算机还只能做你让她做的事情,不能做你想让她做的事情。
没有人能写出完美的程序,因为完美的程序根本不存在。
Debugging
将占据你大部分的工作日。让我们看看一些与
Debugging
相关的问题,了解一些通用的发现解决
Bug
的策略。
Debugging的哲学
在程序员中间, Bug
是一个敏感的话题。 Debugging
并不像破解一个难题那样吸引人。报告一个 Bug
,你可能遭遇否认,指责,无理的申辩或者冷漠。
程序员应该坚信这个事实: Debugging
只是解决问题,你应该像解决其他难题一样解决她。
如果发现了第三方软件的 Bug
,你会去咒骂创造这个 Bug
的家伙。在一些地方,这几乎成为的一种习俗,通过批评第三方软件,宣泄不满和缓解压力。然而,从技术上言,你应该集中精力去解决这个问题,而不是忙着骂街。
Fix the Problem, Not the Blame
-快解决问题吧, 不要责备别人了
|
问题已经出现,是不是你的责任,其实无关紧要。重要的是,这是你的问题,现在必须由你来解决。
调整你的心态
人最容易欺骗自己
◣
Edward Bulwer-Lytton
,
The Disowned
在你开始Debugging之前,要调整好你的心态。首先要脱去虚荣心的外衣; 如果你正面对来自项目的压力,先把这个压力抛到脑后。尽量让自己放松。记住Debugging第一个规则:
恐慌的情绪很容易产生,尤其是你正在面对最终期限的情况下。也许,当你Debugging的时候,正有一个精神紧张的领导或客户站在你的身后,对着你的脖子喘气。但是你必须放松下来,集中精力做你的工作,思考导致程序出毛病的原因。
当你亲自目击到一个Bug,或者看到一个Bug报告,你的第一个反应也许是“这是不可能的”,这是错误想法。不要总在开始就认为“这不可能发生”,不要在这种思路上浪费一个脑细胞。
在Debugging的时候,千万别犯了目光短浅的错误。不要试图添加一些“上帝之手”式的代码,把问题掩藏起来。大多情况下,导致问题的原因就在不远的地方,但也可能这个问题相关了很多其他的模块。无论如何,你应该找到问题的根本原因,而不是只解决问题的一个特别表现。
从哪里着手
在你开始寻找 Bug
以前,首先保证:你的程序在编译的时候,编译器没有产生任何警告信息。我们总是喜欢把编译器的警告级别设为最高。如果编译器能够帮助你发现某种错误,没有必要自己浪费时间去解决,我们应该集中精力解决更困难的问题。
当你解决任何问题的时候,你都应该先搜集所有尽量多的信息。 Bug
报告不是一个精确的科学,很容易就会被偶然性误导。你不可避免要为偶然性浪费时间。第一步,你需要一个尽量精确观察结果。
当 Bug
的报告是来自其他人的时候,其准确性就更糟糕了,你也许需要登门拜访一下报告 Bug
的同志,以了解更多的细节。
Andy
曾经参与过一个很大的图形应用程序项目,在临近发布的时候,测试人员报告:每当使用一个刷子去画一个大斜线,程序就会死掉。开发人员争论说:这个程序没有错误,我试过用那只刷去画,程序运行的很好。两个人的反复交流了多次,两个人的火气也越来越大。
最后,我们把这两个人叫到一个屋里面。测试人员选中了那个刷子,然后从右上角向左下角画了一条线,“哦!”
程序的开发者小声说,他只试过从左下到右上画。
这个故事告诉我们两点:
u
你也许需要和报告 Bug
的人面谈,以得到更多的信息。
u
就像那个程序员总是沿一个方向画线,人工测试是不够的。你应该在用户的角度上,努力测试到每一个边界条件,系统全面地进行单元测试。
Debugging的策略
不要自以为是,程序就是不按照你认为的方式运行。
让Bug重现
要修改一个Bug, 最好的开始方法, 就是让Bug再现。如果你不能再现Bug,你今后又如何知道:你是否把程序改好了。
而且,我们希望能够使用一个简单的命令,就把Bug再现出来,而不是必须通过很多的操作步骤。如果,你必须15步的操作,才能让Bug再现,这个Bug的修复会比较困难。有的时候,你需要写一些代码,把出现问题的部分孤立出来。也许你在这样做的时候,突然就发现并解决了Bug。
|
直观的展现程序的数据
很多情况下,要搞清一个程序究竟在干些什么,最简单的方法,就是好好看一下这个程序正在处理的数据。最直接的方式,就是在执行的过程中,查看变量的值,你可能通过 print
的语句打印变量值,或者在程序运行的图形界面上查看。
但是,如果你使用一个好调试工具,能把数据直观的展现,你就可以更深入地观察你的数据。有许多这样的调试工具。
如果你的调试器,不大能支持这样的直观方式。你甚至可以自己动手去做,使用一张白纸,一个铅笔。或者使用其他的绘图软件。
DDD
支持一些直观展现数据的能力,它是免费的,并且支持多种编程语言。 C
, C++
, Fortran
, Java
, Modula
, Pascal
, Perl
和 Python
。
使用跟踪语句
调试器主要关注于程序当前的状态。有时,你要了解的更多,你需要观察程序在一段时间的流程。查看当前的调用栈,只能让你了解,程序如何到达这里,它不能告诉你,程序如何到达这里,特别是在一个事件驱动的程序中。
跟踪语句输出一些简短的信息,程序把它打印到屏幕上,或者记录到文件中,告诉“到这里啦”或者“现在 x
的值是 2
”
。这是一个基本的调试技术。和在 IDE
界面上跟踪代码的执行相比较,这种方法在对付一些种类错误时有更高的效率。特别是当程序的逻辑相关时间的时候,这种跟踪方法非常的好用,比如:在并发处理,实时系统和事件驱动的应用程序中。
你可以使用跟踪语句,深入到你的代码中,形象地观测函数调用如何一步步地深入。
跟踪语句打印的消息,应该使用固定的规则,保持输出格式一致。你可以使用文本分析程序对它们自动分析。比如:你正在查找资源没有释放的问题,你可以把所有的 Open
和 Close
代码记录到日志文件。使用一个简单的 Perl
程序分析这个日志,,你就可以很容易的发现问题出在哪里。
如果发现不正常的变量, 近一步检查一下它周围的数据
很多时候,你在观察一个变量在执行状态下的值,你知道这个值应该是一个小的整数,但却看到了类似0x6e69614d结果。在你卷起袖筒,准备大干一场前,你应该快速的查看一下周围的内存中的数据。你很可能找到一些重要线索。看下面的情况:
20333231
|
6e69614d
|
2c
745320
|
746f
4e0a
|
1 2 3
|
M a i n
|
S t . /n
|
N o t
|
2c
6e776f
|
2058580a
|
31323433
|
00000a
33
|
own .
|
/n x x
|
3 4 2 1
|
3/n/0/0
|
看起来,有个家伙记录住址信息时,超过了其存放的边界。现在你找到了解决问题的线索:你应该找到这个家伙.
|
让橡皮鸭子帮忙
查找导致问题的原因,有一个非常简单且十分有效的方法,那就是把这个问题解释给另外的人听。那个人站在你的身后,看着你的电脑屏幕,不时地点一点他 (
或她 )
的头 (
就像放在浴缸里的橡皮鸭子 )
。他可以一句话都不说,就是这个一步一步解释的简单过程,可能突然触发你的灵感。
这听起来很简单,相对于独立思索问题,解释给别人听会让你更清晰地思考问题。假设把你的问题解释给别人听,你也许马上能找到看待这个问题的一个新的角度。
优先排出最大的可能性
在许多系统里,你要调试的代码中,有的是你写的,有的是其他人写的,有的是第三方的产品完成的 (
数据库,网络通信引擎,图形库,特殊的算法等 )
,还有平台环境 (
操作系统,系统库和编译器 )
。
虽然 Bug
是有可能存在操作系统,编译器,或第三方软件中的,但开始的时候,绝对不能这样想。 Bug
更有可能是你开发的引用程序的代码中。假设是你不恰当的调用了库函数,而不是函数库的问题。即使真的是函数库的问题,在你报告一个 Bug
以前,要优先排出你自己代码错误的可能性。
我们曾经开发过一个项目,项目中有一个老资格的同志,他说 Solaris
平台下的 select
调用有问题。虽然,这个调用被广泛的使用和验证,没有人能说服他。他花费了一个星期的时间也没有解决这个问题。最后,他终于坐了下来仔细阅读 select
的手册,然后发现了真正的原因,并在 1
分钟内就把问题解决了。“ Select is broken
”现在成为了我们的口头禅,用来劝说那些责备系统毛病的同志。
记住,当你看到了一个马蹄的图案,你应该认为它是个马,而不是一个斑马。操作系统一般不会有问题,数据库也是。
如果你发现,“只是一个小的变动”,然后系统就不能运行了。这个小的变动,就很可能是问题的原因,不管这是多么的不可思议,它很可能是直接或间接地导致了问题。变动的影响经常超出你的控制,比如:新版本的操作系统,编译器,数据库或者其他的第三方的软件,可能导致你原来好的代码不能运行。新的 Bug
可能表现出来。你正在解决的 Bug
可能消失了。 API
的变化,功能的修改 ;
当外部的条件发生变化的时候,你必须认为这是一个新的开始,你必须重新测试整个系统。在你要升级以前,要关注一下进度计划。你也许应该等到下一次发布的时候再考虑变化。
如果,观察到一个问题表现后,你不知道问题出在哪里,可以使用一个流行的 "
二分查找法 "
,先观测代码的开始和结尾地方,然后观测代码中间的地方,定位问题出现在那一半,不断的使用这种手段,定位问题出现的地方。
奇怪的事情总是发生
当觉得一个 Bug
很奇怪的时候 (
这个时候,你总是说 "
真是邪门 ")
,你必须对这个结论重新判断。在认为不可能出问题的地方,你是不是测试到了所有的边界条件 ?
虽然这个代码已经使用了好多年了,就没有出问题的可能了吗 ?
你感到奇怪的程度和你对程序的信任程度成正比。当你感到奇怪的时候,你应该意识到,你之前的假设是错误的。不要因为认为它没有问题,就跳过一个函数或一段代码。你应该,在当前的环境,当前的数据,证明它没有问题
Don't Assume It – Prove it.
-先不要轻易下结论, 去证明你的想法
|
当你最终解决了一个 Bug
,除了修改它之外,还要想一想:为什么开始的时候没有发现它。考虑是否应该修改你的单元测试,这样你就能发现这样的 Bug
。
如果,你发现 Bug
的原因是其他人提供的数据不正确,你应该考虑:使用更好的数据检查手段,让这种问题尽快的报告,而不是让这种数据,导致整个系统崩溃。
如果,你在这个地方发现了一个 Bug
的原因,你应该思考这个原因在其他会不会导致其他 Bug?
如果有,现在就应该改好它。
如果,你花费了很长的时间解决一个 Bug
,你应该问自己:为什么。下一次,你可不可以有更高地效率 ?
可能你应该,编写更好地测试脚本,或者写一个日志文件分析程序。
最好,如果错误的原因,是因为一些人的误解,就要在整个团队中讨论这个问题。如果一个人误解了,其他人也可能误解。
Debugging Checklist
u
这个问题是直接反应了 Bug
,还是仅仅是一个 Bug
地表象 ?
u
编译器,操作系统,可能有问题吗 ?
更有可能是你自己程序的问题吧 ?
u
如果你把这个问题解释给其他人听,你会如何说 ?
u
为什么有问题的程序通过了单元测试,这个测试充分吗 ?
如果你使用现在的数据进行单元测试,会有什么后果 ?
u
导致这个问题的原因,在系统的其他地方可能导致其他问题吗 ?
后记
我一直对我的朋友说:计算机书,一定要看原版。市面上的翻译书质量太差,我甚至听说:无望出版社(真名隐去,这种出版社肯定没有希望,所以叫无望)让北方某高校外语系的学生翻译书,学生们拿来一个叫“西方慢车”(也是假名)的翻译软件,把程序输出的句子理顺后交了差。其实,既便是一个英文专家,如果不是真正搞计算机的人,也不能把计算机书翻译好。了解我的人都知道,我的英文并不好,但我却觉得看原版的书更容易看懂。
翻译这么难吗?我决定自己试试,于是有了这篇文字。我本来认为《 The Pragmatic Programmer
》这本书没有被翻译过。翻译过后,才了解到中文版已经有了。有时间,我一定拿来对比一下。
个人认为:英文翻译中文,应该注意下面几点:
(一)
变长句为短句。我国文字以短句为贵,言简意赅。长句的中文读起来很费力。
(二)
变被动为主动。英文的被动语态多,直接翻译过来,读着不舒服。
(三)
删除冗余。意思要点到为止,汉语更注重上下文,如果信息可以在上下文中得到,就不必重复。
(四)
添加说明。如果认为原文一句话没有说明白,就要添加文字。英文单词不能和汉语词汇一一对应,有时需要多来一句才能把意思说清。
(五)
使用口语词汇。使用日常对话使用的词汇,让文字通俗易懂。像政府报告那样的文字,反正我看不下去。
(六)
最重要的一条:翻译者自己要看明白,不然不要动手。