mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-08 02:03:34 +08:00
481 lines
22 KiB
Markdown
481 lines
22 KiB
Markdown
---
|
||
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 上的 Keychain,Android 上的 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)
|