C++ API 设计 14 第九章 文档

第九章 文档

在本书的第一章,我把API定义为一个或多个头文件再加上文档支持。事实上,除非API附带上文档,否则API还是处于未完成的状态。这是因为头文件并未指定API的行为,只是包含各种函数和方法的调用协议。David L. Parnas很好地解释过这些(Parnas, 1994):

重用是说起来容易,做起来难。这么做同时需要良好的设计和优秀的文档。即使是有不错的设计,这还是比较少见的,如果没有优秀的文档,我们还是看不到重用的组件。

编写良好的文档是API的一个重要构成元素。因此,我这里花一整个章节来详细描述这个主题。开始我先解释一下为什么应该这么在意文档,接着介绍一下各种文档类型,这可以通过几种工具来完成编写文档的任务

有种编写API文档的最简单的方式就是利用工具自动化抽取头文件中的注释,为你生成API文档。这些工具中最流行和功能齐备的是:Doxygen,来自Dimitri van Heesch。我将会花些时间来看看如何在项目中使用Doxygen,并为文档化各种API元素提供几个样例模板。

文档化实现代码也是一种不错的练习,不过这种文档是只供你内部使用。而API的头文件文档是更重要的,因为这个是给你的用户使用的。

到现在我不得不承认在本书的源码中的注释很少或者根本没有。这么做的目的是尽量让这些例子简短和集中关注重要的部分,这可以让你和其他读者更容易理解,而且不会让本书的篇幅增加一倍。做为一个折衷的方案,我保证随书附带的源码例子都是有良好的文档,你编写的API头文件也该这么做

9.1 编写文档的原因

我希望并不需要说服你向用户提供有关如何使用你的API的详细信息,这是一件好事。这完全是正确的,如果你遵循品质章节中的核心原理,那么接口就应该是一致的、可发现的和易于使用的。然而,这并不能代替良好的文档。专业地编写文档是发布高质量、世界一流的API的重要构成要素。事实上,良好的文档为用户在采用或选择API时提供了鉴别的依据。

9.1.1 定义行为

API相当于功能说明书。也就是说,它应该定义如何使用接口和接口的行为。现在看一个头文件的方法,它会告诉你它的参数的数量和方法,但是并没有告知方法的行为。出于演示的目的,考虑下面这个表示RGB颜色的类定义。

[代码 P268 第一段]

因为这个类的每个方法都完全定义了参数和返回类型,所以你可以编写代码创建这个类的实例并调用每个方法。不过,这个类还有很多行为是未知的。例如:

q红、绿和蓝浮点值是表示成0.0到1.0范围,还是从0到100%,又或者是0.0到255.0(127.5可以精确地表示中间值),最后或者是0到65535?

q如果传入构造函数或Set()方法的值不在范围内时会发生什么?是保持原值、截断值、取绝对值或尝试把值缩放到有效范围内?

q构造函数和Set()方法的参数顺序是什么样的?假设是红、绿和蓝,但是这在函数签名中没有明确地注明。

下面更新过的这个类版本提供了一些文档,通过给出注释的形式,帮助澄清上述的这些疑点。(三斜线将在本章的稍后进行解释,当我讨论更多关于Doxygen的细节时。)

[代码 P268 第二段]

这些注释都很简短,达到最小化了,但是它们都指定了API的行为,通知用户使用它的合理范围。文档并不一定要是冗长的。用户欢迎更多的细节,最小化的文档比起什么都没有还是好多了。

提示

良好的文档描述的是如何使用API和API在不同的输入下的行为。

编写这些类型的注释是一种好习惯,正如在头文件中编写一样。一旦API更加稳定和接近符合发布的质量时,你可以随时回头添加更多的内容或修改细节。但是,如果你没有那么多时间,需要API尽快出炉,那么你至少也要给出一些基本的文档。更好的是:在API发布前,你应该明确制定编写文档的计划。

你甚至还考虑到为校订控制系统创建委托钩子(commit hook,也叫做触发器)来阻止不包含文档的新公共API代码。如果这个策略太苛刻了,你仍然可以执行代码检查,把它们当成一种参考意见,而不是检测新代码的障碍。

