瑞星卡卡安全论坛技术交流区系统软件 【转载】软件开发人员必备工具书 【代码大全】

«23456789»   6  /  16  页   跳转

【转载】软件开发人员必备工具书 【代码大全】

计划开发一个程序族。如果想改进一个程序,最好把将要改动的那部分放进子程序中,将
其独立。这样,就可以改动这个子程序而不致影响程序的其余部分,或者干脆用一个全新的子
程序代替它。几年前,我曾经负责一个替保险推销员编写系列软件的小组,我们不得不根据每
一个推销员的保险率、报价单格式等等来完成一个特定的程序。但这些程序的绝大部分又都是
相同的:输入潜在客户的子程序,客户数据库中存储的信息、查看、计算价格等等。这个小组
对程序进行了模块化,这样,随推销员而变化的部分都放在自己的模块中。最初的程序可能要
用三个月的时间来开发,但是,在此之后,每来一个推销员,我们只改写其中屈指可数的几个
模块就可以了。两三天就可能写完一个要求的程序,这简直是一种享受!
提高部分代码的可读性。把一段代码放入一个精心命名的子程序,是说明其功能的最好办
法。这样就不必阅读这样一段语句:
if ( Node <> NULL )
while ( Node.Next <> NULL ) do
Node = Node.Next
LeafName = Node.Name
else
LeafName = " "
代替它的是:
LeafName = GetleafName(Node)
这个程序是如此简短,它所需要的注释仅仅是一个恰当的名字而已。用一个函数调用来代
替一个有六行的代码段,使得含有这段代码的子程序复杂性大为降低,并且其功能也自动得到
了注释。
提高可移植性。可以使用子程序来把不可移植部分、明确性分析和将来的移植性工作分隔
开来,不可移植的部分包括:非标准语言特性、硬件的依赖性和操作系统的依赖性等。
分隔复杂操作。复杂操作包括:繁杂的算法、通信协议、棘手的布尔测试、对复杂数据的
操作等等。这些操作都很容易引发错误。如果真的有错误,那么如果这个错误是在某个子程序
中,而不是隐藏在整个程序中的话,查找起来要容易得多。这个错误不会影响到其它子程序,
因为为了修正错误只要改动一个子程序就可以了。如果发现了一个更为简单迅速的算法,那么
用它来代替一个被独立在子程序中的算法是非常容易的。在开发阶段,尝试几种方案并选择其
中一个最好的是非常容易的。
独立非标准语言函数的使用。绝大多数实现语言都含有一些非标准的但却方便的扩展。使
用这种扩展的影响是两面性的,因为在另外一个环境下它可能无法使用。这个运行环境的差异
可能是由于硬件不同、语言的生产商不同、或者虽然生产商相同、但版本不同而产生的。如果
使用了某种扩展,可以建立一个作为进入这种扩展大门的子程序。然后,在需要时,可以用订
做的扩展来代替这一非标准扩展。
简化复杂的布尔测试。很少有必要为理解程序流程而去理解复杂的布尔测试。把这种测试
放入函数中可以提高代码的可读性,因为:
(1) 测试的细节已经被隐含了。
(2) 清楚的函数名称已经概括了测试目的。
赋予这种测试一个函数,该函数强调了它的意义,而且这也鼓励了在函数内部增强其可读
gototop
 

