标准的调试方法

时间:2009-04-08 03:47:15

标签: debugging standards

调试问题的标准方法是什么?这可能看起来像一个相当广泛的问题,有些人回答“这取决于问题”,但我认为我们很多人本能调试,并没有真正尝试过我们的过程。这就是为什么我们说'它取决于'。

我最近被迫说出了我的流程,因为一些开发人员和我正在解决相同的问题,我们正在以完全不同的方式对其进行调试。我希望他们理解我想要做的事情,反之亦然。

经过一番反思后,我意识到我的调试方式实际上非常单调。我首先尝试能够可靠地复制问题(特别是在我的本地机器上)。然后通过一系列消除(这是我认为它依赖于问题的地方)尝试识别问题。

其他人试图以完全不同的方式做到这一点。

那么,只是想知道你们那里有什么工作?如果你不得不用文字形式化它,你会说你的过程是用于调试的吗?

顺便说一句,我们还没有发现我们的问题=)

11 个答案:

答案 0 :(得分:7)

我的方法因我对手头系统的熟悉程度而异。通常我做的事情如下:

  1. 尽可能复制失败。
  2. 检查失败状态以确定失败的直接原因。
  3. 如果我熟悉这个系统,我可能会对根本原因有一个很好的猜测。如果没有,我开始通过软件机械追踪数据,同时挑战软件的基本假设。
  4. 如果问题似乎具有一致的触发器,我可以使用调试器手动前进代码,同时挑战代码所做的隐含假设。
  5. 当然,追根溯源的事情可能会变得多毛。这就是拥有转储(或更好,一个活的,破碎的过程)可以真正无价的地方。

    我认为调试过程中的关键点是挑战前概念和假设。 我在该组件中发现错误的次数我或者一位同事发誓工作得很好。

    我的更直观的朋友和同事告诉我,当他们看我调试或让我帮他们解决问题时,我很迂腐。 :)

答案 1 :(得分:4)

考虑抓住David J Agans所着的书“Debugging”。副标题是“找出最难以捉摸的软件和硬件问题的9个不可或缺的规则”。他的调试规则列表 - 在网站上以海报形式提供(并且还有该书的链接):

  • 了解系统
  • 让它失败
  • 退出思考,看看
  • 分而治之
  • 一次改变一件事
  • 保持审计跟踪
  • 检查插头
  • 获取全新视图
  • 如果您没有修复它,则无法修复

最后一点与软件行业特别相关。

答案 2 :(得分:3)

当我遇到一个我无法理解的错误时,我想制作一个问题模型。复制问题代码部分,并开始一次删除一部分功能。每次删除后对代码运行单元测试。通过这个过程,您将删除带有错误的功能(因此,找到错误),或者您将把错误隔离到包含问题本质的核心代码片段。一旦你弄明白问题的本质,它就更容易修复。

答案 3 :(得分:2)

我通常首先根据手头的信息形成一个假设。一旦完成,我努力证明它是正确的。如果它被证明是错误的,我会从一个不同的假设开始。

使用这种方法可以很容易地解决大多数多线程同步问题。

此外,您还需要充分了解正在使用的调试器及其功能。我在Windows应用程序上工作,并发现windbg非常有助于发现错误。

答案 4 :(得分:2)

将错误减少到最简单的形式通常会导致对问题有更深入的了解,并增加在必要时能够让其他人参与的好处。

设置快速复制方案,以便有效利用您的时间来测试您选择的任何hypothosis。

创建工具以快速转储环境以进行比较。

创建和重现日志记录的bug已转到最高级别。

检查系统日志是否有任何警告。

查看文件日期和时间戳,以了解问题是否可能是最近的介绍。

在源存储库中查看相关模块中的最近活动。

应用演绎推理并应用Ockham's Razor原则。

愿意退后一步,暂时解决问题。

答案 5 :(得分:2)

我在网上挑选了一些我不记得的内容(可能已被CodingHorror ...)

调试101:

  • 重现
  • 逐步缩小范围
  • 避免使用调试器
  • 一次只改变一件事

心理方法:

  • 橡皮鸭调试
  • 不要推测
  • 不要太快责怪工具
  • 了解问题和解决方案
  • 休息一下
  • 考虑多种原因

错误预防方法:

  • 监控您自己的故障注入习惯
  • 尽早介绍调试工具
  • 松散耦合和信息隐藏
  • 编写回归测试以防止再次出现

