变化驱动:正交设计

转载自:https://www.jianshu.com/p/d127b8afc8cb

作者:袁英杰

一个出发点

当谈起软件设计目的时,能够获得所有人认同的答案只有一个:功能实现。 因为这是一个软件存在的根本原因。

而在计算机软件发展的初期,这一点也正是所有人做软件设计的唯一动机。因而,很自然的,整个软件都被放在单一过程中,然后用到处存在的goto语句控制流程。

尽管理论上讲,任意复杂的系统都可以被放入同一个函数里。但随着软件越来复杂,即便是智商最为发达的程序员也发现,单一过程的复杂度已经超出他的掌控极限。这逼迫人们必须对大问题进行分解,分而治之。

时至今日,尽管超大函数,上帝类依然并不罕见,但当大到一定程度,上帝类的创造者最终也会发现自己终究没有上帝般的掌控力。因而,哪怕是软件设计素养为负值的开发者,或多或少也会对一个复杂系统进行一定程度的拆分。

这就是模块化设计的最初动机。

两个问题

一旦人们开始进行进行模块化拆分,就必须解决如下两个问题:

  1. 究竟软件模块该怎样划分才是合理的/li>
  2. 将一个大单元划分为多个小单元之后,它们之间必然要通过衔接点进行合作。如果我们把这些衔接点看作API,那么问题就变为:怎样定义API才是合理的/li>

更简单的说:怎么分后再怎么合/p>

变化驱动:正交设计

这幅图揭示了模块化设计的全部:首先将一个低内聚的模块首先拆分为多个高内聚的模块;然后再考虑这多个模块之间的API设计,以降低这些高内聚的软件单元之间的耦合度

除了内聚耦合之外,上面这幅图还揭示了另外一种关系:正交。具备正交关系的两个模块,可以做到一方的变化不会影响另外一方的变化。换句话说,双方各自独自变化,互不影响。

而这幅图的右侧,正是我们模块化的目标。它描述了永恒的三方关系客户API实现,以及它们之间的关系。这个三方关系图清晰的指出了我们应该关注的内聚性耦合性,以及正交性都发生在何处。

四个策略

相对于局部化影响高内聚,低耦合原则已经清晰和具体许多。但依然更像是在描述目标或结果,而没有指明该如何达成的方法。虽然《代码大全》列举了那么多的内聚性耦合性的分类,但对于想应用它们的软件设计人员,依然感觉如隔靴挠痒,不得要领。

因而,我们需要从它推导出更为明确,更具指导性和操作性的设计原则。

为了做到这一点,我们必须首先搞清楚:内聚耦合,和变化之间的关系是怎样的,以至于高内聚、低耦合的模块化方式能够更容易应对变化/p>

我们再次回顾内聚耦合的定义:它们是用来衡量代码元素之间的关联紧密程度。很容易得知:元素之间的关联紧密程度越高,一个变化引起它们相互之间都发生变化的可能性就越高。反之,关联程度越弱,变化引起的连锁变化的概率就越低。

因而,我们要把容易互相影响的、关联程度紧密的元素,都封装在一个模块内部(而这正是我们老生常谈的封装变化的动机);同时让模块之间的关联紧密程度尽可能降低,以让模块间尽可能不要相互影响。从而最终做到局部化影响

因而,Uncle Bob说:一个类只应该有一个变化原因。他进一步谈到:所谓一个变化原因,指一个变化会导致整个类所包含的各个元素都要发生变化。为何会如此为它们的关联程度太紧密(因而高内聚),以至于牵一发而动全身。

因此,Uncle Bob将职责定义为变化原因

在一些时候,我们可以直接判定一个模块是否包含多重职责。因为它们确实包含着明显没有什么关联的两组代码元素。

但在另外一些场景下,我们则无法清晰的判定:一个模块是否真的包含多重变化原因,或多重职责。比如如下代码:

这是一个对学生按照身高从低到高进行排序的算法。对于这段代码,如果我们进行猜测,会发现很多点都有变化的可能,如果对这些变化都进行分离和管理,确实会提高系统的内聚度。但如果我们现在就将整个系统每个可能的变化点都分离出来,无疑会让整个系统陷入无边无际的不必要的复杂度。

破解这类难题的方法是:既然我们知道高内聚,低耦合的设计是为了软件更容易应对变化的,那么我们为何不反过来,让实际发生的需求变化来驱动我们识别变化,管理变化,从而让我们的系统达到恰如其分的内聚度和耦合度/p>

策略一:消除重复

首先进入我们射程的就是重复代码。编写重复代码不仅仅会让有追求的程序员感到乏味。真正致命的是:“重复”极度违背高内聚、低耦合原则,从而会大幅提升软件的长期维护成本。

我们之前已经讨论过,所谓高内聚,指的是关联紧密的事物应该被放在一起。没有比两段完全相同的代码关联更为紧密。因而重复代码意味着低内聚

而更为糟糕的是,本质重复的代码,其实都在表达(即依赖)同一项知识。如果它们表达(即依赖)的知识发生了变化,这些重复的代码统统都要修改。因而, 重复代码也意味着高耦合

重复代码意味着耦合:

变化驱动:正交设计

