一个71.6万行Flutter的工程,按以前的行业经验值估算(100 行/人日),人工迁移重写成原生双端需要 约 51 人年 ------10 个人干五年。而当你重写完的时候,你重写的那个版本已经不存在了。 但是使用AI,我们实际用掉的是 2.5 人年,就完成了以前几乎不可能完成的任务。
为了让大家直观感受迁移效率,我列一下我们的大致迁移节奏:
我们的迁移节奏不是「一堆人猛干三个月」这种形态,而是分了两个阶段:
- 地基期 :2026 年 3--6 月,3 个人,4 个月,建流水线与公共基础层(1.0 人年)
- 冲刺期 :2026 年 7--8 月,9 个人 ,2 个月,完成绝大部分模块迁移(1.5 人年) 人均日产出从基线的 100 行变成 2,046 行 ,约 20 倍 。更值得注意的是:冲刺期人数增加到 3 倍,人均产能反而又提升了 64%------流水线建成后加人是超线性的,这在传统软件项目里几乎不会发生。
| 项 | 迁移前 | 迁移后 |
|---|---|---|
| 技术栈 | Flutter | Kotlin/Compose + Swift/SwiftUI |
| 代码规模 | 716,215 行 Dart | 599,074 Kotlin + 526,032 Swift = 1,125,106 行 |
| 平台能力依赖 | 144 个 pub 依赖,其中 33 个自维护 fork | 平台原生 API |
| 设备安装占用 | 244.35 MB | 193.85 MB(−20.6%) |
| Token 成本 | --- | 约 $70,000 |
| 工作量 | 人工重写基线约 51 人年 | 2.5 人年 |
本文将向大家展示,我们是如何借助AI完成一个百万行APP的架构迁移。 本文会讲 :流水线怎么设计、项目级上下文资产怎么建、百万行 AI 代码的准入标准怎么定,以及七个真实踩过的坑。关键数据我尽可能给出可复算的口径,让大家能更清楚的度量这次AI迁移的效果 本文不讲:具体业务实现、AI 工具使用教程,以及「AI 将如何改变软件工程」这类展望。
一、我们为什么放弃 Flutter
先说清立场:这不是 Flutter 批判文。 我们的这个产品使用Flutter跑了6年,在项目最开始移动端只有2个人的时候,Flutter确实帮我们省下大量重复投入,支持我们快速扩展业务。放弃它不是因为它不行,而是因为产品长成了一个不再适合它的形状。
1.1 平台能力接入成本
原工程 pubspec.yaml 有 144 个直接依赖,其中 33 个没有版本号 ------指向 git 或本地路径,意味着它们是自维护的 fork 或私有插件:
| 能力域 | 依赖形态 |
|---|---|
| 设备通信(蓝牙 / 局域网穿透 / TLS 长连接) | 自维护 fork ×3 |
| 音视频(实时流 / 播放器 / 转码 / 实时消息) | 自维护 fork ×4 |
| 3D 渲染(模型预览 / 分组) | 自维护 fork ×2 |
| IM | 自维护 fork ×1 |
| 加密 / 日志 / 前台任务 / 实时活动 | 自维护 fork ×5 |
| 图片 / 系统能力 / WebView / 扫码 / 分享 / 下载 | 公开插件 ×30+ |
| UI 补丁(瀑布流 / 下拉刷新 / 弹窗 / 富文本 / 跑马灯...) | 公开插件 ×20+ |
33 个自维护 fork 是最贵的一项。 每一个都意味着:自己维护两份原生代码 + 一层 Dart 桥;上游更新要自己 rebase;排查链路是「Dart 层 → 桥 → 原生层」三段。
这时候「一套代码两端跑」已经不成立了------你写的是三套。 跨端只在最上面那层界面里还成立,而那层恰恰最便宜。
还有个隐蔽成本:UI 补丁类插件有二十多个。瀑布流、下拉刷新、粘性头部这些在两端原生都是一等公民,在跨端里要靠社区插件补------每一个都是一个维护面和一个潜在断供点。
1.2 长期维护性
框架升级的破坏性变更 :大版本升级带来 API 变更,而我们有 144 个依赖要跟着动,我们实在没有足够的人力去升级这么多依赖。对于能用的,只能钉死旧版本,实在有问题需要fix或者出现重大更新,才更新依赖。如果不钉死版本,那么这些依赖随时的升级,将给工程的可控性带来灾难。
三方库断供:33 个自维护 fork 的存在本身就是断供已经发生过的证据------之所以 fork,是因为上游不满足我们需求,我们需要自定义修改;或者干脆三方库不更新了。
原生端的对应风险小得多:平台官方 API 有明确的废弃周期和迁移路径,而且不需要等一个中间层的作者(pub库的作者)先适配完。
1.3 包体:红利被谁吃掉了
生命苦短,数据说话。旧包是Flutter打出的Android包,新包是使用Kotlin打出的Android包。
| 口径 | 旧包 | 新包 | 变化 |
|---|---|---|---|
| 设备安装占用(同 ABI) | 244.35 MB | 193.85 MB | −20.6% |
| 原生库设备占用 | 174.80 MB | 102.72 MB | −41.2% |
| 业务字节码(dex) | 10.18 MB | 17.18 MB | +68.8% |
把旧包里只因为用 Flutter 才存在 的东西挑出来:引擎 libflutter.so 10.59 MB + Dart AOT 产物 libapp.so 12.44 MB + 自带资源体系 flutter_assets 45.77 MB = 68.80 MB,占同 ABI 安装占用的 28.2%。
最该注意的是中间那项。 一个 71.6 万行 Dart 代码的 App,它全部业务逻辑的编译产物只有 12.44 MB,占整个包的 5.1%。为了让这 5% 跑起来,需要 10.59 MB 引擎和 45.77 MB 独立资源体系。
注意:新包的资源部分反而更大,但其中 14.09 MB 是业务数据包的版本差异、约 6 MB 是新增的 React Native 能力,与框架迁移无关 ,所以该引用的是 −20.6% 和 −41.2%,不是 APK 的 −13.7%。
1.4 判断框架:什么时候不该用跨端
跨端在这些场景下仍然明显更优 :业务形态以界面为主;团队规模小于双端各配一队的门槛(如果你只有 3 个移动端工程师,跨端不是技术选择,是生存选择,一如我们团队最开始 );产品处于快速验证期;平台能力需求稳定且已被成熟插件覆盖------注意是「稳定」,可怕的不是需要插件,是需要不断引入新插件。
反过来是一张自检表,命中越多性价比越低:
- 需要为核心能力自维护插件 fork(命中 3 个以上就是强信号)
- 包体里原生库体积远超业务代码
- 核心页面同时存在实时流、硬件通信、原生渲染中的两项以上
- 因为某个插件没跟上,框架版本被卡在旧版
- 需要靠二十个以上 UI 插件补齐基础控件
这五条我们全中。 所以这不是追求技术纯粹的重写,是成本结构倒挂之后的止损。
性能声明 :迁移前后暂时没有采集到可对比的性能基准(帧率、卡顿率、内存、启动耗时均无同口径留痕),所以本文不做任何性能收益的主张------尽管性能确实是当初做决策的考虑之一。
最后必须承认:如果没有 AI 把重写成本从约 51 人年压到 2.5 人年,这个决策我们做不了。 我们在两年前就发现了上述问题,想要迁移,只是当时代价太大。技术判断和技术可行性是两件事。
二、为什么敢用 AI 重写
2.1 跨语言迁移恰好是 AI 最擅长的任务
大多数 AI 编程的失败,失败在同一个地方:任务本身没有客观的对错标准。
跨语言迁移是个异类,它同时满足四个条件------这四条几乎构成了「AI 可规模化任务」的定义:
- 输入完全确定 :不需要理解需求文档,原工程源码就是需求,歧义度接近零。
- 输出有客观验证信号 :编译器给二值判定,测试给二值判定,与原实现的一致性审计给可枚举的差异清单。「做对了没有」不需要人来回答。
- 业务决策已全部做完 :迁移的是上线多年的成熟产品,每个交互细节都是过去几年真人吵出来的结果。AI 不做产品决策,它只做翻译------而做决策恰恰是模型最不可靠的部分。
- 天然可切分、可回滚。
编译器是最好的 Reward Signal------免费、即时、零噪声,且不会被模型的措辞说服。整条流水线本质上是在堆奖励信号的密度:
编译通过 → 语法与类型正确
静态检查通过 → 符合项目约定
提交门禁通过 → 避开项目特有陷阱
单测通过 → 行为符合规格
Journey 通过 → 主链路可用
一致性审计无差异 → 与原实现等价
六层信号,从廉价到昂贵。每加一层,就把「需要人来判断」的范围压缩一次。
反过来说,如果任务里找不出这样的信号,那么无论模型多强都无法规模化------因为验证会成为瓶颈。
2.2 三条路径
| 维度 | 人工重写 | 自动转译工具 | AI Agent 流水线 |
|---|---|---|---|
| 工作量 | 约 51 人年 | 低 | 约 2.5 人年(含流水线建设) |
| 可行性 | ❌ 排期上不可能 | ✅ | ⚠️ 有不确定性 |
| 产出可维护性 | ✅ 最好 | ❌ 不可维护 | ✅ 良好(受门禁保障) |
| 架构可改进 | ✅ | ❌ 原样搬运技术债 | ✅ |
| 止损难度 | 高(沉没成本大) | 低 | 低(可分模块回退) |
人工重写出局不是因为不好,是因为不存在。 51 人年意味着 10 个人干五年,而产品每个月都在迭代------当你重写完的时候,你重写的那个版本已经不存在了。
自动转译的问题更隐蔽也更致命。 机械转换能跑通,但产出的是「用目标语言写的源语言代码」:状态管理、异步模型、UI 层级还是原框架的形状。你花了一次重写的钱,买回来一份既不像 Flutter 也不像原生的代码,还得永远维护它。
2.3 三道止损,都是结构性的
① 原工程全程不冻结。 最重要也最贵的一道。git 记录显示,2026 年 3 月到 7 月原工程每月仍有 235 ~ 465 次提交 ,8 月才降到 33 次。双轨并行跑了五个月------任何时刻叫停迁移,线上产品都不受影响。
代价是原工程成了移动靶 (后面算账)。但这个代价必须付:一个不能中途取消的迁移方案,无论看起来多有把握,都是不可接受的风险。
② 模块级可回退。 一致性状态表逐模块记录进度,任何模块可单独回退到「未迁移」。止损单位是模块,不是整个项目。
③ 分批灰度。 60 多个国家分批放量,任何一批可回退。这一道在代码之外,保证前两道全失效时用户侧损失仍然有界。
事前担心的 vs 实际发生的
事前担心「AI 写不出能编译的代码」「大量幻觉 API」------都没发生。事前唯一猜中的是成本失控。
而事后真正的大坑,事前一个都没想到:代码能编译能跑但功能不完整且报告完成;测试与实现同源共享误解;移动靶带来的持续返工;工程师对「只 review 不写代码」的心理阻力。
这是最值得分享的一条经验:风险评估做得再细,事前清单和事后真实坑单的重合度可能很低。 所以止损机制的价值不在于「预判了什么」,而在于它对没预判到的事情同样有效------三道止损没有一道是针对上面四个坑设计的,但对它们同样管用。
2.4 决策记录(脱敏片段)
markdown
# ADR-00X:客户端框架迁移方案选型
## 决策
采用 AI Agent 流水线迁移至原生双端,不采用人工重写与机械转译。
## 前提条件(不满足则本决策不成立)
1. 原工程在迁移期间不冻结,全程可回退
2. 建立模块级状态账本,止损单位为模块
3. 基础层(网络/存储/设备通信/公共组件/共享模型)人工评审全覆盖
------业务层依赖自动门禁与一致性审计
4. 分批灰度,任一批次可独立回滚
## 已知代价
- 原工程持续演进带来的重复对齐成本
- Token 成本存在失控风险,需设预算监控
- 团队工作模式需从「写」转向「审」,存在适应期
## 复核点
首批模块完成后复核产出质量与成本;不达标则回退。
最重要的是**「前提条件」**那一节------它不是「我们打算这么做」,而是「如果做不到这四条,则迁移方案不可行」。 把前提写死比把结论写死有用得多:结论会被沉没成本绑架,前提不会。
三、迁移流水线设计
把一百万行代码交给 AI,难点从来不是「让模型写出一个页面」。难点是:让它连续做一千次,每一次的失败都可定位、可回滚、可重试,且一千次的产出合在一起仍然是一个能编译、能跑、架构没走样的工程。 为此我们做了大量的基建以及试验,
3.1 全景
三条原则贯穿全图:
- 每一环的失败都必须写进同一个文件账本,而不是留在模型的上下文里。 上下文会丢,文件不会。
- 每一层结束都必须有一次机器判定------不是「看起来对」,是编译器或测试给出的二值信号。
- 循环必须有退出条件。 没有退出条件的 Agent Loop 不是自动化,是烧钱。
3.2 模块切分:规模化的第一道生死线
切分粒度试过三种:按业务大模块(上下文必然溢出,质量断崖 )、按文件(切得太碎,跨文件业务规则被撕裂 )、按「页面 × 层」(最终方案)。
最终坐标是二维的:纵向按架构分层(models / data / domain / viewmodel / ui),横向按页面。一个任务 = 一个页面的一层。 它同时满足两个约束:单层实现通常几百行,稳稳落在上下文预算内;层与层之间有清晰的编译边界,可以插入机器校验点。
分层顺序本身就是拓扑排序。规则写死成一句话:未完成下层依赖,不得迁移上层。 这消灭了一整类失败模式------UI 层的 Agent 不需要猜某个 UseCase 的签名,因为它已经在文件系统里躺着了。
循环依赖不交给 AI 解:切分阶段就人工打断,把参与环的共享类型上提到独立公共模块,环变成树。这是架构决策。
上下文预算闸门
全流水线最不起眼、但救了我们最多次的设计。生成前先扫源码行数:
| 源码 LOC | 行动 |
|---|---|
| ≤ 1000 | 直接执行 |
| 1000 ~ 3000 | 提示偏大,建议先拆子模块,人工确认后继续 |
| > 3000 | 写入问题账本并停止,强制人工给出拆分策略 |
超过 3000 行硬迁的产物我们见过:最开始我们就是这么做的,将一整个模块,大概2W行代码交给AI迁移。提示词就是将XX模块迁移到Android项目。结果是:能编译,页面能显示,但中间三分之一的业务分支被模型「概括」掉了 。而AI不会告诉你它省略了什么。 闸门的唯一意义,是不给AI省略的机会。
3.3 五阶段逐环拆解
生成
Prompt 不是自由文本,是有固定槽位的结构。最关键的是真值来源优先级:
markdown
视觉/布局真值(高优先覆盖低优先):
1. 设计稿规格 → 2. 原工程实现截图 → 3. 页面级规格文档
→ 4. 模块级规格文档 → 5. 原工程源码(兜底)
业务逻辑、交互时序、网络参数真值:
始终以原工程源码为准,不接受任何降级
分两条线的原因:视觉可以被更好的设计稿覆盖,业务语义不行。一个把接口参数「优化」掉的迁移,编译能过、测试可能也能过,但线上会出事。写进规格,不指望模型自觉。
参考代码注入同样显式:不是把整个工程塞进上下文,而是给一张既有实现映射表 ,搜索顺序写死为「设计系统层 → 公共组件层 → 模块私有」。这一条把「重复造轮子」从概率问题变成规则问题。
编译校验
关键细节:编译目标不是固定的。 代码按层落到不同 Gradle 模块------模型层落公共模型模块,国际化落组件模块,路由注册落壳工程。每层校验目标必须跟随实际落点解析,跨多个模块时一律以壳工程全量编译兜底。
失败回喂也有讲究:原始编译输出动辄几百行,直接回喂等于污染上下文。只回喂三样------错误文件路径、错误行符号、编译器给出的期望类型。修不好就停,不允许模型基于半截信息猜。
用例回归
单测按 TDD 红绿重构,断言取自规格文档而非实现;Journey 覆盖「进页面 → 加载 → 交互 → 跳转」主链路。没有 Journey 的模块不算验收通过,只能标「待人工验收」。
回滚与重试
matlab
round = 1
loop:
verify → PASS? → 成功退出
收集未解决项 → 分类为 auto / human
if auto 为空 and human 非空: 停止,移交人工
fix(auto 集合)
if 本轮未解决项集合相对上一轮无任何收敛: 停止(零进展)
if fix 自判范围过大: 停止,建议重跑该层
round += 1
if round > max_rounds(默认 3): 停止,报告剩余项
五个退出条件里,「零进展护栏」最重要。没有它,模型会在两个互斥修复方案之间震荡------A 改完 B 挂,B 改完 A 挂,轮次上限内一直烧 token 而净收益为零。判据很简单:本轮未解决项集合相比上一轮在数量和条目上都没减少,判零进展,立即停。
三次自动修复不成功即转人工。这个数字是试出来的,第 4 次及以后的成功率低到不值得为它付费。
失败任务的路由判据:视觉偏差、分支缺失、接口参数差异、组件错用、国际化缺失、导航差异 → 自动修复;标注「待确认」的、命中预算闸门的、需要改测试断言才能通过的、修复范围大到需重跑整层的 → 人工队列。注意最后两类坚决不给 AI 自己处理------前者是在放宽验收标准,后者是在悄悄重构,两件事都必须由人拍板。
3.4 Harness:把状态写进 git 里的 markdown
流水线要跑起来还差一层调度。我们没写庞大的调度服务,而是把状态写进版本控制的文件里------整套方案里最「土」也最有效的决定。
每个模块目录下一组固定文件:index.md(模块概览)、{page}.md(页面级完整规格)、plan.md / tasks.md(方案与任务)、pages.md(页面 × 五层状态矩阵)、migrated.md(跨页面去重的唯一依据 )、issues.md(异常与卡点)。
状态机就藏在 pages.md 的矩阵里,每格是 [ ] / [x] / [!]。调度逻辑退化成一句话------找到第一个未完成的格子,为它起一个 Subagent。
三个收益当初没完全预料到:状态可 diff、可 review、可回滚 (出问题 git log 就能定位是哪轮引入的);人和 AI 读同一份状态 (不需要额外做进度看板);跨会话续跑(上下文丢了不要紧,账本还在------这是能跑半年而不是三天的前提)。
并发控制的规则反而是「少即是多」:同一层内的页面之间禁止并行。 我们试过并行,代价是两个 Agent 同时判断「这个组件不存在,我建一个」。去重账本只有在串行时才可信。
3.5 实际进度曲线
冲刺期每周净增行数(git log --numstat,新增 − 删除):
| 周次 | W1 | W2 | W3 | W4 | W5 | W6 | W7 | W8 | W9 |
|---|---|---|---|---|---|---|---|---|---|
| 端 A | 32,808 | 80,864 | 40,496 | 47,027 | 85,412 | 73,269 | 56,456 | 50,950 | 20,181 |
| 端 B | 10,446 | 87,300 | 62,158 | 39,618 | 81,606 | 90,298 | 43,129 | 25,891 | 9,036 |
曲线不是线性爬升,而是在第 2 周就冲到峰值。 这与人海战术完全不同------人力项目的产能受限于新人熟悉代码库的速度,前期必然平缓。AI 流水线的瓶颈在别处:一旦规格文档和 Skill 就位,产出立刻拉满。前期真正花时间的是建流水线,不是写代码。
W8 之后的下降不是「没活了」,是重心从生成转向了收敛。 同期删除行占比显著上升------那是 verify-fix 循环在工作。这条曲线的后半段,才是决定项目能不能上线的部分。
3.6 两个阶段的产能:加人为什么是超线性的
| 阶段 | 时间 | 人力 | 人日 | git 净增 | 人均日产出 |
|---|---|---|---|---|---|
| 地基期 | 3--6 月 | 3 人 × 4 月(1.0 人年) | 220 | 380,458 | 1,729 行/人日 |
| 冲刺期 | 7--8 月 | 9 人 × 2 月(1.5 人年) | 330 | 934,725 | 2,832 行/人日 |
人数增加到 3 倍的同时,人均产能又提升了 64%。 Brooks 定律说的正好相反------往延期项目里加人只会让它更晚。为什么这里反过来了?
① 新人要熟悉的不是代码库,是流水线。 冲刺期加进来的 6 个人不需要读懂已有的几十万行代码,只需要读懂「怎么起一个迁移任务」。上手成本从「读代码库」降到了「读一份工作流」。
② 沟通成本被状态账本吸收了。 谁在做什么、这块归谁、这个组件有没有人建过------这些问题都由文件回答。人不需要问人。
③ 地基期的产出被后置兑现了。 那 3 个人写的不只是 38 万行代码,还有 85 个 Skill、7 条静态规则、8 个门禁脚本和全部公共组件层。这部分投入的收益全部计入了冲刺期的产能。
所以冲刺期那个数字是「借」了地基期的力,合起来算才公允。反过来这是最重要的一条实践含义:
如果你打算用 AI 做大规模迁移,前期一定要忍住不加人。 3 个人把流水线建对,比 9 个人一起摸索快得多------因为流水线是乘数,不是加数。
3.7 一次通过率:测不了,但有更好的替代品
「任务一次通过率」这个指标我们测不出来 ,原因很结构性:自动修复循环跑在提交之前。 一个任务可能在Agent中经历 3 轮 verify-fix,但最终只产生一次 git 提交。
不过值得先问:这个指标真的重要吗? 循环内的重试是廉价的 (机器跑,成本是 token);提交之后的返工是昂贵的------它意味着六层门禁全放行了,问题却还是漏进了代码库,要人来发现、定位、修。
所以我们改测后者。取冲刺期内由 Feat 提交新增 的源文件,看是否被随后的 Fix 提交触碰:
| 观察窗口 | 端 A(2,687 个新增文件) | 端 B(1,612 个新增文件) |
|---|---|---|
| 7 天内被返工 | 6.1% | 14.7% |
| 30 天内被返工 | 16.4% | 34.6% |
| 30 天零返工率 | 83.6% | 65.4% |
端 A 首次返工间隔中位数 12 天。
① 八成以上的产出提交后一个月内没被返工。 考虑到这是完全由 AI 生成的代码,这个数字比事前预期高。
② 两端差了一倍 :两端流水线成熟度不同(Skill 库先在端 A 打磨)、团队构成不同。我们没有做归因,因为这不是我们的重点------但它提示了一件事:流水线的收益不会自动跨端复制,每一端都要重新打磨。
③ 返工中位数 12 天,说明问题不是当场发现的。 编译/测试能抓的问题会集中在 1--2 天内。12 天意味着大部分返工来自后续验收审计、联调、或移动靶效应------这三类恰恰都是自动门禁抓不到的。
边界:它不是一次通过率 (循环内重试不可见,真实的一次通过比例只会更低);Fix 提交不全是「AI 写错了」 (需求变更、原工程同期改动都会记成 Fix),它是返工率不是缺陷率。
四、上下文工程:Skills 与 MCP
第三章讲流水线怎么跑,这一章讲:跑之前,你得先把「这个项目该怎么写代码」翻译成机器能执行的东西。
多数人对 AI 编程的投入停在 Prompt 上------写得更长、更细、更严厉。我们的实测结论是:同一个模型,配一套结构化的项目上下文资产,和裸 Prompt 比差距大到不像同一个工具。 而且这份投入是资产会复利,Prompt 是消耗品。
项目收尾时这套资产的规模:85 个 Skill、28 条自定义命令、9 份工程规则、7 条自研静态检查规则、8 个提交门禁脚本。
4.1 四层结构
分层的判据只有一个问题:这条知识的命中率有多高? 每次都要用的进 L1 常驻;只在特定任务用的进 L2 触发式加载(省 token 的主战场);该现查的进 L3;根本不需要模型知道、机器自己能拦的进 L4。
第四层最容易被忽略,却是性价比最高的一层。 能用一条静态检查规则拦住的错误,不要写进 Prompt 让模型记住------前者确定性,后者概率性,而且静态检查不花钱。
4.2 Skill 设计
85 个 Skill 分两类。流程型 对应流水线环节,14 个(compare / plan / implement / models / data / domain / viewmodel / ui / verify / fix / verify-fix-loop...)。它们高度同构,所以抽了一份共享基线,每个 Skill 开头一句「先读基线」。
这不是洁癖,是被逼的。早期这些内容在 14 个文件里各抄一份,很快就漂移了:A Skill 说「同层内页面禁止并行」,B Skill 忘了同步,于是 B 环节开始出现重复组件。Skill 之间的规范漂移,症状和团队里两个人理解不一致一模一样,且更难发现------因为没人会抱怨。
领域型 对应本工程的具体约定,内容特征是大量的「禁止」和「唯一入口」:
业务侧唯一下拉刷新组件是 X;分页列表走 Y;已废弃的 W 禁止新用;feature 模块禁止直连底层实现 V。
为什么写得这么绝对?因为模型天然倾向于「造一个新的」------它在别处见过一万种写法,而你这个工程里「正确的那一种」,只有你告诉它。留下任何选择空间,长期跑下来就会得到 N 种实现。
粒度的甜点区是「一个页面的一层」,正好和任务粒度对齐 ------这不是巧合,任务粒度和 Skill 粒度必须同构,否则总有一方要在运行时做拼接。
4.3 一个真实 Skill 的完整定义(脱敏)
markdown
---
name: Migrate UI Layer To Compose
description: 按 Page 串行迁移原工程 UI 到 Compose,先查规格文档为真值,
连接已迁移 ViewModel 并对齐交互行为(每 Page 一 Subagent)。
metadata:
triggers:
files: ['migrate/*/pages.md', '**/*Screen.kt']
keywords: [migrate ui, UI 迁移, widget to composable]
---
# Migrate UI Layer To Compose
先读 [migration-base](../references/migration-base.md)。
读取目标模块的 `pages.md`,**Page 间串行,每 Page 一 Subagent**。
**依赖 models / data / domain / viewmodel 四层已完成。**
## 真值来源优先级([强制])
视觉/布局按以下优先级取真值(高优先覆盖低优先);
**业务逻辑、交互时序、网络参数始终以原工程源码为准**:
0a. 设计稿 After 规格(plan.md §11 对应条目)------最高优先
0b. 原工程实现截图(screenshots/{page}/*.png)------第二优先
1. {page}.md 的「布局结构」「UiState 分支」「视觉度量」节
2. index.md 的相关节
3. 仅在上述均未覆盖时,才回落读取原工程源码
## Subagent 任务(针对当前 Page)
1. 按优先级读取布局伪代码、状态分支、视觉度量(颜色/字号/间距/圆角)
2. 分析结构:布局 / AppBar / 列表 / Tab / 弹窗 / 表单 /
空·错·Loading 态 / 下拉刷新 / 分页 / 动画 / 路由跳转
3. **先搜索本工程是否已有对应实现**:有则对齐扩展,无则新建
4. 按技术映射表转换(列表→LazyColumn、异步 Builder→
collectAsStateWithLifecycle、Navigator→Nav3 ...)
5. 连接 ViewModel:观察 uiState、调用事件方法、消费一次性 Event
6. 对齐交互:初始加载 / 刷新 / 分页 / 错误重试 / 表单校验 /
禁用态 / 空态 / 导航结果回传
7. 编译验证,回写状态账本(migrated / pages / tasks / issues)
## 视觉与工程约定
**[强制]** 颜色/字号/字重/间距/圆角须与视觉真值严格一致,经主题 token 落地;
**在实现处注释所用视觉真值来源与原始取值。**
## Anti-Patterns
- **No 业务逻辑入 Composable**:业务逻辑在 ViewModel / Domain
- **No 网络请求入 Composable**
- **No 擅改技术栈**:不确定记入 issues.md,不要自己发明
- **No 魔法数字**:颜色/尺寸/间距必须走 token
- **No 跨模块混写**:仅读写当前模块的状态文件
四个设计点:① 触发器是文件路径 + 关键词 ,模型碰到 *Screen.kt 会自动加载,人不需要记得提醒它。② 真值优先级是核心,不是流程步骤 ------步骤是模型本来就会的,「信息冲突时听谁的」才是它不知道的 。③ 第 3 步是硬约束:先搜索,再新建 ------这一条挡住的重复实现比后面所有 review 加起来都多。④ Anti-Patterns 用「No X」祈使句而非「应该 Y」------实测否定式约束的遵守率明显高于肯定式建议,大概是因为否定式可以被模型当成校验项自查。
4.4 MCP:把「查」和「记」分开
常见的错误直觉是:上下文窗口越大,就越应该把代码塞进去。我们的做法相反------塞进上下文的只有「规格」,代码本身一律现查。
挂载四类工具:设计稿读取(UI 迁移时直接拿设计规格原始值,而不是让模型「看图估计」)、内部文档网关(排期表与实验开关清单直接进流水线,避免人工转录丢字段)、代码检索与符号跳转(回答「这个组件已经存在吗」)、编译测试与 Git(让模型自己拿二值反馈)。
三个具体原因:代码会变,上下文里的快照不会 (模型早期读进来的文件可能在第 20 步已被它自己改过);注意力是稀缺的 (20 万 token 上下文里,第 3 条约束的权重会被稀释到可忽略);成本(塞进去的 token 每轮重新计费,查一次只计费一次)。
设计稿这一路特别值得说:UI 迁移最贵的不是「写不出来」,是**「写出来了但差 2dp」**------人 review 要拿尺子量,返工一轮要重走完整条流水线。接成可查询工具后,视觉度量从「估计」变成「读数」。
4.5 把编码规范变成机器约束
规范只是「告诉」,门禁才是「保证」。 我们把反复被违反的几条下沉成了机器检查。
7 条自研静态检查规则,例如:
kotlin
/**
* 检测 ViewModel 把可变状态容器暴露为 public 属性,破坏单向数据流。
* 违规:val uiState = MutableStateFlow(FooUiState()) // ❌
* 合规:private val _uiState = MutableStateFlow(FooUiState())
* val uiState: StateFlow<FooUiState> = _uiState.asStateFlow() // ✅
*/
另一条更有意思:跨子域直接 import 检测 。大 feature 内部有五个子域,规则强制它们只能通过平台层接口通信。这是典型的「人 review 时看不出来、但半年后会让模块无法拆分」的问题------它不产生 bug,只产生债 。AI 尤其容易犯:它看到一个类名能解决当前问题就直接 import,它没有「这会让架构劣化」的直觉。
8 个提交门禁脚本覆盖静态检查表达不了的项目特有陷阱:跨模块同名资源覆盖、矢量路径超长导致打包截断、导航返回后滚动位置丢失、间距组件误用外部修饰符、页面埋点缺失、禁止某类持久化 API、新增文案必须同步英文源。
每一条背后都是一次真实线上问题或一次返工。写进规范,模型遵守率大概八成;写成脚本,遵守率一百。
28 条自定义命令的价值不在省几个字,在消除调用方差------同一个环节五个人五种说法,模型就有五种行为。这和十年前把构建脚本固化成 CI 任务是同一件事。
4.6 Skills 到底有多大效果:一次基于 git 的自然实验
我们没做随机对照实验------项目在跑,没人会为了发文章把一半模块交给裸 Prompt。但 git 里有个天然断点 :通用工程 Skill 与全局规则从 2026-03-02 开工就有,迁移专用 Skill 库 2026-06-16 才落地。以此为界,前后做的是同一件事,团队没变,模型没换。
先排除两个看起来诱人、实际是伪信号的指标:
| 伪指标 | 实测 | 为什么不能用 |
|---|---|---|
Fix 类提交占比 |
11.3% → 52.4% | 流水线本身就是「生成→验收→修复」,Fix 提交是 fix 循环的设计产物 |
| 代码 churn(删除/新增) | 30.7% → 35.9% | 同上。后期重心转向收敛,churn 上升恰恰是审计在工作 |
这两个指标方向都「变差」了,但它们衡量的不是质量。 把它们当成「AI 让代码质量下降」的证据,是这类分析最常见的误读。
真正可用的是违反已写入上下文的具体规则的密度 ,按每万行新增归一化,且要挑没有机器门禁覆盖的规则------有门禁兜底就无法区分是上下文还是门禁起了作用。
| 指标 | Skill 库前 | Skill 库后 | 变化 | 有机器门禁 |
|---|---|---|---|---|
| 期内新增 Kotlin 行 | 370,427 | 786,355 | ×2.1 | --- |
| 硬编码颜色字面量 | 13.90 / 万行 | 5.41 / 万行 | −61% | 无 |
| 越过 app 内主题直读系统深色 | 0.97 / 万行 | 0.01 / 万行 | −99% | 无 |
| 无反馈的裸点击修饰符 | 4.58 / 万行 | 2.73 / 万行 | −40% | 无 |
| 裸日志调用 | 2.26 / 万行 | 0.47 / 万行 | −79% | 部分 |
| 新建组件落在模块私有的占比 | 69.2% | 44.0% | −25pp | 无 |
| 共享组件新建密度 | 2.70 / 万行 | 4.93 / 万行 | +83% | 无 |
加粗的四项完全没有机器门禁覆盖,改善只能归因于上下文资产本身。
① 最大的收益在组件复用。 私有目录占比从 69.2% 降到 44.0%,共享组件新建密度提升 83%。翻译成人话:Skill 落地前 AI 的默认动作是「在手边造一个」,落地后变成了「先去公共层找,找不到就建在公共层」。 这正是裸 Prompt 最缺的那类知识------它不是通用编程知识,是「这个工程里已经有什么」。这类知识只能靠工程自己提供,换模型解决不了。
② 项目特有规则的遵守是断崖式的。 「越过 app 内主题直读系统深色」几乎归零(0.97 → 0.01)。这条尤其说明问题:通用最佳实践里读系统深色完全正确,只有在这个工程里(因为 app 内有独立主题设置)它才是错的。 模型不可能自己知道,写进上下文它就再没犯过。
③ 绝对值都不是零,别夸大。 落地后硬编码颜色仍有 5.41/万行,私有组件仍占 44%。上下文工程把违规率压下一个数量级,但压不到零------剩下的那部分才是人工评审和机器门禁存在的意义。
边界 :这不是随机对照实验,同期还有其他变量在动(团队经验积累、公共组件库变厚);它证明的是「完整上下文资产 vs 只有通用规则」,真实的裸 Prompt 基线只会比表中的「前」更差。
五、质量保障:AI 生成代码的准入标准
每次讲这个项目,第一个问题都一样:百万行 AI 写的代码,你怎么敢上线?
先给答案:我们不「敢」,我们是把「敢不敢」拆成了一串不需要勇气的判定。 每一道要么是机器给的二值信号,要么是一条写死的清单。
5.1 六层门禁
markdown
AI 产出 → ① 编译 → ② 静态检查(含 7 条自研规则)→ ③ 提交门禁脚本(8 项)
→ ④ 单测 + Journey → ⑤ 八维度一致性审计 → ⑥ 人工评审 → ⑦ 分批灰度
任一层失败 → 写入问题账本 → 进 fix 循环 → 回到 ①
前五层全自动,不消耗人的时间。这个比例是整件事能成立的前提------如果人工评审必须覆盖全部产出,流水线的产能上限就等于评审产能。而第六层本身也被大幅收缩了(下一节)。
前三层拦的不是 bug,是**「会变成债的正确代码」**。一段把可变状态暴露成 public 的 ViewModel,编译过、测试过、功能正常------它只是让这个类在未来任何改动中都可能被外部直接改状态。人 review 看 30 个文件时会发现,看 300 个时不会。规则会。
第四层有个必须承认的结构性弱点(第六章展开):当测试用例和实现出自同一个模型时,它们会共享同一套误解。
第五层「八维度一致性审计」是最有效的一层。 它不看代码写得好不好,只回答:它和原工程一致吗? 八个维度:业务逻辑(状态分支、边界条件、显隐规则)、网络层(路径、参数名、默认值、分页字段)、视觉(颜色/字号/字重/间距/圆角实际取值)、路由深链、国际化(文案 key 与 14 语言覆盖)、资源、埋点、权限与平台差异。
关键设计:期望值必须写清「规格文档路径 + 章节 + 取值」,不允许只写「不一致」。 这样每条不一致都可回溯,人 review 不需要重读一遍原工程。
还有条流程约束:审计通过之前,不允许把模块标记为「已完成」。 早期让 Journey 一绿就标完成,结果被随后的审计反复推翻------测试通过只证明主链路能跑,不证明业务分支完整。
5.2 人工评审的真实覆盖面:只审基础层
这一节最容易被误解,所以先把实际做法直说:我们没有按「高危业务」划分评审范围,而是按「架构层次」。人工评审集中在基础层,业务层没有做系统性人工评审。
| 层 | 生产代码行数 | 占比 | 人工评审 |
|---|---|---|---|
core/*(网络、存储、设备通信、UI 基础) |
59,977 | 10.2% | ✅ 覆盖 |
common/*(共享模型、公共组件、导航、领域层) |
112,339 | 19.1% | ✅ 基建部分覆盖 |
app/*(壳层、路由、深链) |
31,141 | 5.3% | 部分覆盖 |
feature/*(全部业务模块) |
384,071 | 65.2% | ❌ 未做系统性人工评审 |
约三分之二的产出代码,从来没有人逐行看过。 它们通过的是六道机器判定,以及最后的分批灰度。
为什么这么切
因为评审的价值不取决于代码有多重要,而取决于错误会被放大多少倍。
一个基础层组件会被几十个业务模块引用,它错一次,错误就被复制几十次 ,而且每次长得都不一样,后面极难统一修。基础层还有个特点:它决定了后续所有 AI 生成的形状------模型的默认动作是「先去公共层找」,如果公共层本身是歪的,后面所有引用它的代码都会跟着歪。
业务层则相反:影响面就是这个页面,发现渠道多,修复代价有界,还有三道止损兜着。所以评审资源全投在放大系数最高的地方。
这个取舍的代价必须说清楚
这不是一个可以无脑照抄的做法。 它成立依赖四个前提,缺一条我都不会这么干:
- 业务层有外部真值可对照 ------迁移任务独有的优势。如果是从零开发的新功能,业务层没有真值,这套就不成立,必须人审。
- 止损机制完整(模块级回退 + 分批灰度 + 原工程不冻结)。
- 基础层足够厚------业务层大量复用已评审组件,实际「完全没被人看过的新逻辑」比 65% 小。
- 接受一致性欠账 ------业务层确实留下了大量「部分一致」条目(举报流程、部分埋点、少数弹窗交互)。这些就是不做业务层人工评审换来的账单。
第四条尤其要诚实:我们不是零成本地省掉了三分之二的评审,我们是用一部分业务一致性换了交付速度。
仍然不允许 AI 自主决策的边界
评审范围可以收缩,但这几条无论哪一层都不放开:架构变更 (新增模块、调整分层依赖);协议与数据结构变更 (包括「顺手」改个字段名);放宽验收标准 (修复方案是「改测试断言让它过」的一律转人工------这是所有质量体系失效的通用路径 );删除既有实现(AI 判断死代码的准确率远低于它表现出的自信)。
这四条约束的是「决策类型」,不是「代码位置」------业务层不做逐行评审,不等于业务层可以自己改协议。
5.3 十年前我给一千人写规范,今天我给 AI 写
2020 年,我在H公司带XX团队34 个人,那段时间我做的一件影响面超出团队的事,是编写《H公司终端 App 稳定性检视标准》V1.0------把稳定性问题归纳成 6 大类 32 小类的问题模型,作为应用产品部 1000 多人的检视依据。
那份文档的方法论,和这次为 AI 写准入标准是同一件事:你没法审查每一行代码,所以必须把「什么是错的」定义得足够结构化,让别人能自己判断。
区别只在对象。人会疲劳、会走捷径、会因为赶版本跳过检视------那份标准需要评审会、需要有人盯。模型不疲劳,但它没有羞耻心:一个人跳过检视会心虚,模型不会;它省略了三分之一的业务分支,会用完全一样的语气告诉你「迁移完成」。
所以重心不同:当年重在分类的完备性 (人会举一反三),现在重在判定的可机器化(凡是能变成检查脚本的绝不留在文档里靠自觉)。
有一点十年没变:写标准的人,必须是那个亲手踩过这些坑的人。 准入标准不能外包,也不能让 AI 自己给自己定。
5.4 AI 代码的典型错误模式
下表基于 16 份模块问题账本 + 28 份验收报告(约 6,400 行审计记录) 的实测分布。口径说明:统计的是问题类型提及频次,不是缺陷绝对数------用它判断相对占比和优先级可靠,当缺陷总数不行。
| 排序 | 错误模式 | 频次 | 典型表现 | 主要拦截环节 |
|---|---|---|---|---|
| 1 | 功能未实现但接口占位 | 最高 | 回调声明了、参数传下去了、UI 也画了,但从未被调用 | 八维度审计 / 人工 |
| 2 | 业务分支缺失 | 高 | 四态判定被简化成二态;前置校验丢失 | 八维度审计 |
| 3 | 导航与路由不一致 | 高 | 路径语义偏离;返回结果无人消费 | 审计 / Journey |
| 4 | 接口参数与默认值差异 | 高 | 参数名对了但默认值不同;分页字段少传 | 审计 / 人工 |
| 5 | 埋点整体缺失 | 高 | 页面功能完整,但曝光/点击事件一个没接 | 审计(专项维度) |
| 6 | 视觉度量偏差 | 中 | 间距差几 dp;硬编码魔法值绕过主题 | 静态检查 + 审计 |
| 7 | 重复造轮子 | 中 | 已有公共组件不复用,模块内平行再造 | Skill 约束 + 人工 |
| 8 | 国际化不完整 | 中 | 只补英文中文,其余 12 语言缺失 | 提交门禁脚本 |
四个反直觉的观察:
① 「幻觉 API」几乎没出现。 模型调用不存在方法的情况极少------因为它有代码检索工具,且分层顺序保证依赖已在文件系统里。幻觉是上下文缺失的症状,不是模型的固有属性。
② 头号问题不是「写错了」,是「没写完但看起来写完了」。 排名第一的「接口占位、逻辑空转」编译能过、页面能显示、截图看起来完全正确。它是所有自动化门禁的盲区 ,只有逐条对交互的八维度审计能抓到。这解释了为什么我们把审计做得那么重:AI 生成代码的主要风险,不是它写出错误的东西,是它写出不完整的东西却报告完成。
③ 埋点值得单列一个维度。 它不影响任何功能验证------编译过、测试过、人工点一遍也发现不了,只有上线后发现数据没了。
④ 静默吞异常这类经典坏味道被静态检查挡在了很前面,所以在审计语料里频次很低。这是「能用规则拦就别靠 review」的正面例证。
问题状态分布 (实测):已修复 40 条,命中预算闸门 23 条,标注「待确认」21 条,确认差异待排期 14 条。「待确认」这一类最有价值 ------它是 AI 主动举手说「规格文档和原工程对不上,我不敢自己定」。一个不会举手的 Agent 才是危险的。
5.5 门禁到底有没有用:逐条度量
「各层门禁的拦截率」这个指标我们给不出来------它需要 CI 的拒绝日志,而流水线没有埋这个统计。
但有个更能说明问题、且可从 git 复算 的替代口径:每条门禁上线前后,它所针对的模式的引入密度变化。 门禁的价值本来就不在「拦了多少次」,而在「上线之后这类问题还发不发生」。
| 门禁 | 上线日 | 目标模式 | 上线前 | 上线后 | 变化 | 强度 |
|---|---|---|---|---|---|---|
| 间距组件误用外部修饰符 | 08-04 | Spacer(modifier = modifier...) |
0.94/万行 | 0.13/万行 | −86% | 拦截 |
| 禁用某类持久化 API | 08-11 | 直接调用平台偏好存储 | 0.96/万行 | 0.61/万行 | −36% | 拦截 |
| 页面曝光埋点 | 08-07 | 新建页面接入 PV | 5 个月共 130 处 | 3 周共 248 处 | 存量补齐 | 拦截 |
| 导航返回滚动位置恢复 | 08-03 | 安全 API 采纳率 | 0% | 19.2% | +19pp | 仅提示 |
| ViewModel 暴露可变状态 | 06-23 | public 可变流 | ~0 | ~0 | 预防性 | 编译失败 |
① 强门禁立竿见影。 间距组件误用降了 86%,那是一条二十行的 shell 脚本。投入产出比高到不讲道理。
② 「仅提示」和「拦截」的差距比想象中大。 滚动位置恢复那条是唯一设计成「提示但不阻断」的,采纳率只从 0% 爬到 19.2%------而同期强门禁都是 80% 以上的降幅。一条不阻断的建议,实际遵守率大概是一条阻断规则的四分之一。无论执行者是人还是模型。
③ 门禁上线的第一效果是存量补齐,不是增量约束。 埋点脚本上线前五个月累计新增 130 处,上线后三周新增 248 处------因为脚本开始拦截,逼着大家在改动既有页面时把历史欠账一起还了。 我们加它的动机是「防止新页面漏埋点」,实际拿到的是存量覆盖率在三周内被补齐。
④ 有些规则是预防性的,事前事后都接近零,这不代表它没用。 它的价值是把「团队现在懂」变成「永远不会退化」 ------尤其在 AI 参与生成、人员会流动的前提下。别用「没拦到东西」去否定一条规则。
5.6 线上验证
产品覆盖 60 多个国家和地区,分批放量,任何一批出问题都可回退。线上结果:Crash 率长期保持在 0.05% 以下(Firebase 统计)。
这是对「百万行 AI 代码敢不敢上线」最直接的回答。 如果生成质量不可控,最先炸的就是这个指标------它不撒谎,也不受任何叙事影响。
但边界要说清楚:0.05% 是稳定性指标,它证明代码不崩,不证明业务行为完全正确。 §5.4 的头号缺陷模式恰恰是 Crash 率捕捉不到的那一类。稳定性达标 ≠ 一致性达标。
六、踩过的坑
没有失败清单的复盘不可信。 七个坑,每个都真实造成了返工,其中三个至今仍有残留。
6.1 局部最优不等于全局最优
AI见过世界上大部分的代码,所有的优秀实践,但是这些优秀实践,都是特定情况下的特定优化,是局部最优,而非全局最优 。 举个例子:它会没有任何心理负担的开线程做并行处理,来提升加载速率等,这在大多数情况是好的。但是当它同时开几百个线程,你得到的不是效率,而是Crash(native分配打满)。每一个AI都觉得自己做的并行处理,能够提升效率,而且单独拿出来看代码都是没有问题的,但是结合在一起就是不稳定的错误。 所以好的架构设计,是AI不可替代的。好的上下文约束,能够避免这种情况。
6.2 切分粒度选错
预算闸门不是设计出来的,是被打出来的。早期按业务模块切分,产出是「能编译、能显示、但业务分支被概括掉三分之一」------而且这类损失不可见,模型不会说「我省略了 12 个分支」,它说「迁移完成」。
教训 :上下文预算不是「模型能塞多少」,是「模型能在多少内容里保持全部约束不衰减」。后者远小于前者,而且它不会报错,只会悄悄降质。
6.3 移动靶:你在迁的东西一直在变
选择原工程不冻结是正确的止损决策,代价现在可以量化:迁移期间原工程有 705 个 UI 源文件发生过改动,改动最频繁的单个文件被修改了 122 次。
一个模块你在 5 月按当时的源码迁完、验收通过;到 7 月,原工程那部分已经改了二十几次。你的「一致」是对着一个旧快照做的。
应对是把一致性状态表当成会过期的缓存而不是永久结论:标记为一致时同时记录基线,原工程该区域再次变更时状态自动降级。
教训 :双轨并行期的真实成本不是「多维护一套代码」,是「迁移结论会过期」。 排期必须为「重新对齐」留预算------这部分工作量在任何迁移方案的初始估算里都不会出现。
6.4 能编译、能跑,但架构走样
最难拦的一类,因为每一层自动门禁都会放行 。仓库里现在有 57 个超过 1000 行的 Kotlin 文件,最长的 ViewModel 有 7,290 行。它编译通过、测试通过、功能正确、线上没问题------它只是不该长成这样。
为什么?因为模型没有「这个文件太大了」的痛感 。人写到 800 行会难受,会想拆;模型不会。而每次单独的追加都是合理的:这个分支要加、这个状态要处理。没有任何一次改动是错的,错的是它们的累积。
后来由人发起「无行为变更的拆分」。这类工作 AI 做得不错,但必须由人发起------发现问题需要「这不对劲」的直觉,那正是模型缺的。
教训 :自动门禁能保证正确性,保证不了合理性。 架构劣化不产生 bug,只产生债,所以它能穿透所有以正确性为判据的检查。
6.5 测试用例与实现同源
我们让 AI 按 TDD 生成测试:先写测试,再写实现。听起来很安全。但当测试和实现出自同一个模型、读同一份规格 时,它们会共享同一套误解。
模型把四态判定理解成两态,于是测试只断言两态,实现也只实现两态------测试全绿,行为错误。 这时的绿灯不仅无用而且有害:它给了一个「已验证」的错觉。
缓解两条:断言必须来自规格文档,不能来自实现 ;八维度审计不看测试,只对原实现 ------这是真正兜底的一层,它的验证真值在模型之外。
教训 :AI 生成的测试,验证的是「实现符合模型的理解」,不是「实现符合需求」。 想让测试有意义,验证信号必须来自模型之外------原实现、真人写的规格、线上数据都行,唯独不能是同一个模型的另一次输出。
6.6 跨模块不一致
同一个业务概念,在 A 模块被建模成一种形状,在 B 模块被建模成另一种。两边都对,合起来是两套并行模型。根因是上下文的天然边界。
三条应对:共享类型强制上提、去重账本作为唯一事实源、同层内禁止并行。第三条最反直觉------放弃并行换取一致性。
教训 :规模化 AI 生成的一致性,靠的不是更大的上下文窗口,是更严格的串行化和更早的共享抽取。
6.7 团队心态:从「写」到「审」
和技术无关,但差点是最大的阻塞项。工程师抵触「只 review 不写代码」,理由不是懒,恰恰相反------是价值感和成长焦虑:「我在这个项目里到底做了什么?」「一年只做 review,技术会不会废掉?」「看别人的代码比自己写累多了,还没成就感。」
第三条是真的:审代码比写代码更累。 写代码是发散,你只需想出一条可行路径;审代码是收敛,你要想遍所有它可能错的地方。
我们做了三件事:把「定标准」明确为工程师的产出 (写一条门禁脚本和写功能代码同等计入产出,而且影响面大得多 ------一条二十行的脚本让某类问题下降 86%,杠杆比手写十个页面大);基础层由人主导 ;公开分享每个人建设的上下文资产。
坦白说第一条和第三条有效;第二条有实质意义,但它安抚不了那些整段时间都在做业务层的人 。这个转变长期看是不可逆的,管理者需要主动重新定义什么算贡献,而不是指望大家自己想通。
6.8 成本:七万美元买到了什么
这次迁移的 AI token 总成本约为 7 万美元。
| 口径 | 数值 |
|---|---|
| Token 总成本 | 约 $70,000 |
| 每千行成品代码 | $62 |
| 每节省 1 个人年的 token 支出 | 约 $1,440 |
最后一行最值得公开,因为它不依赖任何薪资假设。 人工基线约 51 人年,实际 2.5 人年,净省 48.6 人年。读者可以直接拿自己团队的全负荷成本去除:
| 若人力全负荷成本 | 节省的人力成本 | token 占比 |
|---|---|---|
| $30,000 / 人年 | $1,458,000 | 4.8% |
| $60,000 / 人年 | $2,916,000 | 2.4% |
| $120,000 / 人年 | $5,832,000 | 1.2% |
在任何合理的假设区间里,token 成本都在个位数百分比。 这意味着:在这类任务上,为了省 token 而牺牲产出质量是明显不划算的交易。 多跑一轮验证、多注入一份规格、多做一次审计------这些开销相对于它们防住的返工,便宜得离谱。
但 7 万美元是装了护栏之后 的结果。没有护栏的话,钱主要烧在两个地方,都不是生成 :无退出条件的循环 (零进展护栏就是为此而设,模型会在互斥方案间震荡,一直烧钱而净收益为零);把代码塞进上下文而不是查询(塞进去的 token 每轮重新计费)。
总数拿到了,但结构没拿到。 三个更有价值的拆分我们没采集:输入/输出分列、生成 vs 修复循环的成本占比 、各阶段成本分布。第二项最值得后来者采集 ------几乎没人公开过「修复循环占了多少钱」,而一条好的流水线,钱应该花在生成上,不是花在补救上。
表:失败模式清单
| # | 现象 | 根因 | 解法 | 可自动化拦截 |
|---|---|---|---|---|
| 1 | 大模块产出漏业务分支且报告完成 | 上下文预算超限 | LOC 预算闸门 | ✅(命中 23 次) |
| 2 | 已验收模块与原工程重新分歧 | 原工程并行演进(705 文件被改) | 一致性状态视为会过期的缓存 | ⚠️ 部分 |
| 3 | 超长文件、职责堆积(57 个 >1000 行) | 模型无「文件太大」的痛感 | 人工发起无行为变更拆分 | ❌ 难(判据是合理性不是正确性) |
| 4 | 测试全绿但行为错误 | 测试与实现同源 | 断言取自规格;审计真值在模型之外 | ⚠️ 部分 |
| 5 | 跨模块并行建模同一概念 | Agent 间上下文不共享 | 共享类型上提 + 去重账本 + 串行 | ✅ |
| 6 | 工程师抵触「只审不写」 | 价值感与成长焦虑 | 重新定义产出:定标准 = 交付物 | ❌ 不可自动化 |
| 7 | Token 成本失控 | 循环无退出条件 | 零进展护栏 + 轮次上限 | ✅ |
八个坑里,三个无法自动化拦截------二个是架构合理性,一个是人。 这恰好也是这次迁移中人最不可替代的两个位置。
七、方法论沉淀
工具会过时,模型会换代,Skill 的写法一年后可能就不适用了。下面七条是我认为换个项目、换个技术栈仍然成立的部分。
一、AI 能规模化的前提,是任务自带客观验证信号。 编译器和测试是最好的 Reward------免费、即时、零噪声,且不会被模型的措辞说服。判断一个任务适不适合规模化交给 AI,先问:「做对了没有」这个问题,能不能不靠人来回答? 不能,那么无论模型多强,验证都会成为瓶颈。
二、上下文工程的投入产出比高于换一个更强的模型。 §4.6 那组数据是直接证据:模型没换 的前提下,硬编码颜色降 61%、组件复用率显著提升、某条项目特有规则的违反率从 0.97/万行降到 0.01。原因很简单:模型缺的不是通用编程能力,是**「这个工程里已经有什么」**。这类知识只能由工程自己提供,换模型解决不了。
三、规模化的瓶颈不在生成,在验证。 整条流水线的绝大部分设计都在解决「怎么知道它做对了」。生成从来不是难点------在你有一千个任务的时候,「每个任务花五分钟人工确认」就意味着这条流水线跑不动。
四、能用规则拦的,不要写进 Prompt。 Prompt 是概率性的,规则是确定性的,而且规则不花 token。更重要的推论:一条不阻断的建议,实际遵守率大概是一条阻断规则的四分之一 (19.2% vs 80%+)。「我们建议」和「不允许提交」之间,隔着四倍。
五、自动化能保证正确性,保证不了合理性。 57 个超长文件全部通过了每一层门禁,因为它们是正确的 ,只是不合理。架构劣化不产生 bug,只产生债,所以它能穿透所有以正确性为判据的检查。那种「这不对劲」的直觉,目前仍然只有人有。
六、人的价值从「生产代码」转移到「定义正确性标准」。 十年前我写《终端 App 稳定性检视标准》服务 1000 多人,今天我写 AI 代码的准入标准。同一件事,对象变了。 变化的是标准的重心从「分类的完备性」转向「判定的可机器化」;不变的是写标准的人必须是亲手踩过坑的人。
七、可回滚、可分批、可度量,缺一不可。 前两条我们做到了:原工程全程不冻结、60 多个国家分批放量。第三条只做到一半 ------结果指标(产出、成本、返工率)事后能算,但流水线自己的过程指标全部没有埋点。代价不是少了几个数字,而是半年里我们不知道哪个环节最贵、哪类任务重试最多、哪条门禁其实没在拦东西,也不知道换掉某个设计之后是变好还是变坏。
如果只让我给准备做同类项目的人一条建议:第一天就给流水线加上运行指标采集。 它比你想加的任何一个 Skill 都重要。
最后。这次迁移里最容易被误读的一点是:AI 的作用不是「让工程师不用写代码了」。
它真正改变的是架构决策的成本结构。
框架该不该换、模块该不该重构、这套技术债要不要还------这些判断,很多团队在两三年前就已经做出来了,然后把它们压在抽屉里。不是因为判断错了,是因为执行代价太大,大到不值得做。
AI 没有让架构师失业,它让架构决策的成本变低了------过去因为「重写代价太大」而不敢做的决策,现在可以做了。