练习 - 使用 Visual Studio Code 进行调试

已完成

现在是时候实践你新获得的调试知识了。 我们刚好有一个完美的机会。 在我们的 Tailwind Traders 应用中,我们正在开发一项新功能:允许以多种货币显示产品的价格。 一位同事为这一目的编写了一些代码,但却没办法找出是哪里出了问题。 我们来帮帮忙。

在 Visual Studio 工作区中创建 JavaScript 文件

对于本练习,需要一个 JavaScript 文件来练习调试。 若要使用调试器启动控件,JavaScript 文件必须位于 Visual Studio 工作区中。

此应用程序的目标是设置三种货币(USD、EUR 和 JPY)之间的汇率。 然后,我们要显示 10 EUR 对应于其他所有货币的价值,精确到小数点后两位。 对于添加的每种货币,应计算与所有其他货币的汇率。

  1. 在 Visual Studio Code 中,在 ./nodejs-debug/ 子文件夹创建名为 mycurrency.js 的文件。

  2. 将以下代码粘贴到新的文件编辑器中:

    const rates = {};
    
    function setExchangeRate(rate, sourceCurrency, targetCurrency) {
      if (rates[sourceCurrency] === undefined) {
        rates[sourceCurrency] = {};
      }
    
      if (rates[targetCurrency] === undefined) {
        rates[targetCurrency] = {};
      }
    
      rates[sourceCurrency][targetCurrency] = rate;
      rates[targetCurrency][sourceCurrency] = 1 / rate;
    }
    
    function convertToCurrency(value, sourceCurrency, targetCurrency) {
      const exchangeRate = rates[sourceCurrency][targetCurrency];
      return exchangeRate && value * exchangeRate;
    }
    
    function formatValueForDisplay(value) {
      return value.toFixed(2);
    }
    
    function printForeignValues(value, sourceCurrency) {
      console.info(`The value of ${value} ${sourceCurrency} is:`);
    
      for (const targetCurrency in rates) {
        if (targetCurrency !== sourceCurrency) {
          const convertedValue = convertToCurrency(value, sourceCurrency, targetCurrency);
          const displayValue = formatValueForDisplay(convertedValue);
          console.info(`- ${convertedValue} ${targetCurrency}`);
        }
      }
    }
    
    setExchangeRate(0.88, 'USD', 'EUR');
    setExchangeRate(107.4, 'USD', 'JPY');
    printForeignValues(10, 'EUR');
    
  3. 要保存该文件,请按“Ctrl+S”(Windows、Linux)或“Cmd+S”(Mac)

创建启动配置

我们会经常用到调试器,因此让我们为应用创建一个启动配置。

  1. 在 Visual Studio Code 中的“运行”选项卡上,选择“创建 launch.json 文件”,然后选择 Node.js 调试程序

    Visual Studio Code 会在工作区的根目录中创建 .vscode/launch.json 配置文件,并打开启动文件进行编辑。

    Screenshot of generated launch configuration.

    默认情况下,将创建启动配置以执行当前打开的文件。 在本示例中,打开的文件为 mycurrency.js。 可以修改启动配置,以自定义程序在调试时的启动方式。

  2. 在启动配置中,查看 program 属性的值。

    {
        // Use IntelliSense to learn about possible attributes.
        // Hover to view descriptions of existing attributes.
        // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
        "version": "0.2.0",
        "configurations": [
            {
                "type": "node",
                "request": "launch",
                "name": "Launch Program",
                "skipFiles": [
                    "<node_internals>/**"
                ],
                "program": "${workspaceFolder}/nodejs-debug/mycurrency.js"
            }
        ]
    }
    
    • ${workspaceFolder} 指示工作区的根。
  3. 关闭 .vscode/launch.json 文件。

注意

可以通过选择右下角的“添加配置”为项目创建不同的启动配置。

分析问题

确保 Visual Studio Code 环境已准备就绪,可监视调试过程:

  • 调试器面板应在左侧打开。 使用左侧的“运行”选项卡图标切换面板的可见性。
  • 调试控制台应在底部打开。 可通过选择“查看”>“调试控制台”或按 Ctrl+Shift+Y (Windows、Linux) 或 Cmd+Shift+Y (Mac) 来打开控制台。

现已准备就绪,接下来即可开始调试。

在调试器启动控件中,启动启用了调试的程序(绿色箭头)。

Screenshot of the Start debugging button in Visual Studio Code.

应看到程序快速完成。 这是正常的,因为尚未添加任何断点。

应在调试控制台中看到此文本,后跟一个异常。

The value of 10 EUR is:
11.363636363636365
- 11.363636363636365 USD
/app/node-101/currency.js:23
  return value.toFixed(2);
               ^
TypeError: Cannot read property 'toFixed' of undefined
    at formatValueForDisplay (/app/node-101/currency.js:23:16)
    at printForeignValues (/app/node-101/currency.js:32:28)
    at Object.<anonymous> (/app/node-101/currency.js:40:1)
    at Module._compile (internal/modules/cjs/loader.js:959:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:995:10)
    at Module.load (internal/modules/cjs/loader.js:815:32)
    at Function.Module._load (internal/modules/cjs/loader.js:727:14)
    at Function.Module.runMain (internal/modules/cjs/loader.js:1047:10)
    at internal/main/run_main_module.js:17:11

