避免使用调试器有什么好处?


101

在我的职业生涯中,我注意到一些开发人员不使用调试工具,而是对错误代码进行抽查以找出问题所在。

虽然很多时候无需调试器就能快速找到代码错误是一项很好的技能,但是当调试器很容易发现诸如错字之类的小错误时,花费大量时间查找问题似乎效率不高。

如果没有调试器,是否可以管理复杂系统?这是明智的吗?使用“ 心理调试 ” 有什么好处?


19
乔纳森,您好,我已对您的问题进行了修订,以避免陷入困境,并保持开放性。

示例:考虑一个代码a = 6/3。,而不是键入错误,而是键入a = 6/2..现在,您在助记符级别。,ADD,JMP指令中进行搜索,然后发现除了2之外还有一个迭代,然后您发现除法器有一个错误的错字。现在您可以推断出始终使用调试器是多么荒谬。
EAGER_STUDENT 2013年

Answers:


153

从外部猜测似乎常常被我称为“调试您的思想”。从某种意义上说,这类似于大师无需看棋盘就能下棋的能力。

这是迄今为止我所知道的最有效的调试技术,因为它根本不需要调试器。您的大脑可以同时探索多个代码路径,从而产生比调试器可能更好的周转时间。

在短暂进入竞争性编程领域之前,我对这种技术并不了解,在这种情况下,使用调试器意味着浪费宝贵的时间。经过大约一年的竞争,我几乎完全开始将这种技术用作我的第一道防线,然后进行调试日志记录,并使用位于遥远第三位的实际调试器。这种做法的一个有用的副作用是,我开始以较慢的速度添加新的错误,因为在编写新代码时“调试在我心中”并没有停止。

当然,这种方法有其局限性,这主要是由于人们在可视化代码的多个路径方面的局限性。我学会了尊重自己的这些局限性,转向调试器来修复更高级算法中的错误。


27
+1我发现“通过猜测编程”是一个加载的短语。思维无可替代。OP没有解释的是“猜测”的有效性。我的怀疑是,这纯粹是猜测(即墙上的意大利面条),而是使用演绎推理。调试器有自己的位置,但是它们并不是演绎推理和简单理解代码的灵丹妙药。
比尔

8
@DJClayworth这并不完全准确:有时即使尝试使用一个好的调试器,尝试使用调试器也是一个糟糕的选择:您最终会浪费大量时间而没有完成很多工作。我马上想到的一个案例是解决并发问题。其他是调试具有高分支因子的递归算法,一些动态编程算法以及硬件中断服务程序。当然,当您真正需要调试器时不使用调试器是很愚蠢的,但是决定何时开始需要调试器是一个高度个人的选择。
dasblinkenlight 2012年

9
+1尽管我发现调试器对于某些类型的错误(尤其是在更复杂的算法中)非常有用,但是对于简单地了解代码确实无法替代
克里斯·布朗

7
@DJClayworth我故意提出一个比“在某些情况下不使用调试器更好的情况”更强的声明:我与竞争性编程的短暂接触告诉我,本能地寻求调试器对我而言不是最有效的行为。这些天来,我开始于(1)快速重新读取代码,以及(2)在(3)寻求调试器之前检查调试跟踪(如果可用)。在许多情况下,第三步是不必要的,因为我在步骤(1)或(2)中发现了问题,编写了重现该问题的单元测试,并编写了修复程序,所有这些都无需使用调试器。
dasblinkenlight 2012年

10
我认为您真正的意思是程序员应该具有调试顺序,而不是单击魔术师的“查找错误”按钮。调试器是一种非常强大的工具,但是您不必打开电锯来修剪树篱。
Spencer Rathbun

41

我对代码库了解得越多,就越不需要调试器(但是我仍然会检查报告的错误,这在任何推理中都是重要的线索)。

这是了解中小型复杂性的一种很好的工具,但是我经常发现它专注于细节而不是全局。一段时间之后,问题就出在这里:在更广泛范围内的交互中,其动态行为往往易于使用其他工具来理解(例如,在模块边界记录输入和输出)。


35

他们可能不是不好的程序员,但他们可能是效率低下的疑难解答者。

我倾向于遵循调试的建议:查找最难以捉摸的软件和硬件问题的9条必不可少的规则(David Agans),而这一规则完全落在“退出思考与观察”的指导下


12
我不同意,尽管我不会拒绝投票。作为delnan说,如果你能理解代码的作用,它可以更快地发现它在做什么错,而不是单步调试,并设法找到时出错。也就是说,当开发人员在通过阅读代码无法识别问题时拒绝使用调试器,这是一个很大的错误。

@Mark加上错误诊断问题并插入新缺陷的额外好处。
基思带来

