什么是调试器?
在开发人员生涯中,总有一刻你会问自己:
为什么我的代码不工作?
提出此问题是任何开发人员日常工作的常规环节。 诀窍在于用最少的时间熟练查找并修复 bug,使挫折感降到最低。 当程序中出现 bug 时,每个人通常都有自己的处理方法。 你可能已经尝试过这些调试方法的一种或几种:
- 再次尝试运行程序,因为程序应该会正常工作。
- 边泡澡边思考。
- 再次阅读代码以找出问题。
- 到外面散散步。
- 在代码中编写几个
Console.WriteLine("here")
消息。
通过使用这些方法,你可能会获得不同程度的成功。 使用调试器是业界公认的一种成功率更高的方法。 但调试器究竟是什么呢?
调试器是一种软件工具,用于通过分析方法观察和控制程序的执行流。 其设计目标是帮助找出 bug 的根本原因,并帮助你解决它。 它的工作方式是将程序托管在自己的执行进程中,或者作为附加到正在运行的程序(例如 .NET)的独立进程运行。
各个调试器采用不同的风格。 有些直接从命令行运行,而有些则附带图形用户界面。 在此模块中,我们将使用 Visual Studio Code 的集成图形调试器。
为什么使用调试器
如果不通过调试器运行代码,这意味着你可能在猜测程序中发生的事。 使用调试器的主要好处是可以监视程序运行。 可以一次跟踪一个程序代码行的执行。 这样可以避免猜错。
每个调试器都有其自己的一组功能。 几乎所有调试器所具有的两个最重要的功能是:
- 控制程序执行。 你可以暂停程序并逐步运行它,以便查看执行了哪些代码及其对程序状态的影响。
- 观察程序的状态。 例如,你可以在代码执行期间随时查看变量的值和函数参数。
掌握调试器的使用是开发人员的一项重要技能,但经常被忽视。 它使你更有效地搜寻代码中的 bug,并帮助你快速了解程序的工作原理。
配置 Visual Studio Code 以进行 .NET 调试
首次在 Visual Studio Code 中打开 C# 文件时,你将收到一条提示,提示你安装推荐的 C# 扩展。
Visual Studio Code 将安装 C# 扩展,并将显示另一条提示,提示你添加所需资产来生成和调试项目。
备注
Visual Studio Code 中的 C# 语言支持是市场中的可选安装。 打开 C# 文件时,如果尚未安装此扩展,则 Visual Studio Code 会自动提示你进行安装。 如果在 Visual Studio Code 中生成或调试 .NET 应用程序时遇到问题,则应验证你的项目是否具有支持 C# 语言的必需资产。
断点
正如你在上一单元中了解到,调试器可帮助你分析和控制程序的执行。 启动 Visual Studio Code 调试器时,会立即开始执行你的代码。 由于代码的执行速度非常快,因此你需要能够对任何语句暂停程序。 你将使用断点执行此操作。
可以通过在要中断的行上单击行号左侧来在 Visual Studio Code 中添加断点。 启用断点后,应该会看到一个红色圆圈。 若要将其删除,再次选择该红色圆圈。
如果通过右键单击添加断点,则还可以选择“添加条件断点”。 这种特殊的断点允许输入中断执行的条件。 此断点仅在满足指定条件时才处于活动状态。 还可以通过右键单击现有断点并选择“编辑断点”来修改该断点。
Visual Studio Code 调试器概述
设置断点并启动应用后,屏幕上将显示新的信息面板和控件。
- 调试器启动控件
- 变量状态
- 受监视的变量状态
- 当前调用堆栈
- 断点
- 执行控件
- 当前执行步骤
- 调试控制台
调试器启动控件
在侧边栏顶部,可以找到启动控件:
- 开始调试。
- 选择活动启动配置。
- 编辑
launch.json
文件。 如有需要,可创建它。 - 打开调试终端。
查看和编辑变量状态
分析导致程序缺陷的原因时,请监视变量状态以查找意外更改。 可使用“变量”面板来执行此操作。
变量按范围划分:
- 局部变量,可在当前范围(通常为当前函数)内访问。
- 全局变量,可从程序中任何位置访问。 还包含来自 JavaScript 运行时的系统对象,因此如果你在其中看到很多内容,请不要感到惊讶。
- 闭包变量,可从当前闭包(如果有)中访问。 闭包将函数的本地范围与它所属的外部函数的范围结合在一起。
选择箭头可以展开范围和变量。 展开对象时,可以看到该对象中定义的所有属性。
可以通过双击变量动态地更改该变量的值。
还可以通过在编辑器窗口中将鼠标直接悬停在函数参数或变量上来查看其值。
监视变量
如果要在一段时间内或不同函数间跟踪变量状态,每次都进行搜索可能很繁琐。 “监视”面板在这种情况下很方便。
可选择加号按钮,输入要监视的变量名称或表达式。 还可以在“变量”面板中右键单击某个变量并选择“添加到监视”。
代码运行时,监视面板内的所有表达式将自动更新。
调用堆栈
每次程序输入函数时,都会向调用堆栈中添加一个条目。 当应用程序变得复杂,多次在函数内调用函数时,调用堆栈会显示函数调用的痕迹。
查找异常的来源很有用。 如果程序中出现意外故障,你会在控制台中看到类似以下示例的内容:
Unhandled exception. System.IndexOutOfRangeException: Index was outside the bounds of the array.
at OrderProcessor.OrderQueue.ProcessNewOrders(String[] orderIds) in C:\Users\Repos\OrderProcessor\OrderQueue.cs:line 12
at OrderProcessor.Program.Main(String[] args) in C:\Users\Repos\OrderProcessor\Program.cs:line 9
错误消息下方那一组 at [...]
行称为“堆栈跟踪”。 堆栈跟踪提供了出现异常前调用的每个函数的名称和源。 但是它可能有点难以理解,因为它还包含来自 .NET 运行时的内部函数。
Visual Studio Code 调用堆栈在这种情况下很方便。 默认情况下,它会筛选掉不需要的信息,仅显示你自己代码中的相关函数。 然后,你可以展开此调用堆栈以找出引发异常的位置。
断点
在“断点”面板中,可以查看和开关在代码中放置的所有断点。 还可以切换在捕获或未捕获异常时中断的选项。 可以使用“断点”面板检查程序状态,并在发生异常时使用“调用堆栈”追溯异常的来源。
控件执行
可以使用这些控件来控制程序的执行流。
从左到右,控件为:
- 继续或暂停执行。 如果执行已暂停,则它将继续,直到命中下一个断点。 如果程序正在运行,则按钮会切换到暂停按钮,可以使用该按钮来暂停执行。
- 单步跳过。 执行当前上下文中的下一个代码语句。
- 单步执行。 与单步跳过类似,但如果下一个语句是函数调用,则转到该函数的第一个代码语句(与
step
命令相同)。 - 单步跳出。如果在函数内部,则执行该函数的其余代码,并在初始函数调用后跳回到该语句(与
out
命令相同)。 - 重启。 从头开始重启程序。
- 停止。 结束执行并退出调试器。
使用调试控制台
对于 Windows 和 Linux,可以选择 Ctrl+Shift+Y 来显示或隐藏调试控制台。 对于 Mac,请选择“Cmd+Shift+Y”。 你可以使用调试控制台来可视化应用程序控制台日志。 你还可以使用它来评估表达式或执行当前执行内容中的代码,例如内置 .NET 调试器中的命令或变量名称。
可以在调试控制台底部的输入字段中输入 .NET 表达式,然后选择 Enter 对其求值。 结果直接显示在控制台中。
通过使用调试控制台,可以快速检查变量值、使用不同的值测试函数或更改当前状态。
备注
尽管调试控制台对执行和评估 .NET 代码非常有用,但当你尝试执行或调试 .NET 控制台应用程序时,这可能有点令人困惑。 这是因为,调试控制台不接受正在运行的程序的终端输入。
若要在调试时处理终端输入,可以使用集成终端(Visual Studio Code 窗口之一)或外部终端。 本教程中使用集成终端。
打开 .vscode/launch.json。
将
console
设置更改为integratedTerminal
从:
"console": "internalConsole",
到:
"console": "integratedTerminal",
保存更改。
在下一单元中,你将了解如何使用调试器修复我们前面看到的斐波那契代码中的 bug。