SoftwarecomplexityEricEvans的经典著作《领域驱动设计》的副标题是“CopingwithSoftwareCoreComplexity”,可见Eric对领域驱动设计的定位是处理软件开发的复杂性花费。Eric甚至认为:“领域驱动设计只有应用于大型项目才能产生巨大的收益。”通过SmartUI反模式,他逆向解释说,如果在软件设计和开发过程中出现以下问题,应该考虑领域驱动设计:没有行为的复用,没有业务问题的抽象。每当操作使用它们时,都必须重复业务规则。快速原型制作和迭代很快达到了极限,因为缺乏抽象限制了重构选项。复杂的功能很快就会让你无所适从,所以程序的扩展只能是增加简单的应用模块,没有什么好的方法来实现更丰富的功能。因此,选择领域驱动设计就是与软件系统的复杂性进行殊死搏斗,以降低软件的复杂性为己任。那么,什么是复杂性?什么是复杂性?甚至复杂系统的专家,例如《复杂》的作者梅兰妮米切尔,也认为复杂性没有明确且公认的定义。然而,在接受Ubiquity杂志的专访时,梅兰妮·米切尔“勉强”给出了复杂系统的通俗定义:由大量相互作用的部分组成的系统。与整个系统相比,这些组件相对简单,没有中央控制,组件之间没有全局通信,组件之间的交互导致了复杂的行为。这个定义几乎可以表达软件复杂性的特征。定义中的组件就是我所说的软件系统的“设计单元”,根据粒度的不同,可以是功能、对象、模块、组件和服务。这些设计单元相对简单,但它们之间的相互作用导致了软件系统的复杂行为。JurgenAppelo从理解能力和预测能力两个维度分析了复杂系统理论。这两个维度分为不同的复杂程度。其中,理解力维度分为简单和复杂两个层次,预测能力维度分为两个层次:有序、复杂和混乱三个层次,如下图:参考复杂的含义,复杂和简单(simple)是相反的,意思非常难理解,而复杂是介于有序(ordered)和混沌(chaotic)之间,在某种程度上被认为是可以预测的,但是会有很多意想不到的事情发生.显然,对于大多数软件系统来说,系统的功能是难以理解的;在控制未来的需求变化方面,虽然我们可以遵循一些设计原则来应对可能发生的变化,但未来的不可预测性使得软件系统的演化仍然存在不可预测的风险。因此,一个软件系统所谓的“复杂性”,其实包括复杂和复杂两个方面。了解软件复杂性的原因需要结合理解力和可预测性来帮助我们思考。理解在软件系统中,是什么阻碍了开发者理解它?想象一下,团队新招了一个人,就像一个来到陌生城市的游客,他会不会迷失在纵横交错的城市交通系统中?如果城市其实是郊外的一个村庄,但只有几间房子,一条街道连接着城市的两端,你还会觉得迷茫吗?因此,影响理解的最重要因素是规模。1.规模软件的要求决定了系统的规模。当需求呈线性增长趋势时,为了实现这些功能,软件体积也会以近似的速度增长。由于需求不可能完全独立,存在着相互影响、相互依存的关系,一个部分的修改会影响到整体。就好像城市中的一条道路因施工需要临时封闭,道路无法通行,过往车辆不得不改道绕行,进而导致其他本已饱和的道路因大量涌入而变得更加拥堵。更多的车辆和超过道路的负载。这种拥堵现象会顺势波及到这些道路的其他分叉路,形成辐射效应拥堵现象。软件开发中的拥塞现象可能更为严重:函数有副作用,函数调用时可能会对函数的结果进行隐式假设;类的职责很多,不敢轻易修改,因为不知道这样的修改会影响到哪些模块;hotcode变动频繁,职责层层包裹,没有明确的界限;在系统的某个角落,隐藏着伺机而动的BUG。当满足触发条件时,整个调用链就会瘫痪;不同的业务场景包括不同的异常场景,每种异常场景的处理方式不同;同步处理和异步处理代码纠缠在一起,程序执行的顺序不可预知。当需求增加时,软件系统的规模也会增加,而且这种增长趋势不是线性的,而是会更陡峭。如果需求出现意外变化,我们没有足够的风险应对措施,在时间紧迫的情况下,难免会在设计上妥协,头疼脚痛,在系统的各个地方打补丁,从而欠下了技术债(TechnicalDebt)。当欠下的技术债越来越多,积累到某个临界点时,量变会引起质变,整个软件系统的复杂度会达到极致,进入衰退的晚年,成为“可怕的”遗留系统。就像饲养场里的“奶牛法则”:奶牛逐渐衰老,最终无奶可挤;然而,与此同时,饲养成本也在上升。2.结构你去过迷宫吗?相似而曲折的结构,让原本封闭狭窄的空间神奇地膨胀成一个完美的空间,变得无穷无尽,仿佛空间被放在一个圆圈里,如果找不到正确的出口条件,就会死循环并且永不退出。许多小而极其复杂的软件系统就像这样一个迷宫。这时,结构就成为决定系统复杂度的关键因素。结构之所以变得复杂,很大程度上是由系统的质量属性决定的。例如,如果我们需要满足高性能和高并发的需求,我们需要考虑在系统中引入缓存、并行处理、CDN、异步消息和支持分区的可伸缩结构。如果我们需要支持海量数据的高效分析,就不得不考虑如何对这些海量数据进行分布和存储,如何有效地利用各个节点的内存和CPU资源来进行计算。从系统结构上看,单体架构一定比微服务架构更简单、更容易控制,就像单细胞生物比人体的生理结构简单数百倍一样;那么,为什么这么多的软件组织开始清算自己的组织呢?软件资产,花费大量人力物力重构现有单体架构,走向微服务化?主要原因不是系统的质量属性。纵观软件设计史,不是说长期集成就一定要结合,长期集成就一定要结合。Divide,butcontinuelysplitandcontinuetosplitcontinuoussplit的小型化过程。分散的软件元素是不可能单独作战的。如何协作和沟通成为系统分解后的主要问题。如果控制不好,这些问题固有的复杂性在某些场景下甚至会超过分解带来的好处。无论是优雅的设计还是糟糕的设计,系统的结构都可能因为一些设计权衡而变得复杂。唯一不同的是,前者主动控制结构的复杂性,而后者带来的复杂性是偶然的,错误的滋生,是一种技术债,可能导致一种乱序设计。在PeteGoodliffe的中,详细列出了系统设计混乱的几个警告标志:代码没有明显的进入系统的路径;没有一致性,没有风格,也没有统一的概念来区分不同系统中的控制流让人感觉不舒服和不可预测系统中有太多“难闻的气味”,整个代码库散发出腐烂的味道,是一堆垃圾在炎热的天气里散发出刺激性气体的堆数据很少放在使用它的地方。通常会引入额外的巴洛克式缓存层,以试图将数据保存在更方便的地方。当我们看一个设计杂乱无章的软件系统时,就像隔着一层半透明的玻璃看事物。系统中的软件元素变得模糊,充满了各种技术债务。在细节层面,代码肮脏,违反了“高内聚松耦合”的设计原则,导致很多代码不是错位就是代码块重复;在架构层面,缺乏清晰的界限,各种通信和调用依赖相互交织,同一个问题域的解决方案五花八门,让人眼花缭乱,仿佛进入了一个没有规则的无序社会。预测能力当我们掌握了事物发展的客观规律时,我们就有了一定的预测未来的能力。例如,如果我们洞悉万有引力的本质,就可以对我们能观测到的宇宙天体建立模型,相对准确地预测未来各个天体的运行轨迹。然而,宇宙的空间是变幻莫测的,或许是因为一颗恒星的死亡产生了黑洞的吸力,可能会在那个星域引起剧烈的动荡,而这种动荡又会传递到更远的星空,从而干扰我们的生命。预测。坦率地说,我们连自己生活的地球上的天气都无法准确预报,何来星空预报?之所以会这样,正是因为未知异变的发生。1.变化未来总会有不可预知的变化。这种不可预测性所带来的复杂性让我们心生恐惧,因为我们不知道什么时候会发生变化,变化的方向会走向何方,从而导致头脑中有一种失重感。变化使事情失去控制,而参与其中的我们会感到害怕。在设计软件系统时,变化使我们患得患失,不知如何把握系统设计的度。如果拒绝对变化做出理性的预测,系统的设计就会变得死板。一旦发生变更,修改成本将非常高;如果发生这种情况,我们之前为更改所支付的费用将永远得不到补偿。这被称为“过度工程”。从需求的角度来看,变化可能来自业务需求或质量属性。就对系统架构的影响而言,后者尤其重要,因为它可能涉及到整个基础设施的变更。在《恰如其分的软件架构》一书中,GeorgeFairbanks介绍了电子邮件托管服务公司RackSpace的日志架构的变化。业务功能没有改变,但是因为邮件数量的不断增长,为了满足性能需求,架构经历了三种完全不同的方案。变化:从最初的本地日志文件,到中央数据库,再到基于HDFS的分布式存储,整个系统发生了近乎颠覆性的变化。这并不是因为RackSpace的设计者缺乏设计能力,而是因为他们在公司成立之初就没有预见到客户数量的增加,导致日志数据的增加超出了现有系统所支持的能力。俗话说:“事后诸葛亮”,当我们回顾一个软件系统的架构设计时,总会发现很多设计决策是那么的懵懂。殊不知,这不是无知,而是在设计之初,我们手中的筹码还不足以让我们赢得这场与未来的战争。2.这是变化的死亡!如果我们把软件系统中自己开发的部分归为需求的范畴,那么还有一种变化,就是因为第三方的库、框架或者平台,甚至是我们依赖的语言版本。变化的连锁反应。比如,作为Java开发者,一定更垂涎于Lambda表达式的简洁和抽象,或者Jigsaw提供的模块定义能力。然而现实是,我们看到大多数企业软件系统还停滞在Java6或Java7上。这是一个幸运的例子,因为我们可以满足这种自满情绪,因为情况还没有到必须改变的地步。但是当我们依赖的第三方有理由让我们改变的时候,我们还能拒绝改变吗?很多软件在版本变更过程中都尽量考虑到API变更对调用方的影响,因此尽可能保持版本向后兼容。本人亲自参与了系统从Spring2.0到4.0的升级,Spark从1.3.1到1.5再到1.6的升级。感谢这些框架或平台设计者对兼容性的体贴关怀,我们的升级成本可以降到**;但是升级之后,如果没有对系统进行全面的回归测试,我们总会心存忐忑。依赖第三方看似简单,但我们并不知道,我们所依赖的库、平台或框架可能会为它们依赖多几个属于第三方的库、平台和框架。每次第一次搭建一个软件系统,都会因为漫长的等待下载依赖而烦恼。所谓多版本并存可能带来的依赖地狱,只要亲身体验过,就永远不会感到战栗。如果你运气不好,可能会出现各种奇怪的问题,让你忙得不可开交。如果变化是不可预测的,那么软件系统也将变得不可预测。一方面,我们需要尽可能地控制变化,至少要将变化的影响限制在一个很小的空间内;另一方面,我们必须确保系统不会因为可扩展性而变得更加复杂,***过度工程的坏名声。软件设计师就像走在高空钢索上的技能挑战者,惊险刺激地调整重心以保持动作的平衡。因此,变革的难点在于如何平衡。【本文为专栏作家“张艺”原创稿件,转载请联系原作者】点此阅读更多该作者好文
