当前位置: 首页 > news >正文

Diomidis Spinellis:有效的调试

\

关键要点

\
  • 有效的调试取决于正确的策略、方法、实践、工具和技巧。 \
  • 找到 bug,并且能够通过详细的日志记录、故障报告、防御性编程和专业工具重现它。 \
  • 修复一个故障之后,能够发现和修复类似的问题并采取一些措施确保他们以后不再发生。 \
  • 给同事详解一个失败的代码片段以帮助你发现其中的问题。 \
  • 通过静态和动态程序分析工具精确定位难以捉摸的 bug。
\

Diomidis Spinellis 撰写的《有效的调试》一书描述了66种不同的方法来有效地调试应用和系统。其中提供了定位与解决错误的方法、策略、技巧和工具,同时也给出了在不同环境下应用他们的案例。\

InfoQ 的读者可以下载有效的调试的摘要。\

InfoQ 就调试软件的不同方法采访了 Spinellis,讨论了如何使用不同的编译器和执行平台来帮助调试软件与重现错误;如何在发现和解决一个错误后同时解决相同的错误;为什么使用图形用户界面来调试更好;如何寻求同事的帮助;如果你想要找到错误的代码,使用动态分析工具和静态程序分析工具来调试代码;程序员如何提升自己调试的技巧。\

InfoQ:是什么促使你写了这本关于调试的书?

\
\

Diomidis Spinellis:我热爱编码是因为它挑战我的思维。几年前,我意识到调试是一个更为艰难和有趣的任务。编码主要工作是将需求翻译为程序语句,而调试涉及许多不同的创造性方法。我还发现,与阅读代码相比,很少有人教导或写作调试相关的东西。\

从那时开始,每当我调试一些东西的时候,我就观察自己,并记录下我使用的方法。我对于在清单中记录下如此多的方法感到很惊讶,列表清单越来越长。因此,我意识到,如果我将列表编撰成册,很多人会受益。有效编程系列完美地匹配了我已经放在一起的材料,很荣幸我的建议被接受为该系列中的一个项目。

\

InfoQ:这本书的目标读者是那些人?

\
\

Diomidis Spinellis:我这本书的目标读者为中级和高级程序员:他们知道如何写代码,但还没有认真正式培训过如何调试自己的代码。如今代码往往最终为一个正在运行的服务(或应用),而不是执行一些任务然后就终止的程序。以至于,我这本书的目标读者也包括开发人员和每天要面对时不时的调试任务的系统管理员。\

书中的一些条目,如使用断点或调试程序的堆栈遍历命令,是相当基础的知识。我在这本书中囊括这些内容是因为我发现,程序员经常采用的调试方法与他们所使用的平台有关。因此,即使一些高级程序员也可能不熟悉其他人经常使用的调试方法。

\

InfoQ:此书中描述了哪些调试软件的不同种类的方法?

\
\

Diomidis Spinellis:在最开始的时候,这本书包含了三章内容,囊括了适用于大多数调试任务的条目。也包含高级别策略,涉及到许多其他方法来解决特定问题,例如逐步缩小良好工作的系统和一个失败系统之间的差异。当你调试问题的时候,通用的方法和做法帮助你表现得更好。这个类别中的一个重要的做法是自动化你的测试场景和任务。这样做不仅提高了你的工作效率,同时会常常为你创造出设计更为复杂的调试方案的机会。接着,你可以将通用工具和技术应用在不同的调试任务中。这些措施包括有效地利用 Unix 命令行工具、编辑器、版本控制系统和追踪工具。\

接下来的四个章节叙述了针对特定平台的方法:利用一个调试器,如何通过调整程序的代码来逐步逼近 bug,以及可以使用哪些方法来构建和运行你的系统。虽然这些方法的具体细节取决于你的平台,但大部分平台都会提供工具,如能够帮你找出含有错误的具体类的静态程序分析器和剖析工具。

\

InfoQ:如何使用不同的编译器和执行平台帮助调试软件?

\
\

