Flutter工程规范:用CLAUDE.md约束Claude Code与Codex的AI编码实践

发布时间:2026/10/17 12:00:28
Flutter工程规范:用CLAUDE.md约束Claude Code与Codex的AI编码实践
1. 这套工程规范到底在解决什么问题Flutter 项目写起来爽维护起来要命这个感受但凡带过两个以上中型项目的人应该都有共鸣。团队里每个人写 Widget 的风格不一样状态管理有的用 Provider 有的用 Riverpod 有的直接 setState 一把梭目录结构更是千人千面——A 同学把页面全塞在lib/pages下面B 同学非要按功能模块拆成features/xxx/presentation合并代码的时候光解决目录冲突就能耗掉半天。更别提那些藏在业务代码里的硬编码颜色值、魔法数字、随手写的print调试语句等到要改主题色或者排查线上问题时翻代码跟考古一样。这两年 AI 辅助编码工具普及之后情况变得更微妙了。Claude Code 和 Codex 这类工具确实能大幅提升写代码的速度你描述一个需求它唰唰唰给你生成一堆 Dart 文件看起来有模有样。但问题在于AI 生成代码的质量完全取决于你给它的约束条件。你不告诉它项目用什么状态管理方案它就默认给你塞一个setState你不告诉它目录规范它就把所有文件平铺在lib根目录你不告诉它命名规则它给你整出my_widget.dart、MyWidget2.dart、widget_new.dart这种让人血压升高的文件名。所以这套工程规范的核心目标很明确把项目约定变成 AI 能读懂、能执行的规则文件让 Claude Code 和 Codex 在生成代码时自动遵守团队规范而不是每次都要人工 review 再返工。具体来说它要解决三个层面的问题代码风格统一命名规范、文件组织、注释要求、格式化规则全部固化成 AI 可读的配置文件。架构约束落地状态管理选型、分层架构、依赖注入方式通过规则文件强制 AI 遵守避免它自由发挥。质量门禁前置静态分析规则、lint 规则、测试覆盖率要求在 AI 生成代码阶段就介入而不是等到 CI 阶段才报错。这套东西适合谁如果你是一个 Flutter 项目的技术负责人团队里有人用 AI 写代码但产出质量参差不齐或者你自己用 Claude Code 辅助开发但每次都要花大量时间手动调整 AI 生成的代码结构再或者你正准备在一个新项目里引入 AI 辅助编码流程想从一开始就把规范立好——那这套方案就是为你准备的。它不要求你有多深的 Flutter 功底但需要你对项目的基本架构有清晰的认识因为规则文件的内容本质上就是你项目架构决策的书面化表达。2. 为什么是 Claude Code Codex 这个组合2.1 两个工具各自的定位差异先说说为什么不是单用一个工具。Claude Code 和 Codex 在能力侧重点上其实有比较明显的差异理解这些差异才能用好它们。Claude Code 的优势在于长上下文理解和复杂指令跟随。你给它一份几百行的规则文件它能比较准确地理解每一条规则的含义并且在生成代码时逐条对照执行。它特别擅长处理那种需要跨多个文件协调的任务比如按照项目规范新建一个用户详情页包含 ViewModel、Repository、Model 三层并且注册路由。你描述清楚需求它能一次性把相关文件都生成出来而且文件之间的引用关系基本正确。Codex 的优势则在于代码补全的精准度和对现有代码风格的模仿能力。你在编辑器里写代码的时候它能根据当前文件的上下文和项目里已有的代码模式给出非常贴合项目风格的补全建议。它更像是一个隐形的高手坐在旁边你写个函数签名它帮你把实现补全而且补全出来的代码跟你项目里其他地方的写法高度一致。所以组合使用的逻辑是Claude Code 负责从零到一的代码生成和架构搭建Codex 负责从一到 N的代码补全和细节完善。规则文件同时喂给两个工具确保它们在同一套约束下工作。2.2 规则文件的核心载体CLAUDE.md 和 .codex 配置Claude Code 读取项目根目录下的CLAUDE.md文件作为项目级指令。这个文件的内容会在每次对话开始时自动加载到上下文中所以你写在这个文件里的规则Claude Code 会当作系统提示一样对待。Codex 则通过项目根目录的.codex目录下的配置文件来读取项目规范同时也会参考.editorconfig、analysis_options.yaml等标准配置文件。这里有个关键点两个工具的规则文件内容应该保持同步但格式可以不同。Claude Code 的CLAUDE.md适合用自然语言描述规则因为它的理解能力强能处理模糊表述。Codex 的配置文件则更适合用结构化的格式比如 YAML 或 JSON因为它对结构化数据的解析更精确。我试过的一种做法是维护一份规则源文件用 YAML 格式写清楚所有规范条目然后写一个简单的脚本把 YAML 转换成CLAUDE.md的自然语言描述和.codex/rules.yaml的结构化配置。这样改规则的时候只需要改一处两个工具都能同步更新。2.3 为什么不用其他方案有人可能会问为什么不直接用 Dart 的 lint 规则或者自定义 analysis 插件原因在于lint 规则只能检查代码写完之后的问题而 AI 规则文件能在代码生成阶段就介入。lint 告诉你这行代码不符合规范但 AI 规则文件告诉 AI不要生成这样的代码。前者是事后补救后者是事前预防效率差距很大。另一个常见方案是写详细的代码模板让 AI 照着模板填。这个方案的问题在于模板的覆盖面有限你不可能为每一种业务场景都准备一个模板。而规则文件是原则性的AI 可以根据原则灵活应对各种新场景。比如你规定所有网络请求必须通过 Repository 层AI 在遇到一个新的 API 调用需求时会自动创建 Repository 方法而不是直接在 Widget 里发请求。3. 规则文件里到底该写什么3.1 项目结构规范让 AI 知道文件该放哪这是最基础也是最重要的一部分。Flutter 项目的目录结构没有官方标准但团队内部必须统一。我建议采用按功能模块划分的方式而不是按文件类型划分。具体来说lib/ app/ # 应用入口、路由、主题 app.dart router.dart theme.dart features/ # 功能模块 user/ data/ # 数据层 models/ repositories/ domain/ # 领域层可选 entities/ usecases/ presentation/ # 表现层 pages/ widgets/ viewmodels/ order/ ... core/ # 核心公共代码 network/ storage/ utils/ widgets/ # 全局通用组件 l10n/ # 国际化在CLAUDE.md里你需要用自然语言把这个结构描述清楚并且给出每个目录的职责说明。比如所有业务功能代码放在lib/features/下每个功能模块一个独立目录。模块内部按data、domain、presentation三层组织。data层负责数据获取和持久化domain层放业务逻辑如果逻辑简单可以省略presentation层放 UI 和状态管理。全局通用的工具类放在lib/core/utils/全局通用组件放在lib/core/widgets/。禁止在lib/根目录下直接创建 Dart 文件。这样写的好处是当你说帮我加一个商品搜索功能时Claude Code 会自动在lib/features/product/下创建data、presentation等子目录而不是把文件乱丢。3.2 命名规范消灭 my_widget_new_final 这类文件名命名规范要覆盖文件命名、类命名、变量命名、常量命名等各个层面。Flutter 社区有一些约定俗成的规则比如文件名用 snake_case类名用 PascalCase但具体到业务层面还需要补充。我在规则文件里通常会写这么几条页面文件以_page.dart结尾比如user_detail_page.dart。ViewModel 文件以_viewmodel.dart结尾类名以ViewModel结尾。Repository 接口以_repository.dart结尾实现类以_repository_impl.dart结尾。私有方法以下划线开头但禁止用下划线开头来隐藏本应该是 public 的方法。布尔变量以is、has、can、should开头。常量用lowerCamelCase禁止用SCREAMING_SNAKE_CASEDart 风格指南的建议。这些规则写进CLAUDE.md之后AI 生成的文件名和类名基本就不会跑偏了。实测下来命名相关的返工率能降低八成以上。3.3 状态管理约定别让 AI 自由发挥状态管理是 Flutter 项目里最容易出现风格分裂的地方。如果规则文件里不写清楚Claude Code 可能这个页面用 Provider下个页面用 Riverpod再下个页面直接 setState。所以必须明确指定本项目统一使用 Riverpod 进行状态管理。所有需要跨组件共享的状态必须通过 Riverpod Provider 暴露。页面内部局部状态可以使用 StatefulWidget 的 setState但禁止在 setState 中处理业务逻辑。ViewModel 继承自StateNotifier或使用AsyncNotifier禁止在 Widget 中直接调用 Repository。同时要给出一个简单的代码示例让 AI 知道正确的写法长什么样。比如// 正确的 ViewModel 写法 class UserDetailViewModel extends StateNotifierUserDetailState { UserDetailViewModel(this._repository) : super(const UserDetailState.initial()); final UserRepository _repository; Futurevoid loadUser(String userId) async { state const UserDetailState.loading(); final result await _repository.getUser(userId); state result.fold( (failure) UserDetailState.error(failure.message), (user) UserDetailState.loaded(user), ); } }规则文件里带上这样的示例代码AI 模仿起来就非常准确。3.4 代码质量红线AI 绝对不能碰的东西这部分是负面清单明确告诉 AI 哪些事情绝对不能做。比如禁止在代码中硬编码颜色值必须使用Theme.of(context).colorScheme或项目定义的颜色常量。禁止在代码中硬编码字符串所有用户可见文本必须走国际化方案。禁止使用print进行日志输出统一使用项目封装的Logger类。禁止在 Widget 的build方法中执行任何耗时操作或网络请求。禁止使用dynamic类型除非有明确的理由并在注释中说明。禁止提交被注释掉的代码块。这些红线写清楚之后AI 生成代码时就会主动避开这些坑。我印象比较深的一次是规则文件里写了禁止硬编码颜色之后Claude Code 生成一个按钮组件时自动去theme.dart里找颜色定义而不是直接写Color(0xFF2196F3)。3.5 测试要求让 AI 顺手把测试写了很多团队不写测试或者测试覆盖率极低。但既然用了 AI 辅助编码让 AI 顺手生成单元测试的成本其实很低。规则文件里可以要求每个 Repository 方法必须配套单元测试测试文件放在test/features/模块名/data/repositories/下。每个 ViewModel 的公开方法必须有对应的状态变化测试。测试使用mocktail进行依赖 mock禁止在测试中发起真实网络请求。这样当你让 Claude Code 实现一个 Repository 方法时它会自动在test目录下生成对应的测试文件。虽然测试质量需要人工把关但至少骨架和基本用例是有的比从零开始写要省力得多。4. 实操从零搭建这套规范4.1 第一步梳理现有项目的架构决策如果你是在一个已有项目上引入这套规范第一件事不是急着写规则文件而是先花半天时间把项目现有的架构决策梳理清楚。具体要做的是打开项目把lib目录的完整结构列出来看看实际的组织方式是什么。翻一遍pubspec.yaml确认用了哪些核心依赖状态管理、网络库、路由库、数据库等。随机抽 10 个 Dart 文件看看命名风格、注释风格、错误处理方式是否一致。跑一遍flutter analyze看看现有的 lint 警告有多少主要是什么类型。这一步的目的是让规则文件反映项目的真实情况而不是理想情况。如果你写了一套完美的规范但项目现状跟规范差距太大AI 生成的新代码跟旧代码会格格不入反而增加维护成本。比较务实的做法是规则文件以现状为基础适当拔高但不要一步到位。4.2 第二步编写规则源文件我建议用 YAML 格式维护一份规则源文件放在项目根目录的project_rules.yaml。结构大概是这样project: name: Flutter 项目工程规范 version: 1.0 structure: root_dirs: - name: app description: 应用入口、路由、主题配置 - name: features description: 业务功能模块按模块拆分 - name: core description: 核心公共代码 feature_structure: - data/models - data/repositories - presentation/pages - presentation/widgets - presentation/viewmodels naming: files: page: *_page.dart viewmodel: *_viewmodel.dart repository: *_repository.dart repository_impl: *_repository_impl.dart classes: page: *Page viewmodel: *ViewModel repository: *Repository state_management: solution: riverpod rules: - 跨组件共享状态必须使用 Riverpod Provider - ViewModel 继承 StateNotifier 或使用 AsyncNotifier - 禁止在 Widget 中直接调用 Repository quality: forbidden: - 硬编码颜色值 - 硬编码用户可见字符串 - 使用 print 输出日志 - 在 build 方法中执行网络请求 - 使用 dynamic 类型 required: - 所有 Repository 方法配套单元测试 - 所有公开 API 必须有文档注释这份 YAML 文件是单一事实来源后续所有配置都从它生成。4.3 第三步生成 CLAUDE.md写一个简单的 Python 脚本把 YAML 转换成CLAUDE.md的自然语言描述。脚本逻辑不复杂就是遍历 YAML 的各个 section拼接成 Markdown 格式的文本。生成出来的CLAUDE.md大概长这样# 项目工程规范 ## 项目结构 所有业务功能代码放在 lib/features/ 下每个功能模块一个独立目录。 模块内部按以下结构组织 - data/models/数据模型 - data/repositories/数据仓库 - presentation/pages/页面 - presentation/widgets/模块内组件 - presentation/viewmodels/状态管理 全局通用工具放在 lib/core/utils/全局通用组件放在 lib/core/widgets/。 禁止在 lib/ 根目录下直接创建 Dart 文件。 ## 命名规范 - 页面文件以 _page.dart 结尾类名以 Page 结尾 - ViewModel 文件以 _viewmodel.dart 结尾类名以 ViewModel 结尾 - Repository 接口以 _repository.dart 结尾实现类以 _repository_impl.dart 结尾 ## 状态管理 本项目统一使用 Riverpod。跨组件共享状态必须通过 Riverpod Provider 暴露。 ViewModel 继承 StateNotifier 或使用 AsyncNotifier。 禁止在 Widget 中直接调用 Repository。 ## 代码质量红线 以下行为严格禁止 - 硬编码颜色值必须使用 Theme 或颜色常量 - 硬编码用户可见字符串必须走国际化 - 使用 print 输出日志统一使用 Logger - 在 build 方法中执行网络请求 - 使用 dynamic 类型 ## 测试要求 每个 Repository 方法必须配套单元测试测试文件放在 test/features/模块名/data/repositories/ 下。 测试使用 mocktail 进行依赖 mock。这个文件放在项目根目录Claude Code 每次启动都会自动读取。4.4 第四步配置 CodexCodex 的配置稍微不同。在项目根目录创建.codex/rules.yaml内容直接从project_rules.yaml拷贝过来即可。同时确保.editorconfig和analysis_options.yaml里的规则跟 YAML 文件保持一致。analysis_options.yaml里要开启比较严格的 lint 规则比如include: package:flutter_lints/flutter.yaml linter: rules: - prefer_const_constructors - prefer_const_declarations - avoid_print - prefer_single_quotes - require_trailing_commas - always_declare_return_types - avoid_dynamic_calls这些 lint 规则跟 AI 规则文件是互补关系AI 规则文件负责生成时预防lint 规则负责生成后检查。4.5 第五步验证规则是否生效配置写完之后一定要做验证。我的做法是设计几个测试用例分别用 Claude Code 和 Codex 跑一遍看看生成结果是否符合预期。测试用例一让 Claude Code 新建一个商品列表页从 API 获取商品数据并展示。预期结果是它在lib/features/product/下创建data/models/product.dart、data/repositories/product_repository.dart、presentation/pages/product_list_page.dart、presentation/viewmodels/product_list_viewmodel.dart并且 ViewModel 使用 Riverpod 的StateNotifierRepository 有对应的测试文件。测试用例二在编辑器里手动写一个 Repository 方法的签名看看 Codex 的补全是否符合项目风格。预期是补全出来的代码使用项目的错误处理方式比如EitherFailure, T而不是直接抛异常。如果验证结果不符合预期就回去调整规则文件的表述。有时候需要把规则写得更具体比如使用 Riverpod可以改成使用 Riverpod 的 StateNotifierProviderViewModel 继承 StateNotifier状态类使用 freezed 定义。5. 踩过的坑和排查技巧5.1 规则文件太长导致 AI 遗忘这是最常见的问题。一开始我把所有能想到的规则都写进CLAUDE.md结果文件超过 800 行Claude Code 在处理复杂任务时反而会忽略一些规则。后来发现规则文件的有效长度大概在 300 到 500 行之间超过这个长度AI 的规则遵循率会明显下降。解决办法是分层CLAUDE.md里只放最核心的规则项目结构、命名规范、状态管理、质量红线详细的代码示例和边缘情况放到单独的文档里在CLAUDE.md中用链接引用。当 AI 需要了解某个具体规则的细节时你再手动把相关文档内容贴给它。5.2 AI 对禁止类规则的理解偏差禁止硬编码颜色这条规则我一开始写的是避免硬编码颜色值。结果 AI 有时候还是会写Color(0xFF333333)因为它觉得避免不是禁止偶尔用一下没关系。后来改成严格禁止硬编码颜色值所有颜色必须从 Theme 或 AppColors 常量类中获取遵循率就上去了。规则文件里的措辞要明确少用尽量、建议、避免这类模糊词多用必须、禁止、统一使用。AI 对强指令的遵循度明显高于弱建议。5.3 规则冲突导致 AI 行为不一致有一次我在CLAUDE.md里写了所有网络请求必须通过 Repository 层但在另一个地方又写了简单的 GET 请求可以直接在 ViewModel 中调用 Dio。这两条规则是冲突的导致 AI 有时候走 Repository有时候直接在 ViewModel 里发请求。排查这类问题的技巧是把规则文件当成代码来 review检查有没有逻辑矛盾。我现在的做法是每次修改规则文件后让 Claude Code 自己读一遍问它这些规则之间有没有冲突的地方。它有时候能发现我自己没注意到的问题。5.4 Codex 补全不符合规范Codex 的补全行为受当前文件上下文的影响很大。如果当前文件里已经有一些不符合规范的代码比如旧代码Codex 会倾向于模仿这些旧代码的风格而不是遵循规则文件。解决办法有两个一是在编辑新文件时先手动写几行符合规范的代码作为种子Codex 会以这几行代码为模板进行补全。二是定期清理旧代码把不符合规范的代码逐步重构掉减少坏样本对 Codex 的干扰。5.5 常见问题速查表问题现象可能原因排查方法解决方案AI 生成的文件放错目录规则文件中目录结构描述不清晰检查 CLAUDE.md 中 structure 部分补充目录职责说明和示例AI 忽略某条规则规则文件过长或措辞模糊统计规则文件行数检查关键词精简规则文件改用强指令措辞Codex 补全风格不一致当前文件存在旧风格代码查看当前文件上下文手动写种子代码逐步重构旧代码AI 生成的测试无法运行测试规则描述不完整检查测试相关规则补充 mock 方式和测试文件位置说明规则更新后 AI 行为没变化缓存或配置未生效重启工具检查配置文件路径确认 CLAUDE.md 在项目根目录5.6 一个实用的调试技巧当你发现 AI 生成的代码不符合预期时不要急着改规则文件。先做一个实验在对话中直接问 AI你刚才生成代码时参考了哪些规则Claude Code 通常会告诉你它读了CLAUDE.md的哪些部分。如果它说我没有看到关于 XXX 的规则那就说明规则文件的表述有问题或者规则文件没有被正确加载。如果它说我看到了规则但我认为这里可以例外那就说明规则的约束力不够强需要改成更明确的禁止性表述。这个技巧帮我省了很多瞎改规则的时间推荐你也试试。6. 持续维护让规范跟着项目一起进化规则文件不是写完就一劳永逸的。项目在迭代架构在调整规则文件也需要同步更新。我的做法是把规则文件的更新纳入代码 review 流程每次有新的架构决策或者发现 AI 反复犯某个错误时就在 PR 里同时更新规则文件。具体来说我会在项目的CONTRIBUTING.md里加一条如果本次 PR 引入了新的代码模式或者修改了目录结构必须同步更新project_rules.yaml并重新生成CLAUDE.md。这样规则文件就不会跟项目实际状况脱节。另外我每个月会花半小时做一次规则文件健康检查跑一遍flutter analyze看看有没有新的 lint 警告类型翻一翻最近的 PR看看有没有 AI 生成的代码被人工大幅修改的情况。如果某个规则经常被违反要么是规则本身不合理要么是表述不够清晰需要调整。这套东西跑顺了之后最大的感受是AI 辅助编码的效率瓶颈不在 AI 本身而在你给它的约束是否清晰。规则文件写得好Claude Code 和 Codex 就是两个靠谱的初级工程师能帮你把重复性的编码工作干掉大半规则文件写得糊它们就是两个自作主张的实习生产出的代码你还得花更多时间收拾。所以在这上面花时间回报率是很高的。