《Effective Debugging:软件和系统调试的66个有效方法》一第9条:相信自己能够把问题调试好

第9条:相信自己能够把问题调试好

软件通常是极其复杂的。机械表的移动机制,仅由100多个部件组成;而整个房屋中的各种器械,其部件总数也只是简单组件的几倍而已。这与典型的软件系统有很大区别,后者很容易就包含成千上万行复杂的代码。我们可以在这两个领域中各举一个较为精密的例子来做比较:A380客机有400万个物理组件,而Linux内核的代码行数则是900万。因此,我们必须在思想上做好充分的准备,才能应对如此复杂的软件。
首先,你要确信自己一定能够找到问题并将其修复。你的心理状态会对调试的结果造成影响,专家们把这叫做“感受到的挑战与自身技能之间的一场对抗”。如果你根本就不相信自己能够克服这个问题,那你的思维就会徘徊不前,甚至想要干脆放弃。在这种情况下,你解决不了实质的问题,而是会盲目地打补丁,以掩盖由该问题所引发的各种症状,这样做对代码是有害的。我们必须记住这一点。
如果问题是可以重现的,那么毫无疑问,你肯定能解决它(而且通常可以按照本书所给出的建议来解决)。如果问题不能重现,那么有一些办法可以令其变得能够重现。在调试的时候,有两个重要的“朋友”可以帮助我们:一个是对数据的访问权,它使我们能够访问到自己所需的全部数据;另一个是功能强大的计算机,它使我们能够对这些数据进行处理。我们可以检查程序发生问题时所表现出来的状况,以及程序的日志和程序的源代码,有时甚至可以检查机器指令,此外,我们也可以在软件栈的任意位置添加详细的日志语句(或者说至少添加一些监测探针)。然后,就可以用工具或较短的脚本来筛选这些数据,并从中找到问题的根源。这是一种综合能力,具备这种能力的人可以在较大的范围内及任意的深度上面进行搜索,从而完成调试工作,这个过程会给人一种独特的满足感。
为了能够高效地进行调试,你还必须留出充足的时间。调试是一项对人要求很高的工作,它比编程更为复杂,因为我们不仅要明白程序的逻辑,而且还要理解其背后的效果(这通常指的是较为底层的效果)。此外,我们还必须把环境、断点、日志记录、各种窗口以及测试用例设置好,以便能够高效地重现问题。不要在还没有解决bug的时候就停手,否则前面所花的时间就全都白费了,即便要停手,也应该在准确理解了接下来所应采取的措施之后再停止。
要想调试复杂的问题,就必须在没有干扰的状态下工作。人脑需要经过一段时间之后才能进入心流(flow)状态,在这种状态下,我们会完全沉浸于自己正在做的事情中。心流这个概念由Mihály Csíkszentmihályi提出,根据他的说法,人在处于心流状态时,能够把自身情绪与自己所做的事情相互契合起来,并通过一种成就感来提升自己做事的毅力与效果。我们在调试复杂的系统时会遇到巨大的困难,而心流状态所带来的好处对于解决这些困难会起到关键的作用。弹出的消息、打来的电话、反复的聊天、持续更新的社交网络,或是跑过来求助的同事,都会对自己造成干扰,从而破坏这种心流状态。一旦离开这种状态,就享受不到它所带来的好处了,因此,我们要尽量避免干扰,应该把用不到的应用程序关掉,把电话调到静音模式,可以在显示器上贴一个请勿打扰的标志(如果你有一间自己的办公室,那也可以把牌子挂到门上)。
还有个很有用的办法,就是在遇到难题的时候去睡一觉。研究者发现,人在睡觉的时候,其神经元之间会形成广泛的连接,这些连接会把看似不相关的路径贯通起来。这对于调试工作可以起到很大的帮助作用,它通常可以使我们找到一种打破思维定式的调试策略,从而跳出当前的困局。在看似不相关的路径之间所形成的这种新连接,是在睡眠的过程中搭建起来的,然而要想使这种机制有利于调试工作,我们还必须合理地运用它。也就是说,我们必须先努力地解决问题,使得必要的数据都存留在脑子里,然后才能在睡眠中找到创新的解法。刚一遇到困难就立刻喝一杯啤酒,然后跑去睡觉,是没有太大作用的。此外还要注意保持充足的睡眠,以便在第二天醒来之后,脑子里与意识有关的那一部分能够听取与潜意识有关的那一部分所给出的建议,从而令工作更有效率。
没有谁会认为调试是一件简单的事情,所以要想高效地调试,就必须有毅力。由于计算机在最底层具有确定性,因此我们最终可以通过深入挖掘把错误的原因隔离出来。在较高一些的层面上,为了提升表达能力与效能,它会引入一些不确定的因素,使得程序的某些行为看起来较为随机(可以思考一下多线程的应用程序是如何运作的)。面对这些与不确定因素有关的错误时,我们要把握住一点:计算机毕竟是一种运行速度很快的可编程机器,它可以运行数量极多的用例,因此,我们最后还是能够把错误原因找出来。由此可见,调试工作之所以会陷入僵局,基本上都是因为缺乏毅力,例如,我们可能没有去写某个测试用例,没有去查看某个日志文件,或是没有去尝试从另外一个角度来研究问题。
最后要注意的是:想成为一名高效的调试工程师,就必须持续地投入精力,去学习环境、工具及相关的知识。只有这样,你才能够在复杂度持续提升的技术工作中保持优势。现在看来,笔者当年在调试时最常犯的错误,就是没有花足够的功夫来把调试工作所需的基础设施搭建好。要想把基础设施搭建好,可能需要把下面四项全都做到:
把健壮的最小测试用例准备好(参见第10条)。
对bug的重现加以自动化。
用脚本来分析日志文件。
了解API或语言特性的实际运作方式。
当我振作起来把精力投入到应该做的事情上面之后,我的调试效率就会急剧提升。一旦进入这种状态,通常可以在几分钟之内查明bug的原因。
要点
确信问题是可以追查并解决的。
给调试工作留出足够的时间。
安排好工作环境,使自己不受干扰。
遇到难题的时候可以先去睡一觉。
不要彻底放弃。
投入精力去学习环境、工具及知识。

