UML软件工程组织

在UCM中使用复合基线的最佳实践
软件配置管理专家 - 全球技术市场, IBM Jim Tykal
在ClearCase v2003版中引入的复合基线,可以帮助软件专业人员和配置经理更好地管理他们的软件构件和子系统。此白皮书论述了使用复合基线的一些最佳实践,以极大地简化基线管理,并增强构件和子系统的重用性。

概述
此白皮书是为了让愿意使用复合基线的软件专业人员和配置经理可以更好地管理他们的软件构件和子系统。假定对同一变更管理过程(UCM)已经有了一个基本的理解。

背景知识
 

构件
在软件工程中,特别是在IBM Rational统一过程(RUP)方法论中,一个构件是一组相关的元素,一起进行开发和发布。一个构件可能是一个库,.dll,.jar,一个可执行程序,或任何可作为一个单元发布的资产集。

在IBM Rational ClearCase UCM中,一个构件是位于一个特定目录树中的一组相关联的元素。由于这个概念比一般的构件定义的更严格,我们会提到一个UCM构件对象当做一个配置管理构件(Configuration Management Component),(或者简写为CM构件)。

配置管理的构件有一个有限的范围和含意,目的是确定ClearCase中的一组元素。一个配置管理构件由一个VOB中的一个单个的元素目录树来表示。目录树的根被称为构件根目录元素(Component Root Directory Element)。根目录可能是一个VOB(在此情况下,构件包括一个VOB中的所有元素)的根,或者它可能是VOB根的下一级的一个目录(其允许在一个VOB中包含多个子VOB级构件)。

图1:一个配置管理构件

构件的使用减少了配置管理的复杂性,因为源代码可以用包含许多元素的逻辑单元来进行管理,而不是根据一个个元素。使用构件也可以提升代码的共享和重用,因为它们可以追踪互相依赖的元素的集合。

无根构件(Rootless Component)
在v2002版中,UCM引入了无根构件:即不包括任何相关元素的构件。因为一个无根构件只是一个UCM PVOB中的一个对象,没有构件根目录元素。

图2:一个无根配置管理构件

乍看起来,一个不收集源文件的构件看起来没有什么用处。但是,在本文后面,我们将阐明无根构件可以减少配置管理复杂性,并在使用复合基线时可以实现其它好处。

Baseline
正象一个构件表示了一组元素,一个基线表示了一个构件中的一组版本。一个基线最多确定了一个构件中的每个元素的一个版本。

:一个基线是一个构件在一个特定时刻的一个快照。它包括在此时工作流中所选择的版本集。当配置一个新工作流时,基线被用来指定哪些版本将被选在此流中。基线是不能变的,因此:

  • 一个特定配置可以在需要时重现
  • 使用相同基线集的流保证会有相同的配置

因此,在一个基线中所包括的版本集不能被修改。

图3:一个UCM基线

在相同的流中创建的基线是相互的一个安排好的关系。例如在单个流中,一个旧的基线被作为一个较新基线的祖先基线,并且一个新基线被称为旧基线的一个派生基线。一个基线最靠近的祖先是其前一个基线。此流的基础基线,是在一个不同的流中所创建的,是此流所创建的第一个基线的前一个基线。

图4:祖先基线和派生基线的一个例子

在图4中,基线BL1是BL2的前任(并且BL2是BL1的一个派生基线)。当创建BL2时,str.htxt.hfile.h有新的版本,但是对于imp.hnum.h,BL2与BL1共有相同的版本。同样地,BL3是BL2的一个派生基线;其它两个基线,BL1和BL2,都是BL3的祖先。BL3捕获了BL2创建后的变化,但是仍然使用num.h的BL1版本和txt.h的BL2版本。txt.h的版本4被检出,因此它不包括在BL3中。查看基线间关系的另一种方式是,一个派生基线包含其祖先基线-在BL2中所捕获的所有变化也被包括在BL3中。

在UCM ClearCase中,基线有三个目的:

  • 记录工作完成,并存储里程碑。每个新的基线合并自前一个基线之后所产生的变化。
  • 定义工作流配置。
  • 提供对已提交工作的访问。

一个基线和一个构件之间的关系与一个版本和和一个元素之间的关系非常相近。例如,基线存在于流中,而版本存在于分支上,基线和版本都有祖先。

流(Stream)
基线和流有相互的关系:基线由流产生,流使用基线作为其配置。一个流设定一组基线,称为其基础,它定义了哪些版本被选在流中。连接到流的视图可以看到基础基线所选中的元素版本,以及在流中所进行的任何变化。

图5:一个UCM流

注意,一个流的基础需要包括每个需要访问的构件(包括可修改的和不可修改的)的一个基线。

项目(Project)
在一个UCM的环境中,一个项目包含了需要管理一个重大开发工作的配置信息。一个项目包括一组开发方针和一组流,它们一起提供了开发的环境。

