Effective Java 第三版——56. 爲全部已公開的API元素編寫文檔註釋

Tips
書中的源代碼地址:https://github.com/jbloch/effective-java-3e-source-code
注意,書中的有些代碼裏方法是基於Java 9 API中的,因此JDK 最好下載 JDK 9以上的版本。html

Effective Java, Third Edition

56. 爲全部已公開的API元素編寫文檔註釋

若是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的元字符。

相關文章
相關標籤/搜索