软件系统的复杂度及其控制

因为涉及一些公式,图片无法上传,此处只有部分内容,完整内容请参考语雀:

软件系统的复杂度评估和控制 · 语雀

信息和计算领域,有很多悬而未决的问题,归约到软件系统或者应用技术领域,复杂度是最为显著和重要的问题之一,它关联着产品、技术、工具、文档、组织等许多方面的现状和历史,又深远密切地影响者它们的未来。能否解决软件复杂度带来的问题,控制好软件复杂度的增长速度,是我们能否多快好省地落实产品计划,实现产品需求,提升产品性能的核心所在。

接触过程序设计的人都了解一个概念,程序=数据结构+算法,其中算法又有时间复杂度和空间复杂度,但是稍有经验者会知道,算法复杂度属于计算复杂度,无法用于评估庞大软件系统,也无法用于指导更加复杂的研发过程。对此,我们衍生出的软件工程,以及其他如系统论、控制论、耗散论、协同学等无关的基础学科,有着更加清晰的问题阐述,更加合适的理论框架,和更加有效的解决方案。但是它们离应用技术领域有稍有距离且难以掌握,我们往往用自己熟悉的语言,用经验性方法去交流、解决软件复杂度带来的问题。显然,很多时候,我们无法将面临的问题,归纳到统一的逻辑框架中,得出始终一致的,有效的结论和方案。

因此,我想借助本文,提出一个简要的复杂度评估模型,用于指导在应用技术领域的架构、设计、重构等工作,并倡导一个思想——将复杂度控制,作为软件迭代的基础目标。为了引申出,并且更好地理解这个模型,我们先来了解一下,关于(软件)复杂度,目前的一些研究和看法。

软件工程中的复杂性定义

软件工程学科也没有提出通用的复杂性定义和度量方式,但针对某些具体的场景,有一些明确的定义和评估方式。

Manny Lehman的复杂度定义
Manny Lehman教授在软件演进法则中首次系统性提出了软件复杂度。
软件(程序)复杂度是软件的一组特征,它由软件内部的相互关联引起。随着软件的实体(模块)的增加,软件内部的相互关联会指数式增长,直至无法被全部掌握和理解。

在他的描述中,软件复杂度的影响是:
软件的高复杂度,会导致在修改软件时引入非主观意图的变更的概率上升,最终在做变更的时候更容易引入缺陷。在更极端的情况下,软件复杂到几乎无法修改。

Halstead的复杂度定义
Halstead 复杂度 (Maurice H. Halstead, 1977) 是软件科学提出的第一个计算机软件的分析“定律”,用以确定计算机软件开发中的一些定量规律。Halstead 复杂度采用一组基本的度量值,这些度量值通常在程序产生之后得出,或者在设计完成之后进行估算。

Halstead 复杂度根据程序中语句行的操作符和操作数的数量计算程序复杂性。

  • 操作符和操作数的量越大,程序结构就越复杂。
  • 操作符通常包括语言保留字、函数调用、运算符,也可以包括有关的分隔符等。
  • 操作数可以是常数和变量等标识符。

John Ousterhout的复杂度定义
斯坦福教授、Tcl语言发明者John Ousterhout,在他的著作《A Philosophy of Software Design》中提出,软件设计的核心在于降低复杂性。他选择从认知的负担和开发工作量的角度来定义软件的复杂性,并且给出了一个复杂度量公式:

[图片上传失败...(image-6479d9-1629287281563)]

子模块的复杂度Cp乘以该模块对应的开发时间权重值tp,累加后得到系统的整体复杂度C。系统整体的复杂度并不简单等于所有子模块复杂度的累加,还要考虑开发维护该模块所花费的时间在整体时间中的占比(对应权重值tp)。也就是说,即使某个模块非常复杂,如果很少使用或修改,也不会对系统的整体复杂度造成大的影响。子模块的复杂度Cp是一个经验值,它关注几个现象:

  • 修改扩散,修改时有连锁反应。
  • 认知负担,开发人员需要多长时间来理解功能模块。
  • 不可知(Unknown Unknowns),开发人员在接到任务时,不知道从哪里入手。

造成复杂的原因一般是代码依赖和晦涩(Obscurity)。其中,依赖是指某部分代码不能被独立地修改和理解,必定会牵涉到其他代码。代码晦涩,是指从代码中难以找到重要信息。

McCabe推出的一系列复杂度
70年代,软件系统已经变得极其复杂,无论是开发还是维护都是一项成本高昂的工作。人们意识到必须使软件模块化,以便于开发、测试和维护。为此,成立于1976的McCabe&Associates公司开发出了McCabe Cyclomatic Complexity Metric(圈复杂度)技术对软件进行结构测试,随后又推出了其他一些复杂度,统称为McCabe复杂度。McCabe复杂度包括:圈复杂度、基本复杂度、模块设计复杂度、设计复杂度、集成复杂度、行数、规范化复杂度、全局数据复杂度、局部数据复杂度、病态数据复杂度。

