dartdoc-conventions

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Dartdoc Conventions — the public surface is a contract

Dartdoc 规范——公开 API 就是契约

Public code is read far more than it is written. A symbol with no leading
_
is a contract callers depend on, so it carries a
///
doc a reader understands without opening the body. Doc comments use
///
and follow Effective Dart: Documentation. Applies whenever you add or change a public declaration, or write an in-body
//
comment.
公共代码被阅读的次数远多于被编写。一个不以
_
开头的符号就是调用方所依赖的契约,因此必须带有
///
文档,让读者无需查看实现即可理解。文档注释使用
///
,并遵循 Effective Dart: Documentation。每当新增或修改公开声明,或编写函数体内的
//
注释时,都应遵循此规范。

Non-negotiable rules

不可妥协的规则

  1. Every public declaration gets a
    ///
    doc
    — public classes, constructors, methods, getters, top-level functions, typedefs, and fields.
    public_member_api_docs
    is an error with no "obvious member" exemption: it flags every undocumented public member, so a missing doc fails the build. Making a symbol public "just in case" is a review reject — make it
    _
    -private instead so it needs no doc and no contract.
  2. ///
    , never
    /** */
    .
    Dartdoc only recognizes
    ///
    . A JavaDoc block is silently ignored and the symbol reads as undocumented.
    slash_for_doc_comments
    flags it.
  3. First line is one standalone sentence ending in a period, in its own paragraph. Tools show only this sentence in API lists, so it must stand alone; a blank
    ///
    line separates it from the body.
  4. Method/function docs start with a verb phrase (third person): "Returns…", "Schedules…", "Loads…", "Marks…". A boolean getter or
    bool
    -returning method starts with "Whether…".
  5. Never restate the name.
    /// The name.
    on
    String name
    ,
    /// Returns the total.
    on
    total()
    — banned. If a public member has nothing to add beyond its name, that is the signal to make it
    _
    -private (a private member needs no doc, so the tension disappears). A member that must stay public still needs a real
    ///
    — never leave it public-and-undocumented; add meaning: units, ranges, nullability, throws, side effects, and the invariant the symbol enforces.
  6. Cross-link identifiers in
    [brackets]
    so dartdoc resolves them:
    /// Throws [StateError] if [id] is unknown; see [copyWith].
    comment_references
    warns on a broken link.
  7. In-body
    //
    explains why, never what.
    The code already says what. Narrating comments (
    // loop over items
    ) rot out of sync and become misinformation. Comment the reason, the gotcha, or the invariant.
  8. Restate an enforced invariant at its enforcement point. Where one line upholds a guarantee — an ordering, a clamp, a persist-before-publish, a canonical-unit conversion — a terse
    //
    states it so a diff that weakens it gets an unmissable flag. Same for a magic constant: cite where the number comes from.
  9. Docs change in the same diff as the code. A wrong doc is worse than none. Every comment your change touches must still be true — put it on the PR checklist.
  10. One library doc per exported barrel. The public entry point (e.g.
    my_package.dart
    ) gets a
    ///
    library doc above the
    library;
    directive.
    dangling_library_doc_comments
    is an error — a leading
    ///
    with no attached declaration must be a real library doc, not an orphan above a blank line or an
    export
    .
  1. 每个公开声明都要有
    ///
    文档
    ——包括公开的 class、构造函数、方法、getter、顶层函数、typedef 和 field。
    public_member_api_docs
    是 error,不存在所谓“显而易见的成员”豁免:它会标记每一个未写文档的公开成员,因此缺少文档就会导致构建失败。以“万一以后需要”为由将符号设为 public 会在审查中被拒绝——应改为使用
    _
    设为私有,这样既不需要文档,也不构成契约。
  2. 始终使用
    ///
    ,绝不使用
    /** */
    。
    Dartdoc 只识别
    ///
    。JavaDoc 风格的代码块会被静默忽略,导致该符号看起来没有文档。
    slash_for_doc_comments
    会标记此类问题。
  3. 首行是单独成句、以句号结尾的一句话,并独占一个段落。 工具在 API 列表中只显示这句话,因此它必须能够独立成立;用一行空白的
    ///
    将其与正文分隔开。
  4. 方法/函数文档以动词短语(第三人称)开头,例如“返回…”、“调度…”、“加载…”、“标记…”。布尔 getter 或返回
    bool
    的方法以“Whether…”开头。
  5. 绝不复述名称。
    String name
    上的
    /// The name.
    、
    total()
    上的
    /// Returns the total.
    都被禁止。如果某个公开成员除了名称之外没有可补充的信息,就应将其设为
    _
    私有(私有成员无需文档,于是这一矛盾自然消失)。必须保持公开的成员仍需提供真实的
    ///
    文档——绝不能让它处于“公开但无文档”的状态;应补充实际含义:单位、范围、可空性、抛出的异常、副作用,以及该符号所强制的不变量。
  6. 使用
    [方括号]
    交叉链接标识符
    ,以便 dartdoc 能够解析:
    /// Throws [StateError] if [id] is unknown; see [copyWith].
    comment_references
    会对断链发出警告。
  7. 函数体内的
    //
    注释解释为什么,绝不解释做什么。
    代码本身已经说明了做什么。叙述性注释(
    // loop over items
    )会逐渐与代码不同步,最终变成错误信息。应该注释原因、陷阱或不变量。
  8. 在执行点重申所强制的不变量。 当某一行代码维持某项保证——例如顺序、clamp、先持久化后发布、规范单位转换——应使用简洁的
    //
    说明这一点,使削弱该保证的 diff 一眼就能被发现。魔法常量也应如此:注明数字来源。
  9. 文档与代码在同一个 diff 中一起修改。 错误的文档比没有文档更糟。你改动到的每条注释都必须仍然属实——将其加入 PR 检查清单。
  10. 每个导出的 barrel 只有一个库文档。 公开入口(例如
    my_package.dart
    )应在
    library;
    指令上方添加
    ///
    库文档。
    dangling_library_doc_comments
    是 error——位于空行或
    export
    上方、没有附着到任何声明的前导
    ///
    ,必须是真正的库文档,而不能是悬空注释。