Diomidis Spinellis:汽车制造商在所有条件下测试新车型:从北极苔原到撒哈拉大沙漠。这有助于他们发现 bug,这是无法通过开着汽车围绕汽车厂的停车场转圈来找到的。类似的想法适用于软件。现代编程语言和 API 是庞大而复杂的;不同的编译器和运行平台会以不同的方式处理代码。例如,一个编译器可能会愉快地接受导致你的程序有不确定表现方式的代码,另一个遇到相同的代码将会发出警告。这同样适用于 API 的实现:当参数错误的时候一个实现方式可以产生令人困惑的行为,而另一个可以立即报出有用的异常。甚至 CPU 的多样性也能帮助你:在一些 ARM CPUs 中的奇存储地址访问双字节值会产生一些故障,而另一些可能会导致非原子行为。\

一个有趣的例子发生在上世纪80年代,Linux 运行在当时流行的 VAX 架构,它有一种特性是访问空指针时会返回 0。当 Sun 公司改变了系统中的那种写法后,产生了一个异常,随之一系列的 bug 浮出了水面。

\

InfoQ:你能做些什么来重现错误?

\
\

Diomidis Spinellis:一旦你能够准确高效地重现一个错误,你就已经完成了修复这个 bug 所需的一半的工作量。这里有两个主要的方法。其中包括从一个导致失败的复杂场景开始,逐步简化它。另一种方法是,猜测导致失败的相关原因并构建一个场景重现它。这两种方法都可以推导出一个能够引起该故障的微小简单的测试用例。详细的日志记录、故障报告和防御性编程往往能为你的行动提供引导。\

在手头一个小的测试用例的情况下,可以大幅削减调试问题所需的工作。你一步通过数十行代码而不是数百行,你检查满屏的日志数据而不是自动过滤数据流,你放大仔细观察几个关键变量而不是观察程序的全局状态,你可以在数秒钟之内运行这个测试用例而不需要花费数个小时。\

我最近在调试一些处理接近 1TB 大小文件数据的程序所产生的错误,这些程序运行可能需要数天。为了解决这些问题,我首先为处理程序添加了许多可以生成详细日志的调试选项。我还添加了许多内部一致性检查和相应的报告。因而我能够将问题相关的数据缩小到 1GB,处理不超过1分钟就能够产生错误。然后,我写了一个小过滤器,将可能导致失败的记录从庞大的数据中隔离出来。进一步将可疑数据减少到几KB大小,然后我可以手动检查,以查明问题所在。为了确保我的解决方案是正确的,并会在未来保持如此,我还添加了一个测试用例模拟错误并验证程序能够正确处理它。\

最后我要说的是,当遇到一些难以重现的 bug 的时候,我要介绍一些专门的工具和方法。这些方法包括验尸调试(使用崩溃的程序的内存映像调试你的代码)、捕获和复制工具(他们对工作在多线程代码的非确定性的 bug 有神奇的效果)和后台实时调试(当你跨过一个很少失败的函数调用是非常有效的)。

\

InfoQ:解决错误之后,如何才能找到和修复其他类似的错误?

\
\

Diomidis Spinellis:这个问题非常好。相同的故障通常会发生在一个以上的地方。造成这种现象通常是因为代码随意地复制粘贴,或者因为一个 API 很容易被滥用了。一旦发现故障,了解它为什么发生,发现和解决类似的问题,并确保其他类似的问题不会在未来出现,这是职业精神的标志。\

通常,你可以通过使用能够匹配任何可以代码的正则表达式搜索并找出相似的错误。你可以用你喜欢的编辑器或 IDE 做到这一点,但我更喜欢使用 Unix 的命令行工具 grep。通过在管道中指定能够匹配那些错误的模式并上报忽略的模式(就是我传递给 grep -v 命令实例的东西),我可以很容易地缩小我的搜索范围。从而修复这些问题,这节省了我和我的同事们的时间,使得我们可以调试更多潜在的错误。\

确保类似的错误不会在未来发生是一件更具挑战性的事情。如果故障是与 API 函数的误用有关,一招是创建一个包装函数,用它来捕获和报告测试过程中的错误。你还可以查看是否可以在持续集成的过程中配置一个代码分析工具捕获这样的错误。

\

InfoQ:为什么使用图形用户界面来调试更好?

\
\

Diomidis Spinellis:虽然我喜欢命令行界面,同时当我使用它们时我觉得自己非常有生产力,但我依然认为调试工作是少数几个使用 GUI 会更有效率的工作之一。这样认为的原因是,同时呈现多种数据对调试工作很有帮助:源代码、局部变量、调用栈、日志消息甚至CPU寄存器。图形界面让你对所有这些显示在独立窗口的数据一目了然,并能够同时更新。另外,你的鼠标指向一个变量、一行代码或者调用堆栈帧通常比手打指令更加高效。\

