调试的最佳实践

时间:2008-12-14 11:56:22

标签: c# .net debugging

我最近使用Visual Studio和WinDbg对托管应用程序进行了大量调试,因此我经常要求协助同事调试情况。有几次我发现人们只是在这里和那里插入断点并希望最好。根据我的经验,这很少是一种有用的技术。

我的方法就是这样。

  • 重现问题。理想情况下尽可能减少输入。

  • 检查出现了什么问题并列出了可能出现错误的理论。

  • 通过调试代码的特定区域,一次检查一个理论。

根据需要重复步骤。

对于复杂的调试问题,我经常与同事一起工作。对于WinDbg,这特别有用。

有关调试的其他有用提示或最佳做法吗?

16 个答案:

答案 0 :(得分:11)

一个非常好的做法是不要立即深入调试器,但要查看代码并认真思考一段时间。

答案 1 :(得分:11)

如果有一个提示我可以给每个人调试,那就是再打破它。

也就是说,当你认为你找到了修复程序并且系统似乎正常工作时。返回修复程序,查看系统是否再次中断。

有时你可能会迷失在你尝试作为潜在解决方案的过程中,并在调试问题时完成系统完全不同的区域。然后你会忘记你在原来工作区域改变了什么。

支持修复然后再现问题可确保候选修复不依赖于您在系统的其他部分中更改的其他内容。您的修补程序补丁是一个正确的独立解决方案。

HTH。

欢呼声,

罗布

答案 2 :(得分:8)

我不确定我在哪里读到“橡皮鸭调试”,但我认为它很棒。基本的想法是在桌面上设置橡皮鸭并向其解释代码。这个想法是,当你向鸭子解释代码时,你最终会发现自己说“现在,这种情况发生了”,你会注意到“这个”并不是你想要发生的事情。

缺少一只鸭子,我发现我只是通过代码并向自己解释。它有效,但我仍然认为我可能带来一只鸭子。

[编辑] 我找到了关于橡皮鸭Rubber Duck Debugging

的内容

答案 3 :(得分:5)

正如另一张海报所说,通过一些艰难的思考,如果你了解正在发生的事情,通常可能只看到逻辑错误。

但通常我们认为我们这样做,而我们没有,或者我们只需要修理一些我们不太了解的东西,所以它又回到了第一原则。

重现问题无疑是至关重要的第一步。如果你不能这样做,那么除非意外,否则你没有机会找到问题。

下一步是毫无疑问地确定通过臭虫命中时实际执行的代码的路径。在可能包含许多事件和多个线程的WinForms应用程序中,这可能只是一个微不足道的练习。

直到你确切地知道代码的去向,世界上关于bug可能在哪里的所有理论都是毫无价值的。如果代码很复杂,那么发现代码不会在断点处停止可以像停止它一样提供信息。

根据我的经验,早期使用断点通常可以成为发现代码工作方式的重要工具。

我经常发现当一个问题看起来特别棘手时,这是因为我对发生的事情做了一个致命的假设,而不是实际验证它。

所以我的'最佳实践'不会继续下去,直到我确定我理解,而不是猜测。

答案 4 :(得分:4)

与调试没有直接关系,但为了使调试在将来更容易,有几点需要考虑:

  • 实施单元测试,最好是以TDD的形式,迫使您继续执行任务并仅在通过测试的目标下进行开发。当你编写测试代码而不是任务时,“漫游”更难。
  • 参与定期重构代码的练习。小型的on-point方法比单片“所有行业的杰克”方法更容易调试。
  • 利用您的团队成员。通常添加一组额外的眼睛可以帮助冲洗掉一些东西。如果你没有以相对快速的方式找到某些东西,你可能会继续忽视它一段时间。
  • 您始终可以在版本控制系统中回滚代码,以尝试隔离导致错误引入的文件版本。一旦你这样做,你可以在最后一次好坏和第一次坏之间进行区分,只关注两者之间的变化。

答案 5 :(得分:3)

VS.NET中使用C#或VB.NET等语言进入调试器的门槛非常低,以至于在您知道问题并且只是单步执行的情况下插入一个或两个断点通常更容易。

有时候我会发现自己正在使用Edit&继续编写代码。这很棒。您可以立即看到结果。当有一些算法或相对难以理解的循环时,它通常是最有用的。

答案 6 :(得分:2)

