架构整洁之道-如何衡量稳定性

94 阅读6分钟

稳定性指标

量化一个组件稳定性的其中一种方法是计算所有入和出的以来关系。通过这种方法,我们就可以计算出一个组件的位置稳定性(positional stability)。

FanIn:入向依赖,这个指标指代了组件外部依赖于组件内部类的数量。

FanOut:出向依赖,这个指标指代了组件内部类依赖于组件外部类的数量。

I:不稳定性,I=FanOutFanIn+FanOutI = \frac{FanOut}{FanIn + FanOut}。该指标的范围是[0,1],I = 0意味着组件是最稳定的,I = 1意味着组件是最不稳定的。

image.png

在这里,我们想要计算组件Cc的稳定性指标,可以观察到有3个类在Cc外部,它们都依赖于Cc内部的类,因此FanIn=3。此外,Cc中的一个类也依赖于组件外部的类,因此FanOut=1,I=1/4。

当I指标等于1时,说明没有组件依赖当前组件(Fan-in=0),同时该组件却依赖于其他组件(Fan-out>0)。这是组件最不稳定的一种情况,我们认为这种组件是“不负责的(irresponsible)、对外依赖的(dependent)”。由于这个组件没有被其他组件依赖,所以自然也就没有力量会干预它的变更,同时也因为该组件依赖于其他组件,所以就必然会经常需要变更。

相反,当I=0的时候,说明当前组件是其他组件所依赖的目标(Fan-in>0),同时其自身并不依赖任何其他组件(Fan-out=0)。我们通常认为这样的组件是“负责的(responsibile)、不对外依赖的(independent)”。这是组件最具稳定性的一种情况,其他组件对它的依赖关系会导致这个组件很难被变更,同时由于它没有对外依赖关系,所以不会有来自外部的变更理由。

稳定依赖原则(SDP)的要求是让每个组件的I指标都必须大于其所依赖组件的I指标。也就是说,组件结构依赖图中各组件的I指标必须要按其依赖关系方向递减。

稳定抽象原则(SAP) 为组件的稳定性与它的抽象化程度建立了一种关联。一方面,该原则要求稳定的组件同时应该是抽象的,这样它的稳定性就不会影响到扩展性。另一方面,该原则也要求一个不稳定的组件应该包含具体的实现代码,这样它的不稳定性就可以通过具体的代码被轻易修改。

将SAP与SDP这两个原则结合起来,就等于组件层次上的DIP。因为SDP要求的是让依赖关系指向更稳定的方向,而SAP则告诉我们稳定性本身就隐含了对抽象化的要求,即依赖关系应该指向更抽象的方向。

衡量抽象化程度

假设A指标是对组件抽象化程度的一个衡量,它的值是组件中抽象类与接口所占的比例。那么:

Nc:组件中类的数量。

Na:组件中抽象类和接口的数量。

A:抽象程度,A=Na/Nc。

A指标的取值范围是从0到1,值为0代表组件中没有任何抽象类,值为1就意味着组件中只有抽象类。

现在,我们可以来定义组件的稳定性I与其抽象化程度A之间的关系了,具体如图下图所示。在该图中,纵轴为A值,横轴为I值。如果我们将两个“设计良好”的组件绘制在该图上,那么最稳定的、包含了无限抽象类的组件应该位于左上角(0,1),最不稳定的、最具体的组件应该位于右下角(1,0)。

image.png

当然,不可能所有的组件都能处于这两个位置上,因为组件通常都有各自的稳定程度和抽象化程度。例如一个抽象类有时会衍生于另一个抽象类,这种情况是很常见的,而这个衍生过程就意味着某种依赖关系的产生。因此,虽然该组件是全抽象的,但它并不是完全稳定的,上述依赖关系降低了它的稳定程度。

image.png

痛苦区

假设某个组件处于(0,0)位置,那么它应该是一个非常稳定但也非常具体的组件。这样的组件在设计上是不佳的,因为它很难被修改,这意味着该组件不能被扩展。这样一来,因为这个组件不是抽象的,而且它又由于稳定性的原因变得特别难以被修改,我们并不希望一个设计良好的组件贴近这个区域,因此(0,0)周围的这个区域被我们称为痛苦区(zone of pain)。

会处于这个区域的典型软件组件是工具型类库。虽然这种类库的I指标为1,但事实上通常是不可变的。例如String组件,虽然其中所有的类都是具体的,但由于它被使用得太过普遍,任何修改都会造成大范围的混乱,因此String组件只能是不可变的。

会处于这个区域的典型软件组件是工具型类库。虽然这种类库的I指标为1,但事实上通常是不可变的。例如String组件,虽然其中所有的类都是具体的,但由于它被使用得太过普遍,任何修改都会造成大范围的混乱,因此String组件只能是不可变的。

无用区

(1,1)这一位置点的组件不会是我们想要的,因为这些组件通常是无限抽象的,但是没有被其他组件依赖,这样的组件往往无法使用。因此我们将这个区域称为无用区。

主序列线

很明显,最多变的组件应该离上述两个区域越远越好。

将距离两个区域最远的点连成一条线,即从(1,0)连接到(0,1)。这条线称为主序列线(mainsequence)。

坐落于主序列线上的组件不会为了追求稳定性而被设计得“太过抽象”,也不会为了避免抽象化而被设计得“太过不稳定”。这样的组件既不会特别难以被修改,又可以实现足够的功能。对于这些组件来说,通常会有足够多的组件依赖于它们,这使得它们会具有一定程度的抽象,同时它们也依赖了足够多的其他组件,这又使得它一定会包含很多具体实现。