为什么 IDE 中的调试更好?

我已经做了二十多年的软件开发人员,使用 C、 Perl、 SQL、 Java、 PHP、 JavaScript 和最近的 Python 编程。我从来没有遇到过无法使用一些仔细的思考和恰当的调试 print语句进行调试的问题。

我尊重许多人说我的技术是原始的,在 IDE 中使用真正的调试器要好得多。然而,根据我的观察,IDE 用户似乎并没有比我使用石刀和熊皮更快或更成功地进行调试。我真诚地愿意学习正确的工具,只是我从来没有看到过使用可视化调试器的令人信服的优势。

此外,除了如何设置断点和显示变量内容的基本知识之外,我从未读过教程或书籍,其中介绍了如何有效地使用 IDE 进行调试。

我错过了什么?是什么使 IDE 调试工具比深思熟虑地使用诊断 print语句更有效?

您能否提供一些资源(教程、书籍、屏幕录像)来展示更好的 IDE 调试技术?


答得好!非常感谢大家抽出时间。很有启发性。我投了很多票,没有一票被否决。

一些值得注意的地方:

  • 调试器可以帮助我检查或修改变量、代码或执行期函式库的任何其他方面,而手动调试需要我停止、编辑和重新执行应用程序(可能需要重新编译)。
  • 调试器可以附加到正在运行的进程或使用崩溃转储,而手动调试则需要“重现”缺陷的步骤。
  • 调试器可以以更易读的方式轻松地显示复杂的数据结构、多线程环境或完整的运行时堆栈。
  • 调试器提供了许多方法来减少执行几乎所有调试任务所需的时间和重复工作。
  • 可视化调试器和控制台调试器都很有用,并且有许多共同的特性。
  • 集成到 IDE 中的可视化调试器还使您能够在单个集成开发环境(因此得名)中方便地访问智能编辑和 IDE 的所有其他特性。
24739 次浏览
  • IDE 调试器允许您更改 运行时变量的值。

  • 一个 IDE 调试器允许您查看 你不知道你想要的变量 看看什么时候开始执行

  • 一个 IDE 调试器允许您查看调用堆栈 并检查的状态 函数传递了奇怪的值。 (认为这个函数是从 几百个地方,你不知道 这些奇怪的价值观是怎么来的 从)

  • IDE 调试器允许您 条件中断执行 基于一个条件, 不是行号

  • IDE 调试器可以让您在出现未处理的异常时检查程序的状态,而不是简单地崩溃。

我已经有将近20年没有开发了,但是我发现使用 IDE/debug 我可以:

  • 看到各种各样的东西,我可能没有想到包括在一个打印声明
  • 一步一步地通过代码,看看它是否匹配的路径,我认为它会采取
  • 将变量设置为某些值,使代码采用某些分支

IDE 调试器通过代码中的跟踪消息为您提供某些功能的一些示例:

  • 在任何时间点查看 呼叫栈,为当前堆栈帧提供上下文。
  • 进入无法重新编译以添加跟踪的库 (假设您可以访问调试符号)
  • 在程序运行时更改变量值
  • 编辑和继续-能够 在运行时更改代码和立即看到变化的结果
  • 能够 看好了变量,看看他们什么时候改变
  • 能够使用 跳过或重复代码段,查看代码将如何执行。这允许您在进行理论更改之前对其进行测试。
  • 实时检查 内存内容
  • 抛出某些 例外时通知您,即使它们是由应用程序处理的。
  • 条件断点 ; 仅在特殊情况下停止应用程序,以便分析堆栈和变量。
  • 在多线程应用程序中查看 线程上下文,这很难通过跟踪实现(因为来自不同线程的跟踪将在输出中交错)。

总之,print 语句(通常)是 静电干扰,如果原来的语句不够详细,那么需要重新编译以获得更多信息。IDE 消除了这个静态障碍,为您提供了指尖上的 充满活力工具包。

当我第一次开始编写代码时,我不能理解调试器有什么大不了的,我认为我可以通过跟踪实现任何东西(当然,那是在 unix 上,而调试器是 GDB)。但是一旦您学会了如何正确地使用图形调试器,您就不想再回到 print 语句了。

