JDK 23 功能:JEP 467 Markdown 文件註釋

JEP 467: Markdown Documentation Comments

身為 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)
/** ... */HTMLJava 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, Markdown2001 以獨立 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 標籤共存,以滿足更複雜的需求。

本篇文章的內容為老喬原創、二創或翻譯而來。雖已善盡校對、順稿與查核義務,但人非聖賢,多少仍會有疏漏之處難以避免。如果大家有任何問題、建議或指教,都歡迎在底下留言與老喬討論!

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

three × 5 =

返回頂端