目录

“Effective Dart:文档”

很容易认为你今天的代码很清晰,却没意识到你有多依赖脑子里已有的上下文。你的代码新手,甚至是你健忘的未来自我,都不会拥有那种上下文。简洁准确的注释只需几秒钟就能写完,但却可以为这些人节省数小时的时间。

我们都知道代码应该是自文档化的,并非所有注释都有帮助。但现实是,我们大多数人写的注释并没有我们应该写的那么多。这就像锻炼:从技术上讲,你 可以 做得太多,但你做得太少的可能性要大得多。试着加把劲。

注释

#

以下技巧适用于您不希望包含在生成的文档中的注释。

应将注释格式化为句子

#
gooddart
// 除非前面有任何内容,否则不会执行此操作。
if (_chunks.isNotEmpty) return false;

除非它是区分大小写的标识符,否则应将首字母大写。以句点(或者“!”或“?”,我想)结尾。这适用于所有注释:文档注释、内联内容,甚至是 TODO。即使它是句子片段也是如此。

不要使用块注释进行文档编写

#
gooddart
void greet(String name) {
  // 假设我们有一个有效的名称。
  print('Hi, $name!');
}
baddart
void greet(String name) {
  /* 假设我们有一个有效的名称。 */
  print('Hi, $name!');
}

您可以使用块注释( /* ... */ )临时注释掉一段代码,但所有其他注释都应使用 //

文档注释

#

文档注释特别方便,因为 dart doc 会解析它们并从中生成 漂亮的文档页面 。文档注释是出现在声明之前的任何注释,并使用 dart doc 查找的特殊 /// 语法。

应使用 /// 文档注释来记录成员和类型

#

Linter 规则:

使用文档注释而不是常规注释,使 dart doc 能够找到它并为其生成文档。

gooddart
/// 未拆分时此块中的字符数。
int get length => ...
baddart
// 未拆分时此块中的字符数。
int get length => ...

由于历史原因, dart doc 支持两种文档注释语法: /// (“C# 风格”)和 /** ... */ (“JavaDoc 风格”)。我们更喜欢 /// ,因为它更紧凑。 /***/ 在多行文档注释中添加了两行无内容的行。在某些情况下, /// 语法也更容易阅读,例如,当文档注释包含使用 * 来标记列表项的项目符号列表时。

如果您偶然发现仍然使用 JavaDoc 风格的代码,请考虑清理它。

最好为公共 API 编写文档注释

#

Linter 规则:

您不必记录每个库、顶级变量、类型和成员,但您应该记录其中大部分。

考虑编写库级文档注释

#

与 Java 等语言不同,在 Java 中,类是程序组织的唯一单元,在 Dart 中,库本身就是用户直接使用、导入和考虑的实体。这使得 library 指令成为介绍读者了解提供的核心概念和功能的绝佳文档位置。考虑包括:

  • 库用途的单句摘要。
  • 对整个库中使用的术语的解释。
  • 一些完整的代码示例,指导如何使用 API。
  • 指向最重要或最常用的类和函数的链接。
  • 指向库关注领域的外部参考的链接。

要记录库,请在 library 指令之前放置文档注释以及可能附加在文件开头的任何注释。

gooddart
/// 一个非常棒的测试库。
@TestOn('browser')
library;

考虑为私有 API 编写文档注释

#

文档注释不仅仅用于库公共 API 的外部使用者。它们也可以帮助理解从库的其他部分调用的私有成员。

应以单句摘要开头文档注释

#

以简短的、以用户为中心的描述开头您的文档注释,并在句末加上句点。句子片段通常就足够了。提供足够的上下文,让读者能够确定方向,并决定他们是否应该继续阅读或在其他地方寻找问题的解决方案。

gooddart
/// 从文件系统中删除 [path] 处的文件。
void delete(String path) {
  ...
}
baddart
/// 根据文件系统的状态和用户的权限,某些操作可能成功也可能失败。如果 [path] 处没有文件或无法访问它,则此函数分别抛出 [IOError][PermissionError]。否则,这将删除文件。
void delete(String path) {
  ...
}

应将文档注释的第一句话分成单独的一段

#

在第一句之后添加一个空行,将其分成单独的一段。如果超过一句解释是有用的,请将其余部分放在后面的段落中。

这有助于您编写一个简洁的第一句话,以总结文档。此外,像 dart doc 这样的工具会在类和成员列表等位置使用第一段作为简短摘要。