下面是我在 VS.NET 调试窗口中使用最多的内容:

  • 调用 stack,这也是找出其他人的代码的一种很好的方法
  • 本地及钟表业。
  • 即时窗口,它基本上是一个 C # 控制台,也让我改变变量的内容,初始化的东西等。
  • 跳过一行,设置下一个语句在其他地方执行的能力。
  • 悬停在变量上的能力,并且有一个工具提示向我显示它们的值。

总之,它为我提供了执行代码状态的360度视图,而不仅仅是一个小窗口。

从来没有找到一本书教这样的东西,但话说回来,它似乎很简单,它几乎所见即所得。

使用 IDE 的一个原因可能是现代 IDE 支持的不仅仅是简单的断点。例如,VisualStudio 提供以下高级调试特性:

  • 定义条件断点(仅在满足条件时中断,或者仅在第 n 次执行断点处的语句时中断)
  • 在未处理的异常或要引发(特定)异常时中断
  • 调试时更改变量
  • 通过设置要执行的下一行来重复一段代码
  • 等等。

此外,在使用调试器时,一旦完成调试,就不必删除所有的 print 语句。

  • 调试器可以附加到正在运行的进程

  • 通常更容易从调试器调试线程代码

IDE 不仅仅是调试,它还可以在很多方面帮助你更快地构建更好的软件:

  • 重构工具
  • 使 api 更容易被发现,或者提醒熟悉项目的确切拼写/大小写(如果你使用同一个系统15年,这种方法就没什么用了,但是很少见)
  • 通过自动完成变量名和类名来节省键入时间
  • 甚至在开始编译之前就发现某些类型的错误
  • 自动跳转到变量/方法/类声明/定义,即使它们不在同一个文件或文件夹中。
  • 在未处理的 处理好了异常时中断

我可以继续说。

我第一时间想到的是:

  1. 调试复杂对象 -调试器允许您深入到对象的内部。如果您的对象有一个由复杂对象组成的数组,那么 print 语句只能到此为止。
  2. 单步执行过去的代码 -Debuggers 还允许您跳过过去不想执行的代码。的确,您也可以手动完成这项工作,但是您需要注入的代码要多得多。

有一件事你绝对不能用“ print”语句进行调试,那就是当一个客户把内存转储给你并说“你的程序崩溃了,你能告诉我为什么吗?”

因为使用 print 语句调试多线程应用程序会让您抓狂。是的,您仍然可以使用 print 语句来完成这项工作,但是需要大量的 print 语句,并且要解开语句的连续打印来模拟多线程执行需要很长的时间。

不幸的是,人类的大脑只是单线程的。

我认为使用 print 语句进行调试是一门失传的艺术,对于每个开发人员来说都非常重要。一旦您知道如何做到这一点,某些类别的 bug 就会变得比通过 IDE 更容易进行调试。了解这种技术的程序员也非常清楚,为了非调试目的,在日志消息中放入什么有用的信息(更不用说实际上最终读取日志)。

也就是说,您确实应该知道如何使用逐步调试器,因为对于不同类型的 bug 来说,它要简单得多。我将把它留给其他已经发布的解释原因的出色答案:)

  • 在整个代码中打印语句会降低可读性。
  • 仅仅为了调试的目的添加和删除它们是非常耗时的
  • 调试器跟踪调用堆栈,使得很容易看到您的位置
  • 可以动态地修改变量
  • 可以在执行暂停期间执行即席命令,以帮助诊断
  • 可以与 print 语句一起使用: Debug. Write (“ ...”)

调试器相对于 printf (注意,不是 IDE 调试器,而是任何调试器)的优点

  1. 可以设置观察点。 这是我最喜欢的发现记忆损坏的方法之一

  2. 可以调试目前无法重新编译的二进制文件

  3. 可以调试需要很长时间重新编译的二进制文件

  4. 可以动态更改变量

  5. 可以动态调用函数

  6. 不存在不刷新调试语句的问题,因此不能准确地调试定时问题

  7. 调试器帮助处理核心转储,打印语句不’

令我惊讶的是,我没有在另一个答案中看到这两个调试方法是 不是互相排斥的

即使使用标准调试器(无论是否基于 IDE) ,printf调试也可以很好地工作。特别是使用日志框架,这样您可以将所有或大部分内容留在发布的产品中,以帮助诊断客户问题。

