如何最有效地调试代码?[关闭]


33

可以将爬入代码中的错误减至最少,但在编写时不能完全消除-程序员是程序员,尽管许多人会不同意,但这只是人类。

当我们确实在代码中检测到错误时,该怎么做才能将其清除?我们应该如何处理它,以最有效地利用我们的宝贵时间,使我们花更少的时间去寻找它,而花费更多的时间进行编码?另外,调试时应该避免什么?

请注意,此处我们并不是在谈论防止错误;我们正在谈论出现错误时该怎么办。我知道这是一个广阔的领域,可能高度依赖语言,平台和工具。如果是这样,请保持涵盖心态和一般方法之类的答案。


链接的问题已被删除。

1
我认为该方法实际上很简单。如果您独自开发它,那么您将了解一切。您甚至可以在不调试的情况下修复该错误。考虑到这一点,最好的方法是花时间编码其他东西,直到对它有很多了解的人可以回答您有关如何解决它的问题。或者,让它休息一下,编写其他代码,直到您想到解决它的想法,这样您就不会浪费时间,也不会浪费精力。我的猜测是您的问题与企业团队管理有关。
Aquarius Power

我认为突袭。现成的,杀虫喷雾。这是一个哲学问题吗?书籍是仅凭优势而制成的……
ejbytes '16

Answers:


38

调试的心态和态度也许是最重要的部分,因为它决定了您修复错误的效率以及从错误中学习的内容(如果有的话)。

诸如The Pragmatic ProgrammerCode Complete之软件开发经典著作基本上都主张采用相同的方法:每个错误都是学习的机会,几乎总是关于您自己的(因为只有初学者首先将责任归咎于编译器/计算机)。

因此,将其视为一个谜,将很有趣。并通过系统地(对我们自己或对他人)表达我们的假设,然后(如果需要的话)逐一测试我们的假设,使用我们可以使用的所有工具,尤其是调试器和自动测试框架,来系统地破解这个谜团。然后,在解开谜团之后,您可以通过查看所有代码中是否存在类似的错误来做得更好。并编写一个自动化测试,以确保不会在不知不觉中再次发生该错误。

最后一点-我更喜欢将错误称为“错误”而不是“错误”-Dijkstra指责他的同事使用后一个术语,因为这是不诚实的,支持这样的观点,即有害和善变的错误精灵会在我们不使用时在我们的程序中植入错误。 t看,而不是因为我们自己的(草率的)想法而呆在那里:http : //www.cs.utexas.edu/users/EWD/transcriptions/EWD10xx/EWD1036.html

例如,我们可以通过不再将bug称为bug而是将其称为error来清理语言。坦率地说,因为它把责任归咎于它所属的地方,即。错误的程序员。在程序员不看的时候恶意潜入的bug的一种泛泛的比喻在理论上是不诚实的,因为它掩盖了错误是程序员自己创造的。这种简单的词汇变化的好处是,它具有如此深远的影响:以前,只有一个错误的程序曾经是“几乎正确的”,而后来有错误的程序只是“错误的”(因为错误)。


7
实际上,我喜欢术语“错误”而不是“错误”,不是因为它将责任归咎于“造成错误的程序员”,而是因为它清楚地表明可能不是程序员有过错。对我来说,“错误”意味着代码中的错误;而“错误”则暗示某处存在错误。也许在代码中,在环境设置中,在需求中。当老板有一个“错误列表”,其中一半是需求变更时,我会发疯。称它为任务列表,ferchrissakes!
Carson63000

2
+1代表“每个错误都是学习的机会,几乎总是关于您自己的(因为只有初学者首先责怪编译器/计算机)”
Md Mahbubur Ra​​hman

您知道术语“ bug”的历史,对吗?我的意思是,用于软件开发。当然,我们今天没有这个问题,但是实际上,一个错误确实会飞入程序员未注意到的计算机硬件中并引起问题。为了避免有人想纠正我,我知道爱迪生在蛾类事件发生之前很早就使用了这个术语,这就是为什么我使用“历史”一词而不是“起源”一词的原因。见 computerworld.com/article/2515435/app-development/...en.wikipedia.org/wiki/Software_bug#Etymology
threed

@threed当然。但是一段时间以来,昆虫并没有引起绝大多数软件错误。
limist

16
  1. 编写测试。测试不仅可以很好地防止错误(以我的经验,正确执行TDD可以消除几乎所有的琐碎,愚蠢的错误),而且还有助于调试。测试迫使您的设计相当模块化,这使得隔离和复制问题变得更加容易。此外,您可以控制环境,因此不会有太多的惊喜。而且,一旦遇到失败的测试用例,就可以合理地确定自己已经弄清了困扰您的行为的真正原因。

  2. 了解如何使用调试器。print语句可能在某种程度上可以很好地工作,但是大多数时候调试器都非常有用(而且一旦知道如何使用它,它比print语句要舒适得多)。

  3. 与某人谈论您的问题,即使这只是橡皮鸭。强迫自己用语言表达自己正在解决的问题确实是奇迹。

  4. 给自己一个时间限制。例如,如果45分钟后您觉得自己无路可走,只需切换到其他任务一段时间。当您回到自己的错误时,希望您能够看到其他您以前从未考虑过的解决方案。


