ITEM 56: 为所有暴露在外的 API 撰写文档

ITEM 56: WRITE DOC COMMENTS FOR ALL EXPOSED API ELEMENTS
  如果 API 要可用,就必须对其进行文档化。传统上,API 文档是手动生成的,保持与代码的同步是一项繁琐的工作。Java 编程环境使用Javadoc 实用程序简化了这项任务。Javadoc 使用特殊格式的文档注释(通常称为doc注释)从源代码自动生成API文档。
  虽然doc注释约定不是正式语言的一部分,但它们构成了每个Java程序员都应该知道的实际API。这些约定在如何编写Doc注释web页面[Javadoc-guide]中进行了描述。虽然这个页面自 Java 4 发布以来一直没有更新,但它仍然是一个宝贵的资源。在Java 9 中添加了一个重要的 doc 标记,{@index};Java 8中添加了 {@implSpec};Java 5中的两个是 {@literal} 和 {@code}。这些标记在前面提到的web页面中是不存在的,但是在本项目中进行了讨论。
  要正确地记录 API,必须在每个导出的类、接口、构造函数、方法和字段声明之前加上doc注释。如果一个类是可序列化的,那么还应该记录它的序列化形式(item 87)。在缺少doc注释的情况下,Javadoc 所能做的最好的事情就是将声明作为受影响 API元素的唯一文档重新生成。使用缺少文档注释的 API 是令人沮丧和容易出错的。公共类不应该使用默认构造函数,因为无法为它们提供文档注释。要编写可维护的代码,您还应该为大多数未导出的类、接口、构造函数、方法和字段编写文档注释,尽管这些注释不必像导出的 API 元素那样全面。
  方法的文档注释应该简明扼要地描述方法与其客户之间的契约。除了为继承而设计的类中的方法(item 19)之外,契约应该说明方法做什么,而不是它如何工作。doc注释应该枚举方法的所有前置条件(为了让客户端调用它,这些条件必须为真)和后置条件(在调用成功完成后,这些条件必须为真)。通常,对于未检查的异常,前置条件由@throw 标记隐式地描述;每个未检查的异常对应于一个前置条件违背。此外,可以在它们的 @param 标记中指定前置条件和受影响的参数。
  除了前置条件和后置条件外,方法还应该记录任何副作用。一个副作用是系统状态的一个可观察到的变化,它不是实现后置条件所明显需要的。例如,如果一个方法启动了一个后台线程,文档应该注意它。
  要完整地描述一个方法的契约,doc 注释中的每个参数都应该有一个 @param 标记,一个 @return 标记(除非方法的返回类型为void),对于方法抛出的每个异常都应该有一个@throw 标记(无论是否选中)(item 74)。如果 @return 标记中的文本与方法的描述相同,则可以忽略它,这取决于您所遵循的编码标准。
  按照惯例,@param 标记或 @return 标记后面的文本应该是描述参数或返回值所表示的值的名词短语。算术表达式很少用来代替名词短语;有关示例,请参见BigInteger。@throw 标记后面的文本应该包含“if”这个单词,后面跟一个描述抛出异常的条件的子句。按照惯例,@param、@return 或 @throw 标记后面的短语或子句不以句点结束。以下的doc注释说明了所有这些惯例:

/**
* 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);

  注意 < p > 和 < i > 这两个标记,Javadoc实用程序将doc注释转换成HTML, doc注释中的任意HTML元素最终会出现在最终的HTML文档中。有时,程序员甚至会在他们的文档注释中嵌入HTML表,尽管这种情况很少见。还要注意在 @throw 子句中的代码片段周围使用了Javadoc {@code}标记。此标记有两个用途:它使代码片段以代码字体呈现,并抑制对HTML标记和嵌套在代码片段中的Javadoc标记的处理。后一个属性允许我们在代码片段中使用小于号(<),即使它是一个HTML元字符。要在doc注释中包含多行代码示例,请使用包装在HTML

 标记。换句话说,在代码示例之前加上字符 
{@code and follow it with }
。这保留了代码中的换行符,并消除了转义HTML元字符的需要,但不需要转义@(如果代码示例使用注释,则必须转义@)。
  最后,请注意doc注释中“此列表”一词的使用。按照惯例,“this”一词指的是在doc注释中为实例方法使用方法时调用的对象。
  正如 item 15 项中提到的,在设计用于继承的类时,必须记录它的自用模式,以便程序员了解覆盖其方法的语义。应该使用在 Java 8 中添加的 @implSpec 标记记录这些自用模式。回想一下,普通的doc注释描述了方法与其客户机之间的契约;相反,@implSpec 注释描述了一个方法和它的子类之间的契约,允许子类依赖于实现行为,如果它们继承了这个方法或者通过super调用它。下面是它在实践中的样子:

/**
* 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 标记,除非您通过命令行切换标记 "implSpec:a:Implementation Requirements:"。希  望在后续的版本中可以弥补这一点。
  不要忘记,您必须采取特殊的操作来生成包含HTML元字符的文档,比如小于号(<)、大于号(>)和与号(&)。将这些字符放入文档的最佳方法是用{@literal}标记包围它们,这将禁止处理HTML标记和嵌套的Javadoc标记。它类似于{@code}标记,只是它不以代码字体呈现文本。例如,这个Javadoc片段:
* A geometric series converges if {@literal |r| < 1}.
  生成文档:“如果|r| < 1,则几何级数收敛。” {@literal}标签可以放在小于号的位置,而不是整个不等号的位置,从而产生相同的文档,但是doc注释在源代码中可读性会降低。这说明了文档注释在源代码和生成的文档中都应该是可读的一般原则。如果您不能同时实现这两个目标,则生成的文档的可读性将超过源代码的可读性。
  每个文档注释的第一个“句子”(定义如下)成为注释所属元素的摘要描述。例如,第255页的doc注释中的摘要描述是“返回列表中指定位置的元素”。摘要描述必须独立地描述它所总结的元素的功能。为了避免混淆,类或接口中的任何两个成员或构造函数都不应该具有相同的摘要描述。要特别注意重载,对于重载,通常使用相同的第一句话是很自然的(但在doc注释中是不可接受的)。
  如果预期的摘要描述包含句点,请小心,因为句点可能会提前终止描述。例如,一个以“A college degree, such as B.S., M.S. or Ph.D ”开头的doc注释,将生成“A college degree, such as B.S., M.S. ”,这里的问题是,描述将在第一个句点结束,然后是空格、制表符或行结束符(或第一个块标记)[Javadoc-ref]。这里,第二段缩写为“M.S.”后面是空格。最好的解决方案是用{@literal}标记来包围周期和任何相关的文本,这样周期后面就不会有空格了:

/**
* A college degree, such as B.S., {@literal M.S.} or Ph.D. */
public class Degree { ... }

  说摘要描述是doc注释中的第一句话有点误导人。按照惯例,它很少是一个完整的句子。对于方法和构造函数,摘要描述应该是描述方法执行的操作的动词短语(包括任何对象)。例如:
• ArrayList(int initialCapacity)—Constructs an empty list with the specified initial capacity.
• Collection.size()—Returns the number of elements in this collection.
  如这些例子所示,使用第三人称陈述句(“返回数字”)而不是第二人称祈使句(“返回数字”)。
  对于类、接口和字段,摘要描述应该是一个名词短语,描述类或接口的实例或字段本身所表示的内容。例如:
• Instant—An instantaneous point on the time-line.
• Math.PI—The double value that is closer than any other to pi, the ratio of the circumference of a circle to its diameter.
  在Java 9中,将客户端索引添加到Javadoc生成的HTML中。这个索引简化了导航大型API文档集的任务,它采用页面右上角的搜索框的形式。当您在框中键入时,您将得到一个匹配页面的下拉菜单。API元素(如类、方法和字段)是自动建立索引的。有时,您可能希望索引对您的API很重要的其他术语。为此添加了{@index}标记。为出现在doc注释中的术语建立索引就像将其包装在这个标记中一样简单,如下面的片段所示:
* This method complies with the {@index IEEE 754} standard.
  泛型、枚举和注释在文档注释中需要特别注意。当记录泛型类型或方法时,请确保记录所有类型参数:

/**
* 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  the type of keys maintained by this map
* @param  the type of mapped values */
public interface Map { ... }

  在记录enum类型时,一定要记录常量、类型和任何公共方法。注意,你可以把整个doc注释放在一行,如果它很短:

/**
* An instrument section of a symphony orchestra. */
public enum OrchestraSection {
  /** Woodwinds, such as flute, clarinet, and oboe. */
  WOODWIND,
  /** Brass instruments, such as french horn and trumpet. */
  BRASS,
  /** Percussion instruments, such as timpani and cymbals. */
  PERCUSSION,
  /** Stringed instruments, such as violin and cello. */
  STRING; 
}

  在记录注释类型时,一定要记录任何成员和类型本身。用名词短语记录成员,就好像它们是字段一样。对于类型的摘要描述,请使用动词短语,它表示当程序元素具有此类注释时的含义:

/**
* Indicates that the annotated method is a test method that
* must throw the designated exception to pass.
*/ 
@Retention(RetentionPolicy.RUNTIME) 
@Target(ElementType.METHOD) 
public @interface ExceptionTest {
  /**
  * The exception that the annotated test method must throw * in order to pass. (The test is permitted to throw any
  * subtype of the type described by this class object.)
  */
  Class value(); 
}

  包级别的文档注释应该放在名为package-info.java的文件中。除了这些注释之外,package-info.java必须包含一个包声明,并且可能包含关于这个声明的注释。类似地,如果您选择使用模块系统(item 15),模块级别的注释应该放在module-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中,检查是默认启用的。诸如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年的历史了,但编写文档注释的权威指南仍然是如何编写文档注释[Javadoc-guide]。
  如果您遵循本项目中的指导原则,则生成的文档应该提供您的API的清晰描述。然而,惟一确定的方法是读取由Javadoc实用程序生成的web页面。对于其他API,这样做是值得的。正如测试一个程序几乎不可避免地会导致对代码的一些更改一样,阅读文档通常也会导致对文档注释的一些细微更改。
  总之,文档注释是记录API的最佳、最有效的方法。对于所有导出的API元素,应该强制使用它们。采用符合标准惯例的一致风格。请记住,文档注释中允许使用任意HTML,并且必须转义HTML元字符。

你可能感兴趣的:(ITEM 56: 为所有暴露在外的 API 撰写文档)