提示

为你每个要实现的组件编写API文档。一旦API完成时再进行修订。

9.1.2 文档化接口契约

术语“契约式设计”(design by contract)是由Bertrand Meyer引入的,做为软件组件中定义正式规范的一种方法(Meyer, 1987)。Meyer不久就在美国注册了这个术语的商标,因此很多开发人员现在使用契约编程(contract programming)来代替。其中的核心概念是一个软件组件为它要提供的服务给出了一份契约或义务,而且使用这个组件的用户同意这份契约。这个概念是由Hoare logic建立的,他为了论证关于程序的正确性而开发了一个正式的系统。这个模型的主要原理有三点:

(1).先决条件:用户在调用函数之前,要满足这个函数所需要的先决条件。如果这个先决条件不满足的话,那么这个函数会无法正确地操作。

(2).后置条件:函数在它结束自己的工作后要保证某种条件。如果后置条件不满足的话,那么函数就无法正确地完成它的工作。

(3).类不变式(class invariant):类的每个实例的约束必须得到满足。这定义了状态必须满足对类的操作是依据当初对类的设计。

指定契约细节的最好方式就是在API的类和函数文档中说明。换句话说,函数应该在调用它们前强制指定所有的预期先决条件,以及在它们完成后要满足所有的结果后置条件。还有类文档应该指定在每个类应遵循的生命期内的任何不变式,也就是在构造后,析构前,在每个公共方法调用的前后。让我们看看几个例子,更具体地说明这个概念。

有个平方根的函数的先决条件就是输入的数值必须是正数或0。后置条件是函数平方的结果应该等于输入的数值(允许适当的误差)。这些条件可以用下面的Doxygen语法来进行文档化。在下一章测试章节中将讨论如何在代码中强制这些条件。

[代码 P270 第一段]

为了给一个更加面向对象的例子,请考虑字符串容器类。这里有一个类不变式和字符串的长度,是由size()方法返回的,必须总大等于0。c_str() 和 data()返回的指针必须总是非NULL的。对于类的append()方法,字符串的长度必须随着输入字符串长度的增加而改变。这些契约由下面的文档注释来表明。(@pre.size()表示在方法调用前的字符串长度。)

[代码 P270 第二段]

提示

契约编程的意思是文档化函数的前置和后置条件,还有类的不变式。

就类之间的继承关系而言,Ken Pugh注意到派生类的先决条件可以弱于、但不能强于它的基类(Pugh, 2006)。也就是说,派生类应该可以处理所有它的基类能够处理的事情,但是它只需要更少的先决条件就能处理额外的事情。相比之下,派生类应该继承它的基类的所有后置条件。也就是说,派生类中的函数必须满足它的基类的所有后置条件,还包括它自己定义的更多后置条件。

9.1.3 传达行为变更

经常有这样的情况:当一个API修改时并不包括任何类定义或函数签名的修改。这种情况发生在只是修改了函数底层实现而不会影响它的接口的时候。从代码的角度看,这种修改对用户是不可见的,因为它不会影响到头文件中函数的签名。然而,API确实有改动,因为它的行为改变了。换句话说,这种更新是源码和二进制兼容的,但不是功能兼容的。

在这种情况下,你可以为API调用修改文档来传达这种变动。例如,考虑下面的函数,返回一系列层级节点的子节点:

[代码 P271 第一段]

根据文档,如果层级中没有子节点,函数返回一个NULL指针。这种行为强制用户防止返回NULL值,例如:

[代码 P271 第二段]

现在我们假设这个API的未来版本,你意识到可以让用户更容易地使用API,为他们提供代码的稳定性。当不含子节点时,你要返回一个有效的Node*指针来代替NULL值。这可以消除对特殊的NULL的检查,因此用户现在可以这样编写代码:

[代码 P272 第一段]

这个修改并没有包括对GetChildren()方法的函数签名的修改。然而,这种修改仍然可以通过更新方法的文档来传达给用户。例如:

[代码 P272 第二段]

9.1.4 哪些需要文档化

你应该为API中的每个公共元素编写文档:类、函数、枚举、常量和类型定义。如果用户可以访问它,你就应该告诉他们它是什么和如何使用它(Bloch,2008)。特别地,你应该关注所有帮助用户如何有效使用类和方法的重要部分。这包括描述方法的行为和它们与API其它部分的关系。

指定参数和返回值的单位是良好文档的另一个特别重要的构成要素。当这么做时,你应该考虑你是否也需要定义值的类型和精度。有个特别相关的例子是计时器类每n秒就调用一次用户的代码。你可以为超时的单位秒编写文档,不过用户还有理由这样发问:

q时间是指现实世界(挂钟)的时间还是处理时间?

q计时器的精确度如何?

qAPI的其它操作会影响精确度吗?如代码块和计时器。

q计时器会随着时间堆积或者总是相对于起始时间触发?

定义这些额外的特性是为了帮助用户判断类是否适合他们的任务。例如,有个空闲的任务只是大约每秒执行一些琐碎的工作,并不关心是1.0秒还是1.1秒后唤醒。然而,在同样情况下,模拟时钟每调用一次都会移动它的秒针,这样很快就会显示错误的时间。

为了帮助你知道API哪些需要进行文档化,当你为类和函数生成文档时,下面提供了一个问题列表。

q类表示的抽象是什么?

q有效的输入是什么?例如,可以传入一个NULL指针吗?

q有效的返回值是什么?例如,什么时候返回true或false?

q它检测什么样的错误条件?例如,它检测文件是否存在吗?

q它有任何的前置条件、后置条件或副作用吗?

q是否有任何未定义的行为?例如,sqrt(-1.0)。

q它有抛出异常吗?

q它是线程安全的吗?

q单位或参数是什么样的?

q空间或时间复杂度是什么样的?例如,O(log n)或O(n2)。

q内存所有权(memory ownership)模型是什么样的?例如,调用者是否负责删除任何返回对象?

q虚方法是否有调用类中的其它的方法?也就是说,当重载派生类中的方法时,用户应该调用什么方法?

q是否有相关的函数应该交叉引用(cross-referenced)?

q这个特性是在API的哪个版本中添加进来的?

q这个方法是废弃的吗?如果是的话,那替代的是什么样的?

q这个特性中是否有已知的错误?

q你是否希望分享任何在将来的改进?

q你可以提供任何代码样例吗?

q文档是否有添加额外的值或见解?例如,在你不知道有个函数叫SetColor()的情况下,注释“sets the color”并没有告诉你多少事情

除了上面列出的API行为,Diomidis Spinellis罗列出一个简短的、所有良好的编码文档需要努力实现的列表(Spinellis, 2010)。他建议文档应该是这样的:

(1).完整的

(2).一致的

(3).轻松地访问

(4).非重复的

提示

为API的每个公共元素编写文档。

邀请其他人来检查你的文档也是一种很好的做法。做为编写代码的开发人员,你往往过于接近问题空间(problem space),可能会假定普通用户所不具备的知识。正如Michi Henning所说的,API的开发人员会“被实现影响心智”。因此,有了开发同伴、QA工程师或技术写手来查看你的API文档,可以从他们处获得新鲜的反馈。

9.2 文档类型

你可以为API提供各种文档类型。这些类型并非全部适用于你的特殊项目,但是值得知道用户所期待的各种选择。

还值得注意的是很多软件产品提供的文档也接受来自用户的贡献。这可以是通过维基(wiki)的使用通过用户可以创建和编辑文档,比如大型的魔兽世界的维基:http://www.wowwiki

.com/或是支持用户增加评论的Web页面,如PHP指南:http://www.php.net/manual/。这显然是一个非常强大的功能,可以构建一个关于API的社区,并能够让大家一起创建文档,这是你自己一个人所无法做到的。即使你无法采用这种由用户贡献的自动化系统,那你也应该提供一种方式让用户能够给你发送反馈,以便可以手动地把这些建议添加到文档中。

