身為 Java 開發人員都應該知道,當我們想要查詢某隻 API 的功能和用法時,我們會查看該 API 的 JavaDoc 以取得更多資訊。另外當我們需要提供函式庫讓他人使用時,撰寫 JavaDoc 亦是必要的程序。JDK 23 中的 JEP 467 針對既有的 JavaDoc 做出了改進,因為長久以來撰寫 JavaDoc 這件繁瑣的事情深深困擾著開發人員。
JEP 467 改變了我們對 JavaDoc 的認知。它為 JavaDoc 工具引入 Markdown 語法支援,徹底改變 Java 開發者撰寫 API 文件的方式。這項更新不僅簡化了文件撰寫過程,還提高了可讀性和維護性。本文將深入探討其實現方式、語法細節,以及它如何與現有的 JavaDoc 系統共存,為 Java 開發社群帶來更現代化、更靈活的文件編寫體驗。
目錄
前言
在 Java 推出之前,已有程式語言和工具支援文件註釋的功能,像是 Lisp(釋出於 1950 年代)允許開發人員在寫程式時,使用 docstrings 語法(開始於 1970 年代,其後發展成語言標準)提供內嵌文件註釋,並且可透過開發工具瀏覽和查詢。不過,其他大多數的程式語言都只有常見的程式碼註解語法,並沒有發展出內嵌文件註釋的功能。
直到 Java 語言第一版正式發佈時,支援內嵌文件註釋的功能成為了其中一項最具代表性的亮點。雖然 Java 並不是最早支援文件註釋的程式語言,不過內建的 JavaDoc 實質上協助了 Java 的推廣,也讓 JavaDoc 成為了最普遍和具有影響力的文件註釋生成工具。
以語法層面來說,Java 可以說是第一個廣泛使用 /** … */
語法的多行註釋形式來支援內嵌文件註釋的程式語言。它專門用來撰寫和生成 API 文檔,並影響了後續許多語言和工具對文件註釋的設計。除了可以包含說明性的文字之外,還能包含 JavaDoc 特有的標籤(如 @param
、@return
等),從而自動生成 HTML 格式的文檔。
各程式語言支援的文件註釋
以下為幾個熱門程式語言所支援的文件註釋工具一覽表。老喬儘可能地一一查證並將其全部整理出來。如果有任何疏漏或錯誤之處,尚請不吝留言指教。
程式語言 | 文件註釋工具 | 語法 | 主要撰寫格式 | 說明 |
---|---|---|---|---|
Java (1995) | JavaDoc (1996) | /** ... */ | HTML | Java 1.0 於 1996 年發佈 並內建 JavaDoc |
JavaScript (1995) | JSDoc (2007) | /** ... */ | HTML | 參考自 JavaDoc 1999 年實驗版,2007 年正式版 |
C# (2000) | NDoc (2000) Sandcastle (2006) | /// ... | XML | 新的 DocFX (2015) 支援 Markdown |
Python (1991) | pydoc (1999) Sphinx (2007) | ''' ... ''' | reStructuredText Markdown | Sphinx 開始支援 Markdown |
PHP (1995) | PHPDoc (2001) | /** ... */ | HTML | 參考自 JavaDoc |
Ruby (1995) | RDoc (2001) | # ... | RDoc, Markdown | 2001 以獨立 gem 形式發佈 2003 為 Ruby 1.8 官方標準 |
Go (2009) | Godoc (2009) | // ... | 類似 Markdown | 雖然 2009 發佈, 但 2011 年才被廣泛認可與使用 |
Kotlin (2011) | KDoc (2011) | /** ... */ | Markdown | 一開始就內建 |
Swift (2014) | DocC (2014) | /// ... | Markdown | 一開始就內建 |
Rust (2015) | Rustdoc (2015) | /// ... | Markdown | 一開始就內建 |
上面的表格反應了文件註釋的格式演進。我們可以發現,越是後期發表的語言,它們的主要撰寫格式幾乎都是 Markdown 為主,或是與 Markdown 類似的語法。
JavaDoc 的痛點
Java 開發人員長期以來一直使用 HTML 標記語言和自定義 JavaDoc 標籤的組合來編寫 API 文件。在 1995 年採用這種格式是非常合理的,因為那時 HTML 既強大又具備標準規格,並且廣受歡迎。然而隨著時間的推移,它的局限性變得越來越明顯。
首先,雖然 Web 瀏覽器仍然廣泛地使用 HTML 作為主要的標記語言,但是自 1995 年以來人們已經不太流行手工撰寫 HTML 格式。主要是它寫起來很繁瑣,而且閱讀起來也不是那麼容易,連帶也使得格式良好的文件註釋變得難以撰寫、閱讀和維護。
其次,有部分原因是 HTML 的繁瑣,使得近年來有許多新進的開發人員並不熟悉 HTML,也加深了撰寫 JavaDoc 的困難度。另外,JavaDoc 標籤(如 {@link}
和 {@code}
)具有一定的學習曲線。開發人員不太熟悉它們,經常需要查閱文件後才能了解它們的用法並正確使用。最近對 JDK 原始碼中的文件註釋的分析表明,超過 95% 的標籤是用於程式碼片段和指向文件其他地方的連結,這代表更簡單的結構形式勢必會受到歡迎。
這些挑戰導致了文件品質下降。因為開發人員可能會因此而避免或懶得撰寫較詳細的文件註釋,或者寫出格式不正確的文件。此外,不少目前的程式碼編輯工具缺乏對 JavaDoc 文件註釋的完整支援,這進一步增加了撰寫高品質 API 文件的難度。因此,需要一種更簡單、更普遍、更直觀的方法來編寫 Java API 文件,以提高文件的品質和開發效率。
JEP 467 概觀
Markdown 是目前很流行的標記語言。它簡單而易於閱讀、易於編寫,並且易於轉換為 HTML。文件註釋通常不會是過於複雜的結構化文件,對於常出現在文件註釋中的結構(段落、清單、樣式文本和連結)來說,Markdown 提供了比 HTML 更為簡單的形式。Markdown 也允許使用 HTML 去支援 Markdown 中未定義的結構。
JEP 467 Markdown 文件註釋功能中,利用 Markdown 簡潔語法的能力去撰寫常見的文件結構,也減少了對 HTML 標記和 JavaDoc 標籤的需求。另外,它同時也保留了使用專門標籤的選項。整體來說,本功能可以讓我們更容易在原始碼中編寫和閱讀文件註釋,並保留產生與以前相同類型 API 文件的能力。
優點
- Markdown 語法比 HTML 和 JavaDoc 標籤更簡潔易讀,降低了編寫和維護 API 文件的門檻
- Markdown 是一種廣泛使用的輕量級標記語言。許多開發者已經熟悉語法,因此可以降低學習門檻並快速上手
- 許多程式碼編輯器和 IDE 都內建了對 Markdown 的支援,可以提供即時預覽和語法高亮等功能
- 簡化的語法可能會鼓勵開發者寫數量更多、品質更好的文件註釋
缺點:
- Markdown 的功能不如 HTML 豐富,可能無法滿足某些複雜的文件需求
- 對於已經使用 HTML 和 JavaDoc 標籤撰寫的註釋,可能需要進行轉換或調整
- 雖然 Markdown 語法相對簡單,但對於不熟悉它的開發人員來說,仍然需要一定的學習成本
- 某些 Markdown 語法錯誤可能不會像 HTML 錯誤那樣容易被檢測到,因此可能出現更多錯誤
介紹
首先我們先舉一個例子,回顧一下舊的 JavaDoc 文件註釋格式是如何撰寫的:
/**
* {@inheritDoc}
*
* Link to {@link String}.
* <p>
* Test items:
* <ul>
* <li> {@code hashCode}
* <li> {@link #equals(Object) equals}
* <li> <em>EM</em>
* </ul>
*
* @return a hash code value for this object.
* @see java.lang.System#identityHashCode
*/
可以注意到在上例中,JavaDoc 文件註釋中參雜著許多 HTML 和 JavaDoc 標籤。如果再加上 Java 範例程式碼的話,內容會變得更複雜。不過,在 Markdown 文件註釋功能引入後,我們將上述的範例重新改寫,就可以發現我們不再需要寫 HTML 語法,並且也只需要用到少量的 JavaDoc 標籤:
/// {@inheritDoc}
///
/// Link to [String].
///
/// Test items:
///
/// - `hashCode`
/// - [equals][#equals(Object)]
/// - _toString_
///
/// @return a hash code value for this object.
/// @see java.lang.System#identityHashCode
Markdown 語法
下面條列範例中提及的 Markdown 語法:
- 若要撰寫 Markdown 文件註釋的話,每一行需要以
///
開始,而不是傳統的/** ... */
語法 - HTML
<p>
元素:空白行即表示段落分隔 - JavaDoc
{@link ...}
標籤:以參考連結的擴展形式替代,藉此來鏈結到其他程式元素 - HTML
<ul>
和<li>
元素:以項目符號清單標記替代,使用-
表示清單中每個項目的開頭 - JavaDoc
{@code ...}
標籤:使用`...`
替代,表示等寬字體 - HTML
<em>
元素:用底線_..._
取代,以表示重點強調 - JavaDoc 區域標籤:例如
@inheritDoc
、@return
和@see
等等通常不受影響
新的註釋風格
引入 ///
作為 Markdown 註釋的開頭,除了用來區別傳統的 /** ... */
註釋之外,還可以克服下面兩個問題:
- 以
/*
開頭的區域註釋之中無法包含多個*/
;意即你無法撰寫/* 123 /* 456 */ 789 */
這樣的語法。因為在註釋中/*
可以出現很多次,但是*/
只能出現一次,並且是在註釋的結尾處,無法出現在中間處。然而,在文件註釋中放置程式碼範例變得越來越普遍,也造成些許使用上的不便。- 在
///
註釋中沒有限制每行可能出現的字元
- 在
- 以
/**
開頭的傳統文件註釋中,在每一行前導空格的後面可以跟著一個或多個星號。當註釋中省略了這樣的星號時,就會與本身以星號開頭的 Markdown 結構(例如強調、清單項和主題分隔)產生歧義。- 在
///
註釋中永遠不會有這樣的歧義
- 在
以上說明了使用單行而不是多行文件註釋的合理性,但我們仍然需要去區分單行文件註釋和其他單行註解。我們可以使用額外的 /
去辨別,這與在傳統文件註釋的開頭使用額外的 *
相呼應。此外如同前面的表格所示,雖然不是主要考慮因素,但其他支援單行文件註釋的語言,如 C#、Swift 和 Rust,已經成功地使用 ///
表示文件註釋一段時間了。
語法說明
JEP 467 的 Markdown 文件註釋使用 CommonMark 變體編寫,同時加強了連結語法,使其能方便地連結到其他程式元素。另外也支援簡單的 GFM 管道表格語法以呈現表格,以及所有的 JavaDoc 標籤。
連結
我們可以使用 Markdown 參考連結的擴展形式來建立指向 API 中其他程式元素的連結,只需要使用方括號即可。例如要連結到 java.util.List
,我們可以寫 [java.util.List]
;如果程式碼中已經有 import java.util.List
語句,那麼只需要寫 [List]
就行了。連結文字將以等寬字體顯示,該連結等效於使用標準的 JavaDoc {@link ...}
標籤。
/// - 模組 [java.base/]
/// - 套件 [java.util]
/// - 類別 [String]
/// - 欄位 [String#CASE_INSENSITIVE_ORDER]
/// - 方法 [String#chars()]
要建立具有替代文字的連結,請使用 [element]
形式。例如,要建立一個指向 java.util.List
的連結,文字為 a list,我們可以寫 [a list][List]
。連結文字將以預設字體顯示,但我們可以在文字中使用格式標記去修改字體樣式。該連結等效於使用標準的 JavaDoc {@linkplain ...}
標籤。
/// - [`java.base` 模組][java.base/]
/// - [`java.util` 套件][java.util]
/// - [類別][String]
/// - [欄位][String#CASE_INSENSITIVE_ORDER]
/// - [方法][String#chars()]
如果在參考連結中出現方括號時,我們必須對它進行轉義,例如某個方法的傳入值是陣列的時候。此時 String.copyValueOf(char[])
的連結必須寫成 [String#copyValueOf(char\\[\\])]
。
我們可以使用所有其他形式的 Markdown 連結,包括指向 URL 的連結,但指向其他程式元素的連結可能是最常見的。
表格
支援簡單的表格,使用 GitHub Flavored Markdown 的語法。例如:
/// | Latin | Greek |
/// |-------|-------|
/// | a | alpha |
/// | b | beta |
/// | c | gamma |
目前表法語法並未支援標題和其他可能的表格輔助功能。如果你需要使用它們的話,仍然建議使用原本的 HTML 表格標籤。
/// 這是 Markdown 註釋
///
/// {@inheritDoc}
///
/// @param value 輸入值
/// @return 處理後的結果
JavaDoc 標籤
Markdown 註釋中仍然可以使用 JavaDoc 標籤,包括像 {@inheritDoc}
這樣的行內標籤或是像 @param
和 @return
這樣的區塊標籤,以保留現有的功能:
/// {@inheritDoc}
/// 此外,這個方法調用 [#wait()]。
///
/// @param i 索引
public void m(int i) {}
JavaDoc 標籤不能在單行程式碼區段(`...`
)或多行程式碼區域(即縮排或是位在 ```
或 ~~~
內的文字區域)裡使用。換句話說,@xxx
和 {@xxx}
在單行程式碼區段和多行程式碼區域中沒有任何意義:
/// 下一行單行程式碼區段中的 {@... } 會被視為是純文字而非 JavaDoc 標籤:
/// `{@inheritDoc}`
///
/// 下面的縮排程式碼區域中,`@Override` 是一個 annotation,
/// 而不是 JavaDoc 標籤:
///
/// @Override
/// public void m() {}
///
/// 同樣,在下面的多行程式碼區域中,`@Override` 是一個 annotation,
/// 而不是 JavaDoc 標籤 tag:
///
/// ```
/// @Override
/// public void m() {}
/// ```
{@inheritDoc}
標籤用來繼承父類別的文件註釋。它們的文件註釋格式不需要相同:
interface Base {
// 父型別使用 JavaDoc 格式
/** 一個方法。 */
void m();
}
class Derived implements Base {
// 子型別可以用 Markdown 格式
/// {@inheritDoc}
public void m() { }
}
用戶定義的 JavaDoc 標籤可以在 Markdown 文件註釋中使用。例如在 JDK 文件中,我們定義和使用 {@jls ...}
作為指向 Java 語言規範的連結,並使用 @implSpec
和 @implNote
等標籤來引入特定訊息:
/// 有關註釋的更多訊息,請參見 {@jls 3.7 註釋}。
///
/// @implSpec
/// 此實現不執行任何操作。
public void doSomething() { }
語法高亮和嵌入式語言
fenced code block 中的開頭 fence 後面可以跟一個 info string,它的第一個單詞用於產生相應生成的 HTML 中的 CSS 類名稱,也可以被 JavaScript 庫用於啟用語法高亮(例如使用 Prism)和渲染圖表(例如使用 Mermaid)。例如,結合適當的函式庫,下例將顯示一個帶有語法高亮的 CSS 代碼片段:
/// ```css
/// p { color: red }
/// ```
語法細節
因為 Markdown 語法中每行開頭和結尾的水平空格有時會具有特殊重要,所以 Markdown 文件註釋內容的規則定義如下:
- 首先會刪除每一行的
///
和其左方的任何前導空格。 - 接著開始逐一刪除
///
右方的前導空格,並將所有行向左移動,直到出現某一行的開頭已沒有剩餘的前導空格。 - 最後每行中的留下來的前導空格和任何尾隨空格都會被保留,因為它們可能很重要。例如,行首的空格可能表示縮排的程式碼區域或清單項目,行尾的空格可能表示硬換行符。
註釋可能包含程式碼範例,而這些範例可能包含自己的註釋:
/// 這是一個例子:
///
/// ```
/// /** Hello World! */
/// public class HelloWorld {
/// public static void main(String... args) {
/// System.out.println("Hello World!"); // 傳統的例子
/// }
/// }
/// ```
如果我們想要在文件註釋中出現空行的話,就必須明確使用 ///
但不帶其他字元:
/// 這是一個例子 ...
///
/// 一個包含空行的三行註釋。
一個完全空白的行將導致任何前後的註釋被視為各自獨立。在這種情況下,除了最後一個註釋之外的其他所有註釋都將會被丟棄,並且只有最後一個註釋才會被視為文件註釋:
/// 此註釋將被視為「懸空註釋」且被忽略。
/// 只有此行才會被視為本方法的文件註釋。
public void m() { }
總結
JEP 467是 Java 平台中一個激動人心的新變革。Markdown 文件註釋功能帶來了更現代化、更易於使用的文件編寫體驗,並大幅簡化了 API 文件的撰寫過程。這不僅提高了文件的可讀性和易於維護性,還能提升整體的開發效率。雖然這項新功能帶來了一些挑戰,諸如新語法學習曲線和相容性處理的問題,但其潛在的好處似乎遠遠超過了這些短期的不便。
藉由 JEP 467,我們可以期待看到 Java 生態系統中整體文件品質提升,開發者也能夠更輕鬆地建立清晰、結構良好的 API 文件。這不僅能為程式碼的長期維護帶來好處,也能夠大大改善開發者之間的溝通和協作。我們應該積極地探索如何最大化地利用新工具來改進開發方式。雖然 Markdown 的功能也許不如 HTML 豐富,但對於大多數 API 文件來說,它已經足夠強大,並且可以與現有的 HTML 和 JavaDoc 標籤共存,以滿足更複雜的需求。
本篇文章的內容為老喬原創、二創或翻譯而來。雖已善盡校對、順稿與查核義務,但人非聖賢,多少仍會有疏漏之處難以避免。如果大家有任何問題、建議或指教,都歡迎在底下留言與老喬討論!