正如这里所有其他答案中提到的,标准调试器的一个关键优点是,它允许您更容易地检查(并可能更改)程序状态的细节。你不必事先知道你可能想看什么——它们都在你的指尖(或多或少)。

既然你要求书籍的指针... ... 至于 Windows 调试,John Robbins 有几个版本的关于 Windows 调试的好书:

为 Microsoft.NET 和 MicrosoftWindows 调试应用程序

注意,最新版本(调试 Microsoft. NET 2.0应用程序)是。因此,如果您想要进行本机代码调试,您可能需要一个较旧的版本(如第一个链接中所示)(它涵盖了两个版本)。NET 和本机)。

我个人认为答案很简单: “一个集成的调试器/IDE 可以快速地提供大量不同的信息,而无需输入命令。信息往往就在你面前,而你却没有告诉它该给你看什么。

可以检索信息的 放松比命令行调试或“ printf”调试更好。

使用 IDE 调试器,只要停止执行,就可以看到当前范围(调用堆栈的所有方向)中所有变量的值。

打印语句可以很好,但是在任何给定的位置向屏幕倾倒如此多的信息会产生大量的打印语句。

此外,许多 IDE 调试器允许您输入和计算方法,并在停止时计算成员,这进一步增加了必须执行的打印语句的数量。

我确实觉得调试器对某些语言比对其他语言更好,但是..。

我的一般看法是,IDE 调试器对于 Java 或 C # 这样的托管语言来说绝对是非常非常棒的,对于 C + + 来说相当有用,对于 Python 这样的脚本语言来说则不是非常有用(但可能是因为我还没有尝试过用于任何脚本语言的好的调试器)。

当我做 Java 开发时,我非常喜欢 IntelliJIDEA 中的调试器。我只是在使用 Python 时使用 print 语句。

如上所述: 调试器! = IDE。

Gdb 和(过去) TurboDebugger (独立)在它们支持的语言中工作得很好,谢谢。(或者更古老的技术: 链接到 xBase 可执行文件本身的 Clipper 调试器)——这些都不需要 IDE

此外,尽管 C/+ + 编码比较少见,printf 语句有时会掩盖您正试图找到的 bug!(例如,堆栈上的 auto vars 中的初始化问题,或者内存分配/对齐问题)

最后,正如其他人所说,您可以同时使用这两种方法。一些实时问题几乎需要打印,或者至少需要一个明智的“ * video _ dbg = (is _ good?进入视频内存的某个地方。我的年龄显示,这是根据 DOS: -)

TMTOWTDI

除了其他海报上的大部分内容之外,我真的很喜欢和电脑一起一行一行地走过去,因为它迫使我一次想一行。通常,我甚至不看变量值就会发现 bug,因为当我点击“下一行”按钮时,我不得不看它。然而,我不认为我的回答会对你有帮助,比尔,因为你可能已经有这个技能了。

就学习资源而言,我没有使用任何资源——我只是浏览了所有的菜单和选项。

这是真正的程序员提出的问题吗?

任何人花费甚至5分钟调试打印语句和 IDE 调试-它会发生在他/她甚至没有问!

我已经使用了 print 和 IDE 进行调试,我更愿意使用 IDE 进行调试。对我来说,唯一不起作用的时候是在时间紧迫的情况下(如调试在线游戏) ,你乱丢打印语句的代码,然后查看日志文件后,它已经变得可怕的错误。然后,如果你仍然不能弄清楚,添加更多的打印和重复。

只是想提一下 IDE 中控制台调试器 vs printf 和 vs 调试器的一个有用特性。

您可以附加到远程应用程序(显然是以 DEBUG 模式编译的) ,并使用 POSIX tee实用程序检查其状态,将调试器输出转储到文件中。与 printf 相比,您可以选择在运行时输出状态的位置。

在我调试部署在 恶劣的环境中的 AdobeFlash 应用程序时,它帮了我很大的忙。您只需定义一些操作,在每个断点中打印所需的状态,使用 fdb | tee output.log启动控制台调试器,然后遍历一些断点。然后,您可以打印日志并通过彻底比较不同断点的状态来分析信息。

不幸的是,这个特性[记录到一个文件]在 GUI 调试器中很少可用,这使得开发人员比较他们头脑中对象的状态。

顺便说一下,我的观点是,在启动调试器之前,应该计划调试的位置和内容。

