高内聚与低耦合
高内聚与低耦合
WHAT
"高内聚"与"低耦合"是软件设计和开发中经常出现的一对概念。它们既是做好设计的途径,也是评价设计好坏的标准。
"高内聚"是说,一个业务应当尽量把相关的功能和代码放到一个模块中。
"低耦合"则是说,一个业务应当尽量减少对其它业务或功能模块的依赖。
高内聚和低耦合这一对概念其实不仅适用于软件行业。
如果你是宅男/宅女,高内聚就是吃喝拉撒睡都不用出门,低耦合就是不用跟人打交道。
如果你是上班族,高内聚就是工作都由自己说了算,低耦合就是跨组/跨部门的沟通协作少。
如果你常跟政府部门打交道,一定还记得以往去政府办一个证明要跑八九个部门、盖十几个章的麻烦,这其实就是政府服务"低内聚"的恶果——后来政府精简办事流程,一个窗口盖完所有章,这就是一种"高内聚"的服务。
如果你是苹果的重度用户,一定对"封闭生态环境"体会深刻:它固然能提供很好的服务,但是深陷其中之后——iPhone、iPad、macbook、iCloud等——那么,即使其中某项服务无以为继了、或者满足不了需求了,也无法轻易地脱身而出、改换门庭。这就是"高耦合"带来的难题。
WHY & WHY NOT
高内聚低耦合的优点和缺点都是显而易见的。
优点
如果一个模块、一个系统能够做到高内聚、低耦合,那么,它就具有了非常高的可扩展性。
可扩展性高意味着“未来有无限可能”:
功能不满足需求了,在模块内部简单扩展就能满足;
性能上达不到要求了,系统内部挖潜就能达标;
技术太过陈旧要更新换代了,自己就能完成新陈代谢……
这么说可能有些费解,我们还是举例子吧。
对宅男/宅女来说,高内聚就是吃喝拉撒睡都不用出门,低耦合就是不用跟人打交道。这样一来,我想吃披萨就吃披萨,想喝两碗豆浆就喝两碗豆浆,完全不用顾虑别人想吃什么、别人会说什么。这样的生活惬意不?
对上班族来说,高内聚就是工作都由自己说了算,低耦合就是跨组/跨部门的沟通协作少。这样一来,我想用数据集A就用数据集A,想用数据集B就用数据集B;想用Excel用Excel,想写脚本写脚本;想用PPT汇报就用PPT汇报,想用demo汇报就用demo汇报。这样的工作快乐不?
技术上……我还是举个例子吧。
我们系统中有一个模块,用于处理用户短信验证操作。签约验证成功后,针对具体的业务还需要做一些后续处理。
随着业务的不断发展,后续业务处理从最初的一套逻辑增加到现在的五套逻辑。此外我们还做了一些技术优化,如优化库表结构、增加并发处理等,最后的流程大概是这样的:
由于整个短信验证模块、以及后续业务处理模块都做到了高内聚、低耦合(自吹自擂脸),因此,无论是业务扩展还是技术优化,都只影响到了各自对应的模块,其它模块、功能对此毫无感知。
尤其是业务扩展、增加一个后续业务处理模块时,其它后续业务处理模块丝毫不受影响。没有影响意味着开发不用改代码、测试不用回归测试、上线时间可以缩短、老代码里不会引入新问题……
可见,高内聚低耦合的设计能带来多赢的结果。
对开发来说,改动的代码当然是越少越好——高内聚低耦合的设计能减少要修改的代码量。
对测试来说,测试范围当然是越小越好——高内聚低耦合的模块只需要测试新增功能而不需要回归原有功能。
对产品来说,需求上线的时间当然是越短越好——高内聚低耦合的设计能减少开发和测试的工作量,上线时间自然也就变短了。
对用户来说,系统问题当然是越少越好——高内聚低耦合的设计至少能保证老代码正常运行,不会因为新功能而莫名崩溃。
缺点
优点如此显而易见,为什么实际工作中很少有人认真执行“高内聚低耦合”的设计要求呢?
如果你是一个政府部门,你是愿意自己只盖一个章、让用户去跑其它部门盖完剩下的十几个章,还是愿意让用户只来你这一个部门、你去跑其它部门改完所有的章?
如果你是一家商店,你是愿意让用户把钱全都花在你的店里,还是愿意提供一堆服务让用户去别的店铺消费?做软件设计也要面对“投入产出比”的考虑。
如果一个良好设计的成本太高而收益太低、甚至与主要目标背道而驰,那么人们自然就会用脚投票、不使用这种设计。
“高内聚低耦合”的设计就常常会陷入这样的困境中。
首先,要严格做到高内聚、低耦合,通常多要付出很多精力来做设计。
换句话说,要做到高内聚、低耦合,我们往往需要付出很多额外成本;而且这些额外成本有时并不一定能带来期望的结果。
以前面说的短信验证功能模块为例,如果五种业务逻辑的差异都只有一两行代码,那么全用if-else的方式放在一个类中会是一种更合适的方法。
但是,如果业务逻辑本身比较复杂、或者彼此之间差异比较大,还用if-else处理的话,最后代码会变成一团乱麻、快速腐化。
因此,尽管我们的设计多了六个类(一个业务分发类、五个不同的业务处理类),并导致了前期开发时多花了一天半天的时间,但整个模块的可维护性、可扩展性都有很大提高,可以说是“过当”了吧。
其次,“高内聚低耦合”是一种原则性的设计准则。
在现实中,原则性的东西一般都会为灵活性——比如工期啦,历史遗留问题啦,部门关系啦——让步。我曾经经常听到“以前就是这么做的,这次也这样处理吧”、“他们的接口就是这样的,我们也没办法”这样的话,也经常看到因为这些原因而放弃更好的设计,甚至因此而不再思考有没有更好的设计。有时真让人感叹技术其实什么都改变不了。
另外,原则性的东西往往太务虚而无法落到实处。“这个模块不够高内聚”,“这段代码的耦合度太高了”,具体是怎么不够高内聚?要改成怎样才能降低耦合度?完全叫人丈二金刚摸不着头脑。
最后,法学上有种罪名叫做“箩筐罪”,当你觉得一个东西有问题、但又说不清楚具体是什么问题时,就可以把它归入“箩筐罪”中——也就是所谓“xx罪是个框,什么都能往里装”。“高内聚低耦合”可以说是软件设计界的“箩筐罪”,只要想往里装就能往里装。例如前面那个短信验证功能模块,它真的满足了“高内聚低耦合”了吗?虽然经过精心设计,但是如果要挑,也还能挑出一箩筐毛病来。
所以,“高内聚低耦合”的度很难把握,过于执着甚至可能走火入魔。这也是为什么“高内聚低耦合”很少在实际工作中提及和应用的原因之一。
HOW
有些问题可以靠技术来解决。例如,开发成本高这个问题在团队开发水平提高到一定层次之后就迎刃而解了;太过务虚的问题也可以通过学习和掌握SOLID、设计模式等具体的设计技能来解决。这些具体的技术会在以后慢慢讨论。但是非技术的问题——例如历史包袱、部门关系等,我就爱莫能助了。
高内聚低耦合与抽象
虽然高内聚低耦合有上面这些问题,但是,在做业务抽象的设计时,我们还是要认真考虑、并遵守这个原则。因为高内聚低耦合不仅是做好设计的途径、也是评价设计好坏的标准:对于面向对象的业务抽象设计来说更是如此。
一个好的业务抽象必须能表达出自己“是什么”或者“能做什么”、而隐藏自己“怎么做”。高内聚、低耦合的理念正适合用来让业务抽象隐藏自己的实现细节。
如果一个业务抽象不够内聚或者过度耦合,它一定会过多地把自己的实现细节泄露出去。还记得在《抽象》一文中出现的QueryService吗?它把queryFromRemote和queryFromLocal暴露到业务抽象外部,导致了使用接口时的种种不便,这就是不够高内聚的结果。还有ExcelService,这个接口与2003版的Excel文件格式过度耦合了,结果无法顺利地升级到20007版Excel上。
版权声明: 本文为 InfoQ 作者【落英亭郎】的原创文章。
原文链接:【http://xie.infoq.cn/article/80406ce250cfb751a276e1498】。文章转载请联系作者。
评论