內容

Effective Dart:文件

很容易認為你的程式碼在今天很明顯,而沒有意識到自己已經依賴多少腦中的脈絡。不熟悉你的程式碼的人,甚至健忘的未來自己,都不會有那個脈絡。簡潔、準確的註解只需花幾秒鐘就能寫好,但可以為這些人省下數小時的時間。

我們都知道程式碼應該是自文件化的,而且並非所有註解都有用。但現實情況是,我們大多數人寫的註解並不如我們應該寫的那麼多。這就像運動:從技術上來說,你可以做得太多,但更有可能的是你做得太少。試著加強它。

註解

#

以下提示適用於你不想包含在產生的文件中中的註解。

以句子格式撰寫 DO 格式註解

#
gooddart
// Not if anything comes before it.
if (_chunks.isNotEmpty) return false;

將第一個字大寫,除非它是大小寫敏感的識別碼。以句號(或「!」或「?」,我想)結尾。這適用於所有註解:文件註解、內嵌內容,甚至 TODO。即使它是一個句子片段。

不要使用區塊註解作為文件

#
gooddart
void greet(String name) {
  // Assume we have a valid name.
  print('Hi, $name!');
}
baddart
void greet(String name) {
  /* Assume we have a valid name. */
  print('Hi, $name!');
}

你可以使用區塊註解(/* ... */)暫時註解掉一段程式碼,但所有其他註解都應該使用 //

文件註解

#

文件註解特別方便,因為 dart doc 會分析它們並從中產生 漂亮的文件頁面。文件註解是出現在宣告之前並使用 dart doc 尋找的特殊 /// 語法的任何註解。

請使用 /// 文件註解來記錄成員和類型

#

Linter 規則: slash_for_doc_comments

使用文件註解而不是一般註解,可以讓 dart doc 找到它並為它產生文件。

gooddart
/// The number of characters in this chunk when unsplit.
int get length => ...
baddart
// The number of characters in this chunk when unsplit.
int get length => ...

出於歷史原因,dart doc 支援兩種文件註解語法:///(「C# 風格」)和 /** ... */(「JavaDoc 風格」)。我們偏好 ///,因為它比較簡潔。/***/ 會在多行文件註解中新增兩行沒有內容的行。在某些情況下,/// 語法也比較容易閱讀,例如當文件註解包含使用 * 來標記清單項目的項目符號清單時。

如果你遇到仍然使用 JavaDoc 風格的程式碼,請考慮清理它。

優先為公開 API 撰寫文件註解

#

Linter 規則: package_api_docspublic_member_api_docs

你不需要記錄每個函式庫、頂層變數、類型和成員,但你應該記錄它們中的大部分。

考慮撰寫函式庫層級的文件註解

#

與 Java 等僅將類別視為程式組織單位的語言不同,在 Dart 中,函式庫本身就是使用者直接使用、匯入和思考的實體。這使得 library 指令成為文件說明的絕佳位置,可讓讀者了解其中提供的概念和功能。請考慮納入

  • 函式庫用途的單句摘要。
  • 函式庫中所用術語的說明。
  • 幾個說明如何使用 API 的完整程式碼範例。
  • 連結到最重要或最常使用的類別和函式。
  • 連結到函式庫相關領域的外部參考。

若要說明函式庫,請在 library 指令和檔案開頭可能附加的註解之前放置文件說明註解。

gooddart
/// A really great test library.
@TestOn('browser')
library;

考慮為私人 API 撰寫文件註解

#

文件說明註解不只適用於函式庫公開 API 的外部使用者。它們對於了解從函式庫其他部分呼叫的私有成員也有幫助。

以單句摘要開始文件註解

#

以簡短、以使用者為中心的說明開始文件說明註解,並以句號作結。句子片段通常就已足夠。僅提供足夠的內容,讓讀者能了解概況,並決定是否要繼續閱讀或尋找其他解決方案。

gooddart
/// Deletes the file at [path] from the file system.
void delete(String path) {
  ...
}
baddart
/// Depending on the state of the file system and the user's permissions,
/// certain operations may or may not be possible. If there is no file at
/// [path] or it can't be accessed, this function throws either [IOError]
/// or [PermissionError], respectively. Otherwise, this deletes the file.
void delete(String path) {
  ...
}

請將文件註解的第一句獨立成一個段落

#

在第一個句子後加入空白行,將其分為獨立的段落。如果說明需要超過一個句子,請將其餘部分放在後面的段落中。

這有助於撰寫簡潔的第一個句子,以總結文件說明。此外,dart doc 等工具會將第一個段落用作類別和成員清單等位置的簡短摘要。

