201706 Let's Talk About Design - xiaoxianfaye/Courses GitHub Wiki
- 1 About Design
- 2 What is Design?
- 3 What is Good Design?
- 4 How to do Design?
- 5 How to improve Design Capability?
- 6 What is Programming?
- 7 Design & Programming
- What is Design?
- What is Good Design?
- How to do Design?
- How to improve Design Capability?
- What is Programming?
我有一个梦想,梦想成真的过程中的所有事情都可以称之为设计。
软件开发过程:分析 – 设计 – 实现,设计是分析和实现之间的桥梁。
- 分析:定义清楚问题是什么 (容易被忽视,但其实非常重要!)
- 设计:问题分析清楚以后,提出解决问题的逻辑框架
- 实现:选择实现技术把逻辑框架的软件模型实现出来
在实际的软件开发过程中,分析、设计和实现很难划分清楚,除非你对这个领域已经非常熟悉。
设计就是把问题变成可计算的。

有一个领域问题,编写软件在计算机上运行来解决这个领域问题。不管用什么方法做设计,结果一定是可计算的。
用不同的语言、不同的方法写程序,都是在做设计,都是在把问题变成可计算的。那么,什么是好的设计呢?
用Java/Python语言写程序,跟用汇编语言写程序,区别在哪里?
用这两类语言写程序时,大脑中想的东西是不一样的。用Java/Python编程时大脑中想到比较多的可能是类、接口、变量、方法/函数等,用汇编语言编程时大脑中想到的比较多的可能是ADD、MOV、JMP等指令和寻址方式等。
在解决某些领域问题时,觉得Java/Python比汇编好用的根本原因在于两者工作在不同的计算模型上。不同的计算模型提供的构建块不同,不同的构建块表达了不同的语义层次,Java/Python等高级语言的计算模型更丰富,提供了更多、更高层次的语义。
编程语言不过是计算模型的反映。
再来看一个纯数学问题,既然已经有了直角坐标(笛卡尔坐标),为什么还要有向量坐标呢?
有这样一个领域问题,在坐标系中计算三角形的面积。

用直角坐标计算三角形的面积,方法如下:

用向量坐标计算三角形的面积,方法如下:

如果把三角形移动或者旋转一下呢?


从解决“在坐标系中计算三角形面积”这个问题的角度,哪种方法更“简单”?
- 这个问题领域的特点是:无论三角形如何平移、旋转,面积不变,和绝对直角坐标无关。
- 直角坐标计算模型是基于绝对直角坐标的,而向量坐标计算模型是基于向量的、与绝对直角坐标无关。
- 直角坐标的计算模型和向量坐标的计算模型提供了不同的语义层次。
- 从计算模型提供的语义层次和问题领域的匹配程度来说,向量坐标的计算模型与“在坐标系中计算三角形面积”这个问题领域更匹配。
- “向量坐标”方法更“简单”, “直角坐标”的方法能解决,但更“复杂”。
设计出的计算模型所提供的语义和问题领域的根本需求是否匹配,匹配就是好的设计,不匹配就是不好的设计。

软件复杂性是软件开发的敌人。软件复杂性分为本质复杂性和非本质复杂性。本质复杂性是问题本身内在的复杂性、没办法克服。但是对于现在大部分的软件系统来讲,我们认为的那些本质复杂性其实都是非本质复杂性,这些非本质复杂性的产生是因为我们采用的工具和思考问题的方式造成的。
复杂性为什么会对我们的软件开发带来障碍呢?是因为复杂性影响软件的“可理解性”。如果软件很难理解,开发和维护就会很困难。
代码在某个层面,可理解性一定是最重要的标准。为了可理解性,宁愿丧失灵活性、丧失可扩展性。如果代码不可理解,可扩展性根本就不重要,因为代码根本就看不懂,没法扩展。如果代码很好理解,就很难改错。这是一个很重要的设计权衡,往往过度追求灵活性、扩展性、正交性,但没有把最重要的可理解性放在第一位。
设计是应对软件复杂性最好的武器,当然必须是好的设计。
设计的核心是抽象。好的抽象可以提供恰当的工具来应对问题领域的本质复杂性,并最小化实现层面的非本质复杂性。同时,一个好的抽象可以清晰、简洁地展现出程序的算法框架,提供良好的组合能力。
无论采用何种设计和抽象,最终都必须是“可计算”的,否则就无法在计算机上实现出来。也就是说,设计活动的本质就是构造出一个针对问题领域的计算模型,基于这个计算模型来解决领域问题。这个计算模型就是抽象的根本,评价其好坏的标准就是其语义是否和问题领域的根本需求相匹配。
业界流行的对象设计技术,从本质上来说是在构建对象计算模型,虽然对象模型提供了较高的语义层次。但是在很多情况下依然和问题领域的计算语义有距离。即使在对象计算这个模型中应用再多的设计原则也解决不了计算语义距离这个问题,而源自对象技术的很多设计技巧和模式也说明了这一点,这些东西大多都是在为对象计算模型打的补丁。
从软件工程的发展历史上来看,也印证了这一观点。从机器语言到高级语言的发展其实就是提供了语义层次更高的计算模型。
我们不能只停留在C、C++、Java、Python等通用语言的语义,通用语言虽然提供了比汇编更丰富的语义构造,但和我们的问题领域还是有很大差距的。

