代码的艺术:用工程思维驱动软件开发
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

2.6.4 系统设计的原则和方法

在系统设计中,有很多重要的原则和方法。受篇幅所限,本节只给出最重要的几条。

1. 单一目的(Single Purpose)

在系统中,每个组件(子系统/模块)的功能都应该足够专注和单一。

我认为,这是系统设计中最重要的原则。“单一目的”是组件复用和系统方便扩展的基础。

很多软件工程师在做系统切分时,没有遵守这个原则,将多个功能放在一个子系统中。这样就会使这个子系统的功能过于复杂,难以维护。另外,这样的子系统由于功能不专一,也难以供其他场景使用(别人需要A,你却给出了A+B,超出了需求,给使用者增加了负担)。

2. 对外关系清晰

在系统中,各子系统/模块之间的关系应该简单而清晰。对于每个子系统,应该通过明确的对外接口来访问。

很多人都感到软件很复杂,而软件的这种复杂性,在很大程度上来自软件中各子系统、各模块之间的耦合关系。非常有意思的是,软件的复杂性(也可以叫“坑”)并不是别人强加给软件工程师的,而是软件工程师自己造成的。这就好比,软件工程师自己“吐”出了“丝”(也就是子系统间的各种关系),而这些“丝”却把软件工程师自己缠住了,让他们无法解脱。所以,在软件中管理子系统间的关系非常重要。

很多软件工程师都学习过这样的内容:要避免在软件中使用“全局变量”。全局变量的可怕之处在于:在你没有察觉的情况下,在某个子系统中开放了一个行为不确定的对外接口,从而使得子系统的对外关系变得不清晰。如果一个软件中存在很多全局变量,这个软件中子系统间的关系就会变得非常混乱,这样的软件非常难以把握和维护。

3. 重视资源约束

在系统设计中,要考虑到资源对于设计的约束。

常见的资源包括计算(CPU资源)、存储(内存、磁盘等)、I/O和网络。在系统设计中,可能会出现一种或多种资源成为瓶颈。作为一名软件工程师,要清楚地了解资源的瓶颈,并采取相应的对策。

有些时候,多种资源之间是可以互相转换的。比如,可以采用“空间换时间”的方式,即使用更多存储来降低对CPU资源的使用;也可以采用“压缩”的方法,即使用更多CPU来降低对网络资源的使用。

顺便说一句,数据结构是所有高校计算机专业学生的必修课。但是在多次现场交流中,我发现很多软件工程师虽然在学校学习过链表、二叉树等概念,但并没有掌握数据结构的本质。在我看来,数据结构这门课可以用以下两句话来概括。

(1)如何用空间换时间。从链表到哈希表,再到二叉树,通过使用更多的存储空间,不断降低查找等操作的复杂度。

(2)如何在“读复杂”和“写复杂”间做权衡。对很多数据结构来说,“读”和“写”经常是一对矛盾的操作。在很多时候,我们会假设“读”的频率远高于“写”,于是在优化中更偏向于牺牲“写”的性能,从而提升“读”的性能。

4. 根据需求做决策

从上面的描述中可以看到,在系统设计中,软件工程师经常需要做设计决策的权衡。要想实现同样的功能,可以消耗更多CPU资源,使用较少的存储资源;也可以消耗较少的CPU资源,使用更多的存储资源。

在做设计决策时,“需求”是重要的决策依据,这也是为什么我们要一再强调需求分析的重要性。

5. 基于模型思考

在系统设计中,软件工程师思考的重点是概念、模型、数据结构和算法,大家应该(也完全可以)脱离代码实现的细节,比如,使用的编程语言、具体的函数实现细节等,而基于模型思考的能力是软件工程师需具备的重要能力之一。

在现实中,我发现很多软件工程师在系统的认识方面很难脱离对代码细节的关注。例如,要求对某个程序的实现进行分析,一名软件工程师给出的回答是:

“这个程序从main函数开始运行,main函数调用A函数,A函数调用B函数、C函数,B函数调用D函数……”

另一名软件工程师给出的回答是:

“这个程序是用来处理HTTP请求的,使用了多线程的机制,在程序中包含了一个转发路由表……”

从以上两个回答中可以明显地看到,两名软件工程师所“看到”的程序是完全不同的,第一名看到的是代码的细节,第二名看到的是系统中所包含的概念和模型。

有些读者可能会问,你说的“模型”这个词太抽象了,到底应该怎么去学习呢?其实从小到大,我们在学校学过的很多课程都是为了锻炼对模型的思考能力,数学、物理、化学这些学科都包含了模型,操作系统、计算机网络、系统结构这些学科里也都有模型的身影。对每个新领域的学习,学习者都会从基本概念出发,在概念间建立联系,通过逻辑构建起一个模型的大厦。有兴趣的读者也可以从一些教科书或论文中看看它们是如何描述系统的模型的。