第 7 章 完备文档的基础

这世上有着数不尽的项目,其中既有工作项目,也有爱好者们开发的开源项目。但是,这些项目中有相当大一部分都没有写文档,或者事后才补写文档。为什么开发者们不喜欢写文档呢?这其中包含着怎样的问题呢?

本章中,我们将考察何种环境能便于开发者写文档。同时作为例子,还将介绍文档编辑工具 Sphinx。

7.1 要记得给项目写文档

项目文档的内容分为许多种。虽然我们不必在一开始就把所有文档都写出来,然而一旦缺少关键文档,就很可能造成项目止步不前。

《敏捷建模:极限编程和统一过程的有效实践》1(Scott Ambler 著)一书是这样回答“应该何时写文档”这个问题的。

1原书名为 Agile Modeling: Effective Practices for eXtreme Programming and the Unified Process,Scott W. Ambler / Ron Jeffries 著,张嘉路译,机械工业出版社,2003 年 1 月出版。——译者注

  • 据我的经验,当实际需要时再去做模型或写文档比较有效

  • 只在遇到麻烦时才更新文档

  • 所有文档都应该尽量晚写,应该留到马上要用时再写

所以我们需要的是写文档的时间计划,以及想写随时就能写的环境。那么,什么样的环境能让人随时可以写文档,什么样的环境又能让人有动力写文档呢?或者反过来说,妨碍我们写文档的因素都有哪些呢?下面我们就从 Python 程序员的视角出发,考察妨碍以及促使我们写文档的因素。

7.1.1 写文档时不想做的事

回顾我们以往的经历,那些没有写文档的情况都源于某些共通的理由,下面就介绍其中几个理由。

不想用写文档的专用工具

写文档时常用的工具有下面这几种。

  • 文字处理软件

  • 电子制表软件

  • Wiki

  • 其他综合型工具

这些工具在使用上有许多地方都和我们平时编程用的编辑器不同。为了写文档,必须用自己用不惯的编辑器或工具,这成了写文档时的一大制约。我们更希望能用平常编程用的编辑器写文档。

不想编辑流水账似的单一文件

一般的文字处理软件都是一个文件管理一篇文档。如果一个文件纵向延伸得较长,我们在编辑或显示内容时就需要将其滚动到特定位置。写文章过程中一旦需要对其他地方进行修改,就必须先滚动到待修改的位置再滚动回来。这种操作往往每写一篇文档就要经历许多次,而且每次寻找原先的位置都很麻烦。

然而这并不是说给工具加个界面分割或是记忆位置的功能就能行的。问题的根源在于一个文档以一个大文件的形式被管理着。这就像一个程序员肯定会觉得可分割的源码写在一个文件里十分影响效率一样。

要解决这个问题,应该像源码一样将文档分割成多个文件管理。

不能用 Mercurial 等管理差别,让人生厌

用文字处理软件写出的文档是单一的二进制文档,不管我们在里面修改了几个字、几段文章或者几幅图片,Mercurial 等版本控制系统都无法掌握该文件中的被修改之处。另外,如果有好几个人编辑同一个文件,很容易出现变更冲突,而且无法事后自动整合双方的变更。

由于存在着这些问题,我们很难放心大胆地去添加变更。

WYSIWYG 工具在装饰和显示上很费时间

WYSIWYG 是 What You See is What You Get 的简写,意思是“所见即所得”。顾名思义,用 WYSIWYG 类工具写文档时能直接看到文档最终的显示或装饰效果。

不过,这有时也会让我们在写文章时分心去考虑显示效果,在装饰和显示上花费多余时间。

不想把参考资料和程序分开写

极限编程等敏捷过程问世以来,人们对程序的概念发生了变化,那就是从“程序是按设计书写的一成不变的东西”变成了“程序是阶段性变化的东西”。

在程序阶段性变化的过程中,文档必须跟着程序的脚步进行更新。如果文档没能跟上程序的变化,看文档的人就会按照陈旧的错误信息去写程序,导致开发无法顺利进行。

其实,人们很早以前就开始借助专门工具来解决这一问题了。这些工具可以自动将函数定义附近的 API 文档反映到参考文档中。但可惜的是,有些编程语言无法使用这类工具。

所以我们需要一个轻松的联动机制,保证 API 和函数的实现与参考文档的内容不出现错位。

不想写没人看的文档

没人看的文档根本没必要写。那么,我们应该如何分辨一个文档有没有人看呢?

写出来的文档没人看,主要原因是这个文档的目的不够明确,让人不知道是写给谁的,为什么写的。所以在项目的早期阶段就该给文档做好计划,规定好什么时候写、为了什么目的而写,以及需要写什么内容。

如果写文档的目的不明确,那写出来的内容也不可能明确。有了一个明确的目的,不但能让我们有机会讨论是不是有必要写这个文档,而且写起来脑中也能有明确的内容。

有些时候,我们会遇到一些必须写的文档,但这些文档又不大可能有人来读。我们也要搞清楚这类文档是为谁写的,为什么要写。如果发现真的没必要写,可以跟委托人商量之后再定夺。

7.1.2 什么样的状态让人想写文档

我们前面考察了妨碍写文档的因素,那么什么样的情况能让我们更愿意写并且更轻松地写文档呢?

消除了妨碍因素,掌握写文档时的关键点后,以下几个“让人愿意写文档并能轻松写文档的条件”就自然而然地浮现了出来。

  • 能在平时用的编辑器上写文档

  • 能把文档分成几个文件来写

  • 能用 Mercurial 等轻松实现版本管理

  • 能集中精神编辑内容,不用顾虑装饰等外观问题

  • API 参考手册与程序的管理一体化

  • 平时的引用可通过 Web 浏览器共享

  • 在提交文档时可转换成漂亮整齐的单一文件

  • 写有用的文档

