flutter-dart-code-review
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseFlutter/Dart Code Review Best Practices
Flutter/Dart代码审查最佳实践
Comprehensive, library-agnostic checklist for reviewing Flutter/Dart applications. These principles apply regardless of which state management solution, routing library, or DI framework is used.
这份全面、与库无关的清单用于审查Flutter/Dart应用。无论使用哪种状态管理方案、路由库或依赖注入框架,这些原则都适用。
1. General Project Health
1. 项目整体健康度
- Project follows consistent folder structure (feature-first or layer-first)
- Proper separation of concerns: UI, business logic, data layers
- No business logic in widgets; widgets are purely presentational
- is clean — no unused dependencies, versions pinned appropriately
pubspec.yaml - includes a strict lint set with strict analyzer settings enabled
analysis_options.yaml - No statements in production code — use
print()dart:developeror a logging packagelog() - Generated files (,
.g.dart,.freezed.dart) are up-to-date or in.gr.dart.gitignore - Platform-specific code isolated behind abstractions
- 项目遵循统一的文件夹结构(功能优先或分层优先)
- 关注点正确分离:UI、业务逻辑、数据层相互独立
- Widget中无业务逻辑;Widget仅负责展示
- 保持整洁——无未使用的依赖,版本已适当固定
pubspec.yaml - 包含严格的lint规则集,并启用了严格的分析器设置
analysis_options.yaml - 生产代码中无语句——使用
print()的dart:developer或日志包log() - 生成文件(、
.g.dart、.freezed.dart)已更新或已加入.gr.dart.gitignore - 平台特定代码通过抽象层隔离
2. Dart Language Pitfalls
2. Dart语言常见陷阱
- Implicit dynamic: Missing type annotations leading to — enable
dynamic,strict-casts,strict-inferencestrict-raw-types - Null safety misuse: Excessive (bang operator) instead of proper null checks or Dart 3 pattern matching (
!)if (value case var v?) - Type promotion failures: Using where local variable promotion would work
this.field - Catching too broadly: without
catch (e)clause; always specify exception typeson - Catching :
Errorsubtypes indicate bugs and should not be caughtError - Unused : Functions marked
asyncthat neverasync— unnecessary overheadawait - overuse:
lateused where nullable or constructor initialization would be safer; defers errors to runtimelate - String concatenation in loops: Use instead of
StringBufferfor iterative string building+ - Mutable state in contexts: Fields in
constconstructor classes should not be mutableconst - Ignoring return values: Use
Futureor explicitly callawaitto signal intentunawaited() - where
varworks: Preferfinalfor locals andfinalfor compile-time constantsconst - Relative imports: Use imports for consistency
package: - Mutable collections exposed: Public APIs should return unmodifiable views, not raw /
ListMap - Missing Dart 3 pattern matching: Prefer switch expressions and over verbose
if-casechecks and manual castingis - Throwaway classes for multiple returns: Use Dart 3 records instead of single-use DTOs
(String, int) - in production code: Use
print()dart:developeror the project's logging package;log()has no log levels and cannot be filteredprint()
- 隐式dynamic类型:缺少类型注解导致类型——启用
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. Widget Best Practices
3. Widget最佳实践
Widget decomposition:
Widget拆分:
- No single widget with a method exceeding ~80-100 lines
build() - Widgets split by encapsulation AND by how they change (rebuild boundaries)
- Private helper methods that return widgets are extracted to separate widget classes (enables element reuse, const propagation, and framework optimizations)
_build*() - Stateless widgets preferred over Stateful where no mutable local state is needed
- Extracted widgets are in separate files when reusable
- 单个Widget的方法代码不超过80-100行
build() - Widget按封装性和变化方式拆分(重建边界)
- 将返回Widget的私有辅助方法提取为独立的Widget类(支持元素复用、const传播和框架优化)
_build*() - 在无需可变局部状态的场景,优先使用Stateless Widget而非Stateful Widget
- 可复用的提取Widget放在独立文件中
Const usage:
Const使用:
- constructors used wherever possible — prevents unnecessary rebuilds
const - literals for collections that don't change (
const,const [])const {} - Constructor is declared when all fields are final
const
- 尽可能使用构造函数——避免不必要的重建
const - 对不变化的集合使用字面量(
const、const [])const {} - 当所有字段均为final时,构造函数声明为
const
Key usage:
Key使用:
- used in lists/grids to preserve state across reorders
ValueKey - used sparingly — only when accessing state across the tree is truly needed
GlobalKey - avoided in
UniqueKey— it forces rebuild every framebuild() - used when identity is based on a data object rather than a single value
ObjectKey
- 在列表/网格中使用以在重排序时保留状态
ValueKey - 谨慎使用——仅在确实需要跨树访问状态时使用
GlobalKey - 避免在中使用
build()——会强制每帧重建UniqueKey - 当身份基于数据对象而非单一值时,使用
ObjectKey
Theming & design system:
主题与设计系统:
- Colors come from — no hardcoded
Theme.of(context).colorSchemeor hex valuesColors.red - Text styles come from — no inline
Theme.of(context).textThemewith raw font sizesTextStyle - Dark mode compatibility verified — no assumptions about light background
- Spacing and sizing use consistent design tokens or constants, not magic numbers
- 颜色来自——不硬编码
Theme.of(context).colorScheme或十六进制值Colors.red - 文本样式来自——不使用内联
Theme.of(context).textTheme和原始字体大小TextStyle - 已验证深色模式兼容性——不假设浅色背景
- 间距和尺寸使用统一的设计令牌或常量,而非魔法数字
Build method complexity:
Build方法复杂度:
- No network calls, file I/O, or heavy computation in
build() - No or
Future.then()work inasyncbuild() - No subscription creation () in
.listen()build() - localized to smallest possible subtree
setState()
- 中无网络请求、文件I/O或繁重计算
build() - 中无
build()或异步操作Future.then() - 中无订阅创建(
build()).listen() - 局限于最小的子树
setState()
4. State Management (Library-Agnostic)
4. 状态管理(与库无关)
These principles apply to all Flutter state management solutions (BLoC, Riverpod, Provider, GetX, MobX, Signals, ValueNotifier, etc.).
这些原则适用于所有Flutter状态管理方案(BLoC、Riverpod、Provider、GetX、MobX、Signals、ValueNotifier等)。
Architecture:
架构:
- Business logic lives outside the widget layer — in a state management component (BLoC, Notifier, Controller, Store, ViewModel, etc.)
- State managers receive dependencies via injection, not by constructing them internally
- A service or repository layer abstracts data sources — widgets and state managers should not call APIs or databases directly
- State managers have a single responsibility — no "god" managers handling unrelated concerns
- Cross-component dependencies follow the solution's conventions:
- In Riverpod: providers depending on providers via is expected — flag only circular or overly tangled chains
ref.watch - In BLoC: blocs should not directly depend on other blocs — prefer shared repositories or presentation-layer coordination
- In other solutions: follow the documented conventions for inter-component communication
- In Riverpod: providers depending on providers via
- 业务逻辑位于Widget层之外——在状态管理组件(BLoC、Notifier、Controller、Store、ViewModel等)中实现
- 状态管理器通过注入接收依赖,而非内部构造
- 服务或仓储层抽象数据源——Widget和状态管理器不应直接调用API或数据库
- 状态管理器单一职责——无处理不相关事务的“上帝”管理器
- 跨组件依赖遵循方案约定:
- Riverpod:Provider通过依赖其他Provider是预期行为——仅标记循环依赖或过度复杂的依赖链
ref.watch - BLoC:Bloc不应直接依赖其他Bloc——优先使用共享仓储或展示层协调
- 其他方案:遵循文档中关于组件间通信的约定
- Riverpod:Provider通过
Immutability & value equality (for immutable-state solutions: BLoC, Riverpod, Redux):
不可变性与值相等(适用于不可变状态方案:BLoC、Riverpod、Redux):
- State objects are immutable — new instances created via or constructors, never mutated in-place
copyWith() - State classes implement and
==properly (all fields included in comparison)hashCode - Mechanism is consistent across the project — manual override, ,
Equatable, Dart records, or otherfreezed - Collections inside state objects are not exposed as raw mutable /
ListMap
- 状态对象不可变——通过或构造函数创建新实例,绝不原地修改
copyWith() - 状态类正确实现和
==(比较时包含所有字段)hashCode - 项目中实现方式一致——手动重写、、
Equatable、Dart记录或其他方式freezed - 状态对象内的集合不暴露为原始可变的/
ListMap
Reactivity discipline (for reactive-mutation solutions: MobX, GetX, Signals):
响应式规范(适用于响应式可变方案:MobX、GetX、Signals):
- State is only mutated through the solution's reactive API (in MobX,
@actionon signals,.valuein GetX) — direct field mutation bypasses change tracking.obs - Derived values use the solution's computed mechanism rather than being stored redundantly
- Reactions and disposers are properly cleaned up (in MobX, effect cleanup in Signals)
ReactionDisposer
- 仅通过方案的响应式API修改状态(MobX中的、Signals的
@action、GetX的.value)——直接字段修改会绕过变更追踪.obs - 派生值使用方案的计算机制,而非冗余存储
- 正确清理响应和销毁器(MobX中的、Signals中的effect清理)
ReactionDisposer
State shape design:
状态结构设计:
- Mutually exclusive states use sealed types, union variants, or the solution's built-in async state type (e.g. Riverpod's ) — not boolean flags (
AsyncValue,isLoading,isError)hasData - Every async operation models loading, success, and error as distinct states
- All state variants are handled exhaustively in UI — no silently ignored cases
- Error states carry error information for display; loading states don't carry stale data
- Nullable data is not used as a loading indicator — states are explicit
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- 互斥状态使用密封类型、联合变体或方案内置的异步状态类型(如Riverpod的)——而非布尔标志(
AsyncValue、isLoading、isError)hasData - 每个异步操作将加载、成功、错误建模为不同状态
- UI中穷举处理所有状态变体——无静默忽略的情况
- 错误状态携带用于展示的错误信息;加载状态不携带过期数据
- 不使用可空数据作为加载指示器——状态是显式的
dart
// 不良示例——布尔标志混乱会导致出现不可能的状态
class UserState {
bool isLoading = false;
bool hasError = false; // 会出现isLoading && hasError同时为true的情况!
User? user;
}
// 良好示例(不可变方式)——密封类型避免出现不可能的状态
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);
}
// 良好示例(响应式方式)——可观察枚举+数据,通过响应式API修改状态
// enum UserStatus { initial, loading, loaded, error }
// 使用方案的可观察对象/signal分别包装状态和数据Rebuild optimization:
重建优化:
- State consumer widgets (Builder, Consumer, Observer, Obx, Watch, etc.) scoped as narrow as possible
- Selectors used to rebuild only when specific fields change — not on every state emission
- widgets used to stop rebuild propagation through the tree
const - Computed/derived state is calculated reactively, not stored redundantly
- 状态消费Widget(Builder、Consumer、Observer、Obx、Watch等)的作用域尽可能小
- 使用选择器仅在特定字段变更时重建——而非每次状态发射都重建
- 使用Widget阻止重建在树中传播
const - 计算/派生状态通过响应式计算,而非冗余存储
Subscriptions & disposal:
订阅与销毁:
- All manual subscriptions () are cancelled in
.listen()/dispose()close() - Stream controllers are closed when no longer needed
- Timers are cancelled in disposal lifecycle
- Framework-managed lifecycle is preferred over manual subscription (declarative builders over )
.listen() - check before
mountedin async callbackssetState - not used after
BuildContextwithout checkingawait(Flutter 3.7+) — stale context causes crashescontext.mounted - No navigation, dialogs, or scaffold messages after async gaps without verifying the widget is still mounted
- never stored in singletons, state managers, or static fields
BuildContext
- 所有手动订阅()在
.listen()/dispose()中取消close() - 流控制器在不再需要时关闭
- 计时器在销毁生命周期中取消
- 优先使用框架管理的生命周期而非手动订阅(声明式构建器优于)
.listen() - 异步回调中调用前检查
setStatemounted - 在后使用
await前检查BuildContext(Flutter 3.7+)——过期上下文会导致崩溃context.mounted - 在异步间隙后,导航、弹窗或脚手架消息前验证Widget仍处于挂载状态
- 绝不将存储在单例、状态管理器或静态字段中
BuildContext
Local vs global state:
局部状态 vs 全局状态:
- Ephemeral UI state (checkbox, slider, animation) uses local state (,
setState)ValueNotifier - Shared state is lifted only as high as needed — not over-globalized
- Feature-scoped state is properly disposed when the feature is no longer active
- 临时UI状态(复选框、滑块、动画)使用局部状态(、
setState)ValueNotifier - 共享状态仅提升到必要的层级——不过度全局化
- 功能范围的状态在功能不再活跃时正确销毁
5. Performance
5. 性能
Unnecessary rebuilds:
不必要的重建:
- not called at root widget level — localize state changes
setState() - widgets used to stop rebuild propagation
const - used around complex subtrees that repaint independently
RepaintBoundary - child parameter used for subtrees independent of animation
AnimatedBuilder
- 不在根Widget层级调用——局部化状态变更
setState() - 使用Widget阻止重建传播
const - 在独立重绘的复杂子树周围使用
RepaintBoundary - 的child参数用于与动画无关的子树
AnimatedBuilder
Expensive operations in build():
Build()中的昂贵操作:
- No sorting, filtering, or mapping large collections in — compute in state management layer
build() - No regex compilation in
build() - usage is specific (e.g.,
MediaQuery.of(context))MediaQuery.sizeOf(context)
- 中无排序、过滤或大型集合映射——在状态管理层计算
build() - 中无正则表达式编译
build() - 精准使用(如
MediaQuery.of(context))MediaQuery.sizeOf(context)
Image optimization:
图片优化:
- Network images use caching (any caching solution appropriate for the project)
- Appropriate image resolution for target device (no loading 4K images for thumbnails)
- with
Image.asset/cacheWidthto decode at display sizecacheHeight - Placeholder and error widgets provided for network images
- 网络图片使用缓存(使用适合项目的缓存方案)
- 为目标设备使用合适的图片分辨率(不为缩略图加载4K图片)
- 使用
Image.asset/cacheWidth以显示尺寸解码cacheHeight - 为网络图片提供占位符和错误Widget
Lazy loading:
懒加载:
- /
ListView.builderused instead ofGridView.builderfor large or dynamic lists (concrete constructors are fine for small, static lists)ListView(children: [...]) - Pagination implemented for large data sets
- Deferred loading () used for heavy libraries in web builds
deferred as
- 大型或动态列表使用/
ListView.builder替代GridView.builder(小型静态列表使用普通构造函数即可)ListView(children: [...]) - 大型数据集实现分页
- Web构建中对重型库使用延迟加载()
deferred as
Other:
其他:
- widget avoided in animations — use
OpacityorAnimatedOpacityFadeTransition - Clipping avoided in animations — pre-clip images
- not overridden on widgets — use
operator ==constructors insteadconst - Intrinsic dimension widgets (,
IntrinsicHeight) used sparingly (extra layout pass)IntrinsicWidth
- 动画中避免使用Widget——使用
Opacity或AnimatedOpacityFadeTransition - 动画中避免裁剪——提前裁剪图片
- 不在Widget上重写——使用
operator ==构造函数替代const - 谨慎使用内在尺寸Widget(、
IntrinsicHeight)——会增加额外布局步骤IntrinsicWidth
6. Testing
6. 测试
Test types and expectations:
测试类型与预期:
- Unit tests: Cover all business logic (state managers, repositories, utility functions)
- Widget tests: Cover individual widget behavior, interactions, and visual output
- Integration tests: Cover critical user flows end-to-end
- Golden tests: Pixel-perfect comparisons for design-critical UI components
- 单元测试:覆盖所有业务逻辑(状态管理器、仓储、工具函数)
- Widget测试:覆盖单个Widget的行为、交互和视觉输出
- 集成测试:覆盖关键用户端到端流程
- Golden测试:对设计关键的UI组件进行像素级对比
Coverage targets:
覆盖率目标:
- Aim for 80%+ line coverage on business logic
- All state transitions have corresponding tests (loading → success, loading → error, retry, etc.)
- Edge cases tested: empty states, error states, loading states, boundary values
- 业务逻辑的行覆盖率目标80%+
- 所有状态转换都有对应的测试(加载→成功、加载→错误、重试等)
- 测试边缘情况:空状态、错误状态、加载状态、边界值
Test isolation:
测试隔离:
- External dependencies (API clients, databases, services) are mocked or faked
- Each test file tests exactly one class/unit
- Tests verify behavior, not implementation details
- Stubs define only the behavior needed for each test (minimal stubbing)
- No shared mutable state between test cases
- 外部依赖(API客户端、数据库、服务)已被模拟或伪造
- 每个测试文件仅测试一个类/单元
- 测试验证行为,而非实现细节
- 存根仅定义每个测试所需的行为(最小化存根)
- 测试用例间无共享可变状态
Widget test quality:
Widget测试质量:
- and
pumpWidgetused correctly for async operationspump - ,
find.byType,find.textused appropriatelyfind.byKey - No flaky tests depending on timing — use or explicit
pumpAndSettlepump(Duration) - Tests run in CI and failures block merges
- 正确使用和
pumpWidget处理异步操作pump - 适当使用、
find.byType、find.textfind.byKey - 无依赖时序的不稳定测试——使用或显式
pumpAndSettlepump(Duration) - 测试在CI中运行,失败会阻止合并
7. Accessibility
7. 可访问性
Semantic widgets:
语义化Widget:
- widget used to provide screen reader labels where automatic labels are insufficient
Semantics - used for purely decorative elements
ExcludeSemantics - used to combine related widgets into a single accessible element
MergeSemantics - Images have property set
semanticLabel
- 在自动标签不足的场景,使用Widget为屏幕阅读器提供标签
Semantics - 纯装饰元素使用
ExcludeSemantics - 使用将相关Widget合并为单个可访问元素
MergeSemantics - 图片设置属性
semanticLabel
Screen reader support:
屏幕阅读器支持:
- All interactive elements are focusable and have meaningful descriptions
- Focus order is logical (follows visual reading order)
- 所有交互元素可聚焦且有意义的描述
- 聚焦顺序符合逻辑(遵循视觉阅读顺序)
Visual accessibility:
视觉可访问性:
- Contrast ratio >= 4.5:1 for text against background
- Tappable targets are at least 48x48 pixels
- Color is not the sole indicator of state (use icons/text alongside)
- Text scales with system font size settings
- 文本与背景的对比度≥4.5:1
- 可点击目标尺寸至少为48x48像素
- 不单独使用颜色作为状态指示器(配合图标/文本)
- 文本随系统字体大小设置缩放
Interaction accessibility:
交互可访问性:
- No no-op callbacks — every button does something or is disabled
onPressed - Error fields suggest corrections
- Context does not change unexpectedly while user is inputting data
- 无空实现的回调——每个按钮要么有功能,要么被禁用
onPressed - 错误字段提供修正建议
- 用户输入时,上下文不会意外变更
8. Platform-Specific Concerns
8. 平台特定注意事项
iOS/Android differences:
iOS/Android差异:
- Platform-adaptive widgets used where appropriate
- Back navigation handled correctly (Android back button, iOS swipe-to-go-back)
- Status bar and safe area handled via widget
SafeArea - Platform-specific permissions declared in and
AndroidManifest.xmlInfo.plist
- 适当使用平台自适应Widget
- 正确处理返回导航(Android返回按钮、iOS滑动返回)
- 通过Widget处理状态栏和安全区域
SafeArea - 在和
AndroidManifest.xml中声明平台特定权限Info.plist
Responsive design:
响应式设计:
- or
LayoutBuilderused for responsive layoutsMediaQuery - Breakpoints defined consistently (phone, tablet, desktop)
- Text doesn't overflow on small screens — use ,
Flexible,ExpandedFittedBox - Landscape orientation tested or explicitly locked
- Web-specific: mouse/keyboard interactions supported, hover states present
- 使用或
LayoutBuilder实现响应式布局MediaQuery - 统一定义断点(手机、平板、桌面)
- 小屏幕上文本不溢出——使用、
Flexible、ExpandedFittedBox - 已测试横屏方向或已显式锁定
- Web特定:支持鼠标/键盘交互,提供悬停状态
9. Security
9. 安全性
Secure storage:
安全存储:
- Sensitive data (tokens, credentials) stored using platform-secure storage (Keychain on iOS, EncryptedSharedPreferences on Android)
- Never store secrets in plaintext storage
- Biometric authentication gating considered for sensitive operations
- 敏感数据(令牌、凭证)使用平台安全存储(iOS的Keychain、Android的EncryptedSharedPreferences)
- 绝不以明文存储机密信息
- 敏感操作考虑生物识别认证 gate
API key handling:
API密钥处理:
- API keys NOT hardcoded in Dart source — use ,
--dart-definefiles excluded from VCS, or compile-time configuration.env - Secrets not committed to git — check
.gitignore - Backend proxy used for truly secret keys (client should never hold server secrets)
- API密钥不硬编码在Dart源码中——使用、排除在VCS之外的
--dart-define文件或编译时配置.env - 机密信息不提交到git——检查
.gitignore - 真正的机密密钥使用后端代理(客户端绝不持有服务器机密)
Input validation:
输入验证:
- All user input validated before sending to API
- Form validation uses proper validation patterns
- No raw SQL or string interpolation of user input
- Deep link URLs validated and sanitized before navigation
- 所有用户输入在发送到API前验证
- 表单验证使用正确的验证规则
- 不使用原始SQL或用户输入的字符串插值
- 深度链接URL在导航前验证和清理
Network security:
网络安全:
- HTTPS enforced for all API calls
- Certificate pinning considered for high-security apps
- Authentication tokens refreshed and expired properly
- No sensitive data logged or printed
- 所有API调用强制使用HTTPS
- 高安全性应用考虑证书固定
- 正确刷新和过期认证令牌
- 不记录或打印敏感数据
10. Package/Dependency Review
10. 包/依赖审查
Evaluating pub.dev packages:
评估pub.dev包:
- Check pub points score (aim for 130+/160)
- Check likes and popularity as community signals
- Verify the publisher is verified on pub.dev
- Check last publish date — stale packages (>1 year) are a risk
- Review open issues and response time from maintainers
- Check license compatibility with your project
- Verify platform support covers your targets
- 检查pub分数(目标130+/160)
- 查看点赞数和流行度作为社区信号
- 验证发布者在pub.dev上已验证
- 检查最后发布日期——过时包(>1年)存在风险
- 查看开放问题和维护者的响应时间
- 检查许可证与项目的兼容性
- 验证平台支持覆盖目标平台
Version constraints:
版本约束:
- Use caret syntax () for dependencies — allows compatible updates
^1.2.3 - Pin exact versions only when absolutely necessary
- Run regularly to track stale dependencies
flutter pub outdated - No dependency overrides in production — only for temporary fixes with a comment/issue link
pubspec.yaml - Minimize transitive dependency count — each dependency is an attack surface
- 依赖使用脱字符语法()——允许兼容更新
^1.2.3 - 仅在绝对必要时固定确切版本
- 定期运行跟踪过时依赖
flutter pub outdated - 生产中无依赖覆盖——仅用于临时修复并附带注释/问题链接
pubspec.yaml - 最小化传递依赖数量——每个依赖都是一个攻击面
Monorepo-specific (melos/workspace):
单仓库特定(melos/workspace):
- Internal packages import only from public API — no (breaks Dart package encapsulation)
package:other/src/internal.dart - Internal package dependencies use workspace resolution, not hardcoded relative strings
path: ../../ - All sub-packages share or inherit root
analysis_options.yaml
- 内部包仅从公共API导入——不使用(违反Dart包封装)
package:other/src/internal.dart - 内部包依赖使用工作区解析,而非硬编码的相对路径
path: ../../ - 所有子包共享或继承根目录的
analysis_options.yaml
11. Navigation and Routing
11. 导航与路由
General principles (apply to any routing solution):
通用原则(适用于任何路由方案):
- One routing approach used consistently — no mixing imperative with a declarative router
Navigator.push - Route arguments are typed — no or
Map<String, dynamic>castingObject? - Route paths defined as constants, enums, or generated — no magic strings scattered in code
- Auth guards/redirects centralized — not duplicated across individual screens
- Deep links configured for both Android and iOS
- Deep link URLs validated and sanitized before navigation
- Navigation state is testable — route changes can be verified in tests
- Back behavior is correct on all platforms
- 统一使用一种路由方式——不混合命令式和声明式路由
Navigator.push - 路由参数是类型化的——无或
Map<String, dynamic>类型转换Object? - 路由路径定义为常量、枚举或生成——无分散在代码中的魔法字符串
- 认证守卫/重定向集中管理——不在各个屏幕重复实现
- 为Android和iOS配置深度链接
- 深度链接URL在导航前验证和清理
- 导航状态可测试——测试中可验证路由变更
- 所有平台上的返回行为正确
12. Error Handling
12. 错误处理
Framework error handling:
框架错误处理:
- overridden to capture framework errors (build, layout, paint)
FlutterError.onError - set for async errors not caught by Flutter
PlatformDispatcher.instance.onError - customized for release mode (user-friendly instead of red screen)
ErrorWidget.builder - Global error capture wrapper around (e.g.,
runApp, Sentry/Crashlytics wrapper)runZonedGuarded
- 重写以捕获框架错误(构建、布局、绘制)
FlutterError.onError - 设置以捕获Flutter未捕获的异步错误
PlatformDispatcher.instance.onError - 自定义用于发布模式(用户友好而非红屏)
ErrorWidget.builder - 在外包裹全局错误捕获(如
runApp、Sentry/Crashlytics包装器)runZonedGuarded
Error reporting:
错误上报:
- Error reporting service integrated (Firebase Crashlytics, Sentry, or equivalent)
- Non-fatal errors reported with stack traces
- State management error observer wired to error reporting (e.g., BlocObserver, ProviderObserver, or equivalent for your solution)
- User-identifiable info (user ID) attached to error reports for debugging
- 集成错误上报服务(Firebase Crashlytics、Sentry或同类服务)
- 上报非致命错误并附带堆栈跟踪
- 状态管理器错误观察者连接到错误上报(如BlocObserver、ProviderObserver或对应方案的等价物)
- 错误上报中附带用户标识信息(如用户ID)以辅助调试
Graceful degradation:
优雅降级:
- API errors result in user-friendly error UI, not crashes
- Retry mechanisms for transient network failures
- Offline state handled gracefully
- Error states in state management carry error info for display
- Raw exceptions (network, parsing) are mapped to user-friendly, localized messages before reaching the UI — never show raw exception strings to users
- API错误展示用户友好的错误UI,而非崩溃
- 临时网络故障实现重试机制
- 优雅处理离线状态
- 状态管理中的错误状态携带用于展示的错误信息
- 原始异常(网络、解析)在到达UI前映射为用户友好的本地化消息——绝不向用户展示原始异常字符串
13. Internationalization (l10n)
13. 国际化(l10n)
Setup:
配置:
- Localization solution configured (Flutter's built-in ARB/l10n, easy_localization, or equivalent)
- Supported locales declared in app configuration
- 已配置本地化方案(Flutter内置的ARB/l10n、easy_localization或等价方案)
- 应用配置中声明了支持的区域设置
Content:
内容:
- All user-visible strings use the localization system — no hardcoded strings in widgets
- Template file includes descriptions/context for translators
- ICU message syntax used for plurals, genders, selects
- Placeholders defined with types
- No missing keys across locales
- 所有用户可见字符串使用本地化系统——Widget中无硬编码字符串
- 模板文件包含翻译人员所需的描述/上下文
- ICU消息语法用于复数、性别、选择
- 占位符已定义类型
- 所有区域设置无缺失的键
Code review:
代码审查:
- Localization accessor used consistently throughout the project
- Date, time, number, and currency formatting is locale-aware
- Text directionality (RTL) supported if targeting Arabic, Hebrew, etc.
- No string concatenation for localized text — use parameterized messages
- 项目中统一使用本地化访问器
- 日期、时间、数字和货币格式支持区域设置
- 若目标为阿拉伯语、希伯来语等,支持文本方向(RTL)
- 本地化文本不使用字符串拼接——使用参数化消息
14. Dependency Injection
14. 依赖注入
Principles (apply to any DI approach):
原则(适用于任何DI方案):
- Classes depend on abstractions (interfaces), not concrete implementations at layer boundaries
- Dependencies provided externally via constructor, DI framework, or provider graph — not created internally
- Registration distinguishes lifetime: singleton vs factory vs lazy singleton
- Environment-specific bindings (dev/staging/prod) use configuration, not runtime checks
if - No circular dependencies in the DI graph
- Service locator calls (if used) are not scattered throughout business logic
- 类依赖抽象(接口),而非层边界的具体实现
- 依赖通过构造函数、DI框架或Provider图外部提供——而非内部创建
- 注册时区分生命周期:单例、工厂、懒加载单例
- 环境特定绑定(开发/预发布/生产)使用配置,而非运行时检查
if - DI图中无循环依赖
- 服务定位器调用(若使用)不分散在业务逻辑中
15. Static Analysis
15. 静态分析
Configuration:
配置:
- present with strict settings enabled
analysis_options.yaml - Strict analyzer settings: ,
strict-casts: true,strict-inference: truestrict-raw-types: true - A comprehensive lint rule set is included (very_good_analysis, flutter_lints, or custom strict rules)
- All sub-packages in monorepos inherit or share the root analysis options
- 存在并启用严格设置
analysis_options.yaml - 严格分析器设置:、
strict-casts: true、strict-inference: truestrict-raw-types: true - 包含全面的lint规则集(very_good_analysis、flutter_lints或自定义严格规则)
- 单仓库中的所有子包继承或共享根目录的分析选项
Enforcement:
执行:
- No unresolved analyzer warnings in committed code
- Lint suppressions () are justified with comments explaining why
// ignore: - runs in CI and failures block merges
flutter analyze
- 提交的代码中无未解决的分析器警告
- Lint抑制()附带注释说明原因
// ignore: - 在CI中运行,失败会阻止合并
flutter analyze
Key rules to verify regardless of lint package:
无论lint包如何,都要验证的关键规则:
- — performance in widget trees
prefer_const_constructors - — use proper logging
avoid_print - — prevent fire-and-forget async bugs
unawaited_futures - — immutability at variable level
prefer_final_locals - — explicit contracts
always_declare_return_types - — specific error handling
avoid_catches_without_on_clauses - — consistent import style
always_use_package_imports
- ——Widget树性能
prefer_const_constructors - ——使用正确的日志
avoid_print - ——防止异步遗忘bug
unawaited_futures - ——变量级别的不可变性
prefer_final_locals - ——显式契约
always_declare_return_types - ——特定错误处理
avoid_catches_without_on_clauses - ——统一导入风格
always_use_package_imports
State Management Quick Reference
状态管理快速参考
The table below maps universal principles to their implementation in popular solutions. Use this to adapt review rules to whichever solution the project uses.
| Principle | BLoC/Cubit | Riverpod | Provider | GetX | MobX | Signals | Built-in |
|---|---|---|---|---|---|---|---|
| State container | | | | | | | |
| UI consumer | | | | | | | |
| Selector | | | | N/A | computed | | N/A |
| Side effects | | | | | | | callbacks |
| Disposal | auto via | | auto via | | | manual | |
| Testing | | | | | store directly | signal directly | widget test |
下表将通用原则映射到主流方案的实现。使用此表将审查规则适配到项目使用的方案。
| 原则 | BLoC/Cubit | Riverpod | Provider | GetX | MobX | Signals | 内置方案 |
|---|---|---|---|---|---|---|---|
| 状态容器 | | | | | | | |
| UI消费者 | | | | | | | |
| 选择器 | | | | N/A | computed | | N/A |
| 副作用 | | | | | | | 回调 |
| 销毁 | 通过 | | 通过 | | | 手动销毁 | |
| 测试 | | | 直接使用 | 测试中使用 | 直接使用store | 直接使用signal | Widget测试 |