8、文档生成:Doxygen 注释规范,自动生成 API 文档,ReadTheDocs 与 Sphinx 集成
说实话,很多团队在项目初期都不重视文档。代码写完了,文档?再说吧。结果呢?半年后新人接手,看着一堆没注释的接口直挠头。我自己就吃过这个亏——有一次接手一个遗留系统,光理解一个模块的调用关系就花了两周。
所以这一章,我们来聊聊怎么把文档这件事,做得既专业又省力。核心就三件事:写好注释、自动生成、持续发布。
8.1 Doxygen 注释规范:让代码自己说话
Doxygen 是目前 C++ 项目最主流的文档生成工具。它支持多种注释风格,但我个人推荐使用 Javadoc 风格,因为可读性最好,也最通用。
8.1.1 基本注释格式
一个标准的 Doxygen 注释块,长这样:
/**
* @brief 计算两个向量的点积
* @param v1 第一个向量
* @param v2 第二个向量
* @return 点积结果
* @throws std::invalid_argument 如果向量维度不一致
*/
double DotProduct(const std::vector<double>& v1, const std::vector<double>& v2);
嗯,这里要注意:@brief 是必须的,它会在生成的文档中作为摘要显示。我见过有人把整个函数逻辑都写在 @brief 里,其实没必要——摘要就一句话,详细说明放到 @details 里。
8.1.2 常用标签一览
| 标签 | 用途 | 示例 |
|---|---|---|
| @brief | 简短描述 | @brief 初始化网络连接 |
| @param | 参数说明 | @param timeout 超时时间(ms) |
| @return | 返回值说明 | @return 成功返回0,失败返回错误码 |
| @throws | 异常说明 | @throws std::runtime_error 连接失败 |
| @note | 注意事项 | @note 该函数不是线程安全的 |
| @see | 参考链接 | @see Disconnect() |
8.1.3 类注释与分组
对于类,我建议用 @ingroup 做分组。这在大型项目中特别有用——你想想看,几百个类散落在文档里,谁找得到?
/**
* @ingroup network
* @brief TCP 客户端连接管理类
* @details 封装了 socket 的创建、连接、收发数据等操作。
* 内部使用非阻塞 I/O 模型。
*/
class TcpClient {
public:
/**
* @brief 连接到指定服务器
* @param host 主机名或 IP
* @param port 端口号
* @return true 连接成功,false 连接失败
*/
bool Connect(const std::string& host, uint16_t port);
private:
int sockfd_;
};
8.2 自动生成 API 文档:Doxygen 配置与运行
注释写好了,怎么生成文档?Doxygen 需要一个配置文件。你可以用 doxygen -g 生成默认配置,然后改几个关键项。
8.2.1 核心配置项
# 项目信息
PROJECT_NAME = "My C++ Project"
PROJECT_BRIEF = "高性能网络库"
# 输出格式:HTML + LaTeX
GENERATE_HTML = YES
GENERATE_LATEX = NO
# 递归扫描子目录
RECURSIVE = YES
# 提取所有实体(包括静态函数、私有成员)
EXTRACT_ALL = YES
EXTRACT_PRIVATE = YES
EXTRACT_STATIC = YES
# 使用中文
OUTPUT_LANGUAGE = Chinese
# 生成调用关系图
HAVE_DOT = YES
CALL_GRAPH = YES
CALLER_GRAPH = YES
apt install graphviz 或 brew install graphviz。
8.2.2 运行与集成
配置好了,一行命令搞定:
doxygen Doxyfile
输出在 html/ 目录下,用浏览器打开 index.html 就能看到完整的 API 文档。我一般会在 CI 脚本里加这一步:每次合并到 main 分支时,自动生成文档并部署到内部服务器。
8.3 ReadTheDocs 与 Sphinx 集成:不止是 API 文档
Doxygen 生成的文档,说白了就是接口说明书。但一个完整的项目文档,还需要用户指南、架构设计、FAQ 这些内容。这时候就需要 Sphinx 出场了。
Sphinx 是一个文档生成工具,最初为 Python 设计,但通过 Breathe 插件可以完美集成 Doxygen 的输出。
8.3.1 整体架构
先看一张图,理解整个文档生成流程:
8.3.2 集成步骤
第一步,安装工具:
pip install sphinx breathe sphinx_rtd_theme
第二步,在 Sphinx 的 conf.py 中配置:
# conf.py
import os
import sys
extensions = [
'breathe',
'sphinx.ext.autodoc',
]
# Breathe 配置:指向 Doxygen 生成的 XML 目录
breathe_projects = {
"MyProject": "./doxygen_output/xml/"
}
breathe_default_project = "MyProject"
# 使用 ReadTheDocs 主题
html_theme = "sphinx_rtd_theme"
第三步,在 .rst 文件中引用 Doxygen 生成的接口:
API 参考
========
.. doxygenclass:: TcpClient
:project: MyProject
:members:
:undoc-members:
这样,Sphinx 就会自动从 Doxygen 的 XML 输出中提取 TcpClient 类的文档,并嵌入到最终页面中。
8.3.3 部署到 ReadTheDocs
ReadTheDocs 是一个免费的文档托管平台。你只需要在项目根目录放一个 .readthedocs.yaml:
# .readthedocs.yaml
version: 2
build:
os: ubuntu-22.04
tools:
python: "3.11"
apt_packages:
- doxygen
- graphviz
python:
install:
- requirements: docs/requirements.txt
sphinx:
configuration: docs/conf.py
然后,在 ReadTheDocs 后台关联你的 GitHub 仓库。每次 push 代码,它就会自动拉取、运行 Doxygen、调用 Sphinx 构建,最后发布文档。
apt_packages 里加上 doxygen 和 graphviz。我见过有人忘了加,结果构建失败,日志里报 "dot not found",排查了半天。
8.4 避坑指南
最后,分享几个我实际项目中遇到的坑:
- 注释与代码不同步——我曾经在一个模块里改了接口参数,但忘了更新 @param,结果文档里还是旧参数。后来我养成了习惯:改代码的同时改注释,哪怕只改一行。
- Doxygen 配置太复杂——默认配置有上千行,别怕。你只需要关注我上面列的那几个核心项,其他保持默认就行。
- 中文乱码——如果源码文件是 UTF-8 编码,记得在 Doxyfile 里设置
INPUT_ENCODING = UTF-8。我遇到过 GBK 编码的文件,折腾了好久。 - 文档太大,构建超时——ReadTheDocs 有构建时间限制。如果项目很大,可以考虑只生成关键模块的文档,或者用
EXCLUDE排除测试代码。
文档这件事,说白了就是「写一次,受益无数次」。花半天时间把 Doxygen + Sphinx + ReadTheDocs 的流水线搭好,以后每次提交代码,文档自动更新。团队里的新人来了,直接扔一个文档链接,省了多少口舌?