• 沒有找到結果。

强内聚性

在文檔中 Code Complete 代码大全 (頁 64-68)

第五章 高质量子程序特点

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(),

CaldoanPayment()和 GetIconlocation()等等。当然,这个评价只有在子程序的名称与其实际内容 相符时才成立。如果它们同时还作其它工作,那么它们的内聚性就要低得多而且命名也不恰当。

顺序内聚性。顺序内聚性是指在子程序内包含需要按特定顺序进行的、逐步分享数据而又 不形成一个完整功能的操作,假设一个子程序包括五个操作:打开文件、读文件、进行两个计 算、输出结果、关闭文件。如果这些操作是由两个子程序完成的,DoStep1()打开文件、读文件 和计算操作,而 DoStep2()则进行输出结果和关闭文件操作。这两个子程序都具有顺序内聚性。

因为用这种方式把操作分隔开来,并没有产生出独立的功能。

但是,如果用一个叫作 GetFileData()的子程序进行打开文件和读文件的操作,那么这个子 程序将具有功能内聚性。当操作来完成一项功能时,它们就可以形成一个具有功能内聚性的子 程序。实际上,如果能用一个很典型的动宾词组来命名一个子程序,那么它往往是功能内聚性,

而不是顺序内聚性。给一个顺序内聚性的子程序命名是非常困难的,于是便产生了像 Dostep1() 这种模棱两可的名字。这往往意味着你需要重新组织和设计子程序,以使它是功能内聚性的。

通讯内聚性。通讯内聚性是在一个子程序中,两个操作只是使用相同数据,而不存在其它 任何联系时产生的。比如,在 GetNameAndChangePhoneNumber()这个子程序中,如果 Name 和 PhoneNumber 是放在同一个用户记录中的,那么这个子程序就是通讯内聚性。这个子程序从事 的是两项而不是一项工作,因此,它不具备功能内聚性。Name 和 PhoneNamber 都存储在用户 记录中,不必按照某一特定顺序来读取它们,所以,它也不具备顺序内聚性。

这个意义上的内聚性还是可以接受的。在实际中,一个系统可能需要在读取一个名字的同 时变更电话号码。一个含有这类子程序的系统可能有些显得别扭,但仍然很清楚且维护性也不 算差,当然从美学角度来说,它与那些只作一项工作的子程序还有一定差距。

临时内聚性。因为同时执行的原因才被放入同一个子程序里,这时产生临时内聚性。典型 的例子有;Startup(),CompleteNewEmployee(),Shutdown()等等,有些程序员认为临时内聚性是 不可接受的,因为它们有时与拙劣的编程联系在一切,比如,在像 Startup()这类子程序中往往含 有东拼西凑的杂烩般的代码。

要避免这个问题,可以把临时内聚性子程序设计成一系列工作的组织者。前述的 Startup ()子程序进行的操作可能包括:读取一个配置文件、初始化一个临时文件、建立内存管理、显示 初始化屏幕。要想使它最有效地完成这些任务,可以让这个子程序去调用其它的专门功能的子 程序,而不是由它自己直接来完成这些任务。

5.3.2 不可取的内聚性

其余类型的内聚性,一般来说都是不可取的。其后果往往是产生一些组织混乱而又难以调 试和改进的代码。如果一个子程序具有不良的内聚性,那最好重新创建一个较好的子程序,而 不要去试图修补它。知道应该避免什么是非常重要的,以下就是一些不可取的内聚性:

过程内聚性。当子程序中的操作是按某一特定顺序进行的,就是过程内聚性。与顺序内聚 性不同,过程内聚性中的顺序操作使用的并不是相同数据。比如,如果用户想按一定的顺序打 印报告,而所拥有的子程序是用于打印销售收入、开支、雇员电话表的。那给这个子程序命名 是非常困难的,而模棱两可的名字往往代表着某种警告。

逻辑内聚性。当一个子程序中同时含有几个操作,而其中一个操作又被传进来的控制标志 所选择时,就产生了逻辑内聚性。之所以称之为逻辑内聚性,是因为这些操作仅仅是因为控制 流,或者说“逻辑”的原因才联系到一起的,它们都被包括在一个很大的 if 或者 case 语句中,

它们之间并没有任何其它逻辑上的联系。