11
@Mark Ba​​nnister-我明白你在说什么。让我修改一下,如果您一直在代码中寻找问题15分钟以上,请放弃并使用调试器,不要固执。
JohnFx 2012年

9
我认为一个好的程序员不应该依赖调试器。一旦他的见解失败-或定期确保他的见解仍在轨道上,这不应阻止他立即使用(如果可用),以确保他的见解仍在轨道上……
即将

1
@mark,除非您使用很小的代码库,否则我认为不可能理解每一行代码。我目前有95%的错误已按照您描述的方式解决,但更棘手的错误是您需要调试器的地方。
wobbily_col 2014年

31

任何工作都需要以正确的方式使用正确的工具。如果您有调试器,请使用它查看实际情况。大多数错误是由假设引起的。

我曾与拒绝使用调试器的开发人员合作,因为他们了解得更多。我曾经收到的经典回应是“崩溃不是我造成的,我整日检查代码(崩溃的地方),没有错”。(从db中读取的空值呢?)老板似乎认为这是一个很好的答复,但客户却没有。

我尽快下了那支队伍。他们的目的是简化工作,将一个10分钟的简单问题变成一个整日忙碌的问题。


18
+1“大多数错误是由假设引起的”是非常明智的话
ZJR 2012年

15
我认为所有错误都是由假设引起的。(看看我在那儿做什么?= P)
dan_waterworth 2012年

4
@ZJR:这就是为什么assert这么好。检查您的假设。经常检查。
Zan Lynx 2012年

@dan_waterworth:不正确。例如,这可能是一个错字。
Thomas Eding

13

关于调试实践的最佳指南是Steve McConnel的书Code Complete。第23章详细介绍了调试,我将从中总结出几点。

  1. 了解问题很重要,调试器的使用不能替代它。
  2. 猜测是错误的调试方法。如果您的同事真的在使用猜测,而不是在思考问题,那么他们做的不好。猜测工作意味着在代码中粘贴随机打印语句,并希望找到有用的东西。
  3. 如果您的同事真的不知道如何使用调试器(而不是选择不使用调试器),则是的,他们无能为力,就像一个不知道应该使用的语言语法的人一样。

2
虽然我同意您的大部分职位,但我认为不称职是不公平的。不使用调试器就可以进行开发,效率低下。有些人比其他人更了解调试器!
ChrisFletcher

我不会随便乱扔“无能”这样的词。我知道有人完全使用print语句进行调试,没有其他人可以做出他所做的贡献。
Mike Dunlavey,2012年

2
@MikeDunlavey那个人知道如何使用调试器并选择不使用它吗?精细。如果他们不知道,那我就坚持。
DJClayworth 2012年

2
随心所欲,该形容词可能会自动应用到您身上。然后您将了解-这是校园的东西。
Mike Dunlavey,2012年

9

很难说。调试通过猜测可能,如果你已经有了大概的错误是什么(传递给库函数,可能是无效的SQL等不正确的值)的想法工作。我承认有时我会在错误本身看起来很小或很明显的情况下执行此操作,例如“字符缓冲区太小”-堆栈跟踪向我显示了它失败的行,并且我不需要调试器来解决该错误。

始终这样做会适得其反,如果前几个“猜测”失败,则猜测可能是错误的问题解决策略,应该调用真正的调试器。通常,我会说使用调试器绝对没有错。

话虽这么说,但我一直在使用调试器很难正常工作的工具和环境,或者最小化和毫无用处的工具,因此不幸的是猜测通常是一种更好的方法。我使用了一些专有工具,这些工具甚至没有适当的调试器。我想如果一个人在这样的环境中工作太长时间,他们最终可能会失去对调试器的信任,而只能依靠猜测方法。


8

我对此主题的讨论没有提到“单元测试”感到惊讶。

因为我进行测试驱动的开发,所以我不会在调试器上花费很多时间。10年前,我曾经尽职尽责地调试一下调试器:

  1. 编写一段代码以确保其正常工作后,
  2. 当我收到错误报告以尝试诊断问题时

经过10年的测试驱动开发,我发现如果满足以下条件,我将成为一名程序员,效率更高:

  1. 编写代码之前先编写单元测试以确保正确编写代码
  2. 收到错误报告后,我会立即编写单元测试,以尝试复制并深入研究该问题。

允许计算机运行代码并验证结果的速度比我认为或逐步执行代码以从心理上验证结果的速度快数千倍,并且不会出错。

我仍然偶尔还是需要调试器,而且我仍然在进行代码的心理分析……但是很少,而且主要用于非常棘手的代码。


+1添加打印语句并重新运行测试然后使用调试器通常更快。
Winston Ewert 2012年

@ winston-通常,启动调试器比编写多个打印语句要快得多,直到找到有问题的代码的位置为止。一切取决于。简单的问题通常以您描述的方式更快地解决,但是复杂的问题是您需要调试器的地方。能够同时使用两者比严格遵守任何绝对原则要好。
wobbily_col 2014年