对于这一类型的重复,比较典型的情况有两种:调用型重复,以及回调型重复。它们的命名来源于:在重复消除后,重复与差异之间的关系是调用,还是回调。

调用型重复:

变化驱动:正交设计

由此,我们得到了第一个策略:消除重复

这个策略,非常明确,极具可操作性:当你看到重复时,尽力消除它。

这个策略,明显提高系统的内聚性,降低了耦合性。除此之外,还得到一个重大收益:可重用性。事实上,消除重复的过程,正是一个提高系统可重用性的过程。

另外对于回调型重复的消除,也是一个提高系统可扩展性的过程。

策略二:分离不同的变化方向

除了重复代码外,另外一个驱动系统朝向高内聚方向演进的信号是:我们经常需要因为同一类原因,修改某个模块。而这个模块的其它部分却保持不变。

比如,在之前我们对学生按照身高从低到高排序的例子中,如果现在我们需要增加对老师按照身高从低到高排序的需求,我们就知道,排序对象是一个新的变化方向。于是,我们将代码重构为:

如果随后又出现一个新的需求:按照学生身高从高到低排序(原来为从低到高)。此时我们知道排序规则也是一个变化的方向。因此,我们将这个变化方向也从现有代码中分离出去。然后得到:

分离不同变化方向,目标在于提高内聚度。因为多个变化方向,意味着一个模块存在多重职责。将不同的变化方向进行分离,也意味着各个变化方向职责的单一化。

从这个例子可以看出,此策略的应用时机也非常明确:当你发现需求导致一个变化方向出现时,将其从原有的设计中分离出去。

分离新的变化方向:

变化驱动:正交设计

从这幅图可以看出,对于API定义所带来的耦合度影响,需要遵循如下原则:

  • 首先,客户实现模块的数量,会对耦合度产生重大的影响。它们数量越多,意味着 API 变更的成本越高,越需要花更大的精力来仔细斟酌。
  • 其次,对于影响面大的API(也意味着耦合度高),需要使用更加弹性的API定义框架,以有利于向前兼容性。

而具体到策略缩小依赖范围,它强调:

  1. API 应包含尽可能少的知识。因为任何一项知识的变化都会导致双方的变化;
  2. API 也应该高内聚,而不应该强迫API的客户依赖它不需要的东西。

策略四:向着稳定的方向依赖

但是,无论我们如何缩小依赖范围,如果两个模块需要协作,它们之间必然存在耦合点(即API)。降低耦合度的努力似乎已经走到了尽头。

我们知道,耦合的最大问题在于:耦合点的变化,会导致依赖方跟着变化。但这也意味着,如果耦合点从来不会变化,那么依赖方也就不会因此而变化。换句话说,耦合点越稳定,依赖方受耦合变化影响的概率就越低。

由此,我们得到最后一个策略:向着稳定的方向依赖

那么,究竟什么样的API更倾向于稳定难知道,站在What,而不是How的角度;即 站在需求的角度,而不是实现方式的角度定义API,会让其更加稳定。

而需求的提出方,一定是客户端,而不是实现侧。这就意味着,我们在定义接口时,应该站在客户的角度,思考用户的本质需要,由此来定义API。而不是站在技术实现的方便程度角度来思考API定义。

而这正是封装信息隐藏的关键。

小结

这四个策略,前两者聚焦于如何划分模块,后两个聚焦于如何定义模块间的API。换句话说,前两者关注于“如何分”,后两条聚焦于“怎么合”。

这四个策略的背后动力非常明确:变化。前两者,都是在明确的变化方向被第一次识别之后(所谓第一颗子弹),进行策略运用,以让模块在变化面前越来越高内聚。而后两者,则是在模块职责分离之后,需要定义模块间API时,尽可能考虑不同的API定义方式对于依赖双方的影响,以达到低耦合

由于这四个策略致力于让系统朝着更具正交性的方向演进,因而它们也被称做正交策略,或者正交四原则

总结

本文首先从一个出发点出发:为了降低软件复杂度,提升可重用性,我们需要模块化

由此得到了两个问题:模块划分必然要解决如何划分,以及模块间如何协作(API 定义)的问题。

基于软件易于应对变化的角度出发。高内聚、低耦合原则是最为核心和关键的高层原则。基于此我们得到了在模块化过程中,我们真正需要关注的三方关系

为了让高内聚、低耦合更具指导性和操作性,我们提出了四个策略。它们以变化驱动,让系统逐步向更好的正交性演进的策略,因此也被称做正交策略正交原则

我们已经在多个系统的设计和开发中,以这四个原则来驱动我们的软件设计,不仅让我们的系统在保持简单的同时,具备所有必要的灵活性。也让设计和开发活动变得高度有章可循,让团队生产率得以大幅提升。

最后,推荐刘光聪的文章《实战正交设计》。这篇文章通过一个例子,来展示了正交策略是如何驱动出更加正交的设计的。

而正交设计与SOLID的关系,可参阅《正交设计,OO与SOLID》。

来源:墨1024

声明:本站部分文章及图片转载于互联网,内容版权归原作者所有,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2020年11月15日
下一篇 2020年11月16日

相关推荐