原子化接口的封装与异常处理
功能函数 = 数据结构 + 算法 + 库提供的接口,这是组件的基本形态。当模块被更多地方调用时,库提供的原始接口需要被封装成原子化接口。原子化接口本身只做两件事:封装库提供的接口和异常处理。它不包含数据结构,也不包含算法——它是独立的封装层。
一、为什么需要原子化接口
直接调用库提供的原始接口有两个问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| int fd = open(path, O_RDONLY);
void do_task() { int fd = open(path, O_RDONLY); close(fd); } void do_other() { int fd = open(path, O_RDONLY); close(fd); }
|
原子化接口解决这两个问题:把库的接口包一层,加上异常处理。
二、原子化接口只做两件事
1 2 3 4 5 6 7 8
| 原子化接口的职责: 1. 封装库提供的接口(一个库接口 → 一个原子化接口) 2. 异常处理(失败时抛异常或返回错误码)
原子化接口不做的: ✗ 不定义数据结构(那是功能函数的事) ✗ 不实现算法(那是功能函数的事) ✗ 不做业务逻辑(那是功能函数的事)
|
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| int safe_open(const char* path) { int fd = open(path, O_RDONLY); if (fd < 0) throw FileOpenError(path); return fd; }
void* safe_mmap(int fd, size_t size) { void* p = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); if (p == MAP_FAILED) throw MapError(); return p; }
void safe_close(int fd) { if (close(fd) < 0) throw CloseError(); }
|
每个原子化接口的结构完全相同:
1 2 3 4 5 6
| 调用库的原始接口 ↓ 检查返回值 ↓ 失败 → 异常处理(抛异常 / 返回错误码 / 记录日志) 成功 → 返回结果
|
三、原子化接口的命名约定
1 2 3 4 5 6 7
| 库接口 原子化接口 区别 ────────────────────────────────────── open() safe_open() 加了异常处理 mmap() safe_mmap() 加了异常处理 close() safe_close() 加了异常处理 read() safe_read() 加了异常处理 write() safe_write() 加了异常处理
|
命名规则:safe_ 前缀 = 封装了异常处理的版本。
四、异常处理的三种策略
1 2 3 4 5 6 7 8 9 10 11
| 策略 1:抛异常 → 失败时 throw,调用者用 try-catch 捕获 → 适合:C++ 项目,异常是正常的错误处理机制
策略 2:返回错误码 → 失败时返回错误码,调用者检查返回值 → 适合:C 项目,或性能敏感不想用异常的场景
策略 3:记录日志 + 继续 → 失败时记录日志,但不中断执行 → 适合:非关键操作(如日志写入失败不应该让整个程序崩溃)
|
选择依据:
1 2
| 关键操作(文件读写、内存分配)→ 抛异常 或 返回错误码 非关键操作(日志、统计)→ 记录日志 + 继续
|
五、功能函数如何使用原子化接口
功能函数组合数据结构 + 算法 + 原子化接口:
1 2 3 4 5 6 7 8 9 10 11
| ScanResult scan_memory(const char* path, const uint8_t* pattern, size_t len) { int fd = safe_open(path); void* mem = safe_mmap(fd, size); MemoryBlock block = { (uint8_t*)mem, size }; ScanResult r = search_pattern(&block, pattern, len); safe_close(fd); return r; }
|
1 2 3 4 5 6 7 8 9
| 功能函数内部的层次:
功能函数(组合层) ├── 数据结构: MemoryBlock ← 功能函数自己的 ├── 算法: search_pattern ← 功能函数自己的 └── 原子化接口: safe_open/safe_mmap/safe_close ← 封装层 ├── 封装 open() ← 库提供的 ├── 封装 mmap() ← 库提供的 └── 异常处理 ← 原子化接口的
|
功能函数不直接调用库的原始接口,而是调用原子化接口。 异常处理被收敛在原子化接口层,功能函数不需要每个调用点都写 if (fd < 0)。
六、两步演化的总结
1 2 3 4 5 6 7 8 9 10 11 12
| 第一步:功能函数 = 数据结构 + 算法 + 库提供的接口 → 直接调用库接口,没有封装,没有异常处理 → 适合:原型阶段、简单工具、一次性脚本
第二步:功能函数 = 数据结构 + 算法 + 原子化接口 → 通过原子化接口调用库,有封装,有异常处理 → 适合:被多处调用的模块、需要可靠性的系统
原子化接口 = 独立的封装层 → 只做封装 + 异常处理 → 不包含数据结构和算法 → 功能函数在它之上组合数据结构和算法
|
七、和其他线的关系
1 2 3 4 5 6 7 8 9 10 11 12
| 原子化接口 ←→ 异常处理流(七条流) 原子化接口是异常处理流在模块级的实现方式
原子化接口 ←→ 接口形态演化(接口) 原子化接口是函数导出形态的进化版(加了封装和异常处理)
原子化接口 ←→ 依赖接口(依赖关系主题) 功能函数依赖原子化接口而非直接依赖库接口
原子化接口 ←→ 从指令到系统主线第十二章 主线讲了组件的基本形态(数据+算法+库接口) 本线讲了组件的进化形态(数据+算法+原子化接口)
|
原子化接口是组件从「能用」到「可靠」的关键一步——它不改变功能函数做什么,只改变功能函数怎么调用底层。