McCabe复杂度是对软件结构进行严格的算术分析得来的,实质上是以图论为工具,对程序拓扑结构复杂性的度量。
它认为程序的复杂性很大程度上取决于程序图的复杂性。单一的顺序结构最为简单,循环和选择所构成的环路越多,程序就越复杂。因此,计算McCabe系列的复杂度时,需要将程序还原成程序图。

程序图是退化的程序流程图。也就是说,把程序流程图的每一个处理符号都退化成一个结点,原来连接不同处理符号的流线变成连接不同结点的有向弧,这样得到的有向图就叫做程序图。程序图仅描述程序内部的控制流程,完全不表现对数据的具体操作分支和循环的具体条件。因此,它往往把一个简单的IF语句与循环语句的复杂性看成是一样的,把嵌套的IF语句与CASE的复杂性看成是一样的。

下面,我们简单了解一下各个复杂度的定义和计算方法。

  • 圈复杂度(Cyclomatic complexity,V(G))

圈复杂度也称为条件复杂度,它可以用来衡量一个模块判定结构的复杂程度。其数量上表现为独立路径的条数,也可理解为覆盖所有的可能情况最少使用的测试用例个数。

计算方法:

  • 点边计算法,V(G) = E - N + 2
  • 节点判定法,V (G) = P + 1

程序的可能错误和高的圈复杂度有着很大关系,圈复杂度最高的模块和方法,其缺陷个数也可能最多。

image
  • 基本复杂度(Essential Complexity, VE(G))

基本复杂度是用来衡量程序非结构化程度的,非结构成分降低了程序的质量,增加了代码的维护难度,使程序难于理解。因此,基本复杂度高意味着非结构化程度高,难以模块化和维护。实际上,消除了一个错误有时会引起其他的错误。