你(或你的用户)编写的大部分文档都是针对API的特定版本的。有个很容易陷入的陷阱是基于维基的解决方案的内容最终是涉及到API的一系列版本。这是值得考虑的:你如何创建一种可以区分不同版本的文档,或许是创建每个主版本文档的完整独立副本。这将允许使用旧版本的用户能够查看特定的发布版本,同时你也可以专注于保持当前版本的API的最新文档。

图9.1显示的是Qt引用文档索引页的一张截图。这是一个不错的例子,涵盖了大量编写良好的文档,演示了等下我会讲到的各种类型。值得注意的是所有的Qt文档是随API分成不同的版本。

9.2.1 自动化API文档

API文档中最明显的类型就是从源码注释中自动生成的类型。这允许你让类和方法的文档紧挨着它们的头文件中的声明。这可以使你最可能在修改API时就立马修改文档。这很容易为新的发布生成最新的文档,并且这种文档可以拥有API中相关的函数间的交叉引用。

提示

使用自动化文档工具来从头文件注释中抽取API文档。

或许这是显而易见的,但是所有的用户文档注释都应该出现在公共头文件中,而不是在.cpp文件中,以便于更容易被用户发现。这对于闭源的API是特别重要的,用户是无法访问.cpp文件的。

这些源码注释通常是由开发人员维护的。然而,如果你有一个专业的技术写手来负责评论的风格、语法和一致性检查,那么你的API文档的质量肯定得以提升很多。而且,他们有开阔的视野,这些技术写手常常可以提供良好的反馈和建议更多的信息来包含突出的或改进过的例子。一些技术写手或许不喜欢直接修改源码,不过在那种情况下他们可以很容易地标记文档的一个硬拷贝hardcopy[l1]),让开发人员去执行真正的代码修改。

[图 P275 第一张]

图9.1

Qt引用文档Web页面的截图:http://doc.qt.nokia.com/。诺基亚公司版权所有?2010。

很多工具都可以用来从C++源码中的注释创建API文档。这些通常可以生成各种格式的输出,如HTML、PDF和LaTeX。下面的列表提供了几种这些工具的链接:

qAutoDuck: http://helpmaster.info/hlp-developmentaids-autoduck.htm

qCcDoc: http://ccdoc.sourceforge.net/

qCppDoc: http://www.cppdoc.com/

qDoc-O-Matic: http://www.doc-o-matic.com/

qDoc++: http://docpp.sourceforge.net/

qDoxygen: http://www.doxygen.org/

qGenHelp: http://www.frasersoft.net/

qHeaderDoc: http://developer.apple.com/darwin/projects/headerdoc/

qHelp Generator: http://www.helpgenerator.com/

qKDOC: http://sirtaj.net/projects/kdoc/

qROBODoc: http://www.xs4all.nl/~rfsber/Robo/robodoc.html

qTwinText: http://www.ptlogica.com/

9.2.2 文档概述

除了自动生成API文档,你应该手动编写API的高级信息。这通常包括API是做什么的概述和用户为什么应该要关注它。在一个大型的组织中,这个任务通常是由技术写手负责的。它甚至还本地化成几种不同的语言。概述文档通常包括以下几个主题:

qAPI的高级别组织视图:解决什么样的问题和API是如何工作的。如果合适的话,使用图表是非常好的做法。

q关键概念、特性和术语。

q使用API的系统需求

q如何下载、安装和配置软件。

q如何提交反馈或报告错误。

q关于API生命周期所在阶段的声明,如:预发布版、维护版、稳定版或废弃版(请参见版本章节)。

9.2.3 实例和指南

实例是非常重要的。API的概述往往是级别太高了,以至于用户并不清楚如何使用API,即使是你给每个类和方法都编写了文档,这不能马上让用户知道如何利用API来实现任务。添加一些小例子可以极大地增加文档的实用性。请记住API是供其他开发人员使用的软件。他们只是想知道如何通过你的接口来完成他们的工作。这可以成为文档中入门指南章节的一部分,可以包括如下内容:

q简短的例子:这些应该是最小化且易于理解的代码片段,用于演示API的关键功能。它们往往不是可以直接编译的代码,只是关注API调用而不包含所有的模板代码。

q演示样例:这些是完整的真实例子,演示的是API是如何用来执行更为复杂和实际的任务。这些应该是易于重用,以便用户可以做为他们自己项目的一个起点。你应该为这些给出源代码。

q教程和演练:教程是用来说明解决问题的步骤,而不是简单地给出最终的结果。这可以用来构建一个复杂的例子和解决特定的API功能,让你可以逐步往样例中添加更多的调用。

q用户贡献:用户可以成为例子的最大来源者。应该鼓励用户给你发送样例代码,这个就可以添加到你的样例合集中,或许可以放在一个特定的contrib目录中,以便可以明显地让人知道这个不是由你支持的。

q常见的问答:一组常见的问题和答案对于文档来说是非常有帮助的。这让用户可以迅速且容易地知道API是否满足他们的需要,如何避免常见的错误或如何解决典型的问题。

编写文档会迫使你从用户的角度来思考问题。因此,它可以帮助你识别API的缺点或可以简化的地方。这是一种很好的做法:在早期你就开始编写高级文档的框架和一些初始的例子,这样可以强迫你整体设计和库的用例做出思考

9.2.4 发布说明

在第一个发布版本之后的每个新的发布都应该包含发布说明。这些是用来告诉用户自从上一个发布后的修改内容。发布说明通常是简洁的文档,包括如下的内容:

q发布的概述,包括对新增内容的描述或发布的重要内容。例如,只包括错误修复。

q哪里可以找到发布链接。

q从上一个发布任何的源码或二进制不相容的鉴定。

q发布中修复错误的列表。

q废弃或移除的特性列表。

qAPI中任何改动的迁移建议。例如:如何使用发布中的更新脚本。

q任何已知的问题:包括本发布中引入的或从上个版本起仍然存在的。

q解决已知问题的建议。

q用户提交的反馈和错误报告的信息。

9.2.5 许可信息

你应该总是要指定你要发布的API下的许可。这让你的用户能够知道你给予他们的授权和他们的义务是什么。一般说来,你可以使用下面两大类的许可。

(1).所有权:软件的所有权仍然属于发行者你并且可以禁止软件的某些用途。通常这些软件都是闭源的并需要许可费(但是也有一个反例,Qt商业许可包括源代码)。所有权许可证可能限制一些行为,如逆向工程、用户或电脑的数量、开发人员的数量或多个用户同时使用软件。

(2).*和开源的软件:这些软件可以使用、学习和毫无限制的修改。还可以经过修改或未修改再进行复制或重新发布,这都没有限制或限制很少。术语“*”是指软件使用版权上的,不是指它的价格。软件遵循这里所说的*、开源软件,通常简写为FOSS或FLOSS(Free, Libre, Open Source Software)。

有两个主要的机构负责批准FLOSS许可。*软件协会(Free Software Foundation,FSF)是由Richard Stallman在1985年建立的,批准的许可证遵循*软件定义(Free Software Definition)和开源倡议(Open Source Initiative,OSI),由Bruce Perens和Eric S. Raymond在1998创立的,批准符合开源定义的许可证。

提示

为API指定许可证是很重要的。

FLOSS许可也有两种基本类型:

著佐权(copyleft):除了允许使用者*使用、散布、修改之外,著佐权条款更要求使用者修改后的衍生作品必须要以同等的授权方式发布。有个弱著佐权的子类别,这常常应用于软件库,允许用户发布代码和该库的链接,而无需发布该库的著佐权许可证下的产品。

*许可(permissive):允许发布修改和未修改的软件拷贝,允许衍生作品采用比原始许可证更有限制性的许可方式。这意味着你可以提供一个开源库,但是用户并不需要让所有的衍生发布也是开源的。