技术方法:

  • 惰性跟踪声明
  • 查阅第三方产品的日志文件
  • 在网上搜索堆栈跟踪
  • 按合同介绍设计
  • 擦拭Slate Clean
  • 间歇性错误
  • Explot Localility
  • 介绍虚拟实现和子类
  • 重新编译/重新链接
  • 探测边界条件和特殊情况
  • 检查版本依赖关系(第三方)
  • 检查最近更改的代码
  • 不信任错误信息
  • 图形错误

答案 6 :(得分:1)

我也是使用淘汰过程的忠实粉丝。排除变量极大地简化了调试任务。这通常是应该做的第一件事。

另一种非常有效的技术是尽可能回滚到上一个工作版本,然后再试一次。这可能非常强大,因为它可以让您更加谨慎地行事。对此的一种变体是将代码提供到一个工作点,功能较少,而不是使用更多功能。

当然,不仅仅是尝试一下,这一点非常重要。这会增加你的绝望,因为它永远不会奏效。我宁愿做50次跑步来收集有关这个bug的信息,而不是采取疯狂的行动并希望它有效。

答案 7 :(得分:1)

我发现“调试”的最佳时间是在编写代码时。换句话说,要防守。检查返回值,自由使用assert,使用某种可靠的日志记录机制并记录所有内容。

为了更直接地回答这个问题,我调试问题的最有效方法是阅读代码。有了日志可以帮助您找到快速阅读的相关代码。没有记录?花时间把它放进去。看起来你可能不会发现这个错误,而你可能不会。日志记录可能会帮助您找到另一个错误,最终一旦您完成了足够的代码,您就会发现它......比设置调试器并尝试重现问题,单步执行等等。

在调试时,我试着想一想可能存在的问题。我提出了一个相当随意的分类系统,但它适用于我:所有的错误都属于四个类别之一。请记住,我在谈论运行时问题,而不是编译器或链接器错误。这四个类别是:

  • 动态内存分配
  • 堆栈溢出
  • 未初始化的变量
  • 逻辑错误

这些类别对我来说对C和C ++最有用,但我希望它们能在其他地方很好地应用。逻辑错误类别很大(例如,当正确的东西是< = b时放一个< b),并且可能包括无法在线程之间同步访问的内容。

知道我在寻找什么(这四件事之一)有助于找到它。找到错误似乎总比修复它们困难得多。

调试的实际机制通常是:

  1. 我是否有自动测试来证明问题?
    • 如果没有,请添加失败的测试
  2. 更改代码,以便测试通过
  3. 确保所有其他测试仍然通过
  4. 检查更改
  5. 您的环境中没有自动化测试?没有时间像现在一样设置它。组织的东西太难了,所以你可以测试你的程序的各个部分?花点时间做到这一点。可能需要“太长时间”来修复这个特定的错误,但是越快开始,其他一切都会越快。同样,您可能无法解决您正在寻找的特定错误,但我打赌您会在此过程中找到并修复其他错误。

答案 8 :(得分:0)

我的调试方法不同,可能是因为我还是初学者。

当我遇到逻辑错误时,我似乎最终会添加更多变量以查看哪些值到达哪里然后我会在导致问题的代码中逐行调试。

答案 9 :(得分:0)

复制问题并生成可重复的测试数据集绝对是调试的第一步也是最重要的一步。

如果我能识别出可重复的错误,我通常会尝试隔离所涉及的组件,直到找到问题为止。经常我会花一点时间排除案件,所以我可以明确说明:问题不在于组件X(或过程Y等)。

答案 10 :(得分:0)

首先,我尝试复制错误,但无法复制错误,在非平凡的程序中基本上不可能猜出问题。

然后,如果可能的话,在单独的独立项目中分解代码。这有几个原因:如果原始项目很大,则很难进行第二次调试,它会消除或突出显示有关代码的任何假设。

我通常总是有另一个VS open的副本,我用它来用于迷你项目中的调试部分,以及测试我后来添加到主项目中的例程。

一旦在单独的模块中重现了错误,就几乎赢得了战斗。

有时打破一段代码并不容易,所以在这些情况下我会根据问题的复杂程度使用不同的方法。在大多数情况下,关于数据的假设似乎来了,咬我,所以我尝试在代码中添加大量断言,以确保我的假设是正确的。我还使用#ifdef禁用代码,直到错误消失。消除对其他模块的依赖等等......像秃鹫一样慢慢盘旋在虫子里......

我认为我没有真正有意识的做法,它变化很大,但一般原则是消除问题的噪音,直到它是非常明显的是什么。希望我听起来不会太混乱:)。