某天深夜,我在实习项目里调 bug,日志里全是「1」「2」「3」这样的错误码。
我盯着屏幕发呆,心里只有一个问号:
“这谁能看懂?到底哪里出问题了?”
后来我开始琢磨,为什么不把错误码设计得更有结构?
于是就有了这篇小结。
它不是架构师级别的复杂方案,而是一个实习生也能落地的速通版思路。
如果你也在写 demo、实习项目,或者以后要进大厂写代码,希望这篇文章能帮到你。
1. 为什么要统一错误码?
- 能定位:看到一个数就能知道是哪个级别、哪个域、哪个模块。
- 能扩展:后续加模块、加错误不会打架。
- 能统计:日志可以按域/模块聚合分析。
一句话:错误码如果有结构,就不用靠注释和记忆去猜。
2. 32 位错误码布局
我最后选了一个 32 位的结构,分成四段:
- [31:28] Level(级别,4 bit)
- [27:20] Group(业务域,8 bit)
- [19:12] Module(模块,8 bit)
- [11:0] Code(模块内错误,12 bit)
记忆方式很简单:4 / 8 / 8 / 12 = 32。十六进制打印时就是 0xLLLLGGMMCCCC 的形式。
编辑
3. 命名和编号的基本规则
- 用 enum class:避免和别的变量冲突。
- 用固定宽度类型:比如
uint8_t/uint16_t/uint32_t,保证跨平台一致。 - 按段分配 Code:一个模块的错误码不要挤在一起,要预留空间,废弃的编号只标记,不复用。
示例分段:
- 0x001~0x0FF:初始化相关
- 0x100~0x1FF:运行时状态
- 0x200~0x2FF:资源/IO
4. 一个极简示例
#include <cstdint>
constexpr uint32_t LV_SHIFT=28, GR_SHIFT=20, MD_SHIFT=12;
constexpr uint32_t LV_MASK=0xF, GR_MASK=0xFF, MD_MASK=0xFF, CD_MASK=0xFFF;
constexpr uint32_t make_error(uint32_t lv,uint32_t gr,uint32_t md,uint32_t cd){
return ((lv&LV_MASK)<<LV_SHIFT)|((gr&GR_MASK)<<GR_SHIFT)|((md&MD_MASK)<<MD_SHIFT)|(cd&CD_MASK);
}
constexpr uint32_t get_lv(uint32_t e){return (e>>LV_SHIFT)&LV_MASK;}
constexpr uint32_t get_gr(uint32_t e){return (e>>GR_SHIFT)&GR_MASK;}
constexpr uint32_t get_md(uint32_t e){return (e>>MD_SHIFT)&MD_MASK;}
constexpr uint32_t get_cd(uint32_t e){return e&CD_MASK;}
再结合一个枚举:
enum class Level : uint8_t { Info=1, Warn=2, Error=3, Fatal=4 };
enum class Group : uint8_t { Core=1 };
enum class Module : uint8_t { Media=1 };
enum class MediaErr : uint16_t { Init=0x001, Prepared=0x004 };
uint32_t ec = make_error((uint32_t)Level::Error, (uint32_t)Group::Core,
(uint32_t)Module::Media, (uint32_t)MediaErr::Init);
这样一来,ec 这个数在日志里就能拆开看:是 Error 级别,Core 组,Media 模块,错误码 0x001。
5. 落地的时候可以怎么做
- 建一个
ERRORS.md文档,记录编号、含义、触发条件、修复建议。 - 每个模块的错误码按号段分配,新增要走流程,废弃不复用。
- 打印日志时统一格式,比如
0xLLLLGGMMCCCC,并同时输出拆解后的四段值。
6. 常见坑
- 直接用
enum,结果和变量重名冲突。 - 用
int,不同平台位数不一样。 - 错误码随便写,最后统计、检索全乱套。
7. 给读者的建议
如果你还在学校或者实习阶段,其实没必要一开始就上很重的架构。先搞懂这个思路:把错误码拆成几段,编号规范化,哪怕只是 Excel 里先记录,也比随便写强得多。后续再加工具函数/枚举,就能逐步走向完整方案。
更多实习笔记在公众号 RushSpace