docs(zh-CN): sync Chinese docs with latest upstream changes

This commit is contained in:
neo
2026-03-21 12:55:58 +08:00
parent 0af0fbf40b
commit e73c2ffa34
85 changed files with 11028 additions and 747 deletions

View File

@@ -0,0 +1,480 @@
---
name: flutter-dart-code-review
description: 库无关的Flutter/Dart代码审查清单涵盖Widget最佳实践、状态管理模式BLoC、Riverpod、Provider、GetX、MobX、Signals、Dart惯用法、性能、可访问性、安全性和整洁架构。
origin: ECC
---
# Flutter/Dart 代码审查最佳实践
适用于审查 Flutter/Dart 应用程序的全面、与库无关的清单。无论使用哪种状态管理方案、路由库或依赖注入框架,这些原则都适用。
***
## 1. 通用项目健康度
* \[ ] 项目遵循一致的文件夹结构(功能优先或分层优先)
* \[ ] 关注点分离得当UI、业务逻辑、数据层
* \[ ] 部件中无业务逻辑;部件纯粹是展示性的
* \[ ] `pubspec.yaml` 是干净的 —— 没有未使用的依赖项,版本已适当固定
* \[ ] `analysis_options.yaml` 包含严格的 lint 规则集,并启用了严格的分析器设置
* \[ ] 生产代码中没有 `print()` 语句 —— 使用 `dart:developer` `log()` 或日志包
* \[ ] 生成的文件 (`.g.dart`, `.freezed.dart`, `.gr.dart`) 是最新的或在 `.gitignore`
* \[ ] 平台特定代码通过抽象进行隔离
***
## 2. Dart 语言陷阱
* \[ ] **隐式动态类型**:缺少类型注解导致 `dynamic` —— 启用 `strict-casts`, `strict-inference`, `strict-raw-types`
* \[ ] **空安全误用**:过度使用 `!`(感叹号操作符)而不是适当的空检查或 Dart 3 模式匹配 (`if (value case var v?)`)
* \[ ] **类型提升失败**:在可以使用局部变量类型提升的地方使用了 `this.field`
* \[ ] **捕获范围过宽**`catch (e)` 没有 `on` 子句;应始终指定异常类型
* \[ ] **捕获 `Error`**`Error` 子类型表示错误,不应被捕获
* \[ ] **未使用的 `async`**:标记为 `async` 但从未 `await` 的函数 —— 不必要的开销
* \[ ] **`late` 过度使用**:在可使用可空类型或构造函数初始化更安全的地方使用了 `late`;将错误推迟到运行时
* \[ ] **循环中的字符串拼接**:使用 `StringBuffer` 而不是 `+` 进行迭代式字符串构建
* \[ ] **`const` 上下文中的可变状态**`const` 构造器类中的字段不应是可变的
* \[ ] **忽略 `Future` 返回值**:使用 `await` 或显式调用 `unawaited()` 来表明意图
* \[ ] **在 `final` 可用时使用 `var`**:局部变量首选 `final`,编译时常量首选 `const`
* \[ ] **相对导入**:为保持一致性,使用 `package:` 导入
* \[ ] **暴露可变集合**:公共 API 应返回不可修改的视图,而不是原始的 `List`/`Map`
* \[ ] **缺少 Dart 3 模式匹配**:优先使用 switch 表达式和 `if-case`,而不是冗长的 `is` 检查和手动类型转换
* \[ ] **为多重返回值使用一次性类**:使用 Dart 3 记录 `(String, int)` 代替一次性 DTO
* \[ ] **生产代码中的 `print()`**:使用 `dart:developer` `log()` 或项目的日志包;`print()` 没有日志级别且无法过滤
***
## 3. 部件最佳实践
### 部件分解:
* \[ ] 没有单个部件的 `build()` 方法超过约 80-100 行
* \[ ] 部件按封装方式以及按变化方式(重建边界)进行拆分
* \[ ] 返回部件的私有 `_build*()` 辅助方法被提取到单独的部件类中(支持元素重用、常量传播和框架优化)
* \[ ] 在不需要可变局部状态的地方,优先使用无状态部件而非有状态部件
* \[ ] 提取的部件在可复用时放在单独的文件中
### Const 使用:
* \[ ] 尽可能使用 `const` 构造器 —— 防止不必要的重建
* \[ ] 对不变化的集合使用 `const` 字面量 (`const []`, `const {}`)
* \[ ] 当所有字段都是 final 时,构造函数声明为 `const`
### Key 使用:
* \[ ] 在列表/网格中使用 `ValueKey` 以在重新排序时保持状态
* \[ ] 谨慎使用 `GlobalKey` —— 仅在确实需要跨树访问状态时使用
* \[ ] 避免在 `build()` 中使用 `UniqueKey` —— 它会强制每帧都重建
* \[ ] 当身份基于数据对象而非单个值时,使用 `ObjectKey`
### 主题与设计系统:
* \[ ] 颜色来自 `Theme.of(context).colorScheme` —— 没有硬编码的 `Colors.red` 或十六进制值
* \[ ] 文本样式来自 `Theme.of(context).textTheme` —— 没有内联的 `TextStyle` 和原始字体大小
* \[ ] 已验证深色模式兼容性 —— 不假设浅色背景
* \[ ] 间距和尺寸使用一致的设计令牌或常量,而不是魔法数字
### Build 方法复杂度:
* \[ ] `build()` 中没有网络调用、文件 I/O 或繁重计算
* \[ ] `build()` 中没有 `Future.then()``async` 工作
* \[ ] `build()` 中没有创建订阅 (`.listen()`)
* \[ ] `setState()` 局部化到尽可能小的子树
***
## 4. 状态管理(与库无关)
这些原则适用于所有 Flutter 状态管理方案BLoC、Riverpod、Provider、GetX、MobX、Signals、ValueNotifier 等)。
### 架构:
* \[ ] 业务逻辑位于部件层之外 —— 在状态管理组件中BLoC、Notifier、Controller、Store、ViewModel 等)
* \[ ] 状态管理器通过依赖注入接收依赖,而不是内部构造它们
* \[ ] 服务或仓库层抽象数据源 —— 部件和状态管理器不应直接调用 API 或数据库
* \[ ] 状态管理器职责单一 —— 没有处理不相关职责的“上帝”管理器
* \[ ] 跨组件依赖遵循解决方案的约定:
***Riverpod** 中:提供者通过 `ref.watch` 依赖其他提供者是预期的 —— 仅标记循环或过度复杂的链
***BLoC**bloc 不应直接依赖其他 bloc —— 优先使用共享仓库或表示层协调
* 在其他解决方案中:遵循文档中关于组件间通信的约定
### 不可变性与值相等性适用于不可变状态解决方案BLoC、Riverpod、Redux
* \[ ] 状态对象是不可变的 —— 通过 `copyWith()` 或构造函数创建新实例,绝不就地修改
* \[ ] 状态类正确实现 `==``hashCode`(比较中包含所有字段)
* \[ ] 机制在整个项目中保持一致 —— 手动覆盖、`Equatable``freezed`、Dart 记录或其他方式
* \[ ] 状态对象内部的集合不作为原始可变的 `List`/`Map` 暴露
### 响应式纪律适用于响应式突变解决方案MobX、GetX、Signals
* \[ ] 状态仅通过解决方案的响应式 API 进行修改MobX 中的 `@action`Signals 上的 `.value`GetX 中的 `.obs`)—— 直接字段修改会绕过变更跟踪
* \[ ] 派生值使用解决方案的计算机制,而不是冗余存储
* \[ ] 反应和清理器被正确清理MobX 中的 `ReactionDisposer`Signals 中的 effect 清理)
### 状态形状设计:
* \[ ] 互斥状态使用密封类型、联合变体或解决方案内置的异步状态类型(例如 Riverpod 的 `AsyncValue`)—— 而不是布尔标志 (`isLoading`, `isError`, `hasData`)
* \[ ] 每个异步操作都将加载、成功和错误建模为不同的状态
* \[ ] UI 中详尽处理所有状态变体 —— 没有静默忽略的情况
* \[ ] 错误状态携带用于显示的错误信息;加载状态不携带陈旧数据
* \[ ] 可空数据不用于作为加载指示器 —— 状态是明确的
```dart
// BAD — boolean flag soup allows impossible states
class UserState {
bool isLoading = false;
bool hasError = false; // isLoading && hasError is representable!
User? user;
}
// GOOD (immutable approach) — sealed types make impossible states unrepresentable
sealed class UserState {}
class UserInitial extends UserState {}
class UserLoading extends UserState {}
class UserLoaded extends UserState {
final User user;
const UserLoaded(this.user);
}
class UserError extends UserState {
final String message;
const UserError(this.message);
}
// GOOD (reactive approach) — observable enum + data, mutations via reactivity API
// enum UserStatus { initial, loading, loaded, error }
// Use your solution's observable/signal to wrap status and data separately
```
### 重建优化:
* \[ ] 状态消费者部件Builder、Consumer、Observer、Obx、Watch 等)的范围尽可能窄
* \[ ] 使用选择器仅在特定字段变化时重建 —— 而不是每次状态发射时
* \[ ] 使用 `const` 部件来阻止重建在树中传播
* \[ ] 计算/派生状态是响应式计算的,而不是冗余存储的
### 订阅与清理:
* \[ ] 所有手动订阅 (`.listen()`) 在 `dispose()` / `close()` 中被取消
* \[ ] 流控制器在不再需要时关闭
* \[ ] 定时器在清理生命周期中被取消
* \[ ] 优先使用框架管理的生命周期,而不是手动订阅(声明式构建器优于 `.listen()`
* \[ ] 异步回调中在 `setState` 之前检查 `mounted`
* \[ ] 在 `await` 之后使用 `BuildContext` 而不检查 `context.mounted`Flutter 3.7+)—— 过时的上下文会导致崩溃
* \[ ] 在异步间隙后,没有在验证部件仍然挂载的情况下进行导航、显示对话框或脚手架消息
* \[ ] `BuildContext` 绝不存储在单例、状态管理器或静态字段中
### 本地状态与全局状态:
* \[ ] 临时 UI 状态(复选框、滑块、动画)使用本地状态 (`setState`, `ValueNotifier`)
* \[ ] 共享状态仅提升到所需的高度 —— 不过度全局化
* \[ ] 功能作用域的状态在功能不再活跃时被正确清理
***
## 5. 性能
### 不必要的重建:
* \[ ] 不在根部件级别调用 `setState()` —— 将状态变化局部化
* \[ ] 使用 `const` 部件来阻止重建传播
* \[ ] 在独立重绘的复杂子树周围使用 `RepaintBoundary`
* \[ ] 使用 `AnimatedBuilder` 的 child 参数处理独立于动画的子树
### build() 中的昂贵操作:
* \[ ] 不在 `build()` 中对大型集合进行排序、过滤或映射 —— 在状态管理层计算
* \[ ] 不在 `build()` 中编译正则表达式
* \[ ] `MediaQuery.of(context)` 的使用是具体的(例如,`MediaQuery.sizeOf(context)`
### 图像优化:
* \[ ] 网络图像使用缓存(适用于项目的任何缓存解决方案)
* \[ ] 为目标设备使用适当的图像分辨率(不为缩略图加载 4K 图像)
* \[ ] 使用带有 `cacheWidth`/`cacheHeight``Image.asset` 以按显示尺寸解码
* \[ ] 为网络图像提供占位符和错误部件
### 懒加载:
* \[ ] 对于大型或动态列表,使用 `ListView.builder` / `GridView.builder` 代替 `ListView(children: [...])`(对于小型、静态列表,具体构造器是可以的)
* \[ ] 为大型数据集实现分页
* \[ ] 在 Web 构建中对重量级库使用延迟加载 (`deferred as`)
### 其他:
* \[ ] 在动画中避免使用 `Opacity` 部件 —— 使用 `AnimatedOpacity``FadeTransition`
* \[ ] 在动画中避免裁剪 —— 预裁剪图像
* \[ ] 不在部件上重写 `operator ==` —— 使用 `const` 构造器代替
* \[ ] 固有尺寸部件 (`IntrinsicHeight`, `IntrinsicWidth`) 谨慎使用(额外的布局传递)
***
## 6. 测试
### 测试类型与期望:
* \[ ] **单元测试**:覆盖所有业务逻辑(状态管理器、仓库、工具函数)
* \[ ] **部件测试**:覆盖单个部件的行为、交互和视觉输出
* \[ ] **集成测试**:端到端覆盖关键用户流程
* \[ ] **Golden 测试**:对设计关键的 UI 组件进行像素级精确比较
### 覆盖率目标:
* \[ ] 业务逻辑的目标行覆盖率达到 80% 以上
* \[ ] 所有状态转换都有对应的测试(加载 → 成功,加载 → 错误,重试等)
* \[ ] 测试边缘情况:空状态、错误状态、加载状态、边界值
### 测试隔离:
* \[ ] 外部依赖API 客户端、数据库、服务)已被模拟或伪造
* \[ ] 每个测试文件仅测试一个类/单元
* \[ ] 测试验证行为,而非实现细节
* \[ ] 存根仅定义每个测试所需的行为(最小化存根)
* \[ ] 测试用例之间没有共享的可变状态
### 小部件测试质量:
* \[ ] `pumpWidget``pump` 被正确用于异步操作
* \[ ] `find.byType``find.text``find.byKey` 使用得当
* \[ ] 没有依赖于时序的不可靠测试——使用 `pumpAndSettle` 或显式的 `pump(Duration)`
* \[ ] 测试在 CI 中运行,失败会阻止合并
***
## 7. 无障碍功能
### 语义化小部件:
* \[ ] 使用 `Semantics` 小部件在自动标签不足时提供屏幕阅读器标签
* \[ ] 使用 `ExcludeSemantics` 处理纯装饰性元素
* \[ ] 使用 `MergeSemantics` 将相关小部件组合成单个可访问元素
* \[ ] 图像设置了 `semanticLabel` 属性
### 屏幕阅读器支持:
* \[ ] 所有交互元素均可聚焦并具有有意义的描述
* \[ ] 焦点顺序符合逻辑(遵循视觉阅读顺序)
### 视觉无障碍:
* \[ ] 文本与背景的对比度 >= 4.5:1
* \[ ] 可点击目标至少为 48x48 像素
* \[ ] 颜色不是状态的唯一指示器(同时使用图标/文本)
* \[ ] 文本随系统字体大小设置缩放
### 交互无障碍:
* \[ ] 没有无操作的 `onPressed` 回调——每个按钮都有作用或处于禁用状态
* \[ ] 错误字段建议更正
* \[ ] 用户输入数据时,上下文不会意外改变
***
## 8. 平台特定考量
### iOS/Android 差异:
* \[ ] 在适当的地方使用平台自适应小部件
* \[ ] 返回导航处理正确Android 返回按钮iOS 滑动返回)
* \[ ] 通过 `SafeArea` 小部件处理状态栏和安全区域
* \[ ] 平台特定权限在 `AndroidManifest.xml``Info.plist` 中声明
### 响应式设计:
* \[ ] 使用 `LayoutBuilder``MediaQuery` 实现响应式布局
* \[ ] 断点定义一致(手机、平板、桌面)
* \[ ] 文本在小屏幕上不会溢出——使用 `Flexible``Expanded``FittedBox`
* \[ ] 测试了横屏方向或明确锁定
* \[ ] Web 特定:支持鼠标/键盘交互,存在悬停状态
***
## 9. 安全性
### 安全存储:
* \[ ] 敏感数据令牌、凭证使用平台安全存储存储iOS 上的 KeychainAndroid 上的 EncryptedSharedPreferences
* \[ ] 从不以明文存储机密信息
* \[ ] 对于敏感操作考虑使用生物识别认证门控
### API 密钥处理:
* \[ ] API 密钥 NOT 硬编码在 Dart 源代码中——使用 `--dart-define``.env` 文件从 VCS 中排除,或使用编译时配置
* \[ ] 机密信息未提交到 git——检查 `.gitignore`
* \[ ] 对真正的秘密密钥使用后端代理(客户端不应持有服务器机密)
### 输入验证:
* \[ ] 所有用户输入在发送到 API 前都经过验证
* \[ ] 表单验证使用适当的验证模式
* \[ ] 没有原始 SQL 或用户输入的字符串插值
* \[ ] 深度链接 URL 在导航前经过验证和清理
### 网络安全:
* \[ ] 所有 API 调用强制使用 HTTPS
* \[ ] 对于高安全性应用考虑证书锁定
* \[ ] 认证令牌正确刷新和过期
* \[ ] 没有记录或打印敏感数据
***
## 10. 包/依赖项审查
### 评估 pub.dev 包:
* \[ ] 检查 **pub 分数**(目标 130+/160
* \[ ] 检查 **点赞数**和**流行度**作为社区信号
* \[ ] 验证发布者在 pub.dev 上**已验证**
* \[ ] 检查最后发布日期——过时的包(>1 年)有风险
* \[ ] 审查维护者的未解决问题和响应时间
* \[ ] 检查许可证与项目的兼容性
* \[ ] 验证平台支持是否覆盖您的目标
### 版本约束:
* \[ ] 对依赖项使用插入符语法(`^1.2.3`)——允许兼容性更新
* \[ ] 仅在绝对必要时固定确切版本
* \[ ] 定期运行 `flutter pub outdated` 以跟踪过时的依赖项
* \[ ] 生产 `pubspec.yaml` 中没有依赖项覆盖——仅用于带有注释/问题链接的临时修复
* \[ ] 最小化传递依赖项数量——每个依赖项都是一个攻击面
### 单仓库特定melos/workspace
* \[ ] 内部包仅从公共 API 导入——没有 `package:other/src/internal.dart`(破坏 Dart 包封装)
* \[ ] 内部包依赖项使用工作区解析,而不是硬编码的 `path: ../../` 相对字符串
* \[ ] 所有子包共享或继承根 `analysis_options.yaml`
***
## 11. 导航和路由
### 通用原则(适用于任何路由解决方案):
* \[ ] 一致使用一种路由方法——不混合命令式 `Navigator.push` 和声明式路由器
* \[ ] 路由参数是类型化的——没有 `Map<String, dynamic>``Object?` 转换
* \[ ] 路由路径定义为常量、枚举或生成——没有散布在代码中的魔法字符串
* \[ ] 认证守卫/重定向集中化——不在各个屏幕中重复
* \[ ] 为 Android 和 iOS 配置深度链接
* \[ ] 深度链接 URL 在导航前经过验证和清理
* \[ ] 导航状态是可测试的——可以在测试中验证路由更改
* \[ ] 在所有平台上返回行为正确
***
## 12. 错误处理
### 框架错误处理:
* \[ ] 重写 `FlutterError.onError` 以捕获框架错误(构建、布局、绘制)
* \[ ] 设置 `PlatformDispatcher.instance.onError` 处理 Flutter 未捕获的异步错误
* \[ ] 为发布模式自定义 `ErrorWidget.builder`(用户友好而非红屏)
* \[ ] 在 `runApp` 周围使用全局错误捕获包装器(例如 `runZonedGuarded`Sentry/Crashlytics 包装器)
### 错误报告:
* \[ ] 集成了错误报告服务Firebase Crashlytics、Sentry 或等效服务)
* \[ ] 报告非致命错误并附上堆栈跟踪
* \[ ] 状态管理错误观察器连接到错误报告例如BlocObserver、ProviderObserver 或适用于您解决方案的等效项)
* \[ ] 为调试目的,将用户可识别信息(用户 ID附加到错误报告
### 优雅降级:
* \[ ] API 错误导致用户友好的错误 UI而非崩溃
* \[ ] 针对瞬时网络故障的重试机制
* \[ ] 优雅处理离线状态
* \[ ] 状态管理中的错误状态携带用于显示的错误信息
* \[ ] 原始异常(网络、解析)在到达 UI 之前被映射为用户友好的本地化消息——从不向用户显示原始异常字符串
***
## 13. 国际化l10n
### 设置:
* \[ ] 配置了本地化解决方案Flutter 内置的 ARB/l10n、easy\_localization 或等效方案)
* \[ ] 在应用配置中声明了支持的语言环境
### 内容:
* \[ ] 所有用户可见字符串都使用本地化系统——小部件中没有硬编码字符串
* \[ ] 模板文件包含翻译人员的描述/上下文
* \[ ] 使用 ICU 消息语法处理复数、性别、选择
* \[ ] 使用类型定义占位符
* \[ ] 跨语言环境没有缺失的键
### 代码审查:
* \[ ] 在整个项目中一致使用本地化访问器
* \[ ] 日期、时间、数字和货币格式化具有语言环境感知能力
* \[ ] 如果目标语言是阿拉伯语、希伯来语等则支持文本方向性RTL
* \[ ] 本地化文本没有字符串拼接——使用参数化消息
***
## 14. 依赖注入
### 原则(适用于任何 DI 方法):
* \[ ] 类在层边界上依赖于抽象(接口),而不是具体实现
* \[ ] 依赖项通过构造函数、DI 框架或提供者图从外部提供——而非内部创建
* \[ ] 注册区分生命周期:单例 vs 工厂 vs 惰性单例
* \[ ] 环境特定绑定(开发/暂存/生产)使用配置,而非运行时 `if` 检查
* \[ ] DI 图中没有循环依赖
* \[ ] 服务定位器调用(如果使用)没有散布在业务逻辑中
***
## 15. 静态分析
### 配置:
* \[ ] 存在 `analysis_options.yaml` 并启用了严格设置
* \[ ] 严格的分析器设置:`strict-casts: true``strict-inference: true``strict-raw-types: true`
* \[ ] 包含全面的 lint 规则集very\_good\_analysis、flutter\_lints 或自定义严格规则)
* \[ ] 单仓库中的所有子包继承或共享根分析选项
### 执行:
* \[ ] 提交的代码中没有未解决的分析器警告
* \[ ] lint 抑制(`// ignore:`)有注释说明原因
* \[ ] `flutter analyze` 在 CI 中运行,失败会阻止合并
### 无论使用何种 lint 包都要验证的关键规则:
* \[ ] `prefer_const_constructors`——小部件树中的性能
* \[ ] `avoid_print`——使用适当的日志记录
* \[ ] `unawaited_futures`——防止即发即弃的异步错误
* \[ ] `prefer_final_locals`——变量级别的不可变性
* \[ ] `always_declare_return_types`——明确的契约
* \[ ] `avoid_catches_without_on_clauses`——具体的错误处理
* \[ ] `always_use_package_imports`——一致的导入风格
***
## 状态管理快速参考
下表将通用原则映射到流行解决方案中的实现。使用此表将审查规则调整为项目使用的任何解决方案。
| 原则 | BLoC/Cubit | Riverpod | Provider | GetX | MobX | Signals | 内置 |
|-----------|-----------|----------|----------|------|------|---------|----------|
| 状态容器 | `Bloc`/`Cubit` | `Notifier`/`AsyncNotifier` | `ChangeNotifier` | `GetxController` | `Store` | `signal()` | `StatefulWidget` |
| UI 消费者 | `BlocBuilder` | `ConsumerWidget` | `Consumer` | `Obx`/`GetBuilder` | `Observer` | `Watch` | `setState` |
| 选择器 | `BlocSelector`/`buildWhen` | `ref.watch(p.select(...))` | `Selector` | N/A | computed | `computed()` | N/A |
| 副作用 | `BlocListener` | `ref.listen` | `Consumer` 回调 | `ever()`/`once()` | `reaction` | `effect()` | 回调 |
| 处置 | 通过 `BlocProvider` 自动 | `.autoDispose` | 通过 `Provider` 自动 | `onClose()` | `ReactionDisposer` | 手动 | `dispose()` |
| 测试 | `blocTest()` | `ProviderContainer` | 直接 `ChangeNotifier` | 在测试中 `Get.put` | 直接测试 store | 直接测试 signal | 小部件测试 |
***
## 来源
* [Effective Dart: 风格](https://dart.dev/effective-dart/style)
* [Effective Dart: 用法](https://dart.dev/effective-dart/usage)
* [Effective Dart: 设计](https://dart.dev/effective-dart/design)
* [Flutter 性能最佳实践](https://docs.flutter.dev/perf/best-practices)
* [Flutter 测试概述](https://docs.flutter.dev/testing/overview)
* [Flutter 无障碍功能](https://docs.flutter.dev/ui/accessibility-and-internationalization/accessibility)
* [Flutter 国际化](https://docs.flutter.dev/ui/accessibility-and-internationalization/internationalization)
* [Flutter 导航和路由](https://docs.flutter.dev/ui/navigation)
* [Flutter 错误处理](https://docs.flutter.dev/testing/errors)
* [Flutter 状态管理选项](https://docs.flutter.dev/data-and-backend/state-mgmt/options)