错误信息必须结构化且可定位,应包含模块名、操作动作、目标对象、错误码和消息;public接口需统一错误码枚举并隔离实现细节,禁止暴露底层异常或硬编码调试信息,错误码语义须长期稳定。

错误信息必须携带可定位的上下文
只返回 std::runtime_error("file open failed") 是无效的。调用方无法知道是哪个文件、在哪个函数、哪一行出的问题。真实项目里,错误信息至少要包含:模块名(如 "db" 或 "config")、操作动作("open")、目标对象("/etc/app.conf")、底层原因("No such file or directory")。C++11 之后推荐用 std::system_error 包装 errno,它自带错误码和系统消息;若需自定义,就用结构体封装:
struct ErrorInfo {
std::string module;
std::string action;
std::string target;
int code; // 枚举类 ErrorCode
std::string message;
};
不要把所有字段都塞进一个字符串拼接里——日志系统或监控平台需要结构化字段做聚合分析。
封装接口不暴露内部异常类型
public 接口函数(比如 Database::connect())不能直接抛 std::runtime_error 或 mysql_error() 这类第三方异常。外部使用者不该依赖你用的是 MySQL 还是 SQLite。正确做法是:私有函数可以自由 throw,但 public 函数 catch 后转成统一的、你定义的错误码或包装后的 ErrorInfo:
- 用
enum class ErrorCode { DbConnectionFailed, ConfigParseError }做语义隔离 - 避免返回
std::exception_ptr或裸指针,这会把异常传播责任推给调用方 - 如果项目禁用异常,public 接口必须返回
ErrorCode或std::optional<T>,且文档明确标注哪些错误可重试、哪些应终止
错误信息里别混入调试专用字段
像 __FILE__、__LINE__、线程 ID、内存地址这类信息,对用户无意义,还可能泄露路径结构或触发安全审计告警。它们该进 debug 日志,而不是暴露给上层接口。常见错误包括:
立即学习“C++免费学习笔记(深入)”;
- 在
getErrorMessage()返回值里硬编码__LINE__字符串 - 把
std::this_thread::get_id()当作错误唯一标识,结果多线程下不可复现 - 把
std::chrono::steady_clock::now().time_since_epoch().count()当错误时间戳,却没考虑时区或 NTP 同步问题
真正该保留的时间信息,是 UTC 时间戳(秒级或毫秒级整数),且由统一日志模块注入,而非每个错误构造时自己取。
错误码和消息必须分离且可翻译
错误消息字符串不是接口契约的一部分。今天写 "Config file not found",明天改成 "Missing config file" 就算语义没变,也会让自动化测试断掉。所以:
- public 接口只返回
ErrorCode枚举值,不返回原始字符串 - 提供独立的
toString(ErrorCode)函数,且该函数支持 locale 切换(比如传入std::locale("zh_CN")) - 若用字符串消息,必须走资源文件或 JSON 配置加载,禁止硬编码在头文件里
最容易被忽略的一点:错误码的语义稳定性比实现更重要。一旦 ErrorCode::FileNotFound 被下游模块用于分支判断,你就不能再把它重命名为 ErrorCode::ResourceNotFound,哪怕逻辑更准确——那不是优化,是破坏 ABI 兼容性。


