gooddart
/// 删除 [path] 处的文件。
///
/// 如果找不到文件,则抛出 [IOError]。如果文件存在但无法删除,则抛出 [PermissionError]
void delete(String path) {
  ...
}
baddart
/// 删除 [path] 处的文件。如果找不到文件,则抛出 [IOError]。如果文件存在但无法删除,则抛出 [PermissionError]
void delete(String path) {
  ...
}

避免与周围上下文出现冗余

#

类的文档注释的阅读者可以清楚地看到类的名称、它实现的接口等等。阅读成员的文档时,签名就在那里,封闭类也很明显。文档注释不需要详细说明这些内容。相反,重点解释读者 不知道 的内容。

gooddart
class RadioButtonWidget extends Widget {
  /// 将工具提示设置为 [lines],它应该使用当前字体进行过换行处理。
  void tooltip(List<String> lines) {
    ...
  }
}
baddart
class RadioButtonWidget extends Widget {
  /// 将此单选按钮小部件的工具提示设置为 [lines] 中的字符串列表。
  void tooltip(List<String> lines) {
    ...
  }
}

如果您真的没有什么有趣的话要说,而这些话不能从声明本身推断出来,那么就省略文档注释。不说任何话总比浪费读者的时间来告诉他们他们已经知道的事情要好。

最好以第三人称动词开头函数或方法注释

#

文档注释应重点说明代码 做什么

gooddart
/// 如果每个元素都满足 [predicate],则返回 `true`
bool all(bool predicate(T element)) => ...

/// 如果尚未运行,则启动秒表。
void start() {
  ...
}

最好以名词短语开头非布尔变量或属性注释

#

文档注释应该强调属性 是什么 。即使对于可能进行计算或其他工作的 getter 也是如此。调用者关心的是这项工作的 结果 ,而不是工作本身。

gooddart
/// 本周的当前日期,其中 `0` 是星期日。
int weekday;

/// 页面上已选中按钮的数量。
int get checkedCount => ...

最好以“Whether”后跟名词或动名词短语开头布尔变量或属性注释

#

文档注释应阐明此变量代表的状态。即使对于可能进行计算或其他工作的 getter 也是如此。调用者关心的是这项工作的 结果 ,而不是工作本身。

gooddart
/// 模态当前是否显示给用户。
bool isVisible;

/// 模态是否应在导航时确认用户的意图。
bool get shouldConfirm => ...

/// 是否调整当前浏览器窗口的大小也会调整模态的大小。
bool get canResize => ...

不要同时为属性的 getter 和 setter 编写文档

#

如果属性同时具有 getter 和 setter,则只为其中一个创建文档注释。 dart doc 将 getter 和 setter 视为单个字段,如果 getter 和 setter 都有文档注释,则 dart doc 将丢弃 setter 的文档注释。

gooddart
/// 水池中水的 pH 值。
///
/// 范围为 0-14,代表酸性到碱性,7 为中性。
int get phLevel => ...
set phLevel(int level) => ...
baddart
/// 水池中水的深度(米)。
int get waterDepth => ...

/// 将水深更新为总共 [meters] 米。
set waterDepth(int meters) => ...

最好以名词短语开头库或类型注释

#

类的文档注释通常是程序中最重要的文档。它们描述类型的不变式,建立其使用的术语,并为类的成员的其他文档注释提供上下文。在这里多花一点力气可以使所有其他成员更容易记录。

gooddart
/// 由硬换行符或软换行符终止的连续不断输出文本块。
///
/// ...
class Chunk { ... }

考虑在文档注释中包含代码示例

#
gooddart
/// 返回两个较小的数字。
///
/// ```dart
/// min(5, 3) == 3
/// ```
num min(num a, num b) => ...

人类擅长从示例中进行概括,因此即使是单个代码示例也能使 API 更易于学习。

应在文档注释中使用方括号来引用作用域内的标识符

#

Linter 规则:

如果您用方括号括起来变量、方法或类型名称等内容,则 dart doc 会查找名称并链接到相关的 API 文档。括号是可选的,但在引用方法或构造函数时可以使它更清晰。

gooddart
/// 如果 ... 则抛出 [StateError]
/// 与 [anotherMethod()] 类似,但 ...

要链接到特定类的成员,请使用类名和成员名,并用点号分隔:

gooddart
/// 与 [Duration.inDays] 类似,但处理小数天数。

点语法也可用于引用命名构造函数。对于未命名的构造函数,在类名之后使用 .new

gooddart
/// 要创建一个点,请调用 [Point.new] 或使用 [Point.polar] 来…

应使用散文解释参数、返回值和异常

#

其他语言使用冗长的标签和部分来描述方法的参数和返回值是什么。

