目錄

高效 Dart 語言指南:文件

你可能沒有意識到,今天你很容易想出來的程式碼,有多麼依賴你當時思路。人們不熟悉你的程式碼,甚至你也忘記了當時程式碼功能中有這樣的思路。寫下簡明扼要的註釋只需要幾秒鐘,但可以讓看這段程式碼的每個人節約幾個小時。

我們知道程式碼應該自文件(self-documenting),並不是所有的註釋都是有用的。但實際情況是,我們大多數人都沒有寫出儘可能多的註釋。和練習一樣:從技術上你可以做很多,但是實際上你只做了一點點。嘗試著逐步提高。

註釋

下面的提示適用於不被產生在文件中的註釋。

像句子一樣來格式化註釋。

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

如果第一個單詞不是大小寫相關的識別符號,則首字母要大寫。使用句號,歎號或者問號結尾。所有的註釋都應該這樣:文件註釋,單行註釋,甚至 TODO。即使它是一個句子的片段。

不要 使用塊註釋作用作解釋說明。

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

可以使用塊註釋 (/* ... */) 來臨時的註釋掉一段程式碼,但是其他的所有註釋都應該使用 //

文件註釋

文件註釋特別有用,應為透過 dart doc 解析這些註釋可以產生 漂亮的文件網頁。文件註釋包括所有出現在宣告之前並使用 /// 語法的註釋,這些註釋使用使用 dartdoc 檢索。

使用 /// 文件註釋來註釋成員和型別。

Linter rule: slash_for_doc_comments

使用文件註釋可以讓 [dartdoc][] 來為你產生程式碼 API 文件。

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

由於歷史原因,dartdoc 支援兩種格式的文件註釋:/// (“C# 格式”) 和 /** ... */ (“JavaDoc 格式”)。我們推薦使用 /// 是因為其更加簡潔。 /***/ 在多行註釋中間添加了開頭和結尾的兩行多餘內容。 /// 在一些情況下也更加易於閱讀,例如,當文件註釋中包含有使用 * 標記的列表內容的時候。

如果你現在還在使用 JavaDoc 風格格式,請考慮使用新的格式。

推薦 為公開發布的 API 編寫文件註釋。

Linter rules: package_api_docs, public_member_api_docs

不必為所有獨立的函式庫,最上層變數,型別以及成員編寫文件註釋。但是它們大多數應該有文件註釋。

考慮 編寫庫級別(library-level)的文件註釋。

與Java等類似的語言不同,Java 中類是程式組織的唯一單元。在 Dart 中,庫本身就是一個實體,使用者可以直接使用,匯入及構思它。這使得成為一個展示文件的好地方。這樣可以向讀者介紹其中提供的主要概念和功能。其中可以考慮包括下列內容:

  • 關於庫用途的單句摘要。

  • 解釋庫中用到的術語。

  • 一些配合 API 的範例程式碼。

  • 最重要和最常用的類和函式的連結。

  • 和庫相關領域的外部連結。

若要給某個庫產生文件,你需要在 library 和任何可能會放置在檔案開頭的註釋之前,加入文件註釋。

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

推薦 為私有API 編寫文件註釋。

文件註釋不僅僅適用於外部使用者使用你庫的公開 API. 它也同樣有助於理解那些私有成員,這些私有成員會被庫的其他部分呼叫。

要在文件註釋開頭有一個單句總結。

註釋文件要以一個以使用者為中心,簡要的描述作為開始。通常句子片段就足夠了。為讀者提供足夠的上下文來定位自己,並決定是否應該繼續閱讀,或尋找其他解決問題的方法。

/// Deletes the file at [path] from the file system.
void delete(String path) {
  ...
}
/// 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) {
  ...
}

讓文件註釋的第一句從段落中分開。

在第一句之後新增一個空行,將其拆分為自己的段落。如果不止一個解釋句子有用,請將其餘部分放在後面的段落中。

這有助於您編寫一個緊湊的第一句話來總結文件。此外,像Dartdoc這樣的工具使用第一段作為類和類成員列表等地方的簡短摘要。

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

避免 與周圍上下文冗餘。

