背景
公司代码规范新加一条:“核心库禁用异常。”
于是我把原本四处 throw 的日志库翻出来,决定用 std::error_code 做一次“无异常化”翻新。过程顺手记下,权当备忘。
选型思路
异常一旦禁用,能选的只剩三样:
- 返回
bool——信息太少; - 输出
int errno——命名空间污染; std::error_code——值语义、可扩展、与标准库同频。
结论:直接上积木。
旧接口
// 旧日子:抛就完事
void rotate_log(const std::string& path);
// 调用方
try {rotate_log("app.log");
} catch (const LogException& e) {std::cerr << e.what() << '\n';
}
新接口
// 新日子:把错误码塞回来
std::error_code rotate_log(const std::string& path) noexcept;
// 调用方
if (auto ec = rotate_log("app.log"); ec) {std::cerr << ec.message() << '\n';
}
错误值设计
先列可能出错的情节:
- 文件根本不存在 →
not_found - 权限不足 →
permission_denied - 磁盘写满 →
no_space - 成功 →
ok(必须是 0)
用 enum class 一次性写死:
enum class LogErr {ok = 0,not_found,permission_denied,no_space
};
错误类别
class LogCategory : public std::error_category {
public:const char* name() const noexcept override { return "log"; }std::string message(int ev) const override {switch (static_cast<LogErr>(ev)) {case LogErr::ok: return "success";case LogErr::not_found: return "log file not found";case LogErr::permission_denied:return "permission denied";case LogErr::no_space: return "disk full";default: return "unknown log error";}}
};inline const std::error_category& log_category() {static LogCategory c;return c;
}
让枚举自动变身
namespace std {
template<> struct is_error_code_enum<LogErr> : true_type {};
}inline std::error_code make_error_code(LogErr e) {return {static_cast<int>(e), log_category()};
}
实现函数
std::error_code rotate_log(const std::string& path) noexcept {if (!fs::exists(path)) return LogErr::not_found;fs::space_info si = fs::space(path);if (si.available < 1_MiB) return LogErr::no_space;std::error_code ec;fs::rename(path, path + "." + timestamp(), ec);return ec ? LogErr::permission_denied : LogErr::ok;
}
注意:内部依旧用 fs:: 的无异常重载,把系统级错误转成自家枚举,保持对外统一语言。
调用侧
for (auto& path : log_files) {if (auto ec = rotate_log(path); ec) {report_to_monitoring("rotate failed", ec);continue; // 单文件失败不阻断批次}
}
没有 try/catch,代码路径平坦;监控端收到的 ec.value() 可直接映射到告警级别。
单元测试
TEST(rotate_log, not_found) {auto ec = rotate_log("missing.log");EXPECT_EQ(ec, LogErr::not_found);EXPECT_TRUE(ec);EXPECT_EQ(ec.message(), "log file not found");
}
用枚举值直接比较,测试用例一眼能读。
经验小结
- 先列“失败场景”再列“枚举值”,保证不遗漏、不重复。
- 一定把
ok钉死在 0,否则if (ec)就全乱。 - 内部若调用其他
error_code接口,就地转换,别让系统错误泄漏到上层。 - 消息串只给人看,逻辑判断永远用枚举或布尔。
结语
把异常关进抽屉后,std::error_code 成了最顺手的那块积木:轻量、可拷贝、能跨线程、还能与标准库拼成同一套“语言”。一次重构,日志库体积没涨,性能没跌,代码审查却少了一堆 “catch (...) 兜底” 的争吵——也算意外收获。