我们软件工程师实际上只在做一件事情,即把现实中的问题搬到计算机上,通过信息化提升生产力。

445607a39c5bcb75b262357cae65fa05.png

信息化的过程就是从“问题空间”到“解决方案空间”的过程。然而,“问题空间”和“解决方案空间”之间有一道鸿沟,程序员的任务就是跨过这条鸿沟。说实话,跨过去并不是很难,理论上,再丑陋的代码也能实现业务功能。

关键是跨过去之后,系统的内建质量(built-in quality)会怎样:一个设计良好的系统,概念清晰,结构合理,即使代码库庞大,依然可理解、可维护。而一个糟糕的系统,只能是屎上雕花。其中,领域概念和领域模型的缺失,是造成这种差异性的罪魁。

1. 为什么需要建模

为什么说领域概念是否明晰、领域模型是否正确是区分好坏系统的关键所在呢?这和我们的大脑结构有关系。

我们人类的大脑天生喜欢“偷懒”,能动用系统1的,我们绝不想启用系统2。(系统1代表直觉,系统2代表理性,出自《快思慢想》)。所以在面对复杂情况的时候,诸如拥有几十万行代码的应用系统,没有人的大脑能装下这么多的信息。唯有将其分而治之,将大问题分解成多个小问题,我们才有可能理解和实现。

这个分解的过程在我们软件领域,主要有两种范式:一个是过程式分解(过程范式),另一个是面向对象分解(对象范式)。

自上而下的过程式分解符合我们的思维习惯,是我们常用的分解形式,非常有用。只是,在问题变得更复杂时,它有一定的局限性。想象一个应用程序,其代码有几十万行。如果使用C语言平均每个子程序编写50到100行代码,那么子程序就有几千到几万个。如果使用面向对象,平均在每个类中汇总10个子程序,那么类的总数就比子程序的总数减少1个数量级,为几百到几千个。当然,即便是1000个类,数量依然庞大。为了可理解性,我们可以按照内聚性,进一步使用“包”、“组件”、“模块”、“服务”做归类分组,以减少我们大脑的思维负担。

1ae3297ea9b210a8b6dae439f781a0bc.png

这就是我们为什么需要面向对象的原因,其本质是因为面向对象技术,相比较过程分解,可以减少构建数量,通过领域封装(对象封装、包封装、组件封装、服务封装......)的层级结构,把问题控制在可以理解的范围,而不至于失控。

可是,我们很多系统还是失控了。因为他们虽然使用了面向对象语言,但干的还是面向过程的勾当。体现在代码上,就是缺少对领域概念的清晰表达、缺乏面向对象的领域封装、领域边界模糊不清、以及领域模型抽象不正确。

因此,领域建模是应用程序设计中核心的核心。因为不管是战略上的基于微服务的领域划分,战术上的OOD,代码的可读性,以及关系数据库的建模等等,都离不开领域概念和模型。

2. 理解业务需求

业务需求是我们工作的起点,只有深入的理解业务需求,理解问题空间。我们才有可能抽象出相对合理的领域模型,才能构建出相对合理的系统。抛开教科书上那些“正规”的需求文档规格说明书,这里,我只介绍我认为有效的需求捕获方法。

2.1 用户故事

User story在维基百科上是这样定义的。

In software development and product management, a user story is an informal, natural language description of one or more features of a software system

https://en.wikipedia.org/wiki/User_story

跟厚重的需求文档比起来,User Story会更敏捷。根据我的经验,大部分的系统,都可以通过这种方式来表达,虽然会遗漏比较多的细节,但是作为理解问题的开始,至少不会那么令人讨厌。

User Story一般的写法是——As a <who>, I want <what> so that <why>。比如我要实现一个博客系统,我就可以这样写我的User Story:

  1. 作者<who>登录博客<what>,so that可以写文章。<why>

  2. 游客<who>可以浏览文章<what>,so that学习文章的内容。<why>

  1. 游客<who>可以写评论<what>,so that发表对文章的看法。<why>

这样写的好处是,我们可以用简短的话语,把一个事件里面的主要要素<who><what><why>都表述出来,即便如此,我们还可以更简便一些,有时候<why>如果是显而易见,或者没那么重要的话,也可以不写。Mike Cohn 也说过类似的话,他说:“‘so that’ clause is optional although still often helpful.” 还是以blog系统为例,剩下的User story我们可以这样写:

  1. 作者<who>打开web编辑器,写文章,发表博客<what>

  1. 作者<who>可以给自己的文章打上1到多个标签<what>

除了缩减,我们也可以添加更多的要素,比如根据5W2H,我们可以用As <who> <when> <where>, I want <what> because <why> 。只是<when>和<where>通常是作为实体的属性存在的,重要性没有那么大,所以常常会被省略,比如同样的关于上面写blog的user story,我们也可以这样表述:

  1. 作者<who>晚上7点<when>在家用手机<where>登录blog<what>,so that可以写博客。<why>

这样写当然也没有问题,只不过“晚上7点”和“手机端”只是作为blog这个实体的属性而存在,不写关系也不大。

