Everything-claude-code flutter-dart-code-review
库无关的Flutter/Dart代码审查清单,涵盖Widget最佳实践、状态管理模式(BLoC、Riverpod、Provider、GetX、MobX、Signals)、Dart惯用法、性能、可访问性、安全性和整洁架构。
install
source · Clone the upstream repo
git clone https://github.com/affaan-m/everything-claude-code
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/affaan-m/everything-claude-code "$T" && mkdir -p ~/.claude/skills && cp -r "$T/docs/zh-CN/skills/flutter-dart-code-review" ~/.claude/skills/affaan-m-everything-claude-code-flutter-dart-code-review && rm -rf "$T"
manifest:
docs/zh-CN/skills/flutter-dart-code-review/SKILL.mdsource content
Flutter/Dart 代码审查最佳实践
适用于审查 Flutter/Dart 应用程序的全面、与库无关的清单。无论使用哪种状态管理方案、路由库或依赖注入框架,这些原则都适用。
1. 通用项目健康度
- [ ] 项目遵循一致的文件夹结构(功能优先或分层优先)
- [ ] 关注点分离得当:UI、业务逻辑、数据层
- [ ] 部件中无业务逻辑;部件纯粹是展示性的
- [ ]
是干净的 —— 没有未使用的依赖项,版本已适当固定pubspec.yaml - [ ]
包含严格的 lint 规则集,并启用了严格的分析器设置analysis_options.yaml - [ ] 生产代码中没有
语句 —— 使用print()dart:developer
或日志包log() - [ ] 生成的文件 (
,.g.dart
,.freezed.dart
) 是最新的或在.gr.dart
中.gitignore - [ ] 平台特定代码通过抽象进行隔离
2. Dart 语言陷阱
- [ ] 隐式动态类型:缺少类型注解导致
—— 启用dynamic
,strict-casts
,strict-inferencestrict-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
,编译时常量首选finalconst - [ ] 相对导入:为保持一致性,使用
导入package: - [ ] 暴露可变集合:公共 API 应返回不可修改的视图,而不是原始的
/ListMap - [ ] 缺少 Dart 3 模式匹配:优先使用 switch 表达式和
,而不是冗长的if-case
检查和手动类型转换is - [ ] 为多重返回值使用一次性类:使用 Dart 3 记录
代替一次性 DTO(String, int) - [ ] 生产代码中的
:使用print()dart:developer
或项目的日志包;log()
没有日志级别且无法过滤print()
3. 部件最佳实践
部件分解:
- [ ] 没有单个部件的
方法超过约 80-100 行build() - [ ] 部件按封装方式以及按变化方式(重建边界)进行拆分
- [ ] 返回部件的私有
辅助方法被提取到单独的部件类中(支持元素重用、常量传播和框架优化)_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 方法复杂度:
- [ ]
中没有网络调用、文件 I/O 或繁重计算build() - [ ]
中没有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 —— 优先使用共享仓库或表示层协调
- 在其他解决方案中:遵循文档中关于组件间通信的约定
- 在 Riverpod 中:提供者通过
不可变性与值相等性(适用于不可变状态解决方案:BLoC、Riverpod、Redux):
- [ ] 状态对象是不可变的 —— 通过
或构造函数创建新实例,绝不就地修改copyWith() - [ ] 状态类正确实现
和==
(比较中包含所有字段)hashCode - [ ] 机制在整个项目中保持一致 —— 手动覆盖、
、Equatable
、Dart 记录或其他方式freezed - [ ] 状态对象内部的集合不作为原始可变的
/List
暴露Map
响应式纪律(适用于响应式突变解决方案:MobX、GetX、Signals):
- [ ] 状态仅通过解决方案的响应式 API 进行修改(MobX 中的
,Signals 上的@action
,GetX 中的.value
)—— 直接字段修改会绕过变更跟踪.obs - [ ] 派生值使用解决方案的计算机制,而不是冗余存储
- [ ] 反应和清理器被正确清理(MobX 中的
,Signals 中的 effect 清理)ReactionDisposer
状态形状设计:
- [ ] 互斥状态使用密封类型、联合变体或解决方案内置的异步状态类型(例如 Riverpod 的
)—— 而不是布尔标志 (AsyncValue
,isLoading
,isError
)hasData - [ ] 每个异步操作都将加载、成功和错误建模为不同的状态
- [ ] UI 中详尽处理所有状态变体 —— 没有静默忽略的情况
- [ ] 错误状态携带用于显示的错误信息;加载状态不携带陈旧数据
- [ ] 可空数据不用于作为加载指示器 —— 状态是明确的
// 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() - [ ] 异步回调中在
之前检查setStatemounted - [ ] 在
之后使用await
而不检查BuildContext
(Flutter 3.7+)—— 过时的上下文会导致崩溃context.mounted - [ ] 在异步间隙后,没有在验证部件仍然挂载的情况下进行导航、显示对话框或脚手架消息
- [ ]
绝不存储在单例、状态管理器或静态字段中BuildContext
本地状态与全局状态:
- [ ] 临时 UI 状态(复选框、滑块、动画)使用本地状态 (
,setState
)ValueNotifier - [ ] 共享状态仅提升到所需的高度 —— 不过度全局化
- [ ] 功能作用域的状态在功能不再活跃时被正确清理
5. 性能
不必要的重建:
- [ ] 不在根部件级别调用
—— 将状态变化局部化setState() - [ ] 使用
部件来阻止重建传播const - [ ] 在独立重绘的复杂子树周围使用
RepaintBoundary - [ ] 使用
的 child 参数处理独立于动画的子树AnimatedBuilder
build() 中的昂贵操作:
- [ ] 不在
中对大型集合进行排序、过滤或映射 —— 在状态管理层计算build() - [ ] 不在
中编译正则表达式build() - [ ]
的使用是具体的(例如,MediaQuery.of(context)
)MediaQuery.sizeOf(context)
图像优化:
- [ ] 网络图像使用缓存(适用于项目的任何缓存解决方案)
- [ ] 为目标设备使用适当的图像分辨率(不为缩略图加载 4K 图像)
- [ ] 使用带有
/cacheWidth
的cacheHeight
以按显示尺寸解码Image.asset - [ ] 为网络图像提供占位符和错误部件
懒加载:
- [ ] 对于大型或动态列表,使用
/ListView.builder
代替GridView.builder
(对于小型、静态列表,具体构造器是可以的)ListView(children: [...]) - [ ] 为大型数据集实现分页
- [ ] 在 Web 构建中对重量级库使用延迟加载 (
)deferred as
其他:
- [ ] 在动画中避免使用
部件 —— 使用Opacity
或AnimatedOpacityFadeTransition - [ ] 在动画中避免裁剪 —— 预裁剪图像
- [ ] 不在部件上重写
—— 使用operator ==
构造器代替const - [ ] 固有尺寸部件 (
,IntrinsicHeight
) 谨慎使用(额外的布局传递)IntrinsicWidth
6. 测试
测试类型与期望:
- [ ] 单元测试:覆盖所有业务逻辑(状态管理器、仓库、工具函数)
- [ ] 部件测试:覆盖单个部件的行为、交互和视觉输出
- [ ] 集成测试:端到端覆盖关键用户流程
- [ ] Golden 测试:对设计关键的 UI 组件进行像素级精确比较
覆盖率目标:
- [ ] 业务逻辑的目标行覆盖率达到 80% 以上
- [ ] 所有状态转换都有对应的测试(加载 → 成功,加载 → 错误,重试等)
- [ ] 测试边缘情况:空状态、错误状态、加载状态、边界值
测试隔离:
- [ ] 外部依赖(API 客户端、数据库、服务)已被模拟或伪造
- [ ] 每个测试文件仅测试一个类/单元
- [ ] 测试验证行为,而非实现细节
- [ ] 存根仅定义每个测试所需的行为(最小化存根)
- [ ] 测试用例之间没有共享的可变状态
小部件测试质量:
- [ ]
和pumpWidget
被正确用于异步操作pump - [ ]
、find.byType
、find.text
使用得当find.byKey - [ ] 没有依赖于时序的不可靠测试——使用
或显式的pumpAndSettlepump(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
、ExpandedFittedBox - [ ] 测试了横屏方向或明确锁定
- [ ] Web 特定:支持鼠标/键盘交互,存在悬停状态
9. 安全性
安全存储:
- [ ] 敏感数据(令牌、凭证)使用平台安全存储存储(iOS 上的 Keychain,Android 上的 EncryptedSharedPreferences)
- [ ] 从不以明文存储机密信息
- [ ] 对于敏感操作考虑使用生物识别认证门控
API 密钥处理:
- [ ] API 密钥 NOT 硬编码在 Dart 源代码中——使用
,--dart-define
文件从 VCS 中排除,或使用编译时配置.env - [ ] 机密信息未提交到 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 导入——没有
(破坏 Dart 包封装)package:other/src/internal.dart - [ ] 内部包依赖项使用工作区解析,而不是硬编码的
相对字符串path: ../../ - [ ] 所有子包共享或继承根
analysis_options.yaml
11. 导航和路由
通用原则(适用于任何路由解决方案):
- [ ] 一致使用一种路由方法——不混合命令式
和声明式路由器Navigator.push - [ ] 路由参数是类型化的——没有
或Map<String, dynamic>
转换Object? - [ ] 路由路径定义为常量、枚举或生成——没有散布在代码中的魔法字符串
- [ ] 认证守卫/重定向集中化——不在各个屏幕中重复
- [ ] 为 Android 和 iOS 配置深度链接
- [ ] 深度链接 URL 在导航前经过验证和清理
- [ ] 导航状态是可测试的——可以在测试中验证路由更改
- [ ] 在所有平台上返回行为正确
12. 错误处理
框架错误处理:
- [ ] 重写
以捕获框架错误(构建、布局、绘制)FlutterError.onError - [ ] 设置
处理 Flutter 未捕获的异步错误PlatformDispatcher.instance.onError - [ ] 为发布模式自定义
(用户友好而非红屏)ErrorWidget.builder - [ ] 在
周围使用全局错误捕获包装器(例如runApp
,Sentry/Crashlytics 包装器)runZonedGuarded
错误报告:
- [ ] 集成了错误报告服务(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: truestrict-raw-types: true - [ ] 包含全面的 lint 规则集(very_good_analysis、flutter_lints 或自定义严格规则)
- [ ] 单仓库中的所有子包继承或共享根分析选项
执行:
- [ ] 提交的代码中没有未解决的分析器警告
- [ ] lint 抑制(
)有注释说明原因// ignore: - [ ]
在 CI 中运行,失败会阻止合并flutter analyze
无论使用何种 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 | 内置 |
|---|---|---|---|---|---|---|---|
| 状态容器 | / | / | | | | | |
| UI 消费者 | | | | / | | | |
| 选择器 | / | | | N/A | computed | | N/A |
| 副作用 | | | 回调 | / | | | 回调 |
| 处置 | 通过 自动 | | 通过 自动 | | | 手动 | |
| 测试 | | | 直接 | 在测试中 | 直接测试 store | 直接测试 signal | 小部件测试 |