每个UCM项目有一个集成流,其配置视图以选择项目的共享元素的最先版本。一个传统的项目也包括多个开发流,其允许开发人员有一个私有的工作空间去开发和测试他们的变更 -- 而不用担心项目团队其他人员的破坏性的变化。一个集成流被连接到项目,但是开发流产生自另一个流,是一种父子关系。因此,集成流可能有子流,而没有父流,但是开发流有父流,可能有或可能没有子流。

每个开发团队组织其项目以满足他们自己的要求,但是通常项目组织会落在两个范畴:面向发布和面向构件。

面向发布的项目

许多项目团队按照产品发布来考虑他们的工作。首先,团队工作在产品的Release 1上。当团队准备开始在Release 2上工作时,他们从Release 1分支,并开始在Release 2流中进行新的开发工作。同样地,当Release 3的工作准备开始时,将从Release 2分支,等等。

图6:一个层叠的面向发布的项目

在Release 1发布后,此发布的补丁可能要在他们自己的项目中进行开发了,从Release 1分支,但将工作交付到Release 2,因此bug修复将会被合并到新的发布中去。

产生自此项目组织的分支层叠(如图6所示)可能会引起一些困难,因此一些略微不同的项目规划通常推荐为面向发布的项目。在此规划中,每个项目都将其发布提交到"main"项目中。然后,此项目可以从Main_Proj集成分支上进行分支,而不用从先前的发布版本中层叠。

图7:一个改进的面向发布的项目

一个面向发布的项目必须有组成最终产品的所有构件的可写权限。工作在项目的一个构件上的开发人员需要持续并且频繁地与工作在其它构件上的开发人员协调工作。

面向构件的项目

其他开发团队通过可重用资源组织他们的工作。这些团队使用项目创建构件(可能是由几个CM构件组成的)。低层的或核心的构件被用来构建中层的构建或子系统,等等,直到最高层的构件被集成为一个产品。

在配置管理中,关键属性是一个子系统的所有代码是在一起发布的。对于开发团队,重要的因素是子系统代表了可以轻易重用的资产。基于子系统设计一个产品或一个产品族会使得将开发工作划分为相关的独立的子工作变得更容易。这种划分可以帮助简化计划和风险管理。

面向构件的关键方面是每个项目都有两个构件的类的访问权限:定制构件和共享构件。项目在定制构件上进行开发工作:这是修改这些构件的唯一的项目。另一方面,共享构件要被使用,但是项目不会计划修改它们,因为这将会破坏共享模型。因此,这些构件通常在项目中被指定为“不可修改的”。

一个项目的目标是产生一组基线,表示将共享构件集成到一个子系统。

图8:一个面向构件的项目

在图8中的例子中,项目Alpha使用CM构件A和C,产生一组定义Alpha子系统的基线集,同时项目Gamma使用CM构件B、C和D,以实施Gamma子系统。项目MLib使用Alpha和Gamma项目所创建的子系统,其由这些项目所产生的基线表示,并且加上在CM构件E中所进行的定制工作。

使用这种项目组织类型的开发组中的子团队受限于他们可能要修改哪些构件。因为较低层的构件可能要被共享,变更必须在关注此构件的项目中以一个主要的、一致的方式进行。如果MLib项目需要在Alpha构件中的新功能,那么此工作必须在Alpha项目中进行。

一个构件定位的一方面在于每个项目都在共享构件的基线选择上有更多的自由。因为这些构件不由项目修改,项目在理论上应当不能一个共享构件的一个版本改变成另一个版本。当构件是具有良好接口的松藕合时,面向构件的项目会运转良好。换句话说,如果一个子系统的一个特定开发任务出现,此方法会运转良好。如果一项开发工作跨越多个子系统,那么将会需要在要进行此工作的不同项目之间进行协调。

混合项目策略

对于项目组织的策略通常由开发团队的组织和观点所确定。UCM支持面向发布的和面向构件的项目;也有可能混合两种策略。一些项目可以专注于产生某些构件,而其他项目可以专注于将构件集成为一个发布版本。对于这两种方式,面向构件的项目的UCM支持更复杂一些,因为在复合基线的使用上会有额外的约束,并且给项目领导增加了更多的负担。在将在稍后进行详细的讨论。

变基
改变一个流的基础基线是通过变基操作进行的。正如早先所提到的,基线提供了对已交付和已测试工作的访问。为了使用已包含在一个基线中的代码,一个流可以进行变基来获得想要的基线。

在一个变基中,用户从流的配置里来选择一个或多个基线来增加或删除。正如一个视图最多可以选择一个元素的一个版本,一个流对于每个构件只能包括一个基线。这是因为对于一个特定的构件,如果在一个流的基础中允许有多个基线,在该构件中选择的版本将会不明确。

在变基过程中,如果有的话,指定的基线会替代其构件的当前基线,即便要对于他们的构件。如果有必要的话,在流中所进行的变化要被合并到新的版本中。注意,虽然提交总是包括要合并的元素,但是变基只包括在流中已经修改的元素也通过新基线选中新版本时的合并。