性的努力。结果是主程序流和测试本身都显得更加清楚了。
是出于模块化的考虑吗吗?绝不是。有了这么些代码放入子程序的理由,这个理由是不必要
的。事实上,有些工作更适合放在一个大的子程序中完成(关于程序最佳长度的讨论见5.5节“子
程序长度”)。
5.1.1 简单而没有写入子程序的操作
编写子程序的最大心理障碍是不情愿为了一个简单的目的而去编写一个简单的子程序。写
一个只有两或三行代码的子程序看起来是完全没有必要的。但经验表明,小的子程序也同样是
很有帮助的。
小型子程序有许多优点,其中之一是改进了可读性。我曾在程序中采用过如下这样一个仅
有一行的代码段,它在程序中出现了十几次:
Points = DeviceUnits * ( POINTS_PER_INCH / DeviceUnitsPerInch ( ) )
这决不是你所读过的最复杂的一行代码。很多人都明白它是用来转换的。他们也会明白程
序中的每行这个代码都在作同一件事,但是,它还可以变得更清楚些,所以,我创建了一个恰
当命名的子程序来作这些工作。
DeviceUnitsToPoints(DeviceUnits Integer): Integer;
begin
DeviceUnitsToPoints = DeviceUnits *
( POINTS_PER_INCH / DeviceUnitsPerInch ( ) )
end
在用这段子程序来代替那些十几次重复出现的代码行后,这些代码行基本上都成了如下的
样子:
Points = DeviceUnitsToPoints ( DeviceUnits )
这显然更具可读性,甚至已经达到了自我说明的地步。
这个例子还暗示了把简单操作放入函数的另外一个原因:简单操作往往倾向于变成复杂操
作。在写这个子程序时我还不知道这一点,但在某种情况下,当某个设备打开时,
DeviceUnitPerInch()会返回零,这意味着我不得不考虑到被“0”除的情况,这样,又需要另外
的三行代码;
DeviceUnitsToPoints( DeviceUnit:integer):integer;
begin
if( DeviceUnitsPerInch ( ) <> 0 ) then
DeviceUnitsPoints = DeviceUnits *
( POINTS_PER_INCH / DeviceUnitsPerInch ( ) )
else
DeviceUnitsToPoints= 0
end
如果原来的代码行仍然在程序中出现十几次,那么这一测试也要重复十几次,需要新增加
36 行代码。而一个简单子程序轻而易举地便把36 变成了3。
gototop
 

5.1.2  创建子程序的理由总结
以下是创建子程序理由概述:
· 降低复杂性
· 避免重复代码段
· 限制改动带来的影响
· 隐含顺序
· 改进性能
· 进行集中控制
· 隐含数据结构
· 隐含指针操作
· 隐含全局变量
· 促进重新使用代码段
· 计划开发一个软件族
· 改善某一代码段可读性
· 改善可移植性
· 分隔复杂操作
· 独立非标准语言函数的使用
· 简化复杂的布尔测试
5.2    子程序名称恰当
一个恰当的子程序名称应该清晰地描述出于程序所作的每一件事。以下是给子程序有效
命名的指导方针:
对于过程的名字,可以用一个较强的动词带目标的形式。一个带有函数的过程往往是对某
一目标进行操作。名字应该反映出这个过程是干什么的,而对某一目标进行操作则意味着我们
应该使用动宾词组。比如,PrintReport),Checkotderlnfo()等,都是关于过程的比较恰当的名
字。
在面向对象的语言中,不必加上对象名,因为对象本身在被调用时就已经出现了。这时可
求助于诸如RePort.Print(),Orderlnfo.Check()和MonthlyRevenu.Cafe()等名字。而像RePort.
PrintRePort这类名字则是冗余的。
对于函数名字,可以使用返回值的描述。一个函数返回到一个值,函数应该用它所返回的
值命名,例如Cos(),PrinterReady(),CurrentPenColor()等等都是不错的函数名字,因为它精确
地描述了函数将返回什么。
避免无意义或者模棱两可的动词。有些动词很灵活,可以有任何意义,比如
HandleCalculation(),ProcessInput()等于程序名词并没有告诉你它是作什么的。这些名字至多
告诉你,它们正在进行一些与计算或输入等有关的处理。当然,有特定技术情形下使用“handle”
等词是个例外。
有时,子程序的唯一问题就是它的名字太模糊了,而子程序本身的设计可能是很好的。如
gototop
 