那么,满足这些条件的文档编辑环境有哪些呢?

如今市面上有很多文档编辑工具,它们各有各的特点,也各有各的长项与短项。这里我们从文档结构的观点出发,来了解一下这些特点的差异。

Wiki 是用来构筑半格(Semilattice)结构文档的工具。半格结构没有起点和终点,是一种各元素之间依靠引用链相连的网状结构。这种结构适合在某一主题下以不断添加关键字的形式编写文档片段。相反地,它不适合编写那种要从头到尾按顺序阅读并提交的文档。

与此相对的还有构筑树结构的工具。树结构存在起点,起点元素之下的各元素有着固定的从属关系。以图书为例,目录页下挂着各章的标题,各章下又挂着各节的标题。树结构的文档适合从头到尾按顺序阅读。不过,单纯的树结构在很多情况下都不大好用,所以我们会添加一个到任意元素的引用,将树结构改造成网状结构。虽然树结构看上去优于半格结构,但它必须有一个主干,所以不适合用来当没有固定结构的字典。

接下来我们将学习 Sphinx,它是构筑树结构文档的文档编辑工具。

Sphinx 是用 Python 编写的工具,用来构建多个以 reStructuredText 语法编写的文本文件,将它们转换为 HTML 或 PDF 等格式。Sphinx 可以将树的各个元素分割成多个文件进行管理。另外,这款工具的功能和特征满足刚才我们说过的“让人愿意写文档并能轻松写文档的条件”中的许多条,甚至能让人更加主动地想要写文档。

NOTE

当然,只要讲究方法、肯下功夫,不管什么工具都有可能让我们达到目的,但其过程是否给人带来压力就另当别论了。我们即将学习的 Sphinx 也完全不是那种在 GUI 中随便一操作就能写文章的工具,但它适合像写程序一样结构化地写文章。

接下来,我们将先来了解一下 Sphinx 的基本使用方法。然后根据本节列出的这些条件,分条学习如何用 Sphinx 实现它们。

7.2 Sphinx 的基础与安装

Sphinx 是一款文档编辑工具,具有十分全面的文档资料。

Sphinx 官方网站

http://www.sphinx-doc.org/en/stable/

Sphinx 使用手册

http://www.sphinx-doc.org/en/stable/contents.html

本节将对以下各项进行简要说明。

  • Sphinx 的安装

  • reStructuredText 入门

  • 用 Sphinx 编写结构化文档的流程

  • Sphinx 扩展

7.2.1 Sphinx 的安装

Sphinx 的安装流程并不复杂,但各位要尽量使用最新版本。这里建议各位使用最新的 Sphinx-1.3 系列(截至 2014 年 12 月时的最新版本)。

安装用以下命令执行。

  1. $ pip install sphinx

这样,我们就装好了 Sphinx 关联的程序包,现在可以用 sphinx-quickstart 命令了。sphinx-quickstart 命令能自动生成启动 Sphinx 所需的数个文件。具体执行方法如 LIST 7.1 所示。

LIST 7.1 sphinx-quickstart

  1. $ sphinx-quickstart -q -p SW-Project -a BeProud -v 1.0 sw-project
  2. Creating file sw-project/conf.py.
  3. Creating file sw-project/index.rst.
  4. Creating file sw-project/Makefile.
  5. Creating file sw-project/make.bat.
  6. Finished: An initial directory structure has been created.
  7. You should now populate your master file sw-project/index.rst and create other documentation
  8. source files. Use the Makefile to build the docs, like so:
  9. make builder
  10. where "builder" is one of the supported builders, e.g. html, latex or linkcheck.
  11. $ ls sw-project/
  12. build conf.py index.rst make.bat Makefile static _templates

执行完毕后,sw-project 目录下就生成了 Sphinx 项目,然后就可以通过 make html 构建 Sphinx 了。Sphinx 写文档的标准扩展名为“.rst”,所以这里会生成 index.rst 文件。

如果不加任何选项直接执行 sphinx-quickstart,则会以对话形式生成 Sphinx 项目。这种情况下,计算机将以对话形式提出几项询问,其中 Project Name、Author、Version 这 3 个问题需要由我们来输入,其他问题则只需根据实际情况选择 Y 或 N 即可。在 LIST 7.2 的例子中,我们让文件生成在 sw-project 目录下。

LIST 7.2 sphinx-quickstart 对话模式

  1. $ sphinx-quickstart
  2. Welcome to the Sphinx 1.3 quickstart utility.
  3. -(中间省略)-
  4. > Root path for the documentation [.]: sw-project
  5. -(中间省略)-
  6. > Project name: SW-Project
  7. > Author name(s): BeProud
  8. -(中间省略)-
  9. > Project version: 1.0
  10. -(中间省略)-
  11. $ ls sw-project
  12. build conf.py index.rst make.bat Makefile static _templates

通过 sphinx-quickstart 设置的内容全都记录在 conf.py 文件中,所以日后想更改设置时需要编辑 conf.py 文件。另外,如果想用中文版的 Sphinx,可以在 conf.py 文件中作如下设置(LIST 7.3)。

LIST 7.3 conf.py

  1. language = 'zh_CN'

详细安装流程以及 Sphinx 的初始设置请参考以下网站。

Sphinx 入门

http://www.sphinx-doc.org/en/stable/install.html

