【AI Native开发-05】技术不确定怎么办:用 Spike 把未知挡在实现之前
这是“AI Native开发”系列的第 5 篇。上一篇的 Spec 模板最后留了一栏“待确认问题”,并说:如果某个问题不是“需求没想清”,而是“技术上不知道可不可行”,它不该靠拍脑袋写进 Spec。本篇就处理这类问题——在 AI 写代码速度极快的时代,把技术不确定性留到实现阶段,代价被放大了一个数量级。Spike 就是专门隔离和清除这类风险的手段。
摘要
本篇区分两种不确定性(需求不确定 / 技术不确定),论证为什么技术不确定绝不能混进 Implement 阶段:AI 会把“我不知道这条路通不通”直接实现成一条走错的路,而且走得飞快。文章给出 Spike(刺探式技术验证)的四条铁律——时间盒、单一问题、代码一次性 / 结论永久、产物不入主干,定义它的准入准出标准,用一个真实案例(静态博客 + 本地 Keystatic 架构下,“删除功能到底落在哪一层”)走一遍完整 Spike,并给出可复用的 Spike 报告模板。核心判断:SDD 不是拒绝探索,而是把“探索”和“交付”放进两条不同的流水线,用不同的验收标准管理。
先分清两种“不知道”
“不知道”有两种,混为一谈是流程失控的常见起点:
需求不确定:
“删除之后要不要能恢复?”“普通作者能不能删自己的稿?”
→ 这是产品决策问题,答案在业务侧,靠问、靠讨论、靠取舍。
→ 产物:Spec 里的一条业务规则或非目标。
技术不确定:
“这个 CMS 有没有提供删除接口?”“静态部署下删除操作发生在哪?”
“第三方限流会不会让幂等重试失效?”
→ 这是事实问题,答案在代码、文档、运行结果里,靠做实验拿证据。
→ 产物:一份 Spike 结论。两者的处理方式完全不同:需求不确定靠人做决定,技术不确定靠实验拿事实。最怕的是把技术不确定当成需求问题在 Spec 里写一句想当然的话——比如写“删除调用 CMS 删除接口”,但根本没验证这个架构下有没有那个接口。AI 不会质疑这句假设,它会一路实现到底。
为什么技术不确定在 AI 时代更危险
传统开发里,工程师在编码时撞到技术不确定,会自然停下来查证——因为他写每一行都要过自己的手,“写不下去”本身就是一个刹车。AI 把这个刹车卸掉了:
人类工程师遇到不确定的 API:
写不出来 → 去查文档 / 做个 demo → 确认后再写
(不确定会自然阻塞动作)
AI 遇到不确定的 API:
基于训练数据里“这类系统通常怎么设计”生成一整套实现
→ 代码结构完整、类型正确、测试可能还过
→ 但前提(那个接口存在 / 那个字段叫这个名)是错的
(不确定不会阻塞动作,反而被流畅地掩盖)这正是第 1 篇 F1(状态假设)和第 3 篇 C1(上下文缺失)的交叉地带:技术不确定 = 上下文里缺了一块“只有跑过才知道”的事实。Spike 的作用,就是在 Gate 1 / Gate 2 之前把这块事实补齐,让 AI 在 Implement 阶段面对的是已知而不是假设。
Spike 是什么:一条刺出去就收回来的探针
Spike(刺探)这个词来自极限编程:用一个最小的、用完即弃的实验,回答一个具体的技术问题。它和正式实现有本质区别:
| 正式实现(Implement) | Spike | |
|---|---|---|
| 目的 | 交付可上线的功能 | 回答一个技术问题 |
| 产物 | 进主干的产品代码 | 结论 + 证据(实验代码丢弃) |
| 质量标准 | 完整、健壮、可维护、有测试 | 能证明结论即可,不求健壮 |
| 时间 | 按功能估算 | 硬性时间盒,到点停 |
| 失败 | 要回滚 | “此路不通”本身就是有价值的结论 |
关键心智:Spike 的代码是一次性的,Spike 的结论是永久的。 实验代码写完、答案拿到就删(或留在分支 / 临时目录,绝不合入主干);但结论要沉淀进 Spec、Plan 或 pitfalls,成为后续正式实现的事实基础。
Spike 的四条铁律
铁律一:时间盒——到点必须停
给 Spike 一个固定的、短的预算(半天到一天,超过就该怀疑问题太大需要拆分)。时间盒不是效率工具,是防止 Spike 偷偷变成正式实现的闸门。AI 做 Spike 尤其危险:你让它“试试这条路”,它能顺手给你写出一个带错误处理、带测试、带注释的完整功能——然后你舍不得删,一个没经过评审的实验就混进了产品。到点停,强制回到“回答问题了吗”这个原点。
铁律二:单一问题——一个 Spike 只回答一件事
差的 Spike 目标:“验证删除功能能不能做”
→ 太大,会一路发散成半个实现
好的 Spike 目标:
“验证在 Keystatic local 存储 + 静态部署下,
是否存在运行时删除接口;如果没有,删除的正确落点是什么。”
→ 单一、可回答、有明确的“答出来了”判据问题必须具体到能用一句话回答“是 / 否 / 选项 C”。如果一个 Spike 要回答三个问题,拆成三个 Spike。
铁律三:代码一次性,结论永久
Spike 里怎么快怎么来:可以写死数据、可以跳过错误处理、可以不写测试。但拿到答案后,实验代码不得进入主干。原因:为“快”而省略的东西会变成技术债,而后来人分不清哪段是产品代码、哪段是实验残骸。结论则相反——必须写下来,而且写在正式实现一定会读到的地方(Spec 的数据约束、Plan 的技术方案、或 pitfalls.md)。
铁律四:先定成功判据,再动手
Spike 开工前就要写清“看到什么结果算问题被回答了”,否则实验做完会陷入“那所以呢”。判据要可判定:
模糊判据:“看看删除好不好做”
可判定判据:
- 若存在运行时删除接口:给出接口名称、调用方式、权限机制 → 结论=走接口
- 若不存在:确认删除=本地删除文件+Git 提交 → 结论=走本地文件操作,
并列出这对“二次确认 / 权限”意味着什么一次架构核查式 Spike:删除功能到底落在哪一层
写“删除草稿文章”的 Spec 时,第 4 篇模板里“数据约束”和“权限边界”两节其实卡着一个没想清的技术问题。这个博客是 Astro 静态站 + Keystatic CMS,而 Keystatic 配置里明确写着:
// keystatic.config.ts
storage: { kind: 'local' },
// 注释原文:EdgeOne Pages 是静态托管,不支持 SSR
// Keystatic 只能在本地开发时使用,编辑后 git push 触发自动部署于是冒出一个技术不确定:“删除文章”在这个架构里到底是什么? 是有个运行时 API?还是 CMS 界面操作?还是直接删文件?不搞清楚,Spec 里写“服务端删除接口返回 403/409”全是空中楼阁——静态托管下根本没有服务端。
这是一个典型的、必须在写 Plan 之前用 Spike 回答的问题。
Spike 记录
# Spike: 删除功能在静态架构中的落点
时间盒:半天
待回答问题(唯一):
本项目 Keystatic local + 静态部署架构下,
“删除文章”的正确技术落点是什么?存在运行时删除接口吗?
成功判据:
- 确认有无运行时删除 API
- 若有:给出接口、权限机制
- 若无:给出删除的真实执行方式,并列出对 Spec 的影响
实验过程(代码一次性,不入主干):
1. 通读 keystatic.config.ts:storage.kind = 'local',
无 GitHub API token 配置 → 非 cloud 模式
2. 核实部署目标:EdgeOne Pages 静态托管,无 SSR / 无服务端函数
3. 本地起服务,在 Keystatic 后台删除一篇测试文章:
观察到对应 .md 文件被直接从本地文件系统删除,
无任何网络请求发出(DevTools Network 无删除 API 调用)
4. 确认删除后需 git commit + push 触发重新部署,线上才生效
结论(永久,回写 Spec):
- 本项目不存在、也不可能有“运行时删除接口”——
没有服务端来承接它
- 删除的真实语义 = 在本地开发环境删除 Markdown 文件 + Git 提交
- “权限”边界因此不是接口鉴权,而是:
能进本地 Keystatic 后台的人 = 有仓库写权限的人
- “二次确认 / 防误删”是前端交互层责任;
“已发布不可删”的约束在文件操作前的校验逻辑里
- 之前 Spec 草稿里“服务端返回 403/409、独立鉴权”的条款,
在本架构下不成立,需改写为基于文件状态的本地校验 + 操作确认这个 Spike 的价值很直接:仓库配置核查已经足以拦下第 4 篇 Spec 范例里的“服务端独立校验、返回 403”——在当前静态架构里,那套设计无处承载。若再补上临时分支里的操作验证,就能把删除后的文件 Diff、构建行为和 CMS 交互一并固化为证据,避免 AI 按错误前提生成整套鉴权接口后才被打回。
这个案例里 AI 最容易掉的坑
如果不做 Spike,直接让 AI 实现“删除功能 + 权限校验”,它几乎必然生成:一个 API route(/api/articles/[id].ts 的 DELETE 处理)、一套会话鉴权中间件、403/409 响应。这套东西在 Next.js 全栈项目里完全正确,在这个静态 Astro 项目里构建出来就是死代码,甚至会误导部署。AI 没有错——它按训练数据里最常见的架构实现;错的是没人先把“这个项目是什么架构”这个事实钉死。Spike 钉死了它。
Spike 的准入与准出
准入(什么时候该开一个 Spike)
开 Spike 的信号:
[ ] Spec / Plan 里有一条关键假设,无法靠读现有代码确认
[ ] 要依赖的 API / 第三方行为没在项目里用过
[ ] 两个技术方案选型,性能 / 可行性差异未知
[ ] 旧系统某个行为没有文档,只能跑一下看
[ ] 不验证就写 Plan,Plan 的核心步骤建立在猜测上
不该开 Spike 的情况:
[ ] 读代码 / 读文档就能确定的事(那是上下文检索,不是 Spike)
[ ] 产品取舍问题(那是需求决策,回去找业务方)
[ ] 正式实现里顺手写个测试就能覆盖的事准入时必须写清三样:待回答的单一问题、时间盒、成功判据。三样写不出来,说明问题还没定义清楚,先别动手。
准出(Spike 怎样算完成)
[ ] 最初的那个问题被明确回答(是/否/选哪个,不能是“大概可以”)
[ ] 有可复现的证据:命令、输出、观察记录,而不是“我试了一下”
[ ] 结论已回写到它该去的地方:
影响需求边界 → Spec
影响技术方案 → Plan
是会重复踩的坑 → pitfalls.md
[ ] 实验代码未合入主干(已删除 / 在独立分支 / 在临时目录)
[ ] 若问题仍未回答:如实记录“不确定仍在”,
并升级为需要人决策的风险,而不是带着猜测进 Implement最后一条很重要:Spike 允许失败。实验证明某条路走不通,是高质量结论;但 Spike 失败后假装问题不存在、硬着头皮进实现,是把风险直接存进生产。准出标准里“不确定仍在”必须显式可见,对应第 2 篇 Verify 报告里的“已知限制”——暴露未知,比伪造确定更有价值。
Spike 在四阶段闸门里的位置
Spike 不是第五个阶段,它是横跨 Gate 1 / Gate 2 的一个旁路循环:
Spec 编写中 / Plan 编写中
│
发现技术不确定?
┌────┴────┐
否 是
│ │
│ ┌────▼─────┐
│ │ Spike │ 时间盒、单一问题
│ │ 拿证据 │ 代码一次性
│ └────┬─────┘
│ 结论回写
│ Spec / Plan / pitfalls
└────┬────┘
▼
闸门判定(Gate 1 / Gate 2)关键:Spike 的结论回流到 Spec 或 Plan,再过一遍闸门,而不是 Spike 做完直接开始写代码。它是给闸门输送事实的补给线,不是绕过闸门的快车道。
附录:Spike 报告模板(可直接复制)
# Spike: <一句话标题>
## 元信息
- 时间盒:<半天 / 一天,截止时间>
- 提出者:
- 关联 Spec / Plan:<链接或路径>
## 待回答问题(只能一个)
<用一句话写成可回答的问题>
## 成功判据(动手前写)
- 若 <结果 A>:结论是 <……>,意味着 <……>
- 若 <结果 B>:结论是 <……>,意味着 <……>
## 实验过程(代码一次性)
1. <做了什么、跑了什么命令>
2. <观察到什么,关键输出 / 截图 / 数据>
3.
## 结论(永久,回写依据)
- 问题的答案:<明确结论>
- 证据:<可复现的命令与输出>
- 对 Spec 的影响:<需新增 / 修改的条款,或“无”>
- 对 Plan 的影响:<技术方案的选择,或“无”>
- 应沉淀到 pitfalls 的条目:<或“无”>
## 实验代码去向
<已删除 / 分支名 / 临时目录——确认未合入主干>
## 残留不确定(如有)
<Spike 没能回答的部分,升级为风险,注明需要谁决策>至此,方法篇的前三块地基铺完了:上下文工程让 AI 理解项目现状,Spec 让 AI 理解要做什么,Spike 把技术未知在动工前清掉。下一篇进入 Gate 2 的核心产物——Plan:为什么必须让 AI 先研究代码库、产出一份受约束的执行计划并经人评审,才准碰代码;以及一份合格的 Plan 为什么必须写清“不碰哪些文件”和“怎么回滚”。
