第10章 编写项目文档
第10章 编写项目文档
学习目标
- 能为一段接口改出可执行的文档示例并说明其失败语义
- 能用 Sphinx 在 CI 中把断链和严重警告判为构建失败
- 自测:为什么一个超长 README 不能替代按受众分层的信息架构?
为什么需要项目文档
想象你接手一台别人搭好的机器,说明书只写着"按这个按钮就行",却没说它吃什么、坏在哪、怎么停。你不敢动它,因为任何意外都可能算到你头上。文档就是这份让你敢接手、敢修、敢交出去的说明书。
没有它,代码再好也只有作者一个人能用。新人一脸懵,接手的人改一处怕崩一片,发布时连"这个版本对应哪份说明"都对不上。等作者离职,项目只剩下一堆没人敢碰的代码。
好的文档不是写得多,而是让对的人在对的地方找到对的信息——把作者脑子里的隐含知识,变成任何一台干净机器都能重放的显式步骤。
文档的五个支柱
先预测:一段示例在作者机器上跑通了,是否就能证明它可维护?不能。还要说清解释器与依赖、输入边界、失败路径、可观察结果和重建步骤。本章围绕五个支柱展开,每个支柱都回答"谁在什么时候需要什么信息"。
先写结构再润色,面向明确读者,示例真实且最小,用模板保持一致。信息轻量但充分。
下面逐个拆解这五个支柱,先看每个支柱是什么,再看它防住哪种失败。
支柱一:技术写作七原则
原书总结的 ↡先写结构再润色、面向明确读者、示例真实且最小的写作约束集,核心是"先写结构再润色"。先定好面向哪个读者、覆盖哪些主题、用什么模板,再去打磨文字;示例要真实且最小,信息轻量但充分。先写出公共行为和失败类型,再选择语法或工具,这样即使工具从原书生态迁到当前生态,调用者仍能依据相同契约判断结果。
def parse(payload: bytes) -> list[Entry]:
"""Parse one feed payload.
Raises:
ParseError: If the document is malformed.
"""
...这段 docstring 先声明公共行为(解析一条 feed)和失败类型(malformed 抛 ParseError),正是"先写契约"在文档层的落地:调用者不读实现也能判断结果。
支柱二:reStructuredText
原书用 ↡用标题、列表、代码块等语义标记表达文档结构的纯文本标记语言 表达标题、列表、内联标记、代码块和链接。关键认知是:标记语法服务于语义结构,不能用视觉缩进替代可解析层级。下面是一段最小示例:
Atomisator Parser
==================
Parse a feed::
entries = parser.parse(payload)
See :class:`atomisator.Parser`.标题用下划线表示层级,:: 引出代码块,:class: 是交叉引用角色。它们都是可被解析器识别的语义标记,不是"缩进好看"的视觉效果——这也是它和随手敲 Markdown 的本质差别。
支柱三:Sphinx 构建
文档工具 ↡把 reStructuredText 源文档、自动 API 和交叉引用构建成可发布站点的文档工具 把源文档、自动 API 和交叉引用构建成可发布站点。构建必须在 CI 中把断链和严重警告当作失败,否则断链会悄悄堆积到没人管。
sphinx-build -W --keep-going docs docs/_build/html
python -m doctest docs/examples.txt
python -m unittest discover -s tests-W 把警告升级为错误,--keep-going 让构建尽量跑完再报全部问题,doctest 把文档里的示例变成可执行证据。三者组合,让"文档过时"在 CI 阶段就暴露,而不是等用户照抄报错。
支柱四:文档组合与受众
设计、使用和运维文档回答的是不同问题,这正是 ↡按设计、使用、运维等不同读者分层提供不同入口的信息架构方法 这套方法要解决的事。生产者需要搭建指南,消费者需要使用教程,运维人员需要排障手册。一个超长 README 把三者混在一起,会让每类读者都在错误的地方翻找。正确做法是按受众分入口,让每类读者第一时间看到自己关心的信息。
支柱五:文档验收
最后由 ↡用可执行示例、明确版本和制品同版本证明文档有效且不过期的验收手段 负责收束。代码示例应可执行,版本与配置要明确,发布时文档和制品同版本;过期页面应删除或标明适用范围,而不是长期保留冲突答案。保存解释器、依赖锁定、输入、命令、退出状态、关键输出和制品摘要,才能让另一台干净机器重放同一结论。
实战验收清单
- 在隔离环境运行三段示例,记录解释器实现、版本和依赖来源。
- 为核心行为增加正常、空输入、失败和重复执行测试,先看到失败再修改实现。
- 清理缓存和临时文件后重跑,证明结果不依赖工作区残留。
- 对历史命令写出当前替代路径,并说明保留的架构不变量与不再采用的安全默认。
迁移决策题
设想团队正在维护一个运行多年的 Python 服务:它仍依赖本章对应的历史工具,但业务不能停机。先不要直接重写。第一步列出技术写作七原则承担的真实输入和输出,再用 reStructuredText 识别构建或运行时依赖;第二步把 Sphinx 构建放进隔离实验,证明当前行为与失败类型;第三步用文档组合与受众设计兼容层,让旧入口和新入口在同一组契约测试下运行;最后以文档验收保存制品摘要、行为差异与回滚条件。只有新路径在正常、边界和故障输入上都达到既定条件,才逐步切换。这样迁移的是可验证契约,而不是把一个旧命令盲目替换为一个新命令。
常见误区
误区 1
现象 → 文档写了却没人看,照样踩坑 原因 → 没有先定结构和受众,把所有信息塞进一个 README 修法 → 先按受众分层再填内容,每类读者给独立入口
误区 2
现象 → Sphinx 构建一堆警告,大家习惯了直接忽略
原因 → CI 没把警告判为失败,断链悄悄堆积
修法 → 构建加 -W --keep-going,警告即失败、阻断合并
误区 3
现象 → 文档里的示例跑不通,读者照抄就报错 原因 → 示例未纳入 CI 验证,文档随代码漂移 修法 → doctest 纳入构建,示例与制品同版本发布
小结
- 先写结构再润色,面向明确读者,示例真实且最小
- reStructuredText 的标记服务于语义结构,不用视觉缩进替代可解析层级
- Sphinx 构建在 CI 中把断链和严重警告判为失败
- 设计、使用、运维文档面向不同受众,需不同入口
- 代码示例可执行,版本配置明确,过期页面标明适用范围
练习
练习
问题 1:(改代码型)把下面 docstring 改成先声明失败类型、再补一句边界说明,并说明它如何帮助验收。
def parse(payload: bytes) -> list[Entry]:
"""Parse one feed payload."""
...问题 2:(问答型)为什么一个超长 README 不能替代按受众分层的信息架构?
问题 3:(独立实现型)为一个已有 Python 包写一份最小 Sphinx 构建配置,要求构建失败即阻断合并。
名词解释
名词解释
本章出现的专业名词,用大白话再讲一遍。
- 技术写作七原则
先写结构再润色、面向明确读者、示例真实且最小的写作约束集。就像盖房子先搭框架再装修,框架没立好,装修再漂亮也会塌。
- reStructuredText
一种用标题、列表、代码块等语义标记表达文档结构的纯文本标记语言。类似 Markdown,但更强调"标记代表含义"而不是"标记只是好看"。
- Sphinx
把 reStructuredText 源文档、自动 API 和交叉引用构建成可发布站点的文档工具。相当于文档界的编译器,把一堆文本文件编成一个能点能跳的网站。
- 文档组合与受众
按设计、使用、运维等不同读者分层提供不同入口的信息架构方法。好比一本说明书拆成"快速上手""完整教程""故障排查"三本小册子,各给对应的人。
- 文档验收
用可执行示例、明确版本和制品同版本证明文档有效且不过期的验收手段。即文档不只是写出来,还要能像代码一样被跑、被检查、被对版本。