MIT_6.031之系统调试
MIT_6.031之系统调试
系统调试的过程
在整个系统连接在一起后发现错误,往往是在用户报告问题后进行的调试。以下是处理这类问题的步骤:
1. 重现错误
重现错误是调试过程中至关重要的一步。以下是详细步骤和示例,帮助理解如何有效地重现错误:
找到可重复的测试用例
- 首先,定义问题:
- 用户报告的问题可能不够具体,您需要明确知道错误发生的上下文。例如,如果用户发现应用程序崩溃,您需要了解崩溃发生时的操作步骤。
- 创建小的测试用例:
- 设法从用户的使用场景中提取出最小的、可重复的代码片段。例如,如果用户报告在处理长文本时出现问题,可以尝试创建一个只包含必要文本的小字符串作为输入。
示例:
1 | public class WordCounter { |
挑战
- 多线程和事件依赖:
- 如果程序是多线程的,错误可能与线程的执行顺序相关。例如,在图形用户界面应用中,某些操作可能依赖于用户的点击顺序或系统事件的发生。
示例:
1 | public class ButtonClick { |
在这种情况下,您可能需要使用工具如 JUnit 或 Mockito 来模拟并发情境,确保一致性。
回归测试
- 将测试用例添加到回归测试套件:
- 在成功修复错误后,确保将测试用例添加到您的回归测试中,以便将来可以自动检测该错误是否重新出现。
示例:
1 | public class RegressionTestSuite { |
2. 减少错误范围
- 缩小问题范围:
- 如果用户传递了莎士比亚剧本的整个文本并发现错误,例如在调用
mostCommonWord(allShakespearesPlaysConcatenated)
时未返回预期结果(如“the”或“a”),而是返回意外结果(如“e”),需要通过分步调试来缩小错误范围。
- 如果用户传递了莎士比亚剧本的整个文本并发现错误,例如在调用
- 分步调试方法:
- 检查上半部:确定莎士比亚的上半部是否显示相同错误。
- 单个剧本:检查单个剧本是否产生相同错误。
- 单个语句:进一步细化到单个语句,查看是否会重复错误。
- 回归测试样例:
- 将简单的测试样例加入回归测试,确保日后不会再次出现相同问题。
3. 了解错误的位置和原因
- 研究数据:
- 查看导致错误的测试输入、错误结果、失败的断言及其对应的堆栈跟踪。
- 假设:
- 提出假设,找出错误可能发生的地方,确保假设尽可能普遍。
4. 实验与重复
- 设计实验:
- 设计实验以测试假设,观察问题并尽量减少对原系统的干扰。
- 收集数据:
- 将实验中收集的数据与原始内容结合,形成新的假设。通过实验,逐步排除一些可能性,并缩小错误位置和原因的范围。
5. 分析堆栈跟踪
阅读堆栈跟踪是调试过程中的关键部分,以下是如何解读的示例:
1 | java.lang.NullPointerException |
- 最上层调用:通常在库代码内部(如
java.util
),表示抛出异常的地方。 - 自己的代码:在堆栈中间,指向自定义代码(如
turtle.TurtleSoupTest.testPersonalArt
),需重点关注。
解读步骤
- 识别异常类型:
- 堆栈跟踪的第一行通常包含异常类型。在这个例子中,
java.lang.NullPointerException
表示程序试图访问一个为null
的对象。- 查看最上层调用:
- 堆栈跟踪的最上层是引发异常的地方,通常是在库代码内部。这是异常首次发生的位置。在上面的例子中,错误发生在
java.util.Objects.requireNonNull(Objects.java:203)
。这行代码尝试确保输入不为null
,因此您需要检查是什么导致了这个输入为null
。- 跟踪调用链:
- 向下查看调用栈中的每一行,以了解程序的执行顺序:
at java.util.AbstractSet.removeAll(AbstractSet.java:169)
表示在尝试移除集合中的元素时,也可能存在空引用的问题。at turtle.TurtleSoup.drawPersonalArt(TurtleSoup.java:29)
表示在TurtleSoup
类的drawPersonalArt
方法中调用了removeAll
,您需要检查该方法在第 29 行的实现,以了解输入数据来源。at turtle.TurtleSoupTest.testPersonalArt(TurtleSoupTest.java:39)
显示错误是由测试用例触发的,因此您还应该检查测试代码,确保传递给drawPersonalArt
的参数都是有效的。- 聚焦于自己的代码:
- 自定义代码的位置在堆栈的中间和底部部分。在这个示例中,
turtle.TurtleSoupTest.testPersonalArt
可能是一个测试方法,您需要重点关注这个测试用例,分析其如何调用drawPersonalArt
以及输入参数是什么。- 研究数据:
- 检查导致异常的输入数据。根据
drawPersonalArt
方法的实现,查找可能的null
引用并加以处理。可以通过调试器或在关键代码段插入打印语句来检查变量值。- 假设和实验:
- 根据分析,提出可能的假设。例如,您可以假设某个参数未初始化,或者某个集合在调用
removeAll
前未正确定义。然后设计实验来验证这些假设,比如在测试中添加断言来确保输入不为null
。
6. 假设与实验
- 假设错误来源:
- 假设错误可能存在于
splitIntoWords()
函数中,并进行实验验证该假设。
- 假设错误可能存在于
- 二分搜索:
- 通过二分搜索方法,将工作流分成两半,优先验证旧代码相对新代码的可靠性。
7. 运行其他测试用例
- 插入打印语句或断言:
- 在程序中插入打印语句或断言,以检查内部状态。
- 使用调试器:
- 设置断点,单步执行代码,观察变量和对象的值。
8. 避免不当做法
- 不要将错误视为特殊案例:
- 避免将错误作为特殊案例解决,因为这会增加复杂性并掩盖问题。
9. 更换组件
- 组件替换:
- 在怀疑特定组件时,尝试更换实现(例如,用线性搜索替代二分搜索)。但需谨慎,避免不必要的时间浪费。
10. 修正错误
- 识别错误类型:
- 判断是编码错误还是设计错误。设计错误可能需要更全面的审查,以查看其他客户端是否受到影响。
- 影响分析:
- 考虑修复程序是否可能引入新的问题。
11. 回归测试
- 将错误的测试用例添加到回归测试套件,并运行所有测试以验证修复是否有效。
12. 总结
- 错误重现:
- 将错误作为测试用例重现并纳入回归测试中。
- 科学方法:
- 采用科学方法发现和修复错误,确保认真且不草率的修正,记录详细笔记以备后续查阅。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Totoroの旅!
评论