Home
avatar

.Sam

【AI Native开发-05】技术不确定时,为什么不该让 AI 直接开写?

这是“AI Native开发”系列的第 5 篇。上一篇的 Spec 模板最后留了一栏“待确认问题”,并说:如果某个问题不是“需求没想清”,而是“技术上不知道可不可行”,它不该靠拍脑袋写进 Spec。本篇就处理这类问题——AI 能更快生成实现,也会更快放大未经核实的技术假设及其返工范围。Spike 就是专门隔离和清除这类风险的手段。

30 秒看懂这篇

“删除后能不能恢复”需要业务做选择;“CMS 有没有删除能力”需要代码、文档或实验给事实。把这两类未知混在一起,Spec 就会变成猜测的集合。

flowchart TD
    A["遇到一个不知道"] --> B{"答案由谁决定?"}
    B -- "业务取舍" --> C["需求不确定"]
    C --> D["讨论与决策 → 回写 Spec"]
    B -- "技术事实" --> E{"读代码 / 文档能确认吗?"}
    E -- "能" --> F["上下文检索 → 记录证据"]
    E -- "不能" --> G["限时 Spike"]
    G --> H["结论与证据 → 回写 Spec / Plan"]
    D --> I["重新过闸门"]
    F --> I
    H --> I

本篇给出 Spike 的时间盒、单一问题、默认一次性实验代码和成功判据,并用当前静态博客的架构核查说明:SDD 不拒绝探索,而是把探索与交付分开管理。 第 04 篇已经改为独立的假想全栈案例;本篇只讨论真实静态博客,两类架构不再混用。

先分清两种“不知道”

“不知道”有两种,混为一谈是流程失控的常见起点:

需求不确定:
  “删除之后要不要能恢复?”“普通作者能不能删自己的稿?”
  → 这是产品决策问题,答案在业务侧,靠问、靠讨论、靠取舍。
  → 产物: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
目的交付可上线的功能回答一个技术问题
主要产物产品代码与测试结论 + 证据;实验代码默认一次性
质量标准完整、健壮、可维护、有测试足以支持结论,可省略非相关健壮性
时间按功能估算预先约定时间盒
失败修复或回滚“此路不通”也是有价值的结论

关键心智:实验代码默认一次性,实验结论要长期保存。 若某段实验代码确实值得转为产品代码,也不能因为“已经能跑”就直接合入;必须重新经过正式 Plan、评审、测试与 Verify。

Spike 的四条铁律

铁律一:时间盒——到点必须停

给 Spike 一个固定的、短的预算(半天到一天,超过就该怀疑问题太大需要拆分)。时间盒不是效率工具,是防止 Spike 偷偷变成正式实现的闸门。AI 做 Spike 尤其危险:你让它“试试这条路”,它能顺手给你写出一个带错误处理、带测试、带注释的完整功能——然后你舍不得删,一个没经过评审的实验就混进了产品。到点停,强制回到“回答问题了吗”这个原点。

铁律二:单一问题——一个 Spike 只回答一件事

差的 Spike 目标:“验证删除功能能不能做”
  → 太大,会一路发散成半个实现

好的 Spike 目标:
  “验证在 Keystatic local 存储 + 静态部署下,
   是否存在运行时删除接口;如果没有,删除的正确落点是什么。”
  → 单一、可回答、有明确的“答出来了”判据

问题必须具体到能用一句话回答“是 / 否 / 选项 C”。如果一个 Spike 要回答三个问题,拆成三个 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: 删除功能在静态架构中的落点
时间盒:半天
待回答问题:
  当前 Keystatic local + 静态部署下,删除文章应在哪一层完成?

已核实的仓库事实:
1. keystatic.config.ts:storage.kind = 'local'
2. astro.config.mjs:output = 'static',当前未配置 SSR adapter
3. package.json:有 build / dev:cms 脚本,没有运行时删除 API 或测试脚本

由配置可以直接得出的结论:
- 当前生产部署没有承接在线删除请求的服务端
- 内容变更应落在本地内容工作流,而不是给静态站虚构在线 REST API
- 第 04 篇假想全栈案例中的 401/403/409 契约不适用于当前静态部署

仍需操作验证、不能靠配置假装已证实的事项:
1. 在临时分支用一篇测试文章执行 Keystatic 删除
2. 记录文件 Diff、必要的本机请求、Git 状态和构建结果
3. 核实谁可操作本地 CMS、谁有仓库写权限;二者不能默认画等号
4. 将命令、输出与结论写入正式 Spike 报告

这个案例最重要的不是宣称“实验已经做过”,而是展示哪些结论能由配置证明,哪些必须运行验证。Keystatic local 说明内容落在本地文件系统,但并不能据此推断 CMS 操作过程中“没有任何网络请求”;本地服务同样可能使用 HTTP。

如果跳过这一步,AI 可能按常见全栈模式生成 API route、会话鉴权和 403/409 响应。问题不在 Astro 是否支持 API——Astro 可以通过 adapter 支持按需渲染——而在当前部署没有这样一条服务端路径。Plan 应服从当前架构,而不是服从模型的常见模板。

Spike 的准入与准出

准入(什么时候该开一个 Spike)

开 Spike 的信号:
[ ] Spec / Plan 里有一条关键假设,无法靠读现有代码确认
[ ] 要依赖的 API / 第三方行为没在项目里用过
[ ] 两个技术方案选型,性能 / 可行性差异未知
[ ] 旧系统某个行为没有文档,只能跑一下看
[ ] 不验证就写 Plan,Plan 的核心步骤建立在猜测上

不该开 Spike 的情况:
[ ] 读代码 / 读文档就能确定的事(那是上下文检索,不是 Spike)
[ ] 产品取舍问题(那是需求决策,回去找业务方)
[ ] 正式实现里顺手写个测试就能覆盖的事

准入时必须写清三样:待回答的单一问题、时间盒、成功判据。三样写不出来,说明问题还没定义清楚,先别动手。

准出(Spike 怎样算完成)

[ ] 最初的那个问题被明确回答(是/否/选哪个,不能是“大概可以”)
[ ] 有可复现的证据:命令、输出、观察记录,而不是“我试了一下”
[ ] 结论已回写到它该去的地方:
      影响需求边界 → Spec
      影响技术方案 → Plan
      是会重复踩的坑 → pitfalls.md
[ ] 实验代码保持一次性(已删除 / 留在独立分支 / 在临时目录);
    如需转为产品代码,已另走正式 Plan、评审与验证,未以 Spike 名义直接合入
[ ] 若问题仍未回答:如实记录“不确定仍在”,
    并升级为需要人决策的风险,而不是带着猜测进 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 为什么必须写清“不碰哪些文件”和“怎么回滚”。

AI Native SDD Spike 技术验证 软件工程