(1)任何您认为的错误都可以,包括错别字。
(2)同一错误仅支付最先指正者报酬。
(3)请根据最新版本作指正。
下册内容目前指正人有:吴佰钊、王周文、刘学斌、成文华、黄树成、李蜀斌、杨雪鸿、王书伟、高洪江、张志坚。
墙上挂了根长藤,长藤上面挂铜铃
《长藤挂铜铃》;词:元庸,曲:梅翁(姚敏),唱:逸敏,1959
8.1 基于核心域的复用
8.1.1 知识的分离
在业务建模和需求工作流,我们一直把目标系统看作是一个整体,想办法推导出涉众在意的整体表现——需求。
系统为了满足需求,必须封装一定的知识。这些知识,没法从天上掉下来,需要软件开发人员一点一点放进去。接下来,我们将思考:
(1)如何准确表达系统需要封装的知识,才能让系统满足需求;
以及进一步
(2)如何合理组织系统需要封装的知识,才能低成本地让系统满足需求。
如果不能合理组织知识,当新需求到来时,准确表达也会越来越难。如果考虑到利润,很难停留在(1)而不追求(2)。
不管是纯粹在大脑里面打转转,还是借助了纸笔或建模工具来协助,以上的思考是逃不掉的。如果需要封装的逻辑很简单,人脑的容量和运算速度能够胜任,在大脑里打转转就算了。不过,能带来利润的系统都是复杂的(参见《软件方法(上)》1.8.1市场没有小系统),借助纸笔或建模工具来显式表达思考的过程很有必要,毕竟大脑容量和运算速度比一般人高出一个数量级的天才是很稀罕的。
有的人故意不显式表达,声称“大脑思考就够了”,背后的真相可能不是天才而是遮羞——你让他显式表达,他也表达不出来,因为没有掌握思考的方法。
思考的方法,也就是知识分离的方法,包括域和域之间的知识分离,以及域内部的知识分离。
8.1.2 核心域和非核心域
一个软件系统封装了若干领域的知识,其中一个领域的知识是系统不能抛弃或替换的,这个领域称为"核心域",其他领域称为"非核心域"。
图8-1展示了不同系统类型的核心域和非核心域概念:
图8-1 不同系统类型的核心域、非核心域概念
以文档处理器为例,开发Microsoft Word和LibreOffice Writer所使用的编程语言和组件不一样,但文档、页、行、字等核心域概念是一样的。即使回到计算机诞生之前或者去到未来,这些概念也依然存在。
关于“核心域”和“非核心域”,一种常用的通俗说法是"业务"和"技术",但"业务"和"技术"的说法是不严谨的。
有的开发人员在潜意识里是这样划分的:
*我懂且我感兴趣的知识→技术;(我懂Java编码,我对Java编码感兴趣,Java编码是技术)
*我懂但不感兴趣的知识→业务;(下单、收银、配送我懂一些,但不感兴趣,这些是业务)
*我不懂但感兴趣的知识→高科技;(我不懂深度学习,但很感兴趣,哇塞,高科技)
*我不懂且不感兴趣的东东→忽悠。(我不懂UML建模,也不感兴趣,妈的,忽悠)
有的开发人员则是这样划分的:
*和计算机无关→业务;
*和计算机有关→技术;
核心域不能以“懂”、“感兴趣”来判断。核心域不一定是非计算机领域,也可以是计算机领域,如图8-1中的操作系统。
本书中的“核心域”指软件系统中不可替换的那部分内容——这个以软件开发人员的知识是可以判断的。
Eric Evans《领域驱动设计》知识体系中,又把本书中的“核心域”中再划分,划分为"核心域"、“通用子域”、“支撑子域”等,例如“Delivery”是核心,“Customer”是通用,“Billing”是支撑——这个划分已经超出了软件开发人员的知识,我不认为软件开发人员有能力以及有必要做这样的判断。
商业竞争非常复杂。一家商场之所以能击败其他对手,原因未必是下单部分有什么不同,倒有可能是在配送环节下了大力气,或者客户服务抓得好。武断地认为某个子领域是“核心”是不合适的。
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的“域之间的架构”,心里安慰自己,我有“架构”了!却忘了,其实还没有触碰到最需要大脑去思考的核心域概念和逻辑。而这又很可能会被巧妙地当成遮羞布——不是我不思考,而是要想的事情太多了顾不过来啊!
我经常听软件组织的架构师向我介绍他们所开发系统的“架构”,口沫横飞,说的基本上都是图8-5的“域之间的架构”。好啊,真棒,我知道了。还有呢?没了?
构思那些“域之间的架构”是某些厂商或者方法学家的工作,我们挑一个适合自己项目的套路用上就行了。有什么问题,可以去请教用这个套路用得好的先行者。
“域内部的架构”,那些核心域概念和复杂逻辑,这是系统最值钱的地方。要是我们没有办法理清楚,别人是帮不到我们的。这才是大脑最该用的地方!
平时我们说“需求变了”,大多数情况是功能需求变化,导致系统所封装的核心域逻辑需要调整,而不是“域之间的架构”需要调整,所以没有必要整天津津乐道“域之间的架构”。
一些以“领域驱动设计”为名的文章,所举例子就1-2个领域类,然后就开始讨论Entity、Service、Repository、DTO、六边形架构……不是说这个知识没用,问题是软件组织缺的是这个嘛?
在一些软件开发技术大会常可以看到这样的场景:某电子商务网站的架构师上台讲了一通,接着某视频网站的架构师上台也讲了一通,咦,两个演讲内容如此相似?原来,他们讲的都是自己系统中“域之间的架构”,而不是核心域内部的机制。究其原因也许并非不为,而是不能——架构师对自己所开发系统的核心域研究太浅。
许多“网红程序员 ”在网上谈论的内容大多是某种语言或框架的新特性,少有探讨他当前所开发系统的复杂领域逻辑,也是同样的原因:并非不为,而是不能。
说了那么多,归纳起来就是一句话:
8.1.4 重视分析工作流
分析,就是从核心域的视角构思系统的内部机理。
在现在的很多软件组织中,分析工作流的技能是非常被忽视的。很多开发人员上手就直接编码,原因并不是软件开发项目的领域逻辑简单到了不需要分析的地步,或者他在大脑里就完成了分析的工作,而是开发人员缺乏分析的技能,只好瞎碰了事,而且为了遮掩自己的无能,还会想各种办法来遮羞。
就像高考的时候,前面几道题比较容易,可以扫一眼就写答案。越往后题目越来越难,学霸会拿出草稿纸,列出已知条件,正推、逆推……理出解题思路,然后再答。
学渣就麻烦了,根本没有学习相关的知识和解题方法,怎么办?
遮羞方法(1):时间。
例如,抱怨考试时间太紧张,来不及思考,只好胡乱写个答案,甚至故意提前交卷,力图给人造成一种“如果时间允许,我是能做对的”的印象——真相是,给再多的时间也不会。
对应到软件开发,就是以“时间紧”、“敏捷”为借口掩盖自己没有能力剖析复杂逻辑的事实。
遮羞方法(2):空间。
例如,考试时故意选择不好写的笔和劣质的草稿纸,力图给人造成一种“如果纸和笔再好一点,我是能做对的”的印象——真相是,不会就是不会,给他再好的纸笔也不会。
对应到软件开发,就是借助“口头交流”、“白板”等容量小的介质,掩盖内容的苍白。白板就这么大,所以客观上你总不好意思让我用白板剖析复杂的逻辑吧?
伽罗瓦在决斗前一天晚上仓促写下自己的数学思想,不停哀叹“我没有时间了”。唉,早干嘛去了,不过伽罗瓦是真懂。
图8-7 伽罗瓦决斗前一天的手书
费马在书的空白处写道,“我确信我发现一种美妙的证法,可惜这里的空白处太小,写不下”,估计费马是忽悠。
此处提到此二人纯属作者关于“时间”、“空间”不够的随意联想,无其他含义。
遮羞方法(3)听起来就比较高大上了:重构。
上世纪80年代末,Bill Opdyke(http://laputan.org/pub/papers/opdyke-thesis.pdf)和Bill Griswold(https://cseweb.ucsd.edu/~wgg/Abstracts/gristhesis.pdf)等人归纳了一些调整软件结构的手法,称为“重构”,后经Martin Fowler等人推广而广为流传。有的开发人员以此为借口逃避思考——“我先写快而脏的代码,然后再重构”。
其实,如果开发人员真的熟练掌握重构的手法,很多时候他完全可以建模领域逻辑,直接得到更合理的结构,根本不需要先走很多弯路再回正路,因为很多知识是共通的。要是开发人员以“重构”为理由拒绝思考,很可能他的“重构”也是空话。
摸着石头过河是难免的,但应该在不得不摸的时候才摸,不应该假装看不见已有的路和桥,无论大小事都主动追求摸着石头过河,而且,很多人不是假装看不见路,而是真的看不见路——就是个睁眼瞎。
待续……