KingSchool 开发代码规范(优化完善版)

53 阅读13分钟

Flutter 开发代码规范(优化完善版)

目录

一、规范目标

  1. 统一团队开发风格,降低协作成本,提升代码可读性、可维护性;
  2. 规避常见性能问题,保障应用流畅性与稳定性;
  3. 符合 Dart/Flutter 官方最佳实践,兼容生态工具链(静态分析、测试、文档生成);
  4. 明确边界规则(强制约束+建议规范),平衡规范严谨性与开发效率。

二、目录结构规范(强制)

基于现有项目结构和新项目特点,采用功能模块化+核心共享的混合结构:

现有项目结构(适配当前项目)

lib/
├── main.dart                  # 应用入口
├── orientation.dart           # 屏幕方向控制
├── time.dart                  # 时间相关功能
├── color/                     # 颜色定义目录
│   └── color.dart
├── components/                # 公共组件目录
│   ├── general_widget/       # 通用组件
│   │   ├── ksAppBar.dart
│   │   ├── ksButton.dart
│   │   └── ksCard.dart
│   ├── analytics_debug/      # 分析调试组件
│   ├── echarts/              # 图表组件
│   ├── float_button/         # 浮动按钮组件
│   └── [其他单独组件]
├── const/                     # 常量目录
│   └── resource.dart
├── entities/                  # 实体模型目录
│   ├── pageView.dart
│   └── theme.dart
├── extension/                 # 扩展目录
│   ├── build_context_extension.dart
│   ├── screen_extension.dart
│   └── widget_extension.dart
├── i18n/                      # 国际化目录
│   ├── locales/
│   │   ├── en.json
│   │   └── zh.json
│   ├── app_localizations.dart
│   └── language_controller.dart
├── generated/                 # 生成代码目录
│   └── json/
│       ├── CoursePlanData.g.dart
│       └── [其他生成文件]
├── mainClass/                 # 主要业务类目录
│   ├── course_data_entity.dart
│   ├── myCourse.dart
│   ├── loginStudent.dart
│   ├── sharedPreferencesUtils.dart
│   └── [其他业务类]
├── pages/                     # 页面目录
│   ├── home.dart
│   ├── backparent.dart
│   ├── Comment/              # 评论模块
│   ├── IntelligentEnglish/   # 智能英语模块
│   ├── SynchronousClassroom/ # 同步课堂模块
│   ├── chinese/              # 语文模块
│   ├── math/                 # 数学模块
│   ├── programming/          # 编程模块
│   └── zhimi/                # 智米模块
├── routes/                    # 路由目录
│   ├── app_routes.dart
│   ├── routes.dart
│   ├── getx_router_observer.dart
│   └── navigator_observer.dart
├── services/                  # 服务目录
│   ├── apiResponseData/      # API响应数据
│   ├── analysis/             # 分析服务
│   ├── testWord/             # 测试单词
│   ├── Httputil.dart
│   ├── request.dart
│   └── [其他服务]
├── style/                     # 样式目录
│   ├── c_base.dart
│   ├── c_color.dart
│   ├── c_font.dart
│   └── c_radii.dart
├── util/                      # 工具目录
│   ├── httpRequest.dart
│   ├── localStorage.dart
│   ├── log_util.dart
│   ├── eventBus.dart
│   └── sensorsAnalytics.dart
└── common/                    # 公共目录
    ├── value/                 # 值定义
    ├── api.dart
    ├── check.dart
    ├── login.dart
    └── [其他公共工具]

三、命名规范(强制)

完全遵循 Dart 官方命名约定,补充 Flutter 特有场景规则:

类型命名规则示例禁止示例
类(Class/Widget)大驼峰(PascalCase),名词/名词短语HomePageCustomButtonUserModelhome_pagecustom_button
函数/方法小驼峰(camelCase),动词/动词短语getUserInfo()showToast()get_user_info()ShowToast()
变量/参数小驼峰(camelCase),名词/名词短语userNameisLoginitemCountuser_nameIsLogin
常量(const/final)全大写+下划线(UPPER_SNAKE_CASE)const MAX_COUNT = 10;final API_BASE_URL = 'xxx'maxCountapiBaseUrl
枚举(enum)枚举名:大驼峰;枚举值:小驼峰enum LoginType { phone, wechat, qq }enum login_type { Phone, Wechat }
路由名称小写+下划线,格式:/模块/页面/user/login/home/index/User/LoginhomeLogin
文件名小写+下划线(snake_case),与类名对应home_page.dart(对应 HomePageHomePage.darthomePage.dart
包名(package)小写+下划线,全小写无大写package:my_app/modules/userpackage:MyApp/Modules/User
私有属性/方法下划线开头 + 小驼峰_userName_calculateTotal()privateUserNamecalculateTotal()

特殊命名补充

  1. 布尔变量建议前缀:ishascanshould(如 isVisiblehasPermission);
  2. 回调函数命名:后缀 Callback,如 OnLoginCallbackOnItemClickCallback
  3. 状态管理相关:
    • GetX控制器类名后缀 Controller(如 UserController);
    • Bloc 类名后缀 Bloc(如 CounterBloc),状态类后缀 State
  4. 工具类:后缀 UtilsHelper(如 DateUtilsStringHelper);
  5. 扩展类:后缀 Extension(如 StringExtensionDateTimeExtension);
  6. 避免使用缩写(除非是通用缩写如 APIUI),禁止拼音命名(如 shouyePagehomePage)。

四、代码风格规范(强制+建议)

4.1 基础格式(强制)

  1. 缩进:2 个空格(Dart 官方标准),禁止使用 Tab;
  2. 行宽:单行长不超过 120 字符(超出需换行),IDE 开启自动换行;
  3. 空行
    • 类/函数之间留 1 个空行;
    • 函数内部逻辑块之间留 1 个空行;
    • 避免连续空行(最多 1 个);
  4. 括号
    • 类、函数、条件语句的左括号与声明在同一行,右括号单独成行;
    • 单行条件语句可省略括号(但逻辑复杂时必须加);
    // 正确
    if (isLogin) {
      navigateToHome();
    }
    
    // 允许(单行简单逻辑)
    if (isLogin) navigateToHome();
    
    // 错误
    if (isLogin)
    {
      navigateToHome();
    }
    
  5. 分号:每条语句必须加分号(Dart 不是 JavaScript);
  6. 逗号:对象/数组最后一个元素后建议加逗号(IDE 自动格式化时更整洁);
    final user = User(
      name: '张三',
      age: 20, // 末尾逗号建议保留
    );
    

4.2 Widget 开发规范(强制)

  1. 优先使用 StatelessWidget:无状态组件性能更优,仅当需要维护状态时使用 StatefulWidget;
  2. Widget 拆分原则
    • 单个 Widget 代码不超过 150 行,超出则拆分为子 Widget;
    • 重复出现的 UI 片段(如列表项、按钮)必须提取为公共组件;
    • 避免嵌套过深(建议不超过 4 层),使用 Padding/SizedBox 替代嵌套 Container,用 Wrap 替代多层 Row/Column
    // 错误(嵌套过深)
    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);
    
  3. build 方法规范
    • 禁止在 build 中执行耗时操作(如网络请求、数据库查询、复杂计算);
    • 禁止在 build 中创建临时对象(如 ListMapStyle),需缓存为 final 变量或提取为常量;
    // 错误(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);
    }
    
  4. const 构造函数
    • 无状态 Widget 且构造参数均为常量时,必须使用 const 构造函数(减少重建开销);
    // 正确
    const CustomButton({super.key, required this.onTap});
    
    // 调用时也需加 const(除非父组件非 const)
    const CustomButton(onTap: _handleTap);
    
  5. 命名参数
    • Widget 构造函数仅使用命名参数({} 包裹),必填参数加 required 关键字;
    • 参数顺序:核心功能参数(如 onTapdata)在前,样式参数(如 colorsize)在后;
    // 正确
    const CustomButton({
      super.key,
      required this.onTap, // 必填参数在前
      this.text = '',
      this.color = Colors.blue, // 样式参数在后
      this.radius = 8.0,
    });
    