此程序所要做的是设置三种货币(USD、EUR、JPY)之间的汇率,并显示 10 EUR 对应于其他所有货币的值,精确到小数点后两位。

在这里可以看到两个 bug:

  • 小数点后不止两位。
  • 程序崩溃并出现异常,无法显示 JPY 值。

提示

  • "outputCapture": "std", 设到你的启动配置文件以增加日志记录输出。
  • 设置一个记录点而不是断点,以避免停止程序执行。 日志点不会“闯入”调试器,而是会将消息记录到控制台。 日志点对于在调试无法暂停或停止的生产服务器时的注入日志记录操作特别有用。

修复位数显示

首先,我们将解决第一个 bug。 由于你没有编写此代码,并且调用了许多不同的函数,因此让我们首先使用分布执行来了解执行流。

使用断点并逐步执行

要添加断点,请选择 printForeignValues(10, 'EUR'); 上第 39 行的左边距

Screenshot of the breakpoint location in the code.

再次开始调试,并使用“单步执行”调试控件单步执行 printForeignValues() 函数:

Screenshot of the Step into button.

检查变量状态

接下来,花一些时间检查“变量”窗格中的其他变量值。

Screenshot of the Variables pane.

  • valuesourceCurrency 变量的值是什么?
  • 对于 rates 变量,你是否看到了三个预期的项:USDEURJPY

要继续单步执行直至设置变量 convertedValue,请使用“单步跳过”调试控件。

Screenshot of the Step over button.

使用“单步跳过”控件五次之后,应会看到 convertedValue 变量的值设置为 11.363636363636365

如果再一次单步跳过,我们将看到 displayValue 变量的值。 该值应为显示的带有两位数 11.36 的格式字符串。

然后我们可以得出结论,到目前为止,函数 convertToCurrency()formatValueForDisplay() 似乎是正确的,并返回了预期的结果。

更正错误

使用“单步执行”一次可访问对 console.info() 函数的调用。 仔细检查此代码行。 看到这里的错误了吗?

我们需要通过使用 displayValue 变量而不是 convertedValue 变量以输出值来修复此程序 bug。

  1. 更新 currency.js 文件以使用正确的变量名称。 更改对第 32 行上的 console.info() 函数的调用以使用 displayValue 变量而不是 convertedValue 变量:

    console.info(`- ${displayValue} ${targetCurrency}`);
    
  2. 保存对文件所做的更改。

  3. 重启程序。

检查程序是否正确地将值 USD 显示为 11.36。 第一个 Bug - 已解决。

找出崩溃的原因

现在让我们弄清楚程序崩溃的原因。

  1. currency.js 文件中,删除在第 39 行设置的断点。

  2. 要强制程序在引发异常后暂停,请选中“断点”窗格中的“未捕获的异常”框。

  3. 在调试器中再次运行该程序。

此程序应在出现异常时暂停,并在编辑器窗口的中间显示一个大型错误报告。

Screenshot of the exception message shown in Visual Studio Code.

查看执行停止的行,并注意异常消息 TypeError: Cannot read property 'toFixed' of undefined。 通过该消息可以推断,value 参数函数的值是 undefined 而不是数字。 此错误是导致异常的原因。

倒回调用堆栈

你在错误消息下面看到的堆栈跟踪可能有点难以理解。 好消息是,Visual Studio Code 会为你处理函数调用堆栈。 默认情况下,“调用堆栈”窗格中只会显示有意义的信息。 让我们使用调用堆栈信息来查找导致此异常的代码。

我们知道,此异常是在调用 formatValueForDisplay() 函数时引发的。

  1. 在调试器面板中,转到“调用堆栈”窗格。

  2. 若要查看 formatValueForDisplay() 函数的调用位置,请双击其下方的函数,即 printForeignValues 函数。

    Visual Studio Code 转到 currency.js 文件中 printForeignValues 函数中调用 formatValueForDisplay() 函数的行:

    const displayValue = formatValueForDisplay(convertedValue);
    

仔细观察此代码行。 导致异常的参数来自于 convertedValue 变量。 需要找出该参数值在何时变为 undefined

一种选择是在此行添加一个断点,并在断点每次在该行停止时检查该变量。 但是,我们不知道何时会出现错误的值,而且在复杂的程序中,这种调试方法可能很麻烦。 让我们看看一个替代方法。

添加条件断点

在本例中,只有当 convertedValue 变量的值为 undefined 时,才能使调试器在此断点处停止。 幸运的是,Visual Studio Code 可以使用鼠标右键选项执行此操作。

  1. currency.js 文件的第 31 行的左边距中,右键单击并选择“添加条件断点”。

    Screenshot of setting a conditional breakpoint in Visual Studio Code.

  2. 右键单击后,输入以下条件以触发断点,然后按 Enter

    `convertedValue === undefined`
    
  3. 重启程序。

