多模块顺序_软件架构基础 3: 什么是好的模块化代码?高内聚、低耦合如何衡量?...
0. 写在前面
什么是好的代码?好的代码应该模块化。
王垠在其《编程的智慧》中也提到,要“写模块化的代码”。(不对人做评价,这篇文章写得是非常好的。)
如果你读过《代码大全》和《代码整洁之道》等书,一定对“高内聚、低耦合”不陌生。
好的模块化代码就是要高内聚、低耦合。
事实上,内聚和耦合是 1972 年就提出的概念,由于耦合不好具体的衡量,Meilir Page-Jones 在 1992 年提出了共生性(Connascence)。本章重点就是介绍如何评估模块化架构,以及引入共生性这一概念来帮助更好的模块化。
1. 模块化
不同的平台、语言为代码提供了不同的复用机制,将相关代码组合成模块。
理解模块对于架构师来说非常重要,因为用来分析架构的工具(可视化等)常常都依赖于模块化的概念。如果一个架构师在设计一个系统时,没有注意到各个部分是如何连接在一起的,那么他们最终创建的系统会带来无数的问题。
架构师必须保持良好的结构,这不会偶然发生。
模块的代码到底是什么?我们用模块化来描述相关代码中的逻辑分组,这些模块可以用来构造一个更复杂的结构。
现代的语言有各种各样的封装机制,例如,许多语言可以在函数/方法、类、包/命名空间中定义行为,每个包都有不同的可见性和范围规则。(这有时候也会让开发人员选择困难)
架构师必须意识到开发者是如何组织包的,如果几个包紧密的耦合在一起,那么重用其中一个包就变得非常困难。
鉴于模块化的重要性,研究人员提供了各种语言无关的标准来衡量,我们专注于三个关键概念:
内聚(Cohesion)
耦合(Coupling)
共生性(Connascence)(注:参考《UML面向对象设计基础》的翻译)
2. 内聚(Cohesion)
内聚性是指子程序中各种操作之间联系的紧密程度,我们的目标是让每一个模块只做好一件事,不去做其他事情。
试图分割一个内聚的模块只会导致耦合性增加和可读性降低。(Attempting to divide a cohesive module would only result in increased coupling and decreased readability.) —— Larry Constantine

计算机科学家们已经定义了一系列的内聚的衡量标准,从最好到最坏列出如下:
功能性内聚(Functional cohesion):模块内所有元素都为完成同一个功能而存在,共同完成一个单一的功能,模块已不可再分,具有最高的内聚;
顺序内聚(Sequential cohesion):模块必须顺序执行;
通信内聚(Communicational cohesion):两个不同操作的模块使用同样的数据。例如,在数据库中添加一条记录,并根据该信息生成一封邮件;
过程内聚(Procedural cohesion):两个模块必须以特定的次序执行;
时间内聚(Temporal cohesion):把需要同时执行的动作组合在一起形成的模块。
逻辑内聚(Logical cohesion):这种模块把几种相关的功能组合在一起, 每次被调用时,由传送给模块参数来确定该模块应完成哪一种功能。
巧合内聚(Coincidental cohesion):模块内的各个元素之间没有任何联系,只是偶然地被凑到一起;内聚程度最低。

内聚不容易考量,特定的模块需要架构师来具体决定,例如,考虑一个模块定义了:
Customer:
add customer
update customer
get customer
notify customer
get customer orders
cancel customer orders
或者可以说将后两个函数剥离出来,分成两个模块:
Customer:
add customer
update customer
get customer
notify customer
Order:
get customer orders
cancel customer orders
哪个更好?一如既往,这要看情况:
订单只有这两个操作吗?如果是这样,将这些操作放在客户包中维护可能是有意义的;
客户包按预期是否会变得更大?
订单是否需要如此多的客户信息?
这些问题代表了软件架构师工作核心的权衡分析。
由于内聚非常主观,计算机科学家制定了一个标准来衡量内聚性,其中 LCOM(Lack of Cohesion in Methods) 为著名。这里涉及到的数学公式平时很少用到,在此不再展开,只需要知道有这么一个公式,在需要的时候可以再查询拿出来用。想进一步了解的读者可以查看:https://en.wikipedia.org/wiki/Programming_complexity
3. 耦合(Coupling)
我们常常谈到要“解耦”,弱耦合是系统可维护的关键。
耦合其实也有多种类型,但在此不再介绍,因为它们已经被共生性(Connascence)所取代。
4. 共生性(Connascence)
1996 年 Meilir Page-Jones 发表了
《What Every Programmer Should Know About Object-Oriented Design》,完善了耦合的度量,并命名为:Connascence。
他是这样定义的:
如果一个组件的改变会要求另一个组件进行修改,才能保持系统的整体正确性,那么这两个组件就是共生的。—— Meilir Page-Jones
共生性分为静态的和动态的。我们将分别介绍各种类型的共生性,对于部分重要的、不易理解的,我将补充一些代码案例,作为具体的参考来帮助理解。
静态共生性:
4.1 名称共生性(Connascence of Name, CoN)
methodA()
改名为 methodB()
时, 调用 methodA()
的地方都要改名,这是代码库中最常见的耦合方式,现代的 IDE 的检索功能使修改代码的名称变得很容易,这是最理想的耦合方式;