4.3 语法规范(强制)

  1. Null Safety 强制启用
    • 禁止使用 dynamic 类型(除非与第三方库兼容必须使用);
    • 可空类型必须显式声明(String?),非空类型必须初始化(或用 late 延迟初始化);
    • 避免滥用 ! 空断言(优先用 ???.if (xxx != null) 安全访问);
    // 错误
    String name; // 非空类型未初始化
    print(user!.name); // 滥用 ! 断言
    
    // 正确
    String? name;
    print(user?.name ?? '默认名称'); // 安全访问+默认值
    
  2. 集合使用
    • 优先使用不可变集合(const Listconst Map),可变集合需显式声明 varList<T>
    • 禁止使用 List() 无参构造,用 [] 替代(更简洁);
  3. 循环与条件
    • 简单条件用 ???.if-else,复杂条件提取为布尔变量(提升可读性);
    • 列表遍历优先用 forEach(简单逻辑)或 map(转换场景),避免嵌套循环;
  4. 扩展方法
    • 复用逻辑优先用 extension 扩展(如 StringExtensionDateTimeExtension),替代工具类静态方法;
    // 正确(扩展方法)
    extension StringExtension on String {
      String capitalize() => isEmpty ? this : '${this[0].toUpperCase()}${substring(1)}';
    }
    
    // 使用
    'hello'.capitalize(); // 比 StringUtils.capitalize('hello') 更简洁
    

4.4 注释规范(强制+建议)

  1. 文档注释(强制)
    • 公开类、函数、常量必须加文档注释(/// 开头,支持 DartDoc 生成文档);
    • 注释需说明 功能用途 + 参数含义 + 返回值 + 异常场景(必要时);
    /// 自定义按钮组件
    /// 
    /// [onTap]:点击回调(必填)
    /// [text]:按钮文本(默认空字符串)
    /// [color]:按钮背景色(默认蓝色)
    /// 返回:无
    const CustomButton({
      super.key,
      required this.onTap,
      this.text = '',
      this.color = Colors.blue,
    });
    
  2. 单行注释(建议)
    • 复杂逻辑、特殊处理(如兼容适配、临时方案)需加单行注释(// 开头);
    • 注释说明 为什么这么做,而非 做了什么(代码本身应体现做了什么);
  3. 注释禁忌
    • 禁止注释无用代码(直接删除,版本控制可回溯);
    • 禁止冗余注释(如 // 定义名称变量String name;)。

五、状态管理规范(强制+建议)

5.1 方案选择(建议)

根据项目复杂度选择合适的状态管理方案,团队统一使用一种核心方案:

项目规模推荐方案适用场景
小型项目StatefulWidget + setState仅需局部状态(如单个页面开关、计数器)
中型项目Provider/Riverpod跨组件状态共享(如用户信息、主题)
当前项目GetX(推荐)快速开发,简化模板代码

5.2 分层原则(强制)

无论使用哪种方案,必须遵循 UI 层-状态层-数据层 分离:

  1. UI 层(Widget):仅负责渲染,不包含业务逻辑,通过状态层获取数据;
  2. 状态层(Controller/Bloc/Provider):管理状态,处理业务逻辑,不直接操作数据源;
  3. 数据层(Repository/API):负责数据获取(网络、本地存储),提供统一接口给状态层;

5.3 状态管理禁忌(强制)

  1. 禁止在 Widget 中直接调用网络/存储接口(必须通过数据层);
  2. 禁止全局状态滥用(优先使用局部状态,跨组件共享才用全局状态);
  3. 禁止状态层持有 Widget 引用(如 BuildContext),通过回调/事件通信;
  4. GetX 规范
    • 控制器类名后缀 Controller(如 UserController);
    • 一个控制器对应一个业务场景,避免超大控制器(拆分多个子控制器);
    • 响应式变量使用 .obs 后缀,通过 Obx()GetX() 监听;

六、GetX使用规范(强制+建议)

6.1 控制器规范

  1. 控制器命名与位置

    • 控制器类名后缀必须为 Controller(如 UserController);
    • 控制器文件存放在对应模块的目录下,建议与页面文件同级或单独 controllers/ 目录;
    • 每个页面/功能对应一个控制器,避免超大控制器;
    // pages/user/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;
        }
      }
    }
    
  2. 状态管理

    • 使用 .obs 创建响应式变量,变量名使用 Rx 前缀约定;
    • 私有状态使用 _ 前缀,公开状态提供 getter 方法;
    • 使用 update() 方法触发 UI 更新(非响应式变量变更时);
    class 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.2 路由管理规范

  1. 路由定义

    • 使用 GetX 的路由管理系统,集中管理路由;
    • 路由名称使用小写+下划线格式,与页面路径对应;
    // routes/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(),
        ),
        GetPage(
          name: home,
          page: () => HomeScreen(),
          transition: Transition.fadeIn,
        ),
        GetPage(
          name: userDetail,
          page: () => UserDetailScreen(),
          transition: Transition.rightToLeft,
        ),
      ];
    }
    
  2. 路由跳转

    • 使用 GetX 提供的路由方法,保持统一;
    • 带参数跳转时使用 arguments 传递对象;
    // 页面跳转
    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.3 依赖管理规范

  1. 依赖注入
    • 使用 Get.put()Get.lazyPut() 进行依赖注入;
    • 在控制器构造函数中注入依赖;
    class UserController extends GetxController {
      final UserRepository userRepository;
      
      UserController(this.userRepository);
      
      // 或者使用Get.find()延迟获取
      UserRepository get repo => Get.find<UserRepository>();
    }
    
    // 在页面中初始化
    class UserPage extends StatelessWidget {
      final UserController controller = Get.put(UserController(UserRepository()));
      
      // ...
    }
    

