重构之后,文档怎样才不会再次过期
很多项目的文档并不是一开始就不可用,而是在几轮需求之后慢慢失真:目录已经迁移了,README 还写着旧命令;接口已经改了,架构图还停留在上个版本;新人照着文档找入口,最后还是只能全局搜索。
所以重构时,文档的目标不应该只是“写一份完整说明”。更好的目标是:让文档本身变成项目的一部分,能随着代码变化低成本更新。
我更倾向把它拆成两类内容:
- 人工维护:架构原则、业务边界、关键决策、约定和取舍。
- 自动同步:目录树、路由表、入口清单、接口索引、模块依赖图。
前者需要人解释“为什么”,后者应该尽量交给脚本回答“现在是什么”。
先体检,再重构
重构最容易踩的坑,是先设计一个理想目录,然后立刻开始搬文件。目录变漂亮了,但业务边界、依赖方向和运行方式没有一起变清楚,维护成本可能只是换了一种形态。
更稳的第一步是项目体检。先记录真实现状:
- 项目的启动入口在哪里。
- 页面、服务、数据模型、工具函数分别集中在哪些目录。
- 哪些模块被大量引用,属于事实上的核心层。
- 哪些依赖方向不清晰,比如 UI 层直接调用底层实现。
- 哪些文件职责过宽,已经很难安全修改。
- 当前构建、测试、打包命令是否可靠。
这一步的产物不需要漂亮,它要诚实。可以先生成一份 ProjectStructure.md,只描述真实文件树和当前职责,不急着写理想架构。
重构的目标是边界,不是目录
好维护的项目通常不是因为目录名字高级,而是因为边界稳定。一个功能应该知道自己能依赖什么、对外暴露什么、内部实现藏在哪里。
一个常见的目标结构可以是这样:
src/
app/ # 应用启动、路由、全局装配
features/ # 按业务功能组织的模块
shared/ # 可复用 UI、工具、通用类型
services/ # 外部服务、接口客户端、平台能力适配
domain/ # 核心业务模型与规则
这只是示例,不应该机械套用。真正应该固定下来的是依赖规则:
flowchart TD App[app] --> Features[features] Features --> Domain[domain] Features --> Shared[shared] Features --> Services[services] Services --> Domain Shared --> Domain
当依赖方向明确之后,文档才有长期价值。因为它不只是解释“文件在哪”,还解释“代码应该往哪里放”。
文档应该分层
重构后的文档可以按读者任务来组织,而不是按作者心情堆在一个大文件里。
README.md 负责让人跑起来:
- 环境要求。
- 安装依赖。
- 本地启动。
- 测试和构建命令。
- 常见问题。
docs/Architecture.md 负责解释系统形状:
- 分层方式。
- 依赖方向。
- 关键模块职责。
- 不推荐的调用方式。
docs/ProjectStructure.md 负责索引目录:
- 当前目录树。
- 每个一级目录的职责。
- 重要入口文件。
- 生成时间和同步命令。
docs/FeatureMap.md 负责连接业务和代码:
- 功能名称。
- 相关页面。
- 相关状态、接口、模型。
- 修改时需要关注的路径。
docs/DataFlow.md 负责解释关键流程:
- 数据从哪里来。
- 在哪里转换。
- 哪些状态会影响 UI。
- 错误、空态、加载态怎么流转。
docs/adr/ 负责记录决策:
- 为什么这么拆模块。
- 为什么替换某个库。
- 为什么保留某段历史逻辑。
- 哪些方案被放弃了。
ADR 不必写得很重,一个文件能说清背景、决策、影响就够了。
自动同步的部分,不要靠人记
最容易过期的文档,往往也是最适合自动生成的文档。比如目录树、路由清单、导出入口、接口定义索引,这些信息都可以从代码里读出来。
可以给项目加一个命令:
{
"scripts": {
"docs:update": "node scripts/update-docs.mjs"
}
}
脚本不需要一开始就很复杂。第一版可以只做几件事:
1. 扫描 src 目录,生成目录树。
2. 识别入口文件和路由文件。
3. 统计 features 下的模块。
4. 更新 docs/ProjectStructure.md 中的自动生成区块。
5. 保留人工维护区块不覆盖。
关键是把文档拆成“脚本可覆盖”和“人工可编辑”两个区域:
# Project Structure
## 维护说明
运行 `npm run docs:update` 刷新自动生成内容。
## 人工说明
这里写目录设计原则、命名约定、例外情况。
<!-- AUTO-GENERATED:START -->
这里由脚本更新。
<!-- AUTO-GENERATED:END -->
这样后续代码变了,不需要人手动同步整篇文档,只需要跑一次命令。
重构过程也要留下轨迹
重构不是一次性工程,更像是一串可验证的小迁移。每次迁移最好满足三个条件:
- 改动范围清楚。
- 行为验证清楚。
- 文档同步清楚。
例如一次迁移可以这样定义:
目标:把用户配置相关逻辑从通用工具目录迁移到 settings feature。
验证:原有配置读取、保存、默认值回退行为保持一致。
文档:更新 FeatureMap 和 ProjectStructure 的相关条目。
这比“整理目录结构”更容易检查,也更容易回滚。
一个实用的落地顺序
我会按这个顺序推进:
- 扫描当前项目,生成第一版真实结构文档。
- 标出核心入口、业务模块、共享工具和高耦合区域。
- 制定目标分层和依赖规则。
- 先迁移一个低风险模块,验证目录和文档同步方式。
- 加入
docs:update,自动刷新结构类文档。 - 按模块逐步重构,每次迁移都补一条决策或变更记录。
- 最后把 README、架构文档、功能地图收敛成稳定版本。
这个顺序的好处是,文档不会等到重构结束才补。它从第一天就参与重构,帮助我们判断每一步是不是让项目更清楚。
判断文档有没有用
一份重构文档是否可维护,可以用几个问题检查:
- 新人能不能在十分钟内跑起项目。
- 修改一个功能时,能不能从功能地图找到主要文件。
- 新增模块时,能不能知道应该放在哪个目录。
- 文档里的目录树是否能通过命令刷新。
- 重要架构决定是否能追溯到原因,而不只是看到结果。
- 删除或迁移模块时,是否知道要同步哪些文档。
如果这些问题都有答案,文档就不只是“项目说明”,而是维护系统的一部分。
结语
重构后的文档最怕两件事:过度依赖人工记忆,或者过度追求一次性完整。
更好的做法是先承认现实:代码会变,目录会变,架构也会变。于是文档体系应该允许变化发生,并且让同步成本足够低。
人工写清楚原则和决策,脚本同步事实和索引。这样文档才不会在重构完成那天达到巅峰,然后开始缓慢过期。