跳转至

Dart/Flutter 编码风格

本文件使用 Dart 和 Flutter 特定内容扩展 common/coding-style.md。

格式化

  • 对所有 .dart 文件使用 dart format — 在 CI 中强制执行 (dart format --set-exit-if-changed .)
  • 行长度:80 个字符(dart format 的默认值)
  • 为了改善差异和格式化,多行参数/形参列表应添加尾随逗号

不可变性

  • 局部变量优先使用 final,编译时常量优先使用 const
  • 如果所有字段都是 final,则使用 const 构造函数
  • 公共 API 应返回不可变集合(List.unmodifiable、Map.unmodifiable)
  • 在不可变状态类中修改状态时使用 copyWith()
// BAD
var count = 0;
List<String> items = ['a', 'b'];

// GOOD
final count = 0;
const items = ['a', 'b'];

命名规则

遵循 Dart 约定: - 变量、参数、命名构造函数使用 camelCase - 类、枚举、typedef、扩展使用 PascalCase - 文件名和库名使用 snake_case - 顶层以 const 声明的常量使用 SCREAMING_SNAKE_CASE - 私有成员添加 _ 前缀 - 扩展名应表示被扩展的类型:使用 StringExtensions 而不是 MyHelpers

Null 安全性

  • 避免使用 !(bang 运算符)— 优先使用 ?.、??、if (x != null) 或 Dart 3 模式匹配。仅当 null 表示程序错误且崩溃是适当行为时才使用 !
  • 仅在能保证首次使用前已初始化时才使用 late(优先使用可空类型或构造函数初始化)
  • 对于必须提供的构造函数参数使用 required
// BAD — user が null の場合、実行時にクラッシュする
final name = user!.name;

// GOOD — null対応演算子を使用
final name = user?.name ?? 'Unknown';

// GOOD — Dart 3 パターンマッチング (網羅的、コンパイラによるチェック)
final name = switch (user) {
  User(:final name) => name,
  null => 'Unknown',
};

// GOOD — 早期リターンによる null ガード
String getUserName(User? user) {
  if (user == null) return 'Unknown';
  return user.name; // ガードの後、非nullに昇格
}

sealed 类型与模式匹配(Dart 3+)

使用 sealed 类对封闭状态层次结构建模:

sealed class AsyncState<T> {
  const AsyncState();
}

final class Loading<T> extends AsyncState<T> {
  const Loading();
}

final class Success<T> extends AsyncState<T> {
  const Success(this.data);
  final T data;
}

final class Failure<T> extends AsyncState<T> {
  const Failure(this.error);
  final Object error;
}

对 sealed 类型始终使用穷尽的 switch — 不要使用 default/通配符:

// BAD
if (state is Loading) { ... }

// GOOD
return switch (state) {
  Loading() => const CircularProgressIndicator(),
  Success(:final data) => DataWidget(data),
  Failure(:final error) => ErrorWidget(error.toString()),
};

错误处理

  • 使用 on 子句指定异常类型 — 绝不使用裸的 catch (e)
  • 绝不捕获 Error 子类型 — 它们表示程序 bug
  • 对可恢复错误使用 Result 风格类型或 sealed 类
  • 不要将异常用于控制流
// BAD
try {
  await fetchUser();
} catch (e) {
  log(e.toString());
}

// GOOD
try {
  await fetchUser();
} on NetworkException catch (e) {
  log('Network error: ${e.message}');
} on NotFoundException {
  handleNotFound();
}

异步 / Future

  • 始终 await Future,或为表示有意的 fire-and-forget 而显式调用 unawaited()
  • 如果没有 await 任何内容,不要将函数标记为 async
  • 并发操作使用 Future.wait / Future.any
  • 在 await 之后使用 BuildContext 之前检查 context.mounted(Flutter 3.7+)
// BAD — Future を無視している
fetchData(); // 意図を示さずにfire-and-forget

// GOOD
unawaited(fetchData()); // 明示的なfire-and-forget
await fetchData();      // または適切に await する

导入

  • 在整个项目中一致使用 package: 导入 — 跨功能或跨层代码不要使用相对导入(../)
  • 顺序:dart: → 外部 package: → 内部 package:(同一包)
  • 禁止未使用的导入 — dart analyze 会通过 unused_import 强制执行

代码生成

  • 生成的文件(.g.dart、.freezed.dart、.gr.dart)要么提交,要么通过 gitignore 一致地排除 — 每个项目选择一种策略
  • 不要手动编辑生成的文件
  • 生成器注解(@JsonSerializable、@freezed、@riverpod 等)只写在常规源文件中

本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 ev/ECC