Skip to content

Latest commit

 

History

History
142 lines (105 loc) · 5.81 KB

3.5 更多调试策略.md

File metadata and controls

142 lines (105 loc) · 5.81 KB

更多调试策略

在上一节中(3.4 - 基础调试策略)。我么开始探索如何手动进行调试。在那一节中,我们对使用语句来打印调试文本提出了一些批评意见。

  1. 调试语句让你的代码很混乱
  2. 调试语句让代码的输出变得很混乱
  3. 调试语句必须在调试完成之后删除,这使得它们无法被复用
  4. 调试语句需要你对你的代码进行修改,需要添加,也需要删除,这可能会带来新的错误。

我们可以缓解其中的一些问题。在本节中,我们将探索一些基础的技术手段来做到这一点。

调试代码的条件化

考虑以下包含一些调试语句的程序

#include <iostream>

int getUserInput()
{
  std::cerr << "getUserInput() called\n";
  std::cout << "Enter a number: ";
  int x{};
  std::cin >> x;
  return x;
}

int main()
{
  std::cerr << "main() called\n";
  int x{ getUserInput() };
  std::cout << "You entered: " << x;
  return 0;
}

当你完成调试语句后,你需要把它们删除,或者注释掉。然后,如果你还想要它们,你就需要把它们加回来,或者取消注释。

在整个程序中更容易禁用和启用调试的一种方法是使用预处理器指令使你的调试语句成为有条件的。

#include <iostream>

#define ENABLE_DEBUG  // 

int getUserInput()
{
  #ifdef ENABLE_DEBUG
  std::cerr << "getUserInput() called\n";
  #endif
  
  std::cout << "Enter a number: ";
  int x{};
  std::cin >> x;
  return x;
}

int main()
{
  #ifdef ENABLE_DEBUG
  std::cerr << "main() called\n";
  #endif
  
  int x{ getUserInput() };
  std::cout << "You entered: " << x;
  return 0;
}

现在,我们可以通过注释/取消注释#define ENABLE_DEBUG来启用调试。这让我们可以重复使用之前添加的调试语句,当我们调试完成之后,只需要禁用它们就可以了,而不需要将它们从代码中删除。如果这是一个多文件程序,#define ENABLE_DEBUG会被放到一个头文件中,包含在所有的代码文件中,这样我们就可以在一个位置注释/取消注释 #define,并让它传播到所有的代码文件。

这解决了必须删除调试语句的问题,但代价是代码更加混乱了。这种方法的另外一个缺点是,如果你写错了字(比如打错了DEBUG),或者忘记将头文件包含到代码文件中,该文件的部分或者全部调试可能无法启用。所以,虽然比上一个版本要好,但仍然有改进空间。

使用logger

通过预处理器进行条件化的另一种方法是将调试信息发送到日志文件中。日志文件是一个记录软件中发生事件的文件(通常存储在磁盘上)。将信息写入日志文件的过程称为日志记录。大多数应用程序和操作系统都会编写日志文件,可以用来帮助诊断发生的问题。

日志文件有几个优点。因为写入日志文件的信息与程序的输出是分开的,所以你可以避免正常输出和调试输出混在一起造成混乱。日志文件也可以很容易的发送给其他人进行诊断 -- 所以如果有人使用你的软件出现了问题,你可以要求他们把日志文件发给你,这可能会帮助你找到问题的线索。

虽然你可以编写自己的代码来创建日志文件并写入日志,但你最好使用现有的许多第三方日志工具之一。使用哪一个由你决定。

为了说明问题,我们将展示使用plog 输出到日志的情况。 plog是以一组头文件的形式实现的,所以它很容易包含在你需要的任何地方,而且它很轻巧,易于使用。

#include <iostream>
#include <plog/Log.h>  // 步骤1: include头文件

int getUserInput()
{
  LOGD << "getUserInput() called";  // LOGD由plog库的定义
  
  std::cout << "Enter a number: ";
  int x{};
  std::cin >> x;
  return x;
}

int main()
{
  plog::init(plog::debug, "Logfile.txt");   // 步骤2: 初始化库
  LOGD << "main() called";   // 步骤3: 输出到日志中,就像写入控制台一样
  
  int x{ getUserInput() };
  std::cout << "You entered: " << x;
  return 0;
}

这是上述日志的输出(在Logfile.txt中)

2018-12-26 20:03:33.295 DEBUG [4752] [main@14] main() called
2018-12-26 20:03:33.296 DEBUG [4752] [getUserInput@4] getUserInput() called

如何include、初始化、使用日志库将根据你使用的日志库有所不同。

请注意:使用此方法不需要条件编译指令,因为大多数记录器都有一个减少/消除向日志写入的方法。这使得代码更容易阅读,因为条件编译会增加很多乱七八糟的内容。在使用plog的时候,可以通过将init语句修改为以下内容来暂时禁止日志记录。

plog::init(plog::none, "Logfile.txt")   // plog::none消除了大多数消息的写入,本质上是关闭日志

我们在以后的课程中不会再使用plog,所以你可以先不用关心。

旁白

如果你想自己编译上面的例子,或者在你自己的项目中使用plog,你可以按照这些说明来安装它。

首先,获取最新的plog版本

  • 访问plog 主页
  • 点击release选项
  • 下载最新版本的源代码

接下来,将整个档案解压到硬盘的任何地方

最后,对于每个项目,将<anywhere>\plog-<version>\include\ 目录添加为IDE的include目录。Visual Studio请查看 . Code::Blocks请查看

(译注),吐槽,C++连个统一的包管理都没有,即使CMake已经如此流行,但是在这个教程里面还是不被涵盖,最终的包管理居然是此种手动下载和设置include的方案。