一个构件的旧基线和新基线之间的关系定义了变基的方向。如果新的基线从一个旧基线继承下来的,变基提升了--流正在获得从相同开始点的其它流中进行的工作。同样地,如果新的基线是旧基线的祖先,这就称为恢复 -- 流移回到一个较早的基线。如果两条基线或者共享一个祖先(但都包含重要开发工作),或者如果他们之间的关系不能被确定,方向就是横向的。

单一的变基操作可能包含很多基线;变基方向是由一个一个基线的基础决定的。因而在一个变基操作中,一个流可能提升一个构件基线,恢复另一个,并且横向变基第三个。

提升

提升是一个变基操作的主要用例。在一个项目整个生命周期中的大多数开发流都是从早先的基线提高到最新的基线,最新基线集成了项目中所进行的工作。流通常是被允许提升的。

限制提升变基发生在派生基线不是来自父流的时候。在变基时可能指定任何流的一个基线。一个流可能选择其基础基线的一个派生基线,并且发生在不是其父流的一个流中。例如,一个开发流可以变基到一个兄弟开发流所创建的一个基线 -- 在此情况下,变基流可能看到工作 -- 此兄弟开发流还没有被提交到父流中。如果几个流尝试提交相同的工作,在合并是将会出现冲突(替代目标流是一种特殊的情况,并且可以设置方针不允许一个流去提交在另一个项目中所完成的工作)。

在提升变基中的一个通用用例可能发生在使用一个构造流的时候,如图9所示。在对应一个里程碑的一个基线被创建之后,稳定构造的工作是在关注此任务的一个流中进行。当构造准备发布时,开发流可以获得构造流上的一个基线,而不是从集成流,因为集成流可能已经接受了其间其它开发人员所提交的更多活动。

图9:一个构造流的例子

然而,开发流不允许被变基到trans2_deploy基线,即使它是开发流当前基础基线trans1的一个派生基线。如果允许此变基,那么Dev_stream1或者Dev_stream2可能要在Build_stream进行之前提交构造稳定性工作。 这样,只有在Build_stream流将其在基线trans2_deploy(曲线箭头所表示的)中的工作提交到开发流的父流--Rel_1.0trans2_deploy集成流之后,才允许变基。

在Build_stream中的工作被包含在父流之后,Dev_stream1和Dev_stream2就可以变基到trans2_deploy上了。

注意,提交操作改变了一个流里的基线之间的关系。在这个例子里,当在Rel_1.0集成流里创建新一个的基线trans3时,它将成为trans2的一个后续基线(与任何基线一样),但是它也将成为Build_stream的trans2_deploy基线的一个派生基线。因为trans2_deploy提交到集成流,trans3将会包含trans2,这是前任/派生关系的一个需求。

恢复

当一个流需要删除一些有问题的变化时,恢复就产生了。如果一个基线被发现有一些严重的问题,流可以恢复到一个早先的基线。然而,只有在涉及的构件中没有变化时,流才能被允许恢复;如果变化已经发生了,ClearCase将必须“解散”这些变化。

如果一个流即使已经发生了变化,还必须要恢复,开发者将必须明确地在变基之前删除新的版本。

横向变基

横向变基发生在基线之间没有继承或者派生关系的时候。当从一个导入基线(或者是它的一个派生基线)变基到一个不同的导入基线(或者是派生基线)时,横向变基就发生了。变基到一个被导入的基线几乎总是一个横向变基。发生的例外情况是,当被导入的基线是当前基线的一个被继承基线时,会改变恢复的方向。被导入的基线没有前任基线,因此他们只与它们的派生基线有关系。另一种横向变基是,当新的和旧的基线是堂兄弟关系时--就是说,共有一个共同的祖先--但两者都不是另一个的祖先。

横向变基的一个用例是一个使用第三方软件的项目,比如一套编译器或其它的工具。项目不在这些工具上进行开发,但是有时它接收一个新的版本。新的版本被放到一个VOB中,进行鉴定,并且如果它通过了,会被打上标签。这个标签随后被导入到一个基线中;变基到这个被导入的基线就是横向变基。

图10:一个横向变基的例子 -- 变基到一个被导入的基线

如果没有基线之间的关系,ClearCase在如何合并这些变更上就会没有引导。这就意味着ClearCase必须遵守以下横向变基上的约束之一。或者是:

  • 构件是不可修改的,并且在其上不存在基线(-ident baselines)

Or

  • 新的基线必须被包含在父流中(使用提升)。

 

使用多个构件管理项目
在任何项目中,项目负责人必须维护被项目的集成流所推荐的基线集。这就意味着要有规律地创建基线 ,测试基线化代码(可能在一个构造流中),建立推荐基线。项目负责人应当负责确保来自不同构件的每个推荐基线集与其它的相一致。此外,他还需要维护推荐基线集的历史信息,以防万一项目碰到某种情况,必须恢复到一个早先的基线集。为了便于跟踪哪些基线已经达到了一定的质量水平,UCM提供了晋升级别。一个基线可以被标记为已构造(BUILT),已测试(TESTED),已拒绝(REJECTED),已发布(RELEASED),等等;晋升级别的列表是可定制的。一些项目使用基线命名方案来帮助了解哪些基线在一起工作(基线命名在2003版里进行得更灵活)。