gooddart
/// Deletes the file at [path].
///
/// Throws an [IOError] if the file could not be found. Throws a
/// [PermissionError] if the file is present but could not be deleted.
void delete(String path) {
  ...
}
baddart
/// Deletes the file at [path]. Throws an [IOError] if the file could not
/// be found. Throws a [PermissionError] if the file is present but could
/// not be deleted.
void delete(String path) {
  ...
}

避免與周圍內容重複

#

類別文件說明註解的讀者可以清楚地看到類別名稱、它實作的介面等。在閱讀成員文件說明時,簽章就在那裡,而且封裝類別很明顯。這些都不需要在文件說明註解中說明。相反地,請專注於說明讀者不知道的內容。

gooddart
class RadioButtonWidget extends Widget {
  /// Sets the tooltip to [lines], which should have been word wrapped using
  /// the current font.
  void tooltip(List<String> lines) {
    ...
  }
}
baddart
class RadioButtonWidget extends Widget {
  /// Sets the tooltip for this radio button widget to the list of strings in
  /// [lines].
  void tooltip(List<String> lines) {
    ...
  }
}

如果您真的沒有什麼有趣的事可以說,而且無法從宣告本身推論出來,請省略文件說明註解。什麼都不說總比浪費讀者時間告訴他們他們已經知道的事情好。

建議以第三人稱動詞開始函式或方法註解

#

文件說明註解應專注於程式碼執行的動作

gooddart
/// Returns `true` if every element satisfies the [predicate].
bool all(bool predicate(T element)) => ...

/// Starts the stopwatch if not already running.
void start() {
  ...
}

建議以名詞片語開始非布林變數或屬性註解

#

文件說明註解應強調屬性的本質。即使是可能會進行計算或其他工作的 getter 也適用。呼叫者關心的是該工作的結果,而不是工作本身。

gooddart
/// The current day of the week, where `0` is Sunday.
int weekday;

/// The number of checked buttons on the page.
int get checkedCount => ...

建議以「Whether」開頭,後接名詞或動名詞片語,開始布林變數或屬性註解

#

文件說明註解應釐清此變數所代表的狀態。即使是可能會進行計算或其他工作的 getter 也適用。呼叫者關心的是該工作的結果,而不是工作本身。

gooddart
/// Whether the modal is currently displayed to the user.
bool isVisible;

/// Whether the modal should confirm the user's intent on navigation.
bool get shouldConfirm => ...

/// Whether resizing the current browser window will also resize the modal.
bool get canResize => ...

請勿同時撰寫屬性的 getter 和 setter 文件

#

如果屬性同時具有 getter 和 setter,則僅為其中一個建立文件註解。dart doc 將 getter 和 setter 視為單一欄位,如果 getter 和 setter 都具有文件註解,則 dart doc 會捨棄 setter 的文件註解。

gooddart
/// The pH level of the water in the pool.
///
/// Ranges from 0-14, representing acidic to basic, with 7 being neutral.
int get phLevel => ...
set phLevel(int level) => ...
baddart
/// The depth of the water in the pool, in meters.
int get waterDepth => ...

/// Updates the water depth to a total of [meters] in height.
set waterDepth(int meters) => ...

建議以名詞片語開始函式庫或類型註解

#

類別的文件註解通常是程式中最重要的文件。它們描述類型的常數,建立其使用的術語,並為類別成員的其他文件註解提供背景。在此處多花一點功夫,可以讓所有其他成員更容易文件化。

gooddart
/// A chunk of non-breaking output text terminated by a hard or soft newline.
///
/// ...
class Chunk { ... }

考慮在文件註解中加入程式碼範例

#
gooddart
/// Returns the lesser of two numbers.
///
/// ```dart
/// min(5, 3) == 3
/// ```
num min(num a, num b) => ...

人類很擅長從範例中概化,因此即使只有一個程式碼範例,也能讓 API 更容易學習。

請在文件註解中使用方括號來指涉範圍內的識別碼

#

Linter 規則:comment_references

如果您將變數、方法或類型名稱等內容置於方括號中,則 dart doc 會查詢名稱並連結至相關的 API 文件。括號是可選的,但當您參照方法或建構函式時,可以讓它更清楚。

gooddart
/// Throws a [StateError] if ...
/// similar to [anotherMethod()], but ...

若要連結至特定類別的成員,請使用類別名稱和成員名稱,並以點號分隔

gooddart
/// Similar to [Duration.inDays], but handles fractional days.

點號語法也可以用來參照命名建構函式。對於未命名的建構函式,請在類別名稱後使用 .new

gooddart
/// To create a point, call [Point.new] or use [Point.polar] to ...

請使用散文來說明參數、回傳值和例外

#

其他語言使用詳細標籤和區段來描述方法的參數和回傳值。

