软件系统复杂性灾难及解决方案探究

软件系统复杂性灾难及解决方案探究

一、What is complex

在谈论复杂性灾难极其解决方案之前,我们首先来理解一下“复杂”这个词。
什么是“复杂”呢的英文单词complex的释义能很好的表达软件工程中涉及到的复杂性。

根据权威的牛津词典,complex的释义为:

made of many different things or parts that are connected; difficult to understand

可以看到complex有三层基本含义,首先是由多个部分或事物组成,其次这些部分之间是有相互关联的;再次,整体不便于理解。

复杂性,是人类 会高速发展的必然产物。过去200年的现代化过程,复杂性是个常见的事情,人类 会总是趋向于越来越复杂。就拿机器来说,从最早的几十个零件,到几百几千几万个零件。一架波音747飞机上面,有600万个零件,275公里长的的各种管线。世界上没有任何一个人能彻底搞得清楚一架波音747飞机的完整构造。

但是作为应对,人类也演化出来了一个处理这种复杂系统的方法,简单来说就是模块化加上分层协作。每个人只负责其中的一个模块,然后层层叠加,构成一个庞大的系统。出了问题也不怕,层层分解,也能找到问题所在。所以,即使像芯片那么复杂的东西,上面集成了数以亿计的晶体管,人类还是可以设计它,制造它和控制它。复杂性看起来没有特别可怕;波音747的可靠性可能要比大多数APP高得多,平均每百万次航班出现1.62次死亡事故;而世界上安全性最高的飞机每百万次航班出现死亡事故0.33次。

二、The Disaster

我们再来看几个数字。2015年,谷歌公司统计了自家所有产品的代码,加起来20亿行。2018年,阿里巴巴公司的程序员,一年写下了12亿行代码。而2020年华为的绝版芯片,5nm工艺的麒麟9000,内含晶体管数量有153亿个。
听起来仿佛也还好吧软件公司的代码行数,和一个芯片上的晶体管数量,在量级上相差无几。所以一个芯片我们人类可以控制,那软件我们人类是不是也可以很容易的控制呢是否定的,它们的复杂性完全不一样。

作为业内崩溃率控制比较优秀的产品,**APP的crash率在万分之2左右;日常也有很多非常多的各类bug。如果是崩溃率稍微差一点的产品,崩溃率可能会更高,日常使用中各种bug更是随处可见。
原因是什么呢看官可以花一分钟的时间思考一下。

《为什么需要生物学思维》这本书里说,一堆东西放在那里,即使很多,很乱,那叫“庞杂”。但是一堆东西,到了一定数量,之间还互相影响,发生“级联效应”,那就叫“复杂”。一旦达到这样的复杂程度,将成为一个无法控制的,甚至是无法理解的系统,我们可以称之为“复杂性灾难”。

物理设备、模块之间的边界比较清晰,相互作用比较明确。而代码和代码之间是要发生各种各样的互相影响的,还会有“级联效应”。几百万行,甚至十几亿行的代码,一旦发生互相之间的关联互动,而且是一层一层的不同级别的互动,产生的情况有多少种呢比整个宇宙的粒子数量还要多。

所以你看,过去机械时代的工程师形象是很严谨的。他们干的活儿是绘制蓝图、按图施工、精确控制、准确无误。当他们造出了一个机械系统,不管多复杂,他们就是这个系统的神。但是今天的软件工程师呢这个系统是我们一行行写的,也是我们造出来的,但我们远远不是这个系统的神。总有一些我们控制不住的东西,比如我们一辈子都要和bug打交道。永远不可能把它们彻底消除。不是软件工程师不优秀,而是bug实在太狡猾。

这是一个很深刻的变化。

举个例子,1986年1月,美国挑战者 航天飞机升空爆炸。不到一个月后,著名物理学家费曼就提交了一份 告,说出事的原因分析出来了,是因为一个密封圈出了问题。虽然也很难,需要费曼这样大神级的物理学家才能分析出来,但是毕竟最后罪魁祸首找到了。
但是10年后,1996年,欧洲阿丽亚娜火箭在发射升空后爆炸。为什么呢一个模糊的原因,说火箭在新环境下使用了一些较为陈旧的软件代码。但是,没有任何一个承包商被追责。因为这次爆炸并不是某个决策失误导致的,找不到挑战者 密封圈的那样的东西。整个发射系统的极度复杂,带来了这样的灾难。
再过10年,2007年,丰田汽车在美国出了一个重大事故,车子在行驶中突然加速,刹车失灵,最后致人死亡。这件事当时闹得很大。事后,丰田请来专家调查原因。查来查去,发现是软件的原因。说到底,是因为丰田发动机软件系统过度庞大、极度复杂,没有办法把事故责任明确地归咎于某个设计或部件出了错。