果用FormatAndPrintOutput() 来代替HandleOutPut() ,这是一个很不错的名字。
在有些情况下,所用的动词意义模糊是由于子程序本身要做的工作太模糊。子程序存在着
功能不清的缺陷,其名字模糊只不过是个标志而已。如果是这种情况,最好的解决办法是重新
构造这个子程序,弄清它们的功能,从而使它们有一个清楚的、精确描述其功能的名字。
描述子程序所做的一切。在子程序名字中,应描述所有输出结果及其附加结果。如果一个
子程序用于计算报告总数,并设置一个全局变量来表示所有的数据都已准备好了,正等待打印,
那么,ComputeReportTotal()就不是一个充分的名字了。而ComputeReport
TotalAndSetPrintingReadyVar()又是一个太长而且太愚蠢的命名。如果子程序带有附加结果,
那必然会产生许多又长又臭的名字。解决的办法不应该是使用描述不足名字,而是采用直接实
现每件事的原则来编程,从而避免程序带有附加结果。
名字的长度要符合需要。研究表明,变量名称的最佳长度是9到15个字母,子程序往往比
变量要复杂,因而其名字也要长些。南安普敦大学的MichaelRees 认为恰当的长度是20 到35
个字母。但是,一般来说15 到20 个字母可能更现实一些,不过有些名称可能有时要比它长。
建立用于通用操作的约定。在某些系统中,区分各种不同的操作是非常重要的。而命名约
定可能是区分这些操作最简单也是最可靠的方法。比如,在开发0S/2 显示管理程序时,如果子
程序是关于直接输入的,就在其名称前面加一个“Get”前缀,如果是非直接输入的则加“Query”
前缀,这样,返回当前输入字符的GetlnputChar()将清除输入缓冲区.而同样是返回当前输入
字符的QuerylnPutChar()则不清除缓冲区。
5.3  强内聚性
内聚性指的是在一个子程序中,各种操作之间互相联系的紧密程度。有些程序员喜欢用“强
度”一词来代替内聚性,在一个子程序中各种操作之间的联系程度有多强?一个诸如Sin()之类
的函数内聚性是很强的,因为整个子程序所从事的工作都是围绕一个函数的。而像SinAndTan()
的内聚程度就要低得多了,因为子程序中所进行的是一项以上的工作。强调强相关性的目的是,
每一个子程序只需作好一项工作,而不必过分考虑其它任务。
这样作的好处是可以提高可靠性。通过对450个Fortran 子程序的调查表明,50%的强内聚
性子程序是没有错误的,而只有18%的弱内聚性子程序才是无错的(Card,carch和Agresti 1986)。
另一项对另外450 个子程序的调查则表明,弱内聚性子程序的出错机会要比强内聚性出错机会
高6 倍,而修正成本则要高19 倍(Selby和Basili 1991)。
关于内聚性的讨论一般是指几个层次。理解概念要比单纯记住名词重要得多。可以利用这
些概念来生成内聚性尽可能强的子程序。
5.3.1  可取的内聚性
内聚性的想法是由wayne stevens,Glenford Myers和Larry Constantine 等人在1974年发表
的一篇论文中提出来的,从那以后,这个想法的某些部分又逐渐得到了完善。以下是一些通常
认为是可以接受的一些内聚类型:
功能内聚性。功能内聚性是最强也是最好的一种内聚,当程序执行一项并且仅仅是一项工
作时,就是这种内聚性,这种内聚性的例子有: sin(), GetCustomerName(), EraseFile(),
gototop
 