baddart
/// 使用给定的名称和缩写定义一个标志。
///
/// @param name 标志的名称。
/// @param abbr 标志的缩写。
/// @returns 新标志。
/// @throws ArgumentError 如果已经存在具有给定名称或缩写的选项。
Flag addFlag(String name, String abbr) => ...

Dart 中的约定是将其集成到方法的描述中,并使用方括号突出显示参数。

gooddart
/// 定义一个标志。
///
/// 如果已经存在名为 [name] 的选项或已经存在使用缩写 [abbr] 的选项,则抛出 [ArgumentError]。返回新标志。
Flag addFlag(String name, String abbr) => ...

应将文档注释放在元数据注释之前

#
gooddart
/// 一个可以打开和关闭的按钮。
@Component(selector: 'toggle')
class ToggleComponent {}
baddart
@Component(selector: 'toggle')
/// 一个可以打开和关闭的按钮。
class ToggleComponent {}

Markdown

#

您可以在文档注释中使用大多数 Markdown 格式,并且 dart doc 将使用 Markdown 包 相应地处理它。

已经有大量的指南可以向您介绍 Markdown。其普遍的流行性是我们选择它的原因。这是一个快速示例,让您了解支持的功能:

dart
/// 这是一个普通文本段落。
///
/// 这句话有两个 *强调* 的词(斜体)和 **两个** _ _强_ _调的词(粗体)。
///
/// 空行会创建一个单独的段落。它有一些用反引号分隔的 `内联代码`
///
/// * 无序列表。
/// * 看起来像 ASCII 项目符号列表。
/// * 你也可以使用 `-``+`
///
/// 1. 有序列表。
/// 2. 是,编号的。
/// 1. 但值无关紧要。
///
///     * 你也可以嵌套列表。
///     * 它们必须至少缩进 4 个空格。
///     * (好吧,包括 `///` 后面的空格在内是 5 个。)
///
/// 代码块用三个反引号括起来:
///
/// ```dart
/// this.code
///     .will
///     .retain(its, formatting);
/// ```
///
/// 代码语言(用于语法高亮显示)默认为 Dart。您可以通过在起始反引号之后放置语言名称来指定它:
///
/// ```html
/// <h1>HTML is magical!</h1>
/// ```
///
/// 链接可以是:
///
/// * https://www.just-a-bare-url.com
/// * [带有内联 URL](https://google.com)
/// * [或分开][ref link]
///
/// [ref link]: https://google.com
///
/// # 一个标题
///
/// ## 一个子标题
///
/// ### 一个子子标题
///
/// #### 如果你需要这么多级别的标题,那就是你的方法错了

避免过度使用 Markdown

#

如有疑问,请减少格式化。格式化的目的是阐明您的内容,而不是替换它。文字才是最重要的。

避免使用 HTML 进行格式化

#

在极少数情况下(例如表格),它 可能 有用,但在几乎所有情况下,如果在 Markdown 中表达过于复杂,最好不要表达它。

最好使用反引号围栏表示代码块

#

Markdown 有两种方法可以指示代码块:在每一行的代码前面缩进四个空格,或者将其包围在一对三个反引号“围栏”行中。当在 Markdown 列表等内容中使用时,前一种语法很脆弱,在这些内容中缩进已经具有意义,或者当代码块本身包含缩进代码时也是如此。

反引号语法避免了这些缩进问题,允许您指示代码的语言,并且与使用反引号表示内联代码一致。

gooddart
/// 你可以像这样使用 [CodeBlockExample]
///
/// ```dart
/// var example = CodeBlockExample();
/// print(example.isItGreat); // "Yes."
/// ```
baddart
/// 你可以像这样使用 [CodeBlockExample]
///
///     var example = CodeBlockExample();
///     print(example.isItGreat); // "Yes."

写作

#

我们认为自己是程序员,但源文件中的大部分字符主要是供人类阅读的。英语是我们用来修改同事大脑的编程语言。对于任何编程语言,都有必要努力提高您的熟练程度。

本节列出了我们文档的一些指南。一般来说,您可以从诸如 技术写作风格 之类的文章中了解有关技术写作最佳实践的更多信息。

最好简洁

#

清晰准确,但也简洁。

避免使用缩写和首字母缩略词,除非它们很明显

#

许多人不知道“即”、“例如”和“等人”是什么意思。您确信您所在领域的每个人都知道的首字母缩略词可能不像您想象的那么广为人知。

最好使用“this”而不是“the”来指代成员的实例

#

在为类记录成员时,您通常需要参考调用成员的对象。“the”可能会产生歧义。

dart
class Box {
  /// 此包装的值。
  Object? _value;

  /// 如果此框包含值,则为真。
  bool get hasValue => _value != null;
}