Documenting a value type

为 value type 编写文档

dart
/// A note the user has authored and can later edit or archive.
///
/// Immutable; derive changes with [copyWith]. [updatedAt] is UTC and never
/// precedes [createdAt].
class Note {
  /// Creates a note; [updatedAt] must not precede [createdAt].
  const Note({
    required this.id,
    required this.title,
    required this.createdAt,
    required this.updatedAt,
  });

  /// Stable unique identifier, assigned once at creation.
  final String id;

  /// Human-readable heading shown in lists; may be empty, never null.
  final String title;

  /// Creation instant, in **UTC**.
  final DateTime createdAt;

  /// Last-edit instant, in **UTC**; equals [createdAt] until first edited.
  final DateTime updatedAt;

  /// Returns a copy with the given fields replaced.
  Note copyWith({String? title, DateTime? updatedAt}) => Note(
        id: id,
        title: title ?? this.title,
        createdAt: createdAt,
        updatedAt: updatedAt ?? this.updatedAt,
      );
}
Every public field carries a
///
, including
title
— under the enforced
public_member_api_docs
a field that "restates the name" is not a licence to drop the doc but a prompt to say something real (or make the field
_
-private). Document units and constraints explicitly where the type does not carry them: minor currency units, a
0
–
23
hour, an inclusive/exclusive range, what
null
means, "must be
> 0
".
dart
/// A note the user has authored and can later edit or archive.
///
/// Immutable; derive changes with [copyWith]. [updatedAt] is UTC and never
/// precedes [createdAt].
class Note {
  /// Creates a note; [updatedAt] must not precede [createdAt].
  const Note({
    required this.id,
    required this.title,
    required this.createdAt,
    required this.updatedAt,
  });

  /// Stable unique identifier, assigned once at creation.
  final String id;

  /// Human-readable heading shown in lists; may be empty, never null.
  final String title;

  /// Creation instant, in **UTC**.
  final DateTime createdAt;

  /// Last-edit instant, in **UTC**; equals [createdAt] until first edited.
  final DateTime updatedAt;

  /// Returns a copy with the given fields replaced.
  Note copyWith({String? title, DateTime? updatedAt}) => Note(
        id: id,
        title: title ?? this.title,
        createdAt: createdAt,
        updatedAt: updatedAt ?? this.updatedAt,
      );
}
每个公开 field 都必须带有
///
文档,包括
title
——在启用的
public_member_api_docs
下,field“复述名称”并不是省略文档的许可,而是提示你应当说明真实信息(或将 field 设为
_
私有)。当类型本身无法表达这些约束时,应明确记录单位和约束:辅币单位、
0
–
23
小时、包含/不包含的边界范围、
null
的含义、“必须
> 0
”等。

Documenting behavior — throws, async, side effects

记录行为——异常、异步与副作用

