Michael Lynch
2026-06-24
D
原文
---
title: "如何写出一份有效的软件设计文档"
author: "Michael Lynch"
source_url: "https://refactoringenglish.com/excerpts/write-an-effective-design-doc/"
published_at: "2026-06-24T00:00:00+00:00"
fetched_at: "2026-09-17T15:46:07Z"
updated_at: "2026-09-17T15:50:52Z"
language: "zh"
review_status: "draft"
---
# 如何写出一份有效的软件设计文档
一份好的设计文档可以为你省下数年的开发时间。写设计文档会迫使你提前想清楚重要决策,以免把时间浪费在错误的实现方案上。它也是团队成员与合作团队协调设计决策的最佳方式。
我曾以开发者身份在 Google、Microsoft 和[自己创办的公司](https://mtlynch.io/projects/)里撰写设计文档。具体做法各不相同,背后的原则却始终一致:设计文档要阐明你正在解决的难题,并帮助团队成员向你反馈意见。
下面,我会介绍自己撰写有效设计文档的方法,并解释哪些内容应该写进设计文档,哪些不应该。
- [设计文档示例](https://refactoringenglish.com/excerpts/write-an-effective-design-doc/#an-example-design-doc)
- [什么时候应该写设计文档?](https://refactoringenglish.com/excerpts/write-an-effective-design-doc/#when-should-you-write-a-design-doc)
- [应该在设计文档上投入多少精力?](https://refactoringenglish.com/excerpts/write-an-effective-design-doc/#how-much-should-you-invest-into-your-design-doc)
- [设计文档应该包含什么?](https://refactoringenglish.com/excerpts/write-an-effective-design-doc/#what-belongs-in-a-design-doc)
- [选错的代价有多大?](https://refactoringenglish.com/excerpts/write-an-effective-design-doc/#whats-the-cost-of-getting-it-wrong)
- [设计文档的组成部分](https://refactoringenglish.com/excerpts/write-an-effective-design-doc/#components-of-a-design-doc)
- [标题](https://refactoringenglish.com/excerpts/write-an-effective-design-doc/#title)
- [元数据](https://refactoringenglish.com/excerpts/write-an-effective-design-doc/#metadata)
- [目标概述](https://refactoringenglish.com/excerpts/write-an-effective-design-doc/#objective)
- [背景](https://refactoringenglish.com/excerpts/write-an-effective-design-doc/#background)
- [相关文档](https://refactoringenglish.com/excerpts/write-an-effective-design-doc/#related-documents)
- [目标](https://refactoringenglish.com/excerpts/write-an-effective-design-doc/#goals)
- [非目标](https://refactoringenglish.com/excerpts/write-an-effective-design-doc/#non-goals)
- [使用场景](https://refactoringenglish.com/excerpts/write-an-effective-design-doc/#scenarios)
- [架构图](https://refactoringenglish.com/excerpts/write-an-effective-design-doc/#diagrams)
- [术语表](https://refactoringenglish.com/excerpts/write-an-effective-design-doc/#glossary)
- [约束条件](https://refactoringenglish.com/excerpts/write-an-effective-design-doc/#constraints)
- [服务级别目标(SLO)](https://refactoringenglish.com/excerpts/write-an-effective-design-doc/#service-level-objectives-slos)
- [监控与告警](https://refactoringenglish.com/excerpts/write-an-effective-design-doc/#monitoring--alerting)
- [时间表](https://refactoringenglish.com/excerpts/write-an-effective-design-doc/#timeline)
- [接口](https://refactoringenglish.com/excerpts/write-an-effective-design-doc/#interfaces)
- [依赖与基础设施](https://refactoringenglish.com/excerpts/write-an-effective-design-doc/#dependencies--infrastructure)
- [安全](https://refactoringenglish.com/excerpts/write-an-effective-design-doc/#security)
- [隐私](https://refactoringenglish.com/excerpts/write-an-effective-design-doc/#privacy)
- [法律因素](https://refactoringenglish.com/excerpts/write-an-effective-design-doc/#legal-considerations)
- [日志](https://refactoringenglish.com/excerpts/write-an-effective-design-doc/#logging)
- [待解决问题](https://refactoringenglish.com/excerpts/write-an-effective-design-doc/#open-issues)
- [已解决问题](https://refactoringenglish.com/excerpts/write-an-effective-design-doc/#resolved-issues)
- [考虑过的替代方案](https://refactoringenglish.com/excerpts/write-an-effective-design-doc/#alternatives-considered)
- [推动设计文档完成评审](https://refactoringenglish.com/excerpts/write-an-effective-design-doc/#driving-your-design-doc-through-review)
## 设计文档示例
关于设计文档,我最常被问到的问题是:哪里能找到一份好的设计文档?我从未见过一份自己认为质量很高的公开设计文档。我写过的文档都留在付费让我撰写它们的公司里。
因此,我按照本文介绍的原则,[从头写了一份设计文档](https://refactoringenglish.com/excerpts/write-an-effective-design-doc/little-moments-design-doc/)。它描述了我[正在开发的一款真实 Web 应用](https://codeberg.org/mtlynch/little-moments)的设计。
<video controls>
<source src="https://refactoringenglish.com/excerpts/write-an-effective-design-doc/little-moments-demo.mp4" type="video/mp4">
你的浏览器不支持 video 标签。
</video>
我在编写任何代码之前就完成了设计文档,并且在[实现应用](https://codeberg.org/mtlynch/little-moments#status)时一直遵循这套设计。
- [Little Moments 设计文档](https://refactoringenglish.com/excerpts/write-an-effective-design-doc/little-moments-design-doc/)
这份设计比我通常为个人业余项目编写的文档更加详尽,但如果要在一个正式项目中协调多人合作,我写的设计文档大致就是这个篇幅和深度。
## 什么时候应该写设计文档?
项目越复杂、风险越高,设计文档的价值就越大。
可以思考以下问题:
- 是否需要多人协调工作来实现这套设计?
- 项目是否需要三个月以上的全职开发?
- 这套实现是否会在生产环境中运行数年?
- 项目是否涉及跨团队协作?
- 项目目标和需求是否含糊不清?
- 是否存在能在设计阶段避免的灾难性风险,例如安全漏洞或法律风险?
只要有一个问题的答案是“是”,花精力写设计文档就很可能值得;如果有两个或更多问题的答案是“是”,那就几乎肯定值得。
## 应该在设计文档上投入多少精力?
设计文档可以是简单的一页纸,也可以是长达 50 页、需要五个不同团队签字批准的文件。你需要判断多大的详细程度才合适。
没有一条放之四海而皆准的规则,规定设计文档应该写多久,就像没有规则规定代码应该测试到什么程度。投入多少取决于团队的目标、风险、期限和文化。有时,设计文档最合理的投入就是零。
## 设计文档应该包含什么?
如果把所有可能的细节都写进设计文档,就等于在设计阶段已经完成了实现本身。这反而违背了设计文档的初衷。
判断一个决策是否该写进设计文档,可以问一个简单的问题:如果选错了,代价是什么?
### 选错的代价有多大?
并非所有设计决策都同等重要,有些选择比另一些更难推翻。
例如,如果你用 C++ 构建 Web 应用,写到 20 万行时才发现 Ruby on Rails 更合适,那就进退两难了。从头重写[绝不会奏效](https://www.joelonsoftware.com/2000/04/06/things-you-should-never-do-part-i/);即使成功用 Rails 写出了新代码,你仍然要同时维护两套使用截然不同语言的代码。
另一些设计决策则无关紧要。例如,应用要显示 100 篇文章,是一次全部展示,还是每次显示 25 篇,让用户点击“加载更多”再看接下来的 25 篇?
这并不重要。
“加载更多”按钮不属于设计层面的问题。如果选了其中一种方案,用户反馈表明它不合适,你花几个小时就能修正。没必要在设计文档中详细记录完整思考过程,更不该浪费评审轮次争论这件事。
## 设计文档的组成部分
下面列出了设计文档中常见的章节。一般来说,不必在每份文档中包含所有章节,只需选择适合自己的部分。
### 标题
项目首先需要一个标题。人们会在交谈中用它指代这个项目,因此标题要尽量简短、独特,并能让人联想到项目内容。
例如,要在应用服务器与数据库服务器之间添加缓存层,**RecencyBank** 就是一个好名字。它读起来顺口,也能说明项目用途。“Project Flying Silver Horse”则是一个坏名字,因为它又长又不知所云。
### 元数据
元数据虽然乏味,却很实用,能帮助读者了解文档的基本背景:
- 作者是谁?(姓名和邮箱地址)
- 文档是什么时候创建的?
- 哪个 URL 是权威版本?
- 如果组织使用 `http://go/recency-bank` 这类[短链接跳转](https://golinks.github.io/golinks/),这一点尤其重要。
- 谁在什么时候批准了这份文档?
- 适用于文档需要团队成员或合作方签字批准的情况。
> **元数据**
>
> - **URL**:http://go/recency-bank-design
> - **作者**:Michael Lynch([michael@refactoringenglish.com](mailto:michael@refactoringenglish.com))
> - **创建时间**:2026-06-22
> - **状态**:已批准
> - alan@ 于 2026-07-14 签字批准
> - betty@ 于 2026-07-15 签字批准
### 目标概述
目标概述用一句话说明项目的目的。它应该出现在文档第一页,并使用任何利益相关方都能理解的直白语言。
> **目标概述**
>
> 在 Trogdor Web 服务器与 Postgres 数据库之间添加缓存层,以提升应用性能。
### 背景
背景章节解释项目的来龙去脉和动机,应该回答以下问题:
- 团队为什么要做这个项目?
- 项目解决什么问题?
- 以前是否尝试过解决这个问题?
> **背景**
>
> 2023 年 Trogdor Web 应用上线时,页面加载时间通常不超过 100ms。三年后,页面加载时长中位数已经膨胀到 600ms,用户因此觉得应用反应迟缓。
>
> 我们调查后发现,数据库查询占页面加载时间的 80%。随着数据存储规模增大,数据库查询也越来越慢。
>
> 我们还发现,95% 的数据库查询都集中在相同的 3% 数据库行上。这种使用模式非常适合内存缓存。缓存可以更快地提供频繁访问的数据,并减少其他所有查询给数据库带来的负载。
> **脱离外部背景,你的设计文档还能让人看懂吗?**
>
> 想一想,在团队成员或合作团队阅读设计文档之前,你会先对他们说些什么。
>
> 再想一想,有些读者会在听到你的任何解释之前就看到文档。因此,他们为了理解文档所需的信息,[都应该出现在文档第一页](https://refactoringenglish.com/blog/useful-feedback-on-design-docs/#write-an-introduction-that-makes-sense-to-everyone)。
### 相关文档
如果项目与其他文档有关联,就应该让读者能轻松找到它们。
可以链接到:
- 项目经理或测试合作方为该项目编写的文档,例如测试计划、功能规格
- 相关系统的设计文档
- 该项目以前版本的设计文档
> **相关文档**
>
> - **测试计划**:http://go/recency-bank-test-plan
> - **Trogdor 性能报告**:http://go/trogdor-perf-2026
### 目标
目标章节描述项目的高层目标。它应该在逻辑上承接背景章节,并说明实现完成后会带来怎样的变化。
不要根据实现细节来设定目标。目标应该说明项目会如何帮助用户、团队或公司。
> 错误示例:用内部实现细节来设定目标
>
> - 在基础设施中加入 Kubernetes。
> 正确示例:根据实际影响设定目标
>
> - 尽量减少部署新版本应用所引发的服务中断。
> **目标**
>
> - 提高用户感受到的 Trogdor Web 应用响应速度。
> - 减轻数据库服务器负载。
### 非目标
目标定义项目范围之内的内容,非目标章节则明确哪些内容不在范围内。
有没有哪些目标,读者可能会误以为属于项目范围?如果有,就把它们明确列为非目标。
> **非目标**
>
> - 构建通用、可复用的缓存系统
> - 我们为 Trogdor Web 应用添加的缓存层会进行针对该应用的优化。将该缓存复用于其他系统不在本项目范围内。
> - 根据地理位置部署缓存
> - 未来可以考虑让缓存部署在离最终用户较近的地区,以降低延迟,但这不属于 v1 的范围。
### 使用场景
如果你的目标是“为图表添加‘以 URL 分享’按钮”,读者可能仍不知道它在实际使用中是什么样子。
使用场景章节可以让读者具体看到,完成后的系统在现实中如何工作。
> **场景:通过 URL 分享报告**
>
> 1. Bob 在 KeyMetrics 仪表盘中创建一份自定义报告。
> 2. Bob 打开菜单栏,点击“分享 > 以 URL 分享”。
> 3. Bob 通过电子邮件把 URL 发给同事 Charlie。
> 4. Charlie 点击链接,以只读模式看到与 Bob 报告完全相同的副本。
### 架构图
架构图的价值非常大,虽然一开始未必看得出来。
作为设计者,你自然明白方案的各个部分如何组合,也能在脑中看见架构。评审者没有这幅图景,因此让他们理解架构的最快方式,就是把它画出来。
[](https://refactoringenglish.com/excerpts/write-an-effective-design-doc/architecture-diagram.svg)
展示简单 Web 应用架构的示例图。
如果不确定图中应该画什么,可以思考以下问题:
- 数据如何流经系统?
- 系统的不同组件如何组合?
- 系统如何与依赖项和下游客户端交互?
- 系统定义了哪些通信协议?
请选择便于修改的绘图工具。我见过开发者在白板上画出漂亮架构图,再拍照放进设计文档。初稿看起来很棒,但之后他们就被这张图困住了:除非从头重画,否则根本无法修改照片。
[Excalidraw](https://excalidraw.com/)、[draw.io](https://www.drawio.com/) 和 [Google Drawings](https://docs.google.com/drawings/) 都是便于修改的流行绘图工具。也可以使用 [Mermaid](https://mermaid.js.org/)、[D2](https://d2lang.com/) 和 [Graphviz](https://graphviz.org/) 等语言,以编程方式生成图表。我用 LLM 帮自己生成绘图代码,体验很好。记得链接到绘图源文件或代码,以便团队成员也能重现这张图。
### 术语表
术语表用来定义读者可能不认识的词。
要认真考虑文档的潜在读者,尤其是刚加入团队的成员和直接团队之外的人。他们能理解文档中提到的内部工具或系统名称吗?
只要可能,就使用受众无需查看术语表便能理解的词。把术语写进术语表总比完全不解释好,但最佳做法是使用大家熟悉的词,或在正文中就地解释,以免读者在文档中来回翻找。
> **术语表**
>
> - **Apposaurus**:团队内部的负载测试工具。我们用 Apposaurus 模拟大量访客涌入 Trogdor Web 应用,以验证应用在预期工作负载下仍能正常运行。
> - **Baba-o-styley**:内部代码检查工具,用于强制执行公司的代码风格约定。
### 约束条件
如果预算、客户、基础设施或依赖项给设计带来了重大约束,就应说明这些约束,让读者理解设计选择的背景。
> **约束条件**
>
> 我们的服务器全部采用 RISC-V,因此所有代码和依赖项都必须能在 RISC-V 架构上运行。
### 服务级别目标(SLO)
SLO 为系统性能设定可衡量的客观指标。你可能听说过服务级别协议(SLA):SLA 其实就是加上未达标经济惩罚的 SLO。
在公司内部,你通常不会因为同事犯错就罚他们的钱(虽然那样好像会很有趣)。所以,设计文档定义的是 SLO,而不是 SLA。
经理可能会要求应用“在移动设备上表现良好”,但这很模糊。经理心中的“表现良好”也许是延迟低于 2ms,而你肯定不想等到代码写完才发现这一点。定义清楚的 SLO 会用具体、客观的方式表达目标,从而消除歧义。
SLO 通常要考虑:
- **运行时间 / 可用性**:系统有多少比例的时间可用?
- **延迟**:服务能以多快的速度完成请求?
- **规模**:系统能处理多大的工作量?
> **服务级别目标**
>
> - Trogdor 面向用户的 HTTP 请求,第 50 百分位延迟:<=200ms
> - Postgres 查询,第 50 百分位延迟:<= 80ms
### 监控与告警
明确了[前述](https://refactoringenglish.com/excerpts/write-an-effective-design-doc/#service-level-objectives-slos) SLO 后,就该考虑如何在生产环境中衡量它们。
验证 SLO 是否达成的最简单方式是手动测试。随着组织逐渐成熟,应该把监控自动化,以便立即发现 SLO 未达标的情况。
定义监控策略时,可以问自己:
- 如果服务宕机,你怎么发现?
- 如果服务性能下降 100 倍,你怎么知道?
- 还有哪些事件应该触发告警?
- 例如 CPU 使用率激增、身份验证失败、系统错误
> **监控**
>
> 以下事件会向值班工程师发出紧急告警:
>
> - Trogdor 面向用户的 HTTP 请求,第 95 百分位延迟:>= 3s
> - Postgres 服务器在过去 2 分钟窗口内的平均 CPU 使用率:>= 90%
### 时间表
时间表把项目拆分为多个里程碑,并明确何时向项目利益相关方交付成果。
请选择能为利益相关方[产出有用成果](https://mtlynch.io/tinypilot-redesign/#structure-for-serial-incremental-results)的里程碑。例如,先做一个显示模拟数据的 UI,首先展示给客户。如果你误解了客户需求,模拟数据能让你尽早发现,而不用等到已经完成把生产数据填入 UI 所需的所有底层工作之后。
如果不知道如何估算项目时间,我强烈推荐 Joel Spolsky 的 [《Painless Software Schedules》](https://www.joelonsoftware.com/2000/03/29/painless-software-schedules/)。文章已经问世 25 年,但它仍是我最喜欢的软件工期估算方法。
> **时间表**
>
> - **里程碑 1(2026-07-01)**:RecencyBank 在测试环境上线,使用硬编码的部分缓存数据,不读取 Postgres。
> - **里程碑 2(2026-07-17)**:RecencyBank 在测试环境上线,并缓存来自 Postgres 的真实数据。
> - **里程碑 3(2026-08-03)**:RecencyBank 在测试环境上线,并执行缓存淘汰与生命周期规则。
> - **里程碑 4(2026-08-22)**:RecencyBank 完成全部实现并部署到生产环境。
### 接口
项目的存在是为了服务人或其他软件系统,那么这些交互是什么样的?
- 对图形界面系统来说,用户界面是什么样的?
- 只需简单草图,不要陷入精确 UI 选择的细枝末节。
- 对软件接口来说,API 或 CLI 的语义是什么?
- 对基于文件的接口来说,文件格式是什么?
> **接口**
>
> Trogdor 的 `Server` 结构体目前直接依赖一个 `PostgresDB` Go `struct`:
>
> ```go
> type Server struct { db PostgresDB }
> ```
>
> `PostgresDB` 导出以下方法:
>
> ```go
> GetUser(id UserID) (User, error)
> ListUsers() ([]User, error)
> ...
> ```
>
> 我们会创建一个 Go `interface` 类型,其对外 API 与 `PostgresDB` 相同:
>
> ```go
> type Store interface {
> GetUser(id UserID) (User, error)
> ListUsers() ([]User, error)
> }
> ```
>
> 我们将实现一种 RecencyBank 缓存类型。它实现相同的 `interface`,并封装后端 `PostgresDB` 结构体。RecencyBank 实现会缓存从 Postgres 读取的数据;如果请求会改变状态,或依赖缓存中没有的数据,则把请求转发给 Postgres。
>
> `Server` 实现唯一需要改动的地方,是把一个成员的类型替换成新的 `interface`:
>
> ```go
> type Server struct { db store.Store }
> ```
### 依赖与基础设施
依赖章节应该回答以下问题:
- 使用哪些编程语言?
- 代码在哪种硬件或服务上运行?
- 持久化数据存放在哪里?
这一节很容易被忽略,但语言、库和基础设施方面的决策,会对系统复杂度和长期维护成本产生重大影响。
要深入思考哪些依赖在实现后很难更换,而不必过于担心那些容易替换的依赖。更改编程语言或存储后端很困难;但如果不满意用于发送邮件的第三方服务,一个下午就能换掉。
> **依赖**
>
> - **语言**:Go
> - 我们已经广泛使用 Go,而且它适合处理高度并行的工作流。
> - **第三方软件包**
> - [bbolt](https://pkg.go.dev/go.etcd.io/bbolt):这是广泛使用的键值存储实现,具备 RecencyBank 所需的许多功能。
### 安全
为了构建安全的软件,开发者必须从设计阶段开始,把安全融入软件的完整生命周期。
安全章节应该回答以下问题:
- 考虑过哪些威胁?
- 例如,攻击者尝试所有可能的密码会怎样?用户上传感染恶意软件的 PDF 又会怎样?
- 系统的[攻击面](https://en.wikipedia.org/wiki/Attack_surface)是什么?
- 也就是说,它会在哪里处理可能带有恶意的数据?
- 信任边界在哪里?
- 数据会在哪个位置从权限较低的系统流向权限较高的系统?
- 例如,在 Web 应用中,来自用户浏览器的请求会跨越信任边界,因为 Web 服务器不应假定浏览器输入是安全的。
即使你认为安全威胁不太可能出现,或与系统无关,记录判断依据仍然有帮助。你的解释可能会启发评审者发现你忽略的威胁。
> **安全**
>
> RecencyBank 不得接受来自公共互联网的直接请求,因为它没有执行任何访问控制。
>
> RecencyBank 将运行在隔离网络中,只接受 Trogdor Web 服务器的入站请求,并且只能向 Postgres 服务器池发出请求。
### 隐私
隐私章节让你有机会想清楚系统会处理哪些敏感数据,以及要采用哪些保护措施来确保数据安全。它应该回答以下问题:
- 系统处理哪些敏感数据?
- 数据会保留多久?
- 谁可以访问?
- 如何保护数据?
- 例如,静态数据和传输中的数据是否都会加密?
> **隐私**
>
> RecencyBank 包含与 Postgres 数据库相同的敏感用户数据,因此沿用 Postgres 系统的隐私政策。尤其要注意,工程师只有在关联了缺陷编号时,才能访问生产环境中的 RecencyBank 系统。工程师必须尽量减少访问的用户数据,只查看调查缺陷绝对需要的内容。
### 法律因素
如果系统用于金融或医疗等高度监管的领域,法律章节可以帮助你遵守相关法律。
即使不在受监管领域,也要考虑系统失控时是否可能违法。解释如何避免危及公司或客户的违法行为。
如果要用开源许可证发布代码,应说明选择了哪种许可证,以及为什么。
> **遵守 FizzleCorp 合同**
>
> 我们与 FizzleCorp 签订的合同严格限制我们为其专有的 FizzlePerfect™ 用户生物特征数据创建新副本。
>
> 好在法务团队审查合同措辞后确认,缓存层符合现有“存储层”定义,因此无需重新谈判合同,就可以在 RecencyBank 中缓存 FizzlePerfect™ 数据。
### 日志
调查缺陷、性能问题或安全事件时,日志可能极具价值。如果从设计阶段就考虑有效的日志记录,系统的长期维护会更加容易。
考虑日志时,可以思考以下问题:
- 服务会记录哪些关键事件?
- 是否有不同日志级别?
- 例如信息、警告、错误、严重错误
- 系统把日志存放在哪里?
- 日志保留多久?
- 谁可以访问日志?
- 是否有任何敏感数据绝不能写进日志?
> **日志**
>
> RecencyBank 会记录以下事件:
>
> - 初始化时,记录初始化 RecencyBank 所使用的参数,以及宿主机的 RAM 容量和使用量。
> - 向内存写入值失败。
> - 缓存失效操作失败。
### 待解决问题
编写设计文档时,你很可能会遇到以下至少一种情况:
- 设计存在缺陷,但你还不知道怎么解决。
- 设计存在空白,但需要收集更多信息才能补上。
- 你在多个方案之间犹豫不决。
在设计文档中创建一个名为“待解决问题”的附录,记录尚未解决的问题。
待解决问题章节中的每一项都应说明:
- 哪个问题还需要继续处理?
- 你看到了哪些解决选项?
- 为了解决问题,下一步马上要做什么?
关于如何在评审阶段[管理待解决问题](https://refactoringenglish.com/blog/useful-feedback-on-design-docs/#aggressively-resolve-comment-threads),请参阅我的配套文章。
> **待解决问题:选择缓存 RAM 容量**
>
> 我们需要决定给缓存层分配多少 RAM。增加 RAM 可以提高性能,但 RAM 很贵,而且继续增加 RAM 带来的收益会逐渐递减。
>
> 从理论上说,应该存在一个最佳 RAM 容量,使缓存系统与数据库的基础设施总成本降到最低。我们可以搭建测试环境并运行多次模拟来找出这个最佳值,但这些模拟会消耗开发时间。
>
> 我估计,创建测试环境并运行第一次模拟需要 3.0 个开发人日。基础设施就绪后,每次额外模拟大约需要 0.75 个开发人日。
>
> **建议方案**:不做测试,直接选择 128 GB RAM。这个数值可能已经接近最佳值,而且开发时间比 RAM 贵得多。
>
> **下一步**:请技术负责人给出意见。
### 已解决问题
解决一个待解决问题后,应总结最终决策,把它从设计文档的“待解决问题”移到“已解决问题”,并保留完整讨论,供日后查阅。
> **已解决问题:选择缓存 RAM 容量**
>
> **决策**:为缓存层配备 128 GB RAM。如果无法达到性能目标,而且瓶颈确实是 RAM,到时可以继续增加。通过测试寻找完美 RAM 容量所需的开发成本,远高于多配一些 RAM 的成本。
>
> 我们需要决定……[此处保留原待解决问题的其余内容]
### 考虑过的替代方案
如果预计读者会问“为什么不采用 X?”,最好在“考虑过的替代方案”章节主动回答。这里也适合说明被否决的选项,尤其是那些起初很有吸引力,或你投入大量时间研究过的方案。
我认识一些开发者,他们会花几个小时仔细记录每一个被否决的设计想法,但我认为这做过头了。无论作为读者还是作者,我只需要在替代方案章节看到几行简短说明:有哪些有力的替代方案,以及它们为什么不可行。
> **考虑过的替代方案**
>
> - Google Cloud Firestore(持久化存储)
> - 它的数据持久性和可靠性很有吸引力,但我不喜欢平台锁定,也不喜欢它难以在本地测试。
## 推动设计文档完成评审
设计文档完成后,下一步是与团队分享并收集反馈。
接下来的章节会介绍一些方法,帮助你获得真正有用、能推动项目向前的设计反馈,而不是让项目陷入争吵和混乱:
- [如何获得有意义的设计文档反馈](https://refactoringenglish.com/excerpts/useful-feedback-on-design-docs/)