- 对问题领域进行深入分析,发现问题领域的核心需求;
- 通过核心需求驱动出计算模型和语义;
- 再围绕这个计算模型提供一套语言,给外面的人使用这个计算模型提供一个接口,这个接口可以是API、可以是数据表达、也可以是语言;
- 最终要实现这个计算模型,实现的方法有解释器和编译器两种。
这就是DDD!这才是DDD!
这就是DSL!这才是DSL!
整个设计不一定要非常完善,只要把核心的计算模型做出来,就已经能很大程度上简化程序和设计了。比如,不是一定要做一个语言出来,有了计算模型以后做一个数据表达就已经能够很好地解决问题了。
提升设计能力的根本在于提升计算模型构造和语义定义能力。
重新思考什么是编程?
设计的本质是针对问题领域构造其语义与问题领域根本需求相匹配的计算模型。
而编程则是使用该计算模型的“指令”控制该计算模型进行计算,这个计算模型的“指令”语言则是常说的 Domain Specific Language。DSL是附属品、是皮毛,核心是计算模型的构造和语义定义。
| Expressing computation on top of XXX Computation model in XXX language. | “编程”不过是在某个计算模型上用某种语言去表达计算。 |
| Computation Model is the “universal machine” in the corresponding Domain. | 计算模型是相应领域中的“通用机器”。 |
| Program language is a way of describing computing machines. | 程序语言不过是描述计算机器的一种方法。 |
| Program is the description of a specific machine can be mimicked by the “universal machine”. | 程序是对特定机器的描述,这个特定机器可以被通用机器仿真。 |
用“Java语言编程”来解释一下这几句话,用Python等语言编程是类似的:
| Expressing computation on top of XXX Computation model in XXX language. | 用Java语言编程不过是在Java通用语言的计算模型上用Java语言来表达计算。 |
| Computation Model is the “universal machine” in the corresponding Domain. | Java通用语言的计算模型是Java通用语言领域的通用机器。 |
| Program language is a way of describing computing machines. | Java程序描述的是Java语言的计算机器。 |
| Program is the description of a specific machine can be mimicked by the “universal machine”. | 用Java程序实现了某个功能,这个Java程序就是对这个功能(特定机器)的描述。 |
借鉴这种思想我们来理解一下DDD/DSL:
| Expressing computation on top of XXX Computation model in XXX language. | 用DSL编程不过是在问题领域的计算模型上用DSL来表达计算。 |
| Computation Model is the “universal machine” in the corresponding Domain. | 问题领域的计算模型是问题领域的通用机器。 |
| Program language is a way of describing computing machines. | DSL程序描述的是DSL语言的计算机器。 |
| Program is the description of a specific machine can be mimicked by the “universal machine”. | 用DSL程序实现了问题领域中的某个功能,这个DSL程序就是对这个功能(特定机器)的描述。 |
对问题领域进行分析,设计计算模型(通用计算机器),设计一种语言来表达这个计算模型,然后用这个语言在这个计算模型上开发程序,开发出来的程序就可以被通用机器模拟出来。
这只是对我们每天都在干的事情的重新认识,并不是新的东西。

- 从问题领域导出核心需求,得到领域的计算模型(通用计算机器),在上面可以包装一个DSL语言或者数据或者API,基于这些开发程序和应用。
- 领域的计算模型和通用语言的计算模型之间存在鸿沟,可以用解释器或者编译器来填补。
- 解释器和编译器听起来很复杂,其实思想很简单,而且我们没有必要实现一个工业级别的、非常全面的解释器和编译器,只要借鉴这个思想实现我们的计算模型就够了。
Common Languages(Java/C) UML对应Java/C/Python等通用语言。
Common Languages(Java/C) UM对应Java/C/Python等通用语言提供的计算模型(通用计算机器)。