原子化接口的封装与异常处理

功能函数 = 数据结构 + 算法 + 库提供的接口,这是组件的基本形态。当模块被更多地方调用时,库提供的原始接口需要被封装成原子化接口。原子化接口本身只做两件事:封装库提供的接口和异常处理。它不包含数据结构,也不包含算法——它是独立的封装层。


一、为什么需要原子化接口

直接调用库提供的原始接口有两个问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 问题 1:没有异常处理
int fd = open(path, O_RDONLY);
// 如果 open 失败返回 -1,代码继续执行
// 后续的 mmap(fd, ...) 会用 -1 作为 fd → 崩溃

// 问题 2:一个功能散落在多处
void do_task() {
int fd = open(path, O_RDONLY); // 到处都是 open
// ...
close(fd);
}
void do_other() {
int fd = open(path, O_RDONLY); // 又是 open
// ...
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
// 原子化接口:封装 open + 异常处理
int safe_open(const char* path) {
int fd = open(path, O_RDONLY); // 封装库接口
if (fd < 0) throw FileOpenError(path); // 异常处理
return fd;
}

// 原子化接口:封装 mmap + 异常处理
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;
}

// 原子化接口:封装 close + 异常处理
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
原子化接口 ←→ 异常处理流(七条流)
原子化接口是异常处理流在模块级的实现方式

原子化接口 ←→ 接口形态演化(接口)
原子化接口是函数导出形态的进化版(加了封装和异常处理)

原子化接口 ←→ 依赖接口(依赖关系主题)
功能函数依赖原子化接口而非直接依赖库接口

原子化接口 ←→ 从指令到系统主线第十二章
主线讲了组件的基本形态(数据+算法+库接口)
本线讲了组件的进化形态(数据+算法+原子化接口)

原子化接口是组件从「能用」到「可靠」的关键一步——它不改变功能函数做什么,只改变功能函数怎么调用底层。