技术设计文档模板:让方案从目标走到验证
文档服务评审
我参加过不少技术评审会,最常见的情况是:作者讲完方案,会议室安静几秒,然后有人问「你到底要解决什么问题?」。这往往不是方案本身有问题,而是文档没有把问题讲清楚,评审者缺少判断的锚点。
技术设计文档的目标不是把所有细节写满,而是让评审者在有限时间内判断方案是否可落地。读者需要看到问题、约束、方案、风险和验证方式。模板的作用是帮助团队减少遗漏,而不是制造格式负担。
这篇文章会拆解一份技术设计文档应该包含哪些章节,每个章节怎么写,以及我在实际项目中踩过的坑。内容基于 Google Fuchsia 的 RFC 最佳实践、Industrial Empathy 的设计文档方法论,以及 AWS 的 ADR 流程等权威资料,结合我自己的工程经验整理而成。
原理:为什么文档结构影响方案质量
漏斗结构
Jesper Agerlin 在 Industrial Empathy 的博客中提出,设计文档应该采用「漏斗结构」——从宽泛的背景逐步收窄到具体设计。先让读者理解问题全貌,再进入技术细节。如果一上来就贴 API 定义和数据库表结构,评审者不知道这些设计是为了什么,很容易在无关紧要的细节上纠缠。
Google Fuchsia 项目的 RFC 最佳实践也强调:大部分 RFC 本质上就是设计文档,核心目的是获得受影响的利益相关者的认可。作者需要「假设评审者对方案毫不知情,所有关键的线下讨论都必须写进正文」。
写取舍比写结论更有价值
技术方案最有价值的部分是「为什么选这个方案,放弃了哪些方案」。只写最终结果,评审者无法判断作者是否考虑过风险。Fuchsia 的建议是:备选方案不需要很多,但要真实——把每个方案的优劣和放弃理由写清楚。
AWS 的 ADR(架构决策记录)流程更进一步:ADR 一旦通过就不可修改,如果后来需要改变方向,必须写一份新的 ADR 来替代旧的。这种机制倒逼作者在写的时候就认真思考取舍,因为「写下来」本身就是一个思考过程。
非目标的价值
很多人写技术方案时只写目标,不写非目标。非目标的作用有两个:一是强迫作者划定边界,避免范围蔓延;二是帮助评审者检查方案是否越界。如果文档声称「不做 X」,但详细设计里到处都是 X 的痕迹,说明方案还没有想清楚。
三个真实案例
案例一:目标模糊导致评审失焦
场景:团队需要把订单服务的查询延迟从 500ms 降到 100ms。我写了一份技术方案,目标是「优化订单查询性能」。
翻车:评审会上,有人问「500ms 的瓶颈在数据库还是在应用层?」,有人问「是只优化查询接口还是所有接口?」,还有人问「降到的 100ms 是 P50 还是 P99?」。我当场都答不上来,会议开了一个小时没有任何结论。
修复:回去后重写了目标部分,把模糊的定性描述改成了带上下文的定量描述:
## ❌ 坏写法:目标模糊
### 目标
优化订单查询性能,提升用户体验。
## ✅ 好写法:目标具体、可验证
### 背景
订单列表接口(GET /api/orders)当前 P99 延迟 520ms,
其中数据库查询占 380ms(73%),序列化占 90ms(17%)。
日均调用量 1200 万次。
### 目标
- 将 GET /api/orders 的 P99 延迟降至 100ms 以下
- 不改变现有 API 契约和返回格式
- 支持未来 6 个月的流量增长(预估 2x)
### 非目标
- 不优化写入链路(POST /api/orders)
- 不迁移现有数据库,不引入新的存储引擎
- 不做读写分离(后续独立项目)目标一旦具体,评审者就能聚焦在「这个方案能不能达到 100ms」上,而不是质疑目标本身。
案例二:缺少备选方案导致决策不可信
场景:需要给平台加一个全文搜索功能。我直接选了 Elasticsearch,方案里写了索引设计、分片策略、集群部署。
翻车:评审时 leader 问「为什么不用 PostgreSQL 的全文检索?我们的数据量是不是真的需要 ES?」。我只能说「ES 是业界标准」,没有给出对比分析。最终方案被打回,要求补充备选方案对比。
修复:补充了三个方案的对比:
## ❌ 坏写法:直接给结论
### 方案
使用 Elasticsearch 8.x 构建全文搜索服务。
(下面 3 页全是 ES 的部署和索引设计)
## ✅ 好写法:列出备选方案和取舍
### 备选方案
#### 方案 A:PostgreSQL 全文检索(tsvector + GIN 索引)
- 优势:无新基础设施,运维成本低,团队熟悉 PG
- 劣势:中文分词需额外扩展(zhparser),500 万条以上性能下降明显
- 适用条件:数据量 < 500 万,查询 QPS < 200
#### 方案 B:Elasticsearch 8.x
- 优势:成熟的中文分词(IK Analyzer),水平扩展能力强,聚合查询丰富
- 劣势:引入新基础设施,集群运维复杂度高,内存占用大(预估 3 节点 × 16GB)
- 适用条件:数据量 > 500 万,需要复杂聚合和模糊搜索
#### 方案 C:Meilisearch
- 优势:轻量级,部署简单,内存占用小,内置中文分词
- 劣势:社区相对较小,复杂查询能力弱于 ES,分布式支持不够成熟
- 适用条件:数据量中等,搜索需求简单,团队规模小
### 选型结论
选择方案 B(Elasticsearch)。原因:
1. 当前商品数据量 800 万条,且每月增长 50 万条,PG 全文检索无法满足
2. 产品需要多维度筛选和聚合统计,Meilisearch 不支持复杂聚合
3. 团队已有 2 人具备 ES 运维经验,学习成本可控
放弃方案 A 的关键原因是数据量已超出 PG 全文检索的舒适区。
放弃方案 C 的关键原因是产品路线图要求多维度聚合,Meilisearch 不支持。备选方案不是为了凑数,而是让评审者看到你的思考过程。即使最终选的方案不变,有了对比分析,评审者对结论的信任度会高很多。
案例三:没有验证计划导致上线翻车
场景:设计了一个基于 Redis 的分布式限流方案,文档里写了算法、数据结构、接口定义,但没有写怎么验证。
翻车:上线后发现,在 Redis 主从切换时,限流计数器会丢失,导致切换瞬间流量暴增打垮了下游服务。事后复盘时发现,文档里完全没有提到 Redis 故障场景的处理。
修复:在文档中补充了验证计划和失败路径:
## ❌ 坏写法:只写正常路径
### 方案
使用 Redis INCR + EXPIRE 实现滑动窗口限流。
(接口定义和代码示例)
## ✅ 好写法:覆盖正常路径和失败路径
### 验证计划
#### 单元测试
- 滑动窗口计数准确性(边界条件:窗口起止、跨窗口请求)
- 并发场景下计数器原子性(100 并发 INCR,最终值应精确)
- Key 过期后自动清理验证
#### 集成测试
- Redis 正常时的限流行为
- Redis 主从切换时的限流行为(预期:短暂放行,不拒绝正常请求)
- Redis 完全不可用时的降级行为(预期:切换到本地令牌桶,允许 1.5x 正常流量)
#### 灰度发布
- 第一阶段:1% 流量,观察限流触发率和下游错误率
- 第二阶段:10% 流量,持续 24 小时
- 第三阶段:全量,配合告警面板观察
### 失败路径和降级策略
| 故障场景 | 影响 | 降级策略 | 恢复方式 |
|---------|------|---------|---------|
| Redis 主节点宕机 | 计数器短暂丢失 | 切换到本地令牌桶(允许 1.5x 流量) | Redis 主从自动切换后恢复 |
| Redis 集群不可用 | 所有限流失效 | 降级为网关层全局限流(粗粒度) | Redis 恢复后自动切回 |
| 限流误杀正常请求 | 用户体验下降 | 白名单机制(VIP 用户不受限) | 调整限流阈值 |验证计划不是可选项。如果文档里没写怎么验证,说明作者自己也不确定方案能不能工作。
文档生命周期
一份技术设计文档从起草到落地,要经历几个阶段。每个阶段的重点不同,文档的详细程度也应该不同。
各阶段文档重点
| 阶段 | 文档重点 | 详细程度 | 参与人 |
|---|---|---|---|
| 起草 | 问题定义、目标、备选方案 | 概要级,关键决策写清楚即可 | 作者 |
| 预评审 | 方案可行性、技术选型 | 中等,核心设计要展开 | 2-3 位同事 |
| 正式评审 | 完整方案、风险、验证计划 | 完整,所有评审要点可判断 | 团队 + 利益相关者 |
| 实施中 | 接口定义、数据模型、发布计划 | 精确,可直接拆任务 | 开发 + 测试 |
| 归档 | 决策结论、取舍理由 | 精炼,提炼为 ADR | 团队 |
推荐章节模板
根据文档规模不同,章节可以压缩或展开。以下是一份中等复杂度的技术设计文档推荐章节:
核心章节对照表
| 章节 | 核心问题 | 常见缺失 | 重要程度 |
|---|---|---|---|
| 背景和现状 | 为什么要做这件事? | 只写结论不写数据 | 必须 |
| 目标与非目标 | 做到什么程度?不做什么? | 目标模糊、没有非目标 | 必须 |
| 方案概览 | 整体怎么做? | 直接进入细节、缺少全局视角 | 必须 |
| 详细设计 | 每个模块怎么做? | 缺少接口定义和数据流 | 必须 |
| 备选方案和取舍 | 为什么不选其他方案? | 只有一个方案、没有取舍分析 | 必须 |
| 失败路径和降级 | 出故障怎么办? | 只写正常路径 | 高 |
| 验证计划 | 怎么证明方案可行? | 没有验证计划或只有手动测试 | 高 |
| 发布和回滚计划 | 怎么上线?怎么回滚? | 没有灰度策略和回滚方案 | 高 |
| 观测和告警 | 上线后怎么监控? | 缺少指标和告警阈值 | 中 |
| 时间线和里程碑 | 什么时候完成什么? | 没有时间线或时间线不现实 | 中 |
章节详略判断
| 项目特征 | 建议详略 | 可省略章节 |
|---|---|---|
| 简单功能(< 3 天工作量) | 概要级,1-2 页 | 备选方案可压缩为一段话 |
| 中等功能(1-2 周) | 标准模板,3-5 页 | 全部章节都需要 |
| 复杂系统(> 1 个月) | 详细设计,5-15 页 | 全部章节展开,可拆分子文档 |
| 基础设施变更 | 详细 + 风险评估 | 不可省略任何章节 |
好文档与坏文档的对比
文档结构对比
| 维度 | 好文档 | 坏文档 |
|---|---|---|
| 开头 | 用数据描述现状和问题 | 「为了提升用户体验」(没有数据) |
| 目标 | 具体、可验证、有数字 | 模糊、定性、无法判断完成 |
| 非目标 | 明确列出不做的事 | 没有非目标,范围随时蔓延 |
| 方案 | 2-3 个备选方案 + 取舍分析 | 只有一个方案,直接给结论 |
| 架构图 | 有数据流和模块边界 | 只画组件不画数据流 |
| 风险 | 列出故障场景和降级策略 | 没有风险章节或只写「无风险」 |
| 验证 | 有测试计划和灰度策略 | 没有验证计划 |
| 篇幅 | 适中,每段都有用 | 过长(堆砌无关细节)或过短(关键信息缺失) |
背景写法对比
## ❌ 坏写法:没有数据支撑
### 背景
当前系统性能较差,用户反馈较多,需要进行优化。
## ✅ 好写法:用数据说明问题
### 背景
近 30 天用户投诉工单中,42%(187/445)与「订单加载慢」相关。
监控数据显示:
- 订单列表接口 P99 延迟 520ms(SLA 目标 < 200ms)
- 高峰期(10:00-12:00)错误率从 0.1% 升至 1.8%
- 数据库慢查询日志中,订单相关查询占 68%
不做优化预计下月流量增长后,P99 将突破 800ms。风险评估写法对比
## ❌ 坏写法:风险章节敷衍
### 风险
- 本方案风险较低
- 已充分考虑各种情况
## ✅ 好写法:具体风险和应对
### 风险评估
| 风险 | 概率 | 影响 | 应对措施 | 负责人 |
|------|------|------|---------|--------|
| Redis 集群容量不足 | 中 | 高 | 提前申请 2 倍内存预留 | @zhangsan |
| 新限流规则误杀正常请求 | 中 | 中 | 灰度期间设置白名单,监控误杀率 | @lisi |
| 下游服务未适配限流响应 | 低 | 高 | 提前通知下游团队,提供适配文档 | @wangwu |
| 回滚时数据不一致 | 低 | 中 | 限流状态不持久化,回滚无副作用 | @zhangsan |发布计划写法对比
## ❌ 坏写法:发布计划空洞
### 发布计划
- 周三上线
- 如有问题回滚
## ✅ 好写法:发布计划具体可执行
### 发布计划
#### 前置条件
- [ ] Redis 集群扩容完成(@zhangsan,周二前)
- [ ] 下游服务适配限流 429 响应(@wangwu,周二前)
- [ ] 监控面板和告警配置完成(@lisi,周二前)
- [ ] 回滚脚本测试通过(@zhangsan,周二前)
#### 灰度策略
| 阶段 | 流量比例 | 持续时间 | 通过条件 | 操作 |
|------|---------|---------|---------|------|
| 第一阶段 | 1% | 4 小时 | 限流触发率 < 0.5%,下游错误率不上升 | 手动扩大 |
| 第二阶段 | 10% | 24 小时 | 同上 + 无用户投诉 | 手动扩大 |
| 第三阶段 | 50% | 12 小时 | 同上 | 手动扩大 |
| 第四阶段 | 100% | 持续观察 | 持续 48 小时无异常 | 全量上线 |
#### 回滚方案
- 触发条件:下游错误率 > 2% 或限流误杀率 > 1%
- 回滚步骤:关闭限流开关(配置中心,无需发版)→ 确认下游恢复 → 排查问题
- 回滚时间:< 1 分钟(配置开关,无需重启)RFC、ADR、设计文档的关系
团队里经常有这些概念的混淆。RFC、ADR 和技术设计文档不是同一个东西,但它们是互补的。
| 维度 | 技术设计文档 | RFC | ADR |
|---|---|---|---|
| 目的 | 描述完整技术方案 | 征求团队意见和达成共识 | 记录单个架构决策 |
| 篇幅 | 中等到长(3-15 页) | 中等(2-8 页) | 短(1-2 页) |
| 生命周期 | 随项目演进可更新 | 通过后变为历史记录 | 通过后不可变,需要新 ADR 替代 |
| 评审方式 | 评审会 + 书面反馈 | PR Review + 评审会 | PR Review |
| 存放位置 | Wiki 或文档平台 | 代码仓库或专用仓库 | 代码仓库 doc/adr/ 目录 |
| 粒度 | 一个完整功能或系统 | 一个变更提案 | 一个决策点 |
协作关系
实际操作中,一份设计文档可能覆盖多个决策点,每个决策点可以提炼为独立的 ADR。RFC 更偏向于征询意见的过程,通过后转化为设计文档或直接进入实施。AWS 的 ADR 流程建议:代码评审时,评审者应该检查代码变更是否违反了已有的 ADR,如果违反需要作者修改代码或提出新的 ADR。
自检清单
文档写完后,可以用这份清单自查。按阶段分组,覆盖从起草到归档的全流程。
起草阶段
- 背景部分有具体数据,不是定性描述
- 目标可验证(有数字、有指标、有时间范围)
- 非目标明确列出,边界清晰
- 至少列出 2 个备选方案,每个方案有优劣分析
- 选型结论有明确理由,不是「业界标准」或「团队熟悉」
设计阶段
- 架构图包含模块边界和数据流方向
- 接口定义完整(入参、出参、错误码)
- 数据模型变更有迁移方案
- 失败路径有降级策略,不是「无风险」
- 依赖的外部服务有超时、重试、熔断策略
评审阶段
- 找过 2-3 位同事预评审,不是直接上评审会
- 评审会前发出文档,给评审者至少 1 天阅读时间
- 评审意见有书面记录,结论写进文档
- 未解决的问题单独列出,不藏在正文里
验证与发布阶段
- 有单元测试计划,覆盖核心逻辑
- 有集成测试或端到端测试计划
- 有灰度发布策略(流量比例、持续时间、通过条件)
- 有回滚方案,且回滚步骤可执行(不是「回滚到上一版本」)
- 有监控面板和告警配置计划
归档阶段
- 核心决策已提炼为 ADR
- 文档已标注最终状态(通过/搁置/废弃)
- 相关方已知晓文档最终结论
模板的使用原则
模板不是教条。不同规模的项目应该用不同详细程度的模板。我见过一些团队把所有项目都套同一份详细模板,结果一个简单的配置变更也要写 5 页文档,最后大家都不愿意写文档了。
几个原则:
- 按需裁剪:简单功能可以压缩到 1-2 页,复杂系统再展开。核心是「目标和取舍」不能省。
- 先写骨架再填肉:先把所有章节标题列出来,即使某些章节暂时写不出来,也能看出文档还缺什么。
- 不要为了填满模板而写废话:如果一个章节没有内容可写(比如简单功能没有备选方案),直接写「本项目不涉及,原因:XXX」,不要硬编。
- 文档是给人读的,不是给机器读的:语气平实,避免空话套话。每一段都要有信息增量。
总结
技术设计文档的核心价值是让评审者能在有限时间内做出判断。好的文档应该做到:读完背景就知道问题有多严重,读完目标就知道做到什么程度算成功,读完备选方案就知道作者考虑过哪些可能性,读完风险就知道出故障时不会手忙脚乱。
文档不是写完就结束了。它是一个思考工具——写的过程就是理清思路的过程。如果你写着写着发现某个章节写不出来(比如想不出备选方案,或者写不出验证计划),那往往不是文档的问题,而是方案本身还没有想清楚。
参考资料
- RFC best practices — Fuchsia (Google) — Google Fuchsia 项目的 RFC 编写与评审最佳实践
- A Design Doc — Industrial Empathy — Jesper Agerlin 关于设计文档结构和方法论的经典文章
- ADR Process — AWS Prescriptive Guidance — AWS 的架构决策记录流程指南
- Software Engineering RFC and Design Doc Examples — Pragmatic Engineer 整理的 RFC 模板和设计文档案例集
- Goals and Failure Modes for RFCs and Technical Design Documents — RFC 和设计文档的目标与常见失败模式
- Writing Technical Specifications: The Art of Tailoring RFCs — 如何根据项目规模裁剪 RFC 的详细程度
- 架构决策记录概览 — Google Cloud — Google Cloud 的 ADR 使用指南
- ADR 流程 — Azure — Microsoft Azure 的 ADR 维护最佳实践