随着一个项目中的构件数量的增长,管理基线的成本也显著地增加了。另外,较大的开发工作经常将工作拆分成许多构件 -- 大的构件由多个CM构件组成。一个大的构件或子系统的状态,被表示成CM构件上的一组基线,增加了项目负责人必须明了的一致基线集的数量。此外,如果子系统是从其它子系统构造的,问题就会有越来越难以管理。

管理基线集的挑战在于遇到使用复合基线

什么是复合基线?
在ClearCase 2002版介绍过,复合基线是将基线组成一个集合的机制。一个基线被指定为复合基线,并且其它基线变成复合基线的成员。

在图11中,基线A1和C3是复合基线Alpha1的成员。

图11:一个复合基线

成员关系意味着,在A1中记录的版本必须与基线C3所表示的版本一起,使用基线Alpha1中的版本发布。成员资格意味着一个代码依赖,所以一个成员基线可以说是依赖于复合基线。成员基线可能自己就是复合基线,因此一连串的依赖关系形成了一个非循环的定向图(不限制成员关系的深度),如图12所示。

图12:一个包括复合基线的复合基线

 

为什么要使用复合基线?
使用复合基线可以大大地简化基线管理,因为对于整个基线集,只有一个基线需要被指定。复合基线表示了所以成员基线的关闭。例如,一个集成流可以构建一个复合基线,其成员包括此流中的所有构件的当前基线。管理员只需要推荐单个复合基线,而不是推荐所有成员基线。另外,该集成流的开发流可以变基到单个基线,就可以获得所有成员基线。因而,在一个流的基础集中包括的Alpha1基线(如图11所示),将意味着流将会从基于成员基线A1的构件A,和从基于成员基线C3的构件C中选取版本。

这样,复合基线就可以用来建模由多个CM构件组成的构件或者子系统。复合基线也可以表示一个子系统,并且允许项目将较低级别的子系统组合成较高级别的子系统。在最低级别,CM构件被包含在一个复合基线中,然后较高级别的构件被一个由复合基线和CM构件的基线组合所构成的复合基线所表示,如图13所示。

图13:复合基线可以表示构件的层级结构

在图13中,Mathlib1复合基线已经被重画以高亮显示,表示Mathlib子系统是由Alpha和Gamma子系统,以及一个CM构件F组成。

注意,图表示了Mathlib1基线的依赖关系树:所有描绘的对象都是基线,因此这个图表必须在流的环境中进行理解。这个例子例示了一个面向构件的项目。Mathlib1基线是Mathlib项目集成流的产物。在此例中的其它项目产生了其它的构件 -- Alpha和Gamma基线。

创建复合基线
 

创建复合基线 -- 编辑基线依赖关系

你可以通过命令行或者图形化界面的方式创建复合基线。然而,它们必须被引进到一个集成流中。基线依赖关系是基于流的环境--它们不允许使用一个提交操作来传播。在提交中,对单独构件的变化被合并到目标流,但是没有传播任何基线关系。变基操作是一个流接收有关基线信息的方式,并且它必然是获得一个复合基线的唯一方式。

要创建第一个复合基线,将拥有复合基线的构件需要包含在集成流的当前配置中。如果有必要,先将集成流变基到构件的合适基线,然后再增加基线依赖关系。

从命令行中,通过在一个构件上创建一个新基线,并且指定其基线将要成为复合基线成员的构件,来创建基线依赖关系。由于是和任何其它基线一起,这些工作必须在一个连接到创建基线的集成流的视图环境中进行。

例如:

cleartool mkbl -component Alpha -adepends_on A,C ALPHA_BL

这个命令在构件Alpha上创建了一个复合基线。构件A和C的基线将会是新复合基线的成员;除非构件A和C已经发生了变化,否则就使用这些构件上的当前基线。

参见ClearCasemkbl使用指南,可以得到更明确的详细内容和例子。

使用图形界面方式来创建一个复合基线是更直接的方式,因为它利用了拖放方式来构建复合基线依赖关系。从集成流的上下文菜单中选择Edit Baseline Dependencies。在Edit Baseline Dependencies对话框中,将其基线将成为复合基线成员的构件拖拽到将具有复合基线的构件下。

图14在ClearCase图形界面方式下创建一个复合基线
(点击这里放大)

在此例中,构件B、C和E上的基线已经成为构件Gamma上的一个复合基线的成员。注意,B、C和E在Edit Baseline Dependencies对话框的最高端依然可见,这是因为这些构件仍然在集成流的基础配置中。关于如何操作这些基线的更多详细内容,请参见标题为引导项目的部分。

在任何时候,一个新的复合基线都可以增加或删除成员基线。

注意,无论在命令行方式,还是在图形化界面方式中,看起来都要操作构件以创建复合基线。然而,关系实际上是在基线之间,而不是在构件之间建立。说构件B依赖于构件Gamma是很诱人的,但是依赖关系只有一个有限的范围。依赖关系可以持续一个特定项目的生命周期,但是它可能有别于其它的项目,所以依赖关系图将会由于构件被增加或删除,随着时间变化而变化。由于此原因,依赖关系必须建立在构件的版本之间 -- 基线 -- 而不是在构件本身之间。