6.4 Widget使用规范

  1. 响应式Widget

    • 使用 Obx()GetX()GetBuilder 包裹响应式部分;
    • 最小化响应范围,避免整个页面重建;
    class 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);
              },
            )),
          ],
        );
      }
    }
    
  2. 页面Widget规范

    • 页面可以使用 GetView<T> 获取控制器;
    • 使用 Get.put() 初始化控制器;
    // pages/user/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: _buildUserList(),
        );
      }
      
      Widget _buildUserList() {
        return RefreshIndicator(
          onRefresh: controller.loadUsers,
          child: Obx(() => ListView.builder(
            itemCount: controller.userList.length,
            itemBuilder: (context, index) {
              return UserItem(user: controller.userList[index]);
            },
          )),
        );
      }
    }
    

6.5 工具类使用规范

  1. GetX工具类
    • 统一使用 GetX 提供的工具方法;
    • 禁止混用原生方法与 GetX 方法;
    // 正确
    Get.snackbar('标题', '消息内容');
    Get.dialog(ConfirmDialog());
    Get.bottomSheet(OptionsSheet());
    Get.toNamed('/detail');
    
    // 避免混用
    // 错误:混用GetX和原生方法
    showDialog(context: context, builder: ...); // 避免使用
    Navigator.push(context, ...); // 避免使用
    

6.6 GetX最佳实践

  1. 性能优化

    • 使用 worker 监听状态变化,执行副作用;
    • 使用 debounceinterval 避免频繁更新;
    class SearchController extends GetxController {
      final RxString searchKeyword = ''.obs;
      
      @override
      void onInit() {
        super.onInit();
        
        // 监听搜索关键词变化,延迟500ms执行搜索
        debounce(
          searchKeyword,
          (_) => performSearch(),
          time: Duration(milliseconds: 500),
        );
      }
      
      void performSearch() {
        // 执行搜索逻辑
      }
    }
    
  2. 错误处理

    • 统一使用 GetX 的错误处理机制;
    • 全局异常捕获;
    // 全局配置
    void main() {
      // 全局错误处理
      Get.config(
        enableLog: true,
        defaultPopGesture: true,
        defaultTransition: Transition.cupertino,
      );
      
      // 异常处理
      FlutterError.onError = (details) {
        Get.snackbar('错误', details.exception.toString());
      };
      
      runApp(MyApp());
    }
    

七、属性访问控制规范(强制)

7.1 私有属性使用原则

  1. 能私有不公开:所有属性默认设置为私有,只有在确实需要外部访问时才提供公共访问方式;
  2. 最小权限原则:只暴露必要的最小接口,隐藏内部实现细节;
  3. 封装性原则:通过 getter/setter 方法控制属性的访问和修改;

7.2 私有属性定义规范

  1. 命名规则:私有属性以 _ 下划线开头,采用小驼峰命名法;

    // 正确
    class UserController extends GetxController {
      // 私有属性
      final RxString _userName = ''.obs;
      final List<User> _userList = [];
      int _pageIndex = 0;
      
      // 公共访问方式
      String get userName => _userName.value;
      List<User> get userList => List.unmodifiable(_userList); // 返回不可修改副本
      
      // 公共修改方式(需要时)
      void updateUserName(String name) {
        if (name.isNotEmpty) {
          _userName.value = name;
        }
      }
    }
    
  2. 响应式变量的私有化

    class UserController extends GetxController {
      // 私有响应式变量
      final RxInt _privateCounter = 0.obs;
      final RxList<User> _privateList = <User>[].obs;
      
      // 只读公共访问
      int get counter => _privateCounter.value;
      List<User> get userList => _privateList.toList(); // 返回副本
      
      // 受控的修改方法
      void incrementCounter() {
        if (_privateCounter.value < 100) {
          _privateCounter.value++;
        }
      }
      
      void addUser(User user) {
        if (!_privateList.contains(user)) {
          _privateList.add(user);
        }
      }
    }
    

