位置:首页 > 进阶教程 > 如何在Flutter工程中让AI生成代码不跑偏

如何在Flutter工程中让AI生成代码不跑偏

时间:2026-07-27  |  作者:白桃企划师  |  阅读:0

这不是模型变笨了,而是约束没跟上能力的增长。下面结合Cursor + Sonnet 5 + Flutter的实际场景,拆解跑偏的根因,并给出一套经过验证的防跑偏体系。

一、跑偏的四大根因

类型典型表现根本原因
范围漂移改A文件时顺手改了B/C/D,甚至重构了无关模块指令边界模糊 + Sonnet 5的agentic倾向(微软实测5比4.6更爱“顺手优化”)
范式摇摆一会儿用Riverpod,一会儿用Bloc;find.text和find.byKey混用项目约定没有锚定在AI可见的位置,或rules太长模型没读完
上下文中毒第10轮以后,模型开始忘记“禁止重写整个build”,又整段重贴长会话未及时compact/新开,早期约束被后续对话稀释
自检幻觉模型声称“跑了一下test全部通过”,实际上没跑或跑错了文件Sonnet 5的agentic自检 + 终端回包噪声,导致它误以为执行成功

这四个根因在Flutter项目中尤其突出——widget嵌套深、build方法长、状态管理方案多、生成文件(.g.dart)与手写文件混杂,模型稍不留神就会越界。

二、防跑偏体系:从硬约束到软习惯

1. 用AGENTS.md + .cursor/rules/*.mdc双层锚定范式

很多开发者把几十条规则塞进一个.cursorrules文件,结果alwaysApply太长,Sonnet 5的新tokenizer吃掉30%容量后模型根本没读完。正确的做法是分层锚定:

第一层:AGENTS.md(项目根,不超过50行)

这个文件是所有AI工具(Cursor、Claude Code、Codex、Windsurf)的通用入口,放在项目根即可生效。只放不可妥协的红线:

# Flutter 项目范式(不可妥协)
- 状态管理:Riverpod(AsyncNotifier + freezed sealed)
- 禁止:Bloc / Provider / setState 混用
- Widget 查找:必须用 WidgetKeys 常量(`find.byKey(WidgetKeys.xxx)`),禁止 `find.text()`
- 代码生成:freezed / json_serializable / riverpod_generator,只改注解,*.g.dart 禁止编辑
- 改 UI 必须 @ 文件+行号,禁止“改登录页”这种模糊指令
- 禁止重写整个 build 方法,只改指定子树 ±5 行

第二层:.cursor/rules/*.mdc(多文件,按globs按需加载)

Cursor 0.45+ 推荐的多文件规则体系,用globs匹配不同文件类型,只有当前文件匹配的规则才会注入:

.cursor/rules/
├── 000-core.mdc          # alwaysApply: true,≤500 字,通用省 token
├── 010-effort.mdc        # alwaysApply: true,控 Sonnet 5 thinking
├── 100-flutter-style.mdc # globs: **/*.dart,Flutter 编码约定
├── 101-state.mdc         # globs: lib/**,状态管理范式
├── 102-test.mdc          # globs: **/*_test.dart,测试范式
└── 103-assets.mdc        # globs: pubspec.yaml,护 pubspec

关键:alwaysApply的总字数一定要控制在500字以内(Sonnet 5的新tokenizer会让相同文本多出30% token),超出部分用globs拆到专用文件里。这样模型每轮都能读到核心红线,又不会被海量规则冲昏头脑。

2. 指令从“开放意图”改为“约束型Prompt”

跑偏的第一诱因是指令太开放。你只说“帮我优化登录页”,模型当然会按它的理解去“优化”——包括重构路由、调整主题、重写状态管理。正确的做法是在指令里画好边界:

跑偏高发 约束型
“帮我把登录页的按钮改成圆角”“@lib/pages/login_page.dart L78,把ElevatedButton的shape改成RoundedRectangleBorder(borderRadius: 12),只改这一个widget,不改build其余部分,不改AuthBloc”
“给网络请求加错误处理”“在login notifier的signInWithPassword里catch DioException,emit LoginState.error(message),不新增provider,不改UI”
“跑一下测试”“跑flutter test test/features/login/,只修失败的case,不新增test,不碰test/shared/”

三个要素缺一不可:改哪(@文件+L号)+ 改什么 + 不改什么。“不改什么”往往比“改什么”更重要——Sonnet 5的agentic天性决定了你不画边界它就会越界。

3. 复杂任务先出Plan,再执行

