文档生成:Doxygen注释规范、文档生成配置、API文档维护
说实话,很多C语言工程师对写文档这件事,骨子里是抗拒的。我年轻时也一样,觉得代码就是最好的文档。直到有一次,我接手一个三年前的通信协议栈项目——代码写得确实漂亮,但没有任何注释,也没有文档。我花了整整两周才理清一个模块的调用关系。从那以后,我再也不敢轻视文档了。
今天聊的Doxygen,就是解决这个痛点的利器。它能把注释自动转成漂亮的HTML或PDF文档。你只要按规范写注释,剩下的交给工具。
一、Doxygen注释规范
Doxygen支持多种注释风格。我个人习惯用Javadoc风格,清晰且易读。下面是我常用的几种模式:
1. 文件头注释
每个源文件和头文件的开头,我都会放一个文件头。它告诉读者这个文件是干什么的。
/**
* @file uart_driver.c
* @brief UART底层驱动实现
* @author 张三
* @date 2024-01-15
* @version 1.0
*
* 本文件实现了UART的初始化、发送、接收功能。
* 支持波特率9600~115200,数据位5~8。
*/
嗯,这里要注意:@file后面最好写实际文件名,别偷懒写个“main.c”就完事。我见过有人把所有文件都写成“file.c”,那生成文档时根本分不清谁是谁。
2. 函数注释
函数注释要说明三件事:功能、参数、返回值。我习惯这样写:
/**
* @brief 初始化UART外设
* @param port UART端口号,取值范围0~2
* @param baudrate 波特率,支持9600/19200/115200
* @return int 0=成功,-1=参数错误,-2=硬件初始化失败
*
* 调用此函数前,请确保GPIO时钟已使能。
* 示例:uart_init(0, 115200);
*/
int uart_init(uint8_t port, uint32_t baudrate);
为什么要把返回值写这么细?因为我在项目中吃过亏。有一次一个同事调用我的函数,只看函数名以为返回0是失败,1是成功,结果整个逻辑反了。所以现在我把每个返回值的含义都写清楚。
3. 结构体与枚举注释
结构体和枚举的注释,我建议写在成员后面,而不是前面。这样生成文档时更直观:
/**
* @brief UART配置参数结构体
*/
typedef struct {
uint32_t baudrate; /**< 波特率,如115200 */
uint8_t data_bits; /**< 数据位,5~8 */
uint8_t stop_bits; /**< 停止位,1或2 */
uint8_t parity; /**< 校验位:0=无,1=奇校验,2=偶校验 */
} uart_config_t;
注意那个 /**< 符号,它表示注释紧跟在前面的成员后面。这是Doxygen特有的语法,用起来很方便。
4. 分组注释
当你的模块有多个相关函数时,可以用分组把它们归类。比如:
/**
* @defgroup UART_Driver UART驱动
* @brief UART初始化、发送、接收相关函数
* @{
*/
void uart_init(uint8_t port, uint32_t baudrate);
int uart_send(uint8_t port, const uint8_t *data, uint16_t len);
int uart_recv(uint8_t port, uint8_t *buf, uint16_t len);
/** @} */
这样生成的文档里,这三个函数会出现在同一个分组下,阅读体验好很多。
二、文档生成配置
Doxygen的配置通过一个叫Doxyfile的文件控制。我第一次用的时候,被里面几百个选项吓到了。其实常用的就十几个。我一般这样配置:
1. 基础配置
# 项目名称
PROJECT_NAME = "MyEmbeddedProject"
# 项目版本号
PROJECT_NUMBER = 1.0.0
# 输出语言(支持中文)
OUTPUT_LANGUAGE = Chinese
# 输出目录
OUTPUT_DIRECTORY = ./docs
# 只处理标记了注释的代码
EXTRACT_ALL = NO
# 包含私有成员
EXTRACT_PRIVATE = YES
# 包含静态函数
EXTRACT_STATIC = YES
这里有个坑:EXTRACT_ALL如果设为YES,Doxygen会把所有没注释的代码也生成文档。看起来省事,但生成的文档里全是“未命名”的函数,反而更乱。我建议设为NO,强迫自己写注释。
2. 输入输出配置
# 源代码目录
INPUT = ./src ./inc
# 文件编码
INPUT_ENCODING = UTF-8
# 递归搜索子目录
RECURSIVE = YES
# 生成HTML文档
GENERATE_HTML = YES
# HTML输出目录
HTML_OUTPUT = ./html
# 生成PDF(需要LaTeX支持)
GENERATE_LATEX = NO
我个人习惯只生成HTML,因为PDF需要装LaTeX,太折腾了。而且HTML可以直接部署到内网服务器上,团队所有人都能看。
3. 图表与关系图
# 生成调用关系图
CALL_GRAPH = YES
# 生成被调用关系图
CALLER_GRAPH = YES
# 生成包含依赖图
INCLUDE_GRAPH = YES
# 使用点图工具(需要安装Graphviz)
HAVE_DOT = YES
这些图非常有用。我曾经靠调用关系图,五分钟就定位到一个死循环的调用链。如果没有图,光看代码可能要半小时。
三、API文档维护
文档写出来只是第一步,维护才是大头。我总结了几条经验:
1. 注释与代码同步更新
这是最容易被忽视的。代码改了,注释没改,比没有注释更害人。我建议在代码评审时,把注释更新也作为一项检查点。
2. 版本号与变更记录
在文件头里加一个变更记录表,记录每次修改的内容和原因:
/**
* @file uart_driver.c
* @brief UART底层驱动实现
* @version 1.2
*
* 变更记录:
* - 1.0 2024-01-15 初始版本
* - 1.1 2024-03-10 增加流控支持
* - 1.2 2024-06-01 修复波特率计算溢出bug
*/
这个习惯帮我省了很多事。有一次客户反馈旧版本有问题,我一看变更记录,立刻知道是哪个版本引入的bug,修复起来非常快。
3. 自动化生成
手动跑Doxygen太累了。我一般在Makefile里加一个目标:
docs:
doxygen Doxyfile
@echo "文档已生成到 ./docs/html/index.html"
然后在CI/CD流水线里,每次代码合并到主分支时自动执行。这样文档永远是最新的。
四、知识体系总览
下面这张图概括了Doxygen文档生成的完整流程:
五、避坑指南
最后分享几个我踩过的坑:
坑1:中文乱码
Doxygen默认用Latin-1编码,处理中文会乱码。解决方案:在Doxyfile里设置 INPUT_ENCODING = UTF-8,并且确保源文件也是UTF-8编码。
坑2:Graphviz没装
如果你要生成调用关系图,必须安装Graphviz。我一开始不知道,折腾了半天图出不来。装好之后记得在Doxyfile里设置 HAVE_DOT = YES。
坑3:注释格式不统一
团队里有人用Javadoc风格,有人用Qt风格,生成的文档乱七八糟。我建议在项目开始时统一规范,并在代码评审时检查。
我的小技巧: 在IDE里配置Doxygen注释模板。比如在VS Code里装个Doxygen插件,输入/**后按回车,自动生成注释框架。省时又规范。
好了,关于Doxygen文档生成,我就聊这么多。记住一句话:好的文档不是写出来的,是维护出来的。养成写注释的习惯,配合自动化工具,你会发现文档工作其实没那么痛苦。
公众号:蓝海资料掘金营,微信deep3321