爆米花烘焙
66.80M · 2026-04-14
去年我开始大量用 Claude Code 辅助开发。速度确实快——一个下午能出一个以前要两天的功能。但渐渐地我发现了一个问题:三个月前写的东西,我自己都看不懂了。
不是代码写得差。代码本身还好。是因为没有文档——或者说,有文档,但文档跟代码对不上。
列表接口的 spec 里写的是"支持按状态筛选",但代码里加了七八个筛选条件,没有一个更新过文档。数据库表里多了三个字段,接口规范里完全没有提及。当我想加一个新功能的时候,我不知道该相信 spec 还是相信代码。
这是 AI 时代催生的一种新型技术债。AI 写代码太快了,文档永远跟不上。
我把这个现象归结为两个独立但相关的问题。
第一个问题:文档腐化。
传统项目里文档腐化已经够烦了。AI 时代更严重,因为生产速度被放大了好几倍,但维护文档的习惯没有跟上。更糟的是,很多人(包括我自己)会在某个需求开始前写一份 spec,然后在开发过程中随着需求变化多次修改,最后项目里存着三四个版本的"spec-v2-final.md",没有人知道哪个是最新的。
第二个问题:AI 的"立刻行动"倾向。
这个更隐蔽。AI coding 工具有一个天然的行为模式:理解需求 → 立刻写代码。它不会主动说"我先把方案写出来你确认一下",而是直接开干。这在需求清晰的时候很好,但在需求模糊、或者理解有偏差的时候,改错方向的代码比没有代码更难处理——因为你得先把错的删掉,再重新来过。
过去几个月我一直在想怎么解决这两个问题,最后做了一套叫 doc-first-dev 的东西:一组可以安装到 Claude Code 的 Agent Skills,把"文档先行"这套工作流强制固化下来。
核心是两个命令:
/spec-first:管理从需求到交付的完整开发周期。每次有新需求或变更,先写/更新文档,确认通过再开发,开发完再跑验收。/whylog-record:在任务结束后把这次的决策背景记录下来——考虑过哪些方案,为什么选现在这个,有什么取舍。两个命令一起用,才能回答开发中最重要的两个问题:现在的代码是什么样的(spec)和为什么是这个样子(决策日志)。
/spec-first 的工作流里,有一个我最看重的机制:在 AI 开始写代码之前,必须经过一次用户确认。
AI 会先分析需求,读代码,把影响到的接口、数据库结构、代码位置整理成文档,然后展示"改之前是什么样,改之后会是什么样"的对比。只有我点击"确认通过,开始开发",它才会动代码。
这个设计看起来简单,但背后的逻辑很重要:确认门把"理解"和"执行"强制解耦了。
在没有这个机制的时候,AI 经常在我话还没说完时就开始写代码了。有时候方向对,有时候不对。不对的时候,我得花时间解释"不是这个意思",然后等它重写。有了确认门之后,我们先把"方向"对齐,再谈"执行"。返工少了很多。
如果在确认环节我觉得方向不对,可以补充说明,AI 会重新分析;如果需求本身变了,也可以在这里调整范围。改文档比改代码便宜。
另一个我思考了很久的问题是:文档里该放什么,不该放什么。
我见过很多项目的 spec 越写越臃肿,里面夹杂着"2023年10月我们讨论过方案A,最终选了方案B,因为..."这类历史记录。这些内容对新人有价值,但每次随机读取 spec 时都要跳过它们。
doc-first-dev 的做法是把两类信息分开存:
docs/plans/<模块>/ 里的 spec 只回答"现在是什么样"——当前的接口规范、数据库设计、代码结构。就地更新,永远只有一份,历史版本交给 git 管理。docs/decisions/log.md 只回答"为什么是这个样子"——方案选择、取舍背景、考虑过但放弃的替代方案。顺序追加,不覆盖。这个分法的好处在于:spec 保持精简,可以快速读完;决策日志保留完整上下文,在需要回溯历史的时候去查。两者的读写模式根本不同——spec 是随机读写,决策日志是顺序追加——分开存才能让各自保持最适合自己的形态。
代码里我们已经很自然地把变量命名(what)和注释(why)分开。文档里做同样的分离,其实是一件很自然的事。
最后一个设计是在 spec 更新完成后的自动检查。
我发现一类高频错误是:数据库加了一个字段,接口规范里没有更新,然后代码里用了这个字段,但调用方不知道可以传这个参数。或者反过来,接口文档里写了某个查询条件,但数据库里没有对应的索引,代码里也没有处理。
doc-first-dev 在每次分析完之后会做三角校验:接口规范中的字段、数据库设计中的字段、代码地图中的方法签名,三者必须对齐。发现不一致时会立刻暴露,并且问你"以代码为准还是以 spec 为准",让你来做决定。
这个机制解决的不是什么高深问题,就是那种"我以为我都改完了但其实漏了一处"的低级错误。但这类错误在 AI 开发中特别容易出现,因为 AI 改代码的范围有时候超出你的预期,而你没有一个系统地检查全貌的方法。
举个具体的例子。假设我要给一个列表接口加"按作者筛选"的功能:
/spec-first 给文章列表接口加一个按作者筛选的参数,支持模糊匹配
接下来:
author 参数、数据库查询逻辑说明、受影响的 Mapper 方法。展示改前改后的对比。POST /article/list 传 author=张三,检查返回结果里所有文章都包含"张三"。整个过程我只需要在确认门那里介入一次。其余的它自己处理。
说实话,这套工具不是适合所有场景的。
适合:
不太适合:
我自己的用法是:新项目的早期探索阶段先不用,等方向确定了、开始认真迭代了,再引入这套工作流。
如果你想试试:
npx skills add zzusp/doc-first-dev
安装完之后,在你的项目里运行 /spec-first 加上一句需求描述,它会自动检测是否需要初始化,然后走流程。
项目地址:github.com/zzusp/doc-f…
写这个工具的出发点很简单:我不想三个月后看不懂自己写的代码。目前用下来,文档腐化的问题确实好了很多。如果你有类似的困扰,可以试试。有什么问题或者想法,欢迎在评论区聊。