另一个问题是,如果你加入一个新的旧项目,没有人真正知道代码是如何执行的,那么你就不能通过回显变量/对象/... b/c 来调试,你根本不知道执行的是什么代码。

在我的工作中,我正面临着这种情况,而视觉 XDebug 帮助我了解到底发生了什么,在哪里发生了什么。

最好的问候

拉斐尔

在 IDE 中进行调试在错误日志和 shell 访问不可用的环境(如共享主机)中是非常有价值的。在这种情况下,带有远程调试器的 IDE 是唯一允许您执行简单操作(如查看 stderrstdout)的工具。

除了已经提到的许多事情之外,调试器相对于 printf 最重要的优势之一是,使用 printf 语句假定您知道 bug 驻留在哪个函数中。在许多情况下,您不需要这样做,因此您必须做一些猜测,并将 print 语句添加到许多其他函数中,以便对其进行本地化。这个 bug 可能在框架代码中,或者远离你想象的地方。在调试器中,设置断点以检查代码不同区域和不同时间点的状态要容易得多。

另外,一个好的调试器将允许您通过向断点附加条件和操作来执行 printf 风格的调试,这样您仍然可以保留 printf 调试的好处,但是不需要修改代码。

使用 print 语句的一个问题是它会使代码变得一团糟。IE,你有一个包含10个部分的函数,你知道它在某个地方崩溃了,但是你不确定在哪里。因此,您需要添加10个额外的 print 语句来确定 bug 的位置。一旦发现并解决了 bug,现在必须通过删除所有这些 print 语句来进行清理。也许你会这么做。也许你会忘记,它将结束在生产和您的用户的控制台将充满调试打印。

作为 IDE 调试的替代方案,你可以尝试使用带有 Php 库的 Google Chrome 扩展 PHP 控制台,它允许:

  • 在 Chrome JavaScript 控制台和通知弹出窗口中查看错误和异常。
  • 转储任意类型变量。
  • 远程执行 PHP 代码。
  • 用密码保护访问。
  • 按请求分组控制台日志。
  • 跳转到文本编辑器中的错误文件: 行。
  • 将错误/调试数据复制到剪贴板(供测试人员使用)。

根据我的经验,简单的打印输出有一个巨大的优势,似乎没有人提到。

IDE 调试器的问题在于所有事情都是实时发生的。你在一个特定的时间停止程序,然后一步一步地完成这些步骤,如果你突然想看看之前发生了什么,那么就不可能回去了。这与我们大脑的工作方式完全不同。大脑收集信息,逐渐形成意见。这样做可能需要迭代事件几次,但是一旦超过了某个点,就不能回头了。

与此相反,一系列选定的打印输出/日志为您提供了“时间事件的空间投影”。它给你一个完整的故事,你可以回去和第四次非常容易,只要向上和向下滚动。它使得回答诸如“ A 在 B 发生之前发生过吗”这样的问题变得容易。它能让你看到你根本没想过的模式。

根据我的经验。IDE 和调试器是解决单个调用堆栈中出现错误时的简单问题以及探索机器在某个崩溃时的当前状态的出色工具。

然而,当我们处理更困难的问题,其中逐渐变化的状态涉及。例如,一个算法破坏了一个数据结构,这反过来又导致另一个算法失败。或者,如果我们想回答诸如“这种情况发生的频率”、“事情是否按照我想象的顺序和方式发生”之类的问题。等等。那么“老式的”日志/打印输出技术有一个明显的优势。

最好的办法是在最合适的时候使用任何一种技术,例如使用日志/打印输出来解决一些 bug,并在需要更详细地研究当前状态的断点处暂停。

还有一些混合方法。例如,当您执行 console.log (object)时,您将在日志中获得一个数据结构小部件,您可以对其进行更详细的展开和探索。这比“死”文本日志有很多明显的优势。

哇,我喜欢这个问题吗? 我从来不敢问。

似乎人们只是有不同的工作方式。 对我来说,最有效的方法是:

  • 对我的代码有一个可靠的思维模型,包括内存管理
  • 使用检测(如打印语句)来跟踪发生的事情。

到现在为止,我已经从事编程工作40多年了,每天都在 C + + 和 Python 中从事重要的技术和科学应用程序的工作,我的个人经验是,调试器对我毫无帮助。

我没说这是好事,也没说这是坏事,我只是想分享一下。