7.3 不同场景下的属性访问控制

场景推荐做法示例
内部状态完全私有,不提供外部访问int _internalState = 0;
只读状态私有变量 + 公共getterfinal _data = Rx<Data>();
Data get data => _data.value;
受控修改私有变量 + 公共修改方法void updateData(Data newData) { ... }
完全公开公共变量(极少情况)final isLoading = false.obs;

7.4 GetX控制器中的属性封装

class ProductController extends GetxController {
  // 1. 完全私有,仅内部使用
  final List<Product> _allProducts = [];
  DateTime _lastUpdateTime = DateTime.now();
  
  // 2. 响应式状态,只读外部访问
  final RxList<Product> _filteredProducts = <Product>[].obs;
  final RxBool _isLoading = false.obs;
  
  // 公共只读访问
  List<Product> get products => _filteredProducts.toList();
  bool get isLoading => _isLoading.value;
  bool get hasProducts => _filteredProducts.isNotEmpty;
  
  // 3. 计算属性
  double get totalPrice {
    return _filteredProducts.fold(0, (sum, product) => sum + product.price);
  }
  
  // 4. 受控的修改方法
  void addProduct(Product product) {
    if (!_allProducts.contains(product)) {
      _allProducts.add(product);
      _applyFilters(); // 内部方法,不公开
      update();
    }
  }
  
  void removeProduct(Product product) {
    _allProducts.remove(product);
    _applyFilters();
    update();
  }
  
  // 5. 私有内部方法
  void _applyFilters() {
    // 内部过滤逻辑
    _filteredProducts.value = _allProducts.where(_filterCondition).toList();
  }
  
  bool _filterCondition(Product product) {
    // 内部过滤条件
    return product.isActive && product.price > 0;
  }
  
  // 6. 异步操作封装
  Future<void> loadProducts() async {
    _isLoading.value = true;
    try {
      final products = await ProductRepository.fetchProducts();
      _allProducts.clear();
      _allProducts.addAll(products);
      _applyFilters();
    } catch (e) {
      Get.snackbar('错误', '加载产品失败: $e');
    } finally {
      _isLoading.value = false;
    }
  }
}

7.5 Widget中的属性封装

class ProductCard extends StatelessWidget {
  final Product product;
  final VoidCallback onTap;
  
  // 私有计算属性
  Color get _cardColor => product.isFeatured ? Colors.amber[50]! : Colors.white;
  String get _priceText => ${product.price.toStringAsFixed(2)}';
  
  const ProductCard({
    super.key,
    required this.product,
    required this.onTap,
  });
  
  @override
  Widget build(BuildContext context) {
    return Card(
      color: _cardColor,
      child: InkWell(
        onTap: onTap,
        child: Padding(
          padding: const EdgeInsets.all(16),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              // 使用私有方法构建UI部分
              _buildHeader(),
              const SizedBox(height: 8),
              _buildPrice(),
              const SizedBox(height: 8),
              _buildTags(),
            ],
          ),
        ),
      ),
    );
  }
  
  // 私有构建方法
  Widget _buildHeader() {
    return Row(
      children: [
        Expanded(
          child: Text(
            product.name,
            style: const TextStyle(
              fontSize: 16,
              fontWeight: FontWeight.bold,
            ),
            maxLines: 2,
            overflow: TextOverflow.ellipsis,
          ),
        ),
        if (product.isFeatured) _buildFeaturedBadge(),
      ],
    );
  }
  
  Widget _buildPrice() {
    return Text(
      _priceText,
      style: TextStyle(
        fontSize: 18,
        fontWeight: FontWeight.bold,
        color: Theme.of(context).primaryColor,
      ),
    );
  }
  
  Widget _buildTags() {
    return Wrap(
      spacing: 4,
      children: product.tags.map((tag) => _buildTag(tag)).toList(),
    );
  }
  
  Widget _buildTag(String tag) {
    return Chip(
      label: Text(tag),
      backgroundColor: Colors.grey[200],
      visualDensity: VisualDensity.compact,
    );
  }
  
  Widget _buildFeaturedBadge() {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
      decoration: BoxDecoration(
        color: Colors.orange,
        borderRadius: BorderRadius.circular(4),
      ),
      child: const Text(
        '推荐',
        style: TextStyle(
          color: Colors.white,
          fontSize: 10,
          fontWeight: FontWeight.bold,
        ),
      ),
    );
  }
}

