调试的最佳实践

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

我的方法就是这样的。

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

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

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

根据需要重复步骤。

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

调试的任何其他有用的提示或最佳实践?

一个最好的做法是不要立即深入调试,而是查看代码并思考一段时间。

如果有一个提示我可以给每个人调试它会再次打破它。

也就是说,当你认为你已经找到修复程序并且系统似乎有效时。 返回修复程序,查看系统是否再次中断。

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

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

HTH。

干杯,

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

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

[编辑]我发现我在哪里读到橡皮鸭橡皮鸭调试

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

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

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

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

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

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

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

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

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

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

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

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

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

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

替代文字http://ecx.images-amazon.com/images/I/51RQ146x9VL._SS500_.jpg

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

过了一会儿,你只需要培养必要的直觉(然后你的日记就会成为你所征服的所有讨厌敌人的非常有趣的记忆)

就像Conceptual Blockbusting所说,我喜欢在遇到困难时尝试不同的方式。 “printf debugging”,思考行为,对代码进行二进制搜索,对版本控制提交进行二进制搜索,编写unit testing以进行澄清,抓取重构 ,以及触发调试器。

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

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

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

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

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

顺便说一句,这就是为什么Linus反对添加内核调试的内置支持。

我将在一个类似的主题上解释我的答案 (这实际上是joseph.ferris对这个主题的回答中的最后一个要点):

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

将源文件的修订版与先前版本区分开来。 差异可能使错误的原因显而易见。

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

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

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

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

我只是在另一篇文章中重播,问题是C调试,但正如我在重播中所述,我认为调试技术与语言无关。

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

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

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

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

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