CaldoanPayment()和GetIconlocation()等等。当然,这个评价只有在子程序的名称与其实际内容
相符时才成立。如果它们同时还作其它工作,那么它们的内聚性就要低得多而且命名也不恰当。
顺序内聚性。顺序内聚性是指在子程序内包含需要按特定顺序进行的、逐步分享数据而又
不形成一个完整功能的操作,假设一个子程序包括五个操作:打开文件、读文件、进行两个计
算、输出结果、关闭文件。如果这些操作是由两个子程序完成的,DoStep1()打开文件、读文件
和计算操作,而DoStep2()则进行输出结果和关闭文件操作。这两个子程序都具有顺序内聚性。
因为用这种方式把操作分隔开来,并没有产生出独立的功能。
但是,如果用一个叫作GetFileData()的子程序进行打开文件和读文件的操作,那么这个子
程序将具有功能内聚性。当操作来完成一项功能时,它们就可以形成一个具有功能内聚性的子
程序。实际上,如果能用一个很典型的动宾词组来命名一个子程序,那么它往往是功能内聚性,
而不是顺序内聚性。给一个顺序内聚性的子程序命名是非常困难的,于是便产生了像Dostep1()
这种模棱两可的名字。这往往意味着你需要重新组织和设计子程序,以使它是功能内聚性的。
通讯内聚性。通讯内聚性是在一个子程序中,两个操作只是使用相同数据,而不存在其它
任何联系时产生的。比如,在GetNameAndChangePhoneNumber()这个子程序中,如果Name 和
PhoneNumber 是放在同一个用户记录中的,那么这个子程序就是通讯内聚性。这个子程序从事
的是两项而不是一项工作,因此,它不具备功能内聚性。Name 和PhoneNamber 都存储在用户
记录中,不必按照某一特定顺序来读取它们,所以,它也不具备顺序内聚性。
这个意义上的内聚性还是可以接受的。在实际中,一个系统可能需要在读取一个名字的同
时变更电话号码。一个含有这类子程序的系统可能有些显得别扭,但仍然很清楚且维护性也不
算差,当然从美学角度来说,它与那些只作一项工作的子程序还有一定差距。
临时内聚性。因为同时执行的原因才被放入同一个子程序里,这时产生临时内聚性。典型
的例子有;Startup(),CompleteNewEmployee(),Shutdown()等等,有些程序员认为临时内聚性是
不可接受的,因为它们有时与拙劣的编程联系在一切,比如,在像Startup()这类子程序中往往含
有东拼西凑的杂烩般的代码。
要避免这个问题,可以把临时内聚性子程序设计成一系列工作的组织者。前述的Startup
()子程序进行的操作可能包括:读取一个配置文件、初始化一个临时文件、建立内存管理、显示
初始化屏幕。要想使它最有效地完成这些任务,可以让这个子程序去调用其它的专门功能的子
程序,而不是由它自己直接来完成这些任务。
5.3.2  不可取的内聚性
其余类型的内聚性,一般来说都是不可取的。其后果往往是产生一些组织混乱而又难以调
试和改进的代码。如果一个子程序具有不良的内聚性,那最好重新创建一个较好的子程序,而
不要去试图修补它。知道应该避免什么是非常重要的,以下就是一些不可取的内聚性:
过程内聚性。当子程序中的操作是按某一特定顺序进行的,就是过程内聚性。与顺序内聚
性不同,过程内聚性中的顺序操作使用的并不是相同数据。比如,如果用户想按一定的顺序打
印报告,而所拥有的子程序是用于打印销售收入、开支、雇员电话表的。那给这个子程序命名
是非常困难的,而模棱两可的名字往往代表着某种警告。
gototop
 

