<audio id="audio" title="01 | 软件设计到底是什么？" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/64/0e/6464cf35de7f644cf93111bdb4e2220e.mp3"></audio>

你好！我是郑晔。

一个软件需要设计，这是你一定认同的。但软件设计到底是什么，不同的人却有着不同的理解：

- 有人认为，设计就是讨论要用什么技术实现功能；
- 有人认为，设计就是要考虑选择哪些框架和中间件；
- 有人认为，设计就是设计模式；
- 有人认为，设计就是Controller、Service加Model；
- ……

你会发现，如果我们按照这些方式去了解“软件设计”，不仅软件设计的知识会很零散，而且你会像站在流沙之上一般：

- 今天你刚学会用Java，明天JavaScript成了新宠，还没等你下定决心转向，Rust又成了一批大公司吹捧的目标；
- 你终于知道了消息队列在解决什么问题，准备学习强大的Kafka，这时候有人告诉你Pulsar在某些地方表现得更好；
- 你总算理解了Observer模式，却有人告诉你JDK中早就提供了原生的支持，但更好的做法应该是用Guava的EventBus；
- 你好不容易弄清楚MVC是怎样回事，却发现后端开发现在的主要工作是写RESTful服务，Controller还没有用，就应该改名成Resource了；
- ……

我们说，软件设计要关注长期变化，需要应对需求规模的膨胀。这些在不断流变的东西可能还没你的软件生命周期长，又怎能支撑起长期的变化呢！

那么回到一开始的问题，软件设计到底是什么呢？

## 核心的模型

在回答这个问题之前，我们先来思考这样一件事：软件的开发目的是什么？

一个直白的答案就是，软件开发是为了解决由需求带来的各种问题，而解决的结果是一个可以运行的交付物。比如，我们在线购物的需求，是通过电商平台这个方案解决的。

那软件设计在这个过程中做的事情是什么呢？就是在需求和解决方案之间架设一个桥梁。

<img src="https://static001.geekbang.org/resource/image/a6/20/a6920a1e9a4a8af9fe86b88f032cb820.jpg" alt="">

区别于解决简单的问题，软件的开发往往是一项长期的工作，会有许多人参与其中。在这种情况下，就需要建立起一个统一的结构，以便于所有人都能有一个共同的理解。这就如同建筑中的图纸，懂建筑的人看了之后，就会产生一个统一的认识。

而在软件的开发过程中，这种统一的结构就是模型，而**软件设计就是要构建出一套模型**。

这里所说的模型，不仅包括用来描述业务的各种实体，也包括完成业务功能的各种组件。人们写代码中常常会用到的服务（Service）、调度器（Scheduler）等概念就是一个一个的模型。

**模型，是一个软件的骨架，是一个软件之所以是这个软件的核心**。一个电商平台，它不用关系型数据库，还可以用NoSQL，但如果没有产品信息，没有订单，它就不再是电商平台了。

可能有不少人一听到模型，就会情不自禁地要打退堂鼓，认为这些内容过于高大上，其实大可不必，**模型的粒度可大可小**。如果把模型理解为一个一个的类，是不是你就会熟悉很多了，这就是小的模型。你也可以把一整个系统当作一个整体来理解，这就是大的模型。

关于设计，你一定听说过一个说法，“**高内聚、低耦合**”，（模块的内聚程度越高越好，模块间的耦合程度越低越好），**这其实就是对模型的要求。**一个“高内聚、低耦合”的模型能够有效地隐藏细节，让人理解起来也更容易，甚至还可以在上面继续扩展。比如，我们后面课程会讲到的程序设计语言，就是提供了一个又一个的编程模型，让我们今天写程序不用再面对各种硬件的差异，还能够在此基础上继续提供新功能。

你在日常工作中用到的各种框架和技术，也是提供了一个又一个的模型，它们大幅度降低了我们的开发门槛。所以你看，整个计算机世界就是在这样一个又一个模型的叠加中，一点一点构建出来的。用一个程序员所熟悉的说法就是：模型是分层的。这就像乐高一样，由一个个小块构建出一个个大一些的部件，再用这些部件组成最终的成品。

这与一些人常规理解的Controller、Service那种分层略有差异。但实际上，这才是在计算机行业中普遍存在的分层。我们熟悉的网络模型就是一个典型的分层模型。按照TCP/IP的分层方法，网络层要构建在网络接口层之上，应用层则要依赖传输层，而我们平时使用的大多数协议则属于应用层。

<img src="https://static001.geekbang.org/resource/image/bc/5a/bc7c736084e79d067477c306a9f5bb5a.jpg" alt="">

即便是在一个软件内部，模型也可以是分层的。**我们可以先从最核心的模型<strong><strong>开始构建**</strong>，有了这个核心模型之后，**<strong>可以**</strong>通过组合这些基础的模型，构建出上面一层的模型</strong>。

我曾经做过一个交易系统的设计。在分析了主要的交易动作之后，我提出了一个交易原语的概念，包括资产冻结、解冻、出金、入金等少数几个动作。然后，把原先的交易动作变成了原语的组合。比如，下单是资产冻结，成交是不同账户的出金和入金，撤单则是资产解冻。

<img src="https://static001.geekbang.org/resource/image/b6/8d/b6432a9f6de195311674a8c0ac5a9c8d.jpg" alt="">

在这个结构下，由交易原语保证每个业务的准确性，由交易动作保证整个操作的事务性。从上面这个图中，你可以看出，这就是一种分层，一种模型上的分层。