Furthermore, it also appears that the command line interface and GUI handle editing dependencies differently.此外,使用命令行界面和图形化界面处理依赖关系编辑也不同。 However, the operations are the same. Options to the /////然而,操作是相同的。mkbl命令的选项可以修改基线关系,并创建基线。图形化界面有两个菜单选项 -- Make BaselineEdit Baseline Dependencies。后面的菜单选项也允许你改变基线关系和创建基线。前面的菜单选项行为表现与命令行mkbl(没有增加或删除基线依赖关系选项)一样。

创建一个复合基线的派生基线

当一个复合基线的一个继承基线被创建时,新的基线会继承其前任基线的成员 -- 除非明确地增加或删除构件。当创建了一个新的派生基线时,ClearCase检查复合基线的依赖关系图,并且在需要时就创建新的基线。这里有三种创建一个新基线的原因:

  • 自从最新的基线被创建后,在构件中发生了变化
  • 依赖关系图发生了变化(增加或删除一个成员)
  • 在一个成员构件中发生了变化

由于新的基线被创建,在依赖关系图中的一个页节点的一个变化将会一直传播到根节点,以包括新的成员基线。

例如,图15举例说明了一个情景,在该情景中,构件C中进行了代码变更,并且然后在构件Alpha上创建了一个新的基线。ClearCase检查基线依赖关系图,并发现在构件C上完成的工作,因此它创建了一个新的基线(C4)来获取这个变化。接下来,由于基线C4取代了基线C3,并且C3是B1的一个成员,ClearCase将创建一个新的基线(B2)来记录与C4的关系。最后,由于B1被替代了,所以需要在Alpha上有一个新的基线。

图15:如何创建一个复合基线的派生基线的例子

在层级结构中创建基线是必需的,因为复合基线的派生基线表示了流的配置。

在复合基线中使用无根构件
如同前面所描述的,当创建一个复合基线的一个派生基线时,一个新的成员基线可能意味着三件事情中的一个:

  • 在构件中发生了变化
  • 在基线依赖关系图中发生了变化
  • 在一个成员构件中发生了变化

由于这些不同的可能含义,一个复合基线可以实现多重任务:在其构件中确定一组版本,收集一组成员基线,并且在成员构件中发生变化。确定哪一个原因引起了基线创建代价可能很高(在性能方面)。

然而,无根构件没有一个相关联的根目录,因而不包含任何元素。这样,一个无根构件无需涉及确定一组版本的任务:在一个无根构件上创建的一个新的复合基线只能表示基线成员中的变化。在一个无根构件上的一个复合基线,有时被称为一个纯复合基线,完全是基线的一个集合。

考虑两种连接构件A和构件C的不同的复合基线(图16)。

图16:在一个复合基线中连接基线的不同方式

在直接连接的情况下,A1是构件A上的一个基线,并且也是定义构件之间依赖关系的一个复合基线。如果构件A的一个元素改变了,或者构件C创建了一个新基线,我们将需要构件A的一个新基线。注意,这种安排表示了这两个构件依赖关系的方向:也就是说,在构件A中的某些东西引用,或者反过来依赖构件C中的某些东西。

另一方面,当构件A和C上的基线通过一个纯粹的复合基线(在无根构件Alpha)连接在一起时,不会提示有关这两个构件之间依赖关系方向的信息。依赖关系只表示这两个基线在流中已经在同一时间被一起使用。然而,这并不说明构件A依赖于构件C。

使用一个纯粹的复合基线,Alpha上的一个新基线只有一个含义:即复合基线的成员中有一些变化。我们将看到在标题为复合基线的最佳实践的部分中,这是极其重要的。

复合基线中的冲突
复合基线促进了构件的重用,使得在一个项目中包含大量的构件和子系统变得更容易。可是随着共享构件的数量逐渐增加,不同子系统将会产生基线冲突的潜在风险增加了。

如果一个复合基线只包含明确命名为成员的独立构件的基线,就不会有任何基线冲突。然而,使用子系统的较高层项目可能在其基础配置中包括许多组复合基线,这些复合基线的成员可能在相同的构件中。对于一个特殊的构件,当成员不是相同基线时,冲突就产生了。

举例来说,假定Alpha1和Gamma2都是包括构件C上的一个基线的复合基线。图17使用Alpha1的一个成员基线C3和Gamma2的一个成员基线C1,说明了这个例子:

图17:复合基线Alpha1和Gamma2冲突的例子

当Alpha1和Gamma2被一起放在Mathlib项目中时,如图18所示,ClearCase就不能断定要使用构件C上的哪一个基线。这就是ClearCase的一个基本公理,即一个视图为了选择一个元素的版本,必须有一个明确的规则。在UCM中,明确的规则意味着一个流只能使用一个基线来选择一个构件的版本。ClearCase将会阻塞导致冲突的变基操作(和基线推荐操作)。