逻辑内聚性。当一个子程序中同时含有几个操作,而其中一个操作又被传进来的控制标志
所选择时,就产生了逻辑内聚性。之所以称之为逻辑内聚性,是因为这些操作仅仅是因为控制
流,或者说“逻辑”的原因才联系到一起的,它们都被包括在一个很大的if或者case语句中,
它们之间并没有任何其它逻辑上的联系。
举例来说,一个叫作InputAll()的子程序,程序的输入内容可能是用户名字、雇员时间卡信
息或者库存数据,至于到底是其中的哪一个,则由传入子程序的控制标志决定。其余类似的子
程序还有ComputeAll(),EditAll(),PrintAll()等等。这类子程序的主要问题是一定要通过传入一
个控制标志来决定子程序处理的内容。解决的办法是编写三个不同的子程序,每个子程序只进
行其中一个操作。如果这三个子程序中含有公共代码段,那么还应把这段代码放入一个较低层
次的子程序中,以供三个子程序调用。并且,把这三个子程序放入一个模块中。
但是,如果一个逻辑内聚性的子程序代码都是一系列if和case语句,并且调用其它子程序,
那么这是允许的。在这种情况下,如果程序的唯一功能是调度命令,而它本身并不进行任何处
理,那么这可以说是一个不错的设计。对这种子程序的专业叫法是“事物处理中心”,事物处理
中心往往被用作基础环境下的事件处理,比如,Apple Macintosh和Microsoft Windows。
偶然内聚性。当同一个子程序中的操作之间无任何联系时,为偶然内聚性。也叫作“无内
聚性”。本章开始时所举的Pascal例程,就是偶然内聚性。
以上这些名称并不重要,要学会其中的思想而不是这些名词。写出功能内聚性的子程序几
乎总是可能的,因此,只要重视功能内聚性以获取最大的好处就可以了。
5.3.3 内聚性举例
以下是几个内聚性的例子,其中既有好的,也有坏的:
功能内聚性例子。比如计算雇员年龄并给出生日的子程序就是功能内聚性的,因为它只完
成一项工作,而且完成得很好。
顺序内聚性的例子。假设有一个按给出的生日计算雇员年龄、退休时间的子程序,如果它
是利用所计算的年龄来确定雇员将要退休的时间,那么它就具有顺序内聚性。而如果它是分别
计算年龄和退休时间的,但使用相同生日数据,那它就只具有通讯内聚性。
确定程序存在哪种不良内聚性,还不如确定如何把它设计得更好重要。怎样使这个子程序
成为功能内聚性呢?可以分别建立两个子程序,一个根据生日计算年龄,另外一个根据生日确
定退休时间,确定退休时间子程序将调用计算年龄的程序,这样,它们就都是功能内聚性的,
而且,其它子程序也可以调用其中任一个子程序,或这两个部调用。
通讯内聚性的例子。比如有一个打印总结报告,并在完成后重新初始化传进来的总结数据
的子程序,这个子程序具有通信内聚性,因为这两个操作仅仅是由于它们使用了相同的数据才
联系在一起。
同前例一样,我们考虑的重点还是如何把它变成是功能内聚性,总结数据应该在产生它的
地方附近被重新初始化,而不应该在打印子程序中重新初始化。把这个子程序分为两个独立的
子程序.第一个打印报告,第二个则在产生或者改动数据的代码附近重新初始化数据。然后,
利用一个较高层次的子程序来代替原来具有通讯相关的子程序,这个子程序将调用前面两个分
出来的子程序。
逻辑内聚性的例子。一个子程序将打印季度开支报告、月份开支报告和日开支报告.具体
gototop
 