对于意外加速事故的发生,丰田发动机软件系统的过度庞大和极度复杂,以及糟糕的设计都应该承担相应的责任。我们无法将事故责任明确地归咎于某个设计或部件出了错,毕竟,这里面存在的问题盘根错节,而且这些问题还会引发汽车软件系统与外部机电系统之间的大规模交互。无论是单独看来,还是整体看来,这个系统的极度复杂性让我们很难理解这些相互作用、相互影响的部件的深层问题和缺陷。

就在过去这几十年里,人类制造的系统的复杂性空前提高了,高到了既搞不清楚原因,也找不到罪魁祸首的程度。有句话说得好:雪崩时,没有一片雪花是无辜的。反过来说,雪崩时,因为没有一片雪花是无辜的,所以,也没有一片雪花是要负责的。

这就是大型软件系统的开发人员面对的复杂性。移动开发的级联程度相对较少,这是什么原因呢移动开发基于比较成熟的框架体系,从整体上来说,移动端的大量工作是构建各种UI界面,然后通过与用户的交互,跟服务端交换信息。因此整体业务的相似度、重复度也较高。所以,移动端复杂性爆炸的场景被延迟了,但是随着业务量的持续增加,依然会有很多莫名其妙的问题,会有很多偶发的crash,让人摸不着头脑,甚至没有有用的堆栈信息,很难分析原因。

插句题外话:如今,特斯拉引领的汽车自动驾驶潮流已经到来。然而由于软硬件系统的复杂性,诸如刹车失灵、突然加速这样的事故也是不可避免的。从软件行业从业者的角度来讲,目前的自动驾驶软件还远远达不到足够应对所有情况的水平,比较复杂的驾驶辅助技术也存在一定的安全隐患,奉劝各位车主谨慎使用,把命运掌握在自己手中。

三、How To Resolve

如何解决复杂性灾难呢外人士给出了很多思路,但是核心思想其实大同小异。例如有计算生物学家提出用生物学思维来解决复杂性灾难的方案。
比如通过冗余来应对风险,模仿鱼类大量产卵来规避物种灭绝。通过大量随机事件来刺激和检验,用进化来适应变化等。其实都是我们比较熟悉的概念:数据库灾备、monkey测试、快速迭代等。

回到软件系统本身。贴吧的代码复杂度基本还在可控范围内,但是如何进一步优化代码架构,降低复杂度,防止复杂度灾难化呢r> 让我们回顾一下大型物理系统的设计和制造,一个可靠的大型物理系统的显著特征是:
精准完备的设计,清晰的模块边界,明确的、有限的相互关联,以及可控的变更。

软件系统的发展变化比较快,很难从一开始就有完备的设计,但是我们依然可以参考优秀的物理系统,努力做到:清晰化模块的边界;减少相互关联,降低级联效应;控制变更。

说到模块,大家都知,分层和模块化是解决复杂问题的基本方案,为什么是呢。
我们先通过公式,来看一下问题复杂性与解决问题的工作量的关系:
if c ( k ) > c ( p ) c(k) > c(p) c(k)>c(p) then w ( k ) > w ( p ) w(k) > w(p) w(k)>w(p)
其中c函数表示问题复杂度,包括问题的现状及其可能的发展变化。w函数表示解决问题所需要的工作量。上面的公式就是一个简单的陈述:解决复杂问题所花的工作时间要高于解决简单问题的时间。

当两个不同的问题结合在一起去解决,通常要比分而治之,单独解决两个问题的复杂度高。也就是 c ( k + p ) > c ( k ) + c ( p ) c(k+p) > c(k) + c(p) c(k+p)>c(k)+c(p)
那么根据前面的公式,我们可以得出新的结论:
if c ( k + p ) > c ( k ) + c ( p ) c(k + p) > c(k) + c(p) c(k+p)>c(k)+c(p) so w ( k + p ) > w ( k ) + w ( p ) w(k + p) > w(k) + w(p) w(k+p)>w(k)+w(p)
也就是说,两个不同问题结合在一起去解决所需要的工作量,大于分别解决两个问题的工作量。所以,模块化可以帮助我们把复杂的大的问题,分解为小的简单的问题去分别解决,我们还可以通过多人协同工作使得大规模软件开发成为可能。

但是想通过无限拆分模块,来无限减小工作量也是不现实的。随着模块数量的增多,模块集成的工作量会随之快速上升。所以我们日常开发中不会无限的去细分模块,而是要在模块粒度和集成成本之间找到一个恰当的平衡。

软件系统复杂性灾难及解决方案探究
那么,我们究竟应该如何划分模块,应该遵循什么样的方法和准则呢p>

To be continued……

声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2021年5月18日
下一篇 2021年5月18日

相关推荐