跳到主内容

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;
}