打印哪一个,将由传入的控制标志决定,这个子程序具有逻辑内聚性,因为它的内部逻辑是由
输进去的外部控制标志决定的。显然,这个子程序不是按只完成一项工作并把它作好的原则。
怎样使这个子程序变为功能内聚性呢?建立三个子程序:一个打印季度报告,一个打印月
报告、一个打印日报告,改进原来的子程序,让它根据传送去控制标志来调用这三个子程序之
一。调用子程序将只有调用代码而没有自己的计算代码,因而具有功能内聚性。而三个被调用
的手程序也显然是功能内聚性的。非常巧合,这个只负责调用其它子程序的子程序也是一个事
务处理中心。最好用如DispatchReporPrinting()之类带有“调度”或“控制”等字眼的词来给事
务处理中心命名,以表示它只负责命令温调度,而本身并不做任何工作。
逻辑内聚性的另一个例子。考虑一个负责打印开支报表、输入新雇员名字并备份数据库的
子程序,其具体执行内容将由传入的控制标志控制。这个子程序只具有逻辑内聚性,虽然这个
关联看起来是不合逻辑的。
要想使它成为功能内聚性,只要按功能把它分成几部分就可以了。不过,这些操作有些过
于凌乱。因此,最好重新建立一个调用各子程序的代码。当拥有几个需要调用的子程序时,重
新组织调用代码是比较容易的。
过程内聚性的例子。假设有一个子程序,它产生读取雇员的名字,然后是地址,最后是它
的电话号码。这种顺序之所以重要,仅仅是因为它符合用户的要求,用户希望按这种顺序进行
屏幕输入。另外一个子程序将读取关于雇员的其它信息。这个子程序是过程内聚性,因为是由
一个特定顺序而不是其它任何原因,把这些操作组合在一起的。
与以前一样,如何把它变为功能内聚性的答案仍然是把它分为几个部分,并把这几部分分
别放入程序中。要保证调用子程序的功能是单一、完善的。调用子程序应该是诸如
GetEmployeeData()这样的子程序,而不该是像GetFirstPartofEmployeeData()这类的子程序。可能
还要改动其余读取数据的子程序。为得到功能内聚性,改动几个子程序是很正常的。
同时具有功能和临时内聚性的程序。考虑一个具有完成一项事物处理所有过程的子程序,
从用户那里读取确认信息,向数据存入一个记录,清除数据域,并给计数器加1。这个程序是
功能内聚性的,因为它只从事一项工作,进行事物处理,但是,更确切地说,这个子程序同时
也是临时内聚性的,不过当一个子程序具有两种以上内聚性时,一般只考虑最强的内聚性。
这个例子提出了如何用一个名字恰如其分地抽象描述出程序内容的问题。比如可以称这
个子程序为ConfirmEntryAndAdjustData(),表示这个干程序仅具有偶然内聚性。而如果称它为
CompleteTransaction(),那么就可能清楚地表示出这个子程序仅具有一个功能,而已具有功能内
聚性。
过程性、临时或者可能的逻辑内聚性。比如一个进行某种复杂计算前5个操作,并把中间
结果返回到调用子程序。由于5 项操作可能要用好几个小时,因此当系统瘫痪时,这个子程序
将把中间结果存入一个文件中,然后,这个子程序检查磁盘,以确定其是否有足够空间来存储
最后计算结果,并把磁盘状态和中间结果返回到调用程序。
这个子程序很可能是过程内聚性的,但你也可能认为它具有临时内聚性,甚至具有逻辑内
聚性。不过,不要忘了问题的关键不是争论它具有哪种不好的内聚性,而是如何改善其内聚性。
原来的子程序是由一系列令人莫名其妙的操作组成的,与功能内聚性相距甚远,首先,调
用子程序不应该调用一个,而应该调用几个独立的子程序:l)进行前5步计算的子程序;2)把
中间结果存入一个文件;3)确定可用的磁盘存储空间。如果调用子程序被称作
gototop
 

ComputeExtravagantNumber(),那么它不应该把中间结果写入一个文件,也决不该为后来的操作
检查磁盘剩余空间,它所作的就仅限于计算一些数而已。对这个子程序的精心重新设计,将至
少影响到一至两个层次上的子程序,对于这顶任务的较好设计,见图5-l。
图5-1 进行任务分解以获得功能内聚性举例
图中画阴影的部分是由原来的子程序从事的工作,在新组织结构中它们位于不同的层次上,
这就是为什么为了把这些工作放人恰当的子程序中,要进行这么多重新组织工作的原因。用几
个功能内聚性的子程序来代替一个具有不良内聚性的子程序是很平常的。
5.4  松散耦合性
所谓耦合性指的是两个子程序之间联系的紧密程度。耦合性与内聚性是不同的。内聚性
是指一个子程序的内部各部分之间的联系程度,而耦合指的是子程序之间的联系程度。研究它
们的目的是建立具有内部整体性(强内聚性),而同时与其它子程序之间的联系的直接、可见、
松散和灵活的子程序(松散耦合)。
子程序之间具有良好耦合的特点是它们之间的耦合是非常松散的,任一个子程序都能很容
易地被其它子程序调用。火车车箱之间的联接是非常容易的,只要把两节车箱推撞到一起,挂
钩就会目前挂上,想象一下,用螺栓把它们固定到一起,或者只有特定的车厢之间才能联接到
一起,那么事情将会有多么麻烦。火车车厢之间的联接之所以非常容易,是因为它们的联接装
置非常简单。同样,在软件中,也应该使子程序之间的耦合尽量简单。
在建立一个子程序时,应尽量避免它对其它子程序有依赖性,应该使它们像商业上的伙伴
一样相互分离,而不要使它们像连体婴儿一样密不可分。类似Sin()的函数是松散耦合的,因为
它所需要的只是一个传递进去的角度值。而类似InitVars(varl,varZ,var3,……,varN)的函
数则是紧密耦会的,因为调用程序事实上知道函数内部做什么。依靠使用同一全局变量联系在
一起的子程序之间,其耦合程度则更高。
5.4.1 耦合标准
以下是几条估计子程序间耦合程度的标准:
耦合规模。所谓耦合规模是指两个子程序之间联系的数量多少。对于耦合来说,联系的数
gototop
 