下面的表格提供了一些常用的FLOSS软件许可列表,并给出了它们对用户影响的概述。所有的这些许可都是由FSF和OSI批准的。这个列表显然是不完整的,只是一个粗略的指南。如果你有法律顾问的话,你应该咨询他为你的产品选择一个最好的许可证。要想知道更多的细节,请访问:http://www.fsf.org/ 和http://www.opensource.org/

[图 P278 排版 开始]

许可证名           简要描述

没有许可证          如果你未指定一个许可证,那么你的用户将没有合法的API使用权,除非它们直接向你获得许可(你就是版权所有人)

GNU GPL许可证   通用公共许可证(General Public License,GPL)是一个著佐权许可证,这意味着所有的衍生作品都要采用GPL发布。开源的GPL库不能做为独占所有权的产品。诺基亚的Qt库起初的发布既有商业也有GPL许可。

GNU LGPL许可证   宽通用公共许可证(Lesser General Public License,LGPL)是一种弱的著佐权许可证,允许开源API采用二进制链接到一个私有代码。衍生作品可以在某种特定的条件下发布,如提供修改或未修改的LGPL库源代码。GTK+是在LGPL下的许可。诺基亚在Qt 4.5版本中添加了LGPL许可。

BSD许可证   BSD许可证是一个简单的*许可证,包括一个命名的所有者或组织合法的免责声明。通常是使用BSD修改过的版本,没有“广告条款”。所有权代码链接到BSD代码就可*发布。Google的Chrome浏览器就是在BSD许可证下发布的。Zend框架和libtiff库也是使用BSD。

MIT/X11许可证   这是和BSD许可证是一脉相承的简单*许可证。所有权代码在链接MIT许可证代码后就可以*发布。MIT许可证软件包括Expat、Mono、Ruby on Rails、Lua 5.0 onwards和X11 Window系统。

Mozilla公共许可证   这是一个弱著佐权许可证,允许开源库用来构建所有权软件。在MPL许可证下的任何代码修改都必须重新发布。Mozilla软件产品火狐和雷鸟是根据MPL开发的。

Apache许可证   这是另一种*许可证,允许所有权软件发布基于Apache许可证上的代码。ApacheWeb服务很明显使用的是Apache许可。Google也使用很多它的产品,如Android和Google Web工具包。

[图P278 排版 结束]

这里我们讨论了你的API可能用到的各种文档类型。图9.2给出另一个软件项目的文档概述的例子:Apache HTTP Server。要注意的是这份文档也本地化成多种不同的语言。

[图 P279 第一张]

图9.2

Apache HTTP Server文档Web页面的截图,http://httpd.apache.org/docs/。版权 @ 2009Apache软件协会。

9.3 文档可用性

有几个研究小组研究API文档的可用性(Jeong等2009;Robillard,2009;Stylos 和Myers, 2008)。这项工作涉及执行可用性研究,研究用户如何浏览文档并执行所专注的任务。目的是指导API设计者更好地编写他们的接口文档。下面的列表给出这些研究成果的相关总结:

q索引页:提供一个目录索引页,方便跳转到每个独立的文档元素处。这可以让用户清楚整个文档集的概念地图。此外,每个跳转的起始点都应该提供一些提示信息,包括它所包含的内容和它所针对的用户群(开发人员、经理、法律顾问等)。

q一致的外观和体验:API文档通常是由几种不同的元素组成的:有些是自动生成的、有些是手动编写的、还有一些是用户提交的。你应该为所有的这些页面使用一致和唯一的风格,以便用户在浏览文档内容时或从其它网站导航到此处时都能清楚地知道。

q代码样例:往往有很多文档需要用户阅读。这个可以想想海量的微软开发人员网络库(Microsoft Developer Network library)。提供代码样例片段和可运行的演示能够很好地帮助用户快速找到和理解他们在代码中使用API时所需要的信息。

q图表:清晰和简明的图表在说明高级概念时是很有用的,特别是对那些只想快速扫描文档的用户。你应该尽可能使用熟悉的图表格式,如:UML或实体关系图。

