当前: 首页 - 图书专区 - Effective Debugging:软件和系统调试的66个有效方法
Effective Debugging:软件和系统调试的66个有效方法


  在线购买
[希] 迪欧米迪斯?斯宾奈里斯(Diomidis Spinellis)著
978-7-111-56889-6
59.00
223
2017年05月26日
爱飞翔 译
计算机 > 数码/设计 > UI/交互设计
Pearson Education Inc.
85
简体中文
16
Effective Debugging: 66 Specific Ways to Debug Software and Systems
店面
Effective系列丛书








Google高级软件工程师Diomidis Spinellis融合自己30多年系统开发和软件调试的实战经验,深入探讨调试的策略、方法、工具和技巧
涵盖调试工作的方方面面,汇聚66条专业调试技巧,分步骤详细讲解,包含大量实用范例代码

每位软件开发者及IT人士都明白高效率的调试是多么重要。在开发者的日常工作中,最耗时间的环节通常就是程序的调试,而且相关的调试技术与技巧可能要花很长时间才能掌握。在本书中,Diomidis Spinellis对最为有用的调试方法、策略、技巧及工具进行系统的归类、整理与演示,以帮助有经验的程序员迅速掌握这些知识。

通过阅读本书,你将学到:
适用于各种软件故障的宏观策略及方法
在编写、编译及运行代码时可以使用的具体技术
如何最为充分地利用调试器所具备的能力
值得花时间来学习的一些通用技能与工具
一些高端的思路与技术,可以帮你摆脱调试时所遇到的困境以及过于复杂的bug
怎样把程序编写得更易于调试
在调试多线程的、异步的以及嵌入式的代码时所使用的特殊办法
怎样通过改进软件的设计、构建及管理来避开某些bug

内容简介
Spinellis有超过35年的编程经验,这些经验可以丰富你的调试技术,帮助你选出最适合解决当前问题的办法。他会用多个与具体厂商无关的范例来讲述通用的原则、宏观的策略、具体的技术、高效的工具以及能够提升调试效率的一些做法。
这66条专业的技巧,涵盖调试工作的各个方面,而且全都带有分步的解说与实际的代码,使你能够用来调试各种软件系统,尤其是用来调试那些因分布式组件和服务之间的复杂交互而引发的问题。
无论是单独发生的运行时错误,还是严重的企业系统故障,本书所提供的建议都可以帮你更加迅速且更加顺畅地把调试工作做好。


