# 56. 为所有已公开的 API 元素编写文档注释 如果 API 要可用,就必须对其进行文档化。传统上,API 文档是手工生成的,保持文档与代码的同步是一件苦差事。Java 编程环境使用 `Javadoc` 实用程序简化了这一任务。`Javadoc` 使用特殊格式的文档注释 (通常称为 doc 注释),从源代码自动生成 API 文档。 虽然文档注释约定不是 Java 语言的正式一部分,但它们构成了每个 Java 程序员都应该知道的事实上的 API。「如何编写文档注释(How to Write Doc Comments)」的网页[Javadoc-guide] 中介绍了这些约定。 虽然自 Java 4 发布以来该页面尚未更新,但它仍然是一个非常宝贵的资源。 Java 9 中添加了一个重要的文档标签,`{@ index}`; Java 8 中有一个,`{@implSpec}`;Java 5 中有两个,`{@literal}` 和 `{@code}`。 上述网页中缺少这些标签的介绍,但在此条目中进行讨论。 **要正确地记录 API,必须在每个导出的类、接口、构造方法、方法和属性声明之前加上文档注释**。如果一个类是可序列化的,还应该记录它的序列化形式 (详见第 87 条)。在没有文档注释的情况下,Javadoc 可以做的最好的事情是将声明重现为受影响的 API 元素的唯一文档。使用缺少文档注释的 API 是令人沮丧和容易出错的。公共类不应该使用默认构造方法,因为无法为它们提供文档注释。要编写可维护的代码,还应该为大多数未导出的类、接口、构造方法、方法和属性编写文档注释,尽管这些注释不需要像导出 API 元素那样完整。 方法的文档注释应该简洁地描述方法与其客户端之间的契约。除了为继承而设计的类中的方法 (详见第 19 条)之外,契约应该说明方法做什么,而不是它如何工作的。文档注释应该列举方法的所有前置条件 (这些条件必须为真,以便客户端调用它们),以及后置条件(这些条件是在调用成功完成后才为真)。通常,对于未检查的异常,前置条件由 `@throw` 标签隐式地描述;每个未检查异常对应于一个先决条件违反( precondition violation)。此外,可以在受影响的参数的 `@param` 标签中指定前置条件。 除了前置条件和后置条件之外,方法还应在文档中记录它的副作用(side effort)。 副作用是系统状态的可观察到的变化,这对于实现后置条件而言显然不是必需的。 例如,如果方法启动后台线程,则文档应记录它。 完整地描述方法的契约,文档注释应该为每个参数都有一个 `@param` 标签,一个 `@return` 标签 (除非方法有 void 返回类型),以及一个 `@throw` 标签(无论是检查异常还是非检查异常)(详见第 74 条)。如果 `@return` 标签中的文本与方法的描述相同,则可以忽略它,这取决于你所遵循的编码标准。 按照惯例,`@param` 或 `@retur` 标签后面的文本应该是一个名词短语,描述参数或返回值所表示的值。 很少使用算术表达式代替名词短语; 请参阅 `BigInteger` 的示例。`@throw` 标签后面的文本应该包含单词「if」,后面跟着一个描述抛出异常的条件的子句。按照惯例,`@param` 、`@return` 或 `@throw` 标签后面的短语或子句不以句号结束。以下的文档注释说明了所有这些约定: ```java /** * Returns the element at the specified position in this list. * *
This method is not guaranteed to run in constant * time. In some implementations it may run in time proportional * to the element position. * * @param index index of element to return; must be * non-negative and less than the size of this list * @return the element at the specified position in this list * @throws IndexOutOfBoundsException if the index is out of range * ({@code index < 0 || index >= this.size()}) */ E get(int index); ``` 请注意在此文档注释(`
`和``)中使用 HTML 标记。 Javadoc 实用工具将文档注释转换为 HTML,文档注释中的任意 HTML 元素最终都会生成 HTML 文档。 有时候,程序员甚至会在他们的文档注释中嵌入 HTML 表格,尽管这种情况很少见。
还要注意在`@throw`子句中的代码片段周围使用 Javadoc 的 `{@code}`标签。这个标签有两个目的:它使代码片段以代码字体形式呈现,并且它抑制了代码片段中 HTML 标记和嵌套 Javadoc 标记的处理。后一个属性允许我们在代码片段中使用小于号(<),即使它是一个 HTML 元字符。要在文档注释中包含多行代码示例,请使用包装在 HTML ``标记中的 Javadoc`{@code}`标签。换句话说,在代码示例前面加上字符`
{@code,然后在代码后面加上}
`。这保留了代码中的换行符,并消除了转义 HTML 元字符的需要,但不需要转义 at 符号(@),如果代码示例使用注释,则必须转义 at 符号(@)。
最后,请注意文档注释中使用的单词「this list」。按照惯例,「this」指的是在实例方法的文档注释中,指向方法调用所在的对象。
正如条目 15 中提到的,当你为继承设计一个类时,必须记录它的自用模式(self-use patterns),以便程序员知道重写它的方法的语义。这些自用模式应该使用在 Java 8 中添加的`@implSpec` 标签来文档记录。回想一下,普通的问问昂注释描述了方法与其客户端之间的契约;相反,`@implSpec` 注释描述了方法与其子类之间的契约,如果它继承了方法或通过 super 调用方法,那么允许子类依赖于实现行为。下面是实际应用中的实例:
```java
/**
* Returns true if this collection is empty.
*
* @implSpec
* This implementation returns {@code this.size() == 0}.
*
* @return true if this collection is empty
*/
public boolean isEmpty() { ... }
```
从 Java 9 开始,Javadoc 实用工具仍然忽略 @implSpec 标签,除非通过命令行开关:`-tag "implSpec:a:Implementation Requirements:"`。希望在后续的版本中可以修正这个错误。
不要忘记,你必须采取特殊操作来生成包含 HTML 元字符的文档,例如小于号(<),大于号(>)和 and 符号(&)。 将这些字符放入文档的最佳方法是使用`{@literal}`标签将它们包围起来,该标签禁止处理 HTML 标记和嵌套的 Javadoc 标记。 它就像`{@code}`标签一样,除了不会以代码字体呈现文本以外。 例如,这个 Javadoc 片段:
```java
* A geometric series converges if {@literal |r| < 1}.
```
它会生成文档:「A geometric series converges if |r| < 1.」。`{@literal}`标签可能只放在小于号的位置,而不是整个不等式,并且生成的文档是一样的,但是文档注释在源代码中的可读性较差。 这说明了**文档注释在源代码和生成的文档中都应该是可读的通用原则**。 如果无法实现这两者,则生成的文档的可读性要胜过在源代码中的可读性。
每个文档注释的第一个「句子」(如下定义)成为注释所在元素的概要描述。 例如,第 255 页上的文档注释中的概要描述为:「返回此列表中指定位置的元素」。概要描述必须独立描述其概述元素的功能。 为避免混淆,**类或接口中的两个成员或构造方法不应具有相同的概要描述**。 要特别注意重载方法,为此通常使用相同的第一句话是自然的(但在文档注释中是不可接受的)。
请小心,如果预期的概要描述包含句点,因为句点可能会提前终止描述。例如,以「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}`标签来包围不愉快的句点和任何相关的文本,这样源代码中的句点后面就不会有空格了:
```java
/**
* A college degree, such as B.S., {@literal M.S.} or Ph.D.
*/
public class Degree { ... }
```
说概要描述是文档注释中的第一句子,其实有点误导人。按照惯例,它很少应该是一个完整的句子。对于方法和构造方法,概要描述应该是一个动词短语 (包括任何对象),描述了该方法执行的操作。例如:
- `ArrayList(int initialCapacity)` —— 构造具有指定初始容量的空列表。
- `Collection.size()` —— 返回此集合中的元素个数。
如这些例子所示,使用第三人称陈述句时态 (“returns the number”)而不是第二人称祈使句(“return the number”)。
对于类,接口和属性,概要描述应该是描述由类或接口的实例或属性本身表示的事物的名词短语。 例如:
- `Instant` —— 时间线上的瞬时点。
- `Math.PI`—— 更加接近 pi 的 double 类型数值,即圆的周长与其直径之比。
在 Java 9 中,客户端索引被添加到 Javadoc 生成的 HTML 中。这个索引以页面右上角的搜索框的形式出现,它简化了导航大型 API 文档集的任务。当你在框中键入时,得到一个匹配页面的下拉菜单。API 元素 (如类、方法和属性) 是自动索引的。有时,可能希望索引对你的 API 很重要的其他术语。为此添加了`{@index}`标签。对文档注释中出现的术语进行索引,就像将其包装在这个标签中一样简单,如下面的片段所示:
```java
* This method complies with the {@index IEEE 754} standard.
```
泛型,枚举和注释需要特别注意文档注释。 **记录泛型类型或方法时,请务必记录所有类型参数**:
```java
/**
* An object that maps keys to values. A map cannot contain
* duplicate keys; each key can map to at most one value.
*
* (Remainder omitted)
*
* @param