高质量的子程序—高内聚

      对于子程序而言,内聚性是指子程序中各种操作之间联系的紧密程序。什么样才是高质量的子程序,其中一点就是高内聚性,其目标是让每一个子程序只把一件事做好,不再做任何其他事情。

     关于内聚的讨论一般会涉及到内聚性的几个层次。理解一些概念要比记住一些特定的术语更重要。这些概念可以帮助你思考如何让子程序尽可能地内聚。

 

      功能性内聚 是最强也是最好的一种内聚性,也就是说让一个子程序仅执行一项操作。例如:getCustomerName()、EraseFile()、sin()以及ageFromBirthdate()这样的子程序都是高度内聚的。当然,以这种方式来评估内聚性,前提是子程序所有执行的操作与名字相符——如果它还做了其他的操作,那么它就不够内聚,同时其命名也有问题。

 

     除此之外,还有其他一些种类的内聚性人们却通常认为是不够理想的。

     顺序内聚 是指在子程序内包含有需要按特定的顺序执行的操作,这些步骤需要共享数据,而且只有在全部执行完毕后才完成了一项完整的功能。

     举一个顺序上的内聚性的例子,假设某个子程序需要按照给定出生日期来计算出员工的年龄和退休时间。如果子程序先计算员工的年龄,再根据他的年龄来计算退休时间,那么它就具有顺序的内聚性。而如果子程序先计算员工的年龄,然后再重新计算他的退休时间,两次计算只是碰巧使用了相同的出生日期,那么这个子程序就只具有通信上的内聚性。

     那么该怎样设计具有功能上的内聚性的子程序呢?你可以创建两个不同的子程序,它们能根据给定的生日分别计算员工的年龄和退休时间。其中,计算退休时间的子程序可以调用计算年龄的子程序。这样两者就都具有功能上的内聚性了。而其他的子程序则可以调用二者之一或全部。

     通信内聚 是指一个子程序中不同的操作使用同样的数据,但不存在其他任何联系。例如某个子程序先根据传给它的汇总数据打印一份汇总报表,然后再把这些汇总数据重新初始化,那么这个子程序就具有通信上的内聚性:因为这两项操作只是因为使用了相同的数据才彼此产生联系。

     要改善这个子程序的内聚性,应该让重新初始化汇总数据的操作尽可能靠近创建汇总数据的地方,而不是放在打印报表的子程序里。应该把这些子程序进一步拆分成几个独立的子程序:一个负责打印报表,一个负责在靠近创建或修改数据的代码的地方重新初始化数据。然后在原本调用那个具有通信内聚性的子程序的更高的子程序中调用这两个子程序。

     临时内聚 是指含有一些因为需要同时执行才放到一起的操作的子程序。典型的例子有:startUp()、completeNetEmployee()、shutdown()等。有些人认为临时内聚性是不可取的,因为它们有时与不良的编程实践相关——比如说在startUp()子程序里塞进一大堆互不相关的代码等。

     为避免这个问题,可以把临时性的子程序看做一系列事件的组织者。前面提到的startUp()子程序可能需要读取配置文件、初始化临时文件、创建表结构,再启动画面。要想使它最有效,应该让原来那具有临时内聚性的子程序去调用其他的子程序,由这些子程序来完成特定的操作,而不是由它直接执行所有的操作。

     这个例子提出这样一个问题,即如何选择一个能够恰当的抽象层次上描述子程序的名字。你可能决定把一个子程序命名为readConfigFileInitScratchFileEct(),它可以暗示该子程序只是巧合的内聚性。而如果你把命名为startUp(),那么很明显,这个子程序就只具有一个功能,且具有功能上的内聚性。

     一般来说,其他类型的内聚性都是不可取的。它们都会导致代码组织混乱、难于调查试、不便修改。如果一个子程序具有不良的内聚性,那最好还是花功夫重新编写,使其具有更好的内聚性,而不是再花精力精确地诊断问题所在了。因此,知道应该避免什么是非常有用的,下面就给出一些不可取的内聚性。

     过程内聚 是指一个子程序中的操作是按特定的顺序进行的。一个例子是依次获取员工的姓名、住址和电话号码的子程序。这些操作执行的顺序之所以重要,只是因为它和用户屏幕提示而输入数据的顺序想一致。另一个子程序用来获取员工的其他数据。这段程序也具有过程上的内聚性,因为它把一组操作赋以特定的顺序,而这些操作并不需要为了除此这外的任何原因而彼此关联。

     为了得到更好的内聚性,可以把不同的操作纳入各自的子程序中。让调用方的子程序具有单一而完整的功能:getEmployee()就比getFirstPartOfEmployeeData()更为可取。你可能还需要修改用来读取其余的子程序。为了让所有的子程序都具有功能上的内聚性,对两个或更多的原有子程序进行修改是很常用见的。

     逻辑内聚 是指若干操作被放入同一个子程序中,通过传入的控制标志选择执行其中的一项操作。之所以称之为逻辑上的内聚性,是因为子程序的控制流或所谓“逻辑”是将这些操作放到一起的唯一原因——它们都被包在一个很大的if语句或case语句中,而不是因为各项操作之间有任何逻辑关联。认为是逻辑上的内聚性的标志性属性就是各项操作之间的关联,因此,似乎更应该称其为“缺乏逻辑的内聚性”。

     这方面的一个例子是名为inputAll()子程序,它根据传入的控制标志决定是输入客户姓名、住址还是其他数据。类似例子还有computeAll()、editAll()、printAll()和saveAll()。这种子程序的主要问题是你不该通过传入控制标志来控制另一个子程序的处理方式。相比之下,让三个程序分别完成不同的操作,要比用一个“根据传入的控制标志选择三项不同的操作之一”的子程序要清晰得多。如果操作中含有一些相同代码或共用了数据,那么应该把那些代码移入一个低层子程序中,这些子程序也应该包裹在一个类中。

     如果子程序里的代码仅由一系列的if语句或者case语句,以及调用其他子程序的语句组成,那么创建这样一个具有逻辑内聚的子程序通过也是可以的。在这种情况下,如果子程序唯一的功能就是发布各种命令,其自身并不做任何处理,这通常也是不错的设计。这类子程序的技术术语便是“事件处理器”。

    巧合内聚 是指子程序中的各种操作之间没有任何可以看到的关联。它也可称为“无内聚”或“混乱内聚”。很难从巧合内聚转变成任何一类更好的内聚——通常你需要深入地重新设计和重新实现。

     以上,感谢《代码大全》作者对其精辟的解析。使我顿时感悟,不再停留在对概念的理解上,以一种更顺畅的思维去分析问题,设计结构。从上面的分析,我时刻很强烈感觉重构的影子。从头到尾,重构的思想一直在脑中不停的转动,例如:提炼方法、搬移方法、引入外加函数、重命名方法都是上面提及的一些思想。

    作者最后一句话说道:编写功能内聚的子程序几乎总是可能的,因此把注意集中于功能内聚,从而得到最大的收获。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值