Skip to content

设计文档写什么:问题、约束、场景与可检查的目标 ​

一份设计文档有二十页:背景、架构图、模块划分、接口列表、表结构。评审会开了两小时,大家对着架构图讨论了半天用不用消息队列。上线后出了问题,回头翻文档,发现没有一处写着「这个系统每秒要处理多少请求」「下游超时时应该怎样」,也没有人记得当初为什么不用缓存。

很多设计文档写的是设计的结果:选了什么组件、分了哪些模块、画了什么图。这些内容有用,但它们回答不了评审时最该问的问题:这个设计要解决什么问题?在什么约束下?怎样知道它做到了?没有这些,评审只能讨论技术偏好,上线后也无从判断设计是否达成了目标。

本文给出一个设计文档的结构,每一部分都说明它回答什么问题、怎样写才可以被检查。这是一篇方法文,没有配套实验;文中引用的实测都来自站内其他文章。

问题与目标可检查的目标、非目标约束业务、外部系统、规模数字注明来源场景刺激—环境—响应—度量功能场景 + 质量场景方案与视图每张图回答一个场景决策记录备选、理由、复评条件验证计划测试、压测、演练、监控验证计划逐项对应目标与质量场景篇幅与决策的影响和不确定性相称:难以回退的写全六部分,局部可回退的写目标、场景与决策
图 1 · 示意:设计文档的价值在于各部分之间可以互相检查。目标和场景写成可检查的形式,方案的每张视图对应某个场景,每个决策的理由回指约束或场景,验证计划把目标和质量场景变成上线前后的具体检查。只有方案和架构图的文档,评审时只能讨论技术偏好。

一、问题与目标:先写「为什么」 ​

第一部分只回答一个问题:要解决的是什么问题,解决到什么程度算成功。

  • 问题陈述:用业务语言描述现状和痛点,最好带上可以核实的事实(「每天约有 X 笔订单因超时重复扣款,来自客服工单统计」),而不是「系统性能不好」。
  • 目标:做完之后,什么会变得不同。目标要能检查:「重复扣款为零」「下单接口 P99 在 300ms 以内」。
  • 非目标:明确不解决什么。「不改造支付渠道的对接」「不处理历史数据」。非目标能防止评审时范围不断扩大,也让读者知道哪些问题是有意留下的。

如果写不出可检查的目标,往往说明问题本身还没想清楚,这时候继续写架构是在浪费时间。

二、约束:设计不能改变的东西 ​

约束是设计必须接受的前提,它们决定了哪些方案根本不在考虑范围内:

约束类型例子
业务规则同一笔订单只能扣款一次;退款不能超过实付金额
外部系统支付渠道的回调最多重试 3 次;对方接口限流每秒 100 次
数据规模与增长当前 2000 万订单,每月增长 5%
技术栈与运维只能用公司现有的 MySQL 与 Kafka;不能引入新的有状态组件
时间与人力两个人、六周
合规支付数据不出境;日志不能记录卡号

约束和目标一样要具体。「高并发」不是约束,「峰值每秒 3000 次下单,来自去年大促的监控」才是。数字的来源要写出来,因为数字会过时,读者需要知道它是测量的、估算的还是猜的。

三、场景:用具体的情况检验设计 ​

架构图描述的是静态结构,场景描述的是系统在具体情况下怎样表现。每个关键场景写成「刺激—环境—响应—度量」:

text
场景:支付回调重复到达
刺激:支付渠道对同一笔交易发送两次成功回调,间隔 2 秒
环境:正常负载,两次回调落在不同的实例上
响应:订单只被标记为已支付一次,第二次回调返回成功但不产生副作用
度量:重复回调测试中,订单状态变化事件恰好 1 个

场景分两类:功能场景(正常下单、取消、退款)和质量场景(峰值负载、下游超时、实例宕机、重复消息、数据迁移中途失败)。质量场景是设计文档最常缺的部分,也是上线后最常出问题的部分。

场景的价值在于它可以被检查:评审时逐个场景追问「按这个设计,这个场景下会发生什么」,比讨论「要不要用消息队列」有效得多。上线前,每个质量场景应该有对应的测试或演练。

用这个例子把后面几节串起来看:一个场景怎样落到视图、决策和验证上。

场景:支付回调重复到达刺激 同一交易两次成功回调,间隔 2 秒环境 正常负载,落在不同实例上响应 只标记一次已支付度量 状态变化事件恰好 1 个视图:运行时交互两个实例并发处理回调的时序图决策:唯一索引去重备选 Redis 锁:放弃,违反「不引入有状态组件」复评:回调要调用多个外部系统时验证:集成测试并发发送两次回调 · 每次构建约束不能引入新的有状态组件回答理由度量变成断言评审时逐个场景追问「按这个设计会发生什么」,比讨论用不用某个组件更有效
图 2 · 示意(沿用文中「支付回调重复到达」的例子):场景写成刺激、环境、响应、度量;时序图回答这个场景下请求怎样流动;决策记录写明放弃的备选和理由,理由回指约束与场景;验证计划把度量变成每次构建都跑的测试。任何一环缺了,评审时都能追问出来

四、方案与视图:每张图回答一个问题 ​

到这一步才写方案本身。一个系统需要从几个角度看,每个视图回答不同的问题:

视图回答的问题常见形式
领域模型有哪些概念,规则由谁守类图、规则表
模块与依赖代码怎样组织,依赖朝哪个方向包图、依赖规则
运行时交互一个场景里请求怎样流动、失败时怎样时序图
部署运行在哪里、几个实例、怎样扩容部署图
数据存在哪里、一致性怎样保证、怎样迁移表结构、数据流

不是每份文档都需要所有视图。原则是:每张图都要能回答第三节里某个场景的问题。一张画满了方框和箭头、却说不出对应哪个场景的架构图,评审时只能用来讨论颜色。读一个已有系统时也可以用同样的视图顺序,见读懂一个系统的设计。

五、备选方案与决策记录 ​

每个关键决策都写出至少一个被放弃的备选方案,以及放弃的理由。理由要关联到前面的目标、约束或场景:

text
决策:重复回调用「订单号 + 交易号」唯一索引去重,不引入分布式锁
备选:Redis 分布式锁
放弃理由:
  - 约束「不能引入新的有状态组件」
  - 场景「回调落在不同实例上」中,锁过期与回调处理时间的关系需要额外设计;唯一索引由数据库保证
复评条件:如果回调处理需要调用多个外部系统、无法放在一个数据库事务里,重新评估

复评条件说明这个决策在什么情况下应该重新考虑,它让后来的人知道决策的前提是什么。候选比较与决策记录的完整做法,见几个模型都说得通时。

比较方案时一起算的东西 ​

只列每个方案的优点和缺点,比较常常不公平:新方案的代价被低估,维持现状的代价被忽略。下面几项要摆在同一张表里。

维持现状也是一个候选。 它不是零成本:已知的故障会继续发生,绕过问题的人工步骤会继续消耗时间,某些需求会继续做不了。把它和其他方案放在一起写,才能回答「为什么现在要改」。

成本按完整的生命周期算。 构建只是一部分,还有迁移(数据、调用方、双写期)、运维(值班、监控、升级)、学习(团队需要掌握的新东西)和退出(将来不用它时要付出什么)。一个方案省下的机器费用,可能变成另一个团队的迁移工作和值班负担。只在自己的边界里算账,会选出把成本推给别人的方案。

机会成本。 投入这个方案的人和时间,同期就不能做别的事。比较时要问的不只是「这个方案值不值」,而是「它是不是这段时间里最值得做的事」。

收益是假设,要标出依据和敏感性。 「新方案能把延迟降低一半」来自压测、来自别人的经验,还是来自推测?依据不同,可信度不同。更有用的是看结论对假设有多敏感:

假设(示意)方案甲:渐进改造方案乙:替换
迁移 3 个月,收益按预期次优最优
迁移拖到 6 个月最优次优
收益只有预期的一半最优次优

表中是示意,不是测量结果。如果两个合理的假设变化都会让排序反转,说明这个决策的风险集中在这些假设上,下一步应该先验证它们(做一个小规模试验、迁移一个切片),而不是继续细化方案。

分阶段,并写下重新评估的条件。 长期方向可以很大,但每个阶段都要交付一个能被使用的结果,并写明在什么信号出现时停下来重新评估:数据量超过某个规模、某个假设被试验推翻、团队里能维护它的人离开、监管要求变化。暂时合适的方案,不应该因为写进了文档就变成永久规则。

变更方案也适用同样的结构:变更前写清目标与代价、怎样验证、怎样回退。事故之后的改进同理:先重现问题,再写改进方案,最后用演练证明改进有效,而不是只写「已修复」。

六、验证计划:怎样知道做到了 ​

最后一部分把第一节的目标和第三节的场景变成具体的检查:

目标 / 场景验证方式何时
重复回调不重复扣款集成测试:并发发送两次回调每次构建
下单 P99 ≤ 300ms压测:峰值负载 3000 次/秒,持续 30 分钟上线前
下游超时不拖垮下单故障注入:支付渠道延迟 10 秒上线前
数据迁移可回退演练:迁移到一半时回滚上线前
重复扣款为零监控:每日对账差异报表上线后持续

验证计划让设计文档从「一份描述」变成「一份可以被检查的承诺」。压测的负载模型怎样选、结果怎样解读,见开放与封闭负载模型;变更怎样做到可回退,见扩展—收缩式变更。

七、写多少 ​

设计文档的篇幅应该与决策的影响和不确定性相称:

  • 影响大、难以回退(数据模型、对外接口、跨团队的依赖):完整的六部分。
  • 影响局限在一个模块、容易回退:问题、目标、关键场景和决策记录,一两页就够。
  • 常规功能:在任务描述里写清目标和验收场景即可,不需要单独的设计文档。

另一个常见的问题是设计投入与真实变化的错配:花大量精力设计「将来可能的扩展」,而真正发生的变化在另一个方向。应对的办法不是更努力地预测,而是在文档里写明设计承诺了哪些变化方向(第五节的复评条件),并在变化真正发生时回来更新文档。文档是活的,过时的设计文档比没有更糟。

八、检查清单 ​

  • 问题陈述带有可核实的事实;目标可检查;写明非目标。
  • 约束具体,数字注明来源。
  • 关键功能与质量场景写成「刺激—环境—响应—度量」。
  • 每个视图都能对应到某个场景的问题。
  • 每个关键决策有被放弃的备选、关联到约束或场景的理由、复评条件。
  • 验证计划覆盖每个目标和质量场景,写明方式与时机。
  • 篇幅与决策的影响和不确定性相称;变化发生时更新文档。

参考资料

文章以 CC BY-NC-SA 4.0 授权 · 代码片段以 MIT 授权