2.2 业务活动调研

上文中blog应用相对比较简单,我们大部分的业务系统都要比这个复杂。比如供应链系统,里面涉及很多用户角色和业务流程。此时我们可以通过用户调研的方式来理解业务。调研的重点一个是用户角色,也就是<who>,另外一个是由这个角色发起的业务活动,也就是<what>。实际上,跟User story关注的重点也是一样的,只是形式有所不同。

cc417244b16a7162c9a2bb9f106237e6.png

比如,我之前进入到一个零售业务的时候,就会带上一个小本本,每天请不同角色的人喝咖啡,然后记录下来他们的业务活动。这对我快速理解这个零售业务起到了很大帮助。

调研回来之后,我将这些活动节点串联起来,就能得到比较完整的业务流程。我们可以借助泳道活动图,理清系统的主要角色,以及他们之间的交互关系。如下图所示,将行业控商小二和区域控商小二的主要业务活动串联起来,就能得到日常工作的业务流。

d6381e5a3a91e16fe4d39f9efd53f05c.png

2.3 其它方式

上面介绍的两种方式都不是需求分析的“正规军”,只是我个人实践中,觉得简洁又非常实用的方法。当然正规军的方法也很有用,比如正规的Use case(有前置条件,异常分支)、或者UML的用例图等手段也可以辅助帮助我们分析业务,理解业务。形式不是重点,能抓到老鼠就是好猫。

有同学可能会有疑问,活动图、流程图、时序图呢?你当然也可以用,只要有助于你理解业务,挖掘重要的领域概念都可以用,只是偏流程和交互相关的,更多的还是在实现阶段用,对于领域建模来说,最重要的还是挖掘概念和抽象模型。

3. 开始领域建模

模型是对现实世界的映射。现实世界中的事物在软件世界中会被映射成实体对象:该事物在现实世界中被赋予什么职责,在软件世界中就被赋予什么职责;在现实世界中拥有什么特性,在软件世界中就拥有什么属性;在现实世界中拥有什么行为,在软件世界中就拥有什么函数;在现实世界中与哪些事物存在怎样的关系,在软件世界中就应当与它们发生怎样的关联。这正是面向对象编程的核心思想,也是领域建模的核心思想

建模的过程就是实现这个映射,跨越鸿沟的过程。其本质是一个整理信息、挖掘概念、发现事物内在联系的过程。

3.1 用例分析法

该方法简单来说就是找名词(nouns)和找动词(verbs),复杂点解释就是通过分析语句,找到里面的重要领域概念,建立概念之间的联系,从而构建我们的领域模型。

使用用例分析法大致需要以下几个步骤:

  1. 描述问题:
    我们需要用简短的语句把问题域描述清楚,书写User story或者用例,是建模的关键前序动作。

  2. 挖掘概念(Digging out concepts):
    领域概念隐含在语句中,重点关注语句中的名词(nouns),因为nouns常常意味着重要的领域概念。这一步不容易做到,因为自然语言有很大的随意性,很多同义词、多义词混淆其中。

  3. 建立关联:
    寻找关系,需要关注动词(verbs)。因为关联意味着两个模型之间存在语义联系,在用例中的表现通常为两个名词被动词连接起来。

我们还是以上文的blog应用为例,演示一下这个用例分析方法的工作过程。

一、描述问题

首先我们需要对问题进行描述,整理出如下的User story:

  1. 作者输入用户名、密码登录博客,so that可以写文章。

  2. 作者打开编辑器,撰写文章,发表博客

  1. 作者可以给自己的文章打上一到多个标签

  2. 作者可以给自己的博客设置一到多个分类

  3. 用户可以发表评论,so that表达对文章的看法。

  4. 用户可以回复他人的评论,so that发表对评论的回应。

二、挖掘概念

对于blog这个简单的应用,我们不难从User story里面找到博客领域的相关概念,譬如作者、文章、博客、标签、分类、评论这些名词都是这个领域里重要的概念。

在这个阶段,我们可以整理出一个“核心领域词汇表”,这个词汇表将作为团队的“统一语言(ubiquitous language)”。对blog这个应用,我们可以得到如下的核心领域词汇表:

中文

英文

解释

文章

article

博客中的文章,和博客同义

标签

tag

文章的标签,文章可以有0到多个标签

分类

category

分类用来管理文章,一篇文章只能属于一个分类

评论

comment

可以对文章的评论,也可以对评论进行评论,评论可以盖楼4abc603381abef89eedb9f21f1cb0b72.png

在核心领域词汇表中,至少要包含中文、英文和解释三个字段,中文术语不必多说,我们日常都在说肯定是需要的;英文术语主要对应的是代码、设计文档、数据库schema等但凡需要用到的地方都要以这个词汇表为准,并保持一致性;解释是要说清楚这些概念的含义,可以是文字,如果需要也可以加图片。

三、建立关联

