APP下载

软件设计中的模块依赖研究

2021-04-25饶建农

电脑知识与技术 2021年9期
关键词:耦合规范

饶建农

摘要:针对软件开发中的模块依赖提出了一种新的模块间依赖模型。新模型的关键思想是依赖是由规范调节的,这样不仅记录了依赖的存在,而且记录了依赖的质量。单个模块不一定只提供单个规范,每个依赖模块可以通过不同的规范使用它。这种依赖的概念比传统的概念更容易解释一些常见的编程习惯用法,并为分析和设计提供了新的评价体系。

关键词:模块依赖;规范;依赖关系图;耦合

中图分类号:TP311      文献标识码:A

文章编号:1009-3044(2021)09-0078-03

开放科学(资源服务)标识码(OSID)

1 背景

软件设计者早就认识到模块之间的依赖关系在评估设计中的意义。当模块之间存在许多相互依赖关系时,系统更难理解,编码分工的灵活性降低,局部变化将产生广泛影响。尽管依赖性起着开创性的作用,但是大多数设计人员对依赖性是什么,以及如何表达依赖性只有模糊的认识。

尽管各种代码依赖因其在分离编译和程序分析中的作用而受到重视,但对设计依赖的重视却很少。因此,依赖关系图在程序设计中的作用要小得多,不能用作分析的基础。

本文提出了一种新的依赖模型。该模型的工作还处于初步阶段,但它确实解决了标准模型的一些问题,特别是更好地解释了面向对象设计中的一些常见的习惯用法。

2 标准模型及其缺陷

模块依赖的标准概念首先由Parnas[1]阐述,大多数开发人员都很熟悉。该系统被描述为一个图,节点表示模块,边表示语法依赖关系。每种模块关系可以赋予不同权重,边上的权值是模块间存在多种关系的权值和[2]。模块A对模块B的依赖,如从A指向B,表明A在向依赖它的模块提供服务时,使用了B。

在进行软件工程开发时,尽管使用了“模块依赖图”来解释软件设计的思路,但是只有这些图是不充分的,因为它们通常不能捕捉到促进设计的洞察力,在相关概念上存在一定的缺陷。

1) 传递性。根据定义,依赖关系是可传递的,而直观上它不应该是。如果A使用B和B使用C,那么,在没有附加信息的情况下,必须假设对C的任何变化都会影响B,从而间接地影响A,从而A使用C。但是,也许B是在A和C之间插入的,以保护A不受到C的变化的影响,而对C的大多数变化都不会影响A。

2) 多态性。现代语言的关键元素,如多态性和接口,没有得到适应。考虑一个典型的Java程序,其中客户端类C使用哈希表类H和键类K,H依赖于K吗?一方面,H的代码没有引用K的代码;另一方面,H很可能属于K之前编写的库,对K的更改肯定会导致H失败。例如,如果K的Equals和Hashcode方法不兼容,就会违反Hashtable的一个关键表示(相同的键保留在相同的Bucket 链中),并且使用给定键进行查找可能不会返回最后插入到该键下面的值。

3)先决条件。给模块提供一个错误的过程参数会导致它失败。如果模块的规范规定了一个前提条件,那么设计人员的意图是将错误输入的责任归咎于调用模块。但是一个模块不能简单地被称为“使用”调用它的模块。

4)替换。由于A对B的依赖并不表示A实际使用了B的哪些属性,因此无法判断B的哪些属性应该保存在替换它的模塊中。因此,依赖关系图不能用来解释组件的兼容性。

3 新模型

提出的新模型是基于两个简单的想法。首先,依赖表示一个假设:如果A的设计者或实现者对插入A的环境做出一个假设,那么模块A依赖于模块B,这个假设通过B的存在在构造的系统中被证明是正确的,原因可能是因为假设A以一定的频率执行;或者传递给它的值满足某些条件;或者通过过程调用返回给它的值,与它以某种方式传递给它们的值有关。其次,依赖关系是由规范协调的:一个模块不直接依赖于另一个模块,而是依赖于另一个模块必须满足的特殊要求。模块A对模块B的依赖关系是由专门指令S所规范的,这意味着模块A假设S保证的条件,系统通过B配置来提供这些条件。

新模型解决了标准模型的一些缺陷但并非全部缺陷。

1) 由于插值规范,不存在传递性问题。如果A通过规范S依赖于B,而B通过规范T依赖于C,那么很明显,保留T所要求的行为的C的变化不会影响B;而影响B的变 化,只有那些导致B不再满足S的变化才会影响A。

2) 阐明了多态模块的使用。哈希表H通过规范对象依赖于它的键类K,在Java中被称为“对象契约”。H对K的依赖是配置的一项工作:在这种情况下,由于一些客户端类C将K类型的对象传递给哈希表类H。H的设计者不期望预测K的使用,但可以假设任何对象被用作键的类都会满足对象。

3) 前置条件表示为相反方向的依赖关系,前置条件和后置条件可以看作是与不同方向的数据相关的不同规格。

4) 关于替换的推理很容易成为使用规范的副产品。一个模块的依赖,使规范S中描述的假设将被适当地保留,如果它所依赖的模块被一个也满足S的模块所取代。如果将某个模块视为未解释的名称(这通常很方便),则可以使用一个明确的排序来证明不涉及精确匹配的替换是正确的。

规范在传统模型中出现,至少是隐含的,但它们扮演了不同的角色。在Parnas的公式中,相关的是使用模块的规范,而不是使用模块。此外,在我们的模型中,规范是与依赖相关而不是与模块相关。一个单独的模块可能有多个规范,每个规范表示一个模块的视图,并提供给依赖于它的另一个模块。在哈希表示例中,键类K出现在规范对象下的哈希表类H中,但在提供特定域属性的更强规范下,它可能出现在客户端类C中。

4 案例研究

许多“四人组”的设计模式[3]都是出于依赖性的考虑。下面举例说明观察者模式的依赖模型。

图1中显示了与观察者重构的程序片段的依赖关系图。左边的原始程序有一个主题类Subj和一个具体的观察者类Cobs。通过对特定的Cobs的过程的调用,从客户端类客户端向Subj的更新传播到Cobs。已经用规范Cobs标记了这种依赖性:使用规范的模块名称是为了建议规范承诺模块设计提供的所有属性。

右边的重构程序主要不同于中介依赖关系的规范。关键的区别在于Subj不再通过专门化Cobs与Monterey.cobs相互作用,而是通过一个更弱的规范Cobs,一个可用于其他观察类的通用观察专门化Cobs。这就是观察者的核心动机:它允许添加新的观察者类,而不需要更改Subj的代码。通过规范Get表示Cobs现在必须向Subj调用以获得状态更新,从Cobs到Subj存在一个回依赖关系。

对客户端类Client和主题Subj之间的关系进行了细化,以显示两种不同的交互。规范更新中介的依赖表示服务Subj在响应客户端的请求更新其内部状态时提供的依赖。标签Reg代表Subj提供的注册服务,其中一个对象被添加到其内部观察员记录中。有两个与Reg相关的依赖关系,显示为双向箭头,对应于注册操作的前置和后置条件。前置条件显示为从Subj到Client的箭头,表示Client有义务避免注册可能创建观察周期的观察者。

最后,Client对Cobs有一个新的依赖关系,因为客户端类现在需要在注册观 察者对象时处理它。假设Client创建观察者对象,可以用规范ConsObs来标记依赖关系,以表示构造函数的使用,但可能没有其他操作。

典型的设计模型是代码在某些方面变得更加复杂。应用模型后有更多的依赖,但它们更系统地组织起来。最重要的是,行为的某些方面被考虑到由通用规范(Reg和 Obs)引导的依赖,这些规范是模型的标志。

5 结束语

5.1 规范调节依赖

程序的依赖结构不能简单地从其代码中提取。一方面,確定一个系统中哪个模块履行另一个模块的假设所产生的责任可能并不容易。在哈希示例中,要找到哈希模块H对关键模块K的依赖关系,至少需要进行基于类型的分析,以确定哪些类型的对象被插入到表中。此外,依赖关系代表了设计者对如何在模块之间划分责任的主观看法。在观察者的例子中,采取了传统的抽象数据类型观点,主题类和观察者类间接地为客户端提供服务。但是一个同样成立的观点是,观察者类依赖于其他类的服务:它的规范要求它显示某些状态更改,因此它需要通知这些更改何时发生。在这个观点中,从Subj到Cobs的通知依赖被逆转。类似地,在哈希示例中,对密钥的关注可能被表示为从哈希类H到客户端类C的先决条件依赖关系,该类将错误密钥传递给H的责任分配给C。

依赖关系图不应与类图或对象模型混淆。类图只是面向对象程序的句法结构的图形表示,显示继承层次结构以及实例变量的源类和目标类。对象模型是堆不变[4]的图形表示:因此,在语义上,模块依赖图是句法的。

由于一个模块不仅可以被视为需要多个服务,而且还可以提供多个服务,因此跟踪两者之间的关系可能是有用的。丰富的依赖关系图可能包括每个模块的传入和传出规范之间的关系,然后可以执行一种“模块切片”,遵循模块之间和模块内部的依赖关系。模块间接依赖的一组模块可能比仅仅通过跟踪模块之间的依赖边缘而获得的一组模块小,特别是对于面向对象的程序,其中类所扮演的“角色”通常在很大程度上是独立的。在观察者示例中,这样的分析将表明Reg对Subj的依赖没有进一步传播,相反,更新依赖被传播到Obs和Get依赖。

在提出的新模型或标准模型中没有捕捉到一些重要的耦合形式,最显著的是那些由于共享而产生的耦合。假设模块M使用模块W编写一个文件,模块R读取它,W写文件的格式必须与R读文件的格式相同。因此,对W的更改可能会破坏R,但两者都不能为对方提供服务,因此依赖的标准概念在它们之间找不到耦合。

5.2 未来的工作

提出的新模型为软件设计及其原理提供了一些新的见解,但它还远未完成。未来需要完成的工作包括:

1)整理依赖关系和代码之间的关系,并开发提取至少近似依赖关系的分析算法。在Java中,应该可以使用基本的类型推断来查找依赖关系,并从显式使用接口或使用类方法的子集来合成规范标签,调查在推理系统的哪些部分损害关键模块时使用依赖关系。前置条件依赖为代表传统模型所忽略的耦合提供了一些希望。

2)找到一种方法来表示由于共享而产生的耦合,可能是使用ML编程语言的共享约束[5]或单元[6]的参数绑定约束。

3)更好地理解依赖关系在软件工程中的作用,也许可以按照Epinger在设计结构矩阵及其应用方面的常规工程工作的思路。

参考文献:

[1] Parnas D L.Designing software for ease of extension and contraction[C].IEEE Transactions on Software Engineering,1979,5(2).

[2] 杜欣,赵康,倪友聪,等.一种基于逻辑的Java模块依赖图构建工具[J].计算机应用与软件,2016,33(4):6-10.

[3] Erich Gamma,Richard Helm,Ralph Johnson,et al.Design Patterns: Elements of Reusable Object-Oriented Software[M].Addison Wesley,1994.

[4] Daniel Jackson.Object Models as Heap Invariants[M]//Carroll Morgan,Annabelle McIver. A chapter in: Programming Methodology.Springer Verlag,2002.

[5] Robin Milner,Mads Tofte,Robert Harper,et al.The Definition of Standard ML (Revised)[M].MIT Press,1997.

[6] Flatt M,Felleisen M.Units: Cool modules for HOT languages[C].Proc. Sigplan 1998 Conference on Programming Language Design and Implementation,1998:236-248.

【通联编辑:谢媛媛】

猜你喜欢

耦合规范
来稿规范
来稿规范
非Lipschitz条件下超前带跳倒向耦合随机微分方程的Wong-Zakai逼近
来稿规范
来稿规范
来稿规范
基于改进SBELM的耦合故障诊断方法
厌氧氨氧化与反硝化耦合脱氮除碳研究Ⅰ:
基于“壳-固”耦合方法模拟焊接装配
求解奇异摄动Volterra积分微分方程的LDG-CFEM耦合方法