ITEM 56: 為所有暴露在外的 API 撰寫文檔

ITEM 56: WRITE DOC COMMENTS FOR ALL EXPOSED API ELEMENTS
??如果 API 要可用,就必須對(duì)其進(jìn)行文檔化。傳統(tǒng)上,API 文檔是手動(dòng)生成的,保持與代碼的同步是一項(xiàng)繁瑣的工作。Java 編程環(huán)境使用Javadoc 實(shí)用程序簡化了這項(xiàng)任務(wù)。Javadoc 使用特殊格式的文檔注釋(通常稱為doc注釋)從源代碼自動(dòng)生成API文檔。
??雖然doc注釋約定不是正式語言的一部分,但它們構(gòu)成了每個(gè)Java程序員都應(yīng)該知道的實(shí)際API。這些約定在如何編寫Doc注釋web頁面[Javadoc-guide]中進(jìn)行了描述。雖然這個(gè)頁面自 Java 4 發(fā)布以來一直沒有更新,但它仍然是一個(gè)寶貴的資源。在Java 9 中添加了一個(gè)重要的 doc 標(biāo)記,{@index};Java 8中添加了 {@implSpec};Java 5中的兩個(gè)是 {@literal} 和 {@code}。這些標(biāo)記在前面提到的web頁面中是不存在的,但是在本項(xiàng)目中進(jìn)行了討論。
??要正確地記錄 API,必須在每個(gè)導(dǎo)出的類、接口、構(gòu)造函數(shù)、方法和字段聲明之前加上doc注釋。如果一個(gè)類是可序列化的,那么還應(yīng)該記錄它的序列化形式(item 87)。在缺少doc注釋的情況下,Javadoc 所能做的最好的事情就是將聲明作為受影響 API元素的唯一文檔重新生成。使用缺少文檔注釋的 API 是令人沮喪和容易出錯(cuò)的。公共類不應(yīng)該使用默認(rèn)構(gòu)造函數(shù),因?yàn)闊o法為它們提供文檔注釋。要編寫可維護(hù)的代碼,您還應(yīng)該為大多數(shù)未導(dǎo)出的類、接口、構(gòu)造函數(shù)、方法和字段編寫文檔注釋,盡管這些注釋不必像導(dǎo)出的 API 元素那樣全面。
??方法的文檔注釋應(yīng)該簡明扼要地描述方法與其客戶之間的契約。除了為繼承而設(shè)計(jì)的類中的方法(item 19)之外,契約應(yīng)該說明方法做什么,而不是它如何工作。doc注釋應(yīng)該枚舉方法的所有前置條件(為了讓客戶端調(diào)用它,這些條件必須為真)和后置條件(在調(diào)用成功完成后,這些條件必須為真)。通常,對(duì)于未檢查的異常,前置條件由@throw 標(biāo)記隱式地描述;每個(gè)未檢查的異常對(duì)應(yīng)于一個(gè)前置條件違背。此外,可以在它們的 @param 標(biāo)記中指定前置條件和受影響的參數(shù)。
??除了前置條件和后置條件外,方法還應(yīng)該記錄任何副作用。一個(gè)副作用是系統(tǒng)狀態(tài)的一個(gè)可觀察到的變化,它不是實(shí)現(xiàn)后置條件所明顯需要的。例如,如果一個(gè)方法啟動(dòng)了一個(gè)后臺(tái)線程,文檔應(yīng)該注意它。
??要完整地描述一個(gè)方法的契約,doc 注釋中的每個(gè)參數(shù)都應(yīng)該有一個(gè) @param 標(biāo)記,一個(gè) @return 標(biāo)記(除非方法的返回類型為void),對(duì)于方法拋出的每個(gè)異常都應(yīng)該有一個(gè)@throw 標(biāo)記(無論是否選中)(item 74)。如果 @return 標(biāo)記中的文本與方法的描述相同,則可以忽略它,這取決于您所遵循的編碼標(biāo)準(zhǔn)。
??按照慣例,@param 標(biāo)記或 @return 標(biāo)記后面的文本應(yīng)該是描述參數(shù)或返回值所表示的值的名詞短語。算術(shù)表達(dá)式很少用來代替名詞短語;有關(guān)示例,請(qǐng)參見BigInteger。@throw 標(biāo)記后面的文本應(yīng)該包含“if”這個(gè)單詞,后面跟一個(gè)描述拋出異常的條件的子句。按照慣例,@param、@return 或 @throw 標(biāo)記后面的短語或子句不以句點(diǎn)結(jié)束。以下的doc注釋說明了所有這些慣例:

/**
* 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 > 這兩個(gè)標(biāo)記,Javadoc實(shí)用程序?qū)oc注釋轉(zhuǎn)換成HTML, doc注釋中的任意HTML元素最終會(huì)出現(xiàn)在最終的HTML文檔中。有時(shí),程序員甚至?xí)谒麄兊奈臋n注釋中嵌入HTML表,盡管這種情況很少見。還要注意在 @throw 子句中的代碼片段周圍使用了Javadoc {@code}標(biāo)記。此標(biāo)記有兩個(gè)用途:它使代碼片段以代碼字體呈現(xiàn),并抑制對(duì)HTML標(biāo)記和嵌套在代碼片段中的Javadoc標(biāo)記的處理。后一個(gè)屬性允許我們?cè)诖a片段中使用小于號(hào)(<),即使它是一個(gè)HTML元字符。要在doc注釋中包含多行代碼示例,請(qǐng)使用包裝在HTML <pre> 標(biāo)記。換句話說,在代碼示例之前加上字符 <pre>{@code and follow it with }</pre>。這保留了代碼中的換行符,并消除了轉(zhuǎn)義HTML元字符的需要,但不需要轉(zhuǎn)義@(如果代碼示例使用注釋,則必須轉(zhuǎn)義@)。
??最后,請(qǐng)注意doc注釋中“此列表”一詞的使用。按照慣例,“this”一詞指的是在doc注釋中為實(shí)例方法使用方法時(shí)調(diào)用的對(duì)象。
??正如 item 15 項(xiàng)中提到的,在設(shè)計(jì)用于繼承的類時(shí),必須記錄它的自用模式,以便程序員了解覆蓋其方法的語義。應(yīng)該使用在 Java 8 中添加的 @implSpec 標(biāo)記記錄這些自用模式。回想一下,普通的doc注釋描述了方法與其客戶機(jī)之間的契約;相反,@implSpec 注釋描述了一個(gè)方法和它的子類之間的契約,允許子類依賴于實(shí)現(xiàn)行為,如果它們繼承了這個(gè)方法或者通過super調(diào)用它。下面是它在實(shí)踐中的樣子:

/**
* 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實(shí)用程序仍然忽略 @implSpec 標(biāo)記,除非您通過命令行切換標(biāo)記 "implSpec:a:Implementation Requirements:"。希??望在后續(xù)的版本中可以彌補(bǔ)這一點(diǎn)。
??不要忘記,您必須采取特殊的操作來生成包含HTML元字符的文檔,比如小于號(hào)(<)、大于號(hào)(>)和與號(hào)(&)。將這些字符放入文檔的最佳方法是用{@literal}標(biāo)記包圍它們,這將禁止處理HTML標(biāo)記和嵌套的Javadoc標(biāo)記。它類似于{@code}標(biāo)記,只是它不以代碼字體呈現(xiàn)文本。例如,這個(gè)Javadoc片段:
* A geometric series converges if {@literal |r| < 1}.
??生成文檔:“如果|r| < 1,則幾何級(jí)數(shù)收斂。” {@literal}標(biāo)簽可以放在小于號(hào)的位置,而不是整個(gè)不等號(hào)的位置,從而產(chǎn)生相同的文檔,但是doc注釋在源代碼中可讀性會(huì)降低。這說明了文檔注釋在源代碼和生成的文檔中都應(yīng)該是可讀的一般原則。如果您不能同時(shí)實(shí)現(xiàn)這兩個(gè)目標(biāo),則生成的文檔的可讀性將超過源代碼的可讀性。
??每個(gè)文檔注釋的第一個(gè)“句子”(定義如下)成為注釋所屬元素的摘要描述。例如,第255頁的doc注釋中的摘要描述是“返回列表中指定位置的元素”。摘要描述必須獨(dú)立地描述它所總結(jié)的元素的功能。為了避免混淆,類或接口中的任何兩個(gè)成員或構(gòu)造函數(shù)都不應(yīng)該具有相同的摘要描述。要特別注意重載,對(duì)于重載,通常使用相同的第一句話是很自然的(但在doc注釋中是不可接受的)。
??如果預(yù)期的摘要描述包含句點(diǎn),請(qǐng)小心,因?yàn)榫潼c(diǎn)可能會(huì)提前終止描述。例如,一個(gè)以“A college degree, such as B.S., M.S. or Ph.D ”開頭的doc注釋,將生成“A college degree, such as B.S., M.S. ”,這里的問題是,描述將在第一個(gè)句點(diǎn)結(jié)束,然后是空格、制表符或行結(jié)束符(或第一個(gè)塊標(biāo)記)[Javadoc-ref]。這里,第二段縮寫為“M.S.”后面是空格。最好的解決方案是用{@literal}標(biāo)記來包圍周期和任何相關(guān)的文本,這樣周期后面就不會(huì)有空格了:

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

??說摘要描述是doc注釋中的第一句話有點(diǎn)誤導(dǎo)人。按照慣例,它很少是一個(gè)完整的句子。對(duì)于方法和構(gòu)造函數(shù),摘要描述應(yīng)該是描述方法執(zhí)行的操作的動(dòng)詞短語(包括任何對(duì)象)。例如:
? ArrayList(int initialCapacity)—Constructs an empty list with the specified initial capacity.
? Collection.size()—Returns the number of elements in this collection.
??如這些例子所示,使用第三人稱陳述句(“返回?cái)?shù)字”)而不是第二人稱祈使句(“返回?cái)?shù)字”)。
??對(duì)于類、接口和字段,摘要描述應(yīng)該是一個(gè)名詞短語,描述類或接口的實(shí)例或字段本身所表示的內(nèi)容。例如:
? 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中。這個(gè)索引簡化了導(dǎo)航大型API文檔集的任務(wù),它采用頁面右上角的搜索框的形式。當(dāng)您在框中鍵入時(shí),您將得到一個(gè)匹配頁面的下拉菜單。API元素(如類、方法和字段)是自動(dòng)建立索引的。有時(shí),您可能希望索引對(duì)您的API很重要的其他術(shù)語。為此添加了{(lán)@index}標(biāo)記。為出現(xiàn)在doc注釋中的術(shù)語建立索引就像將其包裝在這個(gè)標(biāo)記中一樣簡單,如下面的片段所示:
* This method complies with the {@index IEEE 754} standard.
??泛型、枚舉和注釋在文檔注釋中需要特別注意。當(dāng)記錄泛型類型或方法時(shí),請(qǐng)確保記錄所有類型參數(shù):

/**
* 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> { ... }

??在記錄enum類型時(shí),一定要記錄常量、類型和任何公共方法。注意,你可以把整個(gè)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; 
}

??在記錄注釋類型時(shí),一定要記錄任何成員和類型本身。用名詞短語記錄成員,就好像它們是字段一樣。對(duì)于類型的摘要描述,請(qǐng)使用動(dòng)詞短語,它表示當(dāng)程序元素具有此類注釋時(shí)的含義:

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

??包級(jí)別的文檔注釋應(yīng)該放在名為package-info.java的文件中。除了這些注釋之外,package-info.java必須包含一個(gè)包聲明,并且可能包含關(guān)于這個(gè)聲明的注釋。類似地,如果您選擇使用模塊系統(tǒng)(item 15),模塊級(jí)別的注釋應(yīng)該放在module-info.java文件中。
在文檔中經(jīng)常忽略的 api 的兩個(gè)方面是線程安全性和可序列化性。無論類或靜態(tài)方法是否線程安全,您都應(yīng)該記錄它的線程安全級(jí)別,如第82項(xiàng)所述。如果一個(gè)類是可序列化的,您應(yīng)該記錄它的序列化形式,如項(xiàng)目87中所述。
??Javadoc具有“繼承”方法注釋的能力。如果API元素沒有doc注釋,Javadoc將搜索最特定的適用doc注釋,優(yōu)先選擇接口而不是超類。搜索算法的詳細(xì)信息可以在Javadoc參考指南[Javadoc-ref]中找到。您還可以使用{@inheritDoc}標(biāo)記從超類型繼承部分doc注釋。這意味著,除其他外,類可以重用它們實(shí)現(xiàn)的接口中的doc注釋,而不是復(fù)制這些注釋。這個(gè)工具有可能減少維護(hù)多組幾乎相同的doc注釋的負(fù)擔(dān),但是它使用起來比較復(fù)雜,并且有一些限制。這些細(xì)節(jié)超出了這本書的范圍。
??關(guān)于文檔注釋,應(yīng)該添加一個(gè)警告。雖然有必要為所有導(dǎo)出的API元素提供文檔注釋,但這并不總是足夠的。對(duì)于由多個(gè)相互關(guān)聯(lián)的類組成的復(fù)雜API,通常需要用一個(gè)描述API整體架構(gòu)的外部文檔來補(bǔ)充文檔注釋。如果存在這樣的文檔,相關(guān)的類或包文檔注釋應(yīng)該包含到它的鏈接。
??Javadoc會(huì)自動(dòng)檢查是否符合本項(xiàng)目中的許多建議。在Java 7中,需要命令行開關(guān)-Xdoclint來獲得這種行為。在Java 8和Java 9中,檢查是默認(rèn)啟用的。諸如checkstyle之類的IDE插件進(jìn)一步檢查是否符合這些建議[Burn01]。您還可以通過一個(gè)HTML有效性檢查器來運(yùn)行Javadoc生成的HTML文件,從而減少doc注釋中出現(xiàn)錯(cuò)誤的可能性。這將檢測(cè)HTML標(biāo)記的許多錯(cuò)誤用法。有幾個(gè)這樣的檢查器可供下載,您可以使用W3C標(biāo)記驗(yàn)證服務(wù)[W3C-validator]在web上驗(yàn)證HTML。在驗(yàn)證生成的HTML時(shí),請(qǐng)記住,從Java 9開始,Javadoc就能夠生成HTML5和HTML 4.01,盡管它仍然默認(rèn)生成HTML 4.01。如果希望Javadoc生成HTML5,請(qǐng)使用-html5命令行開關(guān)。
??本項(xiàng)目中描述的約定涵蓋了基本內(nèi)容。盡管撰寫本文時(shí)它已經(jīng)有15年的歷史了,但編寫文檔注釋的權(quán)威指南仍然是如何編寫文檔注釋[Javadoc-guide]。
??如果您遵循本項(xiàng)目中的指導(dǎo)原則,則生成的文檔應(yīng)該提供您的API的清晰描述。然而,惟一確定的方法是讀取由Javadoc實(shí)用程序生成的web頁面。對(duì)于其他API,這樣做是值得的。正如測(cè)試一個(gè)程序幾乎不可避免地會(huì)導(dǎo)致對(duì)代碼的一些更改一樣,閱讀文檔通常也會(huì)導(dǎo)致對(duì)文檔注釋的一些細(xì)微更改。
??總之,文檔注釋是記錄API的最佳、最有效的方法。對(duì)于所有導(dǎo)出的API元素,應(yīng)該強(qiáng)制使用它們。采用符合標(biāo)準(zhǔn)慣例的一致風(fēng)格。請(qǐng)記住,文檔注釋中允許使用任意HTML,并且必須轉(zhuǎn)義HTML元字符。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容