图18:在使用复合基线时的基线冲突

ClearCase要求用户为有疑问的构件明确指定一个基线 -- 在本例中,是构件C -- 以解决基线冲突。被选择的这个基线表示要覆盖冲突的复合基线的成员。明确地包含在一个流的基础配置中的一个基线 -- 无论它是否解决了一个冲突 -- 将会覆盖一个复合基线所指示的构件的任何基线。

注意,面向发布的项目比面向构件的项目更容易避免冲突。在前者,所有构件的基线通常都是从相同的流(例如,集成流)中(被一个任务流或开发流)选择的。在此流上的最高层复合基线通过复合基线的良好定义,必需是首尾一致的。换句话说,在一个流中所构建的复合基线的所有成员基线必须是一致的 -- 任何构件中的任何元素同时只能选择一个版本。

在图19中,项目负责人选择基线Cx来解决构件C上的冲突。结果,Alpha1中的基线C3和Gamma2中的基线C1被忽略了,并且Cx被用来选择构件C中的版本。

图19:使用覆盖解决基线冲突

注意,项目负责人不是必需要选择冲突基线的某一个。冲突构件的另一个基线可以被选择为覆盖。对于新的流,它可以是任何其它基线。对于变基,必须满足控制变基方向的规则。项目负责人必须选择一个与项目的基线集中的其它基线一致的一个基线。在图19的例子中,项目负责人可以选择C1作为Cx的覆盖替代,但是必须确保Alpha1所选择的配置与C1是一致的。选择覆盖基线Cx,将必须检测Alpha和Gamma子系统,以确保它们与Cx是一致的。

注意:覆盖将一直生效,一直到覆盖基线明确地从基础基线集中删除掉(例如,使用cleartool rebase -dbaseline),或者基础基线集被完全替代掉(在变基到父流地推荐基线集时发生:cleartool rebase -recommended)。

对于项目经理来说,覆盖基线的选择是UCM重点突出的一项决定,但是此决定不能自动化。项目团队必须在每一个冲突的情况下,确定正确的覆盖。

复合基线的最佳实践
 

引导项目
我们可以看到,在图形界面中编辑基线依赖关系的例子中,当创建一个复合基线时,不会影响创建该复合基线的流的基础配置。在我们创建Gamma上的复合基线后,流的基础配置仍然包含构件B、C、E和Gamma上的基线。为了让开发流有一个合适的配置,那么集成流需要只推荐复合基线。尽管如此,把所有成员基线放在集成流的集成配置中可能会混乱。

解决此混乱的一种方式是建立一个特殊项目去“引导”复合基线。引导项目应当包括其基础配置中的所有构件。当我们在引导项目中创建复合基线时,一个开发项目就可以从此初始的复合基线开始,并作为其基础配置。

图20:一个为其它项目使用而创建gamma和mathlib上的复合基线的引导项目

在一个引导项目中,集成流和开发流有相近的基础配置。项目负责人不必需要了解单个的基线,因为项目可以从一个单个的、包括所有的复合基线开始。持续项目在尝试选择基线以包括在其基础配置时,不会产生混淆。

如果你知道开发开始时基线关系,并且不期望它们在以后进行修改,引导项目就会是有用的。

为复合基线使用无根构件
当你使用一个无根构件去创建一个纯粹的复合基线时,一个新的复合基线只有一种含义:在基线依赖图中已经有了一个变化。这就使得ClearCase通过纯粹的复合基线提供了比基于普通构件的复合基线更大的配置灵活性。

我们可以看到Mathlib项目中的纯复合基线的重要性的图解。考虑到一个复合基线完全是由普通构件的基线组成的。假定,项目在构件F中有一些开发工作,并且项目负责人想要创建一个基线捕获它。

图21:Mathlib项目在构件F上有变化

当项目负责人尝试创建一个新基线时,此操作检查Mathlib上的基线依赖图。然而,ClearCase也看到Alpha和Gamma复合基线已经变化,因为它们的成员C3和C1被Cx所覆盖,不再可见了。这样,新的基线将在构件F、Alpha、Gamma和Mathlib上创建。

图22:在一个新基线创建之后的Mathlib复合基线的状态

在Alpha和Gamma上将会创建基线,即使它们在项目中被标记为不可修改的。在这些构件中没有进行明确的变化。可以,在Mathlib集成流的环境中,Alpha和Gamma包含了基于Cx的变化。首先,这不可能出现一个真正的变化,但是因为项目领导在Mathlib子系统的配置中选择使用Cx(覆盖),这表示了一种变化。Mathlib子系统将使用Cx进行测试,并且不能与C3或C1一起发布,因为它不是与这些基线一起构建的。

注意,在Mathlib2被创建以后,M_Alpha2和M_Gamma3包括了覆盖基线Cx。M_Alpha2和M_Gamma3之间没有冲突,因此当另外一个流变基到Mathlib2时,将不会需要去解决构件C中的冲突。当然,这在构建Mathlib2的流上没有效果;Cx的覆盖在那里仍然需要。