dart
/// Schedules a reminder for [task] at [when].
///
/// [when] must be in the future; a past instant is clamped to now. Persists the
/// scheduled row before arming the OS notification, so a crash mid-call cannot
/// leave a notification the store never recorded.
///
/// Throws [PermissionDeniedException] if the OS denied notification permission.
Future<void> scheduleReminder(Task task, DateTime when) async {
  // persist-before-arm: the store is the source of truth, the OS mirrors it
  await _store.saveScheduled(task.id, when);
  await _plugin.schedule(task.id, when);
}
State async behavior, what a method writes or mutates, and every exception it can throw. If a function is total (returns for every input, never throws), say so — it is a real guarantee callers rely on.
dart
/// Schedules a reminder for [task] at [when].
///
/// [when] must be in the future; a past instant is clamped to now. Persists the
/// scheduled row before arming the OS notification, so a crash mid-call cannot
/// leave a notification the store never recorded.
///
/// Throws [PermissionDeniedException] if the OS denied notification permission.
Future<void> scheduleReminder(Task task, DateTime when) async {
  // persist-before-arm: the store is the source of truth, the OS mirrors it
  await _store.saveScheduled(task.id, when);
  await _plugin.schedule(task.id, when);
}
应说明异步行为、方法会写入或改变什么,以及所有可能抛出的异常。如果函数是 total(对任何输入都有返回且从不抛出异常),也应明确说明——这是调用方依赖的真实保证。

Library docs and the exported barrel

库文档与导出的 barrel

dart
/// Pure-Dart domain core: value types, typed failures, and total functions.
///
/// No Flutter, Riverpod, or `dart:io` import — this purity is what lets the
/// core be unit-tested without a widget harness and reused across platforms.
library;

export 'src/note.dart';
export 'src/task.dart';
The
///
attaches to the
library;
directive. Never leave a
///
dangling above an
export
or a blank line.
dart
/// Pure-Dart domain core: value types, typed failures, and total functions.
///
/// No Flutter, Riverpod, or `dart:io` import — this purity is what lets the
/// core be unit-tested without a widget harness and reused across platforms.
library;

export 'src/note.dart';
export 'src/task.dart';
///
会附着到
library;
指令。绝不要把
///
悬空放在
export
或空行上方。

Restating the invariant at the enforcement point

在执行点重申不变量

Where a symbol enforces a rule, document the rule as part of its contract and pin it in-body at the one line that upholds it:
dart
/// Failure modes of a checkout. Switched exhaustively by the caller, so a new
/// case is a compile error at every call site.
sealed class CheckoutFailure {
  /// Const base constructor, so subclasses can be `const`.
  const CheckoutFailure();
}

/// The account balance was below the order total; no charge was made.
final class InsufficientFunds extends CheckoutFailure {
  /// Creates an [InsufficientFunds] failure.
  const InsufficientFunds();
}

/// Read-only view of the current order; mutated only through this notifier.
class OrderNotifier extends Notifier<Order> {
  
  Order build() => Order.empty();

  /// Adds [item] and recomputes the total.
  ///
  /// Persists via the repository before emitting, so a mid-write crash cannot
  /// surface a line the store never saw.
  Future<void> addItem(Item item) async {
    // persist-before-emit: emitted state is always a state the store holds
    await _repository.append(item);
    state = state.withItem(item);
  }
}
当符号强制执行某项规则时,应将该规则作为契约的一部分记录下来,并在维持该规则的那一行代码中用函数体注释加以固定:
dart
/// Failure modes of a checkout. Switched exhaustively by the caller, so a new
/// case is a compile error at every call site.
sealed class CheckoutFailure {
  /// Const base constructor, so subclasses can be `const`.
  const CheckoutFailure();
}

/// The account balance was below the order total; no charge was made.
final class InsufficientFunds extends CheckoutFailure {
  /// Creates an [InsufficientFunds] failure.
  const InsufficientFunds();
}

/// Read-only view of the current order; mutated only through this notifier.
class OrderNotifier extends Notifier<Order> {
  
  Order build() => Order.empty();

  /// Adds [item] and recomputes the total.
  ///
  /// Persists via the repository before emitting, so a mid-write crash cannot
  /// surface a line the store never saw.
  Future<void> addItem(Item item) async {
    // persist-before-emit: emitted state is always a state the store holds
    await _repository.append(item);
    state = state.withItem(item);
  }
}

Anti-patterns