7.2.2 reStructuredText 入门

Sphinx 要用 reStructuredText(reST)语法写文档。这里我们学习几个具有代表性的 reST 写法(LIST 7.4)。

LIST 7.4 sample.rst

  1. ==============
  2. 章标题
  3. ==============
  4. : 项目1:字段列表内容1
  5. : 项目2:字段列表内容2
  6. : 项目3:字段列表内容3
  7. 节标题1
  8. =====================
  9. 节内第一个段落的文章。
  10. 换行会被忽略。
  11. 第二个段落的文章。
  12. 用空行划分段落。
  13. 空行至少为一行,输入多少行效果都一样。
  14. 1. 规定编号的有序列表
  15. 2. 规定编号的有序列表
  16. #. 自动编号的有序列表
  17. #. 自动编号的有序列表
  18. 没有编号的无序列表按以下格式书写。
  19. 无序列表
  20. 无序列表
  21. + 低一级的无序列表
  22. + 低一级的无序列表
  23. 可以给词汇添加多种特殊意义。
  24. - ** 强调**
  25. - 斜体
  26. - `` 显示为字符串``
  27. - ` 链接字符串A`_
  28. - ` 链接字符串B<http://docs.sphinx-users.jp>`__
  29. - :doc:`index` 到对象文件的链接。会自动替换为章标题。
  30. .. _ 链接字符串A: http://sphinx-users.jp

为了从首页链接到这个 sample.rst 文件,我们在 index.rst 中作如下描述(LIST 7.5)。

LIST 7.5 index.rst

  1. .. toctree::
  2. :maxdepth: 2
  3. sample

这样就可以构建 sample.rst 了。接下来我们执行 make html(LIST 7.6)。

LIST 7.6 make html

  1. $ make html

构建结果将输出到“_build/html”目录下。用浏览器打开“_build/html/sample.html”可以看到如图 7.1 所示的输出结果。

{%}

图 7.1 Sphinx 输出示例

reStructuredText 入门

http://www.sphinx-doc.org/en/stable/rest.html

7.2.3 用 Sphinx 写结构化文档的流程

Sphinx 在很多地方都引入了结构化的概念,因此它为写文档的人提供了一个方便写文档且方便整理的环境。下面就是利用 Sphinx 提供的结构化来编写文档的流程。

标题与元素

我们从零开始写文档时,往往会不知从何写起。虽然想到什么写什么不失为一种办法,但最好还是先列出标题大纲,整理一下文档内容的结构(LIST 7.7)。

LIST 7.7 先逐条列出标题

  1. 标题1
  2. 副标题1
  3. 副标题2
  4. 标题2
  5. * 标题3

然后再将想到的东西填进适当位置,使文章逐渐丰满起来(LIST 7.8)。

LIST 7.8 给标题添加内容

  1. 标题1
  2. 副标题1
  3. * 副标题2
  4. 副标题3
  5. 在这里逐渐补充副标题3 的内容。
  6. 如果想到了与副标题3 无关的内容,
  7. 就补充到其他合适的地方。
  8. 标题2
  9. * 副标题1
  10. 副标题2
  11. 标题3
  12. 副标题1
  13. 副标题2

这种具有明确父子关系或兄弟关系的文本称为结构化文本。本例中既用了标题和内容这种具有明显父子关系的逐条列记,又用了空格缩进的文本。在写文档时,如果我们脑中没有一个明确的框架,就需要不断重复这种堆砌“标题”与“表达内容的元素”(本例中是副标题)的工作。

单一文件内的结构化

用 reStructuredText 写的文章会被结构化,看上去条理分明。比如用 reStructuredText 描述会议记录并添加到邮件中,收件人就可以直接流畅地阅读。

结构化让各条信息之间的关系更加明确,并列、父子结构一目了然,而且信息的换位与重排也能很轻松地进行。

按照这种结构化规则写出的文本具有树结构(图 7.2)。

第 7 章 完备文档的基础 - 图2

图 7.2 文本的树结构

文件与目录的结构化

在写文档的过程中,会遇到标题下的内容过于详细的情况,这会破坏文章的平衡性。另外,如果我们只往一个文件里写,文章的主干就会越来越模糊,事情也会越来越讲不清楚。当文章达到一定规模,其内容需要分类分割时,我们可以根据文件和目录将其分割并结构化。分割后,主干与分支之间必须保持一个父子关联。这样一来,文件就能在分割之后仍保持树结构了(图 7.3)。

{%}

图 7.3 在维持树结构的前提下分割文件

等到文件数量多起来,可以将文件分组,按目录进行结构化。总而言之,就是灵活运用文件与目录,不断将结构化向前推进。

先将文档的章或节按目录或文件进行分割,这有助于我们今后对章节进行增减与重排。另外,由于文件被分成了多个,所以可以 1 人或多人同时编辑多个地方。举个例子,如果我们同时想到好几个点,那么可以同时打开多个文件做记录,这要比编辑单一文件更容易找到信息的位置,而且能很轻松地定位到之前中断工作的地方。

Sphinx 能将所有文档文件组合到一个树结构中。这样,所有文件都被排成了一个序列,并以让人能从上至下阅读的格式进行输出。该定义需在文档中用 .. toctree:: 指令描述。

只要有了 toctree 这样一个主干,文档就能在被分割成多个文件之后仍保持其结构。

网状结构

只要按照一定的规则给文档加入关键字或到其他章节的跳转,就能实现 Wiki 那种灵活的网状结构。脚注、交叉引用、术语集、索引等就是此类网状结构(图 7.4)。

{%}

图 7.4 结构化文档的网状结构

