仓库源文

.. Kenneth Lee 版权所有 2020

:Authors: Kenneth Lee :Version: 1.0

概念视图


正文

这是这个文档的姊妹篇:

    :doc:`开发视图`

说一下概念视图(对我来说,概念视图就是逻辑视图)是什么,应该怎么用。

概念视图就是名称空间怎么定义。比如你做一个照相机的框架,你分成照相机应用,相机 HAL,相机驱动,相机DSP固件4个模块。这里,所有这些模块里面都有“传感器数据”这个概 念,它表示照出来的那个照片。但这个概念在每层中的意思都是不一样的。比如对于相机 DSP,他可能是刚刚经过DSP数据过滤的那个Off Screen的数据。对于相机驱动来说,它可 能是一个Packet的表述,是一种DSP报给CPU的报文。对于HAL,它是一个函数和数据结构的 综合,而对于应用,可能是一组连续Off Screen在内存中的表达。

那么我们怎么说明白这个处理过程(或者这个功能),是怎么工作的呢?名字和名字是不 一样的。程序员说“程序”是高级语言表达的一个个语句,计算机说“程序”是机器码表达的 一条条“指令”,跨域层次以后,同一个名字会表示不同的东西,我们需要理清楚这个名称 空间,什么概念在什么位置上暴露,才能实现有效的思考和沟通。

这种所谓的“理清楚”,有可能是文字的解释和示例,给出这些名字在上下文中是怎么用的 ,也可以是配合相关描述给出Class Diagram说这写“名字”之前的关联是怎么样的。形式不 重要,理清关系重要。Class Diagram只是一种很好的媒介和方法,并非是这个定义的核心 。

很多时候,我们说不清楚一个功能,或者说不清楚其他视图如何定义,都是因为我们其实 没有解剖清楚这个概念视图,一个Off Screen的概念满天飞,这里是这个意思,那里是那 个意思,说半天也不知道你说啥。

说不清楚还好了,毕竟是个(可以看到的)问题,我们可以改正。更大的问题是你用这个 方法来思考,就会混淆整个概念空间。比如你的相机驱动和DSP交互,你有DMA的概念,你 在应用层和HAL交互,本来这个概念(可能)就应该看不见了,这样上下的关联才是解耦合 的,但你没有理清这种关系,到了业务层,也要让人家知道什么时候分配虚拟内存,什么 时候分配DMA内存,这样还搞什么分层啊,你就一层,所有概念都是在一起的,这样的系统 有什么架构可言?后面完全没法演进啊,一演进全部代码都换掉得了。

如果你严肃描述其他任何一个视图,甚至只是描述你的API手册,概念空间都是必须的。比 如你就描述一个服务的对外接口。你要求人家先找到服务ID,然后基于服务ID申请一个“会 话”,然后基于会话,发起请求,然后返回“响应”。这些个“服务”、“会话”、“请求”,“响 应”的概念就需要至少“名词解释”一次吧?而且你只要定义这个东西,你马上就能发现,你 的名称空间丢失什么。比如你定义了“服务是一种全网标识可以响应特定请求的软件实例” ,你就马上会发现你需要定义:“一个服务器可以提供多少个服务?服务在全网如何标识? 如何找到它?(可能需要名称服务)”,这些设计,无论是用什么语言实现,也无论你如何 组织你的模块,都是你改变不了的。这层逻辑不能自恰,你的下一层设计怎么做都是做不 好的。你做API定义,不能给我列出一堆函数,完全不管这些概念如何定义,觉得我应该看 到你这组函数就知道你在说什么吧?

例子

我们看个例子。比如前面提到的这个服务器,服务,会话的关系可能是这样的:

    .. figure:: _static/概念视图例子.jpg

就这张图其实你什么都看不懂,还还是需要配合其他描述手段(比如文字或者API等配合) ,才能说明白这些关系是什么样的。

而且同一个概念,我们不一定可以从一个角度来看待它,上面这个逻辑还可以是这样的:

    .. figure:: _static/概念视图例子2.jpg

下面这个角度,我们把服务器这个概念从你的用户接口层面拿掉了(其实没有完全拿掉, 但明确的关联没有了),你也不用管我是靠那台服务器给你提供服务的,你从名称服务上 获得一个会话,然后你用会话请求服务,你别管我背后有多少服务器,也不要管我是如何 标识服务的,我可能用ip加端口表示,也可能用dev_id+qp_id表示,你基于会话来跟我说 话就好了。

总的来说,概念视图本身不复杂,它只是给你提醒一个非常非常重要的入题角度。设计的 设计技巧并非画这种图本身,而是你要用的设计。他是一种语言,你有语言而没有思想, 也表达不出什么东西来。所以,还是那句话,不要“填空”,你要“技术”。

补充1

我们再说远一点。概念视图是4+1建模方法中最有架构特质的一个视图,因为“概念逻辑”是 穿越所有实现的,就是说我不管你实现上玩什么小九九,把功能做成硬件也好,软件也罢 ,让用户输入也行,你帮用户决定也中,你概念上说不通,你的设计就玩不出花来。因为 你的设计最终也要能表达成概念逻辑的。你可以做各种封装,但封装完了,你还是要给每 个层面一个逻辑链,要表述这个逻辑链,你有些东西是省不掉的。

比如我们上面用会话封装了服务器,但我的会话是跟谁说话?你封装掉的其实是服务器的 标识和位置,你可从来没有封装掉服务器的概念。而且,封装越多,你的服务能力就越差 。比如你会话封装掉了位置信息 ,用户也可能对你封装了这个位置信息,这样,你想就近 提供服务这个目的就不能得逞了。比如他从一地申请这个服务,然后迁移给另一个位置使 用,本来如果他注意到你的位置,他可以做出更好的选择,你牛逼哄哄,不暴露这个信息 ,你怎么都做不快。这种问题在设计中到处都是,比如你的芯片NUMA成本很高,你非要封 装这个差异,那我就随便迁移我们的进程了,你的效率又跟不上。

