如果API要可用,就必须对其进行文档化。传统上,API文档是手工生成的,保持与代码的同步是一件苦差事。Java编程环境使用Javadoc实用程序简化了这一任务。Javadoc使用特殊格式的文档注释(通常称为doc注释)从源代码自动生成API文档。
虽然doc注释约定不是正式语言的一部分,但它们实际上构成了每个Java程序员都应该知道的API。这些约定在如何编写Doc注释web页面[Javadocguide]中进行了描述。虽然自Java 4发布以来这个页面没有更新,但它仍然是一个非常宝贵的资源。在Java 9中添加了一个重要的doc标签,{@index};Java 8中的一个,{@implSpec};Java 5中有两个,{@literal}和{@code}。上述web页面中缺少这些标记,但将在本项目中讨论。
要正确地记录API,必须在每个导出的类、接口、构造函数、方法和字段声明之前加上doc注释。如果一个类是可序列化的,还应该记录它的序列化形式(item87 )。在缺少doc注释的情况下,Javadoc所能做的最好的事情就是将声明复制为受影响API元素的唯一文档。使用缺少文档注释的API是令人沮丧和容易出错的。公共类不应该使用默认构造函数,因为无法为它们提供doc注释。要编写可维护的代码,还应该为大多数未导出的类、接口、构造函数、方法和字段编写doc注释,尽管这些注释不需要像导出API元素那样完整。
方法的doc注释应该简洁地描述方法与其客户机之间的契约。除为继承而设计的类中的方法(item19 )外,契约应该说明方法做了什么,而不是它如何做它的工作。doc注释应该枚举方法的所有前置条件(这些条件必须为真,以便客户机调用它们)和后置条件(这些条件是在调用成功完成后才为真)。通常,先决条件是通过@throw标签的未检查异常隐式描述的;每个未检查异常对应于一个先决条件违反。此外,可以在前置条件及其@param标记中指定受影响的参数。
除了前置条件和后置条件外,方法还应该记录任何副作用。副作用是系统状态的一个可观察到的变化,它不是实现后置条件所明显需要的。例如,如果一个方法启动了一个后台线程,文档应该注意它。
要完整地描述方法的契约,doc注释应该为每个参数都有一个@param标记,一个@return标记(除非方法有一个void返回类型),以及一个@throw标记(对于方法抛出的每个异常,无论是否是可检查的)( item74 )。如果@return标记中的文本与方法的描述相同,则可以忽略它,这取决于您所遵循的编码标准。
按照惯例,@param标记或@return标记后面的文本应该是一个名词短语,描述参数或返回值所表示的值。算术表达式很少用来代替名词短语;有关示例,请参见BigInteger。@throw标记后面的文本应该包含单词“if”,后跟描述引发异常的条件的子句。按照惯例,@param、@return或@throw标记后面的短语或子句不以句号结束。:以下的doc注释说明了所有这些约定:
请注意在这个doc注释中使用HTML标记(<p> 和<i>). 。Javadoc实用程序将doc注释转换为HTML, doc注释中的任意HTML元素最终会出现在生成的HTML文档中。有时候,程序员甚至会在他们的doc注释中嵌入HTML表,尽管这种情况很少见。
还要注意在@throw子句中的代码片段周围使用Javadoc {@code}标记。这个标记有两个目的:它使代码片段以代码字体呈现,并且它抑制了代码片段中HTML标记和嵌套Javadoc标记的处理。后一个属性允许我们在代码片段中使用小于号(<),即使它是一个HTML元字符。要在doc注释中包含多行代码示例,使用Javadoc {@code}标记封装在HTML<pre>标记中。换句话说,在代码示例之前加上字符<pre>{@code and follow it with }</pre>.这保护线在代码中中断,并消除了转义HTML元字符的需要,但不需要转义at符号(@),如果代码示例使用注释,则必须转义@。
最后,请注意doc注释中使用的单词“this list”。按照惯例,“this”指的是在doc注释中为实例方法使用方法时调用方法的对象。
正如第15项中提到的,当您为继承设计一个类时,您必须记录它的自用模式,以便程序员知道覆盖它的方法的语义。这些自用模式应该使用在Java 8中添加的@implSpec标记来记录。回想一下,普通的doc注释描述了方法与其客户机之间的契约;相反,@implSpec注释描述了方法与其子类之间的契约,允许子类依赖于实现行为(如果它们继承了方法或通过super调用方法)。下面是它在实践中的样子:
Java 9开始,Javadoc实用程序仍然忽略@implSpec标记,除非您通过命令行开关-标记“implSpec:a:Implementation Requirements:”。希望在后续的版本中可以纠正这个错误。
不要忘记,您必须采取特殊的操作来生成包含HTML元字符的文档,比如小于号(<)、大于号(>)和与号(&)。将这些字符放到文档中最好的方法是用{@literal}标记包围它们,这将抑制HTML标记和嵌套Javadoc标记的处理。它类似于{@code}标记,只是它不以代码字体呈现文本。例如,这个Javadoc片段:
生成文档:“如果|r| < 1,则几何级数收敛。”使用相同的结果文档,{@literal}标记可以放在小于号周围,而不是整个不等式周围,但是doc注释在源代码中可读性较差。:这说明了doc注释在源代码和生成的文档中都应该是可读的。如果不能同时实现这两个目标,生成的文档的可读性将超过源代码的可读性。
每个doc注释的第一个“句子”(定义如下)成为注释所属元素的摘要描述。例如,255页doc注释中的摘要描述是“返回列表中指定位置的元素”。摘要描述必须独立地描述它总结的元素的功能。为了避免混淆,类或接口中的任何两个成员或构造函数都不应具有相同的摘要描述。特别注意重载,对于重载,通常使用相同的第一句话是很自然的(但在doc注释中是不可接受的)。
如果预期的摘要描述包含句点,请小心,因为句点可能会提前终止描述。例如,以短语开头的doc注释"“A college degree, such as B.S., M.S. or Ph.D.”将在总结描述的结果““A college degree, such as B.S., M.S.” ”问题是摘要描述在第一个句点结束,然后是空格、制表符或行结束符(或第一个块标记)[Javadoc-ref]。这里是缩写“M.S.”中的第二个句号后面跟着一个空格。最好的解决方案是用{@literal}标记来包围违规的句点和任何相关的文本,这样源代码中的句点后面就不会有空格了:
说摘要描述是doc注释中的第一句话有点误导人。按照惯例,它很少应该是一个完整的句子。对于方法和构造函数,摘要描述应该是一个动词短语(包括任何对象),描述方法执行的操作。例如:
如这些例子所示,使用第三人称陈述句时态(“return the number”)而不是第二人称祈使句(“return the number”)。
对于类、接口和字段,摘要描述应该是一个名词短语,描述由类或接口的实例或字段本身表示的事物。例如:
在Java 9中,客户端索引被添加到Javadoc生成的HTML中。这个索引以页面右上角的搜索框的形式出现,它简化了导航大型API文档集的任务。当您在框中键入时,您将得到一个匹配页面的下拉菜单。API元素(如类、方法和字段)是自动索引的。有时,您可能希望索引对您的API很重要的其他术语。为此添加了{@index}标记。对doc注释中出现的术语进行索引,就像将其包装在这个标签中一样简单,如下面的片段所示:
- This method complies with the {@index IEEE 754} standard.
泛型、枚举和注释在doc注释中需要特别注意。当记录泛型类型或方法时,请确保记录所有类型参数:
在记录枚举类型时,一定要记录常量、类型和任何公共方法。注意,如果文档很短,你可以把整个文档注释放在一行:
在记录注释类型时,一定要记录任何成员和类型本身。用名词短语记录成员,就好像它们是字段一样。对于类型的摘要描述,请使用动词短语,它表示当程序元素具有此类注释时的含义:
包级别的doc注释应该放在名为packageinfo.java的文件中。除了这些注释之外,package-info.java必须包含一个包声明,并且可能包含关于这个声明的注释。类似地,如果您选择使用模块系统( item15),模块级别的注释应该放在模块-info.java文件中。
在文档中经常忽略的api的两个方面是线程安全性和可序列化性。无论类或静态方法是否线程安全,都应该记录它的线程安全级别,如项目82所述。如果一个类是可序列化的,您应该记录它的序列化形式,如第87项中所述。
Javadoc具有“继承”方法注释的能力。如果API元素没有doc注释,Javadoc将搜索最特定的适用doc注释,优先选择接口而不是超类。搜索算法的详细信息可以在Javadoc参考指南[Javadoc-ref]中找到。您还可以使用{@inheritDoc}标记从超类型继承部分doc注释。:这意味着类可以重用它们实现的接口中的doc注释,而不是复制这些注释。这个工具有潜力减少维护多个几乎相同的doc注释集的负担,但是它使用起来很棘手,并且有一些限制。这些细节超出了这本书的范围。
关于文档注释,应该添加一个警告。虽然有必要为所有导出的API元素提供文档注释,但这并不总是足够的。对于由多个相互关联的类组成的复杂api,通常需要用描述API总体架构的外部文档来补充文档注释。如果存在这样的文档,相关的类或包文档注释应该包含到它的链接。
Javadoc会自动检查是否符合本项目中的许多建议。在Java 7中,需要命令行开关-Xdoclint来获得这种行为。在Java 8和Java 9中,默认情况下启用了check。诸如checkstyle之类的IDE插件在检查是否符合这些建议方面走得更远[Burn01]。还可以通过HTML有效性检查器运行Javadoc生成的HTML文件来降低doc注释中出现错误的可能性。这将检测HTML标记的许多不正确用法。有几个这样的检查器可供下载,您可以使用W3C标记验证服务[W3C-validator]在web上验证HTML。在验证生成的HTML时,请记住,从Java 9开始,Javadoc就能够生成HTML5和HTML 4.01,尽管默认情况下它仍然生成HTML 4.01。如果希望Javadoc生成HTML5,请使用-html5命令行开关。
本项目中描述的约定涵盖了基本内容。尽管撰写本文时已经有15年的历史,但编写doc注释的最终指南仍然是如何编写doc注释[Javadoc-guide]。
如果您遵循本项目中的指导原则,生成的文档应该提供对API的清晰描述。然而,唯一确定的方法是阅读Javadoc实用程序生成的web页面。对于其他人将使用的每个API,都值得这样做。正如测试程序几乎不可避免地会导致对代码的一些更改一样,阅读文档通常也会导致对doc注释的一些较小更改。
总之,文档注释是记录API的最佳、最有效的方法。应该认为所有导出的API元素都必须使用它们。采用符合标准约定的一致样式。请记住,在文档注释中允许任意HTML,并且必须转义HTML元字符。
本文写于2019.7.18,历时1天