2
+1表示“强迫自己用语言表达自己正在解决的问题确实很神奇。”
Md Mahbubur Ra​​hman

加上(1),几乎您在代码中看到的每个错误都意味着测试套件中存在一个错误-或至少是一个遗漏。同时解决这两个问题,不仅可以证明您已解决问题,而且可以避免再次引入该问题。
朱莉娅·海沃德

3

我认为错误的复制也很重要。可以列出所有重现该错误的案例,然后可以确保您的错误修复程序涵盖了所有这些案例。


3

关于这一主题,我读了一本非常出色的书,名为《程序为什么失败》,其中概述了各种查找错误的策略,从应用科学方法隔离和解决错误到增量调试,不一而足。本书另一个有趣的部分是它消除了术语“ bug”。Zeller的方法是:

(1)程序员在代码中造成缺陷。(2)缺陷引起感染(3)感染传播(4)感染引起故障。

如果您想提高调试技巧,我强烈推荐这本书。

以我个人的经验,我在应用程序中发现了很多错误,但是管理层只是简单地迫使我们继续开发新功能。我经常听到“我们自己发现了这个错误,客户还没有注意到它,所以请留到他们发现为止。” 我认为对修复漏洞采取积极主动的态度是非常糟糕的主意,因为当需要实际解决问题时,您还需要解决其他问题,并且更多的功能管理人员希望尽快出手,因此您被抓住了在一个恶性循环中,该恶性循环可能导致大量压力并消耗out尽,最终导致缺陷缠身的系统。

发现错误时,交流也是另一个因素。发送电子邮件或将其记录在错误跟踪器上都很好,但是以我个人的经验,其他开发人员会发现类似的错误,而不是重用您投入的用于修复代码的解决方案(因为他们已经忘记了所有这些) ),它们会添加自己的版本,因此您的代码中有5种不同的解决方案,因此,它看上去更肿且令人困惑。因此,当您确实要修复错误时,请确保有一些人检查此修复程序,并在他们修复了类似问题并找到解决问题的好方法的情况下向您提供反馈。

limist提到了《实用程序员》这本书,其中有一些有趣的错误修复材料。使用我在上一段中给出的示例,我将看一下:Software Entrophy,其中使用了一个残破寡妇的类比。如果出现两个破碎的窗口,您的团队可能会无动于衷,除非您采取积极的态度。


我也听说过“我们自己发现了这个bug,客户还没有注意到它,所以就把它留到他们发现为止”也太多次了。而在经历上实地考察,通常是客户已经注意到了,但还没有报道它。有时候是因为他们认为没有意义,因为它不会被解决,有时是因为他们已经在寻找竞争对手的替代产品,有时(无论是对还是错)“好吧,无论如何,这都是一堆废话。”
朱莉娅·海沃德

@JuliaHayward-这是很常见的情况,但是在您的情况下,您的客户可能会对功能感到满意,而不必担心幕后的情况。当客户再次要求其他功能时,问题开始浮出水面,您需要添加其他增强功能,例如使您的应用程序支持多语言,兼容移动设备等等,您开始查看现有功能并查看所有缝隙。
荒凉星球

只是向您展示,世界上有关软件设计,测试和良好沟通的所有书籍,以及您使用的许多产品都是一团糟。尽管知道正确的方法,但压力和不切实际的截止日期(面对已经很混乱的代码)是导致代码保持现状的原因。我本人对此没有任何答案,我在办公室里表现得很face吟,因为我踢着脚喊着保持代码健康和开发过程顺利,但是有时候团队却没有。不能很好地粘合在一起。
荒凉星球

3

错误,错误,问题,缺陷-不管您想称它什么,它都没有太大的区别。我会坚持下去,因为那是我过去的习惯。

  1. 弄清楚问题的根源:将客户的“鲍勃仍不在系统中”转换为“当我尝试为鲍勃创建用户记录时,它会失败,并出现重复的关键异常,尽管鲍勃还没有在那里'
  2. 找出是真正的问题还是误解(事实上,鲍勃不在那儿,没有人叫鲍勃,插入应该可以工作)。
  3. 尝试获得可重现该问题的最低限度的可靠步骤-类似于“给出具有用户记录'Bruce'的系统,当插入用户记录'Bob'时,会发生异常”
  4. 这是您的测试-如果可能,将其放入自动测试工具中,您可以一次又一次地运行它,这在调试时将非常宝贵。您还可以使其成为测试套件的一部分,以确保以后不再出现该特定问题。
  5. 释放调试器并开始放置断点-在运行测试时找出代码路径,并找出问题所在。在执行此操作时,还可以通过使其尽可能窄来优化测试-理想情况下是单元测试。
  6. 修复它-验证您的测试通过。
  7. 验证客户描述的原始问题是否也已解决(非常重要-您可能只解决了部分问题)。确认您没有在程序的其他方面引入回归。

