本文书接上回《懂了这个道理,人月神话不再是神话!》,关注公众号(老肖想当外语大佬)获取信息:
-
最新文章更新;
-
DDD框架源码(.NET、Java双平台);
-
加群畅聊,建模分析、技术交流;
-
视频和直播在B站。
关注公众号一定要星标,以及时获得最新推送。
背景
关于“复杂度”我在系列开篇《关于领域驱动设计,大家都理解错了》中就有所剖析,然而在与大家交流的过程中发现,很多对于软件设计决策的分析,最底层的观点冲突来自与对“复杂度”理解的冲突,大家的结论都是构建在它上面的,因此,在这里全面地展示我对于“复杂度”的理解,以期与大家能够在更深的层次上识别共识,明晰分歧。
软件成本与复杂度
我们在设计软件系统的过程中,有一个贯穿始终的约束条件,就是“软件成本”,时间成本、人力成本等等资源,都是有限的,衡量一支团队的效率,同样也可以用“软件成本”来作为计算依据,而软件成本,我认为最核心的因素就是软件的“复杂度”,如果“复杂度”是一个有刻度的尺子,那么我认为,复杂度越高,软件成本越高。而我的软件设计决策的所有依据,都构建在这个起点。如果我们期望降低软件成本,那么就意味着,我们需要降低软件的复杂度,那么理解复杂度就是我们的必经之路。
理解复杂度
要理解复杂度,我们不得不先回答一个问题:如何判断“复杂”和“简单”?我们先来看几个例子,下面两个系统,你觉得哪个更复杂?我想大部分人一定会选“系统2”,因为显然它的元素数量更多。
如果我们的系统元素之间存在一些关系,那么下面两个系统呢?是不是感觉“系统3”更复杂?
基于上面的例子,我们更极端地设想一下,假如你有一个系统A,有100个独立的模块,这些模块之间没有相互联系,另外一个系统B,有10个模块,这些模块之间相互会有影响。我们假设这里的一个个“模块”本身复杂度相同,那么你认为系统A和系统B哪个更复杂?对我而言,我会坚定地认为系统B更复杂。
基于上面的例子,我得出了两个判断:
-
系统复杂度与元素的数量和元素的关系有关;
-
元素的关系对系统复杂度的影响远远大于元素的数量所产生的影响;
基于上述的观点,我们就可以根据元素数量、元素关系这两个要素来衡量“复杂度”,也就是说我们可以有一个判断复杂度的标尺,这个标尺有两个重要参数:元素数量、元素关系。
我们知道,不可衡量则不可改进,而有了这个标尺,也意味着我们可以做衡量,可以对决策方案的优劣取舍做判断,这就是我们能够持续改进的起点。
尊重复杂度
经典的复杂度守恒定律表示,“复杂度不可被消除,只可被转移”,我深以为然,回到软件设计领域,我们的复杂度具体在哪里呢?我认为我们的软件系统复杂度的根源,来自于需求本身的复杂度,它决定了最终解决方案复杂度。
既然复杂度无法被消除,那我们是不是可以推导得出,解决方案的复杂度,最小等于需求的复杂度?那么我们在设计方案的过程中,要做的,就是尽可能避免引入额外的复杂度。这个过程,系统元素数量和元素关系成为我们关注的要点,而且元素关系是其中的重中之重。
在我们分析需求,给出解决方案的过程中,复杂度由两方面组成,一个是业务逻辑本身的复杂度,一个是引入的技术复杂度,其中业务逻辑是来自需求,不能打折扣,因此这部分的复杂度是无法被消除的,这也就是为什么我所说的“尊重复杂度”,尊重业务固有复杂度,不要尝试消除它,我们能做的,仅仅是将它的复杂度尽可能封闭在一个个“收纳箱”里,这些收纳箱,就是我们常说的“领域”。
而对于“技术复杂度”,我们要做的就是避免过度设计,避免引入额外的复杂技术,避免牛刀杀鸡,尽可能采取与需求匹配的技术解决方案。
如果你了解“熵”的概念,也许会发现,我们在讨论复杂度逻辑,与“熵增定律”不谋而合,而软件系统的复杂度也是随着我们一步步迭代走向更加复杂和混乱的,我们能做的就是尽可能维持复杂度的增速尽可能地低,使得系统复杂度尽可能在我们所能掌控的范围内。
因此,尊重复杂度,也是对自然规律的尊重。
掌控复杂度
那么,在我们设计和实现软件系统的过程中,该怎么做呢?既然我们知道复杂度与元素数量和元素关系有关,而元素关系又是复杂度的核心来源,那么意味着我们设计的策略可以是:
-
尽可能避免引入元素关系;
-
实在不行,用元素数量来置换元素关系,也是划算的;
这些策略,体现在具体的软件系统建模中,具像化出来就是:
-
保持模型(聚合)之间的边界不被打破;
-
如果一个需求现有模型无法满足,就考虑创建一个新的模型,而不是把原有的多个模型耦合在一起;
要掌控关系,我们需要具体到代码层面,识别什么是关系,我认为有下面特征的代码都属于关系:
-
聚合类型之间的相互引用;
-
一个类型,同时对多个聚合类型产生操作或影响;
基于上述认知,我们就得出一些代码原则:
-
模型(聚合)之间不相互依赖;
-
如果现有模型(聚合)无法满足需求,就创建一个新的模型(聚合);
-
模型之间的相互影响,通过“事件”传递信息,从而实现数据最终一致;
关于具体的代码怎么写,我在《DDD建模后写代码的正确姿势(Java、dotnet双平台)》一文中有一些示例展示,也欢迎大家参与我们DDD实战项目d3shop,一起体验掌控复杂度的实践,具体参见《欢迎加入d3shop,一个DDD实战项目》。
后续
本篇是从一个平面的视角来解析复杂度,探讨的是元素之间的关系对复杂度的影响,而一个复杂的系统,并不是简单的一层,在不同的层次,进行展开,又可以看作是一个子系统,下篇我们将从纵深的视角来剖析复杂度与领域驱动设计思想的相互关联。