分类: LINUX
2012-07-02 10:24:16
你曾有过这样的经历吗?工作了一整天,终于完成了某项功能后回家,不料第二天早晨一来却发现那项功能不再工作了。原因是什么呢?因为有人比你走得更 晚,并且更改了你所依赖的某些东西!这就是所谓的“晨后综合症"。受“晨后综合症”困扰的团队常常几周时间都无法构建出一个稳定的版本,每个人都忙于一遍 遍更改他们的代码,试图使之能够相容与其他人所做的最近更改,从而造成团队士气低落,效率不高,陷入了可怕的集成地狱中。为此有的团队在集成期间禁 止 check in ,这显然不是一个好办法,一方面,如果不用技术手段很难避免有意或无意的 check in ,另一方面开发者为了进行后续开发而又不影响当前集成的版本,可能不得不手工进行版本管理, 再有,它使团队成员间的协作也变得困难。
对包进行发布能有效避免晨后综合症的发生。造成晨后综合症的原因在于依赖者所依赖的包是变化的,这就使依赖者工作于一个不稳定的基础之上,对被依赖 者的改变,依赖者必须作出相容的改变。采用包的发布机制,依赖者必须选择被依赖包发布的一个特定版本,而发布后的包,其内容是不允许变化的,因此依赖者所 依赖的东西就不会改变;同时,被依赖包的任何改变都必须作为一个新版本发布,而依赖者有权决定是否采用这个新版本,换句话说,是否接受被依赖者的改变是由 依赖者决定的,而在此之前,依赖者是不得不被动地接受被依赖者的改变。
2 包的设计原则
要对包进行发布,首先要先设计好包,对规模较大的应用来说,划分包的组合很多,仅仅把看起来像是适合在一起的类放进相同的包中,得到的往往是一种不 好的包结构:发布很困难、不容易重用、难于更改等等,这种包结构带来的可能是更多的麻烦。显然我们需要一些原则来指导包的划分,以下列出这些原则,前三个 原则用来指导把类划分到包中,关注包的内聚性,后三个原则用来处理包之间的相互关系,关注包的耦合性。
2.1 REP 重用发布等价原则重用的粒度就是发布的粒度。
如果一个包中的软件是用来重用的,那么它就不能再包含不是为了重用目的而设计的软件。换句话说,一个包中的软件要么都是可重用的,要么都不是可重用 的。简单地声明一个类是可重用的做法是不现实的,我们所重用的任何东西都必须同时被发布和跟踪。如果一个包同时包含了可重用的类和不可重用的类,那么当不 可重用的类发生变化时,就要进行一次包的发布,而原本不受影响的重用者就需要决定是否采用新版本,以及采用新版本后可能的编译,连接和测试工作。这些内耗 操作是应该避免的。
2.2 CRP 共同重用原则一个包中的所有类应该是共同重用的。如果重用了包中的一个类,那么就要重用包中的所有类。
在大多数情况下,一个可重用抽象需要多个类来表达,该原则规定这些类应该在一个包中,而属于不同抽象的类不应该在一个包中。乍看起来,这条原则 和 REP 有点相似,但实际上还是不同的,比如,抽象 A 包括类 A1、A2、A3 ,抽象 B 包含类 B1、B2、B3 ,从 REP 原则来 看,这个包没有什么不对,因为该包的软件都是可重用的,但它却违反了 CRP 原则,因为重用者完全可以只重用 A 抽象的类或只重用 B 抽象的类,这 样当任一抽象的改变都会导致该包重新发布,虽然该发布对某一抽象的使用者是无意义的,但是他们仍然需要需要重新验证和重新发布,这会白费相当数量的努力。
2.3 CCP 共同封闭原则包中的所用类对于同一类性质的变化应该是共同封闭的。一个变化若对一个包产生影响,则将对该包中的所有类产生影响,而对其他的包不造成任何影响。
REP 和 CRP 关注的都是重用性, CCP 关注的是可维护性。对大多数的应用来说,可维护性的重要性是超过可重用性的。一个变化(包括需求 上的,设计上的等等)可能会引起多个类的更改, CCP 要求我们把这些类放在一个包中,同时把那些不受影响的类放到其他的包中,因此一个包只有一个引起 变化的原因,一个变化只对一个包产生影响,这样大大减少了重新发布的次数和重新发布对其他包的影响,从而提高了软件的可维护性。
2.4 ADP 无环依赖原则在包的依赖关系图中不允许存在环。
如果包的依赖关系图中存在环,就会导致环上的所有软件包必须同时发布,如下图,
由于 P3 使用了 P1 中的一个类而形成了依赖环,若要发布 P1 ,必须先发布 P2 和 P3 ,而发布 P3 又要先发布 P1 和 P2,P1、P2、P3 实际上已经变成了同一个大包,于是工作于这些包上的开发人员不可避免地会遭受晨后综合症。
去掉依赖环的方法有两个:
使用DIP。如图,把Y的接口和实现分离,
增加新包。如图,把 P1 和 P3 都依赖的类移到一个新包中
2.5 SDP 稳定依赖原则
朝着稳定的方向进行依赖。
如果一个包被很多包所依赖,那么它就是稳定的,因为要使所有依赖于它的包能够相容于对它所做的更改,往往需要非常大的工作量。一个系统中的所有包并 非都应该都是稳定的,因为如果那样的话,系统将很难更改。 SDP 指导我们处理稳定包和不稳定包之间的关系:不稳定的包应该依赖于稳定的包,一个包应该 依赖于比他更稳定的包。你设计了一个不稳定的包,期望它能随变化容易地更改,可当它被一个稳定的包依赖后,它就再也不会易于更改了,这就使软件难于修改和 变化。
2.6 SAP 稳定抽象原则包的稳定程度应该和其稳定程度一致。
一个系统的高层构架和设计决策应该被放进稳定的包中,因为这些构架决策不应该经常改变,然而稳定包的不易更改的特点会使这些架构决策不灵活,显然只 用稳定性来度量一个包是不够的, SAP 告诉我们:稳定的包也应该是抽象的。它应该包含抽象类,系统通过在其他包中实现该稳定包中的抽象类来进行扩展, 而该稳定包无需修改,从而在保持稳定的同时也不失灵活性。
3 包的设计过程
几年前当我还不知道这些包的设计原则时,认为包就是系统的高层的功能分解,应该在项目开始时就设计好,结果遭受了失败。实际上,包的依赖关系图和描 绘系统的功能之间几乎没有关系,它是系统可构建性的映射图。项目开始时,我们还不知道系统中有哪些类,更不必说它们之间的关系,在这种情况下不仅很难创建 出包依赖关系图,即使创建出来也很可能是不合理的。包的依赖关系结构应该是和系统的逻辑设计一起增长和演化的,项目开始时不必考虑包,系统仍以类的粒度组 织,随着开发的进行,类越来越多,对依赖关系进行管理,避免项目开发中出现晨后综合症的需要也不断增长,此时我们应用 CCP 原则对把可能一同变化的类 组织成包进行发布,随着系统的不断增长,我们开始关注创建可重用的元素,于是开始使用 CRP 和 REP 来指导包的组合。最后使用 ADP、SDP、 SAP 对包图进行度量,去掉不好的依赖。
4 你真的采用了包的发布机制了吗 如果说某个做产品的团队没有采用包的发布机制,它也许会大叫冤枉:我们明明是进行了发布的呀,你看,这不是我们发布的1.0、1.2版本吗?事实 上,任何一个产品都离不开包的发布,只不过对这样的团队而言,他们系统里真正意义上的包只有一个,那就是整个系统,而该包的发布常常是在经过了晨后综合症 的洗礼之后,是在开发基本完毕后进行的,实际上,我们更应该在产品的开发过程中使用这一机制,这样,产品的开发过程才会以合理,有序的方式进行,产品才能 尽快地交付。