别再让 AI 一上来就写代码:用 SDD 规划 Chrome 翻译插件
很多人第一次使用 AI Coding Agent,都会被它的速度震撼。
你只说一句"帮我做一个用户认证系统",它就能连续生成几千行代码。页面能打开,按钮能点击,乍一看,过去几天的工作似乎几分钟就完成了。
问题通常在第二周出现:
- 技术栈不是你想要的;
- 模块之间缺少清晰边界;
- 新需求一来,前面的代码要大面积返工;
- 会话一旦切换,AI 就不再记得之前做过的关键决策;
- 代码虽然能运行,却很难判断是否真的完成了需求。
这不是 AI 不够强,而是我们给它的上下文不够完整。
当代码生成越来越便宜,真正稀缺的已经不是"把代码写出来",而是把意图描述得足够清晰、可执行、可验证。Spec-Driven Development,也就是规范驱动开发,解决的正是这个问题。
下面以一个 Chrome 网页翻译插件为例,看看如何在一行代码都没有写的阶段,完成产品的"第一次创造"。
一、Vibe Coding 为什么容易先快后慢
Vibe Coding 很容易让人上头。开发者不断在对话框里下达任务,AI 不断生成代码,整个过程充满即时反馈。
但"即时产出很多代码"不等于"项目正在稳定前进"。
例如,我们想做一个 Chrome 插件,最初只给 AI 这样一句话:
做一个能翻译英文网页的浏览器插件。
这里至少有一连串没有回答的问题:
- 翻译整张网页,还是只翻译文章正文?
- 导航栏、广告、评论区需要保留吗?
- 原文中的标题、列表、链接和图片如何处理?
- 调用哪一家模型?以后是否允许切换?
- 翻译结果一次性出现,还是流式展示?
- 结果放在哪里,普通弹窗还是侧边栏?
- 是否保留历史记录?
- 最终只是阅读,还是还要复制、下载?
如果人没有回答,AI 只能猜。
猜测并不一定会让第一版无法运行。更麻烦的是,它可能选择一个"当前能跑、后续难改"的方案。当新需求出现时,已经生成的大量代码反而会变成负担。
另一个常见问题是会话上下文不稳定。需求、架构和约束只存在于某次聊天里,一旦开启新会话,这些信息就可能丢失。后来的 AI 不知道为什么这样设计,只能根据现有代码倒推意图。倒推错误后,就会继续产生不一致的实现。
所以,问题的核心不是生成速度,而是缺少一份能够跨会话持续存在的项目上下文。
二、SDD:让项目经历两次创造
"以终为始"背后有一个很朴素的原则:任何复杂事物都要经历两次创造。
第一次是心智创造。我们先把目标、边界、结构和路线设计清楚。
第二次是物理创造。按照已经确认的设计,真正写出代码并完成验证。
盖房子之前要有蓝图,创业之前要有商业计划。软件开发同样如此。SDD 做的事情,就是把第一次创造沉淀为规范,并让这些规范成为 AI Coding Agent 的长期上下文。
一套轻量但完整的规范可以由三部分组成:
| 规范 | 回答的问题 | 在团队中的角色 |
|---|---|---|
| 需求规范 | 做什么、为什么做、什么不做、怎样算完成 | 产品经理 |
| 架构设计 | 用什么技术、模块如何协作、风险如何处理 | 架构师 |
| 任务拆分 | 先做什么、后做什么、哪些能并行、如何逐项验收 | 项目经理 |
这三份规范不是为了走流程,更不是为了写一堆没人看的材料。它们直接承担三种职责:
- 把模糊想法变成明确决策;
- 给不同会话中的 AI 提供一致上下文;
- 为生成结果提供可核对的验收依据。
"文档即代码"的重点也在这里:规范和代码一样会影响系统行为,也必须和代码一起迭代、一起进入版本管理。
规范也不是越多越好,而是要按需加载。讨论产品范围时,需求规范是主要上下文;设计正文提取时,再加载架构决策和对应页面;执行某个开发任务时,只需要带上任务说明、相关接口和验收条件。这样既能保留项目全局方向,又不会让当前会话被无关信息淹没。
新的 AI 会话意味着新的上下文,但不应该意味着项目失忆。只要关键决策已经写进规范,新会话就可以重新加载它们继续工作,不需要依赖越来越长的聊天记录。
遇到自己不熟悉的关键难点,也不要让 AI 直接猜实现。例如"如何从任意网页提取正文"会决定整条产品链路,此时应该先围绕这一问题调研、比较方案、验证技术边界,再把结论写进架构规范。研究和设计本身就是开发工作,而不是编码前可有可无的准备。
三、第一步不是选框架,而是定义产品
先把这个翻译插件压缩成一句准确的话:
用户浏览英文文章时,可以一键提取页面主要内容,将其转换为 Markdown,调用 AI 完成英译中,并以流式效果展示和保存最近一次翻译结果。
这句话已经比"做个翻译插件"清楚很多,但仍然不够直接驱动开发。接下来要把它拆成目标用户、核心链路、输出契约和范围边界。
1. 明确用户与使用场景
目标用户是需要阅读英文文章、新闻、博客和技术文档的中文用户。
典型场景也很简单:用户正在浏览一篇英文文章,希望跳过复制原文、打开翻译工具、整理格式等重复步骤,在当前页面附近完成阅读和结果获取。
这个定义会直接影响后续设计。产品重点不是通用网页翻译,也不是替换网页中每一个英文节点,而是提取"主要文章内容"。
2. 把核心链路写成一句话
整个 MVP 的主链路可以写成:
text
打开英文文章
→ 一键提取正文
→ 转换为 Markdown
→ 调用大模型翻译
→ 流式展示结果
→ 复制或下载 Markdown
→ 本地保存最近一次结果
这条链路是后面需求、架构和任务拆分的共同主轴。任何功能如果无法服务这条链路,就需要重新判断是否应该进入 MVP。
3. 为输出定义固定契约
如果只要求"返回翻译结果",模型可能每次给出不同结构。下游的渲染、复制、下载也会因此出现不一致。
更稳妥的做法是先定义固定输出:
markdown
# [文章标题]
> **作者**:[作者名]
> **原文链接**:[原始文章 URL]
[翻译后的正文]
这段格式看起来很小,作用却很大。它同时约束了内容提取、模型输出、结果渲染和 Markdown 下载四个环节。
- 标题、作者和原始链接必须在提取阶段获得;
- 模型只负责翻译正文,不应该随意改写元信息;
- 展示层可以直接渲染完整 Markdown;
- 下载层不需要重新猜测各字段如何拼接。
如果作者无法识别,可以留空或者显示"未知";但这同样应该在需求阶段决定,而不是让每个模块分别处理。
四、把"正文提取"拆成可验收需求
"提取网页内容"是一个很容易低估的需求。
真实网页中除了文章,还混合着导航栏、侧边栏、广告、评论、弹窗和页脚。正文内部又可能包含标题、普通段落、引用、列表、链接、加粗文本和图片。
因此需求不能只写"拿到页面文字",而要写清楚结果应该是什么:
- 识别主要文章区域,过滤与正文无关的内容;
- 将正文转换为 Markdown;
- 保留标题、段落、列表、链接、引用和加粗等基本结构;
- 将图片统一转换为
; alt使用图片描述,无法取得时允许为空;src保留图片真实地址;- 同时获得文章标题、作者和原文链接。
这样一来,"提取成功"就不再等于拿到一个很长的字符串,而是得到一份结构清晰、能够继续交给模型处理的数据。
可以先定义提取结果的数据契约:
| 字段 | 含义 |
|---|---|
| 标题 | 页面文章标题 |
| 作者 | 可识别的作者信息,无法识别时为空 |
| 原文链接 | 当前文章的完整地址 |
| Markdown 正文 | 去除页面噪声并保留结构的正文 |
这张表不是在提前编写实现,而是在确认模块之间到底要传什么。正文提取、AI 翻译、结果展示和本地存储都围绕同一份结构工作,后面就不容易出现隐式约定。
五、模型调用也要先确定边界
MVP 只做英译中,并要求翻译后保留 Markdown 结构。
这意味着模型的任务不是总结,也不是重新排版。提示词至少要表达三层约束:
- 将英文正文翻译为中文;
- 保留 Markdown 的标题、列表、引用、链接等结构;
- 不破坏
这样的图片语法。
同时,模型接入不能和某一个厂商完全绑死。统一采用 OpenAI 兼容方式后,用户只需要配置 API Key、Base URL 和模型名称三个参数。
当前可以使用 Qwen,默认模型可以是 qwen-plus,以后切换兼容模型时,核心业务流程不需要跟着重写。
API Key 由用户配置,保存在本地;它不能硬编码进项目,也不应该出现在版本库和日志中。
六、MVP 的关键,不只是"要做什么"
一个可靠的需求规范,一定会同时写清楚"不做什么"。
这款插件的首期范围可以明确限制为:
- 不做翻译历史列表,只保存最近一次结果;
- 不做多语言互译,只做英译中;
- 不做账号体系;
- 不做云端同步;
- 不做正文编辑器;
- 不增加与核心链路无关的复杂功能。
有人可能会觉得这些功能都很有价值,为什么不一起做?
因为 MVP 的目的不是一次性穷尽所有可能,而是先形成最小但完整的可用闭环。历史记录意味着列表、搜索、删除和存储结构;账号体系又会带来认证和云端数据。它们会显著扩大系统边界,却不会帮助我们验证"一键提取并翻译文章"这个核心价值。
"不做清单"还能防止 AI 自作主张地补充功能。对生成式开发来说,限制条件和功能列表同样重要。
七、先画布局,再决定页面职责
有了功能范围,还需要让每一个动作找到明确的位置。
最初可以把主入口设计成普通 Popup:顶部放设置入口,中间显示当前页面信息,核心区域放"一键翻译"和"下载 Markdown",底部显示提取、翻译和错误状态。
结果页负责三件事:
- 展示文章标题、作者和原文链接;
- 以流式效果渲染翻译后的 Markdown;
- 提供复制和下载操作。
设置页则只保留 API Key、模型和 Base URL 三项配置,以及一个保存按钮。
这种布局足够简洁,但在进一步推演使用场景后,会暴露一个问题:翻译后的文章可能很长,而传统 Popup 的显示空间有限。
于是需求发生了合理迭代:主界面改为从浏览器右侧打开的侧边栏,高度与页面一致,为长内容阅读和持续输出提供更稳定的空间。
这次修改揭示了 SDD 中很重要的一条原则:
新需求不是直接追加一句提示词,而是要回到规范,检查它影响了哪些页面、模块、数据流和旧实现。
当侧边栏成为最终形态后,至少要同步完成四件事:
- 更新界面布局和交互描述;
- 更新架构中的前端上下文;
- 更新构建入口与任务清单;
- 移除已经不再使用的传统 Popup 实现。
最后一点尤其容易被忽略。只增加新代码、不删除旧代码,会让项目同时存在两套入口。AI 在后续会话中也可能读到过期实现,进而继续沿着错误方向修改。
需求清单中的文字也要一起消除冲突。例如早期边界写着"本期不做导出",后续却已经把"下载 Markdown"加入页面和任务,那么就必须更新边界与验收标准,明确最终范围包含下载。规范中同时存在"要下载"和"不导出",AI 无法判断哪一条才是真实意图。
八、让验收标准替代"看起来差不多"
没有验收标准时,AI 交付代码后,我们常常只能打开页面凭感觉判断。这样的验收既不稳定,也很难覆盖完整链路。
围绕核心目标,可以先定义五条端到端标准:
- 在英文文章页面中,可以一键提取主要正文并生成 Markdown;
- 原文图片能够转换为
; - 翻译结果能够持续增量展示,而不是长时间无反馈后一次出现;
- 最终内容符合标题、作者、原文链接和正文组成的固定格式;
- 最近一次结果保存在本地,重新打开插件后能够恢复。
当复制和下载被纳入最终范围后,也应该把它们补进验收标准:复制得到完整 Markdown;下载按钮在没有结果时禁用,有结果时生成格式正确的 .md 文件。
验收标准有两个直接价值:
- 写代码之前,它帮助我们发现需求遗漏;
- 写代码之后,它让每次 AI 生成都有清晰终点。
九、规范也需要版本控制
AI 生成代码很快,因此更需要及时建立可追溯的版本节点。
每完成一个独立、可验证的任务,就应该提交一次代码和对应规范。这样既能追踪"为什么改",也能在 AI 产生错误修改时快速回退。
常见的回退场景可以分成三种:
bash
# 修改还没有进入暂存区:丢弃本轮修改
git restore .
# 已经暂存但还没有提交:先取消暂存,再丢弃修改
git restore --staged .
git restore .
# 已经形成独立提交,并明确需要回到上一个提交
git reset --hard HEAD^
最后一种操作会直接丢弃提交和工作区修改,使用前必须确认目标提交以及未保存内容。重点不是记住某条命令,而是让每一轮 AI 修改都有可追踪、可比较、可恢复的边界。
版本控制也不应该只跟踪源码。需求从 Popup 变成侧边栏时,需求规范、架构、布局、任务和代码应该位于同一个版本节点。否则代码已经变化,AI 读到的规范却仍然停留在旧版本,项目很快会再次失去一致性。
十、从想法到可执行规范,我们究竟完成了什么
到这里还没有开始编写业务代码,但项目已经完成了最重要的一轮收敛:
- 产品目标从"网页翻译"缩小为"英文文章正文提取、Markdown 翻译与展示";
- 主链路被固定为提取、转换、翻译、展示、复制或下载、保存;
- 输出格式成为跨模块契约;
- MVP 的非目标被明确排除;
- 页面职责已经划分;
- 长文章场景推动入口从 Popup 迭代为侧边栏;
- 每个核心功能都有可检查的验收标准;
- 需求和代码将被纳入同一套版本管理。
这就是 SDD 的第一次创造。
它没有降低 AI 的代码生成能力,而是让这种能力有了方向。
此时的项目仍然处在规划阶段:技术方案只是待实现、待验证的设计,任务清单也只是后续编码的执行路线。只有等真实代码完成、流式链路跑通并经过验收之后,才适合继续写实现分析。把"准备怎么做"和"已经做成什么"分开,既是对文章读者负责,也是 SDD 强调可验证性的题中之义。