跨文件重构、新feature、Router结构调整这类任务,不要让模型直接写代码。在Cursor Composer或Claude Code里先让它:

Plan阶段你只需要盯三件事:

  • 涉及文件是否真的需要改(模型常列8个文件,其实3个就够了,砍掉多余的)
  • 有没有偷偷加“顺便优化”项(“顺便抽一下Theme”“顺便加个golden test”——全部砍掉)
  • 验证命令是否正确(flutter test的范围、build_runner是否需要跑)

一轮Plan通常只需200-300 token,却能省掉后面3-5轮返工。Plan不是浪费token,是买保险。

4. 会话管理:一个会话一件事,及时compact / 新开

跑偏的高发时段是第10-20轮——早期约束被后续对话稀释,模型开始“自由发挥”。

  • 一个会话只干一件事:修登录、改UI、写测试,三个会话分开
  • 关注context ring:Cursor聊天框左侧的色环变黄就该开新Chat了,别硬撑
  • 用好Checkpoint回滚:Composer改歪了直接回滚到上一个checkpoint,不要在错误基础上追问——追问只会叠token和叠跑偏
  • /compact带引导:在Claude Code里compact时加一句“保留WidgetKeys约定和Riverpod范式,丢弃具体改了哪几行”,否则compact后红线也可能被压丢

5. Flutter专属的两个防跑偏钩子

WidgetKeys常量化

Flutter测试和UI中最常见的跑偏是模型在find.text('登录')和find.byKey(WidgetKeys.loginSubmit)之间反复横跳。解决方案是在lib/core/keys/下统一维护key常量:

abstract class LoginKeys {
  static const emailField = Key('login_email_field');
  static const passwordField = Key('login_password_field');
  static const submit = Key('login_submit');
}

然后在100-flutter-style.mdc里写死:“新增交互元素必须加WidgetKeys,改existing的find必须用WidgetKeys.xxx”。模型一旦看到常量引用,就不再敢擅自改成find.text。

build_runner护城河

Flutter项目中模型手搓.g.dart是灾难——它觉得“帮你省一步”,结果手写的generated code和注解对不上,编译失败。在AGENTS.md里写死:

同时.cursorignore里把*.g.dart、*.freezed.dart、*.config.dart全部排除,让模型连读都读不到这些文件,从根本上杜绝手搓。

三、跑偏的早期信号:第一轮就掐掉

别等到第5轮才发现方向错了。模型第一轮回复中间出现以下任何一条,立即打断并重给指令:

  • 开始复述你的需求(“好的,我来帮你优化登录页...”)→ 它在寒暄,大概率接下来要越权
  • 开始改你没有@的文件 → 范围漂移,立刻喊停:“只改@的这个文件”
  • 开始写你没要求的test / docs / README → agentic自检溢出,检查rules里“写完不必主动跑test”那条是否生效
  • 开始重构不相关代码(“顺便把Theme也抽一下”)→ Sonnet 5经典病,指令里缺“不改UI其余部分”
  • 回复中thinking_blocks很长但结论飘 → L1任务不该触发thinking,检查010-effort.mdc是否压住了

四、总结

AI驱动的Flutter工程,核心矛盾不是“模型能不能写”,而是“模型能不能只写你让它写的”。防跑偏不是靠“更聪明的模型”,而是靠一套可执行的约束体系:

层级工具作用
红线锚定AGENTS.md(< 50行)跨工具通用,不可妥协
细则分层.cursor/rules/*.mdc(globs按需加载)按文件类型注入,alwaysApply ≤500字
指令设计约束型prompt(改哪 + 改什么 + 不改什么)给模型画边界
任务流程Plan → 确认 → 执行复杂任务先对齐
会话卫生单会话单事 + 及时compact/新开防止约束稀释
Flutter特化WidgetKeys常量 + build_runner护城河堵住两个最高频的跑偏点

这套体系已经在多个Flutter生产项目中验证:同样的Cursor Pro $20额度,配之前半个月见底,配之后撑满一个月,且代码质量明显提升——不是因为模型变强了,而是因为它终于知道哪些事不该做。

AI编码的未来不是“模型全能”,而是“模型在你画的圈子里全能”。圈子画好了,它才不会跑偏。

免责声明:文中图文均来自网络,如有侵权请联系删除,心愿游戏发布此文仅为传递信息,不代表心愿游戏认同其观点或证实其描述。

相关文章

更多

精选合集

更多

大家都在玩

热门话题

大家都在看

更多