静态共生(Static connascence)
静态共生是指源码级的耦合(与执行时耦合相对),它是结构化设计所定义的传入和传出耦合的细化。换句话说,无论是传入还是传出,对于架构师来说以下类型的静态connascence是指某一事物被耦合的程度:
-
命名共生 (Connascence of Name (CoN))
多个组件必须就一个实体的名称达成一致
代码耦合的最常见形式就是方法名,也是最理想的方式,因为现代的IDE很容易的就能解决这个问题
-
类型共生(Connascence of Type(CoT))
多个组件必须就实体的类型达成一致
这种类型的共生指的是在许多静态类型语言中常常将变量和参数限制为特定类型,然而,这并不纯粹是一种语
言特性,像Clojure和Clojure Spec等一些动态类型的语言提供了选择性类型。
-
含义共生(Connascence of Meaning(CoM))和约定共生(Connascence of Convention(CoC))
多个组件必须就特定值的含义达成一致
在代码库中,这种类型的共生最常见的案例就是使用硬编码的数字,而不是用常数。例如,在一些语言中,通常会
做这种定义
int TRUE = 1;int FALSE = 0。想象一下,如果有人把这些值的含义翻转过来,就会出现问题。 -
位置共生(Connascence of Position (CoP))
多个实体必须就值的顺序达成一致
这是对方法和函数调用的参数值的一个问题,即使在静态类型的语言中也一样。例如,如果开发人员创建了一
个方法
void updateSeat(String name, String seatLocation),并以updateSeat("14D", "Ford, N")来调用它,即使入参类型匹配,语义也是不正确的。
-
算法共生(Connascence of Algorithm (CoA))
多个组件必须就某一算法达成一致
这种共生的常见情况是,开发人员写一个安全散列算法,该算法必须在服务器和客户端运行,并产生相同的
结果来验证用户身份。这代表了一种高形式的耦合,如果任何一方的算法改变了任何细节,握手将会失效。
动态共生(Dynamic connascence)
另一种共生是动态共生,它在运行时分析调用,下面是对不同类型的动态共生的描述:
-
执行共生(Connascence of Execution (CoE))
组件之间的执行顺序很重要
看这段代码
email = new Email(); email.setRecipient("foo@example.com"); email.setSender("me@me.com"); email.send(); email.setSubject("whoops");这段代码是无法如预期工作的,因为邮件的某些属性需要在发送之前就设置好。
-
时序共生(Connascence of Timing (CoT))
组件之间的执行时序很重要
这类共生的典型案例是两个线程同时执行造成的竞争条件,影响联合操作的结果。
-
值共生(Connascence of Values (CoV))
当几个值相互关联且必须一起改变时发生
示例:开发人员将一个矩形定义为四个点,点代表了矩形的角。为了保持数据结构的完整性,开发者不能随意改变
其中一个点而不考虑对其他点的影响。
更常见也更麻烦的情况涉及到事务,特别是在分布式系统中。当架构师设计了一个具有多数据库,或者说单数据库
多
schema的系统,当需要在所有的库中更新一个值时,所有的值必须一起改变,或者根本不改变。 -
身份共生(Connascence of Identity (CoI))
当几个值相互关联且必须一起改变时发生
这种共生常见例子是两个独立的组件必须共享和更新一个共同的数据结构,如分布式队列。
架构师在确定动态共生关系时比较困难,因为我们缺乏分析运行时调用的工具,来达到分析调用图那样的效果。
共生属性(Connascence properties)
共生(Connascence)是架构师和开发人员的分析工具,它的一些属性有助于开发人员合理使用它。以下是对这些属性的一一描述:
-
强度(Strength)
架构师通过开发人员对某类型耦合进行重构的难易程度来确定共生的强度,不同类型的共生显然更可取,如下图所示,架构师和开发人员可以通过重构来改善代码的耦合,使之向更好的共生发展。
架构师应该倾向于静态共生而不是动态共生,因为开发人员可以通过简单的源码分析来定位,而且现代IDE更是显得静态共生微不足道。例如含义共生,开发人员可以通过创建一个良好命名的常量,而不是用一个magic value来重构,从而改善这种共生。
-
连带性(Locality)
连带性衡量的是代码库中各模块之间的近似程度,近距离的代码(比如在同一模块中)通常比分开的代码(在不同的模块或代码库中)具有更多和更高的连带性。换句话说,相隔较远时表示耦合性较差的共生,相隔较近时则没有问题。例如,如果同一个组件中的两个类具有共生意义,那么相比具有相同共生形式的两个组件,它对项目代码健康的损害要更小。
开发者必须将强度和连带性放在一起考虑。在同一模块内发现的较强的共生形式,相比分散开来的相同共生形式要更合适。
-
程度(Degree)
共生的程度与它造成的影响大小有关——比如它影响的是个别几个类还是许多类?较低程度的共生对项目代码的损害较小。比如说,你的项目只有零星几个模块,拥有高动态共生并无大碍。然而,现实中代码库往往会随时间增长,一个小问题也会发酵。
Page-Jones(
Object-Oriented Design
一书作者)提供了三条利用共生来提高系统模块化程度的建议:
-
通过将系统分解成许多封装的元素来最小化整体的共生
-
最大限度地减少任何超出封装边界的剩余共生
-
最大限度地提高封装边界内的共生
Jim Weirich重新普及了共生的概念,并给出了两个很好的建议:
程度法则:将强势的共生形式转化为弱势的共生形式。
连带性法则:随着软件元素之间距离的增加,使用较弱的共生形式。
-
统一耦合和共生度量标准
从架构师的角度来看,耦合(Coupling)和共生(Connascence)这两种观点可能重叠的。Page-Jones所认定的静态共生代表了出入耦合的程度,结构化编程只关心进或出,而共生关心的是事物如何耦合在一起。为了更好的理解和区分这两种观点,可参考下图:
在上图中,左边是结构化编程的耦合概念,右边是共生特性。结构化编程中所谓的数据耦合(方法调用),共生为它应该如何表现提供了建议。另外,结构化编程并没有涉及到动态共生所涵盖的领域。