尝试用软件调试问题/问题/错误时,通常使用什么过程?[关闭]


15

大多数人似乎将调试视为一门艺术,而不是一门科学。对于这里将其视为一门科学而不是一门艺术的人们来说,遇到新的问题/错误/问题时,通常使用什么流程?

Answers:


13

一般来说,我要做的是:

  1. 尝试找出问题所在。考虑一下该漏洞首次出现时发生了什么变化。您在哪里工作?您要更改代码的哪一部分?我有99%的错误都是通过这种方式解决的。通常这很愚蠢。

  2. 如果我猜出问题出在哪里,请仔细看一下似乎是引起问题的代码。阅读。大声朗读。问自己:“我要达到什么目标?”。对于某些类型的问题:它可能会有一些副作用,还是可能以我未曾想到的方式在其他地方受到代码的影响?

  3. 尝试各种方法来分析问题出在哪里,何时何地(见下文)。

  4. 如果仍然没有线索,请检查源代码的旧版本是否存在相同的问题,请尝试查找在开发时间表中何时首次出现该问题。为此,您需要使用良好的版本控制系统,例如git(git具有一种称为bisect的功能,正好适合这种调试)。

  5. 如果仍然没有线索,请稍事休息……实际上通常会有所帮助。

  6. 返回到绘图板-查看程序应该如何工作以及是否真正有意义。

这实际上取决于问题的种类,但是假设我对问题可能在哪里有了一个大致的了解,那么:

  • 如果我怀疑问题出在代码的某些部分/最近的更改中,我会尝试通过使代码更简单来删除/注释/更改,或者通过使代码更简单来使错误消失的任何措施,然后带回有问题的代码并采取措施。好看看。

  • 运行带有断点的调试器(如果可能的话),并查看我的数据在试图查找开始运行不良时的外观,以更好地了解问题出在哪里。


1
+1休息一下。最困难的问题只会在您感到沮丧并在第6个小时进行调试时变得更加困难。知道何时休息是我获得的最有用的调试技能之一。
布拉德·加德纳

很棒的答案。我再好不过了。
EricBoersma,2011年

1
就像我的方法一样,但是您忘了一点,您让同事快速浏览一下,他们立即注意到拼写错误……
ChrisAnnODell 2011年

1
极好的答案。我只想补充一点,一分预防胜过一磅的治疗。调试过程的很大一部分是在一开始编写代码的时候,我只进行很小的增量更改,并在每个代码之间进行本地编译,测试和提交。这样,如果突然出现错误,则可能的可疑列表非常小,并且可以通过bzr qdiff命令轻松查看。
卡尔·比勒费尔特

8

我尝试使用测试驱动的开发(TDD)。我编写了一个复制该错误的测试,然后尝试使该测试通过。有时编写测试的行为有助于发现错误。

这使我大部分时间都无法进入调试器,并提供了回归测试以防止重新引入该错误。

一些链接:


4
我认为这个答案非常不完整。我不理解这么多投票。
Alex

1
它只获得了如此多的赞誉,因为它包含了神奇的缩写:TDD。
Bjarke Freund-Hansen

@Alex-我添加了一些链接。“查找错误,编写测试”中有一个示例。我可以对此进行扩展,但实际上就是这么简单。
TrueWill 2011年

7

科学一词有许多定义,但听起来您可能是指可能更准确地称为“ 科学方法 ”的事物。 ”。科学方法可以概括为观察某些现象(可能是错误或程序意外行为),提出假设或假设以解释行为,并最有可能尝试进行证明(编写能够可靠地再现问题的测试)。

可能发生的错误类型(现象)实际上是无止境的,有些不一定需要定义明确的过程。例如,有时您观察到一个错误,并且仅仅因为非常熟悉代码,就立即知道导致该错误的原因。在其他时候,您知道给定一些输入(操作,一系列步骤等),就会发生不正确的结果(崩溃,错误的输出等)。对于那些情况,它通常不需要太多的“科学”思考。有些想法可以帮助减少搜索空间,但是一种常见的方法只是在调试器中单步执行代码并查看问题出在哪里。

