<audio id="audio" title="45 | 架构：怎么做详细设计？" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/5a/cb/5aa0400c71fa490c142c60b465b41ecb.mp3"></audio>

你好，我是七牛云许式伟。

我们第三章 “服务端开发篇” 就快要结束了。我们原计划的第三章会分拆为两章：

- 第三章：服务端开发篇。主要介绍服务端的基础架构与业务架构。
- 第四章：服务治理篇。主要介绍服务端程序上线与线上服务如何管理的问题。

原先计划的 “第五章：通用架构范式篇” 会取消，核心内容会融合到其他的章节中。详细的调整结果，近期我们会与大家同步新的大纲。

今天我们把话题重新回到架构上。

关于架构，前面我们已经聊了第一步的需求分析和第二步系统的概要设计：

- [17 | 架构：需求分析（上）](https://time.geekbang.org/column/article/100140)
- [18 | 架构：需求分析（下）- 实战案例](https://time.geekbang.org/column/article/100930)
- [32 | 架构：系统的概要设计](https://time.geekbang.org/column/article/117783)

需求分析并不是纯技术的东西，和编程这件事情无关。它关乎的是用户需求的梳理、产品的清晰定义、可能的演变方向。

需求分析的目标和最终结果，都是要最终形成清晰的产品定义。产品定义将明确产品的元素，明确产品的边界，与产业上下游、合作伙伴的分工。

在需求分析阶段，我们关注用户需求的精确表述。我们会引入角色，也就是系统的各类参与方，以及角色间的交互方式，也就是用户故事。

在概要设计阶段，我们一般以子系统为维度来阐述系统各个角色之间的关系。对于关键的子系统，我们还会进一步分解它，甚至详细到把该子系统的所有模块的职责和接口都确定下来。

这个阶段我们的核心意图并不是确定系统完整的模块列表，我们的焦点是整个系统如何被有效地串联起来。如果某个子系统不做进一步的分解也不会在项目上有什么风险，那么我们并不需要在这个阶段对其细化。

为了降低风险，概要设计阶段也应该有代码产出。

这样做的好处是，一上来我们就关注了全局系统性风险的消除，并且给了每个子系统或模块的负责人一个更具象且确定性的认知。

代码即文档。代码是理解一致性更强的文档。

经过系统的概要设计，整个系统的概貌就了然于胸了。详细设计阶段，是需要各个子系统或模块的负责人，对他负责的部分进行进一步的细化。

详细设计关注的是子系统或模块的全貌。

请记住，详细设计并不是只谈实现就完事，更不是一个架构图。它包括以下这些内容。

<li>
**现状与需求**
<ul>
- 现在在哪里，遇到了什么问题，要做何改进。

**需求满足方式**

- 要做成啥样？交付物的规格，或者说使用界面（接口）。
- 怎么做到？交付物的实现原理。

概要设计和详细设计的工作内容会有一定的重叠。

概要设计的核心目标是串联整个系统，消除系统的重大风险。在这个过程中，对一些关键模块的实现细节有所考虑是非常正常的。但从另一个角度来说，分解粒度也不能过粗，不应该把特别庞大的子系统直接分出去，这样项目执行的风险就太高了。

但两者的分工不同，考虑的问题重心不同。

比如，从使用界面（接口）来说，概要设计不一定会把子系统或模块的完整接口都列出来，实际上它只关注最核心的部分。但是从详细设计角度来说，接口描述的完备性是必需的。

## 现状与需求

我们先看看现状与需求。

从逻辑自洽的角度，我们任何一篇文档，首先关注的都应该是要解决的问题与目标。

现状与需求的陈述，要简明扼要。

现状大家都知道，所以不要长篇累牍。更多的是陈述与我们要做的改变相关的重要事实，侧重点在于强调这些事实的存在性和重要性。

比如，假设我们要对某个模块重构。那么，现状就是要谈清楚现在的业务架构是怎样的？它到底有什么样的问题。

需求陈述是对痛点和改进方向的一次共识确认。痛点只要够痛，大家都知道，所以同样不需要长篇累牍。

每个子系统或模块，都有自己的角色分工与用户故事。我们不用重新做一遍需求分析，但对需求分析的核心结论，在详细设计开始之前需要明确。

这很重要。它是我们详细设计所要满足的业务目标。

## 使用界面（接口）

聊完了现状与需求，接着我们就要谈需求的满足方式。它分两个方面：一方面是交付物的规格，或者说使用界面（接口）。另一方面是背后的实现原理，我们怎么做到的。

规格，或者说使用界面，体现的是别人要怎么使用我。

我们前面一直在强调，使用界面（接口）应该自然体现业务需求，就是强调程序是为用户需求服务的。而我们的架构设计，在需求分析与后续的概要设计、详细设计等过程之间也要有自然的延续性。

使用界面这一部分要详细写，它是团队共识确认的关键。

我们的交付物有哪些可执行文件，有哪些包（package）？如果可执行文件，那么它是一个界面程序，还是服务？如果是服务，网络协议是什么样的？如果是包，它又包含哪些公开的类或函数。

在 “[32 | 架构：系统的概要设计](https://time.geekbang.org/column/article/117783)” 这一讲中，我们花了非常长的篇幅介绍使用界面（接口）是怎么回事，今天我们就不对这一点进行展开。

需要强调的是，使用界面需要有明确的书写规范。它也是团队共识管理的重要组成，是团队效率、团队默契形成的象征。

更需要强调的是，使用界面的稳定是至关重要的。

接口的变更需谨慎！

对使用界面的不兼容调整，可能出现严重的后果。技术上，可能会导致客户异常，出现编译失败需要重写代码，或者更严重的是，可能导致他们的系统崩溃。商业上，则可能导致大量的客户流失。

## 实现：数据结构+算法

聊完使用界面，接下来就要谈实现原理了，它要体现的是我如何做到。

在 “[42 | 实战（二）：“画图”程序后端实战](https://time.geekbang.org/column/article/136884)” 一讲中，我们提到过以下这个大家耳熟能详的公式：

> 
程序 = 数据结构 + 算法


它是一个很好的指导思想。当我们谈程序的实现时，我们总是从数据结构和算法两个维度去描述它。

我们先看数据结构。

数据结构从大的层面分，可分为基于内存的数据结构，和基于外存（比如 SSD 盘）的数据结构。

对于桌面程序，大部分情况下我们打交道的都是基于内存的数据结构。外存数据结构也会有所涉及，但往往局限于 IO 子系统。

但对于服务端程序，数据结构不完全是我们自己能够做主的。数据结构大部分情况下都是基于外存的，而且有极高的质量要求。

在 “[36 | 业务状态与存储中间件](https://time.geekbang.org/column/article/127490)” 这一讲中我们也说过，存储即数据结构。所以，服务端程序在数据结构这一点上，最为重要的一件事是选择合适的存储中间件。然后我们再在该存储中间件之上组织我们的数据。

这是数据库这样的存储中间件流行起来的原因。无论是关系型数据库，还是文档型数据库，他们都被设计为一种泛业务场景的数据结构，有很好的业务适应性。

所以在服务端我们谈数据结构，谈的不是内存数据结构，往往谈的是数据库的表结构设计。当然表（Table）是在关系型数据库中的说法，在 mongodb 中我们叫集合（Collection）。但不管我们用的是哪种数据库，出于惯例我们往往还是以 “定义表结构” 一词来表达我们想干什么。

描述表结构，核心需要包含以下内容：

- 字段名；
- 类型；
- 字段含义，以及是否指向另一个表的某个字段；
- 索引。

你会发现，其实定义表结构和定义内存数据结构本质是完全一致的。定义内存中的一个类（或结构体），我们也关心字段名（成员变量名）和类型，也关心字段的含义，以及它是否指向另一个类（或结构体）的某个字段（成员变量）。

但表结构比内存数据结构多了一个概念：索引。

索引为何存在？我认为有这样几方面的原因。一方面是因为数据库是泛业务场景的通用数据结构，它是动态的，需要依赖索引来提升数据访问的效率。另一方面是因为多租户。多租户导致数据量的爆发式增长，导致大部分情况下遍历查找变得不现实。

索引怎么设计？它完全取决于算法。算法里面使用了哪些数据访问的特征，这些数据访问的频次预期是多少，这些决定了我们添加哪些索引是最划算的。

在涉及的类比较多，或数据库的表结构比较复杂的时候，有时我们会用 UML 类图来对数据结构进行直观的呈现。

谈清楚了数据结构，我们接着聊算法。

在 “程序 = 数据结构 + 算法” 这个说法中，“算法” 指的是什么？在 “[42 | 实战（二）：“画图”程序后端实战](https://time.geekbang.org/column/article/136884)” 一讲中，我们这么说：

> 
在架构过程中，需求分析阶段，我们关注用户需求的精确表述，我们会引入角色，也就是系统的各类参与方，以及角色间的交互方式，也就是用户故事。


> 
到了详细设计阶段，角色和用户故事就变成了子系统、模块、类或者函数的使用界面（接口）。我们前面一直在强调，使用界面（接口）应该自然体现业务需求，就是强调程序是为用户需求服务的。而我们的架构设计，在需求分析与后续的概要设计、详细设计等过程之间也有自然的延续性。


> 
所以算法，最直白的含义，指的是用户故事背后的实现机制。


> 
数据结构 + 算法，是为了满足最初的角色与用户故事定义，这是架构的详细设计阶段核心关注点。


那么，怎么描述一个用户故事对应的算法？

一种方式是基于 UML 时序图（Sequence Diagram）。以下是我个人用过的很好的在线版 UML 时序图制作工具：

- [https://www.websequencediagrams.com/](https://www.websequencediagrams.com/)

另一种方式是基于伪代码（Pseudo Code）。在逻辑较为复杂时，伪代码往往有更好的呈现效果。比如，服务端程序对数据库的 SQL 操作往往比较复杂，但是从 UML 时序图来说流程却并不长，这个时候去画 UML 时序图的意义就不大。

## 结语

今天我们聊的是怎么做详细设计。

详细设计并不是只谈实现就完事，更不是一个架构图。它包括以下这些内容。

<li>
**现状与需求**
<ul>
- 现在在哪里，遇到了什么问题，要作何改进。

**需求满足方式**

- 要做成啥样？交付物的规格，或者说使用界面（接口）。
- 怎么做到？交付物的实现原理。

“程序 = 数据结构 + 算法” 是我们很熟悉的一个公式。它其实是怎么描述实现原理的很好的指导方针。当我们谈程序的实现时，我们总是从数据结构和算法两个维度去描述它。

如果你对今天的内容有什么思考与解读，欢迎给我留言，我们一起讨论。下一讲我们对第三章 “服务端开发篇” 进行回顾与总结。

如果你觉得有所收获，也欢迎把文章分享给你的朋友。感谢你的收听，我们下期再见。