在此系统中理解这个非常重要,基线M_Alpha2和M_Gamma3是必需的。这是因为Mathlib2的真正含意是,所有由其成员所指示的基线表示了在Mathlib2被创建时的流的状态。如果测试和确认在此时发生,那么这可能是一个重要基线 - 例如,一个可以发布的基线。同样,因为确认发生在Cx上,而不是C1或C3,Mathlib2的成员必须么表示此配置。

Alpha和Gamma的新基线在Mathlib项目上有非常实际的效果:项目现在被限制为只能推进这些构件 -- 它们不再能恢复或变基到旁边的基线

这会成为一个问题,例如,如果在此开发团队中的不同项目(Alpha,Gamma,Mathlib)正在将他们的工作提交到一个集中的项目Main_Proj上,如下图23所示。

图23:Mathlib和产生子系统的项目提交到一个主项目

如果Mathlib项目从Main_Proj(例如Alpha1)中的一个基线开始,那么在它创建M_Alpha2基线之后,就不能再从Alpha项目获得最新的基线(trans_2)。从Alpha1变基到trans_2是一种侧向变基,因为两个基线都不是另一个基线的祖先。由于Mathlib项目置于Alpha上的基线所表现出的构件Alpha的变化,侧向变基在ClearCase中是不允许的。

如先前所论述的,如果Alpha、Gamma和Mathlib是无根构件,侧向变基的限制就不适用了。因为无根构件不包含变化,ClearCase认为在这些构件上没有冲突,因此变基规则放宽了。

图24:Alpha、Gamma和Mathlib是无根构件情况下的Mathlib子系统

由于变基操作变动了依赖关系图,这些规则仍然是实用的,因此每个复合基线应当在一个无根构件中创建。另外,变基操作只有在页结点构件中的一个有代码变化时会发生阻塞 -- 在有可能丢失的代码变化时。

注意:Release v2002版和v2003版在此情况下,要求一个补丁,才能正确地进行工作。

逻辑上,无根构件表示了子系统。Alpha和Gamma是正在被使用来产生Mathlib子系统的构件。这些构件是无根的事实可以看作是反映了子系统是逻辑结构的事实。

迁移到无根构件

如果一个例如Mathlib的项目已经使用普通构件创建了一个复合基线,将项目改为使用纯复合基线并不困难。

项目领导者可以创建无根构件,以代替每个具有复合基线的构件。在Mathlib的例子中,这些可能是Alpha_nr,Gamma_nr和Mathlib_nr。然后新的复合基线可以在无根构件上创建,并将包括原来的构件作为一个成员,如图25所示。

图25:将一个复合基线从一个有根构件更改为一个无根构件

避免无根构件的策略

并不是所有项目或项目定位需要由使用无根构件所提供的灵活性。它们在发生基线冲突时需要使用,并且项目构架可能要求一个项目进行侧向变基,从多个不同的源流和项目中货的基线。

例如,一个不包括覆盖复合基线的项目,将永远不会产生冲突。在面向发布的项目的开发团队中,一个项目可能只使用前一个项目的基线,因此变基的灵活性通常可能不需要。

为代替无根构件,项目领导者要确保子系统与共享构件进行同步。当在构件C上创建一个新基线时,使用该基线的所有项目也要为它们的基于此基线的子系统发布一个新基线。这就防止了基线冲突,尽管这可能只是对于那些使用较小数量共享构件的项目是可行的。

使用构造流来稳定复合基线
因为复合基线聚集了多个构件的基线,在一个集成流中进行小的、单独的变更就会更困难,例如那些由于达到一个里程碑而需要进行版本稳定化的变更。

对于任何项目,如果测试是基于集成流,测试一个预备发布的版本以公布其基线的任务可能是困难的。在一个特定活动被提交完成之后,项目负责人可能希望构造并测试此产品。例如,严重的bug必须在一个基线被公布之前被解决掉,在此期间,可能会有其它已经提交的活动,因而这些需要进行测试。项目负责人可以锁定集成流,以防止这些额外的提交,但是这将会停止与稳定化此发布版本相关的所有开发工作。

要解决此问题,可以创建一个专注于构造稳定化任务的流。项目负责人制定一个还未测试版本的初始基线,并使用此基线作为一个构造流的基础基线。然后,项目负责人就可以控制在此构造流上产生哪些变化。可以直接在构造流上进行修复工作,也可以在一个开发流上修复完成后提交到构造流上。在构造流中实施的修复工作将不会受到正在进行的对集成流的提交工作的影响。

图26:一个构造流的例子

当构造流上的产品稳定时,你可以在构造流中创建一个基线,并将其提交到集成流上。然后构造流的基线被集成流所推荐,并且开发流可以变基到此基线。

面向发布项目中的复合基线
当项目按照一种面向发布的方式进行组织时,使用复合基线可以缩短推荐基线的列表。较短的列表可以使得了解在不同时间推荐了哪些基线变得更容易。复合基线可以用来表示产品中的主要子系统,或者在最简单的情况下,一个项目可以将其所有基线统一到一个单一的构件中,从而被推荐和提交到后续项目。