q搜索:一个好用的搜索工具是十分重要的,这可以让用户尽快找到他们所需要的信息。文档的所有部分都应该是可供搜索的,既包括自动生成的API文档,也包括手动编写的文本。

q面包末(Breadcrumbs):这是用来辅助定位的,让用户知道当前位置所处的文档层级,并可以很方便地返回上级。术语“面包末”用来描述:使用一个分隔符号来配合一系列页面显示当前位置的通用技术。例如:“索引 > 概述 > 概念”。此外,它可以用来让用户很容易地返回各个更高一级的页面。

q术语:应该保持重要术语定义和使用的一致性。然而,你应该避免使用太古怪或深奥的术语,因为这是没有必要的,会混淆视听和困扰用户。

API的可用性描述的是学习使用API的难易程度:一个难以使用的API就很可能难以学习。为此目的,Martin Robillard研究的问题是什么会导致不易学会使用一个API。他发现学习一个API的最大障碍就是其所支持的文档和资源。例如,他列出了API学习的一些障碍,如下所示(Robillard, 2009):

q缺乏代码样例:提供的样例不充分或不适当。

q内容不完全:文档缺失或给得不合适。

q缺乏对任务的关注:没有提供如何完成特定任务的细节。

q设计的基本原理不合规格:提供的高级架构和设计基本原理不充分或不适当。

q数据格式不恰当:文档中未提供所需的格式。

除了这些要点,很多研究还关注这个事实:开发人员常常不愿意认真阅读API文档(Zhong 等, 2009)。这种建议认为有时提供更多的文档其实是不好的,因为用户可能会因为大量的文档而错过要点和警告。通过使用高级别教程和样例代码可以帮助解决这个问题。在类和方法之间的交叉引用可以引导用户发现他们原先不知道的特性。一些研究人员还建议使用“字云”(译者注:word clouds,特殊外观的字体,比较醒目。),或者其它可变字体技术来高亮重要或通用的类(Stylos等, 2009)。

9.4 使用文档工具

Doxygen可以根据你在源码中编写的注释来自动生成各种格式的API文档。它支持很多编程语言,包括:C、C++、Objective-C、Java、Python、Fortran、C#和PHP,它可以生成多种格式的输出,包括:HTML、LaTeX、PDF、XML、RTF和 UNIX等。

Doxygen是开源的(使用GNU许可证发布),有提供各个平台的二进制文件,包括:Windows、Linux和Mac。它是从1997年来开始开发的,现在已经成为一个成熟和强大的系统。因为它是一个很流行的工具且有很多项目使用它,为了提供一个具有实践意义的资源,我将在本章的剩余部分着重讲解你该在自己的项目中如何使用Doxygen的基础内容。然而,类似的准则也适用于前面列举过的自动化生成API文档的其它工具。Doxygen的网站是http://www.doxygen.org/

9.4.1 配置文件

Doxygen可进行高度配置的,最近的版本有200多个配置选项。所有的这些配置选项都可以通过Doxygen配置文件来指定。这是一个ASCII文本文件,采用简单的“=”号键值对的格式。在运行Doxygen时你可以添加一个-g命令行参数来生成默认的配置文件。

接着,你可以编辑这个配置文件来指定关于你源码的一些细节,改变在某些情况下的默认。下面是我找出来的常常需要修改的一些条目:

[代码 P281 第一段]

要执行这些初始化设置,你只需在源码目录中运行Doxygen,它就会创建一个包含API文档的子目录(假设你已经设置了前面给过的HTML_OUTPUT)。下面的章节将介绍如何往代码中添加Doxygen可以拾取并添加到生成文档中的注释。

9.4.2 注释风格和命令

你必须使用特殊的注释风格来通知Doxygen生成你想要的API文档注释文本。Doxygen支持多种注释风格,如下所示:

[代码 P282 第二段]

