AI Coding 之后,哪些设计知识值得留下?
用 AI Coding 和规格驱动开发(SDD)做项目时,我一直有个疑问:功能完成后,spec.md、plan.md 这些文档还要留在 Git 仓库里吗?
实现方案可能在开发过程中发生变化。如果文档没有同步更新,后续 AI 把它当作当前实现的依据,就可能基于过期信息继续修改代码。但直接删除,也可能丢掉有用的需求和设计背景。
这篇文章讨论这些文档该如何处理,以及哪些设计知识值得依据实际实现整理下来,供后续开发者和 AI 使用。
一、先区分任务记录与长期设计文档
需求说明、实现计划和长期设计文档,回答的问题不同。
本文采用下面这组命名约定。它们不是行业统一标准,实际项目可以使用其他文件名。
| 文档 | 主要回答的问题 | 常见内容 |
|---|---|---|
spec.md | 这次准备做什么? | 需求、行为预期、验收条件 |
plan.md | 这次准备怎么实现? | 修改范围、实施步骤、迁移安排 |
design.md | 系统当前如何工作?修改时需要知道什么? | 职责边界、运行机制、约束、已确认的设计取舍 |
例如,一个实现计划可能写着:先增加任务表,再实现后台执行器,最后补充重试测试。这能解释开发顺序,却不能直接帮助后续维护者回答:执行器何时领取任务?失败后谁负责重新调度?重复领取会发生什么?
长期设计文档应该围绕后面这些问题组织,而不是把已经完成的 Checklist 换个标题。
这也不意味着设计文档只能在实现后编写。它也可以在实现前起草,用于讨论方案;但本文关注的是代码实现完成后的设计文档:哪些设想已经落地,哪些发生了变化,哪些仍然只是计划。
如果每次任务都会生成独立的
spec.md和plan.md,文档可能逐渐积累。对于仅服务单次任务的文档,可以在实现完成、测试通过并合并 PR 后,将仍有价值的信息整理到对应的长期文档中:需求约定保留在持续维护的规格说明中,运行机制写入设计文档,重要决策记入 ADR,再归档或删除原任务文档。仍用于约束后续行为或验收的规格说明应继续维护。是否提交到 Git 仓库,取决于共享和追溯需求,而非文件名。
二、什么时候值得单独写一份文档?
独立文档有维护成本。如果一个函数和对应测试就能解释全部行为,再写一遍通常只会增加重复。
所以在编写前,可以先问三个问题来检查是否需要单独的设计文档。
1. 理解这个功能,是否必须跨多个文件?
以异步任务为例,提交入口、任务存储、执行器和失败处理可能分散在不同位置。每个文件都不复杂,但读者需要自己拼出完整流程。
此时,一份说明组件关系和主要流程的文档,比逐个解释方法名称更有用。
2. 是否存在容易被后续修改破坏的约束?
这里的约束,是指修改设计时必须保持成立的条件,也常被称为不变量。
例如:“订单只有在支付确认后才能进入已支付状态。”代码可能通过多个分支和事务操作共同保证这件事,单独看某个方法未必能意识到它的重要性。
文档需要把约束明确写出来,并指出由什么机制或测试保障。如果目前无法确认实现是否充分保障了它,就应标为待核验,而不是把设计要求写成已经成立的事实。
3. 是否存在代码本身难以解释的取舍?
代码通常能显示选择的结果,却未必保留选择过程。
为什么暂时不支持自动恢复?为什么保留一个看似重复的存储层?如果当时确实讨论过多个方案,最终选择及其条件值得留下。否则,后续维护者可能在不知道原有约束的情况下重新作出决定。
不必机械统计命中了几项。更实用的判断是:缺少这份说明,下一位维护者是否需要反复跨文件推理,或者容易误改重要机制?
文档粒度也应围绕能独立理解的模块或子系统划分。例如,认证、任务调度、文档导入可以分别说明;给任务调度增加一个配置项,通常更新原文档即可,不必每个 Feature 都新建一篇。
三、文档应该写什么:从一个教学示例开始
下面使用一个假设的异步任务模块说明方法。(它不是实际项目复盘,只是一个用于讲述的案例)
假设我们已经确认了这些实现行为:
- 提交入口将任务保存为
pending。 - Executor 领取任务,将其转为
running后处理。 - 成功时转为
succeeded。 - 失败后允许额外重试,最多三次;重试耗尽后转为
failed。
这里有个问题:这些内容太概括性了。设计文档还应帮助读者建立更多理解,包括但不限于职责边界、运行流程等
职责和边界
这个模块负责接收任务、调度执行并记录结果;具体业务操作由处理器完成。
这句话确定了修改入口:调整领取策略,应查看调度部分;增加一种业务任务,应查看处理器扩展方式。边界说明要与实际接口、依赖关系相符,不能只写一段理想中的分层。
主要运行流程
用一段简短说明串起提交、保存、领取、执行和结果写回,标出关键组件或符号,让读者知道去哪里继续阅读。
这里值得解释的是组件之间的协作,不是抄写每个方法内部的分支。
约束及其保障方式
“领取后进入 running”只是状态变化,还不能证明并发执行器不会领取同一个任务。
文档需要进一步说明:任务领取是否依赖条件更新、事务或其他机制?这些机制保证了什么,又没有保证什么?
尤其不能把“同一时刻只有一个执行器领取”直接写成“业务操作恰好执行一次”。如果业务操作已经成功,但结果写回失败,后续重试仍可能重复产生副作用。
这种跨越多个步骤才能发现的风险,正是文档值得解释的内容。
失败和恢复边界
有重试次数限制,不等于具备完整恢复能力。还应检查:
- 哪些失败允许重试,是否有等待间隔?
- 执行器在
running状态下崩溃,任务如何处理? - 重试时,已经发生的业务副作用如何处理?
- 没有自动恢复时,是否需要人工介入?
如果实现尚未覆盖某种情况,就记录为限制或待解决问题。不要为了让文档看起来完整,补上一套代码里不存在的恢复机制。
已确认的决策理由
假设代码明确限制最多重试三次,但没有任何记录解释这个数字。
可以写:
当前最多额外重试三次。该次数选择的理由未记录。
不能直接写:
为了平衡成功率与系统负载,系统采用三次重试。
后一句可能听起来合理,但它只是对实现的解释,不是已经证实的决策理由。若没有当时的讨论、决策记录或负责人确认,就应保留未知。
四、如何让 AI 协助整理,而不编造 Why
让 AI 读取需求说明和开发对话,然后“总结一份设计文档”,容易把原先的计划写成当前实现。
更稳妥的方式,是先区分要确认的问题,再找对应证据。下面是本文建议的整理方式,不是某个文档模板规定的统一流程。
| 要确认的问题 | 优先查看的材料 | 需要注意 |
|---|---|---|
| 当前实际如何工作 | 当前代码、有效配置、相关测试 | 测试未必覆盖所有路径;默认配置未必等于部署配置 |
| 预期应该如何工作 | 已接受的需求、接口契约、测试断言 | 与实现有冲突时,需要明确记录差异 |
| 为什么选择当前方案 | ADR、PR 讨论、明确的决策说明 | 必须确认材料对应的是当前方案,而非已经放弃的设想 |
| 最初准备怎么做 | 实现计划、开发对话 | 可作背景,但不能单独证明已经落地 |
这比给所有材料排一条绝对优先级更准确。代码适合证明当前行为,决策记录适合解释选择理由;当两者矛盾时,需要调查,而不是简单让其中一方覆盖另一方。(这里推荐人工 Review + AI Review,不要完全交给 AI 来做,避免幻觉)
第一步:先整理事实与缺口
先让 AI 输出主要流程、关键约束、证据位置和无法确认的部分。
每条重要判断都应能回到对应材料。例如:重试次数来自哪里,领取逻辑由哪个入口触发,某项设计理由记录在哪次讨论中。
找不到的部分直接标为“未找到证据”或“待确认”。没有找到证据不等于一定没有相关实现,也不等于该设计不存在,不然 AI 可能会编造一个不存在的设计。
第二步:人工核对高风险结论
重点核对涉及并发、状态转换、重试、资源释放和权限边界的判断。AI 找到了相关符号,不代表已经完整理解其调用条件。
对设计理由尤其要谨慎。如果负责人只能给出今天的解释,应标注为当前补充说明,不要伪装成当初已有记录的决策。因为 AI 容易根据代码反推出一个设计理由,这会成为隐患的开端。
第三步:再组织成长期文档
确认事实以后,再让 AI 按职责、流程、约束、决策和限制组织内容。下面这段 prompt 作为一个参考模板,各位可自行扩展:
根据已确认的材料整理模块设计文档:
1. 区分当前实现、预期行为和未来计划。
2. 重要行为和约束注明证据位置。
3. 不根据实现反推并编造设计理由。
4. 实现与文档不一致时,列出冲突,不自行替双方作决定。
5. 缺少理由时标记“原因未记录”;无法确认行为时标记“待核验”。
6. 说明失败处理、恢复方式及已知限制,不补写不存在的能力。
7. 优先解释跨组件机制,不逐个复述方法。
五、文档放在哪里,才不会越写越乱?
项目变复杂后,可以把不同层次的信息分开。下面是一种可选的组织方式,也是我正在使用的,不是必须一次建齐的目录标准。
ARCHITECTURE.md
docs/
├── design/
│ ├── task-scheduling.md
│ └── authentication.md
└── adr/
└── 0001-example-decision.md
ARCHITECTURE.md:这里的架构不是前端或后端的架构,而是一个项目级别的架构地图,主要用于帮助用户快速定位和理解代码。design-xxx.md:模块级别的长期设计文档adr-xxx.md:记录重要的架构决策历史
项目架构地图:帮助定位
ARCHITECTURE.md 说明项目解决什么问题、主要模块如何组织、边界在哪里,以及有哪些全局约束。
有一个社区主流的模板你可以参考:就是 matklad 在 ARCHITECTURE.md 一文中强调保持简短,描述稳定的代码地图和架构不变量,避免深入每个模块的内部细节。
因此,项目地图不必成为一份随每个函数变化的说明书。上面教学示例的详细重试机制,放在任务模块的文档里更合适。
模块设计文档:解释当前机制
docs/design/ 下的文档负责解释一个模块的职责、运行流程、约束、失败处理和限制。
小模块可以只写几段。需要更多结构时,可以借鉴 arc42 并按需裁剪。arc42 提供 12 个章节,但使用它不等于必须填满所有章节,也不局限于大型项目。
ADR:保留重要决策
ADR(架构决策记录)围绕一次重要选择展开:当时的问题是什么,考虑了哪些方案,最终选了什么,理由和后果是什么。
MADR 提供了相应模板,其中一些部分是可选的。它适合补充决策历史,不适合独自承担整个模块的运行说明。
这三类文档允许互相引用,但不必重复粘贴。例如,模块文档简述当前采用的方案,并链接相关 ADR;详细备选方案和当时的权衡留在 ADR 中。
六、如何维护:更新机制,不重新生成历史
生成第一版文档只是开始。后续修改代码时,需要检查这次变化影响了哪类知识。
| 变更 | 文档处理方式 |
|---|---|
| 局部重构,外部行为和设计边界不变 | 通常不需要修改高层文档;若引用的关键符号失效,则修正定位信息 |
| 模块的数据流、状态转换、失败处理或约束变化 | 更新对应模块文档 |
| 模块职责、依赖方向或系统边界变化 | 检查项目架构地图及相关模块文档 |
| 重要方案被替代 | 按项目约定新增决策记录,标明与旧决策的关系,保留旧记录 |
可以把“是否影响设计文档”加入变更检查:不要求每个 PR 都改文档,但需要判断这次修改是否让已有说明失真。
对于稳定的项目地图,matklad 的建议是定期回顾,而不是追求与代码细节实时同步。模块文档则应在其描述的关键机制变化时校准。两者维护频率不同,不能用同一条规则一概而论。
如果使用 AI 更新,先让它指出现有文档中受影响的段落,再给出局部修改。整篇重新生成可能抹掉原有约束、已知限制和决策依据,也会增加审查成本。
当文档开始大量重复代码、读者必须反复修正过期细节时,还应考虑删减,而不只是继续补充。长期维护的目标是保留有用的理解线索,不是保存尽可能多的文字。
七、将文档整理流程封装为 Skill
如果经常需要整理或更新设计文档,可以把重复使用的流程封装为一个 Skill:模板作为参考文件,SKILL.md 说明如何查找证据、区分实现与计划,以及更新已有文档。
前面提到的“不编造设计理由”“标记待核验内容”“优先局部更新”等规则,也可以一起保留。具体的模块结构、运行机制和设计取舍,仍需从当前项目中核对,不能由模板预先填好。
这样“偷懒”省掉的是每次重新交代整理要求,而不是核对事实和审查文档。
总结
AI Coding 之后是否需要沉淀设计文档,取决于后续理解和修改所需的知识,而不是这次生成了多少代码。
如果实现很局部,代码、测试和注释已经能解释清楚,就不必另写一篇。如果理解一个能力需要跨组件推理,或者存在容易误改的约束与重要取舍,就值得留下简短、可核对的说明。
整理时要分清:代码能确认哪些行为,历史材料记录了哪些理由,还有哪些问题尚未得到解释。可以让 AI 帮忙寻找和组织证据,但缺少理由时,留下“未记录”比补上一段顺畅的解释更可靠。
从一个最难理解、最担心误改的模块开始即可。先说明它负责什么、如何运转、哪些条件不能破坏,以及已知的限制,再决定是否需要更完整的文档结构。
References
- matklad:ARCHITECTURE.md:简短的项目架构地图、代码定位、边界和架构不变量。
- arc42 官方概览:可按需裁剪的架构文档模板。
- MADR:Markdown 架构决策记录模板与说明。
- Simon Brown:The Software Guidebook:进一步阅读。其书籍介绍与目录涵盖代码之外的补充文档、运行维护和决策记录;本文未对全书作逐章解读。
