推荐这篇文章,作者在 Google 和 Microsoft 都写过设计文档,他把设计文档的每个部分拆解到极其具体——每节都有示例、反例和"你需要回答什么问题"。如果你们团队的设计文档写得稀烂,这篇直接拿来当模板。
怎么写一份有效的软件设计文档
一份好的设计文档可以省下你数年的开发时间。写设计文档迫使你在浪费时间在错误实现上之前,先想清楚重要的决策。它也是协调团队和合作团队之间设计决策的最佳方式。
下面是我创建有效设计文档的方法,以及什么属于设计文档,什么不属于。
什么时候应该写设计文档
项目越复杂或风险越大,写设计文档的价值就越大。问这些问题:
• 会有多人协调工作来实现这个设计吗?
• 项目会超过三个月的全职开发工作吗?
• 实现会在生产环境中跑几年吗?
• 项目涉及跨团队协作吗?
• 项目的目标和需求模糊吗?
• 存在设计时可以预防的灾难性风险(比如安全漏洞、法律风险)吗?
如果对任何一个问题回答"是",可能值得写。对两个以上,"几乎一定"值得。
设计文档中应该投入多少
设计文档可以是简单的一页纸,也可以是 50 页需要五个不同团队签字的文档。没有通用规则规定你应该在设计文档上花多长时间,就像没有规则规定代码应该测多少。正确的投入取决于团队的目标、风险、截止时间和文化。有时候,正确的投入是零。
什么属于设计文档
一个简单的经验法则:如果我在这件事上错了,代价是什么?
不是所有设计决策同等重要。有些选择比其他选择灵活得多。如果你用 C++ 写了一个 web 应用,20 万行后发现 Ruby on Rails 才是更好的选择,你卡住了。另一些设计决策微不足道:比如一个"加载更多"按钮,如果你选错了,用户反馈几小时就能修复。你不会因为这件事在文档里写满你的思考过程,更不该浪费审查周期争论它。
设计文档的组成部分
标题
人们会在对话中用标题来指代你的项目,所以要有这些品质:简短(容易口头说出)、独特(让人清楚指的是哪个项目)、有画面感(概念上代表你的项目)。好名字:RecencyBank。坏名字:"飞天银马计划"。
元数据
作者(名字 + 邮箱)、创建日期、权威 URL。尤其当你们组织用短链接重定向如 http://go/recency-bank。
目标
一句话解释项目的 purpose,应该在文档第一页用任何干系人都能理解的平实语言出现。"通过在 Trogdor web 服务器和 Postgres 数据库之间增加缓存层来提高应用性能。"
背景
回答:团队为什么接这个项目?解决什么问题?之前有尝试解决吗?如果有相关文档,链接它们——项目测试计划、相关系统的设计文档、项目先前迭代的设计文档。
关键检查:你的设计文档在没有外部上下文的情况下能读懂吗?
目标
描述项目的高层目标,从背景部分逻辑连接过来,解释实现完成后世界是什么样子。避免用实现细节来设定目标——目标应该表达项目对用户、团队或公司的好处。
❌ "将 Kubernetes 添加到我们的基础设施。"
✅ "最大限度地减少与部署新应用版本相关的中断。"
非目标
如果有些目标读者可能误以为在范围内的,明确写在非目标里。"创建一个通用、可复用的缓存系统——范围外。""位置感知缓存——范围外。"
场景
如果你的目标是"给图表加一个分享为 URL 的按钮",读者可能不理解实际是什么样子。场景部分允许你描绘一幅画面:Bob 创建自定义报告 → 点击菜单栏的分享 → 邮件链接给 Charlie → Charlie 看到只读模式下的完全相同的报告。
图表
图表极有价值,尽管可能看起来不那么明显。作为设计作者,你直观理解各部分如何拼在一起。你的审查者没有这个心理图景。最快让他们看到它的方法就是画出来。
考虑:数据如何流经你的系统?不同组件如何拼在一起?系统如何与依赖和下游客户端交互?定义了哪些通信协议?
选一个方便编辑的图表工具——Excalidraw、 Drawings。作者见过开发者画漂亮的白板图然后拍照放进文档,但第一稿很惊艳之后永远困在那张图上因为他们不能编辑照片。
术语表
定义读者可能不认识的术语。仔细想一下文档的潜在读者——特别是新成员和团队之外的人。最好的方案是使用可识别的术语,或在行内定义,这样读者不必在文档里跳来跳去。
约束
如果有重大约束——预算、客户端、基础设施或依赖——解释这些约束,让读者理解设计选择的背景。"我们的服务器全是 RISC-V,所有代码和依赖必须在 RISC-V 架构上运行。"
服务水平目标(SLO)
SLO 是服务向客户端或用户提供的可衡量目标。在公司内部通常不会因为错误而惩罚同事(虽然那会有点好玩),所以设计文档定义 SLO 而非 SLA。
典型考虑:正常运行时间 / 可用性、延迟、规模。好的 SLO 防止模糊——"50% 用户面 HTTP 请求延迟 <=200ms",不是"在移动设备上性能好"。
监控 / 告警
如果你的服务挂了,你怎么知道?如果性能慢了 100 倍,你怎么知道?什么事件应该触发告警?"Trogdor 的 95% 用户面 HTTP 请求延迟 >= 3s——通知值班工程师。"
时间线
将项目分解为里程碑,指定干系人什么时候收到交付物。选择创造有用制品的里程碑。比如先做一个显示假数据的 UI 给客户看——假数据让你在错误理解需求时能尽早发现,而不是已经实现了所有后端管线再用生产数据填充 UI 后才发现。
接口
你的项目存在是为了服务人或其他软件系统,这些交互长什么样?图形系统的 UI(只需简单草图)、软件接口的 API 或 CLI 语义、文件接口的文件格式。用具体的代码示例说明接口的变化:type Server struct { db PostgresDB } → type Server struct { db }
依赖 / 基础设施
用什么编程语言?代码跑在什么硬件或服务上?持久数据存在哪里?深入思考哪些依赖在实现后难以改变——换语言或存储后端很难,但换第三方发邮件服务一个下午的事。
安全
考虑了哪些威胁?攻击面是什么?信任边界在哪里?即使认为安全威胁不太可能或无关,文档化你的推理仍然有助于提示审查者找出你忽略的威胁。
隐私
系统处理什么敏感数据?保留多久?谁可以访问?如何保护它?
法律考虑
如果在金融或医疗等高度监管领域——如何遵守相关法律。即使不在监管领域:如果事情出错系统是否可能违法?如何避免?如果开源,定义选什么许可和为什么。
日志
关键事件是什么?有不同日志级别吗?日志存在哪?保留多久?谁可以访问?有没有敏感数据必须排除出日志?
开放问题
文档化你的待解决问题:问题需要更多工作的是什么?看到什么选项能解决?下一步是什么?
"选择缓存 RAM 大小:加 RAM 提高性能但贵且有收益递减。理论上可以设置测试环境跑模拟来发现最优值,但那些模拟要花 3 天开发时间。建议方案:选 128GB 不测试,可能接近最优,开发时间显著比 RAM 贵。下一步:问技术主管。"
已解决问题
解决开放问题后,总结决策,从开放问题移到已解决问题,保留完整讨论以备后查。
已考虑的替代方案
如果预料读者会问"为什么没选 X",主动回答。简要几行描述强力替代方案及为什么不行。不用在文档里耗费数小时写每个被拒方案的详尽理由。
驱动你的设计文档通过审查
完成设计文档后,下一阶段是和团队分享并收集反馈。(作者有一篇后续文章讲如何获得有意义的反馈。)
原文:Michael Lynch, "How to Write an Effective Software Design Document", Refactoring English, 2026-06-24
#
软件工程# #
设计文档# #
工程实践#