閱讀類別的文件註釋的可以清楚地看到類別的名稱,它實現的介面等等。當讀取成員的文件時,命名和封裝它的類是顯而易見的。這些都不需要寫在文件註釋中。相反,應該專注於解釋讀者所不知道的內容。

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

If you really don’t have anything interesting to say that can’t be inferred from the declaration itself, then omit the doc comment. It’s better to say nothing than waste a reader’s time telling them something they already know.

推薦 用第三人稱來開始函式或者方法的文件註釋。

註釋應該關注於程式碼 所實現的 功能。

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

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

推薦 使用名詞短語來為非布林值變數或屬性註釋。

註釋文件應該表述這個屬性什麼。雖然 getter 函式會做些計算,但是也要求這樣,呼叫者關心的是其結果而不是如何計算的。

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

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

PREFER starting a boolean variable or property comment with “Whether” followed by a noun or gerund phrase

The doc comment should clarify the states this variable represents. This is true even for getters which may do calculation or other work. What the caller cares about is the result of that work, not the work itself.

/// 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 => ...

DON’T write documentation for both the getter and setter of a property

如果一個屬性同時包含 getter 和 setter,請只為其中一個新增文件。 dart doc 命令會將 getter 和 setter 作為同一個屬性進行處理,而如果它們都包含文件註釋,dart docs 命令會將 setter 的文件忽略。

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

推薦 使用名詞短語來開始庫和型別註釋。

在程式中,類別的註釋通常是最重要的文件。類別的註釋描述了型別的不變性、介紹其使用的術語、提供類成員使用的上下文資訊。為類提供一些註釋可以讓其他類成員的註釋更易於理解和編寫。

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

考慮 在文件註釋中新增範例程式碼。

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

人類非常擅長從範例中抽象出實質內容,所以即使提供一行最簡單的範例程式碼都可以讓 API 更易於理解。

使用方括號在文件註釋中參考作用域內的識別符號

Linter rule: comment_references

如果給變數,方法,或型別等名稱加上方括號,則 dartdoc 會查詢名稱並連結到相關的 API 文件。括號是可選的,但是當你在參考一個方法或者建構函式時,可以讓註釋更清晰。

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

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

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

點語法也可用於參考命名建構函式。對於未命名的建構函式,在類別名稱後面加上 .new 來使用預設構造:

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

使用平白簡單的陳述式來描述引數、返回值以及例外資訊。

其他語言使用各種標籤和額外的註釋來描述引數和返回值。

/// 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 的慣例是將其整合到方法的描述中,使用方括號高亮並標記引數。

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

把註釋文件放到註解之前。

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

Markdown

文件註釋中允許使用大多數 markdown 格式,並且 dartdoc 會根據 markdown package. 進行解析。

有很多指南已經向您介紹Markdown。它普遍受歡迎是我們選擇它的原因。這裡只是一個簡單的例子,讓您瞭解所支援的內容:

/// 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 有兩種方式來標註一塊程式碼:每行程式碼縮排4個空格,或者在程式碼上下各標註三個反引號。當縮排已經包含其他意義,或者程式碼段自身就包含縮排時,在 Markdown 中使用前一種語法就顯得很脆弱。

反引號語法避免了那些縮排的問題,而且能夠指出程式碼的語言型別,內聯程式碼同樣可以使用反引號標註。

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

如何寫註釋

雖然我們認為我們是程式設計師,但是大部分情況下原始碼中的內容都是為了讓人類更易於閱讀和理解程式碼。對於任何程式語言,都值得努力練習來提高熟練程度。

This section lists a few guidelines for our docs. You can learn more about best practices for technical writing, in general, from articles such as Technical writing style.

推薦 簡潔.

要清晰和準確,並且簡潔。

避免 縮寫和首字母縮寫詞,除非很常見。

很多人都不知道 “i.e.”、 “e.g.” 和 “et. al.” 的意思。你所在領域的首字母縮略詞對於其他人可能並不瞭解。

推薦 使用 “this” 而不是 “the” 來參考例項成員。

註釋中提及到類別的成員,通常是指被呼叫的物件例項的成員。使用 “the” 可能會導致混淆。

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

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