量越少越好,因为一个于程序的接口越少,那么在把它与其它子程序相连接时,所要做的工作
也越少。一个只有一个参数的子程序与调用它的程序间的耦合程序,要比有6 个参数的子程序
与调用它的程序间的耦合程度松散得多。一个使用整型参数的子程序与其调用程序之间的耦合
程度,也要比一个使用有10 个元素数组或者结构化数据的子程序与其调用程序之间的耦合程度
松散得多。同样,使用一个全局变量的子程序与使用十二个全局变量的子程序相比,其耦合程
度也要松散得多。
密切性。密切性指的是两个子程序之间联系的直接程度。联系越直接越好,两个子程序之
间联系最密切的是参数表中的参数。这时,两个程序直接通讯。这时这个参数就像接吻时的嘴
唇。联系密切程度稍低的是作用于同一全局数据的两个子程序。它们之间交流的直接性稍低。
全局变量就像是两个子程序之间的爱情,它可能消失在信中,也可能到你想要它到的地方。联
系程度最低的是作用于同一数据库记录或文件的两个子程序,它们都需要这个数据但却又羞于
通知对方,这个被分享的数据就像是在课堂上传阅着的一张写有“你喜欢我吗?请回答‘是’
还是‘不是’”的纸条。
可见性。可见性是指两个子程序之间联系的显著程度。编程不像是在中央情报局中工作,
不会因为行动隐蔽而受到表彰,它更像是作广告,干得越是大张旗鼓,受到的表彰也就越多。
在参数表中传递数据是明显的,因而也是好的。而通过改动全局数据以便让别的子程序来使用
它,则是一个隐蔽的联系因而也是不好的。对全局数据联系进行注释以使其更明显,可能稍好
些。
灵活性。灵活性是指改变两个子程序之间联系的容易程度。形象地说,你更喜欢电话上的
快速插口装置,而不会喜欢用电烙铁把铜线焊到一起,灵活性可能有一部分是由其它耦合特性
决定的,但也有一些区别。比如,有一个按照给定的被雇用日期和被雇用部门,寻找雇员的第
一个监工的子程序,并命名它为LookUpFirstsupervisor()。同时,还有一个对变量EmpRec 进行
结构化的子程序,变量EmpRec 包括雇用日期、雇用部门等信息,第二个子程序把这个变量传
给第一个子程序。
从其它观点来看,两个子程序之间的耦合是非常松散的。因为处于第一个和第二个子程序
之间的变量EmpRec 是公共的,所以它们之间只有一个联系。现在,假设需要用第三个子程序
来调用子程序LookUpFirstSupervisor(),但这个子程序中不含EmpRec,却含有雇用部门和雇用
日期信息。这时LookUpFirstSupervisor()就不是那么友好了,它不情愿与第三个子程序进行合作。
对于调用LookUpFirstsupervisor()的子程序来说,它必须知道EmpRec 的数据结构。它可以
使用一个仅有两个域的变元EmpRec,但这又需要知道LookUpFirstSupervisor()内部结构,即那
些仅供它使用的域,这是一个愚蠢的解决方法。第二个方案是改动LookUpFirstSupervisor,使它
自带雇用部门和雇用日期信息,而不是使用EmpRec。不管采用哪种方案,这个子程序都不像最
初看起来那么灵活了。
如果愿意的话,一个不友好的子程序也是可以变为友好的。这种情况可以通过让它明确带
有雇用部门和日期信息,而不再使用EmpRec来达到这一目的。
简而言之,如果一个子程序越容易被其它子程序调用,那么它的耦合程度也就越低。这样
的好处是可以增强灵活性和维护性。在建立系统结构时,应该沿着相互耦合程度的最低线将其
分开。如果把程序看成一块木头的话,就是要沿着它的纹理把它劈开。
gototop
 