反模式

  • /** JavaDoc-style */
    — dartdoc ignores it; the symbol reads as undocumented and fails
    public_member_api_docs
    .
  • Restating the name —
    /// Gets the id.
    on
    String get id
    . Delete it or add units/ranges/invariants.
  • A paragraph before the one-sentence summary — the first sentence must stand alone in list views.
  • Narrating mechanics in-body —
    // loop over items
    ,
    i++ // increment
    . Explain why, or delete.
  • A magic constant with no source — a bare
    3
    for a cap with no comment saying where the number is defined.
  • Stale docs describing pre-refactor parameters, or an invariant comment left on code that no longer honors it.
  • Commented-out code "just in case" — git remembers.
    // TODO
    with no owner or issue link
    — write
    // TODO(name): reason
    or nothing.
  • Documenting private trivia while a public method sits undocumented.
  • A dangling
    ///
    above a blank line or an
    export
    — fails
    dangling_library_doc_comments
    .
  • Banner / ASCII-art comment dividers that bloat files; rely on structure and naming.
  • /** JavaDoc 风格 */
    ——dartdoc 会忽略它;该符号会显示为未写文档,并触发
    public_member_api_docs
    。
  • 复述名称——
    String get id
    上的
    /// Gets the id.
    。应删除它,或补充单位/范围/不变量。
  • 在单句摘要之前添加段落——首句在列表视图中必须能够独立成立。
  • 在函数体内叙述实现过程——
    // loop over items
    、
    i++ // increment
    。应解释为什么,否则直接删除。
  • 没有来源的魔法常量——例如用裸写的
    3
    表示上限,却没有注释说明这个数字定义在哪里。
  • 过时的文档——描述重构前的参数,或不变量注释所对应的代码已不再满足该不变量。
  • 注释掉的代码——“以防万一”——
    git
    会一直保留它。没有负责人或 issue 链接的
    // TODO
    ——应写成
    // TODO(name): reason
    ,否则不要写。
  • 记录无关紧要的私有细节,同时却让某个公开方法没有文档。
  • 悬空的
    ///
    ——位于空行或
    export
    上方,会触发
    dangling_library_doc_comments
    。
  • Banner / ASCII-art 注释分隔符——只会让文件膨胀;应依靠结构和命名。

Definition of done

完成标准

  • Every public declaration in the touched code has a
    ///
    doc; first line is a standalone sentence ending in a period.
  • Method/function docs start with a verb; boolean getters start with "Whether".
  • Units, ranges, nullability, throws/
    Result
    , and side effects documented where they exist.
  • Identifiers cross-linked with
    [brackets]
    ; no broken
    comment_references
    .
  • Enforced invariants restated at their enforcement points; magic constants cite their source.
  • No restated-name docs, no narrating
    //
    , no commented-out code, no ownerless TODO, no JavaDoc blocks.
  • Each exported barrel has a
    library;
    doc; no dangling doc comments.
  • Every comment the diff touched is still true.
  • dart analyze --fatal-infos --fatal-warnings
    clean;
    dart doc
    generates without warnings for packages.
  • 所有被改动代码中的每个公开声明都有
    ///
    文档;首行是单独成句并以句号结尾的一句话。
  • 方法/函数文档以动词开头;布尔 getter 以“Whether”开头。
  • 在存在相关约束时,记录单位、范围、可空性、抛出的异常/
    Result
    和副作用。
  • 标识符使用
    [方括号]
    交叉链接;不存在失效的
    comment_references
    。
  • 在执行点重申所强制的不变量;魔法常量注明来源。
  • 不存在复述名称的文档、叙述实现的
    //
    、被注释掉的代码、没有负责人的 TODO 或 JavaDoc 代码块。
  • 每个导出的 barrel 都有
    library;
    文档;不存在悬空文档注释。
  • diff 改动到的每条注释都仍然属实。
  • dart analyze --fatal-infos --fatal-warnings
    检查通过;对于 package,
    dart doc
    生成文档时无警告。

Related skills

相关 skill

  • See
    lint-and-style-config
    for wiring
    public_member_api_docs
    ,
    dangling_library_doc_comments
    , and
    comment_references
    as analyzer errors.
  • See
    naming-conventions
    for the role-suffix names (
    Notifier
    /
    Repository
    /
    Service
    /
    Failure
    ) these docs describe.
  • See
    dart3-idioms-and-coding-standards
    for the sealed-type and total-function guarantees the docs promise.
  • See
    error-handling-typed-results
    for the
    Result
    /
    Failure
    contract a method doc must state.
  • 参阅
    lint-and-style-config
    ,了解如何将
    public_member_api_docs
    、
    dangling_library_doc_comments
    和
    comment_references
    配置为 analyzer error。
  • 参阅
    naming-conventions
    ,了解这些文档所描述的角色后缀命名(
    Notifier
    /
    Repository
    /
    Service
    /
    Failure
    )。
  • 参阅
    dart3-idioms-and-coding-standards
    ,了解文档所承诺的 sealed type 和 total function 保证。
  • 参阅
    error-handling-typed-results
    ,了解方法文档必须说明的
    Result
    /
    Failure
    契约。

References

参考资料