我用 AI 写了三个月代码,然后我再也看不懂自己写的东西了
一个熟悉的故事
去年我开始大量用 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)和为什么是这个样子(决策日志)。
三个我觉得值得说的设计决策
1. 确认门:分析和动手之间的强制停顿
/spec-first 的工作流里,有一个我最看重的机制:在 AI 开始写代码之前,必须经过一次用户确认。
AI 会先分析需求,读代码,把影响到的接口、数据库结构、代码位置整理成文档,然后展示"改之前是什么样,改之后会是什么样"的对比。只有我点击"确认通过,开始开发",它才会动代码。
这个设计看起来简单,但背后的逻辑很重要:确认门把"理解"和"执行"强制解耦了。
在没有这个机制的时候,AI 经常在我话还没说完时就开始写代码了。有时候方向对,有时候不对。不对的时候,我得花时间解释"不是这个意思",然后等它重写。有了确认门之后,我们先把"方向"对齐,再谈"执行"。返工少了很多。
如果在确认环节我觉得方向不对,可以补充说明,AI 会重新分析;如果需求本身变了,也可以在这里调整范围。改文档比改代码便宜。
2. Spec 和决策日志分开存
另一个我思考了很久的问题是:文档里该放什么,不该放什么。
我见过很多项目的 spec 越写越臃肿,里面夹杂着"2023年10月我们讨论过方案A,最终选了方案B,因为..."这类历史记录。这些内容对新人有价值,但每次随机读取 spec 时都要跳过它们。
doc-first-dev 的做法是把两类信息分开存:
docs/plans/<模块>/里的 spec 只回答"现在是什么样"——当前的接口规范、数据库设计、代码结构。就地更新,永远只有一份,历史版本交给 git 管理。docs/decisions/log.md只回答"为什么是这个样子"——方案选择、取舍背景、考虑过但放弃的替代方案。顺序追加,不覆盖。
这个分法的好处在于:spec 保持精简,可以快速读完;决策日志保留完整上下文,在需要回溯历史的时候去查。两者的读写模式根本不同——spec 是随机读写,决策日志是顺序追加——分开存才能让各自保持最适合自己的形态。
代码里我们已经很自然地把变量命名(what)和注释(why)分开。文档里做同样的分离,其实是一件很自然的事。
3. 三角一致性:接口 ↔ 数据库 ↔ 代码,三角对齐
最后一个设计是在 spec 更新完成后的自动检查。
我发现一类高频错误是:数据库加了一个字段,接口规范里没有更新,然后代码里用了这个字段,但调用方不知道可以传这个参数。或者反过来,接口文档里写了某个查询条件,但数据库里没有对应的索引,代码里也没有处理。
doc-first-dev 在每次分析完之后会做三角校验:接口规范中的字段、数据库设计中的字段、代码地图中的方法签名,三者必须对齐。发现不一致时会立刻暴露,并且问你"以代码为准还是以 spec 为准",让你来做决定。
这个机制解决的不是什么高深问题,就是那种"我以为我都改完了但其实漏了一处"的低级错误。但这类错误在 AI 开发中特别容易出现,因为 AI 改代码的范围有时候超出你的预期,而你没有一个系统地检查全貌的方法。
一次典型的使用过程是什么样的
举个具体的例子。假设我要给一个列表接口加"按作者筛选"的功能:
/spec-first 给文章列表接口加一个按作者筛选的参数,支持模糊匹配
接下来:
- AI 读当前的 spec,找到相关接口和数据库表,分析影响范围。
- 它把需要修改的内容整理出来:接口规范加
author参数、数据库查询逻辑说明、受影响的 Mapper 方法。展示改前改后的对比。 - 我检查一遍,觉得没问题,点"确认通过,开始开发"。
- AI 开始改代码——Mapper、Service、Controller,按顺序来。
- 改完构建一遍,然后自动进入验收:实际调用
POST /article/list传author=张三,检查返回结果里所有文章都包含"张三"。 - 验收通过,输出简报:改了哪些文件,验收结果是什么。
整个过程我只需要在确认门那里介入一次。其余的它自己处理。
适合谁用,不适合谁用
说实话,这套工具不是适合所有场景的。
适合:
- 你在维护一个要持续迭代的项目,而不是一次性脚本
- 你(或你的团队)用 AI 写了大量代码,但文档状态已经一团糟
- 你希望 AI 的每次修改都有迹可循,可以回溯
不太适合:
- 纯探索性的原型,你只是想快速验证一个想法
- 完全不在意文档的快速实验
- 项目生命周期很短,文档的投资回报不合算
我自己的用法是:新项目的早期探索阶段先不用,等方向确定了、开始认真迭代了,再引入这套工作流。
安装
如果你想试试:
npx skills add zzusp/doc-first-dev
安装完之后,在你的项目里运行 /spec-first 加上一句需求描述,它会自动检测是否需要初始化,然后走流程。
写这个工具的出发点很简单:我不想三个月后看不懂自己写的代码。目前用下来,文档腐化的问题确实好了很多。如果你有类似的困扰,可以试试。有什么问题或者想法,欢迎在评论区聊。