5.4.2  耦合层次
传统上,把耦合层次称为非直觉性(unintuitive)。所以,在以下叙述中,将交替使用这两
个名字。在以下叙述中,既有好的耦合,也有不好的耦合。
简单数据耦合。如果两个子程序之间传递的数据是非结构化的,并且全部都是通过参数表
进行的,这通常称作“正常耦合”,这也是一种最好的耦合。
数据结构耦合。如果在两个程序之间传递的数据是结构化的,并且是通过参数表实现传递
的,它们之间就是数据结构耦合的。这种耦合有时也称之为“邮票耦合”(stamp coupling)(不
过我总觉得这种叫法非常奇怪)。如果使用恰当的话,这种耦合也不错,它与简单耦合的主要区
别是它所采用的数据是结构化的。
控制耦合。如果一个子程序通过传入另一个子程序的数据通知它该作什么,那么这两个子
程序就是控制耦合的。控制耦合是令人不快的,因为它往往与逻辑内聚性联在一起,并且,通
常都要求调用程序了解被调子程序的内容与结构。
全局数据耦合。如果两个子程序使用同一个全局数据,那它就是全局数据耦合的。这也就
是通常所说的“公共耦合”或“全局耦合”。如果所使用的数据是只读的,那么这种耦合还是可
以忍受的,但是,总的来说,全局耦合是不受欢迎的,因为这时子程序之间的联系既不密切,
又不可见。这种联系容易被疏漏掉,甚至可以认为它是一种由信息隐含带来的错误——信息丢
失。
不合理耦合( pathological)。如果一个子程序使用了另外一个子程序中代码,或者它改变了
其中的局部变量,那么它们就是不合理耦合的。这种耦合也称之为“内容耦合”。这一类耦合是
不能接受的,因为它不满足关于耦合规模、密切性、可见性和灵活性中的任何一条标准。虽然
这是一个很紧的联系,但是这种联系却是不密切的。改动另一个子程序中的数据无异于在其后
背桶上一刀,而且,这背后一刀从表面上又是看不出来的。由于它是建立在一个子程序完全了
解另一个程序内容的基础之上的,因此其灵活性也是很差的。许多结构化语言中,都有明确禁
止不合理耦合的语法规则。但是,在Basic或汇编语言中,它却是允许的。因此,在用这种语言
编程时,一定要小心!
以上所有类型的耦合,如图5-2 所示。
5.4.3  耦合举例
以下是上述各种耦合的例子,其中有好的,也有坏的:
简单数据耦合的例子。比如,一个程序向Tan()子程序传递含有角度值的变量,那它们之
间就是简单数据耦合的。
简单数据耦合的另一个例子。两个程序向另一个子程序传递姓名、住址、电话号码、生日
和身份证号码等五个变量。
可接受的数据结构耦合的例子。一个程序向另一个子程序传递变量EmpRec,EmpRec是一
个结构化的变量,包括姓名、住址、生日等等五个方面的数据,而被调用的子干程序则全部使
用这五个域。
不可取的数据结构耦合举例。一个程序向另一个子程序传递同样的变量EmpRec,但是,如
果被调用的子程序只使用其中两个域,比如电话号码和生日。这虽然还是数据结构耦合,但却
不是个很好的应用,如果把生日和电话号码作为简单变量来传递的话,将使联系更加灵活,
gototop
 
«23456789»   6  /  16  页   跳转
页面顶部
Powered by Discuz!NT