软件架构与系统复杂性

什么是复杂性

复杂或复杂性与简单相对立,那么复杂是什么?它是我们大脑中的一个概念,但是我在网上找不到一个给复杂恰当的定义描述,它会有不同的解释。
其中洛克在《人类理解论》中说道:『一些思想是由简单的思想组合而成,我称此为复杂;比如美、感激、人、军队、宇宙等。』
作为研究复杂系统的专家 Melanie Mitchell,也没有给出一个明确的公认的定义。她在《复杂》一书中给出了复杂系统加以定义:『复杂系统是由大量组分组成的网络,不存在中央控制,通过简单运作规则产生出复杂的集体行为和复杂的信息处理,并通过学习和进化产生适应性。』
上述复杂系统中的组分对应软件系统中的组成部分,基于不同粒度可以是对象、函数、类、包、模块、组件和服务等。每一部分都应该是相对单一的职责,细粒度部分之间耦合提供更粗粒度功能,不同组分之间相互协作来提供系统功能,继而组合成我们复杂的软件系统。

软件系统复杂性由何而来

计算机的产生对我们生产生活产生的影响不言而喻,其中软件系统的功能是随着我们实际生活需求的变化而变化的。人有七情六欲带来的各种需求,接收信息的方式主要是视觉、听觉。而机器擅长的只是简单的逻辑处理和数值计算,两者之间有着巨大的鸿沟。如何让机器提供视觉和听觉的手段来满足人们的需求,这里抛开硬件不谈,软件层面有操作系统提供基本的软件运行环境。
软件系统则只需要专注于如何组织和管理数据来满足人们的工作生活娱乐需求,一方面要关注人的需求和需求变化,另一方面要关注机器层面能提供的计算能力。
软件系统的复杂性来自于两个方面,一方面是需求侧复杂,导致大多数系统的功能都难以理解;另一方面是难以把控需求的变化,虽然我们遵循一些设计原则可以对未来进行一些预判,但还是存在不可预测的风险。

如何度量复杂度

在《复杂》一书中作者列举了不同角度可能度量复杂性的方法。

  • 生物学上尝试通过基因组的规模来度量。
  • 信息学上尝试通过熵、信息量、交互信息来度量。
  • 用算法信息量度量复杂性(能够产生对事物完整描述的最短计算机程序的长度。)
  • 此外还有逻辑深度、热力学深度、分形维度等方面。

复杂度并没有一个统一明确的度量方式,我们可以站在一个角度上对具体的某类或粒度提供一个可供参考的度量方法。不论我们如何度量,我们在开发软件系统中的一个重要目标就是控制和降低系统复杂度。在巨著《人月神话》中提出了两个重要概念:

  • 本质复杂度:指由于一问题的本质不适合简单的求解方式,所有可行的求解方式都很复杂的情形。
  • 偶然复杂度:指电脑软件开发过程中所引入不必要的复杂度。

偶然复杂度不是待求解问题的本质,相对而言, 本质复杂度和待求解问题的本质有关,是无法避免的。偶然复杂度一般是因为选用求解问题的方法时所引入的。

在源代码层面为了描述工程质量有以下两个方面衡量:

  • 圈复杂度:根据代码中的路径数量计算的循环复杂性。每当一个函数的控制流发生分裂时,复杂度计数器就会增加1。每个函数的最小复杂度为1。由于关键字和功能的不同,这种计算方法在语言上略有不同。以 Java 为例增加复杂度的关键字有:if, for, while, case, catch, throw, &&。
  • 认知复杂度:是由sonarQube设计的一个算法,算法将一段程序代码被理解的复杂程度,估算成一个整数——可以等同于代码的理解成本。作为指导程序员编写“既可测试又可维护”的方法。

在认知复杂度的计算方法中主要基于以下三条规则:

  1. 忽略那些允许将多个语句可读性地速记为一个的结构。
  2. 在代码的线性流程中,每中断一次就累加 1。
  3. 当断流结构被嵌套时难度累加 1。

下面实例对比两种复杂度度量方法的差异,在不同写法上圈复杂度的统计和认知复杂度的统计有何差异。

上图是两种写法在圈复杂度的统计方法,得出的值都是 4,也就是从逻辑上来说是相同的。但是在可读性上来说,明显右侧的 switch 代码更高。认知复杂度就是为了度量人的易于理解性上存在的。

以上是认知复杂度算法给这两种方法打出了明显不同的分数,这些分数更能反映出它们的相对可理解性。更具体的内容可以查看 CognitiveComplexity

如何管理系统复杂度

架构的本质目标就是管理复杂度,而管理复杂度有以下三种有效的手段:

  • 抽象:从众多的具体事物当中抽取共同的、本质的属性,摒弃差异的非本质属性,简化描述形成概念。
  • 分治:把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。
  • 领域知识:是指一组有内在联系的知识的集合,它往往与特定的职业、研究方向、兴趣、社群或文化圈层等相关联。