让 AI 写代码最爽的一刻是 prompt 下去、代码出来的那 30 秒;最崩的一刻是打开页面发现它「做完了」但根本不是你要的。
我上周把 Vibe Coding 第一课(莫欣)的讲义啃完,加了 5 道代码题实操,把 SpecDD + TDD 这套流程整理成 5 步。核心就一句话:AI 只能「应试」,不能「自证」——它写没写完、写对没写对,必须有一个先于代码存在的客观检查说了算。
先把两个词掰开
SpecDD(Spec-Driven Development,定义驱动开发):动手之前,先和 AI 把「什么算做对了」写成一份规格文件——场景、输入输出、关键行为、异常处理、验收方式,再加一条「本次不做什么」。检验标准很朴素:照着这份 Spec 干活,还有没有要猜的地方。 有,就还没到开工的时候。
TDD(Test-Driven Development,测试驱动开发):代码还没写,先写一个小测试表达 Spec 里的预期行为。这时功能未实现,测试必然失败(红)→ 写最小代码让它通过(绿)→ 整理代码不改行为(重构)。AI 场景里,这个测试就是防止 Agent「自称做完」的验收单。
关系一句话说清:SpecDD 定义验收标准,TDD 把标准变成先于代码存在的检查。AI 只能「应试」不能「自证」。
第 1 步:先画图纸再开工
让 AI 写码前,先写个文件讲清五件事:做什么给谁用|输入什么输出什么|正常该怎么表现|出了问题怎么办|最后怎么验收。再补一句「本次不做什么」:不自动回复、不管理后台。
边界清单比愿望清单好控得多。「做到什么程度算过」没有定义时,AI 给你做词频图也算「实现了需求」——但词频图、自动回复、问答页面解决的是三个不同的问题。
我做题时最深的一个体会是「口径必须写进 Spec」。题目要求按时间范围过滤消息,sent_at(发送时间)和 collected_at(采集时间)两种口径结果不同,Spec 确认后必须显式传入,不许默认;没有发送时间的消息「保留未知」,不许偷换成采集时间:
def filter_by_range(messages: list[dict], start, end, field: str) -> list[dict]:
if field not in ("sent_at", "collected_at"):
raise ValueError("时间口径必须由 Spec 明确指定")
out = []
for m in messages:
t = m.get(field)
if t is None:
continue # 未知就是未知,不偷换口径
if start <= t <= end:
out.append(m)
return out
第 2 步:AI 给你规划后,只盯一样东西:它自己脑补的
规划输出里那些「写得很肯定、但你从没提过」的规则——比如我遇到的「只分析带问号的评论」——是 AI 替你做的决定,不是需求。第一动作不是采纳也不是删掉,是追问:这条规则来自用户需求,还是你自己的建议?
审查规划可以按四类拆:已确认需求 / Agent 假设 / 实现方案 / 开发任务。重点盯第二类,跑得越顺的脑补规则越危险。
第 3 步:验收清单写在开工前
TDD 循环:红→绿→重构。两个坑:
- 红灯要坏得对。缺依赖、模块导不进来这类报错是「考场坏了」,不算验收——先修运行条件,这时候还不能说明业务规则得到了检查。
- 测试第一次就绿,别造假红灯。要么目标行为早已实现(保留即可),要么断言漏了关键结果——是断言的问题,不是改坏代码表演一次的事。
第 4 步:AI 应用最核心——验证分两种,别混
查逻辑(去重、筛选、引用不越界)→ 固定假数据 + 模型替身,逐字段断言,像数学题有标准答案。数数这种活是程序干的,不归模型:
class FakeModel:
def __init__(self, answer): self.answer = answer; self.call_count = 0
def complete(self, msgs, q):
self.call_count += 1
return self.answer
def start_analysis(snapshot: list[dict], question: str, model) -> dict:
msgs = tuple(snapshot) # 快照语义:本批输入就此冻结
if len(msgs) == 0:
return {"status": "empty_range"}
ans = model.complete(msgs, question)
return {"status": "ok", "answer": ans,
"count": len(msgs), # 程序统计,不是模型统计
"input_ids": [m["msg_id"] for m in msgs]}
快照为空时断言 call_count == 0——模型一次都不能被调用;引用了范围外的 msg_id 断言被拒。这类用替身测,稳定、免费、可重复。
看效果(摘要准不准、有没有编)→ 真实调用、多样本反复跑。程序只能校验「引用没越界」,校验不了「引用真能支撑回答」——后者是语义判断,标准是「措辞可以变,意思必须留住」。一次通过≠稳定。
第 5 步:别信「测试全部通过」这句话
AI 报告全部通过时,看四项汇报里有没有:真实模型调用过吗?页面实际操作过吗? 程序逻辑测试 100% 通过,只说明确定性逻辑没问题;模型效果和用户流程仍然是待验证状态。这是两个轨道,不能互相顶替。
速查表
| 模块 | 核心 |
|---|---|
| Spec | 场景+输入输出+关键行为+异常+验收;「哪些还要靠猜」 |
| 审规划 | 已确认需求/Agent假设/实现方案/开发任务;盯「很肯定但没讨论过」 |
| 用例 | 前置+输入操作+预期+不应发生+检查方法 |
| TDD | 红灯(因目标行为)→最小绿灯→重构;不为通过删断言 |
| 双轨验证 | 程序逻辑用替身精确断言;模型效果真实调用+语义标准 |
一句话总结:一句话生成是表演,说清什么叫「做对了」才是工程。
本篇最早于 2026 年 10 月 16 日发布于小红书,此为博客完整版。