但是,我发现最有趣的情况,也许是值得进行科学处理的情况,会给您一些最终结果并要求您解释它是如何发生的。一个明显的例子是崩溃转储。您可以加载故障转储并观察系统状态,而您的工作是解释它如何进入该状态。崩溃(或核心)转储可能显示异常,死锁,内部错误或用户定义的某些“不良”状态(例如,呆滞)。对于这些情况,我通常遵循以下步骤:

  • 狭义观察:研究适用于特定问题的直接信息。这里很明显的事情是调用堆栈,局部变量(如果您可以看到它们),围绕该问题的代码行。这种类型的特定位置研究并不总是适用。例如,研究“慢速”系统可能没有像这样的明显起始位置,但是崩溃或内部错误情况可能会立即引起人们的关注。这里的一个特定步骤可能是使用诸如windbg之类的工具(在已加载的故障转储中运行!analyze -v并查看其内容)。

  • 广泛观察:研究系统的其他部分。检查系统中所有线程的状态,查看任何全局信息(用户/操作/项目数,活动事务/进程/小部件等),系统(OS)信息等。如果用户提供了任何外部详细信息,再结合您的观察来思考。例如,如果他们告诉您该问题在每个星期二下午发生,请问自己这意味着什么。

  • 假设的:这是真正有趣的部分(我并不是很有趣)。它通常需要进行大量相反的逻辑思考。考虑系统如何进入当前状态可能非常令人愉快。我怀疑这是许多人认为是一门艺术的部分。我想这可能是程序员只是开始随机向其扔东西以查看发生了什么。但是根据经验,这可能是一个定义明确的过程。如果您在这一点上进行逻辑思考,通常可以定义导致给定状态的可能路径集。我知道我们处于状态S5。为此,需要发生S4a或S4b,并且可能需要在S4a之前发生S3等。更常见的是,可能有多个项目可以导致给定状态。有时在草稿上写下简单的流程图或状态图或一系列与时间有关的步骤可能会有所帮助。这里的实际过程将视情况而变化很大,但是此时认真思考(以及前面步骤中的重新检查)通常会提供一个或多个合理的答案。还要注意,此步骤的一个极其重要的部分是消除不可能的事情。删除不可能的项目可以帮助缩小解决方案的空间(记住,Sherlock Holmes关于消除不可能项目后所说的话)。还要注意,此步骤的一个极其重要的部分是消除不可能的事情。删除不可能的项目可以帮助缩小解决方案的空间(记住,Sherlock Holmes关于消除不可能项目后所说的话)。还要注意,此步骤的一个极其重要的部分是消除不可能的事情。删除不可能的项目可以帮助缩小解决方案的空间(记住,Sherlock Holmes关于消除不可能项目后所说的话)。

  • 实验:在此阶段,尝试根据上一步得出的假设重现问题。如果您在上一步中进行了认真的思考,那么这应该非常简单。有时我“作弊”并修改代码库以帮助进行给定的测试。例如,我最近正在调查一次撞车事故,该撞车事故归因于比赛状况。为了验证它,我只是在两行代码之间放置了一个Sleep(500),以允许另一个线程在“正确的”时间执行其不良操作。我不知道在“真实”科学中是否允许这样做,但是在您拥有的代码中这是完全合理的。

如果您成功地复制了它,那么您可能就快完成了(剩下的就是修复它的简单步骤了……但这又是一天了)。确保将新测试检入回归测试系统。我要指出的是,我打算在之前的声明中将其修正为简单易懂。寻找解决方案并实施它可能需要大量的工作。我认为错误的修复不是调试过程的一部分,而是开发过程。而且,如果涉及修复程序,那么就需要进行一些设计和审查。