程序现在应在第 31 行停止,我们可以检查调用堆栈值。

观察当前状态

现在,我们花一些时间来分析当前程序的状态。

  • convertedValue 变量的值来自对 convertToCurrency(value, sourceCurrency, targetCurrency) 函数的调用。 我们需要检查此函数调用中的参数值,并确认它们是否正确。

  • 具体而言,我们需要检查 value 变量并确认它具有预期值 10

查看一下 convertToCurrency() 函数的代码。

function convertToCurrency(value, sourceCurrency, targetCurrency) {
  const exchangeRate = rates[sourceCurrency][targetCurrency];
  return exchangeRate && value * exchangeRate;
}

你知道此代码的结果为 undefined。 你还知道 value 变量已设置为 10。 此信息有助于我们了解问题一定出在 exchangeRate 变量的值上。

currency.js 文件中,将鼠标悬停在 rates 变量之上进行查看:

Screenshot of peeking at the rates variable value.

你尝试获取 EURJPY 的汇率,但如果展开 EUR 值,你会发现只有 USD 的转换率。 缺少 JPY 的转换率。

修复缺失的转换率

现在已知缺失某些转换率,让我们来了解原因。 要删除所有现有断点,请在“断点”窗格中选择“删除所有断点”图标。

Screenshot of the button to remove all breakpoints.

观察汇率变量

让我们设置一个断点来监视 rates 变量。

  1. currency.js 文件中,在 setExchangeRate(0.88, 'USD', 'EUR'); 函数的第 37 行添加一个断点。

  2. 在“监视”窗格中,选择加号,然后输入 rates

    每次更改 rates 变量的值时,更新后的值都会显示在“监视”窗格中。

  3. 重启程序。

  4. 当断点在第一次调用 setExchangeRate() 函数时停止时,使用“单步跳过”控件。

  5. 在“监视”窗格中,查看 rates 变量的值。

    此时可以看到 USDEUR 的互汇率相同,符合我们的预期。

  6. 在第二次调用 setExchangeRate() 函数时再次单步跳过。

    你会看到 USDJPY 的互汇率相等,但并未显示 EURJPY 之间的汇率。

现在来看看 setExchangeRate() 函数的代码。

function setExchangeRate(rate, sourceCurrency, targetCurrency) {
  if (rates[sourceCurrency] === undefined) {
    rates[sourceCurrency] = {};
  }

  if (rates[targetCurrency] === undefined) {
    rates[targetCurrency] = {};
  }

  rates[sourceCurrency][targetCurrency] = rate;
  rates[targetCurrency][sourceCurrency] = 1 / rate;
}

此函数中最重要的行是最后两行。 看来你找到第二个 bug 了! 兑换率仅可在 sourceCurrencytargetCurrency 变量之间进行设置。 该计划还需要计算添加的其他货币的兑换率。

修复代码

我们来修复兑换率问题的代码。

  1. 更新 currency.js 文件以计算其他货币的兑换率。

    替换第 12 行和第 13 行的代码:

    rates[sourceCurrency][targetCurrency] = rate;
    rates[targetCurrency][sourceCurrency] = 1 / rate;
    

    使用此更新后的代码:

    for (const currency in rates) {
      if (currency !== targetCurrency) {
        // Use a pivot rate for currencies that don't have the direct conversion rate
        const pivotRate = currency === sourceCurrency ? 1 : rates[currency][sourceCurrency];
        rates[currency][targetCurrency] = rate * pivotRate;
        rates[targetCurrency][currency] = 1 / (rate * pivotRate);
      }
    }
    
  2. 保存对文件所做的更改。

更新后的代码可设置 sourceCurrencytargetCurrency 以外的任何货币的兑换率。 程序使用 sourceCurrency 的兑换率来推断另一种货币与 targetCurrency 之间的汇率。 然后,代码会相应地设置其他货币的兑换率。

注意

只有当 sourceCurrency 和其他货币之间的汇率已经存在时,此修复才有效,在本例中,这是一个可以接受的限制。

测试更正

我们来测试一下更改。

  1. 删除所有断点并监视变量。

  2. 重启程序。

现在应在控制台中看到预期的结果,而不会出现任何故障。

The value of 10 EUR is:
- 11.36 USD
- 1220.45 JPY

就是这样。 代码修复了。 现在,你可以使用 Visual Studio Code 高效调试事先不了解的代码。 干得不错!

清理开发容器

完成项目后,你可能希望清理开发环境或将其返回到其典型状态。

删除 GitHub Codespaces 环境可确保可以最大程度地提高帐户获得的每核心免费小时数权利。

重要

有关 GitHub 帐户权利的详细信息,请参阅 GitHub Codespaces 每月包含的存储和核心小时数

  1. 登录到 GitHub Codespaces 仪表板 (https://github.com/codespaces)。

  2. 找到当前正在运行的、源自 MicrosoftDocs/node-essentials GitHub 存储库的 Codespaces。

    Screenshot of all the running Codespaces including their status and templates.

  3. 打开 codespace 的上下文菜单,然后选择“删除”。

    Screenshot of the context menu for a single codespace with the delete option highlighted.