第六章模块化设计

第六章模块设计
目录
6.1模块化:内聚性与耦合性
石棉布规格
6.2  信息隐蔽
6.3  建立模块的理由
6.4  任何语言中实现模块
6.5  小结
相关章节
高质量子程序的特点:见第5章
高层次设计:见第7章
抽象数据类型:见第12.3节
“你已经把你的子程序放入我的模块中”
“不,你已经围绕着我的子程序设计好了模块”
人们对于子程序和模块之间的区别往往不很注意,但事实上应该充分了解它们之间的区别,以便尽可能地利用模块所带来的便利。
“Routine”和“Modu1e”这两个单词的意义是很灵活的,在不同的环境下,它们之间的区别可能会变化很大。在本书中,子程序是具有一定功能的,可以调用的函数或过程,关于这一点在第五章已经论述过了。
而模块则是指数据及作用于数据的子程序的集合。模块也可能是指,可以提供一系列互相联系功能的子程序集合,而这些子程序之间不一定有公共的数据。模块的例子有:C语言中的源文件,某些Pascal版本中的单元及Ada语言中的“包”等等。如果你所使用的语言不直接支持模块,那么可以通过用分别编程技术来模仿它,这也可以得到许多由模块带来的优点。
6.1模块化:内聚性与耦合性
“模块化”同时涉及到子程序设计和模块设计。这是一种值得研究的,非常有用的思想方法。
在1981年出版的《Software  Maintenance  Guidebook》一书中,Glass和Noiseux认为模块化给维护性带来的好处要比给结构带来的好处多得多,它是提高维护性的最重要因素。Lientz 和Swanson在《Software Maintenance Management》一书中引用的一项研究表明,89%的代码使用者认为使用模块化编程改进了维护性(1980)。在一次理解测验中发现,采用模块化设计程序的可读性要比不采用这种设计的程序可读性高15%(1979)。
模块化设计的目标是使每个子程序都成为一个“黑盒子”,你知道进入盒子和从盒子里出来的是什么,却不知道里边发生什么。它的接口非常简单,功能明确,对任何一个特定的输入,
你都可以精确地预测它相应的输出结果。如果你的子程序像一个黑盒子,那么它将是高度模块化的,其功能明确,接口简单,使用也灵活。
使用单独一个子程序是很难达到这一目的的,这也正是引入模块的原因。一组子程序常常要使用一套公用的数据,在这种情况下,由于子程序间要共享数据,因而它们不是高度模块化的,作为一个单个的子程序,它们的接口也不简单。但是,作为一个整体,这组子程序则完全有可能为程序的其它部分提供一个简单的接口,也完全有可能达到高度模块化这一目标。
6.1.1模块内聚性
草耙子
模块的内聚性准则,与单个子程序的内聚性准则一样,都是十分简单的。一个模块应该提供一组相互联系的服务。
比如一个进行驾驶控制模拟的模块,其中应含有描述汽车目前的控制设置和目前速度的数据。它可以提供像设定速度、恢复到刚才的速度、刹车等功能。在其内部,可能还有附加的子程序和数据来支持这些功能,但是,模块外的子程序则不需对它们有任何了解。如果这样的话,那么这个模块的内聚性将是非常强的,因为模块中的每个子程序都是为提供驾驶控制模拟服务的。
再比如一个进行三角函数计算的子程序,模块中可能含有Sin()、Cos()、Tan()、Arcsin()等全部密切相关的三角函数子程序。如果这些子程序都是标准的三角函数,那么它们无须共享数据,但这些子程序间仍然是有联系的,因此这个模块的内聚性仍然是非常强的。
下面是一个内聚性不好的模块例子,设想一个模块中含有几个子程序为实现一个堆栈:init_stack()、push()和pop();模块中同时还含有格式化报告数据和定义子程序中用到的所有全局数据的子程序。很难看出堆栈与报告子程序或全局数据部分有什么联系,因此模块的内聚性是很差的。这些子程序应该按照模块中心的原则进行重新组织。
在上例中,对模块内聚性的估计是以模块数据和功能为基础进行的。它是在把模块作为一个整体的层次上进行的。因而,模块中的子程序并不会因为模块内聚性好而一定具有良好的内聚性。所以模块中
的每个子程序设计,也要以保证良好内聚性为准则。关于这方面的问题,见5.3节“强内聚性”。
6.1.2  模块耦合
模块与程序其它部分间的耦合标准与子程序间的耦合标准也是类似的。模块应被设计成可以提供一整套功能,以便程序的其它部分与它清楚地相互作用。
在上述的驾驶控制例子中,模块担任了如下功能:SetSpeed()、GetCurrentSettings()、ResumeFormerSpeed()和Deactivate()。这是一套完整的功能,因而程序的其它部分与它的相互作用完全是通过规定的公用接口进行的。
如果模块所提供的功能是不完善的,其它子程序可能被迫对其内部数据进行读写操作。这就打开了黑盒子盖而使其成为透明的了,这实际上破坏了模块化。结构化设计的先驱Larry Constantine指出,模块提供的功能必须是完整的,以便它的调用者们可以各取所需。
hca2
为了设计出强内聚而又松散耦合的模块,必须在设计模块和设计单个子程序的标准之间进行平衡与折衷。降低子程序之间耦合性的重要措施之一,就是尽可能减少使用全局变量。而创建模块的原因之一则是为了让子程序可以共享数据;你若想使同一模块中的子程序不必通过参
数表进行传递,可以采用对其中所有数据进行直接存取来实现。
双电源切换装置从所有模块中的子程序可以对它进行存取的角度来说,模块中数据很像是全局数据。但从不是程序中所有的子程序都可以对它进行存取的角度来说,它又不像是全局数据,它只对模块中的子程序来说,才是可以存取的。因此,在模块设计中的最重要决定之一,便是决定哪个子程序需要对模块中数据进行直接存取。如果某个子程序仅仅是由于可以对模块中数据进行存取的原因才留在模块中的,那么,它应该被从模块中去掉。
6.2  信息隐蔽
如果你阅读了书中所有推荐参阅文献的注释,你就会发现其中有400多个是关于信息隐蔽的。拥有这么多参考文献的内容一定是非常重要的吧?是的,它的确非常重要。
进行信息隐蔽的设计思想贯穿了软件开发的每一个层次,从使用命名的常量而不是使用自由常量到子程序设计、模块设计和整个程序设计。由于这一思想往往是在模块这一层次得到最充分体现的。因此,我们在本章详细讨论它。
信息隐蔽是为数不多的几个在实践中无可辩驳地证明了自己价值的理论之一(Boehm 1987)。研究发现,应用信息隐蔽进行设计的大型程序容易更改指数要比没采用这一技术的高4倍。同时,信息隐蔽也是结构化设计和面向对象设什的基础之一。在结构化设计中,黑盒子思想便来源于信息隐蔽。在面向对象设计中,也是信息隐蔽引发了抽象化和封装化的设计思想。
6.2.1保密
信息隐蔽中的关键概念是“保密”。每一个模块的最大特点都是通过设计和实现,使它对其它模块保密。这个秘密或许是可能被改动的区域、某个文件的格式化、一个数据结构的实现方式、或是一个需要与程序其它部分隔离开来,以便其中的错误产生的危害最小的区域。模块的作用是将自己的信息隐蔽起来以保卫自己的隐私权。信息隐蔽的另一个称谓是“封装”,其意思是一个外表与内容不一样的盒子。
无论管它叫什么,信息隐蔽都是设计子程序和模块的一种方法,它对模块的意义更重要些。当你隐藏秘密时,你就设计了一组存取同一套数据的子程序。对一个系统的改动可能涉及到几个子程序,但是,它只应涉及一个模块。
在设计模块时,一项重要任务就是决定哪些特性应该是对模块外部公开的,哪些应该是作为秘密隐藏起来的,一个模块中可能使用25个子程序而只暴露出其中的5个,其余20个都只在内部使用。模块中也可能用到了几个数据结构,但却把它们全部隐藏起来。它可能会也可能不会提供把数据结构信息通知给程序其余部分的子程序。模块设计的这一方面一般被称作“可见性”,因为它主要涉及了模块的功能特性是否是对外部分开或暴露的。
模块的接口应该尽可能少地暴露它的内部内容。一个模块应该像是一座冰山,你只看到它的一角,而
它其余7/8的部分则藏在水面下。
与设计的其它方面一样,设计模块的接口也是一个逐渐的过程,如果接口在第一次是不正确的,可以再试几次直到它稳定下来;如果它稳定不下来,那么就需要重新设计它。
可以用各种不同的图形来代表模块。模块表示图的关键是,它应该区分开仅供模块内部使用的功能和对外开放的功能。这种图形通常称之为“积木图”,是由Erody Boock在开发Ada
语言过程中提出来的。图6-1表示出了一种模块图。
图6-1一个模块中公用和个别部分
其中公用部分是矩形块,个别部分如黑盒子那样表示。
信息隐蔽不必暗示出一个系统的形状;系统可能具有分层结构,也可能像图6-2中所示那样具有网状结构。
图6-2  网状结构系统
在网状结构中,你只要规定哪些模块可以与其它模块通信,这种特定的通信是如何进行的,然后再进行联接的就可以了,如图6-3所示,积木图也可以用在网状结构中。
图6-3  用包含信息隐蔽思想的符号表示网状系统
6.2.2信息隐蔽举例
几年前我曾写了一个中型系统(有20K行代码),在其中广泛使用了链表结构。问题域是由数据结点构成的,每一个结点又与亚坐标、实坐标和等同点相联接。由于我选用了链表结构,因此在程序中到处都是类似这样的语句:
node =
and
phone = node.data.phone
这些语句直接对链表数据结构进行操作。尽管链表非常自然地将问题进行了模块化,但是这种方法对内存的使用效率却非常低,于是我想使用整型数组索引来代替内存指针,因为这样可以提高内存利用率,并且为在其它区域进行性能优化创造机会。但是,由于刚才提到的那种编码语句充满了程序,因而修改工作非常困难。因为我无法在20000多行代码中把它们一一出来。如果当初我采用了含有如下存取子程序的模块的话,我只要在一个地方即存取子程序中改动代码就可能了。
node = NearestNeighbor(node)
phone = EmergencyContact(node)
我到底赢得了多少内存?我将会赢得或者失去多少速度,我不知道,但是如果当初我隐含了数据结构的细节并且使用了存取子程序,我就可以很容易地到答案。而且,我还可以尝试一下另外几种方法。我本来可以从许多方案中挑选一个最好的,可是,由于我把数据结构的细节暴露给了整个程序,我不得不使用我所厌恶的方案。
除了方便修改,隐含复杂数据结构细节的另一个重要原因是:隐含细节可以澄清你编写某段代码的意图。在上例中,一个富有经验的程序员不难读懂下面这条语句的:node =
显然,这个语句指的是一个链表结构,但除此之外,它什么也不能告诉你。然而,一个像node = NearestNeighbour(node)这样的存取子程序,则清楚描述了链表所代表的内容,因而这是很有用的,并且提醒你应该对node这个名称进行改进(node与其邻居有什么关系),node = 这样的语句与实际相脱离,你根本无法想到应该改进它们的名称以说明实际问题。
隐含数据结构的最后一个原因是出于对可靠性的考虑。如果你用一个专门的子程序来存取数据结构,你只需在其中设置一个安全验证就可以了。否则,你就不得不在所有这个子程序访问变量的地方设置安全验证。比如,如果你使用了链表,并且想读取链表中的下一个元素,并且要注意不超过链表的最后一个元素,你可能用如下的代码:
if  ( <>null )  then
圈套器
node =
如果在某种情形下,为了更谨慎一些,你可以使用如下代码:
if  ( node<>null )  then
激光笔
if ( <>null ) then

本文发布于:2024-09-21 14:36:58,感谢您对本站的认可!

本文链接:https://www.17tex.com/tex/4/158365.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:模块   子程序   进行   数据   设计   可能   使用
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2024 Comsenz Inc.Powered by © 易纺专利技术学习网 豫ICP备2022007602号 豫公网安备41160202000603 站长QQ:729038198 关于我们 投诉建议