30.编写内聚的代码

“你要编写一些新的代码,首先要决定的就是把这些代码放在什么地方。其实放在什么地方问题不大,你就赶紧开始吧,看看IDE中现在打开的是哪个类,直接加进去就是了。如果所有的代码都在一个类或组件里面,要找起来是很方便的。”

内聚性用来评估一个组件(包、模块或配件)中成员的功能相关性。内聚程度高,表明各个成员共同完成了一个功能特性或是一组功能特性。内聚程度低的话,表明各个成员提供的功能是互不相干的。

假定把所有的衣服都扔到一个抽屉里面。当需要找一双袜子的时候,要翻遍里面所有的衣服——裤子、内衣、T恤等——才能找到。这很麻烦,特别是在赶时间的时候。现在,假定把所有的袜子都放在一个抽屉里面(而且是成双放置的),全部的T恤放在另外一个抽屉中,其他衣服也分门别类。要找到一双袜子,只要打开正确的抽屉就可以了。

广告:个人专属 VPN,独立 IP,流量大,速度快,连接稳定,多机房切换,每月最低仅 5 美元

与此类似,如何组织一个组件中的代码,会对开发人员的生产力和全部代码的可维护性产生重要影响。在决定创建一个类的时候,问问自己,这个类的功能是不是与组件中其他某个类的功能类似,而且功能紧密相关。这就是组件级的内聚性。

类也要遵循内聚性。如果一个类的方法和属性共同完成了一个功能(或是一系列紧密相关的功能),这个类就是内聚的。

看看Charles Hess先生于1866年申请的专利,“可变换的钢琴、睡椅和五斗柜”(见图6-2)。

根据他的专利说明,他提供了“……附加的睡椅和五斗柜……以填满钢琴下未被使用的空间……”。接下来他说明了为什么要发明这个可变换的钢琴。读者可能已经见过类似这种发明的项目代码结构了,而且也许其中有你的份。这个发明不具备任何内聚性,任何一个人都可以想象得到,要维护这个怪物(比如换垫子、调钢琴等)会是多么困难。

看看最近的例子。Venkat曾经见过一个用ASP开发的、有20个页面的Web应用。每个页面都以HTML开头,并包含大量VBScript脚本,其中还 了访问数据库的SQL语句。客户当然会认为这个应用的开发已经失去了控制,并且无法维护。如果每个页面都包括展示逻辑、业务逻辑和访问数据看代码,就有太多的东西都堆在一个地方了。

假定要对数据库的表结构进行一次微调。这个微小的变化会导致应用中所有的页面发生变化,而且每个页面中都会有多处改变——这个应用很快就变成了一场灾难。

如果应用使用了中间层对象(比如一个COM组件)来访问数据库,数据库表结构更所造成的影响就可以控制在一定的范围之内,代码也更容易维护。

低内聚性的代码会造成很严重的后果。假设有这样一个类,实现了物种完全不想干的功能。如果这5个功能的需求或希捷发生了变化,这个类也必须跟着改变。如果一个(或者一个组件)变化的过于频繁,这样的改变会对整个系统形成“涟漪效应”,并导致更多的维护和成本的发生。考虑另一个只实现了一种功能的类,这个类变化的频度就没有那么高。类似的,一个更具内聚性的组件不会有太多导致其变化的原因,也因此而更加稳定。根据单一指责原则(查看《敏捷软件开发:原则、模式与实践》[Mar02]),一个模块应该只有一个发生变化的原因。

一些设计技巧可以起到帮助作用。举例来说,我们常常使用模型-视图-控制器(MVC)模式来分离展示层逻辑、控制器和模型。这个模式非常有效,因为它可以让开发人员获得更高的内聚性。模型中的类包含一种功能,在控制器中的类包含另外的功能,而驶入中的类则只关心UI。

内聚性会影响一个组件的可重用性。组件粒度是在设计时要考虑的一个重要因素。根据重用发布等价原则([Mar02]):重用的粒度于发布的粒度相同。这就是说,程序库用户所需要的,是完整的程序库,而不是其中的一部分。如果不能遵循这个原则,组件用户就会被强迫只能使用所发布组件的一部分。很不幸的是,他们仍然会被不关心的那一部分的更新所影响。软件包越大,可重用性就越差。

让类的功能尽量集中,让组件尽量小。要避免创建很大的类或组件,也不要创建无所不包的大杂烩类。

切身感受

感觉类和组件的功能都很集中:每个类或组件只做一件事,而且做得很好。Bug很容易跟踪,代码也易于修改,因为类和组件的责任都很清晰。

平衡的艺术

□ 有可能会把一些东西拆分成很多微小的部分,而使其失去了实用价值。当你需要一只袜子的时候,一盒棉线不能带给你任何帮助。

□ 具有良好内聚性的代码,可能会根据需求的变化,而成比例地进行变更。考虑一下,实现一个简单的功能变化需要变更多少代码。