7.6 检查清单

在编写代码时,请检查以下问题:

  1. ✅ 这个属性是否真的需要被外部访问?
  2. ✅ 如果不需要,是否已设置为私有(_开头)?
  3. ✅ 如果需要只读访问,是否提供了getter方法?
  4. ✅ 如果需要修改,是否提供了受控的修改方法?
  5. ✅ 集合类型返回时,是否返回了不可修改的副本?
  6. ✅ 是否避免了直接暴露内部可变状态?

八、性能优化规范(强制)

8.1 渲染优化

  1. 列表优化
    • 长列表必须使用 ListView.builder/GridView.builder(懒加载),禁止使用 ListView(children: [...])(一次性加载所有子项);
    • 列表项高度固定时,设置 itemExtent 提升性能;
  2. 图片优化
    • 图片资源按分辨率适配(mipmap-mdpi/mipmap-hdpi 等),避免使用超大图;
    • 网络图片使用缓存(如 cached_network_image),设置占位图/错误图;
    • 本地图片使用 AssetImage 而非 FileImage,并通过常量引用;
  3. 减少重绘
    • 频繁变化的 Widget 用 RepaintBoundary 包裹(隔离重绘区域);
    • 避免在 AnimatedBuilder 外部构建无关 Widget;

8.2 内存优化

  1. 资源释放
    • 订阅流(Stream)、控制器必须在 disposeonClose 中取消订阅;
    • 大文件(如视频、音频)使用后及时释放,避免内存泄漏;
  2. 避免内存泄漏场景
    • 禁止匿名函数/闭包持有 BuildContext 长期引用(如异步回调中);
    • 禁止静态变量持有 Widget/State 实例;

8.3 代码执行优化

  1. 异步操作
    • 网络请求、文件读写必须用异步(async/await),禁止同步阻塞;
    • 多个独立异步任务用 Future.wait 并行执行(而非串行 await);
  2. 计算优化
    • 复杂计算(如数据解析、加密)放在 isolate 中执行(避免阻塞 UI 线程);
    • 重复计算结果缓存(如 final 变量、memoizer 工具)。

九、资源与配置规范(强制)

9.1 静态资源管理

  1. 图片
    • 存放路径:assets/images/(按模块拆分);
    • 引用方式:通过常量类封装,禁止硬编码路径;
    // const/resource.dart 或专门的 images.dart
    class AppImages {
      static const String userAvatar = 'assets/images/user/avatar.png';
      static const String iconHome = 'assets/images/home/icon_home.png';
    }
    
    // 使用
    Image.asset(AppImages.userAvatar);
    
  2. 字符串
    • 存放路径:通过国际化文件管理;
    • 禁止硬编码字符串(便于统一修改和国际化);
  3. 颜色/尺寸
    • 颜色:通过 style/c_color.dart 管理;
    • 尺寸:统一间距、字体大小,避免魔法数字;

9.2 路由管理

  1. 路由配置
    • 集中管理路由表(routes/app_routes.dart),使用命名路由;
    • 路由名称格式:/模块/页面(如 /user/login/course/detail);
  2. 路由跳转
    • 使用封装后的路由工具,统一处理动画、参数传递;
    • 路由参数传递:优先使用强类型,避免 Map 动态类型;

9.3 依赖管理

  1. pubspec.yaml 规范
    • 依赖分组(dependencies/dev_dependencies),并按功能排序;
    • 版本约束:核心依赖指定稳定版本,避免使用 any
    • 定期更新依赖,并移除未使用的依赖;
  2. 禁止重复依赖:如已使用 dio,禁止再引入 http 库;

十、规范落地与维护

  1. 定期复盘:每季度更新规范(适配 Flutter 新版本特性),团队内部分享违规案例与优化方案;
  2. 新人培训:将规范纳入新人入职培训,配套示例项目(展示规范落地效果)。