baddart
/// Defines a flag with the given name and abbreviation.
///
/// @param name The name of the flag.
/// @param abbr The abbreviation for the flag.
/// @returns The new flag.
/// @throws ArgumentError If there is already an option with
///     the given name or abbreviation.
Flag addFlag(String name, String abbr) => ...

Dart 中的慣例是將其整合到方法的描述中,並使用方括號突顯參數。

gooddart
/// Defines a flag.
///
/// Throws an [ArgumentError] if there is already an option named [name] or
/// there is already an option using abbreviation [abbr]. Returns the new flag.
Flag addFlag(String name, String abbr) => ...

請將文件註解置於元資料註解之前

#
gooddart
/// A button that can be flipped on and off.
@Component(selector: 'toggle')
class ToggleComponent {}
baddart
@Component(selector: 'toggle')
/// A button that can be flipped on and off.
class ToggleComponent {}

Markdown

#

您可以在文件註解中使用大多數 markdown 格式,而 dart doc 會使用 markdown 套件 進行處理。

已經有許多指南可以為您介紹 Markdown。我們選擇它的原因是它普遍受到歡迎。以下只是一個簡短範例,讓您了解支援哪些內容

dart
/// This is a paragraph of regular text.
///
/// This sentence has *two* _emphasized_ words (italics) and **two**
/// __strong__ ones (bold).
///
/// A blank line creates a separate paragraph. It has some `inline code`
/// delimited using backticks.
///
/// * Unordered lists.
/// * Look like ASCII bullet lists.
/// * You can also use `-` or `+`.
///
/// 1. Numbered lists.
/// 2. Are, well, numbered.
/// 1. But the values don't matter.
///
///     * You can nest lists too.
///     * They must be indented at least 4 spaces.
///     * (Well, 5 including the space after `///`.)
///
/// Code blocks are fenced in triple backticks:
///
/// ```dart
/// this.code
///     .will
///     .retain(its, formatting);
/// ```
///
/// The code language (for syntax highlighting) defaults to Dart. You can
/// specify it by putting the name of the language after the opening backticks:
///
/// ```html
/// <h1>HTML is magical!</h1>
/// ```
///
/// Links can be:
///
/// * https://www.just-a-bare-url.com
/// * [with the URL inline](https://google.com)
/// * [or separated out][ref link]
///
/// [ref link]: https://google.com
///
/// # A Header
///
/// ## A subheader
///
/// ### A subsubheader
///
/// #### If you need this many levels of headers, you're doing it wrong

避免過度使用 Markdown

#

有疑問時,格式化越少越好。格式化的目的是讓您的內容更清晰,而不是取代它。文字才是最重要的。

避免使用 HTML 格式化

#

在少數情況下,例如表格,使用它可能很有用,但在幾乎所有情況下,如果它太複雜而無法以 Markdown 表達,那麼最好不要表達它。

建議使用反引號圍欄作為程式碼區塊

#

Markdown 有兩種方式可以表示程式碼區塊:在每一行縮排程式碼四個空格,或將其置於一對三個反引號「圍欄」行中。前一種語法在用於 Markdown 清單等縮排已具有意義的內容中,或當程式碼區塊本身包含縮排程式碼時,會很脆弱。

反引號語法避免了這些縮排問題,讓你可以指出程式碼的語言,並且與使用反引號表示內嵌程式碼一致。

gooddart
/// You can use [CodeBlockExample] like this:
///
/// ```dart
/// var example = CodeBlockExample();
/// print(example.isItGreat); // "Yes."
/// ```
baddart
/// You can use [CodeBlockExample] like this:
///
///     var example = CodeBlockExample();
///     print(example.isItGreat); // "Yes."

寫作

#

我們認為自己是程式設計師,但原始檔中的大多數字元主要是供人類閱讀的。英語是我們用來編寫程式碼以修改同事大腦的語言。對於任何程式語言來說,努力提高你的熟練度是值得的。

本節列出我們文件的一些準則。你可以從文章中瞭解更多關於技術寫作的最佳實務,例如 技術寫作風格

建議簡潔

#

清楚且精確,但也要簡潔。

避免使用縮寫和縮寫詞,除非顯而易見

#

許多人不知道「即」、「例如」和「等人」的意思。你確定你領域中的每個人都知道的縮寫可能沒有你想像的那麼廣為人知。

建議使用「this」而非「the」來指涉成員的執行個體

#

在為類別記錄成員時,你通常需要參照成員被呼叫的物件。使用「the」可能會引起歧義。

dart
class Box {
  /// The value this wraps.
  Object? _value;

  /// True if this box contains a value.
  bool get hasValue => _value != null;
}