接下来,我将阐述复杂性成因的两大要素:理解力和预测力。
在理解力方面,面对新事物时我们可能会遇到障碍,那么,主要是什么因素影响了我们对新事物的理解能力?我认为有两个主要方面,认为影响因素主要有两个:事物的规模和其内在结构。
在软件开发中,随着需求的线性增长,为了满足这些需求,软件的规模也相应地以相似速度扩张。而且,功能之间不可能完全独立,它们相互影响、相互依赖,这可能导致软件系统中的一处更改影响到其他部分,形成了所谓的“牵一发而动全身”的现象。

如果说,影响我们理解新事物的因素中的规模这一因子,是由业务功能本身带来的,那结构因子则是由非功能性需求带来的。例如,当前流行的微服务架构,在与单一结构体相比时,系统的结构复杂性显著增加。在理解整个系统调用链路依赖情况时必须依赖系统拓扑图才能理解。
在预测力方面,软件系统很难实现像建筑行业那样的按图施工,对程序员而言,完全依照蓝图逐步实现项目是不切实际的。没有人能够在项目初期就设计出大型项目的完整蓝图,同时,软件项目始终处于不断的变动之中。
变化使我们犹豫不决,难以把握系统设计的最佳平衡点。然而,如果拒绝对变化进行合理的预测,系统设计可能会变得过于僵化,一旦变化来临,所需的修改成本将极为高昂;如果我们过分关注变化可能带来的影响,试图涵盖所有可能的变化,那么一旦预期的变化并未发生,我们为这些变化所做的投资将无法得到回报。
建模应对软件复杂度通过分析复杂度成因,我相信你大概理解了复杂度的组成部分。现在我再解释一下怎么去应对,如何降低甚至解决这些复杂度。
在图中,尽管每个成因都有对应的解决策略(以橙色方块表示),但复杂性是一个综合性问题,需要一个全面的方法来解决。对于这些解决策略之间的内在联系,建模是关键。因此,我在图中用紫色虚线标出了建模如何应对这些复杂性问题。在深入讨论如何通过构建模型来应对复杂性之前,我们先简要地了解一下模型的概念。
模型如果单独讲模型,可能你会觉得有些突兀,但实际上,我们一直在应用建模技术来解决软件领域的难题。例如,著名的公式“程序 = 数据结构 + 算法”体现了模型的概念,它利用数据结构来界定问题,并通过算法来解决问题。
面对产品管理者提出的文件系统需求,我们会考虑使用树形数据结构来表达文件的目录结构,并通过深度优先搜索或递归算法来实现目录的展示。此外,现代系统普遍采用数据库技术,通过需求分析,我们可以识别系统中的实体及其相互关系,并据此构建实体-关系模型(E-R 模型)。
可这些方法主要针对技术问题,便于开发人员之间的沟通。但鉴于我们当前构建的系统多为业务导向,需求来自不同领域,技术人员不可能掌握所有行业的专业知识,我们应如何应对?
软件建模领域的权威 Eric Evans 提出了一种解决方案:领域驱动设计(Domain Driven Design),这是一种以模型为驱动的设计方法,它通过构建领域模型来捕捉和表达领域知识,从而创建更易于维护的软件。
在应用建模方法来处理复杂性时,我们主要关注两个方面:首先是对模型准确性的理解,其次是模型在建模过程中的具体应用。接下来,我们对这两点进行具体的分析:
正确性模型,是一个软件的骨架,是一个软件之所以是这个软件的核心。一个电商平台,即使它不用关系型数据库,也还可以用 NoSQL,但如果没有产品信息,没有订单,它就不再是电商平台了。
那是不是可以说模型的正确性是整个建模的关键呢?
我们一起看个最常见系统——论坛,论坛里面最主要的业务是发布首贴和回复帖子,网友在某个子论坛发贴,此贴称为首贴或主题贴,别的人在首帖后回帖。
可以观察到,论坛和主题帖之间存在一种一对多的关联,首贴和回帖之间同样如此,那我们把模型构建为:
如果我们换个角度思考,无论是主题帖还是回复帖,它们都是论坛的一部分,区别仅在于帖子的类型,所以我们也可以把模型这样构建成下图:
从业务角度来看,两种模型都能够满足既定目标,因此我们认为模型没有绝对的对错,关键在于综合考量并选择最合适的模型。
模型作用建模法是以模型为驱动的一种设计方法,但从上文得知,对于不同的模型来说,没有正确或错误之分,只有适合与不适合,那么建模方法论的核心点在哪儿?
对此,Eric 指出模型有以下三个用途:
模型与设计核心的相互塑型。正是模型与实现之间密切的联系使得模型与现实相关并且保证对于模型的讨论分析能够应用于最终产品——可运行的程序。模型是所有团队成员所使用语言的核心。在交流过程中,团队成员使用统一的语言,并且由于模型与实现紧密相连,开发人员讨论模型实际上就是在讨论程序。模型用来提炼知识。通过持续优化模型,我们可以深化对领域的理解,发现领域深层次的问题。从这三点,我们也能看出,需要关注模型与代码实现的关联,统一语言与模型关联,实现模型即代码,模型即文档的宏观目标,体现模型在各环节的重要性。
与 OOAD 对比在以往的设计方法中,被软件企业采用较多是 OOAD,我们一起回顾一下整体流程,看看它相较于建模法的弊端。
在原生面向对象设计方法中分为了 OOA 与 OOD 两个阶段:
Object-Oriented Analysis (OOA):面向对象的分析与设计的侧重点是业务领域分析,与软件所要应用的行业领域相关,而与软件技术关系不大,需要由领域专家进行。这一部分的工作被称为“需求分析”。
Object-oriented design (OOD),用面向对象的方法为真实世界建立一个计算机中的虚拟模型,OOD 的主要任务是跨越业务领域模型与可实际运行的软件系统之间的鸿沟,OOD 的难度是非常大的,负责 OOD 工作的人被称为系统架构设计师。
虽然面向对象的分析与设计方法不再像数据结构、算法、E-R 模型那样只适用于技术人员,但在整个项目流程中,业务人员与技术人员还是有明显的边界,一个软件系统是否能交付成功,真实业务需求到软件系统的翻译过程是否完整准确映射,取决于领域专家的输出质量,也包括系统架构设计师对需求分析的加工质量。
从项目整体过程看,虽然使用技术提升了,但流程与以往方法并无二致,依然被分为了分析与设计模型两个步骤,最终软件交付的质量取决于两个步骤之间的匹配程度。
知识消化与面向对象的分析与设计方法相比,通过建模应对复杂度,可以提高软件最终的交付质量,不用再担心分析与设计这两个步骤在匹配程度上的分离,以及由此带来的实际业务问题。
软件开发的核心难度在于处理隐藏在业务知识中的复杂度,建模法通过模型对这种复杂度进行简化与精练,促使业务方与技术方能更紧密合作,减少中间环节,共同打造软件系统。
建模方法改进了以往设计方法的分析与设计割裂的缺陷。通过模型与统一语言拉近了业务方与技术方,使他们融合为一体,减少中间翻译环节。
此外,Eric 还提出了知识消化的概念,业务方与技术方通过统一语言和模型,共同探索业务知识,形成业务知识消化的循环迭代过程。
上图反映的就是业务方与技术方通过统一语言讨论需求;发现模型中的缺失或者不恰当的概念,精炼模型以反映业务的实践情况;对模型的修改引发了统一语言的改变,再以试验和头脑风暴的态度,使用新的语言以验证模型的准确。
技术在重构代码时,修改了代码,同时也就变动了模型,模型修改提取出新的统一语言,双方使用新统一语言进行后续的沟通迭代。
经过正逆双向的循环,让业务与技术深入交流,业务方通过模型大致了解软件实现情况,技术方也能通过模型提炼新的统一语言,从而有了部分业务控制权,这样软件系统就不再只是业务需求的翻译品,而是由双方共同打造,能够保障交付的完成度和准确度的优秀软件,这样就避免了交付返工再交付的恶性循环,也可以增强双方的信任感。
总结首先,我们讲了复杂系统,以及复杂度的两个成因——理解力和预测力。之后,我们讲了用建模的办法来应对复杂度,就模型来说,它是一个软件的骨架,是一个软件之所以是这个软件的核心。
并不存在所谓绝对正确的模型,关键在于全面考量并选择最合适的模型。模型的作用可以概括为三个主要方面:模型与设计核心的相互塑造、作为团队成员共同语言的核心、以及作为知识提炼的工具。最后,我们对比了面向对象的分析与设计方法与建模方法,并阐释了业务团队和技术团队如何通过共同的语言和模型来共同探索业务知识,形成了一个循环迭代的业务知识消化
目前,领域驱动设计(DDD)的流行度持续不减,许多人认为它有助于微服务的拆分,但实际上这只是表面现象。根本上,DDD 之所以受到青睐是因为它是一种建模技术,能够实质性地帮助我们降低软件系统的复杂性。
而使用建模方法前需要正确认识模型,不是追求模型的正确性,而是技术方与业务方在不断地交流与反馈中,逐步完成对模型的淬炼,双方对业务知识达成共识,整合分析模型与设计模型,真正达到代码即模型,模型即文档大融合。