我见过的大多数错误都无法可靠地再现,并且对于其中的一部分,大多数错误在再现之后仍需要进行大量调试工作,然后才能开始进行任何修复工作。即使您不是说“成功复制它”,而是说“成功地缩小了明确执行该错误的单元测试”,但我想说调试工作还没有结束。对我来说,调试一旦完成,我就可以解决问题,而且我有可靠的证据证明我的修复实际上可以解决问题。
blueberryfields

我同意修复它可能需要进行大量工作。我确实在我的话中说了讽刺意味“修复它的简单步骤”,但这在类型上并不是很好。

4

尝试减少测试用例。当足够小时,通常更容易找到引起问题的相应代码。

可能是新签入导致了该问题,并且以前的每日构建正常。在这种情况下,您来自源代码管理的更改日志应该可以帮助您确定要捕获谁。

另外,如果您使用C / C ++,请考虑运行valgrind或进行净化以隔离与内存相关的问题。


2

调试最困难的部分是隔离问题,尤其是当问题埋在几层下面时。在大学里,我学习了音乐录制,奇怪的是,这里有直接适用的Studio电子课程。我将使用调试工作室环境作为系统调试过程的说明。

  1. 测试您的仪表。使用已知校准电压下的测试音,仪表应显示“ U”(单位增益)。 翻译:如果您的工具损坏了,则无法使用它们找出其他错误。
  2. 从末尾测试每个组件/增益阶段。使用应用于舞台输入的相同测试音调,舞台的输出应保持不变。 翻译:通过从输出向后隔离每个对象,我们可以建立对代码的信任,直到找到混乱的地方。如果您的工具花了几层信号来指示问题,则需要知道两者之间的层对此没有帮助。

调试代码实际上并没有什么不同。当代码引发异常时,调试要容易得多。您可以从该异常的堆栈跟踪中追溯,并在关键位置设置断点。通常在设置变量之后,或在调用引发异常的方法的行上。您可能会发现一个或多个值不正确。如果不正确(当不应该为null或值超出范围时为null),则这是发现为什么不正确的过程。IDE中的断点等效于电子测试点(设计用于仪表的探针以检查电路)。

现在,一旦我完成了发现我真正的问题所在的艰苦部分,我将编写一些单元测试以在将来进行检查。


2

由于我难以在下午晚些时候找到这些令人讨厌的错误,因此我最有效的策略是站起来步行几分钟。通常,有关可能的错误源的新想法会在30秒后开始产生。


2

有关更实用的方法:

  1. 如果该错误与未处理的异常有关,请查看堆栈跟踪。空引用,超出范围的索引等以及您自己定义的异常是最常见的,您可以将此错误分配给初级开发人员,它可能很容易并且是一个很好的学习经验。

  2. 如果不是每台机器都发生这种情况,则可能是竞争条件/线程问题的一种形式。这些都是非常有趣的,可以将无聊的高级程序员放在上面。许多日志记录,丰富的知识和好的工具就可以完成此任务。

  3. 错误的另一大类是测试团队或客户不喜欢特定的行为。例如,他们不喜欢您决定显示用户ID,或者在搜索时不会自动完成。这些是真正的错误,请考虑具有更好的产品管理和更广阔的视野的开发人员。如果开发人员在考虑扩展的情况下构建系统,则应该花相对较短的时间来“修复”此问题。

  4. 通过拥有良好的日志记录系统并收集足够的信息来解决这些错误,可以解决80%的所有其他错误。对多个级别的复杂日志记录系统(例如Log4Net / Log4J)使用内置跟踪

  5. 性能错误是它们自己的类别,这里的golder规则是“先测量,然后再解决!”,您会惊讶地发现有这么多开发人员只是猜测问题出在哪里,然后就去修复它只是为了看看之后,响应时间仅减少3-4%。


如果我可以为那5个人中的每人+1,我一定会的!
jmort253

1

我有两种方法:

  1. 将给定的问题分成较小的部分,然后按照Divide and ConquerParadigm 征服每个较小的部分。
  2. 每当我对任何值有疑问时,我都只打印输出变量的值,以查看变量的确切输入和输出。

这种方法大多数时候对我有帮助。

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.