好，到这里我们已经对软件设计中的模型有了一个初步的认识。总结一下就是，模型是一个软件的核心；模型的粒度可大可小；好的模型应该“高内聚、低耦合”；模型可以分层，由底层的模型提供接口，构建出上层的模型。

后续我们这个课程的大部分内容都会围绕着模型来讲：怎样理解模型、建立模型、评判模型的优劣等等。

学会这些知识之后，能在多大的粒度上应用它们，你就能掌控多大的模块。不过，仅仅是把软件设计理解成构建模型，这个理解还不够。模型设计也不能任意妄为，需要有一定的约束，而这个约束，就是软件设计要构建的另一个部分：规范。

## 约束的规范

如果说，软件设计要构建出一套模型，这还是比较直观好理解的。因为模型通常可以直接体现在代码中。但软件设计的另一部分——规范，就常常会被忽略。

**规范，就是限定了什么样的需求应该以怎样的方式去完成****。**比如：

- 与业务处理相关的代码，应该体现在领域模型中；
- 与网络连接相关的代码，应该写在网关里；
- 与外部系统集成的代码，需要有防腐层；
- ……

其实，每个项目都会有自己的规范。比如，你总会遇到一些项目里的老人，他们会告诉你，这个代码应该写在这，而不应该写在那，这就是某种意义上的规范。虽然规范通常都有，但问题常常也在。

**一种<strong><strong>常见的问题**</strong>就是缺乏显式的、统一的规范。</strong>

规范的一个重要作用，就是维系软件长期的演化。如果没有显式的规范，项目的维系只能依靠团队成员个人的发挥，老成员一个没留神，新成员就可能创造出一种诡异的新写法，项目就朝着失控又迈出了一步。

不知道你是否接触过这样的项目，多种不同的做法并存其中：

- 数据库访问，有用MyBatis的，有用JDBC的，也有用Hibernate的；
- 外部接口设计，有用REST风格的，有用URL表示各种动作的；
- 文件组织，有的按照业务功能划分（比如，产品、订单等），有的按照代码结构划分（比如，Resource、Service等）；
- ……

没有一个统一的规范，每一个项目上的新成员都会痛斥一番前人的不负责任。然后，新的人准备另起炉灶，增加一些新东西。这种场景你是不是很熟悉呢？混乱通常就是这样开始的。

如果存在一个显式的、统一的规范，项目会按照一个统一的方向行进。即便未来设计要演化、规范要调整，有一个统一的规范也要比散弹打鸟来得可控得多。

关于规范，**还有一种常见问题就是，<strong><strong>规范不符合**</strong>软件设计原则</strong>。我给你讲一个让我印象深刻的故事。

我曾经遇到一个网关出现了OOM（Out of Memory，内存溢出）。这个网关日常的内存消耗高达150G，一次流量暴增它就扛不住了。后来经过优化，把内存消耗降到了8G。

如果单看数字，这是一个接近20倍的优化，大手笔啊，但这里面究竟发生了什么呢？实际上，这次优化最核心的内容就是构建了一个防腐层，将请求过来的JSON转换成了普通的内存对象。而原来的做法是把JSON解析器解析出来的对象到处使用，因为这些对象上附加很多额外的信息，导致占用了大量的内存。

很遗憾，这不是大牛战天斗地的故事，只是因为旧的规范不符合软件设计原则而导致的错误：外部请求的对象需要在防腐层转换为内部对象。

## 模型与规范

有了模型，有了规范，**那模型与规范是什么关系呢？模型与规范，二者相辅相成**。一个项目最初建立起的模型，往往是要符合一定规范的，而规范的制定也有赖于模型。这就像讨论户型设计时，你可以按照各种方式组合不同的空间（模型），却不会把厨房与卫生间放在一起（规范）。

至此，我们已经知道了，软件设计既包含构建出一套模型，也包括制定出相应的规范。再回过头来看这节课开头的问题，你是不是对软件设计有了重新的认识呢？特定技术、框架和中间件，只是支撑我们模型的实现，而设计模式、Controller、Service、Model这些东西也只是一个特定的实现结果，是某些特定场景下的模型。

## 总结时刻

今天，我们学习了软件设计到底是什么，它应该包括“模型”和“规范”两部分：

<li>
模型，是一个软件的骨架，是一个软件之所以是这个软件的核心。模型的粒度可大可小。我们所说的“高内聚、低耦合”指的就是对模型的要求，一个好的模型可以有效地隐藏细节，让开发者易于理解。模型是分层的，可以不断地叠加，基于一个基础的模型去构建上一层的模型，计算机世界就是这样一点点构建出来的。
</li>
<li>
规范，就是限定了什么样的需求应该以怎样的方式去完成。它对于维系软件长期演化至关重要。关于规范，常见的两种问题是：一个项目缺乏显式的、统一的规范；规范不符合软件设计原则。
</li>
<li>
模型与规范，二者相辅相成，一个项目最初建立起的模型，往往是要符合一定规范的，而规范的制定也有赖于模型。
</li>

有了对软件设计的初步了解，我们就准备开始做设计了，但该从哪入手呢？这就是我们下一讲的内容。

如果今天的内容你只能记住一件事，那请记住：**软件设计**，**应该包括模型和规范**。

<img src="https://static001.geekbang.org/resource/image/03/58/038f4e3e01a9cdd4d40ccf37e6771558.jpg" alt="">

## 思考题

最后，我想请你分享一下，你的项目是如何做设计的。欢迎在留言区写下你的想法。

感谢阅读，如果你觉得这一讲的内容对你有帮助的话，也欢迎把它分享给你的朋友。
