Per Kroll (pkroll@us.ibm.com), Director, Rational Unified Process, IBM
2004 年 4 月 01 日
本文来自 Rational Edge :一个理想的迭代开发方法模型在很多方面与理想的瀑布开发模型有着根本上的不同。但是,从实际来说,没有一个团队严格的应用了每一种开发方法模型。本文解释了为什么开发团队决定逐步的从类似瀑布型的开发方法转变成更加类似迭代开发的方法,同时也概述了能够帮助这种转变的步骤。
多数的软件开发团队仍然在开发项目中使用瀑布型 的开发过程。采用极端的瀑布型开发方法意味着你要以严格的顺序来完成一系列的项目阶段:需求分析、设计、实现/集成然后是测试。当项目中出现的问题解是困难的并且解决问题是昂贵时,你可能会推迟测试直到项目周期的末端;这些问题也能够严重的威胁软件发布的期限并且使主要的团队成员在某些开发环节上是空闲的。
实际上,多数的开发团队使用了改进了的 瀑布型开发方法,他们将项目分解成为两个或者更多的部分,有时这些部分被称为阶段或者是时期。这种改良可以帮助简化集成、使测试人员更早的进行测试工作和提供更早的项目状态的观测。这种方法也将代码分解成了易于管理的片断并最小化了以存根和驱动程序形式的、被测试需要的代码集成。此外这种方法允许你原型化你认为有风险的或者有难度的部分,并且使用来自每一个阶段的反馈修改你的设计。然而,使用瀑布型开发方法的执行与想象是相反的:很多设计团队把在阶段 1 之后的修改设计视为他们的最初设计或者需求过程的失败。虽然一个改进了的瀑布型开发方法并不排除反馈的使用,但是它并没有促进、支持和鼓励反馈的使用。最后,想要最小化风险就不要典型的驱动一个瀑布型的开发项目。对于软件开发过程来说,本文探索了”迭代“开发方法超越传统的瀑布型开发方法的进步。
相比较而言,迭代开发方法 — 以 IBM 的 Rational 统一过程 ®,或者 RUP ®为例— 包括了一系列的增量的步骤或者迭代。每一个迭代都包括一些或者很多的开发活动(需求、分析、设计、实现等等),就像你在图 1 中看到的那样。每一个迭代也有一系列良好定义的目标并生成最终系统的部分工作实现。每个后续的迭代都建立在前一个迭代的基础上以使系统得到发展和细化,直到最终产品被完成。
早期的迭代着重于需求、分析和设计;后期的迭代着重于实现和测试。
图 1: RUP 的迭代开发。每一个迭代都包括需求、分析、设计、实现和测试活动。同时,每个迭代都建立在前一个迭代工作的基础上,每一次迭代都会生成更加接近最终产品的可执行版本。
迭代开发方法已经证明了自己优于瀑布型的开发方法,理由如下:
- 它允许需求的变化。需求的变化和“进一步的蔓延” — 技术和客户驱动的特性的累加 — 一直是项目中导致麻烦、延期交付、令客户不满意和使开发人员泄气的主要原因。为了解决这些问题,使用迭代开发方法的团队应该在项目开发的几周里就关注生成和演示可执行的软件,这样就强制了需求的检查并可以帮助减少需求从而反映系统的本质。
- 集成不是在项目的尾声进行的“大动作”。将系统的集成留到项目的结尾几乎总是会导致耗时的返工 — 有时这种返工会花费整个项目工作量的百分之四十的时间。为了避免这种返工,每一次迭代都以集成构建系统各部分结束;这样不断的积累将最小化日后的返工。
- 早期的迭代可以暴露风险。迭代的开发方法可以帮助团队在早期的迭代中减少风险,因为在这些迭代中包括了对所有过程组件的测试。当早期的迭代覆盖了项目的很多方面时 — 工具、购买的软件和团队成员的技能等等 — 团队能够很快的发现被预感的风险是否是真实的,并且能够在问题相对容易并花费很少成本解决时揭示没有被发现的新的风险。
- 对产品的管理能够采取战术性的变化。迭代开发能够快速的生成可执行的架构(虽然功能有限),这个架构能够为了应对竞争对手的快速版本发布容易的调整产品使之成为”轻量级的“或者“改进的”版本。
- 它使重用更加容易。识别在迭代中进行的部分设计和实现的公用部分要比在计划期间找出公用部分更加容易。在早期开发中的设计评审允许架构师们发现潜在的可重用的机会,并且利用这个机会为接下来的迭代开发成熟的公用代码。
- 你能够在每一个迭代中发现并更正缺陷。这会生成健壮的架构和高质量的应用。你甚至能够在早期的迭代中而不是在项目末期的大规模测试阶段发现缺陷。你能够在性能瓶颈没有破坏你的计划之前发现它。
- 它能够更好的利用项目的人员资源。很多开发组织使用一种管道式的组织方式来匹配他们的瀑布型开发方法:分析人员将被完成的需求发送给设计人员,设计人员将被完成的设计发送给开发编程人员,编程人员再将他们开发的组件发送给集成人员,集成人员将组件集成起来发送给测试人员测试。这种多次的传递不仅容易产生错误而且应用造成误解;这种方式也会使人们感觉他们对最终的产品有很少的责任。迭代开发过程鼓励在项目的各个环节中团队成员参与范围更加宽广的活动,允许团队成员扮演多种角色。项目经理能够更好的利用可得到的项目人员并其可以消除有风险的传递。
- 团队成员能够沿着项目的道路进行学习。工作在迭代开发的项目中的开发人员在软件开发周期内有很多的机会从他们所范的错误中吸取教训,并能够从一个迭代到另一个迭代的过程中增进他们的技能。通过评估每一个迭代,项目经理能够为团队成员发现培训的机会。相反,工作在瀑布型开发方法中的开发人员典型的被限制在狭窄的技术专长上,并且仅仅有机会从事设计、编码或者测试之一方面的工作。
- 你能够沿着项目的道路改进开发的过程。迭代末尾的评估不仅能够从产品或者计划方面揭示项目的状态;他们也可以帮助项目经理分析在下一个迭代中如何改进项目的组织结构和过程。
一些项目经理反抗采用迭代的开发方法,将迭代开发视为一种没有尽头的、不可控的形式。然而,在 RUP 中,这个项目是能够被很好的控制的。迭代的次数、周期和目标被仔细的计划;并且项目参与者的任务和角色被良好的定义。此外,进展的客观量度应该能够被获取。虽然团队要从一个迭代到下一个迭代中重做一些事情,但是这个工作也是可以被仔细的控制的。
|
多数的瀑布型的项目将开发工作划分为阶段或者时期;我们也可以将这个划分视为向迭代方法转换的第一步。但是为了实现向迭代开发方法的过渡,我们要使用下面四个步骤来应用不同的过程原则:
- 尽早的构建功能原型。
- 划分详细设计、实现和测试阶段到不同的迭代中。
- 在项目早期基线化一个可执行的架构。
- 采用迭代式的和风险驱动的管理过程。
让我们来更进一步的解释每一个步骤。
作为向迭代开发转换的第一步,在需求和设计阶段考虑一个或者更多的功能原型。这些原型的目标是减少主要的技术风险和澄清项目涉众对系统应该做什么的理解。
通过识别最重要的三个技术风险和最重要的三个有必要澄清的功能部分开始。技术风险也许与新技术、对整个方案影响很多的未决定的技术选择或者你知道的很难满足的技术需求有关。功能上的风险也许与项目涉众为关键的功能性提供了模糊的需求或者几个需求对项目系统都是核心的有关。
对于每一个重要的技术风险,拟订你要原型化什么以减少风险。考虑一下下面的例子:
技术风险: 项目需要将一个已存在的应用移植到 IBM WebSphere Application Server 上运行。用户已经开始抱怨应用的性能,并且你所担心的是移植后也许性能会更加的慢。
原型: 构建一个架构的原型来找出移植你的应用的不同方法。要求一个专家级的 WebSphere 架构师来帮助你。评价结果并编写架构的和设计的指导方针为开发团队提供什么 应该做和什么 不应该做。这将增加你移植的应用的性能是足够好的以避免在项目后期返工的可能性。
技术风险: 你正在为安排和估计软件项目构建一个新的应用。你知道对于这个应用和购买的软件的关键区分将是如何能够很好的支持迭代计划。然而,这也是在你的需求说明中最模糊的部分之一。
原型: 基于你关于如何支持迭代项目计划的设想构建一个功能原型。通过向不同的项目涉中演示原型,你将可以鼓励他们更加注意项目的计划,并且他们能够告诉你他们对你的哪些设想是赞同的、哪些是不赞同的。原型将帮助你澄清计划的需求,也能够为你提供关于用户体验和对于你的应用的外表和感觉的有用的信息。这个原型甚至能够产生一些可重用的代码。
很多项目团队发现在他们知道项目是真正关于什么的之前划分一个项目成为有意义的迭代是困难的。但是,当你已经进入了详细设计阶段时,你通常对需求是什么和系统的架构看起来象什么样子有了很好的理解。这是我们试验迭代开发的时候了!
你能够使用两个主要的方法来确定你应该在什么样的迭代中作些什么事情。让我们从正反两方面讨论一下每一个方法。
方法 1 :同时开发一个或者多个子系统。让我们假设你有九个子系统,每一个都有数量日益增加的组件。你可以划分详细设计、实现和测试阶段到三个迭代中,每个迭代瞄准实现九个子系统中的三个。如果在不同的子系统之间存在有限的依赖这将工作的相当的好。例如,如果你的九个子系统的每一个都为用户提供良好定义的一系列能力,你可以在第一个迭代中开发优先级最高的子系统,其次重要的子系统在第二个迭代中实现,以此类推。这种方法有很大的优点:如果你的进度落后了时间计划,你仍然可以交付可运行的具有最重要能力的部分系统。
然而,如果你有一个分层的体系架构,在上层的子系统依赖于底层子系统的能力,这种方法将不能够很好的工作。如果你必须要在一个时间内构建一个子系统,这样的体系架构将迫使你首先构建底层的子系统,然后构建越来越上层的子系统。但是为了构建在底层子系统的正确的能力,你通常需要在上层的子系统上进行大量的详细设计和实现的工作,因为他们决定了什么是你在底层子系统中需要的。这产生了 “catch-22”的现象;第二个方法解释了如何解决这个问题。
方法 2 :首先开发最重要的场景。如果你使用方法 1 ,你一次只能开发一个子系统。使用方法 2 ,你将重点放在了重要的场景上,或者使用系统的关键方法上,然后再添加更多的不是那么重要的场景。这与方法 1 有什么不同呢?让我们来看一个例子。
假设你正在构建一个新的应用,这个应用将为用户提供管理缺陷的能力。这是一个分层的应用,被构建在 WebSphere Application Server 上,使用 DB2 作为底层的数据库。在首先的迭代中,你开发了一系列重要的场景,比如输入一个简单的缺陷。在第二次迭代中,你为这些场景添加了复杂性 — 例如,你也许使缺陷能够被一个工作流来处理。在第三次迭代中,你通过为非典型的用户提供完整的支持,比如保存部分的缺陷条目然后返回到这个条目中的能力等等。
使用这种方法,你在 所有的迭代中完成 所有的子系统的工作,但是在第一个迭代中你仍然关注最重要的场景,而将不是非常重要的或者最小难度的场景留到最后的迭代中实现。
如果你正工作在一个良好定义的体系架构的系统中时,方法 1 是更加适合的 — 比如,一个已存在系统的增进或者开发使用简单体系架构的新应用。多数构建复杂应用的项目应该使用方法 2 ,但是他们应该以这样的方式来计划迭代,这种方法能够削减后来迭代的范围以弥补可能的时间推延。
你可以将这个步骤看作是更加正式和有组织的完成步骤 1 ( 尽早的构建功能原型)的方法。但是什么是“可执行的架构”呢?
可执行的架构是系统的部分的实现,它被构建以演示架构的设计所支持的关键的功能。更重要的是,它能够证明设计能够满足对于性能、生产能力、容量可靠性、可测量性和其他方面的需求。构建一个可执行的架构允许你在稍后的阶段中在一个坚实的基础上构建所有的系统功能性的能力。这个可执行的架构是一个 进化的原型,它的目的是当系统的架构成熟时,保持已经被证明的特性并保证他们最大可能的满足系统的需求。换句话说,这些特性将是交付系统的一部分。与你在步骤 1中构建的 功能原型相比,这个进化的原型覆盖了架构问题的所有方面。
生成一个进化的原型意味着你要设计、实现和测试一系统的个框架结构或者架构。在应用的角度上,系统的功能还没有被完成,但是大多数的构建模块之间的接口已经被实现了,你能够(并应该)在某种程度上编译并测试架构。指导初始的负载和性能测试。这个原型也会反映你的关键设计的决定,包括技术、主要组件和他们之间接口的选择。
但是你将如何为这个进化的原型提出系统的架构呢?关键是将重点放在最重要的百分之二十到三十的用例上(系统为用户提供的完全的服务)。这里是一些决定什么用例是最重要的方法。
- 功能是应用的核心,或者它使用的关键的接口。系统的关键功能应该能够决定系统的体系架构。通常的情况下架构师通过分析很多因素来识别最重要的用例:冗余管理策略、资源争夺风险、性能风险和数据安全策略等等。例如,在一个 POS 系统中,检查(Check Out)和支付(Pay)是关键的用例,因为它验证了信用卡确认系统的接口 — 并且它从性能和负载方面也是重要的。
- 选择描述了 必须被交付功能的用例 。交付不包含关键功能的应用系统是没有意义的。例如,一个订单输入系统如果不允许用户输入订单将是不可接受的。通常,领域和问题专家能够从用户的角度理解被需要的关键功能(主要的行为、峰值数据处理、关键控制事务等等),并且他们可以帮助定义关键的用例。
- 选择描述了系统体系架构方面的功能并且没有被其他关键用例所包含的用例。为了确保你的团队能够将注意力放在所有主要的技术风险上,他们必须理解系统的每一个区域。甚至如果一定的架构领域没有出现在高风险中,它也许是隐藏了技术上的难度,这种技术上的难度仅仅能够通过设计、实现和测试架构领域的一些功能才能够被暴露出来。
上面所列出的第一个和最后一个标准是架构师最关心的;项目经理主要关注于前两个。
对于每一个关键的用例,识别最重要的场景并用他们创建可执行的系统架构。换句话说就是设计、实现和 测试这些场景。
如果你将要遵循上面所描述的步骤 2 和步骤 3 ,那么你将会非常的接近“理想”迭代开发的模型。那么,你接下来的步骤应该是融合步骤 2 和步骤 3 ,并添加支持风险驱动和迭代开发的管理生命周期。这就是在 RUP 中被描述的迭代开式的生命周期。
RUP 对迭代开发提供了一个结构化的方法,它将一个项目划分成为四个阶段:初始阶段、细化阶段、构建阶段和转换阶段。每一个阶段包含了一个或者更多的迭代,每个迭代都关注于产生必要的技术上的可交付物以实现阶段的业务目标。团队经历的迭代次数与需要实现阶段的目标的数量是一样的,而不是更多。如果在团队计划的迭代次数内他们没有成功的实现这些目标,他们必须为那个阶段添加额外的迭代 — 并且推迟项目。为了避免这种事情发生,你一定要严格的将你的精力集中在每个阶段你需要实现的业务目标上。例如,在初始阶段将精力过于集中在需求上将会成生相反的效果。下面是典型的阶段目标的简要描述。
- 初始阶段: 通过获得对所有需求的高层次的理解和确立系统的范围来建立对系统将要构建什么的良好理解。减少多数的业务风险,为构建系统产生业务用例,并从所有项目决策人得到是否继续项目的确认。
- 细化阶段: 关心多数最具技术难度的任务:设计、实现、测试和基线化一个可执行的系统架构,包括子系统、他们的接口、关键组件和架构上的机制(例如,如何处理进程间通讯或者持久性问题)。通过实现和验证实际的代码,处理主要的技术任务,比如资源争夺风险、性能风险和数据安全风险。
- 构建阶段: 当你从一个可执行的系统架构迁移到系统的第一个可操作的版本时,需要完成大半的实现工作。部署数个内部和 alpha 版本以确保系统是可用的并且能够满足用户的需要。通过部署一个完全功能的系统 beta 版本来结束这个阶段,包括安装、支持文档和培训材料;然而,要记住功能、性能和系统总的质量将很可能需要调整。
- 转换阶段: 确保软件能够满足软件用户的需要。这个包括在系统发布的准备中测试产品,并且基于用户的反馈对系统进行小的调整。在软件开发周期的这个点上,用户反馈应该主要的关注在微调产品和配置、安装以及可用性的问题上;所有主要的结构问题已经在项目周期的早期被解决了。 1
|
在本文中,我们已经描述了你如何能够使用四个步骤逐步的从瀑布型的开发方法转换到增量的迭代开发方法。每一个步骤都以最小的中断为你的开发工作添加了切实的价值。一些团队每次不止使用一个步骤;其他的团队可能在一个步骤上运作了几个项目,然后才执行下一个步骤。然而,你应该选择使用这种明智的实施方法,因为它能够帮助你在开发组织中减少由于过程变化所带来的风险。
|
1 对于实践中 RUP 软件开发周期的详细描述,参见 The Rational Unified Process Made Easy的第 5-8 章,Per Kroll 和 Philippe Kruchten 著(Addison-Wesley, 2003)。
Per Kroll 是 IBM Rational 软件 Rational Unified Process 开发和产品管理团队的主管。自从 1992 年开发他就开发从事 RUP 的客户培训、指导和咨询工作。当 1996 年 RUP 产品团队组建时,他担任了最初的 RUP 产品经理。他也将很多精力投到了认证合作伙伴和培训 Rational 的职员的 RUP 服务上。 |