计算方法:
将圈复杂度图中的结构化部分简化成一个点,计算简化以后流程图的圈复杂度就是基本复杂度。

  • 模块设计复杂度(Module Design Complexity (iv(G))

模块设计复杂度用来衡量模块之间的调用关系,复杂度越高,模块之间耦合性越高,越难隔离,维护和复用。

计算方法:
从模块控制流图中移去那些不包含调用子模块的判定和循环结构后得到的圈复杂度。模块设计复杂度通常远小于圈复杂度。

  • 设计复杂度(Design Complexity (S0)

设计复杂度以数量来衡量程序模块之间的相互作用关系,它提供了系统级模块设计复杂度的概况,有助于衡量进行自底向上集成测试的效果,而且提供了全面衡量程序设计规格和复杂度的数据,不反映独立模块的内部情况。高设计复杂度的系统意味着系统各部分之间有着复杂的相互关系,这样系统将难以维护。

计算方法:
程序中所有模块设计复杂度之和。

[图片上传失败...(image-fa6075-1629287281563)]

  • 集成复杂度(Integration Complexity (S1) )

集成复杂度是为了防止错误所必须进行的集成测试的数量表示,另一种说法是程序中独立线性子树的数目。一棵子树是一个有返回的调用序列。就像圈复杂度是测试路径的数目,而集成复杂度是程序或其子系统的独立线性子树。

计算方法:
S1=S0-N+1, N是程序中模块的数目。

  • 行数(Number of Lines (nl) )

模块中总的代码行数,包括注释。

  • 规范化复杂度(Normalized Complexity (nv) )

规范化复杂度等于圈复杂度除以行数

计算方法:
nv=v(G)/nl

复杂性科学中的定义

系统复杂性有专门的学术分支,而软件系统正是一种特定的易于研究的系统,因此,复杂性学科的许多结论和方法,都适用于软件系统,甚至比软件工程中的描述更加通用。

复杂性学科认为,复杂系统是指相互依赖,每个组件的行为依赖其他组件的行为,减少部分或者分解后不能运行的系统。它以本体论和认知论为框架,将复杂性分为以下几类:

image

组分复杂性
组分复杂性是指,复杂系统拥有数目繁多的组分,组分间拥有多样且复杂的相互作用,要素与要素间的关系呈现出各种不确定。它包括:

  1. 构成复杂性

系统演化过程中,构成系统的不同因素会影响其自身的复杂性。

  1. 分类复杂性

组分个体要素之间的变异,及其在空间分布上的不规则性,以及由于组分的异质,导致组分种类姿态万千而引起的系统复杂性。

  1. 规模复杂性

单元数量越大,单元类型越多,系统则因自身规模的增大而复杂。

结构复杂性
复杂性会随关联结构的从属性和多样性的提高,以及联结数量和强度的提高而增加,整合生成结构复杂性。它包括:

  1. 组织复杂性

组织形态复杂度的提高,带来了组织结构的多样性和复杂性,开放系统在演化过程中,结构状态的横向、纵向和空间分布的差异性越大,系统复杂性越高。

  1. 层次复杂性

系统不同层级间的作用差别很大,构成系统的子系统的层次越多,系统结构越复杂。

  1. 过程复杂性

在复杂系统进化和演化过程中,系统内部的要素之间的相互作用、系统与环境边界交互作用、及系统与外部环境的复杂作用都会产生复杂性。系统通过自组织耗散行为和自组织临界,不断变革内部结构和外部环境之间的关系,可能会出现分岔、混沌等现象因而会在演化过程中产生复杂性。

功能复杂性
功能复杂性是指系统中要素的平衡性与系统整体功能(结构)之间的关系带来的复杂性。它包括:

  1. 预测复杂性

当预测系统状态演化时,复杂性可定义为系统状态或行为的不可预测性。

  1. 保持复杂性

当希望保持系统功能时,复杂性可定义为系统的功能,关于系统要素平衡程度的灵敏性(脆弱性或非鲁棒性)。

  1. 调控复杂性

当改变系统功能时,复杂性可定义为如何实现新功能或所需功能的难度。

如何根据功能,对系统的要素进行合理的分配,将会直接影响到系统的功能复杂性。从控制论的角度的角度看,系统的复杂性与系统的能控性、可观性或能达性均密切相关,系统设计必须平衡系统性能和复杂性之间的关系。

描述复杂性
从描述系统的状态的工作量、信息量以及存储量角度出发,来定义系统的复杂性。描述复杂性是以数学的复杂性理论和信息论为形式表现出来的,认为系统的复杂性就是描述系统特征的复杂性。

  1. 计算复杂性

等效于由计算机解决一个问题,所消耗的时间和空间度量。

  1. 算法复杂性

问题解决过程中,涉及的描述、步骤、方法、以及仿真程序等要素的无规则和随机性带来的复杂性。

  1. 有效复杂性

以对系统规律性认知的表述长度来衡量系统的复杂性。

复杂性分类的联系
在三维空间中,该分类可以看作是以基元、功能维、结构维为基准,以描述复杂性为手段体现具
体表示过程来定义系统的复杂性,如图:

image

复杂性分类及联系示意图

复杂性的度量
即使在复杂性学科中,度量方式也并未统一,针对不同的类别的复杂性,有着多种不同的度量方式,有些精确的,有些是经验的,但都相对复杂。鉴于我们的讨论目的以及时间成本,此处不再予以展开。多数时候,我们只需要了解几种复杂度的概念、机制、模糊的度量方案,用于引导我们解决问题的思路,或者比较几种实现方案的复杂度优劣。

复杂度定义整理

了解了软件工程和复杂性科学中的复杂度定义和度量方式之后,我们对复杂度的研究情况,有了整体上的印象。可以看到,复杂度本身的衡量也是趋于复杂的。我们研究复杂度,是为了利用它解决我们的问题,而不是为了多出一个问题。所以,我们简单总结一下,每种复杂度的定义的优缺点。

作为最早提出的复杂度,它是一种经验性的表述,没有度量方式,仅指出了复杂度由软件的规模和内部的关联引起,并且会导致难以理解和修改。

使用程序程序的操作符、操作数作为复杂度的度量方式,本质上,主要是度量程序规模引起的复杂度,是一种模糊的局限性较强的度量方式。

首次在软件领域,提出了使用认知难度和工作量来衡量复杂度,但是它提出的评估方法依然是模糊的,更关键的是,是后验的、总结式的,无法在应用在设计和架构阶段。

基于图论来计算程序复杂度,推出了核心的圈复杂度,以及基于圈复杂度的多个辅助指标,并且开发了统计工具。因为采取图论作为理论工具,接近程序本质,而且理论成熟,因此是一种准确的清晰的复杂度定义,被广泛接受和应用。但是也因为如此,McCabe系列的复杂度都偏向于代码细节,更适合用作函数、类级别的复杂度评估,而作为软件系统复杂度评估手段时,统计会非常困难,理解统计结果,得出有效结论也会非常困难,往往会陷入复杂度评估陷阱,甚至本末倒置。

作为复杂性方向的学科,最系统地剖析了复杂性的本质,且引入了三维空间来衡量系统的复杂度,引入描述复杂度来粘合空间系中的复杂度分量和作为最终复杂度的定量。了解了复杂度科学之后,我们很容易联想、理解软件工程中的复杂度概念。比如,软件难于修改,实际是表达调控复杂度;圈复杂度,实际是表达预测复杂度;计算机科学中的算法复杂度属于描述复杂度等等。

纵观已有的复杂度体系,笔者看来,仍有一些问题尚未解决。例如:

  1. 软件工程的复杂度概念贴近应用技术领域,但不够系统。复杂性学科中的定义虽然完善,但其中的概念偏于通用和抽象,难以直接用于应用技术领域问题的讨论和判定。
  2. 所有的复杂度,都未将目标系统的需求(外部变更)和组织考虑为复杂度因素,导致复杂度控制会沦为滞后性的思维和措施,且难以落实到组织行为。
  3. 多数的复杂度解析,缺少形式化描述和推理过程,导致复杂度理解和讨论,也是一件复杂的事情。
  4. 未提出复杂度的优化策略,并给出系统性证明。

复杂度的形式化定义
在展开软件系统复杂度形式化之前,我们先讨论抽象系统中,依赖关系带来的复杂度如何评估。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容