实体之间的关联肯定是有某个动作触发的,因此,我们要特别关注User story中的动词。在blog案例中,像撰写、打上、发表、设置、回复这些动词都有可能建立概念之间的联系。比如一个作者可以撰写多篇文章,所以Author和Article之间的关系就是one2many的关系。

83a463420142455229b38eca6e10a39b.png

至此,对于blog应用,我们的领域建模工作就完成的差不多了。有了这个领域模型,后续的数据模型设计,API设计,详细设计都可以围绕着这个核心领域模型展开了。

3.2 四色建模法

如果我们所有的业务都像blog应用这么清晰明了那该多美好。然而更多的业务场景要更复杂,语言表述也要更晦涩。四色建模可以作为用例分析的补充。因为,所有建模方法的本质都是一样的,即从问题域中挖掘领域概念,建立领域模型。四色建模,以及后面介绍的事件风暴也是如此,只是他们提供了更多的一些方法和视角,所以是一种补充。

57bef31888abc91fa4904456a0804a83.png

上图大家看到的就是四色建模。2、3、4都不是什么新鲜玩意,用例分析法的找名词,基本上就能把它们揪出来。

四色建模的补充作用,关键就在于Moment-interval,也难怪Peter会起一个怎么奇怪的名字,查阅了一些资料才发现,取这个名字也是被迫无奈,正如这篇文章中所说:

Peter Coad called this archetype «transaction»; however, this caused a lot of confusion. He actually meant the purer definition of the word as an exchange or interaction between parties. Because of the confusion, he renamed the archetype from «transaction» to «moment-interval»。

https://curtis.schlak.com/2013/05/09/modeling-in-color-part-iii.html

他本来想取transaction这个名字的,但是这个词容易引起歧义,所以改成了Moment-interval,想表达的是一种“交互”(exchange or interaction)。

好吧,既然如此,我们就暂且叫它“交互对象”吧。这东西之所以有用,是因为我们在用语言表述问题的时候,因为语言的灵活性、歧义性,会经常表达不明确。比如一些重要的领域概念可能会以动词(verb)的形式进行伪装。

比如这个用例:People join the club to become members.

join在这里虽然是一个动词,但是它暗含着一个重要的moment-interval,或者叫“交互行为(interaction)”。这个交互行为实际上是一个非常关键的业务信息,即入会申请(application)。只有先入会,才能成为会员,不是吗?

这就是四色建模给到我们的启示,我们的语言会“骗人”,有时候需要进一步挖掘,从多个角度观察,多聆听,多学习,才能把“宝藏”挖出来。

当然,上面的用例也可以换一种表述方式:people<who> submit an application to the club<what>, so that he can become a member<why>.

如果这样写的话,我们就能比较容易的识别出application这个重要的领域对象。所以,一切的根源还是在于“语言”,这也再一次佐证了按照用户故事(who)——(what)——(why)的方式写用例会更好,好的需求描述能让“其义自见”。

Tips:这里还有一个小技巧,即我们重要的信息最终都要落数据库,而数据库对数据记录的操作只有CRUD,因此我们在写User Story的时候,尽量把动词往CRUD上面引导,这样其背后的实体就能自然涌现出来了。

比如apply to be a member,把apply改成submit(对应CRUD中的Create),那么application这个重要的实体就复现出来了。

3.3 事件风暴法

“设”和“计”都是言字旁,也是在暗示语言和思考在设计中的重要性。事件风暴的好处是,我们可以让领域专家、产品经理、业务人员等充分讨论业务活动、业务流程。聆听他们的语言,从而有机会在这个过程中发现一些重要的领域知识(领域事件、命令、角色、实体等)。

45c908cb4deb494d6384d1e57398141c.png

Eric在《Domain Driven Design》中也表达了同样的观点,他在第九章“将隐式概念显性化”中说道,聆听语言、阅读专业书籍是挖掘隐式概念的有效方法。特别要留意领域专家说出来的在我们设计中没有的term(术语),这常常意味着一个重要领域概念的缺失。

我觉得时间允许的话,大家可以尝试玩一下,因为脑暴的确可以帮助我们思考的更全面,有机会发现一些隐式的关键领域概念。即使没有,这种“团建活动”,对于增进团队之间的情感和更深层次的理解业务也是有益的。

4. 总结

语言、语言、语言,重要的事情说三遍领域建模就是在分析语言!从语言中挖掘关键的领域概念、建立模型。因此,需求描述很重要,在建模之前,我们要尽量用正确的语言来描述需求(User story是很不错的范式)。

之后,我们要深入理解和分析这些语言,虽然“nouns are objects”可以解决大部分问题,但还不够,因此面对更加复杂的场景,为了挖掘更多的隐式概念,我们可以借助更多的辅助手段,比如四色建模、事件风暴、专家会谈、阅读领域书籍等等。但归根结底,都是语言的思维游戏。

最后,我想说领域建模作为一种能力,是可以通过《刻意练习》习得和不断提升的。越多的锻炼就能建立越强大的心理表征能力,届时,你会发现你已经不再需要这些“方法”的指引,更多的时候只是在用“直觉”。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