链接可以让我们更容易地在文档中找到想找的信息。Sphinx 会为我们提供清晰的术语集、API 参考手册等信息,我们可以利用这个功能来统一术语。

比如在写文章的过程中发现有术语需要统一,我们可以先用 :term:术语 的形式将该术语写下来。由于 Sphinx 在 make 时会提示该术语没有对应的术语说明,所以我们完全可以把写术语说明的工作放到最后再做,这样既不会打断写文章的进度,也不必担心出现遗漏。

此外,我们还可以用 :doc:../sub/index 这样的形式指定引用页面的相对路径,Sphinx 在 make 时会自动将该页面的标题和链接填充到这里。

结构化的 3 个阶段

正如我们前面所介绍的,结构化分为如下 3 个阶段(图 7.5)。

① 单一文件内的结构化

② 分割成多个文件 / 目录并结构化

③ 连接成无直接父子关系结构的网络

{%}

图 7.5 结构化的 3 个阶段

通过这样从内到外地让文章结构化、分组化,文档将自然而然地得到整理,写起来当然也会轻松许多。另外,Sphinx 以树结构为基础,与 Wiki 那种仅由网络形成的半格结构不同,阅读起来主次分明,易于理解。以树结构为主让文档整体有了一根脊梁,然后在各结构之间添加神经网络,这样可以加强文档阅读时的灵活性。

7.2.4 Sphinx 扩展

Sphinx 捆绑了 TeX 排版系统等扩展,另外也支持单独安装第三方开发的扩展。这些扩展包括流程图、序列图等图表的植入扩展、UML 画图、乐谱绘图、HTML 模板变更等。当然我们还可以自己开发扩展。

扩展功能在 Sphinx 的 conf.py 文件中设置。比如我们可以像 LIST 7.9 这样描述。

LIST 7.9 conf.py

  1. extensions = ['sphinx.ext.pngmath', 'sphinx.ext.todo', 'sphinx.ext.autodoc',]

这样就激活了 Sphinx 主体程序自带的 3 个扩展功能。这里将介绍的是 sphinx.ext.pngmath。

pngmath 扩展用来在文档中以图片形式显示数学公式。我们只要在文档中描述了代表数学公式的字符串,该扩展就会在构建时将它们转换为 PNG 图像文件植入文档。不过,用这个扩展的前提是具备 LaTeX 环境 2。

2Sphinx-users.jp 网站上有介绍 LaTeX 环境的安装流程。——编者注

我们在文档中作如下描述(LIST 7.10)。

LIST 7.10 math.rst

  1. =======
  2. 数学公式
  3. =======
  4. .. math:: (a + b)^2 = a^2 + 2ab + b^2

构建之后会显示如图 7.6 所示的数学公式。

{%}

图 7.6 sphinx.ext.pngmath 的数学公式绘图

Sphinx 对数学公式的支持

http://www.sphinx-doc.org/en/stable/ext/math.html

经由LaTeX 输出PDF 文档3(日语)

http://sphinx-users.jp/cookbook/pdf/latex.html

3该部分内容是 Sphinx-users.jp 网站针对日语文档的生成这种情况而编写的。英语文档的生成则参照 Sphinx 手册即可。——编者注

7.3 导入 Sphinx 可解决的问题与新出现的问题

本章最开始就列举了下列“让人愿意写文档并能轻松写文档的条件”。

  • 能在平时用的编辑器上写文档

  • 能把文档分成几个文件来写

  • 能用 Mercurial 等轻松实现版本管理

  • 能集中精神编辑内容,不用顾虑装饰等外观问题

  • API 参考手册与程序的管理一体化

  • 平时的引用可通过 Web 浏览器共享

  • 在提交文档时可转换成漂亮整齐的单一文件格式

  • 写有用的文档

现在我们分条学习如何用 Sphinx 实现这些条件。

7.3.1 由于是纯文本,所以能在平时用的编辑器上写文档

想必程序员都有过读纯文本文档的经历。特别是开源程序的附属文档,其中有许多图表都是由纯文本表达,它们和程序一起被视为源码进行管理。为什么很多人选择用这种方法写文档呢?理由有以下几点。

  • 不必借助特殊工具,仅用纯文本就能表达结构或图表

  • 编写、阅览文档不依赖特殊工具,无论该工具有偿无偿

  • 可以用源码管理工具来管理文本文件的变更

  • 写代码和写文档都能在惯用的编辑器上进行

可见,用纯文本写文档并不是心血来潮,它是有明确优势的。

Sphinx 就是用纯文本写文档。用户按照特定的规则描述纯文本,然后 Sphinx 来解释该规则,将文本以 HTML、PDF 等格式输出,这就是 Sphinx 的作用。

图片要以独立文件的形式统一保存,跟文档的文本文件放在同一目录或其子目录中(LIST 7.11)。这样,一旦日后遇到容量等问题需要改变大小或格式时,能方便地统一转换。

LIST 7.11 用于植入图片的 figure 指令

  1. 方便多人同时编辑,也便于用版本管理工具进行管理
  2. .. figure:: images/7-sphinx-vcs-manage.png
  3. Mercurial 管理 Sphinx 文档

7.3.2  信息与视图相分离,所以能集中精神编辑内容,不用顾虑装饰等外观问题

Sphinx 的以下特征实现了信息( Data)与视图( View)的分离,所以我们能集中精神写文档。

  • 用 reStructuredText 写文档看不到最终的显示效果

  • 可以集中精力描述逻辑结构化的正文

  • 最终的设计由 Sphinx(或者由制定主题的设计师)调整

  • 相当于其他文档编辑工具的大纲功能