举例来说,一个叫作 InputAll()的子程序,程序的输入内容可能是用户名字、雇员时间卡信 息或者库存数据,至于到底是其中的哪一个,则由传入子程序的控制标志决定。其余类似的子 程序还有 ComputeAll(),EditAll(),PrintAll()等等。这类子程序的主要问题是一定要通过传入一 个控制标志来决定子程序处理的内容。解决的办法是编写三个不同的子程序,每个子程序只进 行其中一个操作。如果这三个子程序中含有公共代码段,那么还应把这段代码放入一个较低层 次的子程序中,以供三个子程序调用。并且,把这三个子程序放入一个模块中。

但是,如果一个逻辑内聚性的子程序代码都是一系列 if 和 case 语句,并且调用其它子程序,

那么这是允许的。在这种情况下,如果程序的唯一功能是调度命令,而它本身并不进行任何处 理,那么这可以说是一个不错的设计。对这种子程序的专业叫法是“事物处理中心”,事物处理 中心往往被用作基础环境下的事件处理,比如,Apple Macintosh 和 Microsoft Windows。

偶然内聚性。当同一个子程序中的操作之间无任何联系时,为偶然内聚性。也叫作“无内 聚性”。本章开始时所举的 Pascal 例程,就是偶然内聚性。

以上这些名称并不重要,要学会其中的思想而不是这些名词。写出功能内聚性的子程序几 乎总是可能的,因此,只要重视功能内聚性以获取最大的好处就可以了。

5.3.3 内聚性举例

以下是几个内聚性的例子,其中既有好的,也有坏的:

功能内聚性例子。比如计算雇员年龄并给出生日的子程序就是功能内聚性的,因为它只完 成一项工作,而且完成得很好。

顺序内聚性的例子。假设有一个按给出的生日计算雇员年龄、退休时间的子程序,如果它 是利用所计算的年龄来确定雇员将要退休的时间,那么它就具有顺序内聚性。而如果它是分别 计算年龄和退休时间的,但使用相同生日数据,那它就只具有通讯内聚性。

确定程序存在哪种不良内聚性,还不如确定如何把它设计得更好重要。怎样使这个子程序 成为功能内聚性呢?可以分别建立两个子程序,一个根据生日计算年龄,另外一个根据生日确 定退休时间,确定退休时间子程序将调用计算年龄的程序,这样,它们就都是功能内聚性的,

而且,其它子程序也可以调用其中任一个子程序,或这两个部调用。

通讯内聚性的例子。比如有一个打印总结报告,并在完成后重新初始化传进来的总结数据 的子程序,这个子程序具有通信内聚性,因为这两个操作仅仅是由于它们使用了相同的数据才 联系在一起。

同前例一样,我们考虑的重点还是如何把它变成是功能内聚性,总结数据应该在产生它的 地方附近被重新初始化,而不应该在打印子程序中重新初始化。把这个子程序分为两个独立的 子程序.第一个打印报告,第二个则在产生或者改动数据的代码附近重新初始化数据。然后,

利用一个较高层次的子程序来代替原来具有通讯相关的子程序,这个子程序将调用前面两个分 出来的子程序。

逻辑内聚性的例子。一个子程序将打印季度开支报告、月份开支报告和日开支报告.具体

打印哪一个,将由传入的控制标志决定,这个子程序具有逻辑内聚性,因为它的内部逻辑是由 输进去的外部控制标志决定的。显然,这个子程序不是按只完成一项工作并把它作好的原则。

怎样使这个子程序变为功能内聚性呢?建立三个子程序:一个打印季度报告,一个打印月 报告、一个打印日报告,改进原来的子程序,让它根据传送去控制标志来调用这三个子程序之 一。调用子程序将只有调用代码而没有自己的计算代码,因而具有功能内聚性。而三个被调用 的子程序也显然是功能内聚性的。非常巧合,这个只负责调用其它子程序的子程序也是一个事

怎样使这个子程序变为功能内聚性呢?建立三个子程序:一个打印季度报告,一个打印月 报告、一个打印日报告,改进原来的子程序,让它根据传送去控制标志来调用这三个子程序之 一。调用子程序将只有调用代码而没有自己的计算代码,因而具有功能内聚性。而三个被调用 的子程序也显然是功能内聚性的。非常巧合,这个只负责调用其它子程序的子程序也是一个事

在文檔中 Code Complete 代码大全 (頁 64-68)