时间: 2024-10-09 00:22:23

《Effective Debugging:软件和系统调试的66个有效方法》一第9条:相信自己能够把问题调试好的相关文章

《Effective Debugging:软件和系统调试的66个有效方法》——第11条:修改完代码之后,要能够尽快看到结果

第11条:修改完代码之后,要能够尽快看到结果 调试通常是一种循序渐进的过程.在每一轮中,我们都要花时间去构建并运行软件,而且要看着它发生故障,这些环节会占用很多时间,而且这些时间并没有用来解决软件中的问题.因此,我们要提前进行准备,设法缩短每一轮调试所花费的时间. 首先从软件的构建入手.我们应该能通过一条命令(如make或mvn compile)或一个按键(如F5)把发生故障的软件迅速构建出来.构建过程应该能够记录文件之间的依赖关系,使得我们在修改了某处代码之后只有少数几个文件需要重新编译.能够

《Effective Debugging:软件和系统调试的66个有效方法》——第7条:试着用多种工具构建软件,并将其放在不同的环境下执行

第7条:试着用多种工具构建软件,并将其放在不同的环境下执行 有时我们可以通过改变环境来锁定一些难以捕获的bug.例如,我们可以用另外一款编译器来构建这个软件,也可以切换到其他的运行时解释器.虚拟机.中间件.操作系统或CPU架构上.由于那些环境可能会更加严格地检查输入数据,或能通过其结构来凸现程序中的错误(参见第17条),因此可以帮助我们发现原来很难找到的一些bug.如果程序不够稳定.总是发生无法重现的崩溃问题,或移植起来不太顺利,那就应该试着把它放在另外一种环境下进行测试,这使得我们能够使用更为

《Effective Debugging:软件和系统调试的66个有效方法》——第8条:把工作焦点放在最为重要的问题上

第8条:把工作焦点放在最为重要的问题上 许多大型软件系统都含有数量极其众多的bug(有一些是已知的bug,还有一些则尚未发现).要想高效地进行调试,就必须把应该受到关注的bug与可以忽略的bug明智地区分开.这样做不是为了单纯地缩减事务清单中的未决事务,而是为了帮助我们开发出稳定.易用.可维护而且效率较高的软件,毕竟这才是公司给我们支付薪水的原因.为此,我们要通过事务追踪系统来设定各项事务的优先级(参见第1条),从而使自己能够把工作重心汇聚在优先级较高的那些事务上,并把优先级较低的事务忽略掉.下

《Effective Debugging:软件和系统调试的66个有效方法》——第15条:查看第三方组件的源代码,以了解其用法

第15条:查看第三方组件的源代码,以了解其用法 我们所要调试的代码之所以会出bug,通常并不是由于它使用的第三方程序库或应用程序本身有问题(参见第14条),而是因为它使用这些第三方组件时所采取的方式有误. 这种情况并不令人惊讶,由于这些软件本身是作为黑盒来与你所写的代码进行集成的,因此,你不太可能在它们之间相互协调.对于这类问题来说,有一个很有用的办法,就是去查看第三方程序库.中间件甚至是底层软件的源代码. 首先,如果想查明某个API为什么没有像你所期望的那样运作,或是想查明某条奇怪的错误消息是