采用哪种风格的注释完全看个人喜好:它们的功能都是一样的。我将在本章的剩余部分采用三斜线(///)风格。

在注释中,你可以指定很多命令,Doxygen可以从中提取出相应的信息。这种信息常常是用来格式化文档的。下面的列表提供的是最常用的命令摘要。要查看完整的列表请doxygen手册。

[代码 P282 第三段]

除了这些命令,Doxygen还支持各种修改下一个单词样式的格式化命令。这些包括\b(粗体)、\c(打字机字体)、和\e(斜体)。你还可以使用\n来强制换行,\\是用来输入一个反斜线字符,\@输入“@”符号。

9.4.3 API注释

Doxygen允许你使用\mainpage注释指定整个API的文档概述。这让你能够为API提供高级别的描述,包括主要类的[l2]故障。这种描述出现在Doxygen生成的文档的前面页码中。通常是把这些注释存储在一个独立的文件中,如overview.dox(这需要你更新Doxygen配置文件的FILE_PATTERNS条目,包含*.dox)。

概述文档中的文本内容可能太长了,可以适当调整版面并分割成几个部分。在这种情况下,你可以使用\section和\subsection命令来引入这种结构。你甚至可以为API的某些部分创建独立的页面来包含更多的细节描述。这可以使用\page命令来完成。

还有,你可能觉得为API定义几组行为是有用的,以便你把API中的各种类分成不同的类别。例如,你可以为类创建分组或关于文件处理的分组、容器类、日志、版本等。声明一个分组是通过\defgroup实现的,使用\ingroup来为那个分组添加任何特定的元素。

把这些特性都整合起来,下面的注释是为API提供的文档概述,分成三个部分和为了获取更多的细节描述而交叉引用了两处额外的页面。页面包含一个链接,显示所有已标记为一个给定的分组的一部分的API元素。

[代码 P283 第二段]

Doxygen为上述注释(使用默认的样式列表)输出的HTML结果显示在图9.3中。

9.4.4 文件注释

你可以把注释放置在每个头文件的顶部,为整个模块提供文档。下面给出一个适合每个文件注释的样例模板:

[代码 P284 第二段]

[图 P285 第一张]

图9.3

Doxygen为\mainpage例子 的HTML输出

[代码 P285 第一段]

如果文件中包含你要添加到一个分组中的已经定义过的功能,那么你也可以把\ingroup命令添加到这个注释中。

9.4.5 类注释

头文件中的每个类也可以包含一个用于描述类的整体目的注释。除了这里给定的样例模板,如果类属于你定义过的一个分组,那么你可以考虑包含\ingroup命令;如果类已经被不赞成使用了,就可以使用\deprecated命令;如果你要提供一些样例代码,可以使用\code ... \endcode命令。

[代码 P285 第二段]

9.4.6 方法注释

你可以为独立的方法提供文档,为每个参数(它们是in、out或in/out参数)和名称提供细节,还包括对返回值的描述。和类样例模板一样,如果合适的话,你也可以考虑添加\ingroup或\deprecated。

[代码 P286 第二段]

如果类中有方法可以分成一个或多个逻辑分组,那么你可以标注给Doxygen,以便它可以把相关的方法分组成一个命名过的小单元。这可以被用来提供类成员的更合适的顺序来代替Doxygen的默认行为。下面的代码片段演示了这两种成员分组的定义。

[代码 P286 第三段]

9.4.7 枚举注释

Doxygen也允许你为枚举提供注释,包括为枚举中每个独立的值提供文档。后者可以使用Doxygen的<注释语法来实现,连接上一个元素的文档来代替下一个元素。

[代码 P287 第一段]

9.4.8 头文件的样例文档

把之前讲过的都全部整合起来,这里有一个使用Doxygen风格注释来描述的文件、类和每个方法的整个头文件。这个例子有在本书配套的源代码中,还包括Doxygen为这个文件生成的HTML输出。

[代码 P287 第二段]

 

 hardcopy是美国英语中对原件的说法 翻译成硬拷贝不好理解。

 as well as provide a breakdown of the major classes.

Power by YOZOSOFT

C++ API 设计 14 第九章 文档,布布扣,bubuko.com

C++ API 设计 14 第九章 文档

上一篇:C++中程序化操作虚函数列表实验


下一篇:C++ API 设计 16 第十一章 脚本