作为本期关于程序员工具专题的一部分,Queue的编辑们决定就调试这一主题进行一次非正式的网络调查。我们请您告诉我们您使用的工具以及您如何使用它们。我们还收集了一些关于那些难以追踪的bug的故事,这些bug有时会让我们想要转行。
我们预计会有各种各样的工具,甚至希望有一些令人兴奋的自制工具,这些工具对其他开发者也会有用。有趣的是,我们发现大多数人使用开发环境的标准调试器中相当直接的功能。更令人惊讶的是,最常见的调试技术仍然是在代码中手动插入跟踪语句。也许调试器的设计者应该注意到这一点。
闲话少说,以下是引起我们兴趣的投稿。
— Terry Coatta,Queue 顾问委员会成员
最喜欢的调试工具: gdb
该工具特别擅长: 线程调试和跨平台调试
该工具不擅长: 可视化调试器,基于Windows/X。不喜欢xxgdb调试器(不好用)。
调试哲学: 解决方案就在其中,如果你找不到bug,那就向别人——任何人——解释发生了什么,当你解释的时候答案就会出现。否则,用手写出这段代码并比较结果。
最糟糕的bug: 一个只有在开启调试时才会发生的竞态条件。
最喜欢的调试工具: C语言用printf(),Java用System.out和println()
该工具特别擅长: 运行时调试器能告诉你的任何事情,一个位置恰当的print语句都能告诉你。而且它们几乎在程序运行的每个系统上都能完全正确地工作。此外,如果你正确地构建了你的应用程序,当用户遇到问题时,你可以为他们提供一种自行调试的方法。这几乎总是为你们双方节省时间。
该工具不擅长: 不幸的是,你必须重新编译和重新运行才能添加新的print语句。通过打印输出进行调试需要专注——如果你停止在你的代码中写入printf()语句,你将停止从它那里获得反馈。
调试哲学: 尽早且频繁地打印(或记录日志)。第二个好的原则可能是:声明你的意图。如果用户在日志文件中看到一条消息,上面写着:“指针
最糟糕的bug: 总的来说,bug并不难找到。如果我的代码变得如此复杂以至于我找不到bug,那么真正的bug是我的设计。那时我开始计划重写。
话虽如此,我花费最多时间的bug似乎总是与并发处理有关(“为什么会发生这种奇怪的死锁情况?”)或与性能有关(“为什么这个例程比我预期的要慢得多?”)。这些bug似乎很困难,因为它们发生在系统行为与开发人员的期望之间存在差异时,也就是说,“奇怪”的死锁只是你从未预料到的死锁。
另一个好例子: 当J2EE/EJB 2.0刚推出时,我们完全困惑为什么有些方法会花费10到20秒才能执行。我们的算法似乎没有任何问题。最终我们开始观察应用服务器和数据库之间的网络接口。每次方法运行时,它都会从数据库传输10到20Mb的数据!那绝对是一个“啊哈!”时刻。那也是我们决定J2EE可能毕竟不是很有用的时候。当您需要从数据库中获取一百个事物的列表时,您希望执行单个查询,而不是每个项目一个查询。
最喜欢的调试工具: truss
该工具特别擅长: 向您展示应用程序实际上在Solaris机器上做什么或寻找什么。特别适用于您没有源代码的应用程序。
该工具不擅长: 我希望能够看到库调用以及系统调用。Linux有工具可以做到这一点:strace和ltrace。
调试哲学: 什么哲学?有bug,而且还会一直有bug。开发人员不会把它们全部找出来,所以我们必须在它们出现时处理它们。通常我们没有能力对它们做任何事情,所以我们只能凑合着找一个变通方案。
最喜欢的调试工具: Websphere Studio Application Developer
该工具特别擅长: 构建基于J2EE的企业应用程序的最佳IDE。创建和部署企业应用程序的过程被最大程度地简化。此外,它非常适合从现有的java文件创建Web服务。
该工具不擅长: 创建容器管理的关联关系(CMRs)。有时会复制外键,所以必须有人手动更改它们。
调试哲学: 使用JUnit测试脚本进行测试。通过将服务器置于调试模式并逐步执行可能出现问题的可疑路径,同时查看变量值来进行调试。
最糟糕的bug: 尝试将EJB QL添加到名为“Group”的现有容器管理持久性(CMP)中。我们无法添加EJB QL,但最终通过更改CMP的名称解决了这个问题。“Group”是SQL中的保留字,因此同名的CMP非常有问题。
最喜欢的调试工具: Sabre-C和Perl内置调试器
该工具特别擅长: 当使用C语言时,我更喜欢Sabre-C。它是一个C语言解释器,允许您将对象模型与通过解释器运行的代码混合使用。我们正在构建大型可执行文件,链接时间长达40分钟。
该工具不擅长: 它对经典C语言开发人员的噩梦——查找内存泄漏——没有太大帮助。
调试哲学: 我的调试哲学30年来变化不大。插入断点并执行直到它捕获。检查进程状态和外部变量。早在专用机器时间的黑暗时代(1970年代的DEC PDP-11),我们会在可执行文件中插入一个自旋指令来实现同样的事情。
最糟糕的bug: 遇到一个bug,它只存在于IBM AIX版本的稍微修改过的Bill Joy的diff程序中。IBM更改了内置函数之一在错误时返回的指针的行为。花了三个星期——真是痛苦。最棒的部分是试图理解Bill Joy的代码。他可以写出我见过的最紧凑、最晦涩的C代码。在一个排序函数中,除了一个简洁的双字短语:“C 273”之外,没有任何注释,该短语指的是通讯的一期,他在其中找到了这种非常高效但极其复杂的排序算法。
最喜欢的调试工具: TextPad
该工具特别擅长: Textpad是一款通用的程序员ASCII编辑器,对于那些使用多种语言工作的人来说尤其有用。语法文件适用于多种语言。
该工具不擅长: Textpad不适用于大型项目。它只适用于小型程序和程序片段。
调试哲学: 找到展示相同问题的最小、最简单的程序。
最糟糕的bug: 我认为允许的C++代码无法工作——结果证明它是有效的C++代码。(我相信当时的版本是Version 5。)Metrowerks CodeWarrior被证明有一个bug(与我的“不要责怪编译器”规则相反!)。
最喜欢的调试工具: Visual Studio
该工具特别擅长: 实时重新构建,自动定位并步入库源代码
该工具不擅长: 由于调试程序的重复重绘,调试屏幕操作代码。
调试哲学: 确定作为根本原因的事件/动作,然后逐步执行该事件/动作的处理。
最糟糕的bug: 库中一个不正确的类型转换/realloc,本应是sizeof(foo)而不是sizeof(bar)。结构的大小非常接近,以至于程序在部署三年后才开始崩溃,当时管理的资源数量随着时间的推移缓慢增加。经过三天的长时间调试后,问题得到解决。
最喜欢的调试工具: VC .Net Debugger
该工具特别擅长: 命中计数断点
调试哲学: 缩小到问题点的区域。
最糟糕的bug: 丢包。请求数据包响应在等待响应开始之前就已到达。
最喜欢的调试工具: Rational Purify
该工具特别擅长: 识别潜在的内存泄漏并结合C/C++处理泄漏。还指出指针(悬空指针和空指针)与目标代码断言的误用。
该工具不擅长: (1) 缺乏常见的跟踪功能,(2) 也缺乏可视化调试功能;(3) 不可定制。
调试哲学: 最好的调试工具是您编写的由您的团队使用的跟踪/日志例程。第三方工具仅擅长识别非常小众的错误子集。同行评审和互相阅读彼此的工作是最好的调试工具。
最糟糕的bug: 与文件句柄/描述符不足相关的资源分配问题。最终通过运行Purify解决,然后我们对某些竞争资源发出独占锁以避免竞争条件。
最喜欢的调试工具: The Debugger 和 gdb
该工具特别擅长: 找到其他调试器找不到的所有东西,例如具有用于触发停止或简单日志记录的逻辑方程的内存监视点。
该工具不擅长: 理解预测分支以及从C/C++源代码到优化的汇编代码,尤其是展开循环和向量处理。
调试哲学: 从一开始就理解硬件,并编写简单明了的代码。
最糟糕的bug: 花了五个星期才弄清楚为什么中断会冻结鼠标,并发现时钟中的八个相位正在驱动特定于应用的集成电路 (ASIC),其中只有一个相位允许从流媒体到元数据的正确传输。
最喜欢的调试工具: Visual Studio
该工具特别擅长: Visual C++ 6.0 调试器是我用过的最好的调试器,尤其是在多线程代码方面。它速度快、准确、能很好地处理动态链接,并且与IDE的其余部分集成得非常好。
该工具不擅长: 调试非Windows代码;调试模板化的C++(但话说回来,目前市场上没有调试器能很好地处理模板)。
调试哲学: 找到重现问题的最简单测试用例,并尽量让你的代码尽早失败。
最糟糕的bug: 20世纪80年代末,在一个运行在数百个处理器上的国际象棋程序中出现了一个间歇性的同步错误。程序每隔几天就会死锁。我从未找到它,这也是我退出大规模并行计算的原因之一。
最初发表于Queue杂志第1卷,第6期—
在 数字图书馆 中评论本文
Charisma Chan, Beth Cooper - 谷歌分布式系统中的调试事件
本文介绍了2019年对谷歌工程师如何调试生产问题进行的研究成果,包括工程师在不同组合中使用的工具类型、高层策略和底层任务,以有效地进行调试。它考察了用于捕获数据的研究方法,总结了生产调查的常见工程过程,并分享了专家如何调试复杂分布式系统的例子。最后,本文将这项研究的谷歌具体性扩展到提供一些您可以在您的组织中应用的实用策略。
Devon H. O'Dell - 调试心态
软件开发人员将35-50%的时间用于验证和调试软件。调试、测试和验证的成本估计占软件开发项目总预算的50-75%,每年超过1000亿美元。虽然工具、语言和环境减少了个人调试任务所花费的时间,但它们并没有显着减少调试的总时间,也没有减少调试的成本。因此,过度关注开发过程中消除bug是适得其反的;程序员应该拥抱调试作为解决问题的一种练习。
Peter Phillips - 使用跟踪增强调试
创建一个模拟器来运行旧程序是一项艰巨的任务。您需要透彻了解目标硬件以及模拟器要执行的原始程序的正确功能。除了功能正确之外,模拟器还必须达到以原始实时速度运行程序的目标性能。实现这些目标不可避免地需要大量的调试。bug通常是模拟器本身中的细微错误,但也可能是对目标硬件的误解,或者是原始程序中实际已知的bug。(也可能是原始程序的二进制数据已变得微妙地损坏或不是预期的版本。)