所以概念空间怎么定义,本身不是一个自由的问题,我们定义概念空间,是为了把用户的 思维就绑定在有利于你的语言中(相对来说,也可以说有利于它),懂得如何操控语言空 间,是操控架构的根本。

补充2

再说一个对于建模每次都要说的问题:无论画什么图,我觉得如果你画出二三十个 实体的,肯定是不指望人看懂的。你画图的目的是抽象,要复杂你写代码去,让图比代码 还复杂,不如写代码去,还建啥模啊?

比如下面这种极端的:

    .. figure:: _static/复杂关联关系.jpg

人家这意思就不是说“关系”,人家做说的是“我很复杂,它么别碰我”。

我还见过一个存储服务器的构架设计,iSCSI是个接口,SCSI是个接口,SCSI FC,SNMP, DNA,HDCP统统都是外部接口,都在一幅图里,这个图还有重点吗?你内部概念一样的,仅 仅通过不同代理提供不同的外部接口,把这些接口封装为一个名字在下一步分解不就好了 ?用写代码的方式来写构架,越写越详细,这模型的效果就没有了呀。‘

补充3

概念视图不是总是存在的。概念视图一般绑定用户需求,但如果你的用户需求就是架构本 身(比如你的系统本身的软件管理功能),你的概念视图就是你的部署和开发视图(严格 来说可以说是不同层次的,但实际中很难分开)。所以,不要把某种视图看做是必不可少 的东西,否则又陷入本本主义了。

补充4

说起来,概念视图有点像古时打仗的“檄文”。所谓“名不正则言不顺”,看起来这个东西和 打仗没有什么关系,只是空头说说的东西,但对于一场战争,它却有举足轻重的作用。你 也许觉得打仗只不过就是打赢对放,你封侯列土,功成名就。对于士兵呢?民工呢?当地 的民众呢?敌人呢?这些人每人都有自己的逻辑。你的檄文说起来好像不接地气,大家也 不见得也有一样的目标,但大家会根据这个目标来判断你是否可信,自己的目标能否依附 于这个目标,你这个目标定义的一点小变化,就决定了一群人到底变成你的朋友,还是你 的敌人(很多时候,这也不能和稀泥。你把一类人当做朋友,另一类人就必然要成为你的 敌人的。

所以,概念(逻辑)空间的定义,就决定了你的架构靠向了什么,决定了你会接受什么逻 辑,放弃什么逻辑。如果你的概念你空间混乱,你就会把冲突的逻辑当成朋友放到你的系 统中,不需要别人打你,你只要军队一多,你自己就先崩溃了。

补充5

这是一个我在其他文档中写的定义,相当于本文的从另一个角度的重写,也放到这里来了 :

所谓逻辑空间,又叫概念空间,对应4+1视图的逻辑视图。它尝试丢开实现的具体方法,仅 从概念上“说清楚”一个功能是如何发生作用的。设计的实现受各种要素的影响,又有很高 的自由度,提供一个接口可以是调用,可以是消息;存储一组数据可以是链表也可以是文 件。这些东西都在变化,但无论是用什么方式来做接口,传递一个“会话”,会话中可以包 含一系列的多个“消息”,消息中需要包含被控制的实体的ID和对它的要求……这些要素是不 会变的,这部分逻辑的设计,就称为逻辑空间设计。如果逻辑空间都无法自恰,那么接口 设计,数据结构设计,流程设计,无论做成怎么样,都不可能自恰的。这就是概念空间建 模可以起的作用。

逻辑空间设计以概念和概念的关系设计为主。而概念是由于不同而引起的。是因为我们需 要区别对待,所以我们才开始在已有的概念之外增加概念,并且让这些概念的关系自恰, 让我们基于这些概念描述的各种关系和方法不会自相矛盾。

比如我们为了说明“访问内存”这个功能,可能会建立遮掩给一个基本的概念空间:内存由 一组连续的存储单元组成,这些存储单元被线性编址,组成有效内存地址空间,内存用户 通过发出读写请求实现对有效空间中的内存单元进行读写。读写请求包括两种:

概念空间描述的时候不一定细致(比如这里并没有细化读写时需要有权限检查等),但它 在这个抽象层面总是逻辑自恰。比如我们这里不会说“内存写操作提供了数据长度和用户ID ,完成写入的目的”,因为这并不符合逻辑,也不会没有“内存地址”这个概念,否则我们说 不清楚什么是内存读写。

在这个名称空间中,我们没有提到“内存距离”这个概念,然则,我们的概念空间不认为访 问不同的内存地址有什么“不同”。

但如果我们现在在实现内存的时候无法一视同仁地实现所有的内存,我们希望使用者尽量 使用某些地址范围内的数据,这样就产生了“区别对待”,区别对待就会产生新的“概念”, 那么我们就需要为这个新的概念建立新的逻辑空间,并且保证这个新的逻辑空间和原有逻 辑空间是自恰的。

比如为了说明内存实现中有NUMA这个概念(内存靠近不同的CPU),你需要建立如下概念:

你可能还需要建立如下逻辑:

这个逻辑和前述的内存基本逻辑可以没有冲突地被表述,NUMA的各种实现,无论实现为什 么形式,都是“有可能的”,但如果概念空间不自恰,无论你用什么方式提供这些接口,它 都不可能可以持续发展。

一些实例:

        linux进程创建有什么区别?

        为什么linux是0 fork出1号进程再fork出shell而不是0进程直接fork出shell?