系统架构设计:顶层架构设计、模块划分原则、接口定义规范、数据流与控制流设计、架构文档撰写
好,咱们进入第二章。系统架构设计。
说实话,很多做算法的同学,一听到「架构」两个字就头大。觉得那是软件工程师的事。我当年也这么想。直到有一次,我花了三个月写好的算法模型,到了集成阶段发现根本跑不通——不是因为算法不对,而是因为数据流设计有缺陷,模块之间互相打架。
那次之后我学乖了。架构设计,其实是算法项目能落地的骨架。骨架歪了,肉长得再好也没用。
顶层架构设计:先画一张大图
顶层架构设计,说白了就是回答三个问题:
- 输入从哪来? 传感器?数据库?网络流?
- 中间怎么处理? 预处理、特征提取、推理、后处理。
- 输出到哪去? 控制指令?可视化界面?存储系统?
我个人习惯,先用一张方框图画清楚。别急着画细节,先定边界。
顶层架构的核心原则:
- 高内聚,低耦合 —— 每个模块只干一件事,模块之间少说话。
- 单向依赖 —— 数据流尽量是单向的,别搞成循环依赖,否则调试起来你会想哭。
- 可替换性 —— 任何一个模块,都应该能被另一个实现替换,而不影响全局。
举个例子。我之前做过一个视觉检测项目。顶层架构就三层:数据采集层、算法处理层、结果输出层。每一层只和相邻层通信。后来客户要求换摄像头,我只改了采集层,算法层一行代码没动。这就是顶层设计的好处。
模块划分原则:别把鸡蛋放在一个篮子里
模块怎么切?我总结了四个字:职责单一。
你想想看,一个模块如果既要做图像预处理,又要做目标检测,还要做结果统计。那这个模块一旦出问题,你根本不知道是哪个环节挂了。
我建议按功能域来划分:
| 模块名称 | 职责 | 典型接口 |
|---|---|---|
| 数据采集模块 | 从硬件或文件读取原始数据 | read_frame() |
| 预处理模块 | 去噪、归一化、尺寸调整 | preprocess(raw_data) |
| 推理引擎模块 | 加载模型,执行前向推理 | infer(features) |
| 后处理模块 | 解析输出,生成业务结果 | post_process(raw_output) |
| 结果分发模块 | 将结果发送到下游系统 | dispatch(result) |
小技巧: 模块划分时,可以先用「动词+名词」的方式命名。比如「读取数据」「执行推理」「发送结果」。这样每个模块的职责一目了然。
接口定义规范:定好规矩,少吵架
接口定义,是架构设计里最容易扯皮的地方。我见过两个团队因为接口参数顺序吵了一下午。
所以,接口规范要提前定死。我个人习惯用以下几条:
- 接口参数尽量少 —— 超过5个参数,就该考虑封装成结构体了。
- 返回值统一 —— 要么全部返回错误码,要么全部抛异常。别混着来。
- 版本号管理 —— 接口一旦发布,不要轻易改。非要改,就升版本。
举个例子,一个标准的推理接口,我通常会这样定义:
// 输入结构体
typedef struct {
uint8_t* data; // 图像数据
int width; // 图像宽度
int height; // 图像高度
int channel; // 通道数
} InputFrame;
// 输出结构体
typedef struct {
int obj_count; // 检测到的目标数量
Rect* bboxes; // 目标框列表
float* scores; // 置信度列表
} DetectionResult;
// 接口函数
int infer_object(InputFrame* input, DetectionResult* output);
你看,输入输出都定义成结构体。这样以后加字段,接口签名不用变。我在项目中吃过亏,一开始接口参数写了一大串,后来加了个「是否启用加速」的开关,所有调用方都得改。从那以后,我再也不用裸参数了。
数据流与控制流设计:谁在干活,谁在指挥
这两个概念容易混淆。我简单解释一下:
- 数据流:数据怎么从一个模块流到下一个模块。比如图像从采集模块到预处理模块,再到推理模块。
- 控制流:谁来决定什么时候做什么事。比如「当采集到一帧图像后,启动预处理」。
我建议把控制流和数据流分开设计。数据流用管道(Pipeline)模式,控制流用状态机或事件驱动。
数据流设计要点:
- 每个模块只处理自己该处理的数据,不要越界。
- 数据传递尽量用共享内存或消息队列,减少拷贝。
- 关键路径上要加缓冲,防止数据抖动。
控制流设计,我习惯用状态机。比如一个典型的算法流水线,状态可能是:
IDLE -> LOADING -> PREPROCESSING -> INFERRING -> POST_PROCESSING -> OUTPUT -> IDLE
每个状态对应一个模块。状态切换由事件触发。这样设计的好处是,你一眼就能看出系统当前在干什么,出问题也好定位。
注意: 控制流里最容易出问题的,是异常处理。比如推理模块超时了怎么办?数据采集失败了怎么办?这些分支一定要在设计阶段就想清楚,别等到上线了再补。
架构文档撰写:写给人看的,不是写给机器看的
架构文档,很多人写成了流水账。我见过一份文档,把每个函数都列了一遍,但看完还是不知道系统怎么工作的。
我建议架构文档包含以下内容:
- 架构图 —— 一张顶层方框图,标注模块和接口。
- 模块说明 —— 每个模块的职责、输入、输出、依赖。
- 数据流描述 —— 用文字或流程图说明数据怎么走。
- 控制流描述 —— 状态机或事件驱动说明。
- 接口定义 —— 关键接口的签名和说明。
- 部署说明 —— 硬件要求、依赖库、启动方式。
我曾经带过一个新人,他看完我的架构文档后,三天就把整个系统搭起来了。这就是好文档的价值。
写文档的小习惯: 我每次写完代码,都会花半小时更新架构文档。别等到项目结束了再写,那时候你早忘了当初为什么这么设计。
嗯,架构设计这部分,内容其实挺多的。但核心就一句话:先想清楚再动手。你花在架构设计上的每一分钟,都会在后期开发中十倍地还给你。
下一章,咱们聊聊详细设计。到时候我会拿一个具体的算法模块,手把手带你走一遍设计流程。