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¶
- 始终
awaitFuture,或为表示有意的 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