跳至主要內容

Effective Dart:文件

人們很容易認為今天的程式碼很淺顯易懂,卻沒有意識到自己多麼依賴腦海中已有的上下文。不熟悉您的程式碼的人,甚至是健忘的未來的自己,都不會有這些上下文。簡潔、準確的註解只需幾秒鐘即可寫完,但可以為這些人節省數小時的時間。

我們都知道程式碼應該自我說明,而且並非所有註解都有幫助。但現實情況是,我們大多數人寫的註解都不夠多。這就像運動:從技術上講,您可能會做得過多,但更有可能的是您做得太少。嘗試加把勁。

註解

#

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

務必像句子一樣格式化註解

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

第一個字首字大寫,除非它是區分大小寫的識別項。以句點(或 '!' 或 '?',我想)結尾。這適用於所有註解:文件註解、行內內容,甚至是 TODO。即使它只是句子片段。

請勿使用區塊註解進行文件編寫

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

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

文件註解

#

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

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

#

Linter 規則:slash_for_doc_comments

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

良好dart
/// The number of characters in this chunk when unsplit.
int get length => ...
不良dart
// The number of characters in this chunk when unsplit.
int get length => ...

基於歷史原因,`dart doc` 支援兩種文件註解語法:`///` ('C# 風格') 和 `/** ... */` ('JavaDoc 風格')。我們偏好 `///`,因為它更精簡。`/**` 和 `*/` 會為多行文件註解增加兩行無內容的行。在某些情況下,`///` 語法也更容易閱讀,例如當文件註解包含使用 `*` 來標記列表項目的項目符號列表時。

如果您偶然發現仍在使用 JavaDoc 風格的程式碼,請考慮清理它。

建議為公開 API 撰寫文件註解

#

Linter 規則:public_member_api_docs

您不必記錄每個程式庫、頂層變數、類型和成員,但您應該記錄大多數。

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

#

與 Java 等類別是程式組織的唯一單位的語言不同,在 Dart 中,程式庫本身是用戶直接使用、匯入和思考的實體。這使得 `library` 指令成為文件的絕佳位置,可以向讀者介紹其中提供的主要概念和功能。請考慮包含

  • 程式庫用途的單句摘要。
  • 程式庫中使用的術語的解釋。
  • 幾個完整的程式碼範例,逐步說明如何使用 API。
  • 連結到最重要或最常用的類別和函式。
  • 連結到程式庫所關注的網域上的外部參考資料。

若要記錄程式庫,請將文件註解放在 `library` 指令之前,以及可能附加在檔案開頭的任何註解之前。

良好dart
/// A really great test library.
@TestOn('browser')
library;

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

#

文件註解不僅適用於程式庫公開 API 的外部消費者。它們也有助於理解從程式庫其他部分呼叫的私有成員。

務必以單句摘要開始文件註解

#

以簡短、以使用者為中心的描述開始您的文件註解,並以句點結尾。句子片段通常就足夠了。提供足夠的上下文,讓讀者可以定向自己,並決定他們應該繼續閱讀還是到別處尋找問題的解決方案。

良好dart
/// Deletes the file at [path] from the file system.
void delete(String path) {
  ...
}
不良dart
/// 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` 等工具會使用第一段作為簡短摘要,例如在類別和成員列表中。

良好dart
/// 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) {
  ...
}
不良dart
/// 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) {
  ...
}

避免與周圍上下文重複

#

類別文件註解的讀者可以清楚地看到類別的名稱、它實作的介面等。當閱讀成員的文件時,簽名就在那裡,並且封閉類別是顯而易見的。所有這些都不需要在文件註解中詳細說明。相反,請專注於解釋讀者 *不* 知道的內容。

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

如果您真的沒有任何有趣的事情要說,而且這些事情無法從宣告本身推斷出來,那麼請省略文件註解。與其浪費讀者的時間告訴他們已經知道的事情,不如什麼都不說。

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

#

文件註解應著重於程式碼 *做什麼*。

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

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

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

#

文件註解應強調屬性 *是什麼*。即使對於可能進行計算或其他工作的 getter 也是如此。呼叫者關心的是該工作的 *結果*,而不是工作本身。

良好dart
/// 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 也是如此。呼叫者關心的是該工作的 *結果*,而不是工作本身。

良好dart
/// 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 的文件註解。

良好dart
/// 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) => ...
不良dart
/// 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) => ...

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

#

類別的文件註解通常是程式中最重要的文件。它們描述了類型的恆定性、確立了它使用的術語,並為類別成員的其他文件註解提供了上下文。在這裡多花一點功夫可以使所有其他成員更容易記錄。

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

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

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

人類擅長從範例中歸納,因此即使是一個程式碼範例也能讓 API 更容易學習。

務必在文件註解中使用方括號來參照範圍內的識別項

#

Linter 規則:comment_references

如果您將變數、方法或類型名稱等內容放在方括號中,則 `dart doc` 會查閱該名稱並連結到相關的 API 文件。括號是選用的,但當您參照方法或建構子時,可以使其更清晰。

良好dart
/// Throws a [StateError] if ...
/// similar to [anotherMethod()], but ...

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

良好dart
/// Similar to [Duration.inDays], but handles fractional days.

點語法也可用於參照具名建構子。對於未命名的建構子,請在類別名稱後使用 `.new`

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

務必使用文字來說明參數、傳回值和例外

#

其他語言使用冗長的標籤和區段來描述方法的參數和傳回值是什麼。

不良dart
/// 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 中的慣例是將其整合到方法的描述中,並使用方括號突出顯示參數。

良好dart
/// 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) => ...

務必將文件註解放在 Metadata 註解之前

#
良好dart
/// A button that can be flipped on and off.
@Component(selector: 'toggle')
class ToggleComponent {}
不良dart
@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 列表等縮排已經有意義的地方使用前者語法時,或者當程式碼區塊本身包含縮排程式碼時,前者語法是不穩定的。

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

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

寫作

#

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

本節列出了我們文件的一些指南。您通常可以從〈Technical writing style〉等文章中了解更多關於技術寫作最佳實務的資訊。

建議簡潔

#

務必清晰準確,但也簡潔。

避免使用縮寫和首字母縮略詞,除非它們很明顯

#

很多人不知道 'i.e.'、'e.g.' 和 'et al.' 的意思。您確信您領域中的每個人都知道的首字母縮略詞可能沒有您想像的那麼廣為人知。

建議使用 'this' 而非 'the' 來指稱成員的實例

#

當記錄類別的成員時,您通常需要參照回正在呼叫成員的物件。使用 'the' 可能會造成歧義。

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

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