《Effective Debugging:软件和系统调试的66个有效方法》——第10条:高效地重现程序中的问题

第10条:高效地重现程序中的问题 要想高效地调试程序问题,一个关键的因素就是要能够可靠且方便地重现它.这么说有三个理由.首先,如果我们总是能做到只按一个按钮就可以重现问题,那么自然能够专心地去寻找问题的原因,而不用再浪费时间去研究怎样才能把这个问题重现一遍.第二,如果我们可以方便地重现问题,那么也就能够同样方便地把问题描述出来,以寻求外人的帮助(参见第2条).第三,修复错误之后,我们可以把重现问题所需的步骤执行一遍,如果程序这次没有出现故障,那就证明我们对其所做的修复是正确的. 创建短小的范例或

《Effective Debugging:软件和系统调试的66个有效方法》——第19条:使调试任务自动化

第19条:使调试任务自动化 我们或许会找到很多个与程序错误有关的因素,但是却没有办法轻易推断出究竟哪一个因素才是致使程序出错的真正原因.为了把这个原因找出来,我们可以编写一小段例程或脚本,把有可能使程序出错的所有情况全都搜索一遍.如果待搜索的情况比较多,不便于手工进行搜索,但是却能够通过循环来进行遍历,那么就可以考虑对其加以自动化.例如,如果想遍历的是500个字符,那么可以通过自动化的脚本来实现,然而如果要把用户可能会输入的所有字符串全都尝试一遍,那么采用自动化脚本就不太合适了. 下面举一个例子

《Effective Debugging:软件和系统调试的66个有效方法》——第16条:使用专门的监测及测试设备

第16条:使用专门的监测及测试设备 调试嵌入式系统及系统软件的时候,我们可能要对从硬件到应用程序的整个计算栈进行分析.调试工作一旦深入硬件层面,我们就需要关注电流的微小变化以及磁矩的对齐情况等细节.在大多数情况下,可以通过强大的IDE以及一些追踪软件与日志记录软件来探查这些问题,然而有的时候,就连这些工具也帮不上忙.这通常发生在软件与硬件有所接触的场合,也就是说,虽然你认为你所写的软件能够像预期的那样运作,但是硬件却有着它自己的处理方式.例如,你把正确的数据写入磁盘,再将其读取出来,却发现这些数

《Effective Debugging:软件和系统调试的66个有效方法》——第18条:从自己的桌面计算机上调试那些不太好用的系统

第18条:从自己的桌面计算机上调试那些不太好用的系统 Jenny和Mike在谈论各自的调试经历.Jenny说:"我不喜欢在客户的计算机上面工作,要用的工具都没装,浏览器里也没有我收藏过的书签.这真是太麻烦了.我访问不了自己的文件,计算机上的按键绑定和快捷键,设置得也都不对."Mike惊讶地看着她说:"快捷键?你还有快捷键可用,这都算不错的了.我调试的那台计算机,连键盘都没有!" 如果你在工作时无法使用自己配置好的这台计算机,那么工作效率确实会大幅降低.除了Jenny

《Effective Debugging:软件和系统调试的66个有效方法》——第20条:开始调试之前与调试完毕之后都要把程序清理干净

第20条:开始调试之前与调试完毕之后都要把程序清理干净 如果你要调试的软件有10个地方可能出错,那么这些错误就会有上千种(2的10次方)表现形式,如果有20个地方可能出错,那么表现形式就会高达一百多万种(2的20次方).因此,在调试的时候,应该优先关注当前区域中最容易解决的那些问题.例如: 能够借助工具而找到的问题(参见第51条). 程序在运行时所产生的警告(例如,可恢复的断言失败). 读起来比较费解,而且与你要调试的bug有所关联的那些代码(参见第48条). 标有XXX.FIXME及TODO字

《Effective Debugging:软件和系统调试的66个有效方法》——第4条:从具体问题入手向上追查bug,或从高层程序入手向下追查bug

第4条:从具体问题入手向上追查bug,或从高层程序入手向下追查bug 要想确定问题的来源,通常有两种办法.一种是从问题的具体表现入手,向上追查其来源,还有一种是从应用程序或系统的顶层入手,逐步向下探查,直至找到其根源.对于某种类型的问题来说,其中一种方法的效果通常要比另一种更好,但是如果你在采用某个方法时遇到了困境,那么不妨试试另一个方法. 如果问题表现得很明确,那我们就应该从发生问题的地方入手,向上追查bug.这可以分成三种情况. 第一种情况是程序崩溃.在这种情况下,为了便于排查问题,我们通常