前面我们学习了如何用 reStructuredText 写文档以及如何用 Sphinx 构建并输出。总而言之,我们可以把装饰和外观交给 Sphinx 处理,自己集中精神写文章内容。

比如,我们要用 Sphinx 编写如下会议记录(LIST 7.12)。

LIST 7.12 会议记录示例

  1. ============================
  2. 2015/2/17 会议记录
  3. ============================
  4. : 参加者: SW 商事平野、BP 清水川、BP 小田切、BP 冈野
  5. : 日期及时间: 2015/2/17(二)10:00 11:30
  6. : 场地: SW 商事的会议室
  7. 今日议程
  8. ==================
  9. 1. 介绍新成员
  10. 2. 面向开发者的程序库的开发情况
  11. 3. 11 月起的工作方针
  12. 介绍新成员
  13. =================
  14. 冈野:
  15. Python 经验 10 年。平时用 Django 框架和 GoogleAppEngine 进行开发。
  16. 参与了许多面向 Python/Django 的开源程序库的开发,
  17. 其中以面向移动电话的开发支持库 django-bpmobile 为代表。

这篇会议记录是用 reStructuredText 写的。如果用 Sphinx 构建,可以获取如图 7.7 ~图 7.9 所示的经过整理的 HTML 或 PDF 文件。

{%}

图 7.7 Sphinx 的 HTML 输出(default 主题)

{%}

图 7.8 Sphinx 的 HTML 输出(bizstyle 主题)

{%}

图 7.9 Sphinx 的 PDF 输出(latexpdfja)

此外,Sphinx 还搭载了强大的代码高亮功能,它可以给程序代码等自动搭配颜色,提高可读性。

代码高亮功能由 Sphinx 捆绑的 Pygments4 提供。支持的格式方面,编程语言、设置文件、HTML 模板等加起来有 200 种左右,而且仍在增加(图 7.10 ~图 7.12)。

4http://pygments.org/docs/lexers/

{%}

图 7.10 Windows ini 格式的高亮

{%}

图 7.11 Python 代码的高亮

{%}

图 7.12 Apache conf 格式的高亮

7.3.3 可根据一个源码输出 PDF 等多种格式

前面我们了解了纯文本的好处以及文件分割的好处。不过,我们仍然希望在印刷和交付时,文档能被整合成一个文件。

另外,虽然文本文件形式的文档与 PDF 文档有着同样的信息价值,但文本文件形式的文档总会给人一种廉价感。为避免因此而受到影响,我们在上交文档时最好准备一份经过简单排版和装饰的 PDF 文件。

Sphinx 可以将同一份源码文件转换成多种不同的格式。输出格式支持 HTML、PDF、EPUB、man、LaTeX、HTML Help(chm)等(图 7.13)。另外,导入自制的扩展后,还可以支持一些原本不支持的格式。

图像说明文字

图 7.13 Sphinx 输出的 PDF

7.3.4 通过结构化,文档可分成几个文件来写

程序员在写程序时不会将所有代码都写在同一个文件里。因为历史和长期以来的经验告诉我们,这样做是非常不明智的。源码要以概念或目的为单位分割成多个文件,在目录中进行分类管理。

这个做法对于文档同样有效。分割、分类的标准可由写文档的人随意制定,但其中还是有一些技巧可言的。比如 LIST 7.13 这个例子就能让人一眼看出文档的结构,非常清晰。

LIST 7.13 文档目录结构示例

  1. 文档/
  2. +-- index.txt
  3. +-- 项目管理/
  4. | +-- index.txt
  5. | +-- project-goal.txt
  6. | +-- member-and-structure.txt
  7. | +-- phase1-schedule.txt
  8. +-- 设计/
  9. | +-- index.txt
  10. | +-- middleware-versions.txt
  11. | +-- framework-comparision.txt
  12. | +-- server-structures.txt
  13. | +-- components-and-interfaces.txt
  14. +-- 开发/
  15. | +-- index.txt
  16. | +-- repository.txt
  17. | +-- coding-rule.txt
  18. | +-- develop-environment.txt
  19. | +-- release-packaging.txt
  20. +-- 参考手册/
  21. | +-- index.txt
  22. | +-- install-and-setup.txt
  23. | +-- class-and-functions.txt
  24. | +-- api-references.txt
  25. +-- 会议记录/
  26. +-- index.txt
  27. +-- 20150201-meeting.txt
  28. +-- 20150208-meeting.txt
  29. +-- 20150215-meeting.txt

这样分类之后,我们写文档时的注意力就会转移到以下几项上。相较于在单一文件中编写文档,这样能更加轻松地整理文档内容。

  • 根据分类或文件名调整该文件涉及内容的范围

  • 文件内容或文章量达到一定规模后,重新整理内容、分割文件

  • 文件达到一定数量后,将同一分类的文件归入一个子目录

如上面例子所示,Sphinx 可以借助文件分割以及目录分类来构筑文档。这些分割开的文件会在使用 Sphinx 构建时通过链接等方式添加关联,并采用适合 HTML、PDF 等格式的形式输出。

至于输出之后的效果,完全可以交给 HTML 或 PDF 等输出算法来处理。写文档的人只需关心文档结构是否符合逻辑,链接数量是否恰到好处等。也就是说,我们能完全以逻辑思考为中心来编辑文档,不必去想多余的事。

7.3.5 能用 Mercurial 等轻松实现版本管理