7

就个人而言,我尝试通过以下方式最大程度地减少调试器的使用:

  • 使用静态检查器和类似的编译器选项,仅通过分析代码即可暗示可能的错误源
  • 以尽可能的功能样式编写具有尽可能少副作用的代码,并尽可能消除可变状态
  • 以最小的合理粒度编写单元测试
  • 不吞咽异常

当然,每个人都会出错,因此即使以这种方式编写程序,如果测试失败,我也会使用调试器检查中间表达式的值。但是,通过遵循上述原则,可以更轻松地找到缺陷,并且调试并不意味着痛苦,不确定的过程。


6

尽可能使用调试器。调试器将只是解决问题(哦,瞧,我们没有检查这个值),或者提供了大量上下文,这些上下文在分析相关代码时非常有用(哇,堆栈完全搞砸了,我会(这是一个缓冲区溢出问题)。


5

调试是一种非常有用的工具,可用于在运行时检查代码中对象和变量的状态。

如前面的答案中所述,调试非常有帮助,但是在某些情况下调试是有限的。

根据我的经验,我发现使用调试器非常有用,因为它有助于揭示我对代码状态所做的错误假设。有些人并不擅长阅读代码以查找错误,因此调试可以帮助揭示您或其他开发人员对代码状态所做的错误假设。

也许您希望参数在传递给方法时永远不会为空,所以您永远不会检查这种情况并像在该参数永远不会为空的情况下那样继续进行。现实情况是,参数结束在即使你设置为先决条件,参数不应该是空的方法的一些点被空。它总是会发生。

与上述示例中调试器的有用性相反,当涉及多线程(即并发,异步处理)时,我发现使用它很困难,而且没有什么用处。它可以提供帮助,但是当在A点的一个线程和B点的一个完全独立的线程中碰到调试器的断点时,容易在多线程迷雾中迷失方向。开发人员被迫按下新的断点“思维过程”,然后在新断点处将自己定向到代码。在断点B的相关性降低之后,开发人员然后切换回第一个断点,并且必须回想起触发断点B之前他/她正在寻找的内容。我知道这可能是一个令人困惑的解释,

而且,并发代码的不可预测性还会进一步分散开发人员调试并发代码的注意力。

最后,以我的诚实意见:

  • 使用并发时进行调试=失去“调试思想模式”焦点的趋势增加

  • 随时可以=提高调试效率b / c您的注意力不会因意外的断点而中断(由于竞赛条件而无法预期)。

2
+1用于在并发环境中提出调试问题,在传统环境中,传统调试器的有用性通常会降低到接近于零。
dasblinkenlight 2012年

4

我认为他们有点过于刻板。就个人而言,当我遇到一个错误时,我会重新检查代码,并尝试从程序逻辑中在代码中进行追踪,因为有时这比仅使用调试器并修复在其自身出现的错误更容易发现其他问题或副作用。 。

即使我认为自己已经确定,也通常会对其进行调试以确保我是对的。当问题更复杂时,我相信调试是绝对必要的。

另外...只是我的看法,但是,没有没有利用现代IDE可以带来的体面优势的借口。如果它可以帮助您更快,更可靠地完成工作,则应该使用它。


4

讨厌概括,但是我遇到的许多程序员都认为只有一种方法可以解决问题。容易想到已经考虑了所有可能的测试。不同的观点可能非常有价值。

通过反复试验进行编程可以提出一些很棒的新方法,并抓住其他人错过的东西。

缺点是,通常需要更长的时间。


4

嗯,这取决于人。就我个人而言,我自己并没有使用调试器。对微控制器进行编程时,基本上是使用LED或将数据写入EEPROM来“调试”其上的代码。我不使用JTAG。

在为PC或服务器编程软件时,我倾向于使用日志记录和大量控制台输出。对于C风格的语言,我使用预处理程序指令,而在Java中,我使用日志级别。

由于我不使用调试器,您会说我做错了吗?这是编辑的工作,目的是告诉我语法错误在哪里,而当逻辑上出现错误时,我只需要运行测试即可。


4

不需要使用调试器和不知道如何(或拒绝)使用调试器之间是有区别的。调试器只是用于跟踪和修复错误的众多工具之一。我与开发人员合作,他们可以将其困惑,也可以与其他认为可行的开发人员合作。

最好的组合是编写代码,以便通过单元测试轻松测试并记录错误。然后,您希望不必查看日志或使用调试器。这有点像购买保险。希望您永远不需要使用它,但是一旦遇到无法通过重新检查代码解决的错误,那么添加适当的错误处理/日志记录,单元测试或学习使用调试器为时已晚。

不同的工具/平台支持不同的调试技术(调试器,日志记录,单元测试等)。只要开发人员熟悉其平台/工具的一些技术,除了重新检查代码之间,它们可能一个熟练的开发人员,但是如果他们在调试方面只有一个窍门,那么他们最终将遇到无法找到或修复的错误。


4

有很多答案,但没有提到Heisenbug吗?

之所以会出现Heisenbug,是因为通常会尝试调试程序,例如插入输出语句或在调试器中运行该程序,通常会修改代码,更改变量的内存地址及其执行时间。

我仅在最坏的情况下使用调试器(针对难以发现的错误)。另外,根据许多著名的开发人员/测试人员所谈论的最佳实践,最好对代码进行完全的单元测试。这样,您可以解决大多数问题,因此无需使用调试器。


3

我最近在这里阅读了反对调试器调试的参数(或者是StackOverflow吗?)。您应该针对您的代码有测试用例。如果测试通过,则调试可能不会执行该错误(假设:您将使用与测试数据相似的数据进行调试)。

另一方面,日志记录是强制性的。如果通过测试并部署到生产环境,则可能会发现您有一个错误。该错误的证据来自过去发生的事情。即有人说:“那是怎么到达那里的?” 如果没有好的日志,您将永远找不到原因。此时,甚至调试器也可能没有用,因为您不知道实际执行该错误的数据是什么样的。您需要能够从日志中调试应用程序。

不幸的是,我的意思很宽泛,并且可能对原始论点造成损害。特别是,“有重要的调试助手要花费开发时间来支持”的立场可能与调试器的重要性正交。但是有关在配置中设置系统状态的困难的部分使我想到了一些东西,这使调试对于发现错误很有用。


3

有了良好的单元测试以及为您提供回溯的异常,您几乎不必使用调试器。

我上次使用调试是在某些旧版应用程序中获得核心文件时。

我是“ debbuger奴才”还是这些人“太顽固”?

都不行 他们只是喜欢让生活变得比原本应该艰难的人。


2

调试只是优秀开发人员应熟练使用的工具。

当然,有时候,如果您知道代码库,则可以真正知道该错误在哪里。但是,您也可能只需要检查一下代码,就可能整天或整整一个星期无法找到讨厌的错误。

在没有某种调试(即使只是将值转储到控制台)的动态类型语言中,有时无法进行猜测。

因此,回答您的问题-也许他们是优秀的程序员,但是他们的故障排除技能和寻找bug的能力很差。


2

取决于问题的范围。如果程序很小,并且划分得很好,那么您可能可以通过查看找出答案。如果该程序是由100多人的团队在几年的时间内开发的450万行代码,那么将不可能发现某些错误。

所说程序中的问题(在C语言中)是内存覆盖。带有内存断点的调试器在错误出现后立即确定了有问题的代码行。但是在这种情况下,任何人都不可能读取并保留所有450万行代码来标识某人在数组中写过的一个点(此外,他们还必须知道gargantuan程序状态的内存运行时布局。大约需要10分钟才能完成此操作)。

重点是:在小型程序或高度模块化的东西中,您可以不用调试器。如果程序真的很大又很复杂,那么调试器可以为您节省很多时间。就像其他人所说的那样,它是一种工具,在某些情况下它比其他任何方法都优越,而在其他情况下则不是最佳选择。


0

如果该错误发生在客户端计算机上,或者其环境与您的环境有很大不同,则设置调试器/远程调试器很麻烦。因此,在寒冷的日子里,您会从现场得到一个错误,“但...我没有调试器”的响应无济于事。因此,您需要仅通过了解代码和日志文件来开发故障排除和查找错误的技能。


-1

一堆废话:“真正的程序员不需要调试器。” 还可以说一个真正的程序员不需要任何IDE,只需给我一个记事本和一支枯燥的铅笔即可。调试器是与其他任何工具一样可以提高生产率的工具。

另外,请注意并非所有负责调试代码的人都熟悉所讨论的代码。许多时间承包商进入的环境中,他们只对发生的事情有一般的理想。甚至可以为它们提供环境的详细说明,或者使用20年的架构图并提供神秘的命名约定指南(尝试了解表X1234和表X4312之间具有字段F1,F2和F3的区别[是的,像这样的垃圾存在](当您是新手时),但很多时候该描述是错误的;否则,为什么会有“神秘”错误。

作为对环境不熟悉的人,您可以花费数小时或数天的时间进行映射,并“了解”大型数据库以查找可能需要解决的问题区域,然后再也无需查看。这是浪费大量的时间和金钱。如果您有权访问调试器,则可以查看正在发生的事情,对其进行更正,然后在几分钟内就消失了。所有这些“您不需要调试器” hooey只是精英主义者的吹捧。


2
这个稻草人不回答所提的问题,无处声明“真正的程序员不需要调试器”
gnat 2013年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.