Debugging Guide
A. 什么是调试?
正如你在本课程中所做的那样,当你编写大量代码时,你会遇到各种类型的错误。其中一些错误,比如语法错误,很容易被发现,因为编译器会准确地指出它们的位置。不幸的是,代码逻辑中的错误就没那么简单了。它们不太可能被系统捕获,因为通常你的代码可以编译,但只是返回了错误的值。IntelliJ(以及大多数其他 IDE)提供了一系列工具,可以帮助你跟踪代码,并准确地看到哪里出现了不一致。
B. 为什么我应该学习调试?
调试是程序员一项至关重要的技能——无论你多么聪明,如果花费大量时间才能找到代码中的错误,那么完成一个项目就需要很长时间。每个程序员都需要知道如何有效地调试。在本课程中,学会自己调试可以让你免于在答疑时间等待助教的帮助。为此,这份备忘单将为你提供调试各种类型错误所需的所有工具!
C. 我可以用打印语句代替吗?
理论上,是的。在某些情况下,打印语句效率更高,例如,当只需要在一段相对独立的代码中,通过循环跟踪一个变量时。你可以先使用打印语句进行调试,如果在 5 分钟后仍然难以找到错误,则可以使用调试器。但要注意,如果你没有事先单步调试就来参加答疑,我们会将你指向本指南,而不是帮你查找错误。
D. 理解堆栈跟踪
当你看到这样的异常时,这意味着你的计算机在运行程序时发现了一个错误。为了方便你找到错误,程序退出前会提供所有必要的信息。第一行遵循以下格式:
[哪个线程出错] [发生了什么错误] [关于错误的更多信息]
我们重点关注后两部分。在这种情况下,它看起来像这样:
Exception in thread “main” java.lang.ArrayIndexOutOfBoundException: Index 5 out of bounds for length 5
这为你提供了两条信息:
错误类型是 数组索引越界异常。
试图访问一个长度为5的数组的第6个元素。
这告诉你发生了什么,但没有告诉你它发生在哪里。这就是堆栈跟踪的用处。标题下面的几行描述了程序出错时,代码执行的具体位置。最上面的跟踪行显示的是程序崩溃时正在执行的代码。下面的列表则以倒序展示了函数之间的调用关系。在这里,我们看到我们的 main
函数在第 6 行调用了 make2DArray
。然后,make2DArray
在第 14 行调用了 makeArray
。最后,代码在 makeArray
中的第 23 行出错。单击蓝色链接会将你跳转到代码的该部分,因此你无需花费时间滚动。掌握这些信息后,你就可以开始调试,找出导致错误的具体调用过程。
E. 这个错误是什么意思
也许你已经阅读了堆栈跟踪,但你不明白这个错误是什么意思。通常,Java 错误的命名具有一定的自解释性,但如果遇到不熟悉的错误,可以参考下面的备忘单:
错误 | 通常的含义是 |
---|---|
___ expected | 缺少___ |
cannot find ___ | 您正在调用计算机无法访问的方法或类。 |
Illegal start of expression | 这行代码之前缺少一个右花括号。 |
Illegal start of type | 你在函数体外写了不该有的代码。 |
Incompatible types -- expected ___ | 你试图将一个值赋给类型不匹配的变量。 |
Missing method body | 你的函数声明末尾多了个分号。 |
Missing return statement | 您应该在此方法中返回一些内容,但您没有。 |
Non-static method cannot be called from a static context | 你直接用类名调用了非静态方法,应该用类的实例调用。 |
*Program Freezes* | 你的程序可能卡死在某个逻辑循环里了。 |
F. 使用调试器单步执行
通过 IntelliJ 运行代码
要使用调试器,您需要通过 IntelliJ 而不是通过控制台运行代码。
从主菜单中,转到 运行 -> 编辑配置。
在标记为 Program Arguments(程序参数)的字段中输入程序的任何参数。 这些参数是您在控制台中运行程序时传递到命令行的参数。
要运行调试器,请单击 Run > Debug(运行 > 调试)或右键单击函数旁边的绿色
箭头,然后选择 Debug(调试)。
设置断点
为了观察代码运行时的状态,我们可以设置断点。 断点会让程序暂停在指定行,方便你查看出错时各个变量的值。 要设置断点,请单击行号和代码之间的空格:
这种断点只会在您的计算机第一次遇到此行时暂停代码。 如果错误只在特定变量取特定值时发生,你可以设置条件断点。
为此,请设置一个普通断点,然后右键单击出现的红色圆圈。 现在,您可以在给定的字段中设置断点条件。 您的条件可以是任何在此代码点编译的 True/False 语句。 这意味着您必须使用当前帧中已存在的变量,但它们不一定必须在当前行中引用。
单步执行代码
顶部工具栏
要单步执行代码,您需要了解调试视图顶部的工具栏。 把鼠标悬停在图标上就能看到它们的名称了。 从左到右,它们是:
按钮 | 功能 |
---|---|
Step Over | 执行当前行,跳到下一行。 |
Step Into | 进入当前行调用的函数 (如果是你写的)。 |
Force Step Into | 强制进入函数 (即使是第三方库的函数,一般用不到)。 |
Step Out | 跳出当前函数或循环,回到调用它的地方。 |
Drop Frame | 回到上一层调用栈 (相当于时间倒流,如果你错过了想看的部分)。 |
Run to Cursor | 运行到光标处 (相当于在光标位置设置一个临时断点)。 |
左侧工具栏
调试菜单的左侧还有一个工具栏。 此菜单用于更常规的控件。 从上到下,它们是:
按钮 | 作用/说明 |
---|---|
再次运行/重新启动 | 使用与当前运行相同的设置再次运行调试器。 |
继续执行程序 | 继续程序执行,直到遇到下一个断点。 |
暂停程序 | 如果程序卡住了/好像死机了,请在没有断点的情况下以调试模式运行,并在程序卡住时点击暂停。 很可能暂停在导致程序卡顿的逻辑循环中。 |
停止程序 | 调试完成后,可以点击此按钮提前结束程序。 |
查看断点 | 点击后会打开一个窗口,显示所有断点。 您可以在此编辑断点设置,并启用/禁用它们。 |
静音断点 | 启用/禁用所有断点 / 开启/关闭所有断点。 |
查看当前状态
在调试模式下,您可以通过两个地方获取有关程序状态的信息。
调试视图
首先,调试视图分为两列:
第一列显示到目前为止的堆栈跟踪。 每一行描述一个堆栈帧,从最内层到最外层排列。 最前面的是帧的名称,也就是被调用函数的名称。 然后,我们看到该帧当前所在的行号。 在此示例中,
make2DArray
在第 14 行调用了makeArray
,因此它会停留在该行,直到makeArray
完成运行。您只能看到当前帧中的变量,其中包括任何全局变量。 在这里,您可以查看它们在您所在的行中的值。
如果您想要比每个变量的值更多的信息,工具栏中有一个名为“求值表达式”的按钮。 按下此按钮允许您基本上即时地将行插入到程序中。
例如,我们在此处输入表达式 arr[i] = 1000
并点击“求值”,结果会显示在“结果面板”和“变量面板”中。 这相当于在 arr[i] = makeItem(i)
之前插入了 arr[i] = 1000
这行代码。 这样做的好处是不会修改代码,避免意外改变程序的行为。
代码视图
获取信息的第二个地方是在代码本身上。
在调试模式下,IntelliJ 会在每行代码旁边显示每行代码中引用的每个变量的值。 同时,最近修改的变量值会被高亮显示。
G. 更多资源
如果您想了解有关使用 IntelliJ 调试器的更多详细信息,JetBrains 提供了一些指南: