查看原文
其他

《软件方法(下)》第8章2023版连载(01)

潘加宇 UMLChina 2024-03-10
DDD领域驱动设计批评文集
做强化自测题获得“软件方法建模师”称号
《软件方法》各章合集

墙上挂了根长藤,长藤上面挂铜铃

《长藤挂铜铃》;词:元庸,曲:梅翁(姚敏),唱:逸敏,1959

您在阅读《软件方法》时如果发现错误,欢迎通过微信umlchina2告知。如果作者认为有道理,决定在下一次发布时根据您的意见修改,每个错误将付给您5.12元报酬,并在书中说明您的贡献。
(1)任何您认为的错误都可以,包括错别字。

(2)同一错误仅支付最先指正者报酬。

(3)请根据最新版本作指正。

下册内容目前指正人有(按指正时间排序):吴佰钊、王周文、刘学斌、成文华、黄树成、李蜀斌、杨雪鸿、王书伟、高洪江、张志坚、龙燔、陈文飞、郭沼兵、陈自平、张彬、李宏伟、赵志军、孙赛刚、孙军。

从分析工作流开始,我们每个内容都分为两章。一章讲述建模知识,一章讲述建模知识如何应用在本书案例中。这样的分割主要考虑到更符合实际的工作。

例如,在讲解分析类图时,我们讲解知识的顺序是这样的:

(1)识别类和属性

(2)审查类和属性

(3)识别类之间的泛化

(4)识别类之间的关联

如果把案例剖析分解到每个知识点,为了让案例的剖析符合内容的顺序,可能就会出现这样的情况:

讲解完识别类和属性后,案例剖析时,先列出很多类和属性,但没有泛化和关联关系,因为类的关系还没有讲到,所以即使观察到,也故意不画上去;接下来,讲解完类之间的关系后,案例剖析时,再把关系加上。

这不符合实际工作中的情况。实际工作中,以上列出的几项工作看起来是交叉进行的。

我们在识别类和属性的过程中,可能会发现一些类有相同的属性,于是泛化出超类,可能会发现有的属性还可以在同一领域内分解,于是识别出另一个类以及两个类之间的关联……

注意我的用词:【看起来】交叉进行,因为更微小的过程依然遵守上述的推导过程(1)→(2)→(3)(4)。

为了避免造成误解,我们先完整地讲解知识部分,本书案例如果有和所讲解知识相关的内容,会随时引用。然后在案例部分,按照实际工作中的思考方式灵活应用前面所讲解的知识点。

8.1 分析工作流概述

8.1.1 知识的表达和组织

在业务建模和需求工作流,我们一直把目标系统看作是一个整体,想办法推导出涉众在意的整体表现,即系统的需求。

系统为了满足需求,必须封装一定的知识。这些知识不会乖乖地自己从知识的海洋中走出来,有组织、有次序地进入系统中,需要软件开发人员一点点识别出来并放入系统。

因此,我们要思考:

(1)如何准确表达系统需要封装的知识,让系统满足需求;

以及进一步

(2)如何合理组织系统需要封装的知识,低成本地让系统满足需求。

如果不能合理组织知识,当新需求到来时,准确表达也会越来越难。如果考虑到利润,很难停留在(1)而不追求(2)。

不管是纯粹在大脑里面打转转,还是借助了纸笔或建模工具来协助,以上的思考是逃不掉的。

如果需要封装的逻辑很简单,人脑的容量和运算速度能够胜任,在大脑里打转转还可以勉强应付,但是,能带来利润的系统都是复杂的(参见《软件方法(上)》1.8.1市场没有小系统),借助纸笔或建模工具来显式表达思考的过程很有必要,毕竟大脑容量和运算速度比一般人高出一个数量级的天才是很稀罕的。

注意区分“在大脑里完成”和“拍脑袋”的区别。

有的人故意不显式表达,声称“大脑思考就够了”,背后的真相可能不是天才而是遮羞——你让他显式表达,他也表达不出来,因为没有掌握思考的方法。

就像考试一样,面对一道有难度的填空题,考生可能有以下三种表现:

学霸:在大脑中迅速完成推导,直接在答卷填写结果。

普生:拿出草稿纸,规规矩矩推导,然后在答卷填写结果。

学渣:拍脑袋蒙一个,直接在答卷填写结果“敏捷试错”。

因为学霸的“在大脑中推导”和学渣的“拍脑袋”有一个容易观察到的共同点:没有草稿纸,所以学渣有时候会用这个共同点来假装自己是学霸,这一点要警惕。

8.1.2 核心域和非核心域

一个信息系统封装了若干领域的知识。其中,有一个领域的知识是该系统不能抛弃或替换的,这个领域称为"核心域",其他领域称为"非核心域"。

图8-1展示了不同系统类型的核心域和非核心域概念。

系统类型

核心域概念

非核心域概念(选择之一)

文档处理器

文档、页、行、字……

CStringArray、CFileDialog、MSXML……

电子商务网站  

商品、订单、会员……

</div>、ActionForm、SessionFactory……

操作系统

处理器、内存、页面……

struct、char、int

图8-1 不同系统类型的核心域、非核心域概念

以文档处理器为例,开发Microsoft Word和LibreOffice Writer所使用的编程语言和组件不一样,但文档、页、行、字等核心域概念是一样的。即使回到计算机诞生之前或者去到未来,这些概念也依然存在。

还可以注意到,图8-1中的操作系统,其核心域概念有“处理器”、“内存”等,说明核心域既可以是非计算机领域,也可以是计算机领域。

关于“核心域”和“非核心域”,一种常用的通俗说法是"业务"和"技术",但"业务"和"技术"的说法不严谨。

有的开发人员在潜意识里用“懂”、“感兴趣”来划分"业务"和"技术":

*我懂且我感兴趣的知识→技术;(我懂Java编码,我对Java编码感兴趣,Java编码是技术)

*我懂但不感兴趣的知识→业务;(下单、收银、配送我懂一些,但不感兴趣,这些是业务)

*我不懂但感兴趣的知识→高科技;(我不懂深度学习,但很感兴趣,哇塞,高科技)

*我不懂且不感兴趣的东东→忽悠。(我不懂UML建模,也不感兴趣,妈的,忽悠)

有的开发人员在潜意识里则用“是否和计算机有关”来划分"业务"和"技术":

*和计算机无关→业务;

*和计算机有关→技术;

【说明1】

本书中的“核心域”和Eric Evans以及DDD(领域驱动设计)话语体系中的“核心域”(Core Domain)意思不同。

本书中的“核心域”指信息系统中不可替换的那部分内容——这个以软件开发人员的知识是可以判断的。

DDD话语体系中,把“领域”(相当于本书中的“核心域”)划分为"核心域"、“通用子域”、“支撑子域”等,例如“Delivery”是核心,“Customer”是通用,“Billing”是支撑——这个划分已经超出了软件开发人员的知识。

一家商场之所以能击败其他对手,原因未必是下单环节有什么不同,倒有可能是在配送环节下了大力气,或者支持的支付渠道多,或者客户服务环节抓得好。没有经过商业竞争的思考,武断地认为某个子领域是系统的“核心”是不合适的。

软件开发人员没有能力和责任做出商业竞争方面的判断。他要做的是,把涉众关于商业竞争方面的思考如实表达,并且用信息系统来封装其中适合封装的部分。

如果发现存在这样的“软件开发人员”,他有责任做出商业竞争方面的判断,那么说明这个人扮演了多个角色,既扮演涉众,也扮演软件开发人员。

【说明2】

另一个可以选择的用词是“问题域(Problem Domain)”,这也是之前大多数面向对象方法学的用语。但近年来,“问题域”和“解决方案域”等用语被领域驱动设计伪创新赶时髦胡乱使用,已经被严重污染。经过考虑,本书使用用语“核心域”和“非核心域”。

8.1.3 域之间映射和协作的套路

我们看一个人员管理系统的核心域类图,如图8-2所示。

图8-2 人员管理系统的核心域类图

如果将图8-2中的Person类映射为C#实现,可能会得到图8-3的C#代码:

图8-3 类的C#实现(用Enterprise Architect映射)

如果将图8-2中的类映射到关系数据库,会得到图8-4所示的数据库结构:

图8-4 将类图映射到数据库模型(用Enterprise Architect映射)

如果采用某种对象-关系映射器框架(例如微软的Entity Framework),Person对象和数据库中的Person表里的一行可能会这样联系起来:

person1=context.Persons.Find(ID)

注意,域和域之间的映射以及协作的套路,与域中的个体并不直接相关。

如果将以上内容中的Person改成Dog,City改成Cat,映射的套路没有变化。即使我们调整了域之间的映射和协作的套路,得到的结果也会按照我们的调整有规律地变化,与域中的个体依然无关。

平时我们看到的一些“架构”,就是域之间映射和协作的一些套路。图8-5列出了现在常被提起的一些“架构”,可能在很多系统中都会观察到,即使这些系统的核心域及非核心域都有不同。

图8-5 一些常见的“架构”

既然域之间的映射和协作有“套路”,过早地混合不同域的知识是不划算的。

如图8-6所示,假设三个域要考虑的因素分别是a、b、c个,如果分开考虑,然后选择好域和域之间映射的套路,负担最小可以变成a+b+c;如果混在一起考虑,大脑的负担最大会达到a×b×c。a、b、c都大于√3时,相乘的结果肯定大于相加的结果。

图8-6 过早混合不同域的知识会增加大脑负担

过早地混合不同域的知识,会加重开发人员大脑的负担,导致开发人员腾不出脑力来思考核心域中更深刻的问题,只好稍微折腾一下如图8-5的“域之间的架构”,心里安慰自己,我有“架构”了!却忘了,其实还没有触碰到最需要大脑去思考的核心域概念和逻辑。而这又很可能会被巧妙地当成遮羞布——不是我不思考,而是要想的事情太多了顾不过来啊!

而这种微妙心态的进一步发展,会导致开发人员有意无意地混合不同域的知识,把复杂度弄成a×b×c,以此达成废话刷工作量——以最少的思考得到最多的“成果”。

近年最时髦的就是借DDD话语体系刷工作量了。例如,刚找出一个类Order,然后周围就围上一圈OrderFactory、OrderRepository、OrderService……,洋洋得意地把工作量刷了好几倍。

我经常听软件组织的架构师向我介绍他们所开发系统的“架构”,口沫横飞,说的基本上都是图8-5的“域之间的架构”。好啊,真棒,我知道了。还有呢?没了?

构思那些“域之间的架构”是某些基础设施厂商或者方法学家的工作,我们挑一个适合自己项目的套路用上就行了。有什么问题,可以去请教用这个套路用得好的先行者。

“域内部的架构”,那些核心域概念和复杂逻辑,这是系统最值钱的地方。要是我们没有办法理清楚,别人是帮不到我们的。这才是大脑最该用的地方!

8.1.4 应对变化,不要吃错药

平时开发人员常说要“应对变化”,甚至有的人还喊口号“拥抱变化”,但是你真的清楚要应对的是什么样的变化吗?

我梳理了“不好了,需求变了!”的各种情况,如图8-7。

我们听到的(患者诉说)

实际情况(病情)

最需改进的技能(处方)

备注

不好了,需求变了!

①需求其实没变,只是需求人员的“认识”变了

(病情没进展,只是之前的诊断错了)

业务建模、需求

最多

需求确实有变化

(病情进展了)

②功能需求 变了

分析

第二多

③质量需求 变了

设计


④设计约束 变了

设计


图8-7 各种“需求变化”

情况①:需求其实没变,只是需求人员的“认识”变了。这种由于业务建模和需求技能不足导致的“伪需求变化”是最多的。

要应对这样的“伪需求变化”,光是有分析和设计技能是没用的,需要提升业务建模和需求技能,识别出真正的系统需求。这些技能已经在本书上册(2到7章)讲解。

用看病类比:

张三出现恶心、乏力、食欲减退等症状,村里的道士九叔给他诊断,认为他被鬼上身了,需要搞一个驱魔仪式。九叔已经精研驱魔理论体系和实践多年,一手辟邪剑法已经练到星耀一级。

那也没用!因为张三得的是乙型肝炎。

情况②:功能需求变化,包括增加了一个用例、增加了一个步骤、输入输出的字段多了一项、某个步骤的业务规则做了调整……。这种情况是第二多的,或者说,排除了“伪需求变化”,真正的需求变化中,功能需求的变化是最多的。

应对情况②,需要提升分析技能。如果能恰当地建模系统要封装的核心域逻辑,使得核心域模型能精确体现核心域的内涵,会大大有助于应对这样的变化。

同样用看病类比:

如果充分了解肝脏的工作机制,当张三被诊断乙肝时,又观察到张三酗酒、熬夜,就可以预测,如果张三再不采取措施,病情可能很快会进展到肝硬化甚至肝癌,这对应变是有帮助的。

情况③和④发生得就没那么频繁:质量需求和设计约束发生变化,例如响应速度、并发容量、运行平台的更换等。应对这样的情况,是设计工作流的工作。

很多人并不了解这里面的区分,常为了“应变”吃错药。明明是情况①和②,却用情况③和④的药,原因是情况①和②的药方他不会开。

例如,一些以“领域驱动设计”为名的资料。既然挂了个名叫“领域驱动设计”,背后暗含的意思应该是目前软件开发缺少“领域驱动”,对吧?结果一看,所举的例子就1-2个领域类,然后就开始讨论Entity、Service、Repository、DTO、六边形架构……这分明说的是“企业应用架构模式”、“互联网系统架构模式”。

不是说这个知识没用,问题是软件组织目前缺的是这个嘛?

*调查:您看过的以“领域驱动设计”为名的文章,里面有几个领域类?*

5个以下

6-10个

大于10个

在一些软件开发技术大会常可以看到这样的场景:某电子商务网站的架构师上台讲了一通,接着某视频网站的架构师上台也讲了一通,咦,两个演讲内容如此相似?原来,他们讲的都是自己系统中“域之间的架构”,而不是核心域内部的机制。究其原因也许并非不为,而是不能——架构师对自己所开发系统的核心域研究太浅。

许多“网红程序员 ”在网上谈论的内容大多是某种语言或框架的新特性,少有探讨他当前所开发系统的复杂领域逻辑,也是同样的原因:并非不为,而是不能。

说了那么多,归纳起来就是一句话:重视分析工作流


[架构师强化]9月25-27晚8点使用状态机整理领域逻辑和生成代码-网络公开课
[架构师强化]10月9-11晚8点企业应用架构模式新解-网络公开课
[EA-029/石油钻井管理平台]35套UML/SysML+EA/StarUML的建模示范视频-全程字幕
如何选择UMLChina服务
作者微信:umlchina2
继续滑动看下一个

《软件方法(下)》第8章2023版连载(01)

潘加宇 UMLChina
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存