Sphinx 的文档由多个目录下的多个纯文本文件共同构成。图片文件也和文本文件一样保存在目录中。在这种结构下,我们能以极细的粒度进行版本管理,而且即便出现多人同时编辑文档的情况,各个变更之间也不会发生冲突(图 7.14)。

{%}

图 7.14 用 Mercurial 管理 Sphinx 文档

7.3.6 API 参考手册与程序的管理一体化

作为开发者,我们有时候会自己写 API,有时候又会拿现成的 API 来用。理想的情况是写好 API 的同时就能做好 API 参考手册,但如果是没有人读的东西,做出来也只是白费功夫。所以什么时候该做什么东西,要仔细结合重要性进行考量。

进行 Python 开发时,只要 docstring 用得好,完全可以解决这一问题。docstring 指的是写在 Python 模块、类、函数最开头的字符串对象。比如,我们会用 docstring 在函数的开头像 LIST 7.14 这样描述文档。

LIST 7.14 path.py

  1. # -*- coding: utf-8 -*-
  2. def commonprefix(path_list):
  3. """
  4. 返回路径的“`path_list`”中最长的公共前缀
  5. (依次判断路径名的每一个字符)
  6. >>> commonprefix(['usrbin/python', 'usrlocal/bin/python'])
  7. 'usr'
  8. >>> commonprefix(['usrbin/python'])
  9. 'usrbin/python'
  10. 如果“`path_list`”为空,则返回空字符串“''”
  11. >>> commonprefix([])
  12. ''
  13. 请注意,由于每次只判断一个字符,
  14. 所以可能返回非法路径
  15. >>> commonprefix(['usrlocal/bin/python', 'usrlocal/bin/pylint'])
  16. 'usrlocal/bin/py'
  17. """
  18. if not path_list: return ''
  19. s1 = min(path_list)
  20. s2 = max(path_list)
  21. for i, c in enumerate(s1):
  22. if c != s2[i]:
  23. return s1[:i]
  24. return s1

如 LIST 7.15 所示,以这种方式写进去的函数文档可以在交互模式中引用。

LIST 7.15 函数文档的引用

  1. >>> help(path.commonprefix)
  2. Help on function commonprefix in module path:
  3.  
  4. commonprefix(path_list):
  5. 返回路径的“`path_list`”中最长的公共前缀
  6. (依次判断路径名的每一个字符)
  7.  
  8. >>> commonprefix(['usrbin/python', 'usrlocal/bin/python'])
  9. 'usr'
  10. >>> commonprefix(['usrbin/python'])
  11. 'usrbin/python'
  12.  
  13. -(中间省略)-

Python 还拥有 doctest 功能,它能够识别出在 docstring 中描述的,也就是和交互模式显示信息相同的部分并进行测试。进行 doctest 的最简单的方法就是执行 LIST 7.16 中的命令。

LIST 7.16 doctest

  1. $ python -m doctest -v path.py
  2. ...
  3. 4 tests in 2 items.
  4. 4 passed and 0 failed.
  5. Test passed.

这样一来,函数文档里写的内容和函数的功能就不会再有偏差了。另外,我们可以放心地将 docstring 植入 Sphinx 文档。植入时要用到 sphinx.ext.autodoc 扩展,具体描述如下(LIST 7.17、LIST 7.18)。

LIST 7.17 conf.py

  1. sys.path.insert(0, '【path.py 所在目录的路径】')
  2. extensions = ['sphinx.ext.autodoc',]

LIST 7.18 path.rst

  1. ================
  2. AutoDoc 范例
  3. ================
  4. .. autofunction:: path.commonprefix

然后我们就能够得到图 7.15 中的输出结果了。

{%}

图 7.15 用 sphinx.ext.autodoc 将 docstring 植入 Sphinx 文档

可见,只要 API 的实现及其相关文档保持在可测试状态,并保证其能自动植入 Sphinx 文档之中,许多问题就迎刃而解了。另外,如测试驱动开发一样,将介绍 API 使用方法的文档兼测试写在实现 API 之前,这既能让人们对该 API 的使用有一个更明确的认识,也能给开发带来帮助。

7.3.7 通过 Web 浏览器共享

文档内常常包含开发规则或 API 参考手册等内容。让成员能在 Web 上浏览到这些信息会带来许多好处。

当我们想和其他开发者共享某个文档,进而展开讨论或交流时,如果能通过 URL 指定该文档,那将让共享变得非常方便。而且这样做不需要在邮件上挂附件,能避免出现多余的文档副本。

Sphinx 用起来最方便的输出格式就是 HTML。我们可以设置一个机制,在 Mercurial 的版本管理之下提交 Sphinx 源码时,用 Jenkins 自动将其构建成 HTML,这样一来就能随时在 Web 上看到最新的文档了。与 Jenkins 的关联我们将在第 10 章中详细了解。

7.3.8 导入 Sphinx 后仍存在的问题

前面我们了解了 Sphinx 作为文档编辑工具的一面,但有些问题并不是导入 Sphinx 就能解决的。另外,对于非程序员或非技术人员来说,Sphinx 的某些优点反而会变成缺点。

所以,我们这里要探讨导入 Sphinx 无法解决的问题以及导入后新增的问题。

写有用的文档

我们在“让人愿意写文档并能轻松写文档的条件”中提到了“写有用的文档”这一条。可惜的是,仅导入 Sphinx 并不能解决这一问题。在 7.4 节,我们将学习如何写有用的文档。

审查需要多花心思

许多文字处理软件都配备了审查或审校的功能,但 Sphinx 中并没有这类功能。所以在进行审查校对、反馈校对内容等工作时,需要额外花些心思才行。

