如何写好软件设计文档

现代软件工程的全过程,有两个阶段非常关键:一个是 “产品设计”,一个是 “架构设计”。产品设计由产品经理主导,关注的是 “如何以产品特性来系统化地满足用户需求”架构设计由架构师主导,关注的是 “业务系统如何系统化地进行分解与交付”

“设计” 一词非常精妙。无论是 “产品设计”,还是 “架构设计”,其实谈的都是 “需求如何被满足” 这件事情的共识。

产品经理与架构师是一体两面,对人的能力要求的确会比较像,但是分工不同,关注的维度不同。产品经理关注的维度,其关键词是:用户需求、技术赋能、商业成功。而架构师关注的维度,其关键词是:用户需求、技术实现、业务迭代

本质上所有 “设计文档” 的内容组织逻辑,都应该是相通的。它们的内容大体如下:

1. 现状 :我们在哪里,现状是什么样的?
2. 需求:我们的问题或诉求是什么,要做何改进?
3. 需求满足方式:

    1. 要做成什么样,交付物规格,或者说使用界面(接口)是什么?
    2. 怎么做到?交付物的实现原理。

设计文档要素的关键在于以下几点。

1. 现状:不要长篇累牍。现状更多的是陈述与我们要做的改变相关的重要事实,侧重于**强调这些事实的存在性和重要性**。
2. 需求:同样不需要长篇累牍。痛点只要够痛,大家都知道,所以**需求陈述是对痛点和改进方向的一次共识确认**。
3. 需求满足方式:要详写,**把我们的设计方案谈清楚**。具体来说,它包括 “交付物规格” 和 “实现原理” 两个方面。
4. 交付物规格,**或者说使用界面,体现的是别人要怎么使用我**。对于 “产品设计”,交付物规格可能是 “产品原型”。对于 “架构设计”,交付物规格可能是 “网络 API 协议” 或者 “包(package)导出的公开类或函数”。
5. 实现原理,谈的是我们是怎么做到的。对于 “产品设计”,它谈的是用户需求对应的 UserStory 设计,也就是业务流具体是怎么完成的。而对于 “架构设计”,它谈的是 UserStory 具体如何被我们的程序逻辑所实现。

程序 = 数据结构 + 算法

它是一个很好的指导思想。当我们谈程序实现逻辑时,我们总是从数据结构和算法两个维度去描述它。其中,“数据结构” 可以是内存数据结构,也可以是外存数据结构,还可以是数据库的 “表结构”。“算法” 基于 “数据结构”,它描述的是 UserStory 的具体实现,它可以是 UML 时序图(Sequence Diagram),也可以是伪代码(Pseudo Code)

多个设计方案的对比

一篇设计文档有时候不是只有一个设计方案,而是有多个可能的需求实现方式。在这个时候,通常我们会概要地描述清楚两个设计方案的本质差别,并且从如下这些维度进行对比:

1. 方案的易实施性与可维护性。
2. 方案的时间复杂度与空间复杂度。

这是因为 “设计” 是软件工程中的头等大事,我们应该在这里 “多浪费点时间”,这样的 “浪费” 最终会得到十倍甚至百倍以上的回报

使用界面(接口)

对于 “模块的详细设计” 来说,规格描述相对简单。因为我们关注的面只是模块本身,而非模块之间的关系。对于模块本身,我们核心关注点是以下两点:一是接口是否足够简单,是否自然体现业务需求。二是尽可能避免进行接口变更,接口要向前兼容

对于 “系统的概要设计” 来说,我们第一关心的是模块关系,第二关心的才是各个模块的核心接口。这些接口能够把系统的关键 UserStory 都串起来。

对于 UserStory 业务流程的表达来说,UML 时序图通常是更好的表达方式

怎么表达模块关系呢?

  1. 一个方法是对模块的调用接口进行分类,一个模块对外提供的访问接口无非是:常规 DOM API,即正常的模块功能调用;事件(Event)的发送与监听;插件(Plugin)的注册。
  2. 另一个表达模块关系的视角,是从架构分解看,我们把系统看作 “一个最小化的核心系统 + 多个彼此正交分解的周边系统”

需要清楚的是,模块关系图的表达是非常粗糙的,虽然它有助于我们理解系统分解的逻辑。为了共识的精确,我们仍然需要将各个模块核心的使用界面(接口)表达出来

实现原理

对于模块的详细设计来说,需要先交代清楚 “数据结构” 是什么样的,然后再将一个个 UserStory 的业务流程讲清楚。

对于系统的概要设计来说,核心是交代清楚不同模块的配合关系,所以无需交代数据结构,只需要把一个个 UserStory 的业务流程讲清楚。

无论是否要画 UML 时序图,在表达上伪代码(Pseudo Code)的设计都是必需的。伪代码的表达方式及语义需要在团队内形成默契。这种伪代码的语义表达必须是精确的。


# 请求
post /v1/foo/bar json {...}

# 返回
ret json {...}

可以使用 API 来表达

Qa

彼此正交就是相互没有直接联系,就算有相互作用也是通过核心系统的接口进行。这里面难点是最小单位的核心系统,需要足够通用的接口方法来保证