在上一课(3.4 — 基本调试策略)中,我们开始探索如何手动调试问题。在那节课中,我们对使用语句打印调试文本提出了一些批评:
- 调试语句会使代码变得杂乱无章
- 调试语句会使程序的输出变得杂乱无章
- 添加和移除调试语句需要修改代码,这可能会引入新的错误
- 调试语句在使用完毕后必须被移除,这使得它们不可重复使用
我们可以缓解其中的一些问题。在本课中,我们将探讨一些基本技术来实现这一点。
调试策略 #1:条件化调试代码
考虑以下包含一些调试语句的程序:
#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 << '\n';
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 << '\n';
return 0;
}
提示:在多文件程序中,
#define ENABLE_DEBUG
应该放在一个共享的头文件中,这样可以在一个位置统一控制所有文件的调试输出。
调试策略 #2:使用日志记录器
日志记录的优势
日志记录提供了以下优点:
- 调试信息与正常输出分离
- 日志文件可以轻松分享给其他开发者
- 包含时间戳和其他有用的元数据
- 可以长期保存以供后续分析
使用 plog 日志记录器示例
#include <plog/Log.h> // 第 1 步:包含日志记录器头文件
#include <plog/Initializers/RollingFileInitializer.h>
#include <iostream>
int getUserInput()
{
PLOGD << "getUserInput() called"; // PLOGD 由 plog 库定义
std::cout << "Enter a number: ";
int x{};
std::cin >> x;
return x;
}
int main()
{
plog::init(plog::debug, "Logfile.txt"); // 第 2 步:初始化日志记录器
PLOGD << "main() called"; // 第 3 步:像写入控制台一样输出到日志
int x{ getUserInput() };
std::cout << "You entered: " << x << '\n';
return 0;
}
日志输出示例
2018-12-26 20:03:33.295 DEBUG [4752] [main@19] main() called
2018-12-26 20:03:33.296 DEBUG [4752] [getUserInput@7] getUserInput() called
安装和配置 plog
要使用 plog,请按以下步骤操作:
- 从 plog 仓库下载最新版本
- 解压文件到本地目录
- 在 IDE 中设置包含目录:
somewhere\plog-master\include\
提示:对于性能敏感的项目,建议考虑使用更快、功能更丰富的日志记录器,如 spdlog。
禁用日志输出
要临时禁用日志记录,只需修改初始化语句:
plog::init(plog::none, "Logfile.txt"); // 使用 plog::none 级别来禁用大多数日志输出