Sphinx 中有用来记录 todo 的扩展表示法。使用这个功能前,先要在 conf.py 中作如下设置(LIST 7.19)。

LIST 7.19 conf.py

  1. extensions = ['sphinx.ext.todo',]
  2. todo_include_todos = True

然后像 LIST 7.20 这样在文档中描述审查校对的内容。

LIST 7.20 在 todo 指令中描述校对内容

  1. 今日议程

  1. 介绍新成员
  2. 面向开发者的程序库的开发情况
  3. 11 月起的工作方针

.. todo:: (sato) 缺少 事务联络,请添加。

如果想获取 todo 的一览表,需要在文档的任意位置加一行 .. todolist:: 然后 make html。

sphinx.ext.todo - Support for todo items

http://www.sphinx-doc.org/en/stable/ext/todo.html

部分人只会在 WYSIWYG 编辑器上写文档

前面也说了,Sphinx 的信息与视图是分离的,所以我们能集中精力去编写文章,不必为装饰等外观方面的问题分心。但是对于一部分人而言,不能把握输出效果就写不下去文章。这一类人会觉得没有 WYSIWYG 编辑器的 Sphinx 很难用。这个问题暂时还没有很好的解决方法。

输出 PDF 需要搭建相应的环境

虽然 Sphinx 支持多种输出格式,但仅有它本身时,是不能输出 PDF 的。输出 PDF 有两种方法(经由 LaTeX 和使用 reportlab 扩展)。这两种方法各有特点,其中经由 LaTeX 的输出更加直观。

关于用 Sphinx 输出 PDF 的方法,http://www.sphinx-doc.org/en/stable/index.html 网站上介绍了相关的导入流程以及使用方法。此外,该网站上还介绍了将 Sphinx 文档经由 LaTeX 输出成 PDF 的方法。

用 Sphinx 生成 PDF 文件

http://www.sphinx-doc.org/en/stable/tutorial.html?highlight=pdf

7.4 文档集的创建与使用

各位在写文档时都注意了哪些点呢?另外,要想写出一篇有用的文档,有哪些点需要注意呢?

文档是有读者的。如果在写文档时没有考虑读者,那我们的意识就会偏到“应该加入哪些信息”“应该写到哪种程度”上,结果就是相关信息越添越多,最后却忘了原本要表达的信息,导致文档不能达到原定的目的。能让读者觉得是好文档的文档,必然考虑了“读者是谁”以及“怎样写能让读者看得更明白”等问题,确保为读者提供了充足且有用的内容。

文档的目标读者、内容、深度等问题,可以拿过去项目的文档来参考。我们知道,“文档的写法”“版本库的用法”“项目的推进放法”这类东西都是可以重复利用的。同样道理,一个项目文档的许多元素完全可以拿到另一个文档中重复利用。

7.4.1 什么是文档集

如今有一种思路,就是为了重复利用文档而将文档模板化,然后构建成文档集(Documentation Portfolio)。《Python 高级编程》5 一书将文档集称为文档工件集,并作出如下定义。

5原书名为 Expert Python Programming,Tarek Ziadé 著,姚军等译,人民邮电出版社,2010 年 1 月。——译者注

从作者的角度,这可以通过拥有一组可复用的模板和描述如何、何时在项目中使用这些模板的指南来完成,它被称为文档工件集。

也就是说,一个文档集由以下内容构成。

  • 文档模板集(规则、流程、会议记录、设计等)

  • 使用文档模板的导航(何时用、怎样用等说明)

7.4.2 项目所需文档的一览表

Scoot Ambler 著的《敏捷建模:极限编程和统一过程的有效实践》一书中有文档集的相关信息,可供各位参考。他在书中提到了项目所需的文档一览表,同时举了例子,现整理如下。

  • 文档一览表(精选)

    • 协议模型:关于系统的技术性界面

    • 设计上的已确定事项:设计和架构方面的重要决定的记录

    • 对上级的概要说明:系统构想、需求、当前的预估、风险、人员计划、日程安排

    • 使用文档:使用时所需信息、流程、环境的概述

    • 项目概要:开发时所需信息、流程、环境的概述

    • 需求文书:定义系统需要完成的目标

    • 支持文档:给技术支持者的培训资料、问题排查、维护团队的联络方式一览表等

    • 系统文档:系统概要、构架、需求事项等的概要

    • 用户文档:使用说明书、培训资料等

敏捷建模的文档列表基本网罗了所有必备信息。但这仍存在一些问题,比如从完全没有文档的状态起步时,我们很难一次性将这些文档全写出来。所以第一步,可以先处理自身团队或组织所需的部分,或者是以过去的文档为基础,将文档的目的抽选出来进行类似上述分类,进而模板化。最终我们将构筑出适用于自己或团队的文档集,供我们在今后的项目中加以利用。

接下来,我们将根据目标读者的类型分别了解一下文档集。

7.4.3 面向项目组长、经理

普通程序员很难想象项目组长、经理们想要的信息,所以更谈不上将它们写入文档。这里最好的办法就是向项目组长、经理询问具体想要的信息。各位不妨以本节所讲的内容为基础,跟项目组长、经理探讨一下访问客户之前应该掌握哪些信息,应该将哪些东西落实在文档中。

项目的目标(终点)

在项目目标中,我们要写出“用户导入我们即将开发的系统后能获取何种收益”“与以往相比有哪些改善”之类的信息,而技术和系统方面的目标则不必在此太过重视。当然,有些项目本身就是为了开发 Python 程序库,这时技术和系统方面的目标就成了必须写入目标的事项。不过,这种情况也必须写清楚为什么要开发这个东西。

