一、AI 编码的真相:它是个"跑偏大师"
我用 AI 写代码快一年了,发现一个残酷的事实:
AI 在"写 Demo"上无敌,在"写能用的系统"上拉胯。
你让它"写一个智能家居控制页面",它能 30 秒给你一个漂亮的 Vue 组件。你让它"把这个模块对接你的三层缓存架构",它就开始跑偏了------变量命名乱飞、边界条件漏掉、错误处理全靠 console.log。
更致命的是,AI 总是想"多写一点"。你让它写 tap 函数,它顺手把 swipe、longPress、drag 全写了。看起来省事,但当你发现 tap 的坐标校验有问题时,你已经不忍心删掉它写的另外 200 行代码了。
AI 的创造力,在 Demo 阶段是优势,在系统构建阶段是灾难。
二、我的反制策略:复古到"函数级颗粒度"
在被 AI 气到无数次之后,我选择了一个"复古"的策略:
不再让 AI 写模块,只让它写函数。
一个模块一个模块地生成 → 跑偏率 60%。
一个文件一个文件地生成 → 跑偏率 30%。
一个函数一个函数地生成 → 跑偏率降到 5% 以下。
为什么?
因为"写一个函数"对 AI 来说,是没有歧义的确定性任务。
| 任务 | AI 的理解 | 跑偏概率 |
|---|---|---|
| 帮我写一个设备管理模块 | 模糊,需要 AI 自己设计边界 | 极高 |
帮我写 device.ts,包含注册和发现 |
较清晰,但内部结构仍需 AI 决定 | 中等 |
帮我写 registerDevice(id, driver, config) 函数,要求返回 Device 对象 |
完全确定,AI 只需要翻译签名 | 极低 |
函数是 AI 编码的"最小确定性单元"。
三、我的 Spec 怎么写:先写验收标准,再写函数签名
我现在写 Spec,不再是"功能描述",而是"函数清单"。
每个函数在 Spec 里长这样:
markdown
## 函数:registerDevice
### 签名
registerDevice(id: string, driver: Driver, config: DeviceConfig): Device
### 职责
向设备注册表添加一个新设备,返回注册后的 Device 对象。
### 边界条件
- id 为空 → 抛出 `InvalidDeviceIdError`
- id 已存在 → 抛出 `DeviceAlreadyExistsError`
- driver 未初始化 → 抛出 `DriverNotReadyError`
### 验收标准(测试用例)
1. 正常注册 → 返回 Device 对象,`device.id` 等于传入 id
2. id 为空 → 抛出指定错误
3. id 重复 → 抛出指定错误
4. driver 未连接 → 抛出指定错误
这不是文档,这是合同。
AI 生成代码后,我只需要跑四个测试用例。全过了,验收通过。有一个没过,AI 重写。
我把这叫做 "验收驱动开发"------不是先写代码再补测试,而是先定义验收标准,再让 AI 写代码。
四、文件预估行数:强制单一职责的紧箍咒
在我的 Spec 里,每个文件都标注了预估行数:~150行、~200行、~250行。
这不是炫技,是给 AI 套上的紧箍咒。
当你告诉 AI "写一个 device.ts",它可能给你生成 800 行,从注册到发现到状态同步全塞进去。
当你告诉 AI "写 device/registry.ts,约 200 行,只负责设备注册",它就被限制住了。
行数限制是强制单一职责的手段。
我的 Spec 里有这样的目录结构:
text
device/
├── index.ts # 导出入口(~50行)
├── types.ts # 类型定义(~150行)
├── registry.ts # 设备注册(~200行)
├── discovery.ts # 设备发现(~250行)
├── state-manager.ts # 状态管理(~250行)
└── ttl-manager.ts # TTL 管理(~200行)
每个文件只做一件事。一个文件超了 300 行,立刻拆。
AI 在"小文件"里跑偏的概率,远低于"大文件"。
五、核心原则验收表格:AI 无法绕过的护栏
我把项目的核心原则,全部变成了验收表格:
| 原则 | 说明 | 验收标准 |
|---|---|---|
| 三层意图缓存 | L1→L2→L3 逐层降级 | L1 < 10ms, L2 < 100ms, L3 < 3s |
| 本地优先 | SQLite 存储,断网可用 | 无外部数据库依赖 |
| 一切皆 CLI | 每个能力是独立命令行工具 | 可单独测试、可管道组合 |
| 文件细颗粒度 | 每个文件 300 行左右 | 单一职责,高内聚低耦合 |
这张表的作用是:任何 AI 生成的代码,都必须能通过这张表的验收。
不符合"文件细颗粒度"?拆。不符合"一切皆 CLI"?重写。
验收标准不是注释,是测试用例。Spec 不是文档,是合同。
六、组装:从函数到系统的逆向工程
函数级颗粒度有一个"副作用":你需要自己组装。
但这不是负担,是控制权。
AI 写好了 50 个纯函数,每个都有独立的测试用例。你要做的,是用这些函数拼出你的系统。
registerDevice + discoverDevices + getDeviceState → 设备管理模块。
matchRule + searchExperience + reactLoop → 三层意图引擎。
组装的过程,就是你理解系统的过程。
以前 AI 帮你写了整个模块,你根本不知道里面有什么。现在 AI 只写了函数,你亲自把它们拼起来,每一行代码的来龙去脉你都知道。
七、这套方法的收益
我用这套方法重构了 HomeSense 的 graph.ts------从 1400 行拆成 8 个文件,每个文件 150-250 行。
| 指标 | 重构前 | 重构后 |
|---|---|---|
| 总行数 | 1400 | 1100(拆开后更清晰) |
| 单文件最大行数 | 1400 | 250 |
| 测试覆盖率 | 未知 | 80%+ |
| AI 跑偏率 | 高(每次都要修) | 低(基本直接可用) |
省下的不是 Token,是"修 AI 代码"的心力。
八、写在最后
AI 时代的开发,最大的陷阱是"快"。
AI 能在 30 秒生成一个模块,但你花 3 小时修它的 bug。
我的方法是"复古":回到函数级颗粒度,先写验收标准,再让 AI 生成代码,最后自己组装。
慢了,但稳了。
Demo 可以快,系统必须稳。
如果你想用 AI 写出"能用的系统",而不是"炫技的 Demo",试试这套方法:
-
先写 Spec,颗粒度到函数
-
每个函数配验收标准
-
文件预估行数,强制单一职责
-
核心原则表格化,逐条验收
-
自己组装,掌控每一行代码
AI 是你的"函数工人",你才是"系统建筑师"。