4.2 类型共生性(Connascence of Type, CoT)
如果一个变量从值 100 变成了一个很大的数,变量的类型可能要从 int 改成 BigInteger
4.3 意义共生性(Connascence of Meaning, CoM)
例如,在很多语言中,通常会把大于 0 的数字认为是 True,0 认为是 False。下面是 Java 中的一个具体例子:
a.compareTo(b)// 如果 a = b,则返回值 0;// 如果 a > b,则返回大于 0 的值;// 如果 a < b,则返回小于 0 的值。
4.4 位置共生性(Connascence of Position, CoP)
函数的参数的位置顺序或个数耦合,例如下面的函数增加一个参数后,函数调用将会出错。
针对这个例子,我们可以通过下面的办法,将位置共生性转为名称共生性来降低耦合性:
class User { FirstName, LastName, Address }void SaveUser(User);
myrepo.SaveUser(new User{ FirstName = "bob", LastName = "Marley", Address = "Jamaica"});
4.5 算法共生性(Connascence of Algorithm, CoA)
多个组件必须就一个特定的算法达成一致。例如:客户端和服务端用相同的算法验证用户身份。这代表一种较高的耦合形式——如果算法细节改变,验证将不再有效。
动态共生性:
4.6 执行共生性(Connascence of Execution, CoE)
代码的执行顺序上的耦合。例如下面的代码,在设置主题之前就发送了,明显在顺序上有问题。
email = new Email();email.setRecipient("foo@example.com");email.setSender("me@me.com");email.send();email.setSubject("whoops");
4.7 时间共生性(Connascence of Timing, CoT)
常见情况是两个线程同时执行造成的竞赛条件。
这里我们可以看一个有趣的例子,发生在 bootstrap 的一个 issue:https://github.com/twbs/bootstrap/issues/3902
// using bootstrap modal$(element).modal('hide')$(element).modal('show') // Error!
// 隐藏一个 modal 大约需要 500ms 的动画,// 如果你在这时候直接调用了 'show',将会发生异常
// 我们必须这样做$(element).modal('hide')$(element).on('hidden.bs.modal', ()=>{$(element).modal('show') // ok})
4.8 值共生性(Connascence of Values, CoV)
常见的情况在分布式事务中,例如需要在多个独立的数据库中做分布式事务。
4.9 身份共生性(Connascence of Identity, CoI)
两个独立的模块需要共享和更新同一个数据结构,例如:分布式队列。
5. 共生性的属性
5.1 强度(Strength)
Page-Jones 指出,共生性有明确的强弱谱系,如下图所示,按强度递增排序。identity 具有最强的共生性,name 具有最弱的共生性。——也就是说用 name 的方式耦合则为最弱的耦合方式。

架构师应该倾向于静态共生性而不是动态共生性,因为开发人员可以通过现代的 IDE 来很快地确定它。
5.2 局部性(Locality)
局部性指两个模块的之间的远近程度。
通常情况下,在同一模块中、距离较近的类比在不同模块中、距离距离较远的类具有更高的共生性。换句话说,随着两个模块在代码中的距离增加,共生性会减弱。
5.3 程度(Degree)
共生性的程度与模块的影响大小有关——它影响了几个类还是几十个类?影响较小的共生性对代码库的损坏就较小。
6. 如何通过共生性来提高系统模块化
讲了这么多,我们到底如何实践共生性呢?
Page-Jones 提供了三个使用共生性来提高系统模块化的指南:
1.通过将系统拆分成封装的元素,使得整体的共生性达到最弱
2.最大限度地减少任何跨越封装边界的共生性
3.最大限度地提高封装边界的共生性
Jim Weirich (传奇的软件架构创新者,Ruby 社区活跃人士)简化了上面较为抽象的指导,提供了两个更具体的建议:
程度法则(Rule of Degree):将强共生性转化为弱共生性。
局部性规则(Rule of Locality):随着软件元素之间距离的增加,应使用较弱的共生性。
7. 耦合性和共生性
从架构师的角度来看,耦合和共生是有所重叠的,这是不同时代的产物,下图列出两者重叠的部分:

共生性提供了更精细化的考量,例如左边的数据耦合,在右边的静态共生性提供了更具体的建议。
8. 局限性
尽管如此,架构师在应用这些指标来分析和设计系统时,存在几个问题:
这些度量从代码层面考察细节,关注代码质量,而不一定是架构。架构师更关注模块如何耦合,而不是耦合程度,例如,架构师关心的是同步或异步通信,而不关心如何实现。
共生性并没有真正解决许多现代架构师必须做出的一个基本决定--在分布式架构(例如:微服务)中,使用同步还是异步通信?在后面会介绍新的方法来思考现代的共生性。
虽然对模块化进行了大量的介绍和思考,开发人员和架构师在实际实施过程中,还是会遇到很多的困难。
纸上得来终觉浅,绝知此事要躬行。
设计良好的架构,并非易事!
欢迎关注我的公众号:
多模块顺序_软件架构基础 3: 什么是好的模块化代码?高内聚、低耦合如何衡量?...相关推荐
- python模块化设计耦合度_模块化开发——高内聚低耦合
前言:emmm最近都在赶进度,已经好久没输出了,接下来都使用MakeDown文档写文,推荐大家使用VsCode编译工具,加入Markdown Preview Enhanced插件,创建一个MD文件,复 ...
- 耦合关系从强到弱顺序_软件设计要求—“高内聚低耦合”
耦合度 一.什么是耦合度 软件设计中通常用耦合度和内聚度作为衡量模块独立程度的标准.划分摸块的一个准则就是高内聚低耦合. 耦合度(Coupling)是对模块间关联程度的度量.耦合的强弱取决与模块间接口 ...
- 高内聚低耦合通俗理解_带你从入门到精通——「高内聚低耦合」
如果这是第二次看到我的文章,欢迎订阅z哥的公号(跨界架构师)哦~ 本文长度为2871字,建议阅读8分钟. 坚持原创,每一篇都是用心之作- 下面的这个场景你可能会觉得很熟悉(Z哥我又要出演了): Z哥: ...
- java 高内聚低耦合_高内聚低耦合法则实例解析
定义:一个对象应该对其他对象保持最少的了解. 问题由来:类与类之间的关系越来越密切,耦合度越来越大,当一个类发生改变时,对另外一个类的影响也越大. 解决方案:尽量降低类与类之间的耦合. 自从我们接触到 ...
- java 高内聚低耦合_高内聚低耦合是矛盾的吗?
随着PMTalk版本的不断迭代,到现在我们已经迭代到5.0了,上线了3年班,在这漫长的时间里,一个产品会在研发中.产品设计有什么问题呢? 这里的问题主要是包含三类 1.技术人员不断变换,代码规范层次不 ...
- java 高内聚低耦合_关于高内聚低耦合概念的理解
概念 高内聚低耦合,是软件工程中的概念,是判断软件设计好坏的标准,主要用于程序的面向对象的设计,主要看类的内聚性是否高,耦合度是否低. 目的 使程序模块的可重用性.移植性大大增强. 通常程序结构中各模 ...
- 8.18 模块设计原则:高内聚低耦合
8.18 模块设计原则:高内聚低耦合 模块内聚 定义(软考) 块内联系:模块内各元素的关联.交互程度 从功能角度:自己的功能自己实现,不麻烦其它模块 如何实现高内聚 功能内聚:模块的功能尽可能单一 模 ...
- 模块独立性与高内聚低耦合
模块独立程度的度量标准 1)耦合 不同模块之间的互联程度的度量 2)内聚 模块内部彼此结合的紧密程度的度量 模块耦合度越高模块独立性越低 模块内聚度越高模块独立性越高 高内聚,低偶合 耦合性也称块间联 ...
- java高内聚低耦合什么意思_高内聚低耦合的理解
在做rm比赛的过程中,发现文件和函数的代码规范逻辑十分重要,这不仅影响到自己看代码的方便与否,还影响到下届队员对我的代码的理解.因此打算开始规范自己的代码习惯,养成良好习惯,以后工作也能用上. 以下转 ...
最新文章
- NIST发布网络安全劳动力框架
- C语言函数指针的MFC版本Demo
- Liststring[] 如何去重
- print打印字符串之谜
- Javascript 第七天 笔记
- 这又何止呢的openeim002
- 背包问题——01背包问题——Charm Bracelet
- 我的YUV播放器MFC小笔记:unicode编码、宽字符
- 如何对您的API进行单元测试
- 活动回顾 | 智慧城市的发展趋势与挑战
- 去年五一的大理丽江之行,今年的得在加班中度过了
- 获取虚拟账号列表失败啥意思_「图」Windows 10 Build 18963发布:可显GPU温度 支持重命名虚拟桌面...
- NETBEAN 启动报错 CANNOT LOCATE JAVA INSTALLATION IN SPECIFIED JDKHOME的解决办法
- ardruino控制继电器_用 Arduino 实现带继电器的拨动开关
- vue 数组元素替换_解决vue数组中对象属性变化页面不渲染问题
- unity 生成和识别二维码
- stm32F107VC通过模拟SPI方式读取LIS3DH三轴加速度传感器数据
- Spring中bean的scop
- android盒子机器码修改器,HiProInfo(盒子机器码修改工具)
- 每秒处理10万高并发订单支付系统架构
热门文章
- ubuntu12.04循环登录,无法进桌面的问题。
- 蓝桥杯 ADV-222 算法提高 7-2求arccos值
- html通用的排班方法,呼叫中心排班的两种主要方法
- springsecurity原理执行流程_3. Spark原理-执行流程解析
- Python中的分组函数(groupby、itertools)
- mongodb 添加用户及权限设置详解
- 直播间搭建项目——延续直播发展趋势
- paper 134:结构张量structure tensor(二)
- linux如何修改文件或目录的权限(chmod)
- POJ1062 昂贵的聘礼(最短路径)