一、接口设计速查表(核心规则)
一个"工业级"的接口,通常要同时满足这 6 点:
- 语义清晰:一眼能看懂函数做什么
- 边界明确:输入、输出、失败条件清楚
- 易于调用:调用者不容易用错
- 易于维护:后续加功能不容易破坏旧逻辑
- 便于文档化:注释可自动生成接口文档
- 便于排错:失败原因、约束条件、数据单位明确
原则
- 函数职责越单一,越容易测试和复用
- 名字应该准确反映行为
| 维度 | 推荐做法 | 不推荐做法 | 说明 |
|---|---|---|---|
| 函数职责 | 一个函数只做一件事 | 一个函数做多步逻辑 | 提高可维护性 |
| 函数命名 | 动词 + 对象(readXxx / calcXxx) |
模糊命名(process / handle) |
语义清晰 |
| 输入参数 | const T& |
T*(无必要) |
避免拷贝 + 防误用 |
| 输出参数 | T& 或返回结构体 |
多个 T* |
提高可读性 |
| 返回值 | bool / enum / struct |
不明确的 int |
表达状态 |
| 多参数 | 使用 struct 封装 | 参数列表过长 | 防止顺序错误 |
| 布尔参数 | enum / 配置 struct | 多个 bool |
可读性差 |
| 所有权 | unique_ptr / shared_ptr |
裸指针返回 | 防内存问题 |
| 参数顺序 | 输入 → 配置 → 输出 | 混乱顺序 | 调用更直观 |
| 接口风格 | const 函数尽量加 const |
忽略 const | 明确不修改 |
1)避免不必要的裸指针
优先顺序
- 只读输入:
const T& - 可修改输入/输出:
T& - 可空、可选对象:
const T*或T* - 资源所有权转移:智能指针
**建议:**如果参数理论上"不应该为空",不要用裸指针。
例如:
cpp
bool process(const Image& img); // 更好
bool process(const Image* img); // 除非允许 nullptr
2)明确"所有权"和"生命周期"
如果接口返回指针,一定要明确:
- 谁负责释放
- 返回对象能活多久
- 是否共享
不推荐:
cpp
Image* createImage();
调用者会问:
- 要不要
delete? - 什么时候释放?
- 是否会泄漏?
推荐:
cpp
std::unique_ptr<Image> createImage();
std::shared_ptr<Image> loadSharedImage(const std::string& path);
二、参数设计速查表
参数设计常见顺序:1、核心输入;2、配置参数;3、输出参数;4、可选参数
| 类型 | 推荐写法 | 含义 |
|---|---|---|
| 只读输入 | const T& |
不修改,避免拷贝 |
| 输出参数 | T& |
明确输出 |
| 可选参数 | const T* |
允许 nullptr |
| 大对象 | const std::vector<T>& |
避免拷贝 |
| 简单类型 | int / double |
直接传值 |
| 所有权转移 | std::unique_ptr<T> |
明确 ownership |
三、返回值设计速查表
| 场景 | 推荐方式 | 示例 |
|---|---|---|
| 单一结果 | 直接返回 | int getWidth() |
| 成功/失败 | bool + 输出参数 |
bool calc(...) |
| 多结果 | struct | ShiftResult |
| 多错误类型 | enum | Status |
| 严重错误 | exception | throw |
1)输入参数与输出参数要分清
推荐优先级通常是:
第一优先:直接返回结果
适合"只有一个主要结果"的函数。
cpp
std::string readFileText(const std::string& path);
第二优先:返回状态,输出参数带结果
适合"可能失败 + 有多个输出"的函数。
cpp
bool calcShift(const cv::Mat& a, const cv::Mat& b, double& dx, double& dy);
第三优先:返回结构体
适合"结果项较多"的函数。
cpp
struct ShiftResult {
bool success = false;
double dx = 0.0;
double dy = 0.0;
double score = 0.0;
};
ShiftResult calcShift(const cv::Mat& a, const cv::Mat& b);
工业实践里,返回结构体 往往比多个输出参数更清晰。
2)布尔参数要慎用
不推荐:
cpp
processImage(img, true, false, true);
根本看不出每个 true/false 什么意思。
推荐方案 1:枚举
cpp
enum class Type {
AAA,
BBB,
CCC
};
推荐方案 2:配置结构体
cpp
struct ProcessOptions {
bool enableFilter = true;
bool enableDebug = false;
bool useabc = false;
};
3)多个相关参数用结构体封装
不推荐:
cpp
bool createROI(int x, int y, int w, int h, int searchRange, double threshold);
推荐:
cpp
struct Param {
int x = 0;
int y = 0;
int width = 0;
int height = 0;
int searchRange = 0;
double threshold = 0.0;
};
bool createROI(const Param& roi);
优点:
- 参数语义集中
- 易扩展
- 不容易传错顺序
4)错误处理方式要统一
常见有三类:
方式 A:返回 bool
适合简单成功/失败
cpp
bool parseConfig(const std::string& path);
方式 B:返回错误码
适合要区分失败原因
cpp
enum class Status {
Ok,
FileNotFound,
InvalidFormat,
OutOfRange
};
Status parseConfig(const std::string& path);
方式 C:异常
适合库式设计或严重错误
cpp
Recipe loadRecipe(const std::string& path); // 失败抛异常
四、命名规范速查表
1)函数命名
| 类型 | 示例 |
|---|---|
| 读取 | readXXX |
| 加载 | loadXXX |
| 解析 | parseXXX |
| 计算 | calcXXX |
| 获取 | getXXX |
| 设置 | setXXX |
| 创建 | creatXXX |
| 更新 | updateXXX |
| 保存 | saveXXX |
| 判断 | isValid / hasData |
2)布尔命名
| 推荐 | 不推荐 |
|---|---|
isXXX |
flag |
hasXXX |
state |
canXXX |
enable |
ShouldXXX |
五、Doxygen 注释标签速查表
| 标签 | 作用 | 示例 |
|---|---|---|
@brief |
一句话描述 | @brief Calculate shift |
@param |
参数说明 | @param img input image |
@return |
返回值 | @return true if success |
@retval |
分情况返回 | @retval true success 适合枚举或布尔返回值 |
@note |
补充说明 | @note image must be grayscale |
@warning |
风险提示 | @warning unstable for noise |
@pre |
前置条件 | @pre img not empty |
@post |
后置条件 | @post result updated |
@see |
相关接口 | @see calcShift |
@todo |
待做 | @todo optimize speed |
@author |
作者 | @author yanlairuci |
@date |
日期 | @date 20260402 |
Doxygen 工具可以解析这些标签,自动生成:
- HTML 文档
- PDF 文档
- 类图 / 调用关系图
六、Doxygen 注释结构模板
接口声明要暴露"必须知道的事"
至少说明:
- 这个函数做什么
- 参数含义
- 参数单位
- 输出含义
- 返回值含义
- 失败条件
- 特殊约束
标准模板(推荐)
cpp
/**
* @brief 简要描述(1句话)
*
* 可选:详细说明(算法/行为)
*
* @param xxx 参数说明(单位 / 范围 / 输入输出)
* @param yyy 参数说明
* @return 返回值说明
*
* @pre 前置条件
* @note 补充说明
* @warning 注意事项
*/
七、@param 写法规范
参数注释建议包含这几类信息:
- 参数是什么
- 输入还是输出
- 数据类型要求
- 单位
- 取值范围
- 方向含义
| 要素 | 示例 |
|---|---|
| 输入/输出 | Input image / Output shift |
| 单位 | pixels |
| 范围 | [0,1] |
| 方向 | positive means right |
| 约束 | must be non-empty |
示例(推荐写法)
cpp
@param colShift Output horizontal shift in pixels. Positive means right.
八、@return 写法规范
如果返回 bool,不要只写 "true or false"。
如果返回容器,也要说明 key/value 语义。
| 类型 | 推荐写法 |
|---|---|
| bool | true if success, false otherwise |
| map | key = id, value = info |
| struct | contains shift and score |
示例
cpp
@return A map where key is polygon ID and value is shape info.
九、常见错误速查
| 错误 | 说明 |
|---|---|
process() |
名字太泛 |
flag |
无语义 |
| 多个 bool | 可读性差 |
| 参数过多 | 易错 |
| 注释重复代码 | 没价值 |
| 返回 int 表示错误 | 不直观 |
十、最佳实践总结(最重要)
👉 接口设计 3 原则:
- 签名自解释(最重要)
- 参数语义清晰
- 错误行为明确
👉 注释设计 3 原则:
- 不重复代码
- 说明约束(尺寸 / 类型 / 单位)
- 写调用者关心的内容
一句话终极总结
👉 好接口 = 不看注释也能大致用对
👉 好注释 = 防止用错 + 解释边界条件
接口设计规范
- 函数名用明确动词,表达单一职责
- 能不用裸指针,就不用裸指针
- 输入参数优先
const T& - 输出结果少时可用引用,多时用结构体
- 错误处理方式在同一模块内统一
- 参数顺序统一:输入 → 配置 → 输出
- 多个相关参数封装成结构体
- 布尔参数尽量少,用枚举或配置结构体替代
Doxygen 注释规范
- 每个对外接口都写
@brief - 每个非显然参数都写
@param - 每个返回值都写
@return或@retval - 参数说明写清楚方向、单位、范围、约束
- 必要时写
@pre、@note、@warning - 注释描述"怎么用"和"边界条件",不是重复函数名
- 注释放在声明上方,保证生成文档时最清晰