1. 为什么 Flutter 项目最先要解决的,是 AI 代码生成的不确定性
先说个我曾经真实踩过的坑。某次给一个中等规模的 Flutter 应用加离线缓存模块,项目里已经有大半代码是 AI 辅助生成的,我图省事,直接把需求描述丢给 Claude Code,让它自己改数据层代码。结果它非常利索地改完了,所有测试也过了,编译也没报错。当时我还挺满意,后来 Code Review 的时候发现一个问题:它把原本项目中统一使用的Either错误处理风格改成了try-catch,还把状态管理里约定好的StateController命名改成了BlocController,整个代码风格直接分裂了。
这个问题的本质,不是哪个 AI 工具不行,而是工程规范缺失。AI 生成代码和人类写代码不一样,人类写代码会下意识跟随项目现有的风格,AI 不懂这些项目内的潜规则,它只能根据上下文里的代码风格去猜测,猜不中的时候就会产生这类隐蔽的偏差。更麻烦的是,这种偏差在编译期通常看不出来,等代码合入主分支之后才逐步暴露,那时候再修成本就高了。
后来我搭了一套 Flutter 工程规范体系,核心思路是:把项目内的代码风格、架构约束、命名约定、文件组织规则都变成 AI 能读取、能执行的显式规则,再配合 Claude Code 和 Codex 的自动化检查能力,让 AI 在生成代码的第一时间就遵守这些规则。
这套方法跑了一段时间之后,AI 生成的代码质量和项目本身代码的一致性有了明显提升,Code Review 的负担小了很多。这篇文章就把整套搭建思路完完整整地写出来,包括工具选型、规则文件怎么写、Claude Code 和 Codex 怎么配合、以及那些文档里不会写的坑。
如果你是 Flutter 开发者,并且已经在用 AI 辅助写代码,或者正准备引入 AI 编程工具,这篇文章应该能帮你省下不少返工的时间。
2. 搭规范前的关键判断:Claude Code 和 Codex 到底各自负责什么
很多人在同一项目里同时使用两个 AI 工具,纯粹是因为每个都有点优势,又不舍得放弃另一个。但如果你不明确它们的分工,两个工具各写各的,反而会让代码风格更混乱。我的做法是让它们分别负责不同的任务场景,并用同一套规则文件约束输出。
2.1 Claude Code 更适合做重构和跨文件修改
Claude Code 在处理跨文件的代码修改、重构、架构调整这类任务时表现更稳,因为它的上下文窗口更大,对项目结构的理解更整体。比如把某个功能模块从Provider状态管理迁移到Riverpod,涉及十几个文件,Claude Code 的表现通常比 Codex 更可靠。
对于这类任务,我给它配置的规则重点是:"保持原有公共接口不变""迁移时保留原有测试用例""所有新增依赖必须声明在 pubspec.yaml 中"。有了这些约束,它做重构的时候会更谨慎。
2.2 Codex 更适合做小范围功能生成和代码补全
Codex 的优势在于轻快、便宜,适合完成明确的小任务,比如"写一个根据传入参数生成分页列表的 Widget""实现一个从 JSON 解析用户模型的 fromJson 方法"这类范围清晰的代码生成。它生成代码速度快,在小范围的独立文件修改上质量很不错。
Codex 的风险在于,它不像 Claude Code 那么了解整个项目的上下文,所以规则文件对它更重要。比如它可能不认识项目里自定义的基础组件AppBaseWidget,直接在代码里新建了一个类似的容器组件,这种问题只能靠规则约束。
2.3 两份独立规则文件的配置结构
我建议不要只维护一份通用的规则文件,而是为两个工具分别维护。原因是它们的配置读取机制不同,可用的规则描述能力也不同。我的配置结构是:
- Claude Code 使用项目根目录下的
CLAUDE.md文件,它每次会话开始时会自动读取; - Codex 使用项目根目录下的
AGENTS.md文件,或者在使用时通过-f参数指定规则文件。
两条工具默认读取的文件不同,所以我们分别维护,同时把一份共享的工程规范核心内容放在两个文件里。这样每个工具体系独立,但规范核心一致。
提示:如果你用 Codex 的本地命令行版本,每次执行时加
-f AGENTS.md参数指定规则文件,避免它因为目录问题没读到规范。这一点实操中很容易踩坑。
2.4 统一规范核心,避免规则漂移
两个文件虽然分开维护,但核心规范必须一致,否则会出现 Claude Code 写的代码遵守一套规则、Codex 遵守另一套的情况。我解决这个问题的办法是,把规范内容集中写在项目根目录的docs/engineering_guidelines.md文件里,CLAUDE.md和AGENTS.md里只写一句"请严格遵守docs/engineering_guidelines.md中的全部规范"。
这样只需要维护一份源文档,既避免了多份文件不同步的问题,也方便项目里的真人开发者阅读同一份规范。
3. 工程规范文件的核心内容:让 AI 能听懂 Flutter 项目的约束
规范文件要想真正起作用,不能只写"代码风格保持一致"这种模糊的话,AI 需要的是可执行的、具体的规则。我按照 Flutter 项目的工程特征,把规范拆成了七大块,每块都有明确的描述和正反示例,下面逐一说明。
3.1 项目架构与目录结构约束
Flutter 项目的架构风格直接决定了 AI 生成的文件应该放在哪里。如果你的项目用的是 feature-first 架构,那么 AI 生成的模块文件就必须放进对应功能目录,否则整个目录会成为大杂烩。
我通常会在规范文件里写明:
项目采用 feature-first 目录结构: lib/features/<feature_name>/ ├── data/ (数据源、仓储实现) ├── domain/ (实体、仓储接口、用例) └── presentation/ (页面、组件、状态管理) 新增功能代码必须放入对应模块目录,禁止在 lib/ 根目录直接创建新文件夹。 共享组件放入 lib/shared/widgets/,共享工具类放入 lib/shared/utils/。同时,我在规范里明确写出禁止事项:"禁止在 main.dart 中直接写业务逻辑,main.dart 只负责初始化与路由配置。"这类否定式约束对 AI 特别有效,因为 AI 倾向于在描述性的规则下自行发挥,而否定式规则能直接阻断它跑偏。
3.2 状态管理方案的统一约束
Flutter 生态里的状态管理方案非常多,项目一旦选定就必须坚持,否则 AI 会因为任务描述里的某个词汇就切换方案。我见过最典型的场景:任务里提到"这个页面需要一个加载状态",AI 就直接引入了一套额外的 loading 方案代码,而项目本身已经有统一的 loading 框架。
我的规范文件里会明确写当前的状态管理方案,并强调"禁止引入新的状态管理库"。
本项目使用 Riverpod 作为唯一状态管理方案。 - 页面状态用 StateProvider 或 NotifierProvider,禁止使用 setState 处理跨组件状态。 - 异步加载场景统一使用 AsyncValue 处理,禁止在 Widget 内部手动管理多个 loading 标志位。 - 团队已封装 AppAsyncBuilder 组件,禁止使用其他方式渲染异步状态。配上一个反面示例:"如果看到语言为setState的代码,必须重写为 Riverpod 的NotifierProvider,并同步调整测试代码"。正反结合,AI 生成代码时不会摸不着头脑。
3.3 命名规范与代码风格
Dart 自带官方风格指南,但项目里通常还会有团队自己的约定,比如私有文件用_前缀、某些特定类型组件用固定后缀等。这些约定要逐条写上。
我自己常用的几条约束是:
- 组件类名使用
PascalCase,文件名使用snake_case,但 StatefulWidget 的 State 类命名为_XxxState; - 页面级 Widget 统一使用后缀
Page,业务组件统一使用后缀Widget,比如LoginPage、UserCardWidget; - 枚举类型文件名加
_enum后缀,数据模型文件名加_model后缀; - JSON 解析方法统一命名为
fromJson/toJson,禁止使用fromMap/toMap; - 常量直接使用
abstract final class定义在lib/shared/constants/目录下,禁止在页面文件里散落魔法数字。
这些规则看似琐碎,但正是这些细节最需要 AI 遵守。任何一个不一致的小命名,在 Flutter 项目里都会成为 Code Review 的一项纠错点,积累多了会非常影响效率。
3.4 依赖注入方式
Flutter 项目的 DI(依赖注入)一般有两种主流方式:使用provider包手动注入,或者使用riverpod自带的容器机制。规范里明确写清楚 DI 写法,能避免 AI 生成出无法编译的代码。
我指定了如下的依赖注入模式:
禁止在 Widget 构造函数中传递 Repository 依赖,统一使用 Riverpod 的 Provider 完成依赖注入。 新增的数据源、仓储、用例类,必须在对应的 provider 文件中完成注册。 Provider 文件位于 lib/features/<feature_name>/di/ 目录。这条规范特别重要。有一次 Codex 生成一个新的登录页面,它把所有依赖都通过构造函数传参传递了,代码编译没问题,但完全破坏了整个项目的依赖注入结构,后来我花了半天时间才把它改成 Provider 注入的方式。现在规范文件里明确约束之后,同类问题基本没有再出现过。
3.5 数据层与网络层规范
Flutter 项目里最容易出问题的是网络请求和数据解析部分,因为这里涉及的代码模式多,AI 可能生成多种不同的写法。我要求的统一模式是:
- 所有网络请求统一使用项目封装的
ApiClient类,禁止直接使用dio或http裸调用; - 接口返回数据统一先经过
ApiResponse<T>包装,再解析到模型; - 模型必须包含
fromJson和copyWith方法; - 所有后端接口的路径常量定义在
lib/shared/constants/api_paths.dart,禁止在业务代码中直接写 URL 字符串。
“禁止直接使用 dio 裸调用”这条,我在实践中发现必须写得非常直白。AI 在生成网络请求代码时,非常倾向直接把dio.get(...)写在业务代码里面,但它不知道项目里已经有封装好的ApiClient。规范里明确禁止之后,AI 就会主动去找ApiClient并调用它。
3.6 错误处理与日志规范
错误处理风格混乱是 AI 生成代码的另一个重灾区。有的地方用try-catch,有的地方用Either,有的地方直接抛异常但不处理。我要求全项目统一使用Result模式:
业务层禁止直接使用 try-catch 捕获异常,统一返回 Result<T>: sealed class Result<T> { ... } 所有有可能失败的调用,都必须返回 Result 类型,由调用方显式处理成功与失败分支。 持久化与框架层代码可以使用 try-catch,但捕获后必须记录日志,禁止静默吞掉异常。 日志统一通过 Logger 工具输出,禁止使用 print。这条规范特别考验 AI 的理解能力。实际上 Claude Code 在处理这种"sealed class 模式"时通常表现良好,它能理解文档里对失败分支处理的描述,而 Codex 有时会在非常短的代码片段里漏掉失败分支处理,所以需要配合下面的自动化检查来兜底。
3.7 测试规范约束
很多 Flutter 开发者不在意 AI 是否生成测试,但如果你想让 AI 长期参与项目开发,测试规范必须从一开始就立好。我的要求是:
新增功能模块必须配套单元测试,测试文件放在 test/ 目录下,并且与模块目录结构保持一致。 测试使用 mockito 或 fake 对象,禁止依赖真实网络请求。 测试命名遵循 given/when/then 风格。AI 生成测试代码的能力很强,特别是 Claude Code,它通常能生成覆盖主干路径的测试,但如果规范里没有要求,它不会主动写。现在这套规范跑下来,AI 生成的功能代码基本能同步生成测试,测试覆盖率在新增代码上维持在 85% 以上,差不多是真人开发的水平了。
4. 让规范真正被执行:Claude Code 和 Codex 的联动工作流
规则文件写好了,但如果只是放在那里,AI 未必每次都会完整遵守,尤其是复杂任务下容易"忘掉"某些规则。所以我在规范文件之外,还搭了一层联动工作流,让两个工具互相检查,效果比单独依赖某一款工具好得多。
4.1 开发流程:Claude Code 主写主审,Codex 负责快速生成
我运用这套体系有三个月了,以下是目前稳定运行的流程:
整个过程分三步:
- 需求拆解后,先把任务交给 Claude Code,给它完整的上下文,包括涉及的功能模块、现有的关联代码、必须遵守的规范条款;
- 简单生成任务交给 Codex 快速完成。比如"写一个
PaginationList组件,支持加载更多和刷新功能,按规范命名放在指定目录"; - Claude Code 生成完主代码后,由 Codex 执行一轮独立的代码审查,检查它是否违反了规范文件中的内容。
为什么要用两个工具互相审查?因为 AI 审查 AI 的代码,和人类审查 AI 的代码不一样,AI 有一个巨大的优势:它极其擅长识别模式偏差。比如命名风格不一致、某个错误处理方式偏离规范,这些在 Codex 审查时能被快速定位。
4.2 Codex 执行规范审查的 Prompt 模板
审查环节的 prompt 我攒了一套模板,实测效果很好,直接贴出来给你参考:
你是本项目的高级 Flutter 工程师。请严格按照 docs/engineering_guidelines.md 中的规范,审查以下文件,并只输出发现的问题列表: [文件路径列表] 要求: 1. 只报告违反规范的问题,不要修改代码; 2. 对每个问题标明违反的具体规范条目编号; 3. 如果没有问题,输出"通过检查"。这套 prompt 的关键点是"只输出问题列表",不要让它直接修。因为 AI 一旦自己动手改,它会倾向于重写整个文件,这往往会引入新的不规范代码。让它只列出问题,我们再决定是让 Claude Code 修改,还是手动修改。
4.3 人工 Review 的兜底时机
你可能会问,既然 AI 互审了,还需要人工 Review 吗。我的回答是:需要,但人的负担可以大幅减轻。
我现在的流程是,AI 互审全部通过之后,我再自己看一遍 diff,重点看这三类内容:业务逻辑是否有遗漏、边界条件是否处理到位、UI 细节是否符合设计稿。而关于"代码风格是否符合规范"这类问题,我基本不再花时间检查了,因为 AI 互审已经能比较可靠地兜住这方面问题。
这套联动流程跑下来,我个人的 Code Review 时间减少了至少一半。
4.4 小改动怎么处理
有人可能会担心,如果只是改一个方法两三行代码,也走这套流程是不是太重了。我的建议是:小改动直接手动改,不进 AI 流程;凡是新增文件或改动超过三十行,都必须走上述联动。这套工作流的成本并不高,但对于大改动,它的价值是实打实的。
如果需要改动多个文件,再给 Claude Code 一次性的完整变更指令,让它一次改完,效率更高。
5. 自动化检查层:让规范违反问题在代码提交前就被拦下
规则文件和工作流解决的是"AI 生成时遵守规范"的问题,但 AI 偶尔还是会出现意外。所以我又加了一层自动化检查,确保任何违反规范的代码在提交之前就被拦下来。这部分用的是 Dart 生态里的标准工具加上 CI 配置。
5.1 基础检查组合:analyze + format + test
Flutter 项目基础的三件套是绕不开的:
dart analyze:静态分析,能查出大部分编码问题;dart format --set-exit-if-changed:检查代码格式是否符合规范;flutter test:跑测试,防止逻辑回归。
这三项我全部挂在 CI 上,任何 PR 未通过检查都不能合并。AI 生成的代码往往在小细节上会在这三项上翻车,比如工具自动生成的格式和项目格式化配置不一致,dart format就能直接体现问题。
有一点要注意:格式检查跑在 CI 上没意义,因为开发机上可以一键格式化,但 CI 上失败只会增加一次提交轮次。它可以作为规范的兜底,但不能是唯一的反馈途径。
5.2 自定义架构扫描脚本
标准检查之外,我还写了一个自定义的 Dart 脚本tool/check_arch.dart,专门扫描项目结构是否符合规范。这个脚本做的事情很机械但很实用:
- 检查
lib/features/下是否有不符合 feature-first 目录结构的新建目录; - 检查是否有业务代码引用
dio或http包进行网络请求; - 检查是否有代码在
lib/下直接定义常量而没有放入lib/shared/constants/; - 检查是否有页面类没有使用
Page后缀。
这个脚本我没做得多复杂,几百行 Dart 代码,用了analyzer包的基础 API,遍历 AST 做字符串和模式匹配。丢到 CI 上跑了这段时间,效果出奇地好——它抓到的规范违反案例比人工 Code Review 更全面,因为在代码量大的情况下,人工很难逐行注意到所有细节。
5.3 CI 执行链的完整配置示例
最后放一个我目前正在使用的 CI 配置片段,使用常见 CI 平台的语法格式,你可以直接抄过去改改:
jobs: verify: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: subosito/flutter-action@v2 with: channel: stable - run: flutter pub get - run: dart analyze - run: dart format --set-exit-if-changed . - run: flutter test --coverage - run: dart tool/check_arch.dart有了这条链子,每次提交 AI 生成的代码,先过自动检查,再过 AI 互审,最后才到人工 Review。检查的等级是层层递进的,前一层通过之后,后面一层的效率就能大幅提升。
5.4 自动化检查遇到的常见坑
最后提醒两个坑。第一个是dart format和编辑器格式化配置不一致的问题,我遇到过 CI 上格式检查失败,但本地 IDE 显示格式一切正常,后来发现是项目里缺了analysis_options.yaml中对formatter的配置。解决方法是在项目里固定一个dart format配置版本,并确保团队所有人都使用同一版本的 Flutter SDK。
第二个坑是自定义扫描脚本容易误报。比如某些第三方包源码里本来就有dio依赖,脚本扫描的时候会把第三方代码也算进去。解决办法是在脚本里增加排除规则,只扫描lib/目录,不扫描第三方包缓存目录。
6. 这套规范体系的实际效果:从参数到可量化的收益
说了这么多,最后把效果量化一下,毕竟没有数据支撑的方法论很难让团队接受。
6.1 代码风格统一度提升
配置这套规范之前,AI 生成的代码和项目既有代码的风格一致性大概是这样:AI 能看懂文件里的局部风格,但跨文件的统一约定基本靠运气。配置之后,我抽了几次项目的代码做了统计,AI 生成代码的风格违反率从早期的 15% 左右降到了 3% 以下。
这个数字说明,规范文件本身就有相当强的约束力,加上自动化检查的兜底,AI 生成代码的偏差基本被控制到了可以接受的范围。
6.2 Code Review 效率提升
之前每次 Code Review AI 生成的代码,平均要花 45 分钟到一个小时,其中大部分时间是在纠正风格和结构问题,真正的业务逻辑审查反而没时间细看。现在 AI 互审和自动化检查把结构问题都拦在了前面,我的 Review 时间压缩到 20 分钟以内,而且这 20 分钟可以完全专注在业务逻辑上。
团队里的 A 同学之前对 AI 生成代码很不信任,觉得每次都要重新改一遍。这套流程跑了一个多月之后,他主动说,现在 AI 提交的代码质量有时候比自己手写的还稳定,至少命名和目录结构从来不用操心了。
6.3 返工率下降
量化返工率可能有点难,但有一个简单的观测指标:合并之后因为代码规范问题而被回滚的 PR 数量。之前平均两三个 PR 就会有一个因为规范问题被退回,这套体系上线之后,最近两个月基本没出现过因为规范问题导致的回滚。这个改善对团队信任度的提升很明显,大家开始愿意让 AI 承担更多的编码任务。
6.4 这套体系的维护成本
当然也要说说不好的方面。规范的维护是有成本的:随着项目演进,规范文件本身要持续更新;新增依赖时需要评估它是否影响现有规范;CI 脚本如果项目结构调整了也需要跟着改。好在这套东西的维护频率不高,大概一两个迭代周期才需要更新一次,完全可以接受。
7. 关于"AI 生成代码到底能不能用于生产项目"的一点个人体会
最后说点个人体会,不谈方法论。
我之前对 AI 生成代码的态度经历了一个变化:一开始完全不信任,后来觉得它写小段代码挺好用的但不敢让它碰大文件,直到搭完这套规范体系,我才觉得 AI 代码生成可以真正进入日常开发流程。它解决的问题不是"AI 能不能写出正确代码"——这个问题的答案早就人尽皆知了,真正的问题是"AI 能不能持续产出符合团队规范的代码"。
搭规范的过程本质上是把团队里很多心照不宣的隐性知识显性化。我自己在写规范文件的过程中,也重新审视了一遍项目的代码组织方式,发现了几个之前一直靠口头提醒的约定,如今终于被写成了可执行的内容。这种沉淀,即使没有 AI,对项目本身也是有好处的。
再分享一个小技巧:规范文件不要一次性写太长,第一次先写核心的二十条,跑两个迭代之后,再根据实际出现的问题把新的规则补进去。AI 规范文件不是法律条文,它更像一个项目的操作手册,需要持续迭代才能贴合项目的真实状态。
如果你也在纠结"AI 写代码靠不靠谱"这个问题,我建议先把规范搭起来再说。工具选择哪家倒是其次,真正的核心是:你愿不愿意花点时间,把团队里那些默认大家都懂的约定,一条一条地写清楚。条条写清楚了,AI 自然就不会乱来。