模块设计质量可视,让我们设计的模块设计更合理
我们探索模块设计的合理、可视,目的是帮助广大模块设计者能更好的认识和改进自己的模块设计,抛砖引玉,你有任何想法都可以联系我们,期待你的加入。
引用百科对于模块化设计的简介:程序的编写不是开始就逐条录入计算机语句和指令,而是首先用主程序、子程序、子过程等框架把软件的主要结构和流程描述出来,并定义和调试好各个框架之间的输入、输出链接关系。逐步求精的结果是得到一系列以功能块为单位的算法描述。以功能块为单位进行程序设计,实现其求解算法的方法称为模块化。模块化的目的是为了降低程序复杂度,使程序设计、调试和维护等操作简单化。改变某个子功能只需相应改变相应模块即可。
每个设计模块的小伙伴都有一个诉求或者疑问:我设计的模块是否理。就像我们在装修自己的小家时,我们会针对住房做功能分区,分区合理的判断原则有:动静分区、公私分区、污洁分区、干湿分区。同样,我们的模块设计合理性也有相关原则来判断。
从模块设计的定义来看,模块主要是一个功能块+输入、输出链接关系。从抽象的角度来看就是功能内聚、功能耦合。因此,我们只要把握好这两点就能得到一个不错的设计。如何把握它们呢?我们可以人下面的框架来入手:
接下来我们从功能内聚、功能耦合两个方面分别看这些原则:
1、 功能内聚
a) 复用/发布等同原则(REP)
软件复用的最小粒度应等同于其发布的最小粒度。
该原则就是指模块中的类和模块必须是紧密相连,即一个模块中不能由一组毫无关联的类和模块组成,它们之间应该有一个共同的主题或者方向。
度量方式:根据模块/类的职责亲合关系来,例如一个子模块或者类,它只被外部模块使用,内部模块和他毫无关系,那么这个子模块或者类,可以考虑把它拆分出去,成为单独的模块或者合并到使用它的模块中。
图1. 模块拆分样例
上图中左边的资产管理模块中存在两个变因,一个资产、一个区域,所以拆分成右边的结构似乎更好。
b) 共同闭包原则(CCP)
包中的所有类对于同一种性质的变化应该是共同封闭的。一个变化若对一个封闭的包产生影响,则将对该包中的所有类产生影响,而对于其他包则不造成任何影响。这是面向对象设计的原则之一SRP在模块设计原则的体现,简单说就是“一个模块不应该同时存在着多个变更的原因”。遵循这个原则可以有效降低因为软件发布、验证和部署带来的工作压力。
如何度量:见下图,是三个依赖的模块,任何一个模块发生变更时,其它模块都不应该伴随变化。
图2. 组件图
c) 共同复用原则(CRP)
指一个包中的所有类应该是共同重用的。如果重用了包中的一个类,那么也就相当于重用了包中的所有类。因此,我们需要将经常共同复用的类和模块放在一个模块中。例如:容器类和相关的遍历器类放在一个模块中。
CRP原则是ISP原则的普适版。ISP建议我们不要依赖带有不需要的函数的类。
如何度量:就是看外部依赖场景中是否都依赖整个模块的内容,还是以资产管理模块设计为例,见下图。
图3. 资产管理图
用户需要变更区域信息时,不会涉及资产相关的变更,所以,变更区域的上级功能模块只依赖区域,而不依赖资产,所以这个资产和区域这两类职责可以分开。
总之,这三个原则帮我们对模块的功能内聚做了很好指引,那是否我们只要将这三个原则都做到最好就能实现最好的模块设计呢?很显然事情没有这么简单,因为模块设计从软件诞生以来都是软件结构化编程的一个重要课题,近年它涉及的方法、工具等都在高速发展,比如最近很火的DDD等。其实,该三个原则相互补充、相辅相成,也相互制约。模块设计人员的任务就是要在这三个原则中进行取舍。下面是三大原则的张力图,供大家参考。
图4. 功能内聚原则张力图
2、 功能耦合
a) 无依赖环原则(ADP)
这个原则很简单,就是模块的依赖关系中不应该包括依赖环。这种依赖环有两种形态:
i. 循环依赖
图5. 循环依赖图
ii. 双向依赖
图6. 双向依赖图
如何度量:UADP Garding已经支持这个度量,详细操作见相关操作指导
解决这个问题有两个常用手段:依赖倒置原则(DIP)、创建一个新的模块
b) 稳定依赖原则
依赖关系必须要指向一个稳定的方向。
我们可以应用如下公式来计算每一个模块的稳定值
Fan-in:入向依赖,组件外部类/文件依赖组件内部类/文件的数量
Fan-out:出向依赖,组件内部类依赖于组件外部类的数量
I:不稳定性,I= Fan-out/(Fan-in + Fan-out),范围【0,1】,I=0表示最稳定;I=1最不稳定
将这些值标记到模块关系图上就能绘制出模块稳定性的依赖关系图,如图7。
图7. 模块稳定性的依赖关系图
从上图中,可以看出:
ü 模块1的稳定值是0.2,说明它比较稳定
ü 模块2的稳定值是0.6,说明它比较不稳定
ü 稳定的模块1依赖不稳定的模块2就是一个不好的设计,因为模块2的不稳定性会引起模块1的不稳定。
ü 我们应该尽量避免这种情况出现,如果出现可以用抽象模块的方式解决。
c) 稳定抽象原则(SAP)
一个模块的抽象化程序应该与期稳定性保持一致。该原则为模块的稳定性与它的抽象化程度建立了一种关联。一方面,以原则要求稳定的模块同时应该应该依赖的是抽象,这样它的稳定性就不会高中生到扩展性。另一方面,该原则也要求一个不稳定的组件应该包含具体的实现代码,这样它的不稳定性就可以通过具体的代码轻易修改。
因此,一个模块想要成为稳定的模块,那么它就应该由接口和抽象类组成,以便将来扩展。如此,这些既稳定又便于扩展的组件可以被组合成既灵活又不会受到过度限制的架构 。
SAP与SDP这两个原则结合起来,就等同于DIP(依赖倒置原则)。因为SDP要求的是让依赖关系指向更稳定的方向,而SAP则告诉我们稳定性本身就隐含了对抽象化的要求,即依赖关系应该指向更抽象的方向。
如何衡量抽象化程度:就是模块中抽象、接口类所占的比例
Nc:组件中的类的数量
Na:组件中抽象类和接口的数量
A:抽象程序,A= Na/Nc
A的范围是0到1,0表示没有任何抽象类,1为全是抽象类。
接下来,我们就可以用抽象化程度A和稳定性程序I定义一个坐标图,横坐标是I,纵坐标是A,中心线是最优设计线,即只有落在这个线上,说明你的设计是最好的,那我们叫他最优设计坐标图,效果见图。其实,点落在中心线很难,最理想的情况是无限逼近这个中心线,当然设计师更多是架构设计的参考,不会一味逼近,因为越逼近,设计成本越高,也许现实是不可取的。因此做好综合权衡即可。
图8. 最优设计坐标图
另外,我们可以计算出最优设计坐标图中每个点与中心线之间的距离,这个距离就是我们设计质量的偏差量。我们以它为纵坐标,以版本号为横坐标,就可以衡量模块在各版本间的模块外部依赖的设计质量变化趋势,从而更好的认识和看护模块。
图9. 模块外部依赖设计质量趋势图
本文来自博客园,作者:易先讯,转载请注明原文链接:https://www.cnblogs.com/gongxianjin/p/16420470.html