有时候,我发现自己正在调试一个系统,缺少一个 GUI 调试器。我就会通过配置我的命令行窗口和编辑器窗口创造出一个简陋版本的 GUI 调试器。一个窗口可能包含相关的源代码,一个列出测试数据,另外一个显示持续更新的日志文件,还有一个可以提供一个命令行提示符。

\

InfoQ:如果你想要发现代码中的错误,如何寻求同事的帮助?

\
\

Diomidis Spinellis:最有效的方法可能就是橡皮鸭技术。它涉及到向其他人解释你的代码是如何运行的。通常情况下,在你解释的中途,你就会惊呼“哦,等等,我怎么这么傻的,这就是问题所在!”,问题解决。发生这种情况时请放心,这不是一个你粗心大意忽略了的愚蠢的错误。通过向你的同事解释代码,你调动到了大脑的不同部分,而这些部分精确定位到了问题。在大多数情况下,你的同事扮演了一个很小的角色。这就是该技术名字的由来:向橡皮鸭解释问题会有同等的效果。\

当然还有其他更正式的方式让同事帮助你捕捉错误,如结对编程和代码审查。在某些情况下,你甚至可以搞角色扮演。例如,如果你正在调试一个通信协议,可以扮演一方,一位同事扮演另一方,然后你可以轮流试图打破协议(或尝试使其工作) 。这样的方式对于其他领域包括安全性(你可以玩 Alice 和 Bob)、人机交互和工作流同样有效。在这里向周围传递物体,如一个“编辑”令牌就可以帮助你。

\

InfoQ:你对于使用静态程序分析工具有什么建议?

\
\

Diomidis Spinellis:使用静态程序分析工具分析你的程序可以发现许多类型的问题,从格式化故障(想想 Python 代码中的 Pylint)到可能会导致程序崩溃或行为异常的代码结构(Coverity 和 FindBugs 是这个领域中两个著名的工具)。此类别中的另一个被低估的工具是你的编译器或解释器。几个命令行选项(例如许多编译器的 -Wall、-Wextra 和 -Wshadow)或声明(例如JavaScript代码中的 “use strict”)可以触发许多有用的警告消息。\

如果你在这个领域是新手,我的建议是先配置能够产生与实践中一致的产生警告的工具。例如,如果由于某种原因,你已经有很多仅有首字母大小写不同的方法,你可能会禁用 FindBugs 的“混乱的方法名”的警告。如果警告级别可以调节,选择不会让你淹没在警告中的最高级别的警告水平。然后有条不紊地移除所有其他的警告。这可能会解决你要找的问题,也更容易看到未来的其他故障。最后,确保你的构建和持续集成进程中运行了静态程序分析工具,这样你的代码就不会在将来出现同样的问题。

\

InfoQ:如何使用动态分析工具来调试代码?

\
\

Diomidis Spinellis:动态程序分析工具能够提供有关你的代码的终极真理,因为他们在程序运行时进行分析。你通常在这样的工具下运行你的程序,并查看它产生的报告。例如,Valgrind 可以发现内存泄露、非法内存访问以及使用未初始化的内存。执行如内置到现代IDE或者成为独立工具(如VisualVM、JProfiler 和 Java Mission Control)的 profiler 能帮助你定位到性能黑洞。其它工具,如 Intel Inspector,能让你发现死锁和竞争条件。\

一个常常被忽略的动态分析工具类型是执行追踪器。他们会显示程序与操作系统交互或运行时系统的库的所有信息。这类工具包括 ltrace、strace、ktrace、truss 和各种 Unix 系统中的 SystemTap 以及 Windows 中的进程监视器。这些工具的优点是,你可以在不需要源码的情况下在一个可执行程序中应用他们。他们通过揭示一个特定的程序是如何运行失败的而无数次帮了我的大忙。

\

InfoQ:程序员可以做些什么来提升他们的调试技巧?

\
\

Diomidis Spinellis:飞行员经常以飞行小时数来报告他们的经验和熟练程度。调试(和编程)技巧还取决于你对这些活动花费了多少时间。然后,铭记调试不是向错误随机扔飞镖可以使你的调试技术和效率有长足的进步。这包括细心谨慎选择最好的方法来解决这个问题、在能使你有生产力的环境中持续投入、配置和运用特殊的工具和学习并深刻理解你在代码中使用的语言特性和 APIs。

