我用 AI 写了三个月代码,结果连自己写的东西都看不懂了
一个开发者的普遍困境
从去年开始,大量开发者涌入 Claude Code 进行 AI 辅助开发。效率提升令人振奋——过去需要两天的功能,现在一个下午就能搞定。但很快,一个尴尬的问题浮出水面:三个月前自己写的代码,如今竟然看不懂了。

问题不在于代码质量——代码本身还算可以。真正的症结出在文档上:更准确地说,是文档虽然存在,却和真实代码完全脱节。
列表接口的 spec 里写着"支持按状态筛选",可实际代码里已经加了七八个筛选条件,文档却从未更新。数据库表多了三个字段,接口规范中却只字未提。当你想添加新功能时,根本不知道该相信 spec 还是相信代码。
这其实就是 AI 时代催生的新型技术债。AI 写代码的速度太快,文档的更新根本追不上。
两个核心症结
这种现象可以拆解为两个独立但又彼此关联的问题。
第一个问题:文档腐化。
传统项目中,文档腐化已经足够让人头疼。到了 AI 时代,这个问题被进一步放大——代码产出速度翻了几倍,但维护文档的习惯却没能同步跟上。更糟糕的是,很多人会在需求启动前写一份 spec,然后在开发中随着需求变化反复修改,最终项目里散落着三四个版本的"spec-v2-final.md",没人知道哪一份才是最新的。
第二个问题:AI 的"即时行动"倾向。
这个问题更加隐蔽。AI 编程工具天然遵循这样的行为模式:理解需求 → 立刻写代码。它不会主动说"我先整理一下方案,等你确认再动手",而是直接开始编码。需求明确时这当然很好,但当需求模糊或理解出现偏差时,方向错误的代码比没有代码更棘手——因为你得先清理掉错误的部分,再从头来过。
打造了一套解决方案
过去几个月,我一直在思考如何解决这两个痛点,最终开发了一套名为 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 加上一句需求描述,它会自动检测是否需要初始化,然后进入流程。
项目地址:github.com/zzusp/doc-f…
开发这个工具的初衷很简单:不想在三个月后看不懂自己写的代码。目前使用下来,文档腐化的问题确实改善了很多。如果你也有类似的困扰,不妨试试看。有任何问题或想法,欢迎在评论区交流。