作者简介
Diomidis Spinellis
雅典经贸大学管理科学与技术系教授,经常讲授复杂系统的开发与调试技术。他是Google的高级软件工程师,也是FreeBSD committer团队的成员,并贡献了一些随OS X及BSD Unix系统而发布的代码。此外,他还开发了UMLGraph及CScout等流行的开源软件。Spinellis所写的《Code Reading》和《Code Quality》曾获得软件开发生产力奖。他是ACM及IEEE的资深成员。
我们在开发软件或对运行软件的系统进行管理的时候,经常会遇到故障。有些故障是因代码问题而引发的编译错误,这种故障可以在短时间内修复;还有一些故障则会使大型系统停机,这将给公司带来每小时数百万的损失(具体货币单位依情况而定)。要想成为一名优秀的专业人士,你就必须在发生故障时迅速找出背后的原因并加以修复。这正是调试的意义所在,也是本书所要谈论的主题。
本书是写给有一定经验的开发者看的,而不是一本介绍性质的读物。它假设读者能够理解用各种编程语言所写成的代码片段,并且会使用高级的GUI编程工具以及基于命令行的编程工具。另一方面,我会在书中详细描述调试技巧,因为我发现:即便是对某些开发方法很有经验的编程专家,也依然需要一些手把手的指导,才能够掌握其他的开发方法。此外,如果你已经花了至少几个月时间来调试一些颇具规模的软件,那么应该会更容易理解书中某些高级技巧所适用的场合。
本书所涵盖的范围
本书所要讲解的调试知识,包括与调试有关的策略、工具及方法。我们当前在开发并运作一款复杂的计算系统时,可能会遇到各种问题,而这些调试知识,则使大家能够应对这些问题。过去我们所说的调试,主要是指检测并修复程序错误,而当前却很少有哪个程序会孤立地运作,即便是一个很小的程序,也会与外部的程序库相链接(通常是动态链接)。更为复杂的程序会运行在应用程序服务器中,会调用Web服务,会使用关系型数据库及NoSQL数据库,会从目录服务器上获取数据,会运行外部的程序,会利用其他的中间件,也会纳入很多第三方的软件包。于是,要想令整个系统及服务正常地运作,就必须确保其中的组件不会发生故障,这些组件可能是由公司内部人员所开发的,也可能是由第三方所提供的,它们所在的主机或许分布在全球各地。为了应对这种局面,软件开发行业开始重视DevOps规程,这套规程旨在同时强调开发者和其他IT专业人员所应担负的职责。与之类似,本书想使读者在面对故障时也能够具备这样一种全面的观念,因为在面对一些极为困难的问题时,我们通常无法立刻判断出该问题到底是由哪一个软件组件所引发的。
本书的内容按照从一般到特殊的顺序来进行安排。首先讲解调试策略(第1章)、调试方法(第2章)以及调试时所用的工具与技术(第3章),这些知识使我们能够应对各种软件故障及系统故障。接下来,讨论在调试工作的各个阶段所用到的具体技巧,也就是在使用调试器(第4章)、编写程序(第5章)、编译软件(第6章)以及运行系统(第7章)时所用到的调试技巧。与多线程和并发有关的bug是很难寻找的,所以最后我们专门用一章(第8章)来讲解特定的调试工具及调试技术,使大家能够找出这些bug。
怎样运用书中的内容
你可以从第一页读起,一页一页往后翻,直到看完。但是别急,其实还有更好的读法。书里给出的建议可以分成以下三种。
策略与方法。这些内容包括我们在面对故障时所应具备的知识以及所应采取的做法。本书第1章和第2章里面的内容就属于这一类,此外,第5章中的很多技巧也可以归入此类。阅读并理解了这些内容之后,你需要在工作中对其加以运用,以便逐渐养成习惯。调试程序的时候,我们需要系统地反思自己所用的办法,如果某个办法行不通,那就应该把自己所经历的路线回顾一遍,这样可以帮助我们发现解决该问题的其他办法。
技巧与工具。这些内容值得大家投入时间去学习,它们主要出现在第3章里面,其他章节中的某些内容(如第36条)也同样可以归为这一类,我们可以在日常工作中运用这些内容来解决问题。大家应该花时间去学习这些内容,并且要逐步实践它们。这或许意味着我们要放弃自己所熟悉的调试工具,而去使用一些学习曲线较为陡峭但是功能上更加先进的调试工具,那些工具虽然一开始学起来比较困难,可是从长远来看,却能够帮助你成为调试方面的专家。
调试的思路。当我们遇到困难时,可以根据这些思路来找寻合适的技巧。这些内容不一定每天都会用到,但是当你遇到一个琢磨不透的问题时,它可以帮助你节省一整天(或者说至少几小时)的时间。比如,如果你不清楚自己所写的C和C++代码为什么无法编译,那么第50条或许能给你一些启发。大家应该快速浏览这些内容,使自己意识到它们可以在某些场合派上用场,等到真正需要使用它们的时候,再去详细研究。
本书对软件开发的其他方面所起的作用
本书里的所有条目都是针对故障的诊断与调试而写的,不过其中有很多建议同样可以用来缩减代码中的bug数量,并且可以使你在遇到这样的bug时能够更为迅速地将其修复。严谨的调试技术与优秀的软件开发方式之间能够形成良性的循环,因此,书中的建议对于你当前或者将来要面对的软件设计、软件构建以及软件管理工作,是可以起到帮助作用的。
设计软件的时候,应该遵循下列建议:
使用与其角色相称的高级机制(参见第47条和第66条)。
提供调试模式(参见第6条和第40条)。
提供对系统操作进行监控与记录的机制(参见第27条、第41条和第56条)。
提供一个选项,使得开发者可以用Unix命令行工具来编写与组件有关的脚本(参见第22条)。
把内部的错误暴露出来,使其表现为软件故障,而不要将其隐藏起来,使其成为软件中的不稳定因素(参见第55条)。
提供一种方式,使得开发者能够在软件发生故障之后获得内存转储(memory dump)信息(参见第35条和第60条)。
从数量和范围方面,尽量缩减软件在执行时的不确定因素(参见第63条)。
构建软件的时候,应该遵循下列建议:
征求同事的意见(参见第39条)。
为你所编写的每个例程创建单元测试(参见第42条)。
用断言来验证自己所做的假设是否成立,以及代码的功能是否正确(参见第43条)。
尽量把代码写得易于维护,也就是要写出易读、稳定且便于分析和修改的代码(参见第46条和第48条)。
在构建程序时避免不确定的因素(参见第52条)。
在对软件的开发及运作进行管理时,应该遵循下列建议(无论是要管理一个团队,还是只管理自己的流程):
用适当的事务追踪系统,把遇到的问题记录下来(参见第1条)。
对各种有待处理的事务进行分类,并排定其优先次序(参见第8条)。
把对软件所做的修改适当地记录在修订管理系统中,并且对该系统进行较好的维护(参见第26条)。
渐进地部署软件,使得我们可以在新旧版本之间进行对比(参见第5条)。
尽量采用各种不同的工具来开发,并试着把程序部署在各种环境中(参见第7条)。
经常对工具与程序库进行更新(参见第14条)。
如果使用了第三方的程序库,那么可以考虑购买该程序库的源代码(参见第15条);考虑购买一些较为完善的工具来锁定那些不太容易找到的错误(参见第51条、第59条、第62条、第64条及第65条)。
寻找专门的工具来调试硬件接口及嵌入式系统(参见第16条)。
使得开发者能够远程调试软件(参见第18条)。
对于消耗资源较多的故障诊断任务来说,要留出足够的CPU及磁盘资源(参见第19条)。
鼓励开发者之间通过代码评审及编程指导等手段进行协作(参见第39条)。
鼓励大家进行测试驱动开发(参见第42条)。
在构建软件的时候,要做性能分析、静态分析以及动态分析(参见第57条、第51条及第59条),并且要打造一套迅速而高效的构建流程与测试流程(参见第53条及第11条)。
对书中术语的说明
本书所说的fault(错误)一词,遵循ISO-24765—2010(Systems and software engi-neering——Vocabulary)标准,它的意思是:“计算机程序里面某个不正确的步骤、流程或数据定义。”这也称为defect(缺陷)。在日常工作中,我们把这叫做bug。与之类似,本书所用的failure(故障)一词,也遵循ISO-24765—2010标准,它指的是:“因系统或系统组件未能在规定限制之下执行所需功能而引发的事件。”故障可以表现为程序崩溃、程序冻结或是程序的结果有误。故障一词强调的是程序运行的后果,而错误一词强调的则是我们所遇到或使用的某个程序本身有问题。不过有的时候,大家也会用错误及缺陷这两个词来指代故障,ISO标准也承认了这一点。笔者在本书中会按照上述定义来区分这几个词,然而在语境比较明确的情况下,我通常也会用问题(problem)一词来指代错误(如“代码有问题”)或故障(如“可以重现的问题”),这样写能够使本书读起来更加流畅,而不至于变成一份法律文件。
Unix操作系统的shell、程序库以及工具,目前都可以运行在各种各样的平台上。笔者采用Unix一词来指代遵从Unix原则及API的任何一种系统,包括Apple的Mac OS X、各种GNU/Linux发行版(如Arch Linux、CentOS、Debian、Fedora、openSUSE、Red Hat Enterprise Linux、Slackware及Ubuntu)、直接从Unix继承而来的系统(如AIX、HP-UX及Solaris)、各种BSD衍生系统(如FreeBSD、OpenBSD及NetBSD),以及运行在Windows系统上的Cygwin。
本书中所列出的C++、Java或Python代码,针对的也是较新的编程语言版本,不过笔者会避开那些奇怪的或是刚刚推出的特性。
书里面会出现“你的代码”和“你的软件”这两种说法,它们指的是你正在调试的代码以及正在开发的软件。这两种说法听起来比较简洁,而且也暗含了一种对代码所有权的宣示,这对于软件开发人员来说是十分重要的。
笔者所说的例程(routine)一词,是指可供调用的代码单元,如成员函数、方法、函数、过程以及子例程等。
Visual Studio及Windows指的是Microsoft公司的相关产品。
修订控制系统(revision control system)及版本控制系统(version control system),是指像Git这样能够对软件配置进行管理的工具。
排版约定
Unix命令行选项使用--this这样的形式,与该选项等价的单字母选项使用-t这样的形式。Windows工具中的对应选项,使用/this这样的形式。
按键采用Shift-F11这样的形式。
文件路径采用/etc/motd这样的形式。
菜单操作采用Debug-New Breakpoint-Break at Function这样的形式。
为了缩短篇幅,笔者会省略C++代码中的std::限定符及std名称空间。
描述GUI(图形用户界面)工具时,笔者所说的功能针对的是撰写本书时所能找到的最新版本。如果你所使用的是另外一个版本,那么请参照对应的菜单或窗口来进行操作,也可以在那个版本的文档中查找对应功能的操作办法。值得注意的是,命令行工具的界面几十年来一直都比较稳定,而不像GUI工具那样,每个版本都会有一些新的东西。大家可以从这个现象中推出很多结论。
代码及勘误
范例代码及英文原版书的勘误表请参见www.spinellis.gr/debugging。
译者序
前言
致谢
第1章 宏观策略 1
第1条:通过事务追踪系统处理所有的问题 1
第2条:在网上确切地查询你所遇到的问题,以寻求解决问题的灵感 4
第3条:确保前置条件与后置条件都能够得到满足 6
第4条:从具体问题入手向上追查bug,或从高层程序入手向下追查bug 7
第5条:在能够正常运作的系统与发生故障的系统之间寻找差别 9
第6条:使用软件自身的调试机制 13
第7条:试着用多种工具构建软件,并将其放在不同的环境下执行 16
第8条:把工作焦点放在最为重要的问题上 20
第2章 通用的方法与做法 23
第9条:相信自己能够把问题调试好 23
第10条:高效地重现程序中的问题 26
第11条:修改完代码之后,要能够尽快看到结果 29
第12条:将复杂的测试场景自动化 30
第13条:使自己尽可能多地观察到与调试有关的数据 32
第14条:考虑对软件进行更新 34
第15条:查看第三方组件的源代码,以了解其用法 35
第16条:使用专门的监测及测试设备 37
第17条:使故障更加突出 40
第18条:从自己的桌面计算机上调试那些不太好用的系统 42
第19条:使调试任务自动化 44
第20条:开始调试之前与调试完毕之后都要把程序清理干净 46
第21条:把属于同一个类型的所有问题全都修复好 47
第3章 通用的工具与技术 49
第22条:用Unix命令行工具对调试数据进行分析 49
第23条:掌握命令行工具的各种选项及习惯用法 55
第24条:用编辑器对调试程序时所需的数据进行浏览 57
第25条:优化工作环境 59
第26条:用版本控制系统寻找bug发生的原因及经过 64
第27条:用工具监测由多个独立程序所构成的系统 67
第4章 调试器的使用技巧 71
第28条:编译代码时把符号信息包含进来,以便于调试 72
第29条:对代码进行单步调试 76
第30条:设置代码断点和数据断点 77
第31条:了解反向调试功能 80
第32条:查看例程之间的相互调用情况 83
第33条:查看变量及表达式的值,以寻找程序中的错误 84
第34条:了解怎样把调试器连接到正在运行的进程上 87
第35条:了解怎样运用核心转储信息来进行调试 89
第36条:把调试工具设置好 92
第37条:学会查看汇编代码及原始内存 95
第5章 编程技术 100
第38条:对可疑的代码进行评审,并手工演练这些代码 100
第39条:审读代码并与同事讨论 102
第40条:给软件添加调试机制 103
第41条:添加日志语句 107
第42条:对软件进行单元测试 111
第43条:用断言进行调试 114
第44条:改动受测程序,以验证自己的推想 118
第45条:尽量缩小正确范例与错误代码之间的差距 119
第46条:简化可疑代码 120
第47条:将可疑代码改用另外一种编程语言来写 123
第48条:改善可疑代码的可读性与结构 124
第49条:要清除bug的根源,而不仅仅消除其症状 128
第6章 编译时的调试技术 130
第50条:对生成的代码进行检视 130
第51条:使用静态程序分析工具 133
第52条:对项目进行配置,令程序能够以固定的方式构建和执行 138
第53条:对调试所用程序库及构建代码时所应执行的检查进行配置 141
第7章 运行时的调试技术 147
第54条:通过构建测试用例来寻找错误 147
第55条:令软件在遇到问题时尽早退出 151
第56条:检视应用程序的日志文件 152
第57条:对系统和进程所执行的操作进行性能评测 156
第58条:追踪程序的执行情况 160
第59条:使用动态程序分析工具 166
第8章 调试多线程的代码 169
第60条:通过事后调试来分析死锁问题 169
第61条:捕获并重现 176
第62条:用专门的工具来探查死锁与竞争条件问题 180
第63条:把不确定的因素隔离出来,或将其移除 186
第64条:检查资源争用情况,以解决与可伸缩性有关的问题 188
第65条:用性能计数器寻找伪共享问题 191
第66条:考虑用更为高级的抽象机制来重写代码 195
网上资源 203
十几年前,我看过Spinellis先生所写的《代码阅读》(Code Reading)和《代码质量》(Code Quality)两本书,特别喜欢这种全面讲解编程工作中某个领域的教程,这次又读到同一位作者所写的《Effective Debugging》,感觉依然很精彩。
这是一本在思路和技巧上都较为丰富的调试手册。
从思路方面来说,本书介绍了许多宏观与微观的调试办法。例如,在面对软件故障时,既可以从整体情况入手,进行自上而下的调试,也可以从具体故障入手,进行自下而上的调试,还可以考虑用高级的抽象机制、便捷的程序库、直白的算法、简洁的逻辑,乃至另外一门更为合适的编程语言,对bug繁多的代码进行改写——这些思路,都能在调试工作中给人以启发。
从技巧方面来说,本书介绍了众多的调试工具与手法:有些可以在程序运行之前,设置断点并对表达式与程序的状态做出断言;有些可以在程序运行之中,将各种调试机制与程序进行连接,并对其执行情况进行记录;有些则可以在程序崩溃之后,通过核心转储等信息来还原当时的情境。此外,作者还讲解了如何把这些前置、中置和后置技巧与版本控制系统、静态分析工具、动态分析工具及性能测评工具结合起来使用,以提升调试的效率。
全书的8个大类中含有66条技巧,这些技巧都是围绕着“重现bug——探查bug——解决bug”这一主线而展开的。针对这三个阶段,作者进行了详细的分步讲解,给出了很多实用的范例代码与建议,而且特别强调了如何才能稳定地捕获并重现bug,以便给后面两个阶段打下良好的基础。
本书或许还能促使大家思考另外一个问题,那就是:在修复完bug之后,怎样防止有人向程序中引入类似的bug?这可以从代码质量与测试两方面入手。提高代码质量,能够减少程序员对代码的误解,进而降低引入bug的概率;而对测试进行完善,则能够提前捕获很多问题,从而不会使这些问题逐渐积累成复杂的bug。这些理念,作者在书中也时常会提到。
总之,这是一本可以打开思路并拓宽眼界的书籍,大家不妨在自己惯用的调试环境之外,多尝试一下作者所介绍的其他技法、工具和语言,以求达到旧学与新知的融合。
翻译本书的过程中,我得到了机械工业出版社华章公司诸位编辑和工作人员的帮助,在此深表谢意。
由于译者水平有限,不足与疏漏之处请大家发邮件至eastarstormlee@gmail.com,或访问github.com/jeffreybaoshenlee/debugging-errata/issues留言,给我以批评和指教。

爱飞翔
计算机\软件测试
读者书评
发表评论



高级搜索
交互系统新概念设计:用户绩效和用户体验设计准则
成功的用户体验:打造优秀产品的UX策略与行动路线图
交互式系统设计:HCI、UX和交互设计指南(原书第3版)


版权所有© 2017  北京华章图文信息有限公司 京ICP备08102525号 京公网安备110102004606号
通信地址:北京市百万庄南街1号 邮编:100037
电话:(010)68318309, 88378998 传真:(010)68311602, 68995260
高校教师服务
华章教育微信
诚聘英才
诚聘英才