\

关于本文作者

\

87d7b436e13e24579b17ec4fae2d9876.jpgDiomidis Spinellis 是雅典经济和商业大学的科学与技术管理系的一位教授。他写过多本广受赞誉并被广泛翻译的书:“阅读代码”、“代码质量:开源透视”与2016年发布的“有效的调试”。Spinellis 担任了十年的 IEEE 软件编委会成员,编撰了规范的“贸易工具”栏目。他还为 Apple 的 OS X 与 BSD Linux 贡献了代码,同时也是 UMLGraph、CScout 和其他开源软件包、库、工具的开发者。他拥有伦敦帝国学院的软件工程工程硕士和计算机科学博士学位。 Spinellis 是 ACM 和 IEEE 的高级成员。从2015年1月起他成为了 IEEE 软件的主编。在以前的生活中,他四次成为国际 C 语言混乱代码大赛的冠军。如今,他试图让他的代码保持无聊。Twitter:@CoolSWEng

\

作者:Ben Linders,阅读英文原文:Q\u0026amp;A with Diomidis Spinellis on Effective Debugging

相关文章:

  • ListView的简单使用
  • vue3技术 watch时 value问题
  • 最大流学习笔记(1)
  • vue3 watchEffect 函数
  • Apaceh 多虚拟主机多站点配置两种方案
  • ubutnu安装geany
  • vue3生命周期钩子函数
  • 每天一个linux命令(11):nl命令
  • ABP文档 - 本地化
  • react-native 安卓真机环境搭建
  • vue3 自定义hook函数 和 toRef
  • git add . 的时候遇到warning: LF will be replaced by CRLF in ...... 解决办法
  • vue3 祖孙组件通讯传值 provide 与 inject 以及 响应式数据的判断
  • Unity3D 学习——入门资料整理
  • vue3父子组件传值 以及注意事项
  • php的引用
  • 【跃迁之路】【519天】程序员高效学习方法论探索系列(实验阶段276-2018.07.09)...
  • Angular6错误 Service: No provider for Renderer2
  • Essential Studio for ASP.NET Web Forms 2017 v2,新增自定义树形网格工具栏
  • github指令
  • Mocha测试初探
  • Quartz初级教程
  • underscore源码剖析之整体架构
  • Wamp集成环境 添加PHP的新版本
  • windows下使用nginx调试简介
  • yii2中session跨域名的问题
  • 构造函数(constructor)与原型链(prototype)关系
  • 聊聊springcloud的EurekaClientAutoConfiguration
  • 漂亮刷新控件-iOS
  • 想晋级高级工程师只知道表面是不够的!Git内部原理介绍
  • 优化 Vue 项目编译文件大小
  • 原生Ajax
  • ​马来语翻译中文去哪比较好?
  • ​软考-高级-系统架构设计师教程(清华第2版)【第9章 软件可靠性基础知识(P320~344)-思维导图】​
  • #1014 : Trie树
  • #NOIP 2014# day.1 T3 飞扬的小鸟 bird
  • #NOIP 2014#Day.2 T3 解方程
  • ${factoryList }后面有空格不影响
  • (11)工业界推荐系统-小红书推荐场景及内部实践【粗排三塔模型】
  • (7)STL算法之交换赋值
  • (WSI分类)WSI分类文献小综述 2024
  • (分享)自己整理的一些简单awk实用语句
  • (附源码)springboot掌上博客系统 毕业设计063131
  • (附源码)计算机毕业设计SSM在线影视购票系统
  • (算法设计与分析)第一章算法概述-习题
  • (学习日记)2024.01.09
  • (转)大型网站的系统架构
  • (转)详解PHP处理密码的几种方式
  • .[hudsonL@cock.li].mkp勒索加密数据库完美恢复---惜分飞
  • .Net 路由处理厉害了
  • ??如何把JavaScript脚本中的参数传到java代码段中
  • @FeignClient注解,fallback和fallbackFactory
  • [ 云计算 | Azure 实践 ] 在 Azure 门户中创建 VM 虚拟机并进行验证
  • [100天算法】-不同路径 III(day 73)
  • [28期] lamp兄弟连28期学员手册,请大家务必看一下