第27章 文档生成:Doxygen注释规范、文档生成配置、API文档自动化
说实话,很多C语言开发者写代码时,最头疼的就是写文档。
我自己刚入行那会儿,也觉得「代码即文档」这句话特别酷。直到有一次接手一个老项目,看到满屏的/* TODO: fix me */和完全没有注释的函数……嗯,那感觉就像在迷宫里找出口,连个路标都没有。
后来我学乖了。文档不是写给别人的,是写给三个月后的自己的。而Doxygen,就是帮你把注释自动转成漂亮文档的神器。
27.1 Doxygen注释规范
Doxygen支持多种注释风格。我个人习惯用/** ... */,因为它在IDE里高亮效果最好。
27.1.1 文件头注释
每个源文件和头文件,我都会加一个文件头。它就像身份证,告诉别人这个文件是干嘛的。
/**
* @file tcp_client.c
* @brief TCP客户端核心实现
* @author 张工
* @date 2024-03-15
* @version 1.2.0
*
* 本文件实现了TCP客户端的连接、发送、接收功能。
* 支持IPv4和IPv6双栈。
*/
这里有个坑——@file后面一定要写文件名。我曾经漏写过,结果Doxygen生成的文档里,这个文件的内容全堆到「未命名文件」里去了,找都找不到。
27.1.2 函数注释
函数注释要写清楚三件事:干什么、参数是什么、返回什么。
/**
* @brief 建立TCP连接
* @param host 目标主机名或IP地址
* @param port 目标端口号(1-65535)
* @param timeout 超时时间,单位毫秒,0表示默认超时
* @return 成功返回套接字描述符,失败返回-1
* @retval -1 连接失败,errno已设置
* @retval -2 参数无效
*
* 注意:本函数会阻塞调用线程,直到连接建立或超时。
* 建议在子线程中调用。
*/
int tcp_connect(const char *host, uint16_t port, uint32_t timeout);
你看,@param后面跟参数名和说明,@return描述一般返回值,@retval描述特定返回值。这样生成的文档,别人一看就知道怎么用。
27.1.3 结构体和枚举注释
结构体注释有两种写法。我推荐成员后置注释,因为对齐后特别清晰。
/**
* @brief TCP连接配置参数
*/
typedef struct {
char host[256]; /**< 目标主机名或IP */
uint16_t port; /**< 端口号 */
uint32_t timeout_ms; /**< 超时时间(毫秒) */
bool keep_alive; /**< 是否启用TCP保活 */
} tcp_config_t;
枚举也是一样:
/**
* @brief 日志级别
*/
typedef enum {
LOG_DEBUG, /**< 调试信息 */
LOG_INFO, /**< 普通信息 */
LOG_WARN, /**< 警告信息 */
LOG_ERROR /**< 错误信息 */
} log_level_t;
27.2 Doxygen配置文件生成
Doxygen的配置项有上百个,但真正需要改的,其实就十几个。
生成配置文件很简单:
doxygen -g Doxyfile
这会生成一个默认的Doxyfile。我每次都会改下面这几个关键项:
| 配置项 | 推荐值 | 说明 |
|---|---|---|
| PROJECT_NAME | 你的项目名 | 显示在文档标题上 |
| PROJECT_BRIEF | 一句话描述 | 显示在文档副标题 |
| OUTPUT_DIRECTORY | doc/ | 文档输出目录 |
| INPUT | src/ include/ | 源代码目录,多个用空格隔开 |
| RECURSIVE | YES | 递归搜索子目录 |
| EXTRACT_ALL | YES | 提取所有实体,即使没有注释 |
| GENERATE_HTML | YES | 生成HTML文档 |
| GENERATE_LATEX | NO | 一般不需要PDF |
| HAVE_DOT | YES | 生成调用图、继承图(需安装Graphviz) |
27.3 文档生成与自动化
配置好Doxyfile后,生成文档就一行命令:
doxygen Doxyfile
但手动执行太原始了。我一般在Makefile里加一个doc目标:
# Makefile 片段
.PHONY: doc clean-doc
doc:
@echo "生成API文档..."
@doxygen Doxyfile
@echo "文档已生成到 doc/html/ 目录"
@echo "用浏览器打开 doc/html/index.html 查看"
clean-doc:
@rm -rf doc/html doc/latex
这样每次更新代码后,跑一下make doc,文档就自动更新了。
27.3.1 持续集成中的文档自动化
在CI流水线里,我习惯把文档生成和部署也加进去。比如GitHub Actions:
# .github/workflows/docs.yml
name: 生成并部署API文档
on:
push:
branches: [main]
paths:
- 'src/**'
- 'include/**'
- 'Doxyfile'
jobs:
build-docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: 安装依赖
run: sudo apt-get install -y doxygen graphviz
- name: 生成文档
run: make doc
- name: 部署到GitHub Pages
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./doc/html
你看,每次往main分支推送代码,文档就会自动重新生成并部署到GitHub Pages上。团队其他人打开浏览器就能看到最新文档。
27.4 知识体系总览
下面这张图,是我对本章知识点的总结。你可以把它当成一个检查清单:
27.5 一些实用技巧
最后分享几个我踩过的坑和总结的技巧:
- 注释要写「为什么」,而不是「是什么」。比如
/* 加1 */ i++;这种注释,删了也不影响理解。但/* 绕过内核的TCP快速重传机制 */这种,才是真正有用的。 - 用
@ingroup给函数分组。当项目有几百个函数时,分组能让文档清晰十倍。我习惯按模块分组:网络组、日志组、配置组…… - 生成文档后,一定要自己打开看看。我曾经以为注释写得挺好,结果生成出来发现
@param和参数名对不上,排版全乱了。嗯,工具不是万能的。 - 把Doxyfile纳入版本控制。这样团队所有人都用同一套配置,不会出现「我生成的文档和你的不一样」这种尴尬。
WARN_AS_ERROR = YES。这样Doxygen遇到注释错误会直接报错退出,强迫你把注释写规范。我在CI里一定会开这个选项。
文档生成这件事,说白了就是「写一次注释,受益无数次」。你想想看,每次有人问你「这个函数怎么用」,你直接甩给他一个文档链接,多省事。
而且,当你半年后回来看自己的代码,看到那些清晰的注释和自动生成的文档,你会感谢当初那个认真写注释的自己。