Flutter 开发代码规范(优化完善版)
📋 目录
- 一、规范目标
- 二、目录结构规范(强制)
- 三、命名规范(强制)
- 四、代码风格规范(强制+建议)
- 五、状态管理规范(强制+建议)
- 六、GetX使用规范(强制+建议)
- 七、性能优化规范(强制)
- 八、资源与配置规范(强制)
- 九、测试与质量规范(强制+建议)
- 十、最佳实践(建议)
- 十一、规范落地与维护
一、规范目标
- 统一团队开发风格,降低协作成本,提升代码可读性、可维护性;
- 规避常见性能问题,保障应用流畅性与稳定性;
- 符合 Dart/Flutter 官方最佳实践,兼容生态工具链(静态分析、测试、文档生成);
- 明确边界规则(强制约束+建议规范),平衡规范严谨性与开发效率。
二、目录结构规范(强制)
基于 Flutter 工程特性,采用 功能+分层 混合结构,核心目录集中在 lib/ 下。
项目结构示例
perl
lib/
├── app/ # 应用入口与全局配置
│ ├── app.dart # 根组件(MyApp)
│ ├── routes.dart # 路由配置(路由表+守卫)
│ ├── theme.dart # 主题配置(亮色/暗色模式、全局样式)
│ └── config.dart # 全局常量(环境变量、接口域名、第三方密钥)
├── core/ # 核心能力层(通用业务+基础依赖)
│ ├── network/ # 网络请求(封装、拦截器、接口定义)
│ │ ├── api.dart # 接口方法(按业务模块拆分,如 user_api.dart)
│ │ ├── client.dart # dio 实例封装(拦截器、超时配置)
│ │ └── model/ # 接口模型(请求/响应实体,用 freezed/json_serializable)
│ ├── storage/ # 本地存储(统一封装)
│ │ ├── sp_utils.dart # SharedPreferences 封装
│ │ └── hive_utils.dart # Hive 数据库封装
│ ├── state/ # 全局状态管理(如 AppBloc、UserProvider)
│ └── utils/ # 工具类(无业务依赖)
│ ├── log_utils.dart # 日志工具(替代 print)
│ ├── toast_utils.dart # 提示工具
│ └── date_utils.dart # 日期工具
├── features/ # 业务功能模块(按功能拆分,内部自治)
│ ├── home/ # 首页模块
│ │ ├── presentation/ # UI 层(页面+组件)
│ │ │ ├── home_page.dart # 页面组件
│ │ │ └── widgets/ # 模块内私有组件
│ │ ├── state/ # 模块内状态(如 HomeBloc、HomeProvider)
│ │ └── domain/ # 业务逻辑(实体、仓库接口)
│ └── user/ # 用户模块(登录/注册/个人中心)
│ ├── presentation/
│ ├── state/
│ └── domain/
├── common/ # 全局公共资源
│ ├── widgets/ # 全局复用组件(如 Button、Input、EmptyView)
│ ├── res/ # 静态资源(集中管理,避免硬编码)
│ │ ├── images.dart # 图片路径常量(AssetImage 封装)
│ │ ├── strings.dart # 字符串常量(支持国际化)
│ │ ├── colors.dart # 颜色常量(主题色+功能色)
│ │ └── dimens.dart # 尺寸常量(间距、字体大小)
│ └── styles/ # 全局样式(文本样式、圆角、阴影)
└── main.dart # 程序入口(仅初始化 App 根组件)
目录规范说明
- 禁止 在
lib/根目录直接存放业务代码(仅保留main.dart); features/按业务模块拆分,模块内遵循 UI-状态-业务 分层,确保高内聚低耦合;- 公共组件/工具需区分 全局(common) 与 模块私有(features/xxx/widgets),避免滥用全局;
- 静态资源(图片、字符串等)必须通过常量类引用,禁止硬编码 (如
Image.asset('images/icon.png')→Image.asset(Images.icon))。
三、命名规范(强制)
完全遵循 Dart 官方命名约定,补充 Flutter 特有场景规则:
| 类型 | 命名规则 | 示例 | 禁止示例 |
|---|---|---|---|
| 类(Class/Widget) | 大驼峰(PascalCase),名词/名词短语 | HomePage、CustomButton、UserModel |
home_page、custom_button |
| 函数/方法 | 小驼峰(camelCase),动词/动词短语 | getUserInfo()、showToast() |
get_user_info()、ShowToast() |
| 变量/参数 | 小驼峰(camelCase),名词/名词短语 | userName、isLogin、itemCount |
user_name、IsLogin |
| 常量(const/final) | 全大写+下划线(UPPER_SNAKE_CASE) | const MAX_COUNT = 10;、final API_BASE_URL = 'xxx' |
maxCount、apiBaseUrl |
| 枚举(enum) | 枚举名:大驼峰;枚举值:小驼峰 | enum LoginType { phone, wechat, qq } |
enum login_type { Phone, Wechat } |
| 路由名称 | 小写+下划线,格式:/模块/页面 |
/user/login、/home/index |
/User/Login、homeLogin |
| 文件名 | 小写+下划线(snake_case),与类名对应 | home_page.dart(对应 HomePage) |
HomePage.dart、homePage.dart |
| 包名(package) | 小写+下划线,全小写无大写 | package:my_app/features/user |
package:MyApp/Features/User |
特殊命名补充
- 布尔变量 建议前缀:
is、has、can、should(如isVisible、hasPermission); - 回调函数 命名:后缀
Callback,如OnLoginCallback、OnItemClickCallback; - 状态管理 相关:Bloc 类名后缀
Bloc(如CounterBloc),状态类后缀State(如CounterState); - GetX控制器 :类名后缀
Controller(如HomeController、UserController); - 避免使用缩写 (除非是通用缩写如
API、UI),禁止拼音 命名(如shouyePage→homePage)。
四、代码风格规范(强制+建议)
4.1 基础格式(强制)
-
缩进:2 个空格(Dart 官方标准),禁止使用 Tab;
-
行宽:单行长不超过 120 字符(超出需换行),IDE 开启自动换行;
-
空行 :
- 类/函数之间留 1 个空行;
- 函数内部逻辑块之间留 1 个空行;
- 避免连续空行(最多 1 个);
-
括号 :
- 类、函数、条件语句的左括号与声明在同一行,右括号单独成行;
- 单行条件语句可省略括号(但逻辑复杂时必须加);
dart// 正确 if (isLogin) { navigateToHome(); } // 允许(单行简单逻辑) if (isLogin) navigateToHome(); // 错误 if (isLogin) { navigateToHome(); } -
分号:每条语句必须加分号(Dart 不是 JavaScript);
-
逗号 :对象/数组最后一个元素后建议加逗号(IDE 自动格式化时更整洁);
dartfinal user = User( name: '张三', age: 20, // 末尾逗号建议保留 );
4.2 Widget 开发规范(强制)
-
优先使用 StatelessWidget:无状态组件性能更优,仅当需要维护状态时使用 StatefulWidget;
-
Widget 拆分原则 :
- 单个 Widget 代码不超过 100 行,超出则拆分为子 Widget(私有组件放
widgets/目录); - 重复出现的 UI 片段(如列表项、按钮)必须提取为公共组件;
- 避免嵌套过深(建议不超过 4 层),使用
Padding/SizedBox替代嵌套Container,用Wrap替代多层Row/Column;
dart// 错误(嵌套过深) Container( child: Padding( padding: EdgeInsets.all(16), child: Row( children: [ Container( child: Text('标题'), ), ], ), ), ); // 正确(拆分+简化嵌套) Padding( padding: EdgeInsets.all(Dimens.p16), child: Row( children: [ _buildTitleText(), ], ), ); Widget _buildTitleText() => Text(Strings.title); - 单个 Widget 代码不超过 100 行,超出则拆分为子 Widget(私有组件放
-
build 方法规范 :
- 禁止在
build中执行耗时操作(如网络请求、数据库查询、复杂计算); - 禁止在
build中创建临时对象(如List、Map、Style),需缓存为final变量或提取为常量;
dart// 错误(build 中创建 List) @override Widget build(BuildContext context) { return ListView( children: [Text('1'), Text('2')], // 每次 build 重建 List ); } // 正确(缓存为 final) final List<Widget> _listItems = [Text('1'), Text('2')]; @override Widget build(BuildContext context) { return ListView(children: _listItems); } - 禁止在
-
const 构造函数 :
- 无状态 Widget 且构造参数均为常量时,必须使用
const构造函数(减少重建开销);
dart// 正确 const CustomButton({super.key, required this.onTap}); // 调用时也需加 const(除非父组件非 const) const CustomButton(onTap: _handleTap); - 无状态 Widget 且构造参数均为常量时,必须使用
-
命名参数 :
- Widget 构造函数仅使用命名参数(
{}包裹),必填参数加required关键字; - 参数顺序:核心功能参数(如
onTap、data)在前,样式参数(如color、size)在后;
dart// 正确 const CustomButton({ super.key, required this.onTap, // 必填参数在前 this.text = '', this.color = Colors.blue, // 样式参数在后 this.radius = 8.0, }); - Widget 构造函数仅使用命名参数(
4.3 语法规范(强制)
-
Null Safety 强制启用 :
- 禁止使用
dynamic类型(除非与第三方库兼容必须使用); - 可空类型必须显式声明(
String?),非空类型必须初始化(或用late延迟初始化); - 避免滥用
!空断言(优先用??、?.、if (xxx != null)安全访问);
dart// 错误 String name; // 非空类型未初始化 print(user!.name); // 滥用 ! 断言 // 正确 String? name; print(user?.name ?? '默认名称'); // 安全访问+默认值 - 禁止使用
-
集合使用 :
- 优先使用不可变集合(
const List、const Map),可变集合需显式声明var或List<T>; - 禁止使用
List()无参构造,用[]替代(更简洁);
- 优先使用不可变集合(
-
循环与条件 :
- 简单条件用
??、?.、if-else,复杂条件提取为布尔变量(提升可读性); - 列表遍历优先用
forEach(简单逻辑)或map(转换场景),避免嵌套循环;
- 简单条件用
-
扩展方法 :
- 复用逻辑优先用
extension扩展(如StringExtension、DateTimeExtension),替代工具类静态方法;
dart// 正确(扩展方法) extension StringExtension on String { String capitalize() => isEmpty ? this : '${this[0].toUpperCase()}${substring(1)}'; } // 使用 'hello'.capitalize(); // 比 StringUtils.capitalize('hello') 更简洁 - 复用逻辑优先用
4.4 注释规范(强制+建议)
-
文档注释(强制) :
- 公开类、函数、常量必须加文档注释(
///开头,支持 DartDoc 生成文档); - 注释需说明 功能用途 + 参数含义 + 返回值 + 异常场景(必要时);
dart/// 自定义按钮组件 /// /// [onTap]:点击回调(必填) /// [text]:按钮文本(默认空字符串) /// [color]:按钮背景色(默认蓝色) /// 返回:无 const CustomButton({ super.key, required this.onTap, this.text = '', this.color = Colors.blue, }); - 公开类、函数、常量必须加文档注释(
-
单行注释(建议) :
- 复杂逻辑、特殊处理(如兼容适配、临时方案)需加单行注释(
//开头); - 注释说明 为什么这么做 ,而非 做了什么(代码本身应体现做了什么);
- 复杂逻辑、特殊处理(如兼容适配、临时方案)需加单行注释(
-
注释禁忌 :
- 禁止注释无用代码(直接删除,版本控制可回溯);
- 禁止冗余注释(如
// 定义名称变量→String name;)。
五、状态管理规范(强制+建议)
5.1 方案选择(建议)
根据项目复杂度选择合适的状态管理方案,团队统一使用一种核心方案:
| 项目规模 | 推荐方案 | 适用场景 |
|---|---|---|
| 小型项目 | StatefulWidget + setState | 仅需局部状态(如单个页面开关、计数器) |
| 中型项目 | Provider/Riverpod | 跨组件状态共享(如用户信息、主题) |
| 大型项目 | Bloc/Cubit | 复杂业务逻辑(如表单提交、流程控制) |
| 快速迭代 | GetX(推荐使用) | 个人项目或小团队,需简化模板代码 |
5.2 分层原则(强制)
无论使用哪种方案,必须遵循 UI 层-状态层-数据层 分离:
- UI 层(Widget):仅负责渲染,不包含业务逻辑,通过状态层获取数据;
- 状态层(Bloc/Provider/Controller):管理状态,处理业务逻辑,不直接操作数据源;
- 数据层(Repository/API):负责数据获取(网络、本地存储),提供统一接口给状态层;
5.3 状态管理禁忌(强制)
- 禁止在 Widget 中直接调用网络/存储接口(必须通过数据层);
- 禁止全局状态滥用(优先使用局部状态,跨组件共享才用全局状态);
- 禁止状态层持有 Widget 引用(如
BuildContext),通过回调/事件通信; - Bloc 规范 :
- 事件(Event)命名后缀
Event(如LoginEvent),状态(State)命名后缀State; - 一个 Bloc 对应一个业务场景,避免超大 Bloc(拆分多个子 Bloc);
- 状态必须是不可变的(使用
freezed生成不可变类)。
- 事件(Event)命名后缀
六、GetX使用规范(强制+建议)
6.1 项目结构适配
当使用GetX时,建议在features/模块内调整目录结构,遵循GetX推荐的三层架构:
bash
features/user/
├── data/ # 数据层
│ ├── models/ # 数据模型
│ ├── repositories/ # 数据仓库
│ └── datasources/ # 数据源(本地/远程)
├── domain/ # 业务逻辑层
│ ├── usecases/ # 用例
│ └── repositories/ # 仓库接口
└── presentation/ # 表现层
├── controllers/ # 控制器
├── views/ # 页面视图
├── widgets/ # 私有组件
└── bindings/ # 绑定类
6.2 Controller规范
-
控制器命名与位置:
- 控制器类名后缀必须为
Controller(如UserController); - 控制器文件存放在对应模块的
presentation/controllers/目录; - 每个页面/功能对应一个控制器,避免超大控制器;
dart// features/user/presentation/controllers/user_controller.dart class UserController extends GetxController { // 状态变量 final RxString userName = ''.obs; final RxBool isLoading = false.obs; final RxList<User> userList = <User>[].obs; // 计算属性 String get formattedName => userName.value.toUpperCase(); // 生命周期 @override void onInit() { super.onInit(); loadUserData(); } @override void onClose() { // 清理资源 super.onClose(); } // 方法 Future<void> loadUserData() async { isLoading.value = true; try { final users = await userRepository.getUsers(); userList.assignAll(users); } catch (e) { Get.snackbar('错误', '加载用户数据失败'); } finally { isLoading.value = false; } } } - 控制器类名后缀必须为
-
状态管理:
- 使用
.obs创建响应式变量,变量名使用Rx前缀约定; - 私有状态使用
_前缀,公开状态提供getter方法; - 使用
update()方法触发UI更新(非响应式变量变更时);
dartclass UserController extends GetxController { // 响应式变量 final RxInt _counter = 0.obs; int get counter => _counter.value; // 非响应式变量 String _searchKeyword = ''; void increment() { _counter.value++; } void updateKeyword(String keyword) { _searchKeyword = keyword; update(); // 手动触发更新 } } - 使用
6.3 路由管理规范
-
路由定义:
- 使用GetX的路由管理系统,集中管理路由;
- 路由名称使用小写+下划线格式,与页面路径对应;
dart// app/routes.dart class AppRoutes { static const String splash = '/'; static const String home = '/home'; static const String login = '/login'; static const String userDetail = '/user/detail'; static final List<GetPage> pages = [ GetPage( name: splash, page: () => SplashScreen(), binding: SplashBinding(), ), GetPage( name: home, page: () => HomeScreen(), bindings: [HomeBinding(), UserBinding()], transition: Transition.fadeIn, ), GetPage( name: userDetail, page: () => UserDetailScreen(), binding: UserDetailBinding(), transition: Transition.rightToLeft, ), ]; } -
路由跳转:
- 使用GetX提供的路由方法,保持统一;
- 带参数跳转时使用
arguments传递对象;
dart// 页面跳转 Get.toNamed(AppRoutes.home); Get.offNamed(AppRoutes.login); // 关闭当前页面 Get.offAllNamed(AppRoutes.home); // 关闭所有页面 // 带参数跳转 Get.toNamed( AppRoutes.userDetail, arguments: User(id: 1, name: '张三'), parameters: {'from': 'home'}, // URL参数 ); // 获取参数 final user = Get.arguments as User; final from = Get.parameters['from'];
6.4 依赖管理规范
-
Binding类使用:
- 每个控制器对应一个Binding类,处理依赖注入;
- Binding类名后缀为
Binding,与控制器对应;
dart// features/user/presentation/bindings/user_binding.dart class UserBinding extends Bindings { @override void dependencies() { // 懒加载注入 Get.lazyPut<UserRepository>( () => UserRepositoryImpl(Get.find<ApiClient>()), fenix: true, // 页面关闭后不会被销毁,下次进入时重用 ); // 立即注入 Get.put<UserController>( UserController(Get.find<UserRepository>()), permanent: true, // 全局单例 ); } } -
依赖获取:
- 使用
Get.find<T>()获取依赖,避免直接new对象; - 在控制器构造函数中注入依赖;
dartclass UserController extends GetxController { final UserRepository userRepository; UserController(this.userRepository); // 或者使用Get.find()延迟获取 UserRepository get repo => Get.find<UserRepository>(); } - 使用
6.5 Widget使用规范
-
响应式Widget:
- 使用
Obx()、GetX()或GetBuilder包裹响应式部分; - 最小化响应范围,避免整个页面重建;
dartclass UserProfile extends StatelessWidget { final UserController controller = Get.find<UserController>(); @override Widget build(BuildContext context) { return Column( children: [ // 使用Obx响应式更新 Obx(() => Text( '用户数: ${controller.userCount.value}', style: TextStyle( color: controller.isLoading.value ? Colors.grey : Colors.black, ), )), // 使用GetBuilder手动更新 GetBuilder<UserController>( builder: (controller) { return Text('用户名: ${controller.userName}'); }, ), // 列表使用Obx Obx(() => ListView.builder( shrinkWrap: true, itemCount: controller.userList.length, itemBuilder: (context, index) { final user = controller.userList[index]; return UserItem(user: user); }, )), ], ); } } - 使用
-
页面Widget规范:
- 页面继承
GetView<T>获取控制器; - 使用
Get.put()初始化控制器;
dart// features/user/presentation/views/user_screen.dart class UserScreen extends GetView<UserController> { const UserScreen({super.key}); @override Widget build(BuildContext context) { // 自动获取控制器 return Scaffold( appBar: AppBar( title: Obx(() => Text('用户(${controller.userCount})')), ), body: controller.obx( (state) => _buildUserList(), onLoading: const CircularProgressIndicator(), onError: (error) => ErrorView(error: error), onEmpty: const EmptyView(), ), ); } Widget _buildUserList() { return RefreshIndicator( onRefresh: controller.loadUsers, child: ListView.builder( itemCount: controller.userList.length, itemBuilder: (context, index) { return UserItem(user: controller.userList[index]); }, ), ); } } - 页面继承
6.6 工具类使用规范
-
GetX工具类:
- 统一使用GetX提供的工具方法;
- 禁止混用原生方法与GetX方法;
dart// 正确 Get.snackbar('标题', '消息内容'); Get.dialog(ConfirmDialog()); Get.bottomSheet(OptionsSheet()); Get.toNamed('/detail'); // 避免混用 // 错误:混用GetX和原生方法 showDialog(context: context, builder: ...); // 避免使用 Navigator.push(context, ...); // 避免使用 -
国际化:
- 使用GetX的国际化方案;
- 统一管理翻译文件;
dart// 使用 Text('title'.tr); Text('greeting'.trParams({'name': '张三'})); // 切换语言 Get.updateLocale(Locale('zh', 'CN'));
6.7 GetX最佳实践
-
性能优化:
- 使用
worker监听状态变化,执行副作用; - 使用
debounce、interval避免频繁更新;
dartclass SearchController extends GetxController { final RxString searchKeyword = ''.obs; @override void onInit() { super.onInit(); // 监听搜索关键词变化,延迟500ms执行搜索 debounce( searchKeyword, (_) => performSearch(), time: Duration(milliseconds: 500), ); // 每隔1秒执行一次 interval( searchKeyword, (_) => logSearch(), time: Duration(seconds: 1), ); } void performSearch() { // 执行搜索逻辑 } } - 使用
-
状态持久化:
- 使用
GetStorage进行轻量级数据持久化; - 重要数据使用
shared_preferences或hive;
dartfinal box = GetStorage(); // 保存数据 box.write('user_token', 'abc123'); // 读取数据 String token = box.read('user_token'); // 监听数据变化 box.listen(() { print('数据发生变化'); }); - 使用
-
错误处理:
- 统一使用GetX的错误处理机制;
- 全局异常捕获;
dart// 全局配置 void main() { // 全局错误处理 Get.config( enableLog: true, defaultPopGesture: true, defaultTransition: Transition.cupertino, ); // 异常处理 FlutterError.onError = (details) { Get.snackbar('错误', details.exception.toString()); }; runApp(MyApp()); }
七、性能优化规范(强制)
7.1 渲染优化
- 列表优化 :
- 长列表必须使用
ListView.builder/GridView.builder(懒加载),禁止使用ListView(children: [...])(一次性加载所有子项); - 列表项高度固定时,设置
itemExtent提升性能;
- 长列表必须使用
- 图片优化 :
- 图片资源按分辨率适配(
mipmap-mdpi/mipmap-hdpi等),避免使用超大图; - 网络图片使用缓存(如
cached_network_image),设置占位图/错误图; - 本地图片使用
AssetImage而非FileImage,并通过images.dart常量引用;
- 图片资源按分辨率适配(
- 减少重绘 :
- 频繁变化的 Widget 用
RepaintBoundary包裹(隔离重绘区域); - 避免在
AnimatedBuilder外部构建无关 Widget;
- 频繁变化的 Widget 用
7.2 内存优化
- 资源释放 :
- 订阅流(Stream)、Bloc 必须在
dispose中取消订阅(使用cancel()或BlocListener自动管理); - 大文件(如视频、音频)使用后及时释放,避免内存泄漏;
- 订阅流(Stream)、Bloc 必须在
- 避免内存泄漏场景 :
- 禁止匿名函数/闭包持有
BuildContext长期引用(如异步回调中); - 禁止静态变量持有 Widget/State 实例;
- 禁止匿名函数/闭包持有
7.3 代码执行优化
- 异步操作 :
- 网络请求、文件读写必须用异步(
async/await),禁止同步阻塞; - 多个独立异步任务用
Future.wait并行执行(而非串行await);
- 网络请求、文件读写必须用异步(
- 计算优化 :
- 复杂计算(如数据解析、加密)放在
isolate中执行(避免阻塞 UI 线程); - 重复计算结果缓存(如
final变量、memoizer工具)。
- 复杂计算(如数据解析、加密)放在
八、资源与配置规范(强制)
8.1 静态资源管理
-
图片 :
- 存放路径:
assets/images/(按模块拆分,如assets/images/user/); - 引用方式:通过
common/res/images.dart常量封装,禁止硬编码路径;
dart// common/res/images.dart class Images { static const String userAvatar = 'assets/images/user/avatar.png'; static const String iconHome = 'assets/images/home/icon_home.png'; } // 使用 Image.asset(Images.userAvatar); - 存放路径:
-
字符串 :
- 存放路径:
common/res/strings.dart(支持国际化时用flutter_localizations); - 禁止硬编码字符串(便于统一修改和国际化);
- 存放路径:
-
颜色/尺寸 :
- 颜色:区分主题色(
primaryColor)、功能色(successColor/errorColor),禁止直接使用Colors.blue(统一维护); - 尺寸:统一间距(
p8/p16)、字体大小(font14/font18),避免魔法数字;
- 颜色:区分主题色(
8.2 路由管理
-
路由配置 :
- 集中管理路由表(
app/routes.dart),使用命名路由(禁止直接Navigator.push(Builder...)); - 路由名称格式:
/模块/页面(如/user/login、/order/detail);
dart// app/routes.dart class Routes { static const String home = '/home/index'; static const String login = '/user/login'; static final Map<String, WidgetBuilder> routes = { home: (context) => const HomePage(), login: (context) => const LoginPage(), }; } - 集中管理路由表(
-
路由跳转 :
- 使用封装后的路由工具(如
RouterUtils.push(context, Routes.login)),统一处理动画、参数传递; - 路由参数传递:优先使用强类型(如通过
ModalRoute.of(context)?.settings.arguments封装模型),避免Map动态类型;
- 使用封装后的路由工具(如
-
路由守卫 :
- 全局路由守卫(如未登录拦截跳登录页)统一在
MaterialApp.onGenerateRoute中处理;
- 全局路由守卫(如未登录拦截跳登录页)统一在
8.3 依赖管理
pubspec.yaml规范 :- 依赖分组(
dependencies/dev_dependencies),并按功能排序(如网络、状态管理、工具类); - 版本约束:核心依赖(如
flutter_bloc、dio)指定稳定版本(如^8.1.0),避免使用any; - 定期更新依赖(
flutter pub upgrade),并移除未使用的依赖(flutter pub clean+dart pub deps检查);
- 依赖分组(
- 禁止重复依赖:如已使用
dio,禁止再引入http库(统一网络请求工具)。
九、测试与质量规范(强制+建议)
9.1 测试规范(建议)
- 测试分层 :
- 单元测试 :测试工具类、业务逻辑(如
utils/、domain/层),覆盖率≥80%; - Widget 测试 :测试核心 UI 组件(如
common/widgets/),验证渲染正确性; - 集成测试:测试关键业务流程(如登录→首页→下单);
- 单元测试 :测试工具类、业务逻辑(如
- 测试文件存放 :
- 单元测试/Widget 测试:与被测试文件同目录,命名为
xxx_test.dart; - 集成测试:放在
integration_test/目录下;
- 单元测试/Widget 测试:与被测试文件同目录,命名为
9.2 静态代码检查(强制)
-
启用 lint 规则 :
- 基础规则:使用
flutter_lints(官方推荐); - 增强规则:集成
dart_code_metrics,自定义强制规则(如禁止print、禁止build中创建对象);
- 基础规则:使用
-
配置文件 :在
analysis_options.yaml中配置规则,示例:yamlinclude: package:flutter_lints/flutter.yaml linter: rules: - avoid_print # 禁止使用 print(用 log_utils 替代) - prefer_const_constructors # 强制使用 const 构造函数 - no_logic_in_create_state # 禁止在 createState 中写逻辑 - unused_import # 禁止未使用的导入 analyzer: exclude: - build/ - .dart_tool/ -
提交前检查 :
- 必须执行
flutter analyze(静态分析无错误); - 必须执行
flutter format .(自动格式化代码);
- 必须执行
9.3 版本控制规范(强制)
- 分支管理 :
master:生产环境分支(仅合并release/hotfix分支);feature:开发分支(日常开发、合并feature分支);release/xxx:上线分支(从master拉出,完成后合并回feature);hotfix/xxx:紧急修复分支(从master拉出,修复后合并master);release/hotfix:分支发布成功后合并到master;
- 提交信息规范 :
- 格式:
类型: 描述(英文,首字母小写); - 类型:
feat(新增功能)、fix(修复 bug)、docs(文档)、style(格式调整)、refactor(重构)、test(测试)、chore(依赖/构建调整); - 示例:
feat: add login page、fix: resolve login button unclickable issue;
- 格式:
- 提交粒度:单次提交仅包含一个功能/修复,代码量不超过 300 行(便于 Code Review)。
十、最佳实践(建议)
- 国际化 :使用
flutter_localizations+intl库,字符串按语言拆分(strings_en.dart/strings_zh.dart); - 主题适配 :支持深色模式,全局样式通过
Theme.of(context)获取(如Theme.of(context).primaryColor); - 错误处理 :
- 统一网络错误(如 401 未授权、500 服务器错误)拦截处理;
- 全局异常捕获(
FlutterError.onError+runZonedGuarded),收集崩溃日志;
- 代码复用 :
- 公共逻辑提取为
mixin(如ToastMixin、LoadingMixin),避免继承冗余; - 表单验证逻辑封装为工具类(如
FormValidator.validatePhone());
- 公共逻辑提取为
- 工具推荐 :
- 序列化:
freezed+json_serializable(自动生成模型代码); - 路由:
auto_route(类型安全路由,避免手动配置); - 日志:
logger(替代print,支持分级日志、格式化输出); - 代码生成:
build_runner(自动化生成模板代码)。
- 序列化:
十一、规范落地与维护
- 工具保障 :IDE 安装
Flutter/Dart插件,开启自动格式化、lint 实时检查; - CI/CD 集成 :提交代码时触发 CI 流程(执行
flutter analyze、flutter test),不通过则禁止合并; - 定期复盘:每季度更新规范(适配 Flutter 新版本特性),团队内部分享违规案例与优化方案;
- 新人培训:将规范纳入新人入职培训,配套示例项目(展示规范落地效果)。