这本书老实说是我读过的关于调试的最好的,特别是当你超出正常的调试情况时。它包含许多技巧,阅读所有“真实故事”很有趣。如果你正在处理大量你自己没有写过的代码,特别是如果它很糟糕,那么这本书是必须的!

http://www.amazon.com/Debugging-Applications-Microsoft%C2%AE-Microsoft-Pro-Developer/dp/0735615365/ref=sr_1_1?ie=UTF8&s=books&qid=1238705836&sr=1-1

alt text http://ecx.images-amazon.com/images/I/51RQ146x9VL._SS500_.jpg

答案 7 :(得分:2)

有帮助的东西,特别是当你刚接触调试时,就是保留某种调试日志,并解决你过去解决的问题。大多数错误遵循相对常见的模式(例如,非线程应用程序中显然随机的问题通常是由于未定义的变量或类似使用未初始化的内存)并且通过跟踪这些模式,您将更好地确定未来的问题。

过了一会儿,你只需要发展必要的直觉(然后你的日记对你所征服的所有讨厌的敌人都会变得非常有趣)

答案 8 :(得分:1)

IMO过多的准备是浪费时间。如果你比较熟悉代码库,你通常可以立即想到问题所在的几个关键位置。在那里放置断点,看看你是否正确。每当您看到更好的关键点时,请移动断点以更接近问题。

当您追踪诸如空指针之类的错误数据时,它取决于它来自何处:如果它作为参数传递,请查看调用堆栈以查找它来自何处。如果它是某些数据结构或对象的一部分(更简单的情况),请在那里放置一个断点,以查看它何时以及如何被修改。

Conitional断点可以提供很大帮助,否则您可以通过添加包含no-ops的if语句来模拟它们。如果在遇到问题之前在热点中有一个断点太频繁,请将其停用并将另一个断点放在一个你知道在问题出现前不久会被击中的地方,然后激活热点中的那个。

答案 9 :(得分:1)

一个好的做法是确保你没有修复症状,但原因是什么。

通常情况下,人们可能会在调试时看到一个奇怪的值并在那里修复它,而不会检查是什么原因导致值到达那里。当然,这是一个非常糟糕的主意。

BTW,这就是为什么Linus反对添加内核调试的内置支持。

答案 10 :(得分:1)

我会解释my answer on a similar thread(这基本上是joseph.ferris's answer to this thread中的最后一个要点):

使用版本控制系统,使用二叉搜索树方法隔离引入错误的文件版本。

将源文件的修订版与先前版本区分开来。差异可能会使错误明显。

答案 11 :(得分:1)

这绝不是技术提示,但它通常适用于我的情况。

只需停止努力即可查找根本原因或修复错误。放松一下:散步,吃晚餐,或者转换到另一项任务(希望更容易) - 无论你喜欢什么......

...然后再考虑一个问题,当你再次“新鲜”的时候。追溯您已经完成的调试的所有心理过程(您所做的理论,实验,假设等)。有可能你会立即看到一些你忽略的关键因素;)。

程序员(或者至少是我)倾向于在长时间的调试过程中逐渐缩小他们对问题的看法并消除创造力。但广泛的视角与创意相结合是人类与虫子斗争中最有力的武器!

答案 12 :(得分:1)

就像Conceptual Blockbusting所暗示的那样,每当我遇到困难时,我都会尝试不同的方式。 “printf debugging”,思考行为,对代码进行二进制搜索,对版本控制提交进行二进制搜索,编写单元测试以澄清scratch refactoring,并启动调试器。

答案 13 :(得分:0)

我喜欢回家的一件事是,你有一个实例在工作,一个没有(比如生产和开发)它是关于差异的,你需要清楚地确定那些可能是什么并处理它们一个在。环境问题可能是最难追查的,如果你不系统地工作,你就会疯狂。

顺便说一句,这是我习惯性地通过IIS而不是cassini运行我的VS webapp项目的原因之一。

答案 14 :(得分:0)

我刚刚在another post重播,问题是C调试,但正如我在重播中所述,我认为调试技术与语言无关。

答案 15 :(得分:0)

我开始对我的所有项目做的另一件事是添加一个TraceListener(或派生类)并使用它来获取我的应用程序的关键快照。

这通常让我很清楚在哪里集中我的初始调试工作。

另外,我可以使用配置文件开关打开/关闭它,所以我甚至可以在生产系统上获得提示,而无需重新编译代码。