首页 / 资讯中心 / 文章详情

AI写Flutter代码总翻车?用工程规范约束Claude Code和Codex

AI写Flutter代码总翻车?用工程规范约束Claude Code和Codex ★ FEATURED ARTICLE
先说个真实经历。有次我把一个 Flutter 项目交给 Claude Code 调整一个页面结果它连续三次提交让我血压升高第一次是绕过 Repository 层直接在页面里塞了个新的 API 调用第二次是把我项目里跑得好好的 Riverpod 状态管理改成了全局单例第三次更绝它为了“优化”启动速度悄悄换了一个我当时根本没引入过的依赖库。后来我把 Codex 也拉进来一起干活心想多一个助手总归能互补结果这俩工具经常各自为政你按你的风格写我按我的想法改提交记录乱到连我自己都看不下去。如果你也遇到过类似情况那这篇文章就是写给你看的。我后来花了差不多一周时间摸索出一套针对 Flutter 项目的 AI 工程规范核心思路很简单用规则文件把项目架构、编码约定、行为红线提前写进 AI 的上下文里从源头减少“自由发挥”的空间。整篇文章会围绕 Claude Code 和 Codex 这两个工具的配合方式展开适合正在用或准备用 AI 写 Flutter 代码的开发者尤其是团队项目里对代码质量和一致性有要求的情况。1. 先搞清楚 AI 写 Flutter 为什么会“翻车”很多人的第一反应是“模型还不够聪明”但做了一段时间后我发现翻车的主要原因是约束缺失不是能力不足。大模型代码生成的那个“聪明劲儿”恰恰是双刃剑它能帮你补全代码也能在你不注意的时候自己发挥一套设计。1.1 那几次让我抓狂的 AI 提交我复盘了一下手头项目里最典型的几次 AI 翻车大概能归成四类。第一类是架构穿透。Flutter 项目通常会分层UI 层、业务逻辑层、数据仓库层、网络层。AI 接到“把登录接口改成新的地址”这种需求时它不会管你之前是不是统一在 Repository 里封装了 Http 请求而是直接在 Button 的点击回调里Dio.post一把梭。功能能跑但代码风格直接破坏了整个项目的分层结构。第二类是状态管理混乱。你用的是 RiverpodAI 却经常冒出setState或者InheritedWidget的写法你用的是 Bloc它非要在 build 方法里处理逻辑。这类问题在长文件中特别明显模型很难自己意识到项目上下文中的状态管理约定是什么。第三类是依赖随意引入。AI 生成代码的时候很喜欢顺手引包今天用intl明天用uuid后天又来个shared_preferences。如果没人拦着一个本来很干净的项目能被它塞进十几个根本不必要的依赖pubspec.yaml 越改越厚。第四类是边界感缺失。让 AI 改 A 功能它顺带把无关的 B 文件也动了让它修个小 bug它把整个文件重构了一遍。这类问题最隐蔽因为 diff 不大容易被忽略但日积月累下来代码会越来越“脏”。1.2 翻车不是模型笨是约束缺失为什么会出现上面四类问题从原理上看有几个关键原因。一是上下文窗口有限。Claude Code 也好Codex 也好它们能看到的内容受上下文长度限制不可能像一位入职三个月的同事那样把整个项目的历史和约定全部装进脑子。它看到的是你当前打开的这几个文件理解是碎片化的。二是局部最优偏好。模型在预测下一个 token 的时候倾向于选择最常见的代码分布。也就是说它写的代码会更接近 GitHub 上的“平均水平”而不是你项目里的“特定标准”。在你的项目里可能约定所有页面都继承BasePage但全世界的 Flutter 代码里默认写法可能就是StatelessWidget。三是**“读懂需求”和“读懂项目”是两码事**。Claude Code 和 Codex 很擅长理解你那一句话需求的字面意思——把按钮改成红色让列表支持下拉刷新——但它们未必理解这句话在你项目架构里应该落到哪个文件、用哪种方式实现。这种“项目感”需要额外的方式去补。明白了这三点方向就清晰了我们不能指望 AI 自己“懂事”而是要通过规则和约定把项目规范显式地喂给它。这也正是这篇文章说的“工程规范”要做的事情。2. Claude Code 与 Codex 的角色分工让两个助手各干各的活既然同时用两个 AI 工具就得先把各自的定位搞清楚。我一开始犯的错是让它俩“抢活干”结果两个模型在同一块代码上互相修改的风格完全不同。后来我重新做了分工效果立刻不一样了。2.1 两个工具的能力边界对比Claude Code 和 Codex 虽然都是 AI 编码助手但开箱感受差别挺大。我根据自己实际用的场景整理了一张表维度Claude CodeCodex任务类型擅长复杂重构、跨文件的架构理解擅长按指令生成模块代码、补全函数上下文利用长对话中保持风格的能力更强更依赖当前项目文件上下文较短时执行力强交互方式终端内对话式可多轮追问快速任务型适合给清晰命令规则文件支持支持 CLAUDE.md 这类项目级指令文件支持 AGENTS.md/项目说明文件适合干的活方案评审、跨层重构、写业务骨架生成 widget、小改动、写单测这不是说谁好谁差而是两类工具的设计取向不同。Claude Code 更像一个可以反复讨论的技术合伙人你给它一个含糊的意图它能通过对话把方案的细节问清楚Codex 更像一个高效的执行员你给它明确的指令它很快给你可运行的结果。如果把活儿反着分配体验会非常别扭。2.2 我的分工方式主沟通、辅执行经过几轮调整我的固定工作流是这样的Claude Code 负责“想清楚”需求拆解、影响范围分析、重构方案设计、代码审查。凡是需要理解项目整体逻辑、判断多个方案取舍的活儿都交给它。因为它更适合上下文多的对话式交互也更容易在长对话中维持对项目规范的记忆。Codex 负责“写出来”一旦方案定了、步骤拆好了具体的页面改造、小模块生成、单测用例补全都交给 Codex。给它的指令通常已经包含明确的文件路径、函数签名和验收标准它执行起来很干脆。有个很关键的细节我让 Claude Code 先把具体改法写成一段“实施说明”再把这段说明粘给 Codex 去执行。实际跑下来非常顺Codex 很少再自作主张。你可能会觉得这流程有点繁琐但相比它俩直接改代码带来的返工成本这点“翻译”工作太值了。2.3 给 AI 一个共同的“项目说明书”这里有个容易被忽略但极其重要的事让两个工具读同一份项目规范文件。Claude Code 会默认读项目根目录下的CLAUDE.mdCodex 则更偏向AGENTS.md。如果你只给 Claude Code 写了规则Codex 完全不知道那分工再合理也是各干各的。我的做法是在项目根目录建两个文件CLAUDE.md和AGENTS.md。这俩文件内容可以高度重合都指向同一份核心规范文档俗称“项目说明书”。里面写清楚这个 Flutter 项目的架构分层、状态管理方案、命名规范、禁止事项以及标注好哪些文件是 AI 绝对不能动的基础设施代码。这样两个工具在动手之前至少“读过同一本手册”。3. 工程规范文件把架构约束写进 AI 的上下文规范文件听起来简单真正写起来很容易跑偏。我见过有人把自己项目的 CLAUDE.md 写成了两千行的“百科全书”结果 AI 上下文一长反而注意力分散重要规则被稀释。规范文件不是越长越好关键是把**“AI 最容易犯错、犯错了代价最大”**的规则放进去。3.1 规范文件该放什么不该放什么先说不该放什么。不要放大段的团队开发流程比如“代码评审需要两名以上成员通过”“每日构建必须健康”之类这些跟 AI 生成代码无关。不要放太抽象的原则比如“代码应保持高内聚低耦合”这种话 AI 读完等于没读它不知道具体怎么执行。不要放频繁变动的信息比如版本号列表、具体接口地址这类信息应该由代码和配置文件去管理写进规则里只会让文件迅速过期。该放什么我的清单是这样项目架构分层说明项目分哪些层每层职责是什么文件放在哪个目录。状态管理约定项目统一用什么方案什么场景允许局部setState。数据流规则页面如何调数据、数据模型如何定义、禁止绕过哪一层。命名与代码风格文件命名、类名、方法命名的规则以及是否需要强制 lint。红线行为哪些操作是禁止的比如“不得擅自增加依赖”“不得修改核心基建文件”“不得用print调试”。代码生成约束期望 AI 输出时保留注释、按最小变更原则修改、避免无关文件被改动。最后一条尤其重要。我一开始写的规范文件侧重“架构理论”后来发现 AI 确实不违反分层了但每次提交顺手把十几个无关文件格式化了。于是我在规则里加了一句硬性要求“只修改实现需求所必需的文件禁止触碰未明确要求的文件。”加上这句之后diff 立刻干净很多。3.2 给你们一个可以直接抄的 CLAUDE.md 模板我把自己项目里目前跑得比较顺的 CLAUDE.md 摘了一段去掉了敏感业务名称你们可以参考这个骨架来搭自己的# 项目工程规范Flutter 版本 ## 架构分层 本 Flutter 项目分为以下层级 - presentation页面与 Widget只允许调用 application 层 - application业务用例与状态管理Riverpod不出现 UI 代码 - data仓库实现负责网络与本地数据 - domain实体与接口定义 禁止在 presentation 层直接创建 Dio/Http 实例或访问数据库。 所有网络请求必须通过 data 层的 repository 接口。 ## 状态管理 统一使用 Riverpod。 禁止全局静态可变状态。 页面内临时 UI 状态如开关、选中项可使用 StatefulWidget。 修改已有 Provider 之前必须注释说明影响范围。 ## 文件与命名规则 - 文件命名使用 snake_case - Provider 命名以 Provider 后缀结尾 - 页面组件命名以 Page 后缀结尾 - 数据模型类放在 domain/modelsjson 解析统一用 fromJson/toJson ## 红线绝对禁止 1. 不得修改 pubspec.yaml 中的依赖除非需求明确要求。 2. 不得修改 core/ 目录下的基础类与主题配置。 3. 不得在业务代码中 print 调试统一使用日志工具。 4. 不得并行重构与任务无关的文件。 5. 不得删除尚未被确认无用的注释代码块。 ## 生成代码要求 - 遵循最小变更原则只修改任务要求涉及的文件。 - 所有公开方法的代码块必须保留并输出。 - 新增文件必须同时添加对应的 widget test。 - 输出前自行检查是否存在未使用的 import、重复代码、测试遗留。这个模板我一开始也写得干巴巴的后来是边踩坑边往里补的。比如“新增文件必须同时添加对应 widget test”这条最初没有结果 AI 每次新增 Feature 都漏测试CI 直接被红叉拦下。补上之后它至少会顺手产出测试文件了。3.3 让 Codex 也遵守同一套规则Codex 对AGENTS.md的读取逻辑我没法从公开文档里确认得很细但实际使用中我会在每次任务指令里显式引用规范文件请先阅读项目根目录的 AGENTS.md并按其中定义的架构分层和红线规则执行。 本次任务在 presentation/pages/login.dart 中实现登录按钮的 loading 状态切换。 要求不得修改 data 层与 domain 层不得新增依赖完成前检查 RedLine 列表。这段“提示词前置”看起来土但效果立竿见影。因为 Codex 在较新的上下文里能看到这条指令它会倾向于先读取规范文件再动工。如果你担心它不读文件也可以在指令里直接把红线规则再粘贴一遍。虽然啰嗦但比 Codex 擅自改坏核心代码要好得多。4. 立法三件套架构蓝图、红线规则、验收清单规范文件是静态的“法条”光有法条还不够。实际用下来我发现AI 在具体执行一个任务时如果只看规则文件仍然缺少三样东西它对任务整体的理解蓝图、不能碰的底线红线、以及做完后怎么算合格验收清单。这三样合起来我称它为“立法三件套”。4.1 架构蓝图让 AI 先看地图再动手蓝图的意思不是画一张图贴到 CLAUDE.md 里而是在每次大任务开始之前用文字描述出这个任务在整个项目架构中的位置和影响面。比如我要让 AI 给某个列表页加一个搜索功能蓝图会写成这样任务目标列表页新增按名称模糊搜索功能。 位置说明 - presentation/pages/product_list_page.dart 负责 UI 展示新增搜索框。 - application/providers/product_list_provider.dart 管理状态与过滤器。 - data/repositories/product_repository.dart 已有 fetchAll 方法本次无需改动网络层。 影响面仅涉及上述两个文件的修改domain、data 层严禁改动。 预期验收输入关键词后列表实时过滤Provider 中 filter 字段不可直接暴露修改方法。这段描述把“改哪个文件、不动哪个文件、验收标准是什么”一次讲清楚。给 Claude Code 看它能更好地理解任务的边界给 Codex 看它能少走弯路。我试过不给蓝图直接丢需求AI 经常把数据源、仓库层、页面全重新写一遍看起来哪哪都改了实际上哪哪都不是你想要的。蓝图里有个技巧尽量写清“不影响什么”而不是只写“要做什么”。AI 对禁止性信息往往比建议性信息执行得更严格。4.2 红线规则什么不能做比怎么做更重要前面提到的“红线”在立法三件套里是专门的章节。我制作了一张红线清单每次任务开始都让 AI 先确认结束前逐条自查。这张清单不长但针对性很强编号红线内容自查方式R1不得新增未授权依赖检查 pubspec.yaml diffR2不得修改 core 目录下基础文件检查 git diff 涉及文件列表R3不得删除未确认无用的注释与代码检查 diff 中 delete 行数R4不得在 repository 层之外创建网络实例搜索 Dio/HttpClient 使用位置R5不得改变既有文件的换行与格式风格检查 diff 中非必要改动这五条不是拍脑袋定的全是几个月里 AI 实际踩过且代价较大的坑。比如 R3有一次 AI 为了“清理代码”把我注释的设计说明大段删掉后来查问题的时候少了重要线索比如 R1AI 加了个本地存储库结果和团队在用的方案冲突回滚浪费了不少时间。这类红线按团队项目实际需求去增删就行不要追求全要追求**“犯一次错损失够大”的规则优先**。4.3 验收清单AI 自测 人工复核的配合最后一道栅栏是验收清单。我让 AI 在每次完成任务时直接在提交信息里附带一个自测清单比如## 变更摘要 - [ ] 仅修改了 lark 要求的两个文件無核心文件改动 - [ ] 无新增依赖 - [ ] 运行 flutter analyze 无新增警告 - [ ] 新增 widget test 并全部通过 - [ ] 已按架构分层放置代码无跨层调用有人可能会觉得这很形式主义但你要明白AI 输出的自测日志本身就是一个“检查强制点”。它会迫使模型在回答前先把逻辑和改动过一遍减少“直接吐答案”的概率。更重要的是这个清单也让人工 review 变的极快——你只要对照它逐项核对就能在没有逐行阅读的情况下快速判断这次提交风险高不高。我个人的复验方式是先看 git diff 里的文件列表再核对自测清单的每一条最后只在有疑虑的地方点开具体代码。这套流程配合下来单次任务 review 时间从二十分钟压缩到了五分钟以内而且问题发现率反而更高了。5. 实战验证让 AI 完成一个真实模块的完整流程光讲理论容易飘我拿一个模拟场景走一遍完整流程。假设现在有一个 Flutter 项目需要实现一个“必做事项清单”模块包含三个功能展示今日任务列表、支持标记完成、支持添加新任务。要用 Claude Code 和 Codex 配合在已有架构上实现不允许破坏既有风格。5.1 需求描述怎么写 AI 才听得懂很多人在这一步就出问题了。给 AI 的描述如果只有一句话——“帮我做一个任务清单模块”——它必翻车因为这句话完全没交代边界、依赖、交互细节。我通常会把需求拆成结构化的描述像写产品 PRD 一样但不那么隆重需求实现今日任务清单模块 v1 交互路径 1. 用户进入今日任务页后默认展示当日全部任务。 2. 任务项右侧有完成/未完成切换按钮。 3. 页面底部固定一个新增按钮点击后弹出底部输入框。 4. 新增任务成功后立即刷新列表。 数据来源 调用 data/repositories/task_repository.dart 中已有的 fetchTasksByDate/addTask 方法不新增网络层。 已有能力 - 项目使用 Riverpod 管理状态任务页对应的 provider 已存在于 application/providers/task_provider.dart。 - UI 组件库包含 AppButton、AppInput 等通用组件必用。 禁止 - 不修改 task_repository.dart 的接口签名。 - 不新增本地存储依赖。 - 不得在页面内直接操作底层缓存。 验收 - flutter analyze 通过。 - 单测覆盖 provider 的加载、添加、切换完成态。这段描述包含了“交互路径”“数据来源”“已有能力”“禁止行为”“验收标准”五个维度。AI 拿到这种输入比拿一句含糊话强十倍。我实测下来Claude Code 读完这个需求后能自己确认几个关键问题比如任务标题的长度限制、切换完成态后是否要本地持久化——因为规范里写了 Repository 的职责它能推断出该走数据层。5.2 实测过程中的意外与修正流程开始后我通常是先让 Claude Code 读需求让它输出改动手册改动手册 1. task_provider.dart 新增 TaskState 类增加 selectedDate、taskList、isLoading 字段。 2. 增加 fetchTasks()、addTask()、toggleTask(id) 方法。 3. task_page.dart 改造为 ConsumerWidget监听 taskProvider。 4. 复用 AppButton、AppInput 组件不新建样式。 5. 不修改 repository 方法签名只做适配调用。 6. 新增 task_provider_test.dart覆盖三个核心场景。这段手册写出来Claude Code 相当于已经把实施思路钉死了。接着我把手册连同需求描述一起丢给 Codex 生成具体代码。这个环节一般是高质量输出但也不是完全一帆风顺。有次 Codex 在task_page.dart里没有使用已有的AppInput而是自己写了一个TextField加样式违反了“必用通用组件”的约定。我的处理方式是把这条错误和纠正方式补充进 CLAUDE.md 的“生成代码要求”里- 页面输入类组件必须从 ui/widgets 下现有组件中选择禁止在 feature 页面新建样式组件。下次再跑类似任务它就没再犯同类错误了。5.3 典型翻车现场AI 私自引入全局状态还有一个值得单独说的案例。在这个模拟场景里我故意漏掉一条规则结果 Codex 果然翻了一次车。它为了实现“新任务添加后立即刷新列表”在 provider 里加了一个全局单例缓存对象把任务列表暴露成了 static 可变变量。逻辑确实能工作但完全违背了 Riverpod 的状态管理思路而且会在热重载时出现各种幽灵状态。我的处理办法是先让 Claude Code 审查这段代码它很快指出这是全局状态泄漏并给出修改方向再让 Codex 按修复方案重写 provider改为标准 Riverpod 的AsyncNotifier方案。从那以后我在红线里又多了一条- 禁止创建 static 可变变量禁止全局单例状态。所有共享状态必须走 Riverpod Provider 或 Notifier。这条规则其实属于比较基础的 Flutter 工程常识但写进 AI 规范里远比在评审时一次次口头纠正来得高效。这类“AI 翻车现场”只要在流程中暴露出来就能反向加固规范让规范本身变得越来越贴合项目的真实约束。6. 规范之外的护栏diff 审查与迭代节奏工程规范不是写一次就万事大吉它需要持续迭代也需要配合一些“线下”的审查手段。我通常会设置两道防线AI 的自查、人的抽查。自检清单上面说了这里重点讲讲人的那一侧以及规范的版本管理。6.1 规范文件越写越臃肿怎么破CLAUDE.md 这种文件有个典型生命周期初版简单但随着 AI 犯的错增多你会忍不住疯狂往里面加规则。加到最后文件可能上千行。结果就是AI 上下文塞满了大量细节核心规则被稀释反而容易触发“注意力衰退”效应。我目前的做法是把规范文件分成两级CLAUDE.md只放“一级规则”架构红线、行为总纲控制在 100 行以内详细技术规范放在另一个docs/ai-coding-standards.md里需要时让 AI 主动查阅。定期清理规则每两周过一遍规范文件删除已经沉淀成 lint 规则或代码模板约定的事项。比如命名规范一旦在analysis_options.yaml里配置了 lint就没必要再写在 CLAUDE.md 里。过期规则即删比如某个旧的 Repository 结构已经被重构掉了规范文件必须同步更新否则 AI 照着过时架构做事反而制造新问题。这套“瘦身”机制保证了规范文件始终精炼AI 读起来负担小规则也相对不容易被忽略。6.2 diff 审查是最后一道硬防线不管 AI 多听话最终防线永远是 diff review。我的习惯是不管任务大小AI 每次提交都必须走git diff --stat看文件级改动再点开关键文件的 diff。这里有两个要点看文件列表是否符合预期。如果蓝图说只改两个文件git diff 里却出现了五个大概率是 AI 做了越界操作。看非必要改动占比。AI 经常顺手格式化、顺手补注释、顺手调整无关代码。每行多余的改动都是以后 merge 冲突的潜在来源。我实测最能发现问题的方法是在提交前后对比pubspec.yaml和core/目录的 diff。因为 AI 一旦动了依赖和基建代码后续排查成本会骤增这两个地方的任何改动都要特别警惕。6.3 后续可以怎么继续扩展这套规范已经运行了挺长时间项目的代码质量和 AI 生成代码的一致性好了一些提交 diff 也顺畅了不少。后面我还想继续尝试几个方向一是把规范文件接到 CI 里做成自动检查AI 提交后由脚本校验是否触碰红线不满足就自动打回二是把 CLAUDE.md 按模块拆分不同子项目加载不同规范避免一个“万能文件”里各种矛盾三是给 AI 建立“项目变更历史”文档让它每次改代码前先看一眼最近几次结构演进减少它拿旧架构套新需求的情况。根据我个人的体会AI 写代码这件事真正的分水岭不在“模型能写多少”而在“你愿意花多少精力去约束它”。像对团队新成员一样先立规矩、再给蓝图、后做验收它大概率会成为得力的帮手否则再强的模型也只是个“换着花样翻车的新人”。希望这篇文章里的模板和流程能帮你们把 AI 写 Flutter 代码的路先铺平一些。
阅读完成 · 觉得有帮助?
咨询建站