项目的目标(终点)自然应该跟所有参与项目的成员共享,但意外的是,这一点在现实中贯彻得并不好。即便团队中某些成员只负责敲代码,我们也应该把目的共享给整个团队。更何况,在文档中写明目标对项目组长本身也有帮助。或许尚不熟悉项目运营的组长会觉得“目标会经常跟着客户的要求变”,但实际看来,目标真正改变的项目并不多。我们不光要询问客户的要求,还要将目标落实到字面上,然后跟客户以及项目组成员共享出来,这样才能给项目创造出一个主干,使开发有条不紊。如果这一点上搞不清楚,那这个项目从头至尾都不会稳定,难免发生差错,甚至会导致返工。

体制

在项目的体制中,要写明各个参与者(团队)的职务,使成员们清楚自己在项目中的定位。职务不能只写名称,还要写明各职务需要做的工作,让人明白每个职务该负起哪些责任。如果只写一个“主程序员”,那么任谁都搞不清楚这个职务应该负责什么。这部分虽然不必写得很严密,但必须让每个职务对应的人员清楚自己的职责范围,不然项目开发的过程中必定出现大问题。另外,某些职务或团队的人数需要根据参与时期进行调整,这类信息最好也写进来。

需求

需求是很重要的。在项目开始之前,需求就已经朦胧地存在于客户脑中了。我们的设计与开发全都要以需求为准。在开发过程中,需求是考察系统完成度的指标,如果需求不明确,我们完全无法判断系统的发展方向是否正确,也无法考察开发到了哪里。到了运营阶段,我们也需要参考需求来了解系统的概念或衡量业务变更的影响。

在定义需求时,一般要将需要哪些功能、需要怎样运营、需要运行性能达到何种程度等信息逐条列出或者汇总成表。

日程、咨询项目表

对实现需求的日程进行描述。能左右日程的因素有很多,比如客户的要求、当前可处理的范围、能影响到日程的外部因素,等等。在初期阶段,日程常会受各种因素影响而变化,所以要定期重新审视并做好记录。

除此之外还有咨询项目表,用来与客户协商尚不明确的事宜。

7.4.4 面向设计者

面向设计者的文档需要对构架和概念多加描述,但随着时间推移,选择该设计、基础结构、开发语言的理由会显得越来越重要,所以这些也要写在文档中。另外,在 OS 和开发语言方面,最好也写上选择该版本的理由。以 Python 为例,假设我们选用了较旧的 2.5 版,理由可能是只有这个版本提供了我们用作基础结构的服务,也可能是当时没有发布新版本,或者是 OS 的程序包管理中没有新版本,又或者是公司的方针规定不采用最初的主版本,总之可能的情况非常之多。如果文档中没有对这些情况做记录,一旦将来要探讨版本变更问题,那将会是一件非常麻烦的事。

涉及下列各项时,请各位务必记录选择的理由。

  • OS、语言、版本的选择

  • 构架设计

  • 基础设施设计

7.4.5 面向开发者

在搭建开发环境方面,我们提倡导入自动化机制来尽量缩短流程,但还是难免会遇到无法自动化的部分,或者是自动化成本过高的情况。因此,我们应该将流程尽量详细地记录下来,以备不时之需。有些时候,流程文档的记录会不够全面,比如只写了搭建步骤却没写构建选项,等等。要知道,流程文档是写给不懂这些的人看的,所以应该将命令行要输入的内容全部网罗进去。

NOTE

能自动化的地方不能太吝啬成本,应当尽量自动化,从而降低搭建环境和编辑文档的成本。

在一些环境搭建流程尚未优化的项目中,单是给一个开发者搭建环境就可能花费超过一周的时间。这种情况下,每当遇到实现或测试阶段都会消耗大量的人力资源。软件开发项目中的许多机制都可以自动化,其中与搭建环境相关的自动化最好在项目起步时就准备好。

本书将在第 9 章和第 11 章中介绍自动化搭建环境的相关内容。

7.4.6 面向客户

面向客户的文档包括指导如何使用成品程序的学习手册,以及总结了日常操作流程的使用指南等。

当然,前面讲到的设计、开发、搭建环境等的文档一般也会交付给客户,但除了客户想自己修改程序之外,这些文档的内容都太过晦涩,而且客户无法对内容加以干涉。与此相对,使用手册是客户日常会用到的东西,而且客户在给这类文档的内容提要求时更清楚自己想要什么。是否需要面向客户的文档,或者文档中该有哪些内容,这都需要与客户认真探讨系统的使用情境之后再决定。如果定不下来,那么很可能是客户自己对系统没有一个明确的设想。这种情况下,客户可能在收到成品后或开始使用后才发现“这跟我想要的不一样”。所以为了防止出

现这类问题,我们必须与客户认真探讨,共享成品在开始运营后的情境,编写出所需的文档。

7.5 小结

本章对“什么样的环境便于开发者写文档”进行了整理,并以 Sphinx 为例介绍了实现此类环境的方法。

我们无法保证大程序从一开始就能被正确实现,同样道理,我们也无法保证大文档从一开始就能有一个正确的结构。Sphinx 这类结构便于文档阶段性成长,它能防止我们在写文档上浪费劳动力,同时也能作为指向标,引导我们最终完成一份恰到好处的文档。

另外,对于“写有用的文档”这一无法用工具解决的问题,各位可以运用文档集来弥补。将已有文档的结构和构造升华为文档集来重复利用,不但能逐渐提高文档的品质,还能有效降低文档编写的成本。