跳到主內容

Effective Dart:文件

今天你可能很容易認為你的程式碼是顯而易見的,卻沒意識到你有多麼依賴腦海中已有的上下文。新接觸你程式碼的人,甚至是你健忘的未來自己,都不會擁有這些上下文。一個簡潔、準確的註釋只需幾秒鐘就能寫好,卻能為這些人節省數小時的時間。

我們都知道程式碼應該自文件化,並且並非所有註釋都有幫助。但現實是,我們大多數人編寫的註釋量都不足。這就像鍛鍊一樣:你理論上可以做太多,但更有可能的是你做得太少。嘗試多加努力。

註釋

#

以下提示適用於你不希望包含在生成文件中的註釋。

DO 將註釋格式化為句子

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

將第一個單詞大寫,除非它是大小寫敏感的識別符號。以句號(或者“!”或“?”,我想)結尾。這適用於所有註釋:文件註釋、內聯內容,甚至 TODO。即使它是一個句子片段。

DON'T 使用塊註釋作為文件

#
良好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 所尋找的特殊 /// 語法的任何註釋。

DO 使用 /// 文件註釋來記錄成員和型別

#

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 風格的程式碼,請考慮清理它。

PREFER 為公共 API 編寫文件註釋

#

Linter 規則:public_member_api_docs

你不必為每個庫、頂級變數、型別和成員編寫文件,但你應該為其中大部分編寫文件。

CONSIDER 編寫庫級文件註釋

#

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

  • 一個單句的庫功能摘要。
  • 庫中使用的術語解釋。
  • 幾個完整的程式碼示例,逐步演示如何使用 API。
  • 指向最重要或最常用類和函式的連結。
  • 指向與庫相關領域的外部參考資料的連結。

要為庫編寫文件,請將文件註釋放在 library 指令以及檔案開頭可能附加的任何註解之前。

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

CONSIDER 為私有 API 編寫文件註釋

#

文件註釋不僅適用於庫公共 API 的外部使用者。它們對於理解從庫的其他部分呼叫的私有成員也很有幫助。

DO 以一句摘要開始文件註釋

#

以一個簡短的、以使用者為中心的描述開始你的文件註釋,並以句號結尾。一個句子片段通常就足夠了。提供足夠的上下文,以便讀者瞭解情況並決定是否繼續閱讀或尋找其他解決方案。

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

DO 將文件註釋的第一句話分成單獨的段落

#

在第一句話後新增一個空行,將其分成一個單獨的段落。如果需要不止一句話的解釋,將其餘部分放在後面的段落中。

這有助於你編寫一個簡潔的第一句話,概括文件內容。此外,像 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) {
  ...
}

AVOID 與周圍上下文重複

#

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

良好dart
class RadioButtonWidget extends Widget {
  /// Sets the tooltip to [lines].
  ///
  /// The lines should be 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) {
    ...
  }
}

如果你確實沒有什麼有趣的內容可以從宣告本身推斷出來,那麼就省略文件註釋。與其浪費讀者的時間告訴他們已經知道的事情,不如什麼都不說。

PREFER 如果函式或方法的主要目的是副作用,則以第三人稱動詞開始其註釋

#

文件註釋應側重於程式碼做了什麼

良好dart
/// Connects to the server and fetches the query results.
Stream<QueryResult> fetchResults(Query query) => ...

/// Starts the stopwatch if not already running.
void start() => ...

PREFER 以名詞短語開始非布林變數或屬性的註釋

#

文件註釋應強調該屬性是什麼。即使對於可能進行計算或其他工作的 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 => ...

PREFER 以“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 => ...

PREFER 如果函式或方法的主要目的是返回值,則使用名詞短語或非祈使動詞短語來註釋

#

如果一個方法在語法上是一個方法,但在概念上它是一個屬性,因此用名詞短語或非祈使動詞短語命名,那麼也應該這樣編寫文件。對於此類非布林函式使用名詞短語,對於此類布林函式使用以“Whether”開頭的短語,就像對待語法屬性或變數一樣。

良好dart
/// The [index]th element of this iterable in iteration order.
E elementAt(int index);

/// Whether this iterable contains an element equal to [element].
bool contains(Object? element);

DON'T 為屬性的 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) => ...

PREFER 以名詞短語開始庫或型別的註釋

#

類的文件註釋通常是程式中最重要的文件。它們描述了型別的不變性、建立了其使用的術語,併為類的其他成員的文件註釋提供了上下文。在這裡多花一點功夫,可以使所有其他成員的文件編寫變得更簡單。

文件應該描述該型別的一個例項

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

CONSIDER 在文件註釋中包含程式碼示例

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

人類擅長從示例中進行歸納,因此即使只有一個程式碼示例也能使 API 更易於學習。

DO 在文件註釋中使用方括號來引用作用域內的識別符號

#

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 doc 在文件註釋中支援的引用,請檢視 文件註釋引用

DO 使用散文解釋引數、返回值和異常

#

其他語言使用冗長的標籤和節來描述方法的引數和返回值。

不良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 abbreviation) => ...

Dart 中的約定是將其整合到方法描述中,並使用方括號突出顯示引數。

考慮使用以“The [parameter]”開頭的段落來描述引數,用“Returns”描述返回值,用“Throws”描述異常。錯誤可以像異常一樣進行文件化,或者僅僅作為必須滿足的要求進行文件化,而無需記錄將丟擲的確切錯誤。

良好dart
/// Defines a flag with the given [name] and [abbreviation].
///
/// The [name] and [abbreviation] strings must not be empty.
///
/// Returns a new flag.
///
/// Throws a [DuplicateFlagException] if there is already an option named
/// [name] or there is already an option using the [abbreviation].
Flag addFlag(String name, String abbreviation) => ...

DO 將文件註釋放在元資料註解之前

#
良好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

AVOID 過度使用 Markdown

#

不確定時,少用格式。格式化的目的是闡明內容,而不是取代內容。文字才是關鍵。

AVOID 使用 HTML 進行格式化

#

在極少數情況下,例如表格,使用它可能有用,但在幾乎所有情況下,如果內容太複雜以至於無法用 Markdown 表達,你最好不要表達它。

PREFER 使用反引號圍欄標記程式碼塊

#

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."

寫作

#

我們認為自己是程式設計師,但原始檔中的大多數字符主要是供人類閱讀的。英語是我們編寫程式碼的語言,用來改變同事的思維。與任何程式語言一樣,投入精力提高熟練度是值得的。

本節列出了我們文件的一些指南。你可以從諸如 技術寫作風格 等文章中瞭解更多關於技術寫作的最佳實踐。

PREFER 簡潔

#

清晰精確,同時也要簡潔。

AVOID 避免使用縮寫詞和首字母縮略詞,除非它們顯而易見

#

許多人不知道“i.e.”、“e.g.”和“et al.”是什麼意思。你確信你所在領域的每個人都知道的那個縮寫詞,可能並沒有你想象的那麼廣為人知。

PREFER 使用“this”而不是“the”來指代成員的例項

#

在為類的成員編寫文件時,你經常需要回溯到該成員被呼叫的物件。使用“the”可能會引起歧義。更傾向於在“this”之後加上一些限定詞,單獨的“this”也可能引起歧義。

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

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