Tips
书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code
注意,书中的有些代码里方法是基于Java 9 API中的,因此JDK 最好下载 JDK 9以上的版本。html
若是API要可用,就必须对其进行文档化。传统上,API文档是手工生成的,保持文档与代码的同步是一件苦差事。Java编程环境使用Javadoc
实用程序简化了这一任务。Javadoc
使用特殊格式的文档注释(一般称为doc注释),从源代码自动生成API文档。html5
虽然文档注释约定不是Java语言的正式一部分,但它们构成了每一个Java程序员都应该知道的事实上的API。“如何编写文档注释(How to Write Doc Comments web page)”的网页[Javadoc-guide]中介绍了这些约定。 虽然自Java 4发布以来该页面还没有更新,但它仍然是一个很是宝贵的资源。 Java 9中添加了一个重要的文档标签,{@ index}
; Java 8中有一个,{@implSpec}
;Java 5中有两个,{@literal}
和{@code}
。 上述网页中缺乏这些标签的介绍,但在此条目中进行讨论。java
要正确地记录API,必须在每一个导出的类、接口、构造方法、方法和属性声明以前加上文档注释。若是一个类是可序列化的,还应该记录它的序列化形式(条目87)。在没有文档注释的状况下,Javadoc能够作的最好的事情是将声明重现为受影响的API元素的惟一文档。使用缺乏文档注释的API是使人沮丧和容易出错的。公共类不该该使用默认构造方法,由于没法为它们提供文档注释。要编写可维护的代码,还应该为大多数未导出的类、接口、构造方法、方法和属性编写文档注释,尽管这些注释不须要像导出API元素那样完整。git
方法的文档注释应该简洁地描述方法与其客户端之间的契约。除了为继承而设计的类中的方法(条目 19)以外,契约应该说明方法作什么,而不是它如何工做的。文档注释应该列举方法的全部前置条件(这些条件必须为真,以便客户端调用它们),以及后置条件(这些条件是在调用成功完成后才为真)。一般,对于未检查的异常,前置条件由@throw
标签隐式地描述;每一个未检查异常对应于一个先决条件违反( precondition violation.)。此外,能够在受影响的参数的@param
标签中指定前置条件。程序员
除了前置条件和后置条件以外,方法还应在文档中记录它的反作用(side effort)。 反作用是系统状态的可观察到的变化,这对于实现后置条件而言显然不是必需的。 例如,若是方法启动后台线程,则文档应记录它。github
完整地描述方法的契约,文档注释应该为每一个参数都有一个@param
标签,一个@return
标签(除非方法有void返回类型),以及一个@throw
标签(不管是检查异常仍是非检查异常)(条目 74)。若是@return
标签中的文本与方法的描述相同,则能够忽略它,这取决于你所遵循的编码标准。web
按照惯例,@param
或@retur
标签后面的文本应该是一个名词短语,描述参数或返回值所表示的值。 不多使用算术表达式代替名词短语; 请参阅BigInteger
的示例。@throw
标签后面的文本应该包含单词“if”,后面跟着一个描述抛出异常的条件的子句。按照惯例,@param
、@return
或@throw
标签后面的短语或子句不以句号结束。如下的文档注释说明了全部这些约定:算法
/** * Returns the element at the specified position in this list. * * <p>This method is <i>not</i> 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>
)中使用HTML标记。 Javadoc实用工具将文档注释转换为HTML,文档注释中的任意HTML元素最终都会生成HTML文档。 有时候,程序员甚至会在他们的文档注释中嵌入HTML表格,尽管这种状况不多见。编程
还要注意在@throw
子句中的代码片断周围使用Javadoc的 {@code}
标签。这个标签有两个目的:它使代码片断以代码字体形式呈现,而且它抑制了代码片断中HTML标记和嵌套Javadoc标记的处理。后一个属性容许咱们在代码片断中使用小于号(<),即便它是一个HTML元字符。要在文档注释中包含多行代码示例,请使用包装在HTML <pre>
标记中的Javadoc{@code}
标签。换句话说,在代码示例前面加上字符<pre>{@code,而后在代码后面加上}</pre>
。这保留了代码中的换行符,并消除了转义HTML元字符的须要,但不须要转义at符号(@),若是代码示例使用注释,则必须转义at符号(@)。api
最后,请注意文档注释中使用的单词“this list”。按照惯例,“this”指的是在实例方法的文档注释中,指向方法调用所在的对象。
正如条目15中提到的,当你为继承设计一个类时,必须记录它的自用模式( self-use patterns),以便程序员知道重写它的方法的语义。这些自用模式应该使用在Java 8中添加的@implSpec标签来文档记录。回想一下,普通的问问昂注释描述了方法与其客户端之间的契约;相反,@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标签,除非经过命令行开关:-tag "implSpec:a:Implementation Requirements:"
。但愿在后续的版本中能够修正这个错误。
不要忘记,你必须采起特殊操做来生成包含HTML元字符的文档,例如小于号(<),大于号(>)和and符号(&)。 将这些字符放入文档的最佳方法是使用{@literal}
标签将它们包围起来,该标签禁止处理HTML标记和嵌套的Javadoc标记。 它就像{@code}
标签同样,除了不会以代码字体呈现文本之外。 例如,这个Javadoc片断:
* 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}
标签来包围不愉快的句点和任何相关的文本,这样源代码中的句点后面就不会有空格了:
/** * A college degree, such as B.S., {@literal M.S.} or Ph.D. */ public class Degree { ... }
说概要描述是文档注释中的第一句子,其实有点误导人。按照惯例,它不多应该是一个完整的句子。对于方法和构造方法,概要描述应该是一个动词短语(包括任何对象),描述了该方法执行的操做。例如:
rrayList(int initialCapacity)
—— 构造具备指定初始容量的空列表。Collection.size()
—— 返回此集合中的元素个数。如这些例子所示,使用第三人称陈述句时态(“returns the number”)而不是第二人称祈使句(“return the number”)。
对于类,接口和属性,概要描述应该是描述由类或接口的实例或属性自己表示的事物的名词短语。 例如:
Instant
—— 时间线上的瞬时点。Math.PI
—— 更加接近pi的double类型数值,即圆的周长与其直径之比。在Java 9中,客户端索引被添加到Javadoc生成的HTML中。这个索引以页面右上角的搜索框的形式出现,它简化了导航大型API文档集的任务。当你在框中键入时,获得一个匹配页面的下拉菜单。API元素(如类、方法和属性)是自动索引的。有时,可能但愿索引对你的API很重要的其余术语。为此添加了{@index}
标签。对文档注释中出现的术语进行索引,就像将其包装在这个标签中同样简单,以下面的片断所示:
* 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 <K> the type of keys maintained by this map * @param <V> the type of mapped values */ public interface Map<K, V> { ... }
在记录枚举类型时,必定要记录常量,以及类型和任何公共方法。注意,若是文档很短,能够把整个文档注释放在一行:
/** * 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<? extends Throwable> value(); }
包级别文档注释应放在名为package-info.java的文件中。 除了这些注释以外,package-info.java还必须包含一个包声明,而且能够在此声明中包含注解。 一样,若是使用模块化系统(条目 15),则应将模块级别注释放在module-info.java文件中。
在文档中常常忽略的API的两个方面,分别是线程安全性和可序列化性。不管类或静态方法是否线程安全,都应该在文档中描述其线程安全级别,如条目 82中所述。若是一个类是可序列化的,应该记录它的序列化形式,如条目 87中所述。
Javadoc具备“继承(inherit)”方法注释的能力。 若是API元素没有文档注释,Javadoc将搜索最具体的适用文档注释,接口文档优先于超类文档。 搜索算法的详细信息能够在The Javadoc Reference Guide [Javadoc-ref]中找到。 还可使用{@inheritDoc}
标签从超类继承部分文档注释。 这意味着,除其余外,类能够重用它们实现的接口的文档注释,而不是复制这些注释。 该工具备可能减轻维护多组几乎相同的文档注释的负担,但使用起来很棘手而且有一些限制。 详细信息超出了本书的范围。
关于文档注释,应该添加一个警告说明。虽然有必要为全部导出的API元素提供文档注释,但这并不老是足够的。对于由多个相互关联的类组成的复杂API,一般须要用描述API整体架构的外部文档来补充文档注释。若是存在这样的文档,相关的类或包文档注释应该包含到外部文档的连接。
Javadoc会自动检查是否符合此条目中的许多建议。在Java 7中,须要命令行开关-Xdoclint
来得到这种行为。在Java 8和Java 9中,默认状况下启用了此检查。诸如checkstyle之类的IDE插件会进一步检查是否符合这些建议[Burn01]。还能够经过HTML有效性检查器运行Javadoc生成的HTML文件来下降文档注释中出现错误的可能性。能够检测HTML标记的许多错误用法。有几个这样的检查器可供下载,可使用 W3C markup validation service 在线验证HTML格式。在验证生成的HTML时,请记住,从Java 9开始,Javadoc就可以生成HTML5和HTML 4.01,尽管默认状况下仍然生成HTML 4.01。若是但愿Javadoc生成HTML5,请使用-html5
命令行开关。
本条目中描述的约定涵盖了基本内容。尽管撰写本文时已经有15年的历史,但编写文档注释的最终指南仍然是《 How to Write Doc Comments》[Javadoc-guide]。
若是你遵循本项目中的指导原则,生成的文档应该提供对API的清晰描述。然而,惟一肯定的方法,是阅读Javadoc实用工具生成的web页面。对于其余人将使用的每一个API,都值得这样作。正如测试程序几乎不可避免地会致使对代码的一些更改同样,阅读文档一般也会致使对文档注释的一些少量的修改。
总之,文档注释是记录API的最佳、最有效的方法。对于全部导出的API元素,它们的使用应被视为必需的。 采用符合标准惯例的一致风格 。请记住,在文档注释中容许任意HTML,但必须转义HTML的元字符。