此外,使用一个单一的复合基线表示最终产品--集中所有构件的基线--减少基线冲突的几率。如果一个项目使用一个单一的复合基线,项目负责人就可以实施一个过程规则,强制所有的构造从最顶端的构件产生,这将会减少一个开发者由于疏忽而引入冲突的概率。

当开发者需要访问那些没有包括在推荐基线集中的内容时,他们仍然可以选择使用覆盖基线。例如,如果一个开发者需要一个bug修复内容,而还没有完成基线推荐过程,开发者就可以取得包含此修复的特定构件的合适基线,即使并没有基线冲突。

当在此情形中使用覆盖基线时,需要多注意一些,因为有可能将一个冲突引入到一个流中。当多个复合基线被覆盖时--但是只有在被覆盖的基线是复合基线时,冲突就可能发生。当一个单一的复合基线被覆盖时,或者如果CM构件的基线被覆盖时,不会产生冲突。

使用复合基线建模一个面向构件项目的构件
当一个项目团队按照构件组织其工作时,每个构件或子系统为了进行开发工作都有其自己的项目,重大成果可以通过使用复合基线建模子系统而实现。集成子系统的项目可以变基到一个单一基线来获得一个子系统。如果使用无根构件来表示子系统,那么使用其基线的流随后将会有更多的灵活性,以变基到该子系统的其它复合基线上。

复合基线可以被用来建模子系统,它是由较小的构件所构造的逻辑构件。在构造一个子系统中所使用的构件分成两种类别:共享的构件,和包含子系统的特定工作的构件。共享构件的重要属性是它们都是不可修改的。如果一个子系统要求对一个共享构件进行修改,将会减少共享该构件的能力。可修改的定制构件存储了需要用来集成共享构件和提供子系统唯一功能的代码。

返回到Mathlib子系统的例子,图27举例说明了在共享构件中的潜在冲突:

图27:Mathlib子系统

Mathlib由共享构件Alpha、Gamma和定制构件F组成。无论Mathlib是一个产品,一个.dll,一个较低级的库,或者其它一些构件。

在Mathlib例子中,一个面向构件开发的方法比一个面向发布的方法更容易引起基线冲突。在多子系统中,一个低级别构件的使用将常常会在一个较高级别的子系统中造成冲突。试图通过协调所有项目避免这样的冲突不太可能付诸于实践,因此任何低级别的构件只能有一个单一基线被任何项目在同一时间使用。

同样,如果开发团队需要在挑选他们所使用构件的基线选择上有完全的自由,那么冲突将很可能发生,因为确保一个构件(如Alpha)的一个随机基线将会使用另一个构件的一个随机基线是有难度的。

虽然将Mathlib认为是Alpha和Gamma的“消耗”基线是非常有诱惑力的,但是这不是一个真正的产生者/消耗者的关系。如同在标题为为复合基线使用无根构件的部分中,Mathlib不是使用Alpha1基线;它使用Alpha1,加上覆盖基线Cx。这样,Mathlib就不是真正地在使用Alpha项目的产品。Alpha项目不产生Alpha基线,但是在出现冲突的情况下,对与需要Alpha而不是一个规则的项目来说,基线必须被看作是一个指导方针。

由于覆盖冲突在面向构件的项目中是一个正常情况,管理员身上就会有一个负担,确保他们正在挑选一致的基线作为覆盖基线。这就要求不同项目负责人之间的协调,因为系统不能自动地作出这样的决策。

这样,在一个面向构件开发组织中挑选一个覆盖,需要成为一个小心的和协商的过程。项目负责人需要知道,在挑选一个覆盖时,他们可能正在拒绝产生构件的项目所作出的决策。可能会有为什么使用一个C1基线构造Gamma的特定原因,并且在一个不同基线被使用时,可能会产生中断。通常,使用C1的一个派生基线将会工作,但是ClearCase不限制覆盖基线的选择。在C1和覆盖基线Cx之间的关系没有保证。

随着一个构件的交付日期的临近,覆盖基线的使用可能会使一个产品不稳定,因为它们表示了代码差异的显著变化。变更需要通过协调产生每个子系统的项目的工作,而进行管理。随着发布日期的临近,团队会与有关较低级构件进行同步,因而减少冲突。

总结
复合基线通过使用一个单一的标识符,提供了表示复杂系统的能力,并且保留了独立管理较低级构件和子系统的灵活性和控制能力。复合基线通过在较高级系统中确定和包含那些构件的特定版本,增强了构件和子系统的可重用性。然而,随着系统的规模和复杂性的不断提高,使用共享构件的不同基线的子系统之间的冲突可能性也增加了。当计划项目层级结构和基线策略时将极大地增强一个开发团队获得复合基线所提高好处的能力时,小心这些限制,并将它们考虑在内。可重用性和简化的项目管理工作将会最小化要求克服基线冲突的工作量。

参考资料

  • 您可以参阅本文在 developerWorks 全球站点上的 英文原文。
     
 
 

版权所有:UML软件工程组织