如果您对代码非常熟悉,或者问题或解决方法很明显,则可以跳过其中一些步骤。

我们应该如何利用它来最有效地利用我们宝贵的时间,使我们花费更少的时间去寻找它,而花费更多的时间进行编码?

我对此表示怀疑,因为这意味着编写新代码比拥有高质量的工作程序有价值。尽可能有效地解决问题并没有错,但是程序不一定会通过添加更多代码而变得更好。


这是IMO的最佳答案
marcusshep

3

我喜欢其他大多数答案,但是在您执行任何操作之前,这里有一些提示。将节省您的时间。

  1. 确定是否确实存在错误。一个bug总是在系统行为和需求之间有所不同。测试人员应该能够阐明预期的行为和实际行为。如果他无法为预期的行为提供支持,则没有任何要求,也没有错误-只是别人的意见。把它退回。

  2. 考虑预期行为错误的可能性。这可能是由于对该要求的误解。这也可能是由于需求本身的缺陷(详细需求和业务需求之间的差额)造成的。您也可以将这些寄回。

  3. 隔离问题。只有经验会教给您最快的方法-有些人几乎可以凭直觉做到这一点。一种基本方法是在使所有其他事物保持不变的同时改变一件事(问题是否发生在其他环境上?使用其他浏览器吗?在不同的测试区域中?在一天的不同时间?)另一种方法是查看堆栈转储或错误消息-有时您只能通过格式化的方式来判断系统的哪个组件引发了原始错误(例如,如果是德语,则可以责怪与您合作的第三方在柏林)。

  4. 如果将其范围缩小到两个可以协作的系统,请通过流量监控器或日志文件检查两个系统之间的消息,并确定哪个系统符合规范,哪个不符合规范。如果方案中有两个以上的系统,则可以执行成对检查并以“降低”应用程序堆栈的方式进行工作。

  5. 隔离问题之所以如此重要,是因为问题可能不是由于您可以控制的代码缺陷(例如第三方系统或环境)引起的,而是您希望让该方尽快接手。这既可以节省您的工作,又可以立即使它们对准目标,从而可以在尽可能短的时间内实现分辨率。您不想在某问题上工作十天,只是发现它确实与其他人的Web服务有关。

  6. 如果您确定确实存在缺陷,并且确实在您控制的代码中,则可以通过查找最后一个“已知良好”的构建并检查源代码管理日志中是否可能引起此问题的更改来进一步隔离问题。这样可以节省很多时间。

  7. 如果您无法从源代码管理中找出问题,那么现在该是连接调试器并逐步检查代码以解决问题的时候了。无论如何,现在您很有可能对这个问题有了一个很好的主意。

一旦知道了错误的所在并可以考虑修复程序,以下是修复此错误的好方法:

  1. 编写一个重现问题并失败的单元测试。

  2. 在不修改单元测试的情况下,使其通过(通过修改应用程序代码)。

  3. 将单元测试保存在测试套件中,以防止/检测回归。


1

这是我的方法:

  1. 每次使用相同的方法查找问题。这将改善您对错误的反应时间。
  2. 最好的方法可能是阅读代码。这是因为所有信息在代码中都可用。您只需要有效的方法来找到正确的位置和理解所有细节的能力。
  3. 调试是非常缓慢的方式,只有在您的程序员还不了解计算机如何执行asm指令/无法理解调用堆栈和基本内容的情况下,调试才有必要
  4. 尝试开发证明技术,例如使用函数原型来推理程序的行为。这将有助于更快地找到正确的位置

1

当我们确实在代码中检测到错误时,该怎么做才能将其清除?我们应该如何处理它,以最有效地利用我们的宝贵时间,使我们花更少的时间去寻找它,而花费更多的时间进行编码?另外,调试时应该避免什么?

假设您处于生产环境中,则需要执行以下操作:

  1. 正确描述“错误”,并确定导致错误发生的事件。

  2. 确定“错误”是代码错误还是规范错误。例如,对于某些系统,输入1个字母名称可能被认为是错误,但对于其他系统则可以接受。有时,用户会报告他/她认为是问题的错误,但是用户对系统行为的期望并不属于要求的一部分。

  3. 如果您已证明存在错误并且该错误是由于代码引起的,则可以确定需要修复哪些代码段以防止错误。还检查行为对当前数据和将来的系统操作的影响(对代码和数据的影响分析)。

  4. 在这一点上,您可能会估计将花费多少资源来修复该错误。您可以立即对其进行修复,也可以计划在即将发布的软件版本中进行修复。这还取决于最终用户是否愿意为修复程序付费。您还应该评估其他可用选项以修复该错误。可能有不止一种方法。您需要选择最适合这种情况的方法。

  5. 分析导致此错误出现的原因(需求,编码,测试等)。强制执行可防止情况再次发生的过程。

  6. 充分记录该情节。

  7. 发布修复程序(或新版本)

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.