摘要
敏捷思想存在很久
敏捷运动源自 1990 年代一些与软件开发过程打交道的人士的工作,他们发现需要寻找软件开发过程的新途径。其实,这些途径中的大部分思想并不是新的,许多人认为,很长一段时间以来的成功软件都是依据这些思想来建造的。但这些思想却被压抑了,没有受到足够的重视,特别是在那些从事软件开发过程的人士中。有一些根本性的原则是所有敏捷方法的共同之处,这些原则与那些传统软件工程方法的前提假设是截然相反的。
从无、到繁重、再到敏捷
多数软件开发仍然是一个显得混乱的活动,即典型的“边写边改”(code and fix)。设计过程充斥着短期的、即时的决定,而无完整的规划。这种模式对小系统开发其实很管用,但是当系统变得越大越复杂时,要想加入新的功能就越来越困难。同时错误故障越来越多,越来越难于排除。一个典型的标志就是当系统功能完成后有一个很长的测试阶段,有时甚至有遥遥无期之感,从而对项目的完成产生严重的影响。
软件行业中最初的一场运动是要改变这种情况,而引入了“正规方法”(methodology)的概念。这些(正规)方法对开发过程有着严格而详尽的规定,以期使软件开发更有可预设性并提高效率,这种思路是借鉴了其他工程领域的实践-因此我把它们称为工程方法(engineering methodologies)(另一个广泛使用的词汇是计划驱动方法(plan-driven methodologies))。
工程方法已存在了很长时间了,但是没有取得令人瞩目的成功,甚至就没怎么引起人们的注意。对这些方法最常听见的批评就是它们的官僚繁琐,要是按照它的要求来,那有做太多的事情需要做,而延缓整个开发进程。
敏捷方法
敏捷方法(agile methodologies)的发展是对这些工程方法的反弹。对许多人来说,这类方法的吸引之处在于对繁文缛节的官僚过程的反叛。它们在无过程和过于繁琐的过程中达到了一种平衡,使得能以不多的步骤过程获取较满意的结果。
表面区别
敏捷型与工程型方法有一些显著的区别。其中一个显而易见的不同反映在文档上。敏捷型不是很面向文档,对于一项任务,它们通常只要求尽可能少的文档。从许多方面来看,它们更象是“面向源码”(code-oriented)。事实上,最根本的文档应该是源码。
本质区别
文档方面的特点不是敏捷型方法的根本特征。文档减少仅仅是个表象,它其实反映的是两个更深层的特点:
- 敏捷型方法是“适应性”而非“预见性”。工程方法试图对一个软件开发项目在很长的时间跨度内作出详细的计划,然后依计划进行开发。这类方法在一般情况下工作良好,但(需求、环境等)有变化时就不太灵了。因此它们本质上是拒绝变化的。而敏捷型方法则欢迎变化。其实,它们的目的就是成为适应变化的过程,甚至能允许改变自身来适应变化。
- 敏捷型方法是“面向人”的(people-oriented)而非“面向过程”的(process-oriented)。工程型方法的目标是定义一个过程,不管是谁用都工作。而敏捷型方法则认为没有任何过程能代替开发人员的技能,过程起的作用是对开发人员的工作提供支持。
预见性与适应性
将设计与建造分离开来
传统的软件开发正规方法的基本思路一般是从其他工程领域,如土木工程,借鉴而来。
这类工程实践中,在实际建造之前,通常非常强调设计规划。
- 工程师首先要画出一系列的图纸,这些图纸准确地说明了要建造什么以及如何建造(包括部分和整体)。许多设计决定,如怎样处理一座桥梁的负荷,在画图纸时就会作出。
- 然后,这些图纸分发给另外一组人员,通常是另外一个公司,去建造。
- 这种方式其实已假定了建造过程将按图纸而来。当然,施工中也会碰到一些问题,但这些都是次要的。
图纸其实就是一个详细的建造计划,它说明了一个项目中必须完成的各个部分,以及如何把这些部分装配成整体。
这样的计划可以进一步定出需要完成的各项任务,以及这些任务之间的依赖关系。这样,能较为合理地制订出生产进度表和项目预算。
这种模式实际上也规定了建造者如何做(施工),这也隐含着建造者不须是高智能型的,尽管他们可能都有非常高超的手上技能。
在此,我们看到的是两类非常不同的活动。
设计是难于预见的,并且需要昂贵的有创造性的人员,建造则要易于预设。
我们有了设计之后,便可对建造进行计划了。而有了建造计划后,我们进行建造则可以是非常可预见性的了。在土木工程中,建造不论在经费上还是在时间上的成本都要比设计和计划大得多。
软件开发模拟工程学科
软件工程方法:我们想要可预见的生产进度计划,以便能使用技能较低的人员。
要达到这一点,我们必须得把设计与建造分离开来。
因此,在软件开发中,我们得想法作出这样的设计,使得计划一经完成,建造将会是直接而明确的。
那么,计划应该采用什么形式呢?对许多人来说,这是设计“标识符号”(notation),如象 UML 需承担的角色了。如果我们能用 UML 作出所有主要的技术决定,那么就可以用 UML 来做建造计划,然后把计划交给程序员去编码,即是建造活动。
但这里存在一个关键问题。你是否能作出这样的设计使得它能够让编码成为一项建造活动?如果能,那么这样干的成本上是否充分地小而使得这种途径值得一用?
第一个问题是到底有多困难能使一个用类似 UML 作出的设计达到交给程序员就能直接编码的状态。
用象 UML 那样的语言作出的设计在纸上看起来非常漂亮,而实际编程时可能会发现严重的缺陷。
- 土木工程师使用的模型是基于多年的工程实践,并结晶在工程典章中。更进一步来说,一些设计上的关键部分,如应力作用,都是建立于坚实的数学分析之上。
- 而在软件设计中,我们对 UML 图纸所能做的只是请专家同行审阅。这当然是很有帮助的,但是往往一些设计错误只能在编码和测试时才能发现。甚至于熟练的设计者,我自认为我属此列,就常常对在把设计变成软件的过程中出现的错误感到意外。
另一个问题是费用比较。
- 建一座桥梁时,设计费用一般占整个工程的 10% 左右,余下的 90%左右为施工建造费用。
- 而在软件开发中,编码所占的时间一般要少得多。McConnell 指出在大型项目中,编码和单元测试只占 15%,这几乎和桥梁工程中的比例倒过来了。
- 即使把所有测试工作都算作是建造的一部分,设计仍要占到 50%。
这就提出了一个重要问题,那就是和其他过程领域的设计相比,软件设计到底是什么性质。
Jack Reeves 提出源码也应是设计文档,而建造应该是编译和链接。任何你认为属于建造的工作都应当是自动化的。
软件开发中的设计与建造
在软件开发中,具体建造费用非常低,几可忽略不计。
软件开发中所有工作都是设计,因此需要富有创造性的才智之士。
创造性的过程是不太容易计划的,因此,可预见性是个不可能达到的目标。
我们应该对用传统工程模式来进行软件开发的做法保持足够的警觉,因为它们是不同类型的活动,因此需要不同的过程。
需求的不可预见性
“这个项目的问题是需求老是在变”。
其实在建造商用软件系统中,需求变更是常态,问题是我们如何来处理它。
一种方法是把需求变更看成是因需求工程(requirements engineering)没作好而导致的结果。一般来说,需求工程(或进行需求分析)是要在着手建造软件之前,获取一幅已完全理解了的待建系统的画面,然后取得客户认可签发,并且还要建立一套规章来限制需求变更。
- 要准确获取所有需求是困难的,特别是当开发商不能提供某些需求的费用信息时。例如,你买车时想在你的车上装一个天窗,而推销员却不能告诉你要在车价上只再加 10 元钱呢,还是 10000 元。如果不知道这点,你如何能决定你是否愿意花钱在车上加个天窗呢。
- 作软件开发的费用估算是不容易的,这有多种原因。
- 部分原因是因为软件开发是一种设计活动,因此难于精确计划。
- 部分原因是系统的“基本材料”变化非常之快。
- 部分原因是开发活动极大地依赖于项目参与人员,而个体是难于预测和量化的。
- 软件的“不可触摸”性也是一个原因。在系统建成之前,有时很难判断一项功能的具体价值。也就是说,只有当你在实实在在地使用系统时,你才能知道哪些功能是有用的,哪些没什么用。
这样的结果颇具讽刺意味,即人们期待需求应该是可变的。
所以,需求不仅是可变的,简直就是应该变的。要让客户把需求固定下来要花很大的力气,特别是当他们“参与”了软件开发并且“知道”软件是多么易于修改。
使你能把所有的需求都固定下来,并不意味着你的开发就没有问题。在当今的经济形势下,决定并推动软件系统功能特性的商业因素飞快地变化着。现在一组很好的功能六个月以后可能就不那么好了。商业世界并不会因你的系统的需求固定下来了而停止不动,商业世界的许多变化是完全不可预测的。
软件开发的一切都取决于系统需求,如果需求不固定,你就不能制订出一个可预见性的计划。
预见性是不可能的吗?
一般来说,不可能。当然,有一些软件开发项目中,预见性是可能的。象航天飞机的软件开发项目,应是这样一个例子。它需要大量的会议、充足的时间、庞大的团队、以及稳定的需求。毕竟,这些是航天飞机的项目。但我并不认为一般的商用软件开发属于这类系统,所以你需要不同的开发过程。
如果你不能遵循一个可预见性方法,而你强装能够,那么这是非常危险的。
通常,一个正规方法的创造者不是很善于(或乐于)给出其方法的边界条件,换句话说,当这些边界条件不满足时,则该方法就不适用。
许多方法学者希望他们的方法能够放之四海而皆准,所以他们既不去了解,也不公布他们方法的边界条件。这导致了人们在错误的情形下来使用一种方法,例如,在不可预见性的环境中使用一种预见性的方法。
使用预见性方法具有强烈的诱惑力,因为预见性毕竟是一个非常需要的特性。
可是,当你不能达到预见性时而你相信你能够,这将会导致这样一种局面:
- 你可以很早就制订出计划,但不能适当地处理计划崩溃的情形。
- 你看见现实与计划慢慢地偏离,而你可以在很长的时间里,装着认为计划仍是有效可行的。
- 但是当偏离积累到足够的时候,你的计划就崩溃了,这通常是很痛苦的。
所以说,在不可预见性的环境中是不能使用预见性方法的。
认识到这点是一个很大的冲击。它意味着我们用的许多控制项目的模式,许多处理客户关系的模式,都不会再是正确的了。预见性的确有非常多的好处,我们很难就放弃预见性而失去这些益处。象很多问题一样,最困难的一点是认识到这些问题的存在。
可是,放弃预见性并不意味着回到不可控制的一片混乱之中。
你所需要的是另一类过程,它们可以让你对不可预设性进行控制,这就是“适应性”的作用了。
不可预见过程的控制:迭代
那么,我们如何对付一个不可预测的世界呢?
最重要,也是最困难的是要随时知道我们在开发中的情形处境,这需要一个诚实的反馈机制来不断准确地告诉我们。
这种机制的关键之点是“迭代式”(iterative)开发方法。这并不是一个新思路,迭代式开发方法已存在很久了,只是名称不同,如“递增式”(Incremental),“渐进式”(Evolutionary),“阶段式”(Staged),“螺旋式”(Spiral)等等。
迭代式开发的要点是经常不断地生产出最终系统的工作版本,这些版本逐部地实现系统所需的功能。它们虽然功能不全,但已实现的功能必须忠实于最终系统的要求,它们必须是经过全面整合和测试的产品。
这样做的理由是:没有什么比一个集成了的、测试过的系统更能作为一个项目扎扎实实的反馈。
- 文档可以隐藏所有的缺陷;
- 未经测试的程序可能隐藏许多缺陷。
但当用户实实在在地坐在系统前来使用它时,所有的问题都会暴露出来。这些问题可能是源码缺陷错误(bug),也有可能是对需求理解有误。
虽然迭代式开发也可用于可预见性环境,但它基本上还是用作“适应性”(adaptive)过程,因为适应性过程能及时地对付需求变更。
需求变更使得长期计划是不稳定的,一个稳定的计划只能是短期的,这通常是一个“迭代周期”(iteration)。
迭代式开发能让每个迭代周期为下面的开发计划提供一个坚实的基础。迭代式开发的一个重要问题是一个迭代周期需要多长。不同的人有不同的答案,XP(极限编程)建议一到三周,SCRUM 建议一个月,Crystal(水晶系列)更长一些。不过,一般的趋势是让每一个周期尽可能地短。这样你就能得到频繁的反馈,能不断地知道你所处的状况。
适应性的客户
这类适应性过程需要与客户建立一种新型的关系。多数客户愿意订一个固定价格的合同。他们告诉开发方他们所需要的功能,招标,签约,然后剩下的便是开发方去建造系统了。
固定价格合同需要稳定的需求,即一个可预见性过程。
适应性过程和不稳定的需求意味着你不能做这种固定价格的合同。把一个固定价格模式弄到适应性过程将导致一个痛苦的结局。
最糟糕的是客户将与软件开发者受到同样的伤害,毕竟客户不会想要一个不需要软件。
这并不是说,你不能为你的软件固定一笔预算。这实际意味着你不能固定时间、价格和范围(scope)。通常敏捷方法是固定时间和价格,而让范围能够可控制地变化。
在适应性过程中,客户实际上能够对软件开发过程进行很深入细微的控制。在每一个迭代阶段中,他们都能检查开发进度,也能变更软件开发方向。这导致了与软件开发者更密切的关系,或曰真正的商业伙伴关系。但并不是每一个客户,也并不是每一个开发商都准备接受这种程度的介入,不过如要让适应性过程能很好工作,这种合作程度是基本的要求。
这种开发方式可以给客户带来很多的益处。首先,这种开发的“回应性”(responsive)是很好的。一个可用的,尽管是很小的系统能够尽早投入使用。客户可以根据变更了的业务需求和实际使用情况,来及时要求改变一些系统功能。
Mary Poppendieck 的总结“后期的需求变化是个很大的优势”。
- 许多人都注意到业务人员很难在一开始就清楚到底需要软件具备什么功能。
- 通常我们看到的是他们在开发过程中逐步认识到什么功能是重要的,什么不是那么重要的。最有价值的功能经常是要等到客户使用了系统之后才明朗起来。
- 敏捷方法正是要利用这一点,鼓励业务人员在开发过程中来梳理他们的需求,在系统建造中把这些变化尽快地整合进去。
什么是成功项目
预见性项目是否成功是由它是否很好地按计划执行来衡量的。一个项目如果在规定的时间和预算内完成,那就是成功的。
对于敏捷型项目实践者来说,最重要的是商业价值(business value)——客户得到的软件的价值是否大于他们的投入。
一个好的预见性项目是依计划而行,而一个好的敏捷型项目会建造出一个与最初计划不太一样却是更好的软件。
把人放在第一位
实施一个适应性过程并不容易,特别是它要求一组高效的开发人员。
高效既体现在高素质的个体,也体现在有能让团队协调一致的工作方式。
这里有一个有趣的和谐:并非只是适应性过程需要强的团队,多数优秀的开发人员也愿意采用适应性过程。
可兼容性程序插件
传统正规方法的目标之一是发展出这样一种过程,使得一个项目的参与人员成为可替代的部件。
这样的一种过程将人看成是一种资源,他们具有不同的角色,如一个分析员,一些程序员,测试员及一个管理人员。
个体是不重要的,只有角色才是重要的。这样一来,在你计划一个项目时,你并不在乎你能得到哪个分析员,哪些测试员,你只需关心你可得到多少,知道资源数量会如何影响你的计划。
参与软件开发的人员不是可替代的部件!
最明确地反对这种观点的当数 Alistair Cockburn。在他的论文“软件开发中人是非线性,一阶的部件”中,他指出可预见性软件开发过程要求“部件”的行为也是可预见性的。但是,人并非可预见性的部件。
他对软件项目的研究导致了如下结论:人是软件开发中最重要的因素。
“在本文的标题里,我将人称为‘部件’。(传统)过程/方法就是这样看待人的。这种观点的错误在于没有看到‘人’是高度可变的和非线性的,不同的个体具备特有的成功或失败模式。那些因素是一阶的,不可忽略的。一种过程或方法的设计者如不能充分考虑到这些因素,那么其后果就是项目的无计划轨迹,就象我们经常看到的那样。”
——[Cockburnnon-linear]
Cockburn 是最鲜明地主张在软件开发中应以人为中心,其实这种概念在许多软件行业的有识之士中已是共识。
问题在于所使用的方法是与这种理念背道而驰的。
这造成了一个很强的正反馈机制。如果你期望你的开发人员是可互替的编程插件,则你不会去试着把他们看成是不同的个体。这会降低士气(和生产率),并使优秀的人才跳到一个能发挥其个性特长的地方,最后你倒是得到你所需要的:可互替的编程插件。
作出使人优先的决定是件大事,它需要很大的决心来推行。
把人作为资源的思想在工商界是根深蒂固的,其根源可追溯到泰勒的“科学管理”方法。
当管理一个工厂时,这种泰勒主义途径是有效的。
但是对有着高度创造性和专业性的工作,我相信软件开发当属此类,泰勒主义并不适用(事实上现代制造业也在脱离泰勒主义模式)。
程序员是负责任的专业人员
泰勒主义的一个关键的理念是认为干活的人并非是那些知道怎样才能把这件活干的好的人。
在工厂中可能是这样,原因是许多工厂里的普通工人并非是最具聪明才智和最富创造力的人员。
另一个原因也许是由于管理层和工人的的工资悬殊太大而导致的关系紧张。
历史证明这种情形在软件开发中是不存在的。不断有优秀人才被吸引到软件行业中,吸引他们的既有耀眼的光芒也有丰厚的回报。
如果你想聘到并留住优秀人才,你得认识到他们是有能力的专业人员。
他们最有资格决定如何干好他们的技术工作。
泰勒主义让计划部门来决定如何干好一件工作的作法只有当计划者比实际操作者更能知道怎样作时才有效。
面向人的过程的管理
- 实施敏捷型过程的一个关键之处是让大家接受一个过程而非强加一个过程。通常软件开发的的过程是由管理人员决定的,因此这样的过程经常受到抵制,特别是如果管理人员已脱离实际的开发活动很长时间了。开发人员愿意选择并遵循一个适应性过程 。
- 另一点是开发人员必须有权作技术方面的所有决定。XP 非常强调这一点。在前期计划中,它就说明只有开发人员才能估算干一件工作所需的时间。
对许多管理人员来说,这样形式的技术领导是一个极大的转变。这种途径要求分担责任,即开发人员和管理人员在一个软件项目的领导方面有同等的地位。管理人员仍然扮演着他们的角色,但需认识并尊重开发人员的专业知识。
之所以强调开发人员的作用,一个重要的原因是 IT 行业的技术变化速度非常之快。今天的新技术可能几年后就过时了。这种情况完全不同于其他行业。即使管理层里的以前做技术的人都要认识到进入管理层意味着他们的技术技能会很快消失,因此必须信任和依靠当前的开发人员。
度量的困难性
如果有一个过程,规定工作应该如何来做的人不是具体去干的人,那么你需要一些方法来度量干工作的人是否工作有效。
在“科学管理”中,有种强烈的力量驱使着发展出客观性方法来度量人们的工作输出。
度量软件是非常困难的。
- 尽管人们已经尽了很大的努力,我们仍然不能对软件的一些很简单方面进行度量,如生产率。
- 如果没有一套有效的度量方法,任何外部的控制都会是困难的。
不存在一套有效的度量方法而要在管理中引入度量将会导致管理本身出问题。
- Robert Austin 指出,当进行度量时,你必须要获取影响这种度量的所有重要因素。任何缺失都将不可避免地使得具体干工作的人改变他们的工作方式以获得最好的度量成绩,甚至于那样会明显地降低他们真正的工作有效性。
- 这种度量的“失效” 正是基于度量的管理方法的致命之处。
Austin 的结论是你得在这两种方法中作选择:基于度量的管理,或是“委托式”(delegatory)管理(干工作的人决定该怎么干)。
基于度量的管理是非常适合简单的、重复性的工作,知识要求低并且易于度量输出——这恰恰与软件开发相反。
关键之处是传统方法假设的前提是基于度量的管理是最有效的管理方式。而敏捷开发者则认为软件开发的特性会使得基于度量的管理导致非常高度的度量“失效” 。实际上使用委托式的管理方式要有效得多,这正是敏捷论者所持观点的中心所在。
业务专家的引领作用
但技术人员并不能包打天下,他们需要应用系统的需求引导。
这导致了适应性过程的另一个重要方面:他们需要与应用领域的业务专家非常紧密的联系。
这种联系的紧密度超过了一般项目中业务人员的介入程度。如果开发人员和业务人员只有偶尔的沟通,那么敏捷型过程是不可能存在的。
这是由适应性过程的特点来决定的。因为敏捷开发的前提是在整个开发过程中,事情变化很快,你需要经常不断的联系沟通以使每个人都能及时知道这些变化。
自适应过程
前面提到的适应性是指在一个开发项目中如何频繁地修改软件以适应不断的需求变更。
但是,还有另一种适应性,即是过程本身随着时间推移变化。
一个项目在开始时用一个适应性过程,不会到一年之后还在用这个过程。随着时间的推移,开发团队会发现什么方式对他们的工作最好,然后改变过程以适应之。
改善自适应过程
- 有哪些做的好的部分?
- 有哪些教训?
- 有哪些可以改进的部分?
- 有哪些没搞清楚的部分?
这些问题会帮助你考虑在下一次迭代中如何对过程进行修正。这样,如果开始时使用的过程有问题的话,随着项目的进行,该过程会得以逐步的完善,以使其能更好地适合开发团队。
敏捷开发的不同风格
‘敏捷’这个词是指软件开发的一种理念。
很多方法都可以归入敏捷型旗下,如极限程序设计(XP),Scrum,精益(Lean Development)等等。
每种方法都有自己特定的思路、社群和领军人物。当然,一个社群如果要宣称自己是敏捷论者,它必须要遵循一些相同的原则。每个社群也从其他社群中借鉴相互的思想与技术。
你是否应走向敏捷?
在目前的软件开发中,多数方法仍是边写边改(code and fix),那么,引入一些纪律约束肯定会比一片混乱要好。
敏捷型途径的主要优点在于它比重型方法的步骤要少得多。
如果你已习惯于无过程,那么遵循简单过程应该比遵循繁琐过程更容易一些。
- 找到合适的项目来全面试验敏捷方法。因为敏捷方法的根本是“面向人”(people-oriented)的,所以,你的团队应该是想要使用敏捷方法的。因为一个勉勉强强的团队不仅很难在一起工作,更主要的是把一个敏捷型方法强加给不愿意这样干的人是与敏捷方法的根本思想背道而驰的。
- 让客户接受敏捷型方法。如果客户不合作,那你就不能看到适应性过程的全面优势。
- 从一个便于管理的小系统开始。许多人声称敏捷方法不能用于大的项目。我们(ThoughtWorks)已成功地运行过敏捷型项目,其项目组成员在 100 人左右,地域跨了几个洲。尽管这样,我还是要建议从小的项目着手。不管怎样,大项目都要更复杂一些,所以最好是从一个便于管理的小系统开始。
- 选择对业务影响小的项目开始。
什么情况下你不应该用敏捷方法呢?
这主要是取决于人。如果有关人员对敏捷方法所要求的密切的合作不感兴趣的话,那么,驱赶他们来做敏捷式开发会是一场苦战。因此,我认为,你绝不能把敏捷方法强加给一个不想试用该方法的团队。