个体和互动高于流程和工具;可工作的软件高于详尽的文档;客户协作高于合同谈判;响应变化高于遵循计划。
更新历史
- 2022.06.23:完成初稿
读后感
敏捷源于编程,但在很多地方,都可以发挥意想不到的神奇效果。天下武功,唯快不破!本书介绍了 scrum + 极限编程 + 精益 + 看板,很多思想都是相通的。
读书笔记
Scrum
- 对继续学习的渴望是一个人可以形成的最重要的态度。——约翰 · 杜威,《经验与教育》
- 如果团队中每一位成员都觉得自己在项目规划和运行中享有同等地位,那么每日站立会议就会变得更加有价值,而且更加有效
- 敏捷的故事始于一小群创新者,他们聚在一起试图找到解决这些问题的新方法。他们一开始就对以下这四则价值观达成了一致,认为这些是成功团队和成功项目共有的特质。他们把这四则价值观称为“敏捷软件开发宣言”(Manifesto for Agile Software Development)。
- 瀑布式流程 要求团队在项目开始的时候完整地写下软件的描述,然后完全按照写下的文档构建软件。
- 瀑布式流程很难应对变化,因为这种流程关注的是文档而不是协作。
- 没有什么银弹流程或实践可以让项目完美运作。
- 有些团队能够成功使用瀑布式流程,那是因为采纳了高效的软件开发实践和原则,尤其是加强了沟通。
- 他开始与其他开发人员一起写单元测试,进行测试驱动开发 (test driven development)。他编写了一个自动化的构建脚本 (automated build script),设置了一台构建服务器 (build server),这台服务器每小时都会签出一次代码,构建软件,并运行测试。
- 她也不能百分之百地确定自己在项目中的角色真的算是 Scrum 主管。她帮助团队将项目分解为多个迭代 (iteration),在任务板 (task board)上跟踪迭代的进度,用上了项目速度图 (velocity chart)和燃尽图 (burndown chart),保证每位成员都能了解最新的情况。燃尽图是一种线图,记录每天项目中未完成的工作,“燃尽”到零意味着工作完成。这是团队成员第一次真正对项目经理做的事情感兴趣,而且这种做法确实改善了项目的进度。
- 产品所有者 (product owner),Tom 开始编写用户故事 (user story),这样可以帮助团队更好地理解他们要开发什么样的软件。根据用户故事,他和团队一起制订构建发布计划 (release plan)。现在他感觉自己直接掌控着整个团队要构建的产品。
- 如果每个人只考虑自己的工作,只关心用户故事如何帮助自己,而不进一步看一看整个团队可以怎样使用用户故事(或其他的敏捷工具、技术和实践),那么最后就有可能出现这样的问题。我们把这种问题称为视角割裂 (fractured perspective),因为每个人对敏捷实践都有不同的看法。
- 敏捷宣言包含四则简明的价值观。下面是这份宣言的完整内容。
- 我们一直在实践中探寻更好的软件开发方法,身体力行的同时也帮助他人。由此我们建立了如下价值观。
- 个体和互动 高于 流程和工具
- 可工作的软件 高于 详尽的文档
- 客户协作 高于 合同谈判
- 响应变化 高于 遵循计划
- 也就是说,虽然右项有其价值,但是我们更重视左项的价值。
- 敏捷团队通常会使用任务板来展示任务并跟踪进度。他们会把任务或用户故事写在索引卡上,然后根据项目的进展移动这些卡片。很多团队还会在任务板上画图跟踪进度
- 没有具体的实践,原则是贫瘠的;但是如果缺乏原则,实践则没有生命、没有个性、没有勇气。伟大的产品出自伟大的团队,而伟大团队有原则、有个性、有勇气、有坚持、有胆量。
- 敏捷宣言 包含了高效团队必备的共同价值观和思想。
- “个体和互动高于流程和工具”的意思是说,团队应该首先关注团队中的人以及人之间沟通的方式,工具和实践是次要的。“可工作的软件高于详尽的文档”的意思是说,交付满足用户需求的软件比交付描述这个软件的说明文档重要。
- “可工作的软件”指的是可以给公司带来价值的软件。
- “客户协作高于合同谈判”的意思是说,要把所有人看作同一个团队的成员。
- 很多高效的敏捷团队把产品所有者 当作项目团队中的一员,通过这种方式与产品所有者合作,而不是将其当作客户进行谈判。
- “响应变化高于遵循计划”的意思是说,要意识到计划是会变的,交付软件产品比严格遵循计划更重要。
- 任务板 是一种敏捷规划工具,大家把用户故事粘贴在任务板上,并且根据用户故事在当前项目或迭代中的状态,把这些用户故事分类在不同的列中。
- 我们可以对 Scrum 概括出以下几点。
- 团队及项目出资方在一起创建一个优先级列表,包含这个团队需要完成的所有工作。这个列表称为产品积压工作表 (product backlog),它既可以是任务的列表,也可以是特性的列表
- 每个月,团队取出列表最上面的一部分,这是团队预估的一个月的工作量。团队将这部分工作扩展为一个详细的任务列表,称为冲刺积压工作表 (sprint backlog)。团队向出资方承诺在月底可以演示或交付上述积压工作表的处理成果。
- 团队成员每天碰面,花五到十分钟同步进度,交流阻碍。这项活动称为每日站立会议。
- 将一个人指派为 Scrum 主管。这个人要负责亲自或指派他人解决站立会议上提出的问题。
- 对于很多刚开始采用敏捷的团队来说,这些可以直接对等为具体的实践(在此以楷体字突出表示,第 4 章会详细解释这些实践)。
- 产品所有者创建并维护一个产品积压工作表 ,即软件的需求列表。
- 团队在限定的时间内完成月度冲刺 (sprint):在产品积压工作表中找出满足一个月工作量的需求,对这些需求进行开发、测试和演示。当前冲刺对应的需求列为冲刺积压工作表 。(有一些采用 Scrum 的团队将一个冲刺的时间跨度定为两周或四周。)
- 团队召开每日站立会议 ,每个人都汇报昨天完成的工作以及当天计划完成的工作,讨论在工作中遇到的任何困难。
- Scrum 主管扮演的角色是领导者、教练以及为团队完成项目保驾护航的指导者。
- 只关注单个实践的团队看不到加强沟通和响应变化的大目标 。
- 敏捷方法 涵盖了实践、思想、建议和团队。
- 诸如Scrum、极限编程和精益这样的敏捷方法不仅包含了很多优越的实践,还要求团队关注这些目标背后的思想。
- 敏捷软件开发的12条原则
- (1) 最优先要做的是尽早、持续地交付有价值的软件,让客户满意。
- (2) 欣然面对需求变化,即使是在开发后期。敏捷过程利用变化为客户维持竞争优势。
- (3) 频繁地交付可工作的软件,从数周到数月,交付周期越短越好。
- (4) 在团队内外,面对面交谈是最有效、也是最高效的沟通方式。
- (5) 在整个项目过程中,业务人员和开发人员必须每天都在一起工作。
- (6) 以受激励的个体为核心构建项目。为他们提供所需的环境和支持,相信他们可以把工作做好。
- (7) 可工作的软件是衡量进度的首要标准。
- (8) 敏捷过程倡导可持续开发。赞助商、开发人员和用户要能够共同、长期维持其步调,稳定向前。
- (9) 坚持不懈地追求技术卓越和良好的设计,以此增强敏捷的能力。
- (10) 简单是尽最大可能减少不必要工作的艺术,是敏捷的根本。
- (11) 最好的架构、需求和设计来自自组织的团队。
- (12) 团队定期反思如何提升效率,并依此调整自己的行为。
- 如果团队中的成员不沟通,他们可能会在粗粒度上保持一致,但是最后却朝着不同的目标前进。详尽的文档容易引入歧义,所以更容易产生这种情形
- 团队沟通的终极目标是形成一种集体意识,在成员之间建立不必直说也能领悟的共同知识,因为反反复复解释同样的事情实在太过低效。如果没有集体意识,团队中不同职责的人需要付出更大的努力才能匹配视角。一个团队越能形成集体意识,越能共享同样的视角,就越容易对同样的问题形成一致的答案。这就为团队构筑了处理变化的坚实基础,可以跳过冲突,立即编写代码,而且不会因为维护文档而分心。
- 这也是频繁发布可工作软件的开发团队应该把最有价值的特性优先开发的原因,这样的话,业务人员就可以第一时间享用到这些价值。这也是契约的一部分。这也是为什么好的敏捷团队会把业务人员看作是团队中与程序员同等重要的成员的原因。敏捷团队与传统团队存在这样巨大的差异。传统的开发团队把业务用户看作是要谈判的客户;而敏捷团队则是与客户(通常是产品所有者)合作,在项目执行的时候客户具有平等的发言权。(这就印证了“客户协作高于合同谈判”的敏捷核心价值观!)
- 详尽的文档和可跟踪性矩阵可能给团队环境和支持带来潜在问题。这些工作没有在团队中鼓励信任,反而鼓励一心自保 (Cover Your Ass,CYA)的态度,在这样氛围下的团队会倾向于采用“合同谈判”的方式,而不是与客户协作的方式。
- 带着 CYA 态度工作的测试员会努力确保每一项需求都有测试覆盖,而不去考虑测试到底能不能对软件的质量有帮助。带着 CYA 态度工作的开发人员会严格遵循需求文档中的每一个字,而不去认真想一想自己开发的功能是不是真正能给用户带来价值。因为在这种氛围中工作,如果按照用户真正需要的方式进行开发,你可能会被指责不遵守需求规格说明书。业务分析员和产品所有者保护自己的方式就是花时间确保项目范围和需求能整齐地排列在一起。这样的话,他们往往会倾向于忽略一些无法与现有文档保持一致的讨厌需求,而不管这些需求本身的价值。
- CYA 是信任的对立面。如果做项目的时候只需要编写所需的最少文档,那么公司的氛围就给了团队信任,相信团队在发生变化的时候可以做出正确的事情。在持有同甘共苦态度的敏捷团队中,如果项目失败了,那么大家都需要承担后果。这种团队中不需要 CYA。这样更容易处理变化,因为不需要维护任何不必要的文档。成员可以通过面对面的沟通解决实际问题,仅仅记录下真正必要的内容。他们可以这么做的原因就在于他们知道公司信任他们,即使项目可能耗时超出预期
- 过于详尽的文档会增加需求含糊以及团队成员之间误解和沟通不畅的风险。
- 典型的“命令 - 控制”式项目经理会努力地通过详尽的进度安排来保证项目的进度,并通过状态汇报来更新所有项目成员的最新状态。但是状态汇报很难获得项目的真正状态。汇报本身就是一种不完美的沟通工具:也许有三个人读的是完全相同的状态汇报,但他们往往对项目进度持有完全不同的理解。此外,对于项目经理来说,状态汇报也会有极强的政治色彩。几乎所有的项目经理都有过这样的巨大压力:有时候需要在状态报告中略去一些会让经理和团队主管难堪的东西,而别人常常需要用这些信息进行决策。如果状态报告并不够好,进度该怎样汇报呢?
- 答案就在可工作的软件中。只要真切地看到了软件在眼前工作,那么你就“得到了”项目的进展。你可以看到软件实现了什么,以及没有实现什么。如果经理承诺了要交付的功能没有在软件中,那会很难堪,但是又不可能不去沟通这个问题,因为软件本身就足以说明问题。
- 敏捷开发人员会培养起非常好的编程习惯,从而帮助自己编写设计良好的代码。他们不停地寻找设计和代码的问题,一旦发现问题,立即将其修复。在项目的开发过程中,只需要在当下多花那么一点点时间编写可靠的代码并及时修复问题,那么留下的这份代码库在未来就会非常好维护。
- 更惨的是,团队的软件设计和架构设计依据都是项目伊始时制订的详尽规格说明书,所以最后出来的代码非常复杂,难以扩展。结果,项目过程中很多小修改都需要打大补丁,代码库中留下了大量的意大利面条式代码。如果团队采用迭代式开发方法,不断交付可工作的软件,那么大家就可以对每一轮迭代制订计划,从而保持一个可持续的开发节奏。通过更简单、更适时的方法设计出来的架构更灵活更可扩展。如果团队使用了更好的设计、架构和编码实践,那么就可以开发出更易维护和扩展的代码。
- 有大量事前设计的团队非常容易做出过于复杂的设计。
- 在设计和架构阶段尽全力就必定意味着要构建可以做到的最棒的架构。对于采用这种方式工作的团队来说,如果提出的需求较少而且设计太简单,那么从直觉上会给人一种偷工减料的感觉。要是他们拿出一份巨大的需求文档和一个复杂的设计,那还会有人质疑吗?当然不会。在流程中有整块整块的阶段让他们做这些事情,意味着人们明确地要求他们这么做。
- 自组织的团队 (self-organizing team)并没有明确的需求和设计环节。自组织团队会用合作的方式对项目进行规划(而不是依赖某个“负责”计划的人),而且会持续地作为一个团队改进计划。采用这种工作方式的团队通常会把项目分解为多个用户故事或其他类型的小块,从能够给公司带来最大价值的块着手,然后再考虑详细的需求、设计和架构。
- 如果不能持续地改进构建软件的方式,那么团队就不算敏捷。敏捷团队会不断地检查并调整,成员会检查自己项目运转的方式,并通过检查的结果对未来进行改进。而且,他们不只是在项目结束的时候这么做。他们会每天开会寻找需要改变的地方,如果有道理,就会改变当前的工作方式 7 。你需要适应的一点是:对于你自己以及你团队中的其他同事,你必须非常诚实,让大家知道哪些事情可行,哪些不可行。对于刚刚迈上敏捷开发道路的团队来说,这一点尤其重要。增强团队实力的唯一方法就是经常回顾自己已经做的事情,然后评估作为一个团队这些事情做得怎么样,最后提出能改进的计划。
- 敏捷的独特之处在于从价值观和原则出发。敏捷团队不仅要诚实地回顾开发软件的方式,还要回顾成员交流的方式,以及与公司其他同事交流的方式。首先要理解原则,然后再采用方法,要完整理解其工作原理,还要在过程中不断地评估和改进。敏捷团队可以真正找到改进项目运行的方法,增强敏捷性,开发并交付更好的软件。
- Scrum 的规则很简单,也很容易讲明白。对于很多要采用敏捷的团队,这是一个非常好的起点。下面是 Scrum 项目的基本模式。
- 在 Scrum 项目中有 3 种主要的角色:产品所有者 、Scrum 主管 和团队成员 。
- 产品所有者和团队其他成员一起工作,负责维护生产积压工作表(production backlog),并对表中的项制订优先级。
- 软件在多轮时间限定的迭代中完成开发,这些迭代称为冲刺 。在每一轮冲刺开始的时候,团队进行冲刺规划 ,从积压工作表中选择出这一轮要开发的特性。确定的列表称为冲刺积压工作表 ,团队利用完整冲刺的时间完成这个列表中所有特性的开发。
- 团队每天碰面,开一个短会,更新成员各自的进度,并讨论遇到的困难。这个会称为每日 Scrum 会议 (Daily Scrum)。每个人都要回答 3 个问题:自上一次每日 Scrum 会议以来,我都干了些什么?从现在起到下一次每日 Scrum 会议的时间内我要做什么?我遇到了什么困难?
- 有一个人(Scrum 主管)要和整个团队一同工作,帮助团队成员克服困难,保证项目正常运转。在每一轮冲刺结束的时候,会有一次冲刺评审 (sprint review)向产品所有者和其他利益干系人展示可工作的软件。团队还会召开回顾会议 ,找出要从这一轮冲刺中吸取的经验教训,这样就可以在未来改进开发软件的方式。
- 为了让 Scrum 发挥作用,团队必须深刻理解集体承诺 (collective commitment)和自组织。Scrum 的理论、实践和规则都很容易掌握。但是如果一组成员不能在固定时间内完成交付,实现集体承诺,那他们就没有真正实现 Scrum 。如果团队成员不再像零散的个人,而是有一个共同的目标,那么这个团队就有能力实现自组织,并能快速解决复杂问题,产生可执行的计划。
- 每一个冲刺开始的时候,Scrum 主管、产品所有者和团队其他成员在一起开会制订计划。这个会议分为两个部分,每一个部分限定时间为 4 小时。在冲刺计划之前,产品所有者需要做功课,提前准备好一份按照优先级排序的产品积压工作表,其中列出了用户和利益干系人要求实现的功能。在会议的前半部分,产品所有者和团队一起选择出要在这一轮冲刺结束时交付的特性,选择的依据是这些特性的价值以及团队对工作量的预估。团队同意在冲刺结束的时候演示一份包含这些功能的可工作的软件。会议前半部分有时间限制(30 天冲刺,则会议限时 4 小时,短一点的冲刺,会议时限可以按比例缩短),在结束的时候,不管讨论了多少内容,团队都要定下这一轮冲刺的积压工作表。在会议后半部分,团队成员在产品所有者的帮助下整理出实现这些功能要用到的具体任务。这一部分的会议也是根据冲刺的长度来限定时间的,但是通常比会议前半部分时间短。在冲刺规划结束时,冲刺积压工作表要拟定。
- 团队每天召开 Scrum 会议。所有的团队成员(包括 Scrum 主管和产品所有者)都必须参加 2 ,其他感兴趣的利益干系人也可以参加(但是必须保持安静,只能作为观察者)。这种会议时间限定为 15 分钟,因此所有的团队成员都必须准时出席。每一位团队成员回答 3 个问题:自上一次每日 Scrum 会议以来,我都干了些什么?从现在起到下一次每日 Scrum 会议的时间内我要做什么?我遇到了什么障碍和困难?每一位团队成员的回答都必须简练。如果需要讨论,那么相关人员可以安排会后立即讨论。
- 冲刺要限定在规划的时限内:很多团队选择 30 个自然日,不过这个时间长度也可以调整,有些团队选择两周冲刺,还有一些团队选择一个月冲刺(规划会议的时间限定也应该作出相应调整)。在冲刺中,团队将冲刺积压工作表中的特性开发到可工作的软件中。他们可以从团队外获得帮助,但是外人不可以指挥团队成员,而且这些人必须值得信任,他们一定要按时交付软件。如果团队中任何人在冲刺的过程中发现他们的承诺超过了实际能力或者可以增加更多的项目,那么他们必须确保第一时间通知产品所有者。产品所有者可以与用户以及利益干系人一起工作,并且调整他们对项目的预期,让冲刺积压工作表与团队的真实能力匹配。如果发现冲刺还没有结束,团队就已经没活可干了,那么他们可以进一步充实冲刺积压工作表。团队必须保证冲刺积压工作表时时更新,而且所有人都能看到。在异常或极端情况下(例如出现了严重的技术问题,发生了组织变动或人事变动),如果团队自知无法交付可工作的软件,产品所有者可以提前终止冲刺并发起新一轮的冲刺规划。但是大家都必须认识到终止冲刺应该是非常罕见的事情,这会极大地阻碍团队开发和交付软件,而且会严重地损害他们与用户以及利益干系人之间的信任。
- 在冲刺结束的时候,团队召开冲刺评审会,向用户和利益干系人展示可工作的软件。演示只包含那些真正完成的功能 3 (在这个例子中,完成意味着团队已经做完了这项特性所有的相关工作,包括测试,而且产品所有者已经确认功能完成)。团队只能展示可工作的软件,而不包括一些中间产物,例如架构图、数据库模式以及功能说明书等。利益干系人可以问问题,团队可以回答问题。在演示结束的时候,团队会询问利益干系人的意见和反馈,后者也有机会分享想法、感受和观点。如果需要,下一轮冲刺规划可以考虑加入修改。产品所有者可以把修改的内容添加到产品积压工作表中。如果需要立即修改,那么这些修改的需求就会出现在下一轮冲刺的积压工作表中。
- 在这一轮冲刺后,团队会召开一个冲刺回顾会议,讨论可以改进工作方式的方法。团队和 Scrum 主管参会(产品所有者也可以参加)。每个人都要回答两个问题:在这一轮冲刺中有哪些事做得不错?未来有哪些事情可以改进? Scrum 主管会记录所有可以进行的改进,然后以非产品项充实产品积压工作表,例如设置新的构建服务器,采用新的编程实践,以及改变办公室布局等。
- 主管的工作方式不同是 Scrum 团队与传统命令 - 控制式项目团队之间最大的区别。
- 在命令 - 控制式项目中,项目经理是负责人,也是进度表和计划的维护人。他代表的是利益干系人,负责收集需求、分解任务、从团队采集任务预估、分配任务并构建进度表。
- 命令 - 控制式项目中的成员很自然地会有一种 CYA 态度。如果别人的计划引出了问题,他们会很快撇清自己的干系。如果有人对进度和计划有绝对权力,那么团队中其他人和产品所有者会欣然把决策都甩给这个人。
- Scrum 项目中没有单独负责计划的人。这么做是有原因的。如果团队中负责计划和负责执行的人不同,那么一旦遇到麻烦,成员很容易互相指责。项目的绝大多数问题都和计划有关,所以这种麻烦总会出现在团队中。因此,Scrum 主管并不负责计划。他可以帮助团队制订计划。更重要的是,他会指导团队使用 Scrum 及其相关实践,让所有人觉得计划是大家一起制订的。Scrum 的实践和价值观帮助大家树立了主人翁意识。
- 在 Scrum 团队中,产品所有者就是对公司作出承诺的那个人。这个人必须站出来,保证在项目结束的时能交付某个具体的东西。产品所有者负责的是项目应该满足的真正业务目标。产品所有者越能有效地让团队理解这些目标,对团队越有责任感,那么项目进展就会越好。如果项目不可避免地陷入了一些困境,例如遇到了技术困难、业务上出现了变动、或是有人离开了团队,产品所有者必须找到某种方式让团队理解当前最新的目标,唤起大家的责任感。产品所有者每天根据业务的变化做决策,每天都与团队碰头,确保大家都能理解积压工作表和项目目标正在发生的变化。
- 产品所有者有作出这些决定的权力。(如果没有的话,他就不应该做这项工作!Scrum 依赖产品所有者对业务决策的能力,包括验收最终结果的能力。)但是他并没有掌握所有的信息,因为很多用户和利益干系人也有有价值的信息和意见。因此,产品所有者还要花很多时间与这些人沟通,以获得开发人员所需要的答案。他还需要借助这个机会掌握一切最新的变更,并负责让产品积压工作表反映公司需求的最新变化。如果产品积压工作表的相对价值发生了变化,他要保证优先级得到调整,从而为团队下一阶段的冲刺规划做好准备。
- 在高效 Scrum 团队中,团队成员并不满足于完成自己的任务。每一个人都发自内心地想要给用户和利益干系人交付出最有价值的软件。当团队中每一位成员都有这种心态的时候,我们才能认为团队作出了集体承诺 。在这种情况下,并非每个人都领到了零碎的任务,而是整个团队投入了开发交付积压工作表中最有价值的过程。在开发过程中发现了与项目有关的新情况,团队会有足够的自由调整完成任务的方式。
- 自组织的团队把预估和计划当作待寻找的事实,而不是借此逼迫整个团队
- Scrum 主管和团队有一些办法可以让产品所有者拥有真正的投入感。最重要的方式是用心倾听 产品所有者的观点和意见,并且意识到产品所有者会给项目带来团队真正需要的专业知识。
- 很多程序员都认为在项目中只有编程才是最重要的事情,其他所有的事情都应该放在技术细节之后。更糟糕的是,很多公司还鼓励这种误解,这些公司的组织架构是围绕着技术人员展开的。项目经理和产品所有者从技术团队分开的公司组织架构会让程序员把其他人当作“局外人”,并且不尊重他们的贡献。
- 产品所有者并不一定了解技术细节,但没有问题。在 Scrum 团队中,每个人都有自己特有的技能和知识,负责最适合自己的任务。产品所有者让大家对项目的目标有真正深入的理解。Scrum 主管和团队与产品所有者沟通越多,询问项目目标越多,越理解产品所有者的观点,那么产品所有者对项目和团队的投入也越多。
- 在《Scrum 敏捷项目管理》一书中,Ken Schwaber 讨论了 5 种 Scrum 价值观:勇气(courage)、承诺(commitment)、尊重(respect)、专注(focus)和开放(openness)。学习这些价值观并理解自组织团队是可以在项目中切实开展的工作。
- 在 Scrum 团队工作的时候,团队中的其他所有人都应该了解你当前正在做的工作,以及你正在做的工作是如何朝着当前目标前进的。基于这个原因,基本的 Scrum 工作模式中的实践都会鼓励团队成员间的开放。例如,通过任务板,大家可以看到其他所有人要做的工作,以及还没有完成的工作量。通过燃尽图,大家可以自己度量冲刺达成目标的速度。高效的每日 Scrum 是纯粹的开放性实践,每个人都会分享自己的任务、遇到的困难以及当前的进度,让整个团队都了解这些事项。所有这些做法都有助于团队培养互相支持和鼓励的氛围。
- Scrum 团队有勇气坚守对项目有利的价值观和原则。要应对与 Scrum 以及敏捷价值观冲突的公司阻力还是需要勇气的。为此,每一位团队成员(尤其是 Scrum 主管)都需要保持警觉。每个人都要相信,交付有价值的软件可以帮助自己克服针对这些价值观的阻力。这也是需要勇气的,特别是坐下来与老板一起做回顾工作的时候。你要勇气对自己说:“帮助这个团队产出更有价值的软件比吹嘘我的个人贡献吹嘘更重要。”
- 团队需要真正理解并吸收以下 Scrum 价值观:承诺、尊重、专注、开放 和勇气 。做到这一点的才是真正的Scrum 团队。
- 每日 Scrum 例会是 Scrum 团队可以利用的最有效的工具之一。这是因为每日 Scrum 例会帮助团队完成了两件重要的事情:首先是检查 (inspection)团队正在做的事情,帮助调整工作以交付最大的价值;其次是让团队有机会在最后责任时刻 (last responsible moment)作出决策,使得团队具有足够的灵活性,可以让正确的人在正确的时间完成正确的工作。当团队中每一位成员都利用每日 Scrum 例会讨论下一轮迭代要开发的必要功能时,做计划所需要的工作就会得到限制,那么整个团队就会开始意识到每日 Scrum 例会是一种有价值的工具并且开始高效地使用这种工具。
- 项目经理会继续给团队成员一项预先分配好的任务。而 Scrum 主管则意识到这是一个让团队领悟敏捷的机会,应该让大家理解任务到底应当如何分配。他会提出这类问题:“你觉得下一步应该做什么事情?”当然,根据团队的实际情况,他也可以继续保持沉默。
- 要点在于,任务分配取决于团队成员。每一位成员在完成了当前的任务之后自己给自己分配下一项任务 。
- 任务分配这件事情应该在每日 Scrum 例会上完成,因为团队其他成员有机会给出自己的意见,帮助整个项目运转顺利。例如,如果有一名开发人员自告奋勇揽下一项复杂数据库优化的任务,那么 DBA 可能会站出来建议这名开发人员先不要做这项任务,并承诺他接下来会负责这项任务。
- 事实上,Scrum 团队根本不会试图得出一个“最终”的任务序列。原因在于,对于大部分项目的任务(尤其是编程任务)来说,团队并不能真正提前知道需要花多长时间。而且,往往只有碰到相关依赖的时候,团队他们才发现有这些依赖。在项目进展的过程中,团队经常会发现遗漏的任务。另外,一开始看起来很小的任务到最后可能被发现是一项重大任务,一开始看起来很重大的任务最后被发现微不足道,这些都是很常见的事情。当然,尽管团队经常会在冲刺过程中发现漏掉了任务,但这并不表示他们没有责任在冲刺规划的过程中尽可能制订出完整的任务清单。
- 表现得像猪一样
- 在会议中,每一位团队成员都要对自己的队友负责。如果在前一次会议中作出的承诺没有实现,那么你要负责解释具体的原因。想象一下自己在一个自组织的团队中,你真心对项目感到有责任感,那么为了做好自己的日常工作,你需要了解哪些情况?最重要的是知晓你手头正在做的工作。但是如果你的团队是真正自组织的,那么就不能指望有一个命令 - 控制式项目经理来帮你决定你要做什么。你需要通过一些其他的机制来获得接下来的任务。因此,你在每日 Scrum 例会上的第一项收获就是下一项任务。轮到你讲述下一次 Scrum 例会前的工作计划时,如果你已经完成了到目前为止分配给你的所有任务,那么你要做的就是看一下“待处理”列表中的任务,然后从中挑选出一项对你和项目最有意义的任务。如果这个选择有问题,那么团队中其他真正表现得像猪的成员会站出来给出意见。
- 细节会后讨论
- 每日 Scrum 例会的目标是定位问题,而不是解决问题。如果在一两分钟的讨论后无法解决问题,那么请另外安排一个后续会议,自认与这个问题有关系的人可以参会。很多这种后续会议的内容都关乎哪些人要负责哪些任务。这就是团队自组织的方式:大部分任务都可以让大家自己给自己分配,但是有一些任务还需要讨论。只有通过每日 Scrum 例会的“检查”环节才能认清哪些问题是可以自分配,还有哪些问题是需要讨论的。
- 轮流先行
- 没有谁充当进度的“守护者”,也没有人在项目中比别人重要。显然,有一些开发人员的专业技能比其他开发人员更高超,但是任何一个人都可能有好的想法。如果团队中有初级员工出了好主意,请不要因为他不是顶尖程序员而忽视他的想法。他可能会发现任务安排中存在的严重问题,整个团队都需要处理这个问题。为了让每个人都能听取其他人的好想法,每天的 Scrum 例会可以由不同的人起头。
- 不要当作例行公事
- 我们每天都要开这些会(有些 Scrum 团队甚至把例会称为“仪式”),所有人都需要参加会议,并且参与每一个步骤。例会很容易将每个人都要回答的三个问题(从上一次例会到现在我都干了些什么?到下一次例会前我要干什么?什么事情阻挡了我的进度?)当作是例行公事,大家只顾着回答问题,而忘记了这么做的初衷。随着时间的推移,例行公事会模糊掉工作实质,因为人们会慢慢产生敷衍行为。这三个问题是每日 Scrum 例会的核心部分,因为团队每天都需要检查这些内容,这样才能尽早地发现问题。比如说,要找到因为某个人要承担太多任务而导致的瓶颈的最有效方法,就是让每个人回答关于阻碍进度的问题,因为第一个会被瓶颈阻碍进度的人肯定比其他人更早发现问题。
- 所有人都要参与
- 所有人包括测试工程师、业务分析师以及团队中其他所有人,产品所有者也算在里面。所有人都要真正投入项目。产品所有者的工作非常重要,因为他要让所有人都知道积压工作表中有哪些任务对用户和公司来说最有价值,他要让所有人了解最新的状态。团队对于自己要交付的价值越了解,他们就能越准确地满足用户的目标。产品所有者还要与团队其他成员一起回答那三个问题,因为团队很容易忘记他在项目中也承担了重要的工作,而他的答案可以帮助团队其他成员理解他的工作。(事实证明,与用户交谈,理解用户的业务需求,以及管理积压工作表的确是需要全力投入的工作,如果开发人员能切身了解这些,那么他们就能更尊重产品所有者的工作。)
- 不要开成最新状态汇报会
- 典型的状态汇报会是每周例行公事的一种典型。我们早已习惯了这种会议,所以很少想到要质疑,甚至都不会多想。状态汇报会应该达成两个目的:一个是让团队里的每一位成员都获得最新信息,另一个是让管理层获得最新信息。但是对于大多数团队来说,这个会议只是一种单向沟通,即单个团队成员与项目经理之间的二人谈话。为了避免这种状况,可以尝试确保每日 Scrum 例会中的每个人都在认真倾听。(这意味着不能查看电子邮件,不能玩手机,甚至不能做与工作相关的事情!)当团队成员开始把每日 Scrum 例会看作提早发现问题,避免走错路浪费开发时间的方式时,大家就会觉得这个例会并没有官僚主义色彩。他们会知道,这是一种以开发人员为中心的实践,可以帮助大家开发出更好的代码。
- 检查每一项任务
- 寻找障碍的时候,不要只盯着手头正在做的事情,而要检查“待处理”栏中的每一个条目,往后看几步,看看是否有存在问题的可能。如果发现了潜在的问题,最好现在就把这个问题拎出来与团队一起讨论,把障碍消除,而不是默默地留着问题直到后面爆发。这也是任务检查需要团队每个人都互相信任的原因。如果有人有意或无意地没有准确描述他正在做以及计划做的事情,那么团队就可能会错过一个潜在的障碍,而这个障碍如果没有及早移出,后面可能会导致更严重的问题。
- 计划需要则改变
- 这是“可见 - 检查 - 调整”周期中的“调整”部分,也是自组织团队的关键工作。团队在每日 Scrum 例会中发现了一个障碍,然后在后续会议中发现他们有一个错误的估算,无法交付一项已经承诺的重要功能。那么继续坚持已经知道不可能实现的计划有意义吗?当然没有。积压工作表和任务板必须反映项目的真实情况,如果发现了一个问题,那么整个团队都必须共同修正积压工作表和任务板。这就是产品所有者作为猪的方便之处,因为他可以立即开始调整其他人的预期。只要记住,不要管人们现在发现了计划的变化之后会有多糟糕的反应。如果你现在不告诉他们,他们日后的反应会更糟糕,他们迟早会发现的。
- 与等到项目结束的时候相比,在每一轮冲刺结束的时候把所有开发的内容整合在一起,可以帮助团队认识到这些问题,甚至可以避免很多类似的问题。这种工作方式还有另外的好处:沟通更有效,利益干系人的参与感更强,项目的状态也更容易度量。当团队把项目分解为多个阶段的时候,这种开发方式称为增量式开发 (incremental development)。Scrum 冲刺就是一种把项目分解为增量的方法,因此 Scrum 是一种增量方法。
- 不过 Scrum 远不止此。Scrum 冲刺的意义不仅仅在于在固定时间安排内交付出可工作的软件。Scrum 的意义还在于理解软件能带来的价值,准确地理解如何交付这些价值,并且在找到了能交付更大价值的方法时改变开发过程。像 Scrum 这样的方法和流程以这种方式工作的时候,这种开发方式称为迭代式开发 (iterative development)。
- 迭代式流程是一种通过持续精炼而不断取得进步的流程。开发团队首先给出一个系统的初步版本,大家都清楚这个版本在某些(甚至很多)方面并不完善或是功能很弱。然后他们迭代性地改善这些方面,直到产品达到令人满意的状态。在每一轮迭代中,更多的细节被加入到软件中,因此软件也一步步地得到了改进。
- 增量式流程是一种把软件分成多个部分开发和交付的流程。每一部分(或称为每一份增量)都代表了一组完整的功能子集。一份增量既可以小也可以大,既可以表示一个系统在小终端上的一个登录界面,也可以表示一组高度灵活的数据管理界面。每一份增量都要有完整的代码和测试。对一份增量的常见预期是这份增量的工作在事后不需要返工
- 交付价值是一个非常有效的可以激励整个团队的奋斗目标。如果一个团队有一个大家都真心相信的奋斗目标,那么他们就会自己考虑如何实现这个目标(以及接受风险的自由),他们会尽力使用各种可能的工具解决实现这个目标途中的各种问题。
- 开发人员(这里说的“开发人员”指的是敏捷团队中的所有成员,包括那些不直接写代码的成员)会因为满足了技艺的自豪感 (pride of workmanship)而受到高度鼓舞。我们希望开发出有用、人们喜欢和爱护的软件。我们希望我们开发出来的软件能优质高效地完成任务。我们还希望我们开发的软件能尽可能地优秀,所以才会花这么多的时间去争论不同的设计、架构和技术。这些东西是团队真正关心的。让用户生活更美好(这是我们交付价值最直接的方式)是一种真实、真诚、鼓舞人心的目标。
- 自组织的团队也会发现依赖关系,但是成员有更好的方式去应对。他们在最后职责时刻处理依赖关系,这个时候他们已经获得了足够多的任务信息,可以执行更为全面的分析。
- 下面是帮助团队理解本章思想的敏捷教练技巧。
- 采用 Scrum 最困难的一个步骤就是找到产品所有者。如果与你共事的团队想采用 Scrum,那么你要帮他们找一个有这种权威以及意愿的人,这个人要代表业务方作出决策。
- 很多敏捷教练发现他们带领的 Scrum 团队遇到问题的原因是他们随便在团队中选了一个人做产品所有者。帮助他们理解产品所有者需要有权代表业务方的利益接受或否决团队做出的功能。
- 团队召开的每日 Scrum 例会是否本质上还是状态汇报会?借此机会帮助团队明白命令 - 控制式团队与自组织团队的区别。
- 帮助 Scrum 主管理明白,他不负责告诉每一位团队成员每天要干什么,也不要跟踪他们是否完成了工作。让团队明白,Scrum 主管的责任是确保大家都遵循 Scrum 的规则,并为他们铲除障碍。
- 用户故事之所以能帮助开发团队理解用户,是因为它清晰地定义了用户需求以及软件应当如何满足该需求。
- 每个用户故事都有一组满意条件 来帮助开发人员知道该用户故事怎么才算完成了。
- 故事点 和速度 用来估计一次冲刺能够容纳多少个用户故事。
- 把燃尽图 张贴在一个显眼的地方,让所有人看见,能够帮助大家了解都有哪些任务完成了,哪些还没有完成,以及项目进展是否顺利。
- 要允许失败:当然,这并不是说在团队即将冲下悬崖的时候你应该袖手旁观,而是要利用每次冲刺中的各种机会让他们适当地失败。相比于把他们保护起来,让他们共同经历失败并从失败中找到出路可以更好地锻炼他们。你以为会给他们带来不利影响的那些东西,也许恰恰对他们最有帮助。不信走着瞧。
极限编程
- 极限编程是一种敏捷开发方法。跟 Scrum 一样,它也是由一系列的实践方法、价值观和原则组成的。实践方法易学习,效果佳,它们能够改变你思考工作的方式,但值得再次强调的是,极限编程与 Scrum 一样,只有团队成员以正确的思维模式去使用它时,这种变化才能真正发生。
- 极限编程有两个主要实践意在帮助你写出更的好代码:测试先行编程和结对编程。这两项实践的重点就是软件开发。先写测试代码,开发人员可以提高软件的质量,而结对编程(两个开发人员共用一台电脑)让每个人身边都多了一双眼睛,有助于减少 bug 进入生产代码的概率。
- 当一个开发人员采用测试先行编程 (test-first programming)时[测试先行编程又称为测试驱动开发 (Test-Driven Development,TDD)],就意味着在他编写代码之前,一定会先编写一个自动化的测试。由于此时产品代码还没有写出来,这个测试肯定没法通过。而一旦这个测试通过了,那么该开发人员就知道他的代码可以工作了。这种做法构造了一种紧密的反馈机制,可以防止代码出现问题。先编写(无法通过的)测试,再编写代码让测试通过,定位问题并找出解决方案,然后再写下一个测试,如此循环。这些自动化测试通常被称为单元测试 (unit test)。“单元”这个词很恰当:对于几乎每一种编程语言,代码都可以清楚地分解成各种单元(类、方法、函数、子过程、模块等),同时几乎每一种语言也都有至少一种构建和运行这些与相应单元对应的自动化测试方法。通过首先编写测试,开发人员可以保证每个单元都实现了其应该实现的功能。
- 测试先行编程可以确保每个独立的代码单元都正确工作,但是它的效用不止于此。它同时也可帮助开发团队避免一些最常见、最严重的代码维护问题。软件开发中时常出现这样的状况:代码的某个部分做了一个改动,结果一个似乎完全不相关的功能引入了 bug,因为改动的人不知道这两个功能有一个共同的依赖。当一个程序员编写了每次构建代码时都会自动运行的单元测试时,一旦共用依赖出现问题,这些自动化的测试马上就无法通过了。上述问题会立马暴露。开发人员发现问题的所在,这样的代码就无法进入代码仓库,问题不会变得更棘手。单元测试还可以帮助程序员编写易于复用的代码。
- 我们将两个极限编程的主要实践归入“集成”这个类别。第一个是 10 分钟构建机制 (10-minute build):开发团队需要一个自动构建全部代码的机制,而且完成自动构建的时间不超过 10 分钟。构建过程包括运行所有的单元测试并生成一个报告,说明哪些测试通过了,哪些没有通过
- 10 分钟听起来好像是一个很随意的时间长度,但从团队的角度来讲这是有一定意义的。如果一次构建需要超过 10 分钟才能运行完毕,团队成员经常进行构建的概率就会降低。频繁构建对团队来讲是非常有价值的,因为这样可以让问题无处遁形。比如说,构建过程中运行了所有的单元测试,因此,构建完毕后,大家就清楚他们是否达到了自动化测试中所预设的质量高度。换句话说,10 分钟构建机制能够快速回答“我们的代码现在是否能正常工作?”这个问题。因为它的耗时足够短,大家都会经常进行构建,团队中的每个人都能够持续了解代码质量的最新状况。
- 这里就要提到持续集成了:开发团队需要不断地进行构建并注意编译错误或单元测试失败。很多团队会专门设立一个构建服务器,每隔一段时间自动签出仓库中最新的代码,运行自动构建过程,并在产生错误的时候通知开发团队。不过,设立一个专门的持续构建服务器只是持续集成的一个部分。持续集成意味着每个团队成员都要随时保持本地副本与主副本一致。也就是说,每个团队成员要定期将最新代码集成到自己的沙盒中。
- 很多极限编程团队都完全采用 Scrum 的计划方法(这也是第 2 章关于 Scrum 和极限编程结合的方法流行起来的一个原因)。做完计划后,开发团队会先为选定的故事和任务编写自动化测试,然后用剩下的时间编写代码来让这些测试得以通过。与 Scrum 团队的自组织形式不同,有些极限编程团队会把当次迭代的所有任务放入一个列表,然后要求开发人员完成手头任务之后就接着去做列表中的下一项任务。这可以防止开发人员只挑自己喜欢的任务做,从而保证任务能够较为均匀地分配给所有人。
- 极限编程团队使用季度循环 (quarterly cycle)这一实践来做长期规划。每个季度,整个团队会坐下来开会审视全局。会上将就一些主题 进行讨论。主题就是宏观概念,可以实际帮助他们将项目的故事组织起来。对主题进行讨论可以帮助团队确定应该将哪些故事加入项目,并与该软件要解决的现实业务问题保持关联。大家也会讨论他们所经历的各种内部或外部问题,比如难缠的 bug,还有尚未落实的那些修正。他们也会花时间审视已经取得的进展:对用户需求的满足程度如何,项目整体的进展如何。有些极限编程团队也召开 Scrum 团队的回顾会议。
- 另一个主要的极限编程团队实践叫作大信息量的工作空间 (informative workspace),即对办公室做一些安排,使得在其中工作的人能够自动获得关于项目的重要信息。一个扩大工作空间信息量的流行做法是把大型的任务板和燃尽图安放在大家都能看见的墙面显眼位置,让大家一抬头就能看到项目进展,随时了解项目进度,由此作出更佳决策。随时可见、就在眼前的图表,以及其他用来在工作环境中展示信息的东西都称为信息辐射体 (information radiator),因为它们会自动将项目当前的信息“辐射”给附近的每个人。
- 敏捷方法(特别是极限编程)的优点在于,它首先承认我们并不完全知道我们具体要开发一个什么东西,而要想弄清楚这个问题,最有效的方法就是把它开发出来。使用这种方法的团队用可工作的软件说话,而不是用详尽的文档,因为从用户那里得到反馈的最佳方法就是先完成软件的部分功能,并把软件交付到用户的手上。
- 因此,与其让你的团队纠结于应付每一个变化,有没有可能创造一个环境,让团队可以做到优雅地应对这些变化,而不必经历无休止的加班与情绪化的冲突呢?没有人喜欢变化,但你也许可以找到一条路,使得每一个变化对项目的影响被限定在一定程度,这里不仅仅是针对代码的质量,也包括开发团队和用户的情感状态。
- 极限编程团队能够做到这一点。他们把这个叫作拥抱变化 (embracing change)。一个极限编程团队并不会简单地把变化当成是不得不干的脏活累活;他们认识到要想给他们的用户做出最好的软件产品,唯一的办法就是经常性地获取用户的反馈并对这些反馈作出快速反应。他们知道会有变化,这些都已经写入他们的日程。变化不再是挥之不去的坏消息(也不再是随之而来的追踪谁是遗漏需求的元凶)。进入极限编程的正确心态不仅意味着接受变化,更要求我们自己要求变化。
- 拥抱变化包含两个层面:项目范围层面和代码层面。变化对于代码的影响可能是非常严重的,有时候一个看起来应该很小的变化常常会令人意外地需要大量恼人且颇具破坏性的重写。开发人员常常会说“废掉这些代码”,然后把这个窟窿用“胶带、别针和口香糖”给它堵上。关于拥抱变化这项实践在代码层面的应用,我们会在本书第 7 章中探讨。这里我们将专注于极限编程团队是如何在软件的范围、功能和行为层面做到拥抱变化的。
- 实践本身是空洞的。如果没有价值观作为它们的后盾,它们不过是机械的行为方式而已。拿结对编程来说,如果仅仅是为了结对而结对,那么它就完全失去意义了。为了讨好你的老板而结对完全就是一种折磨。反过来,如果是为了相互沟通、获取反馈、简化系统、发现错误、得到鼓励,那它就非常有意义。
- 极限编程也有一个很类似的模式。如果你和你的团队抵制变化,消极地抵抗用户修改软件功能的要求,而且认为构建容易修改的软件是不现实甚至不可能的,那么你就没有掌握极限编程的要领。如果你和团队没有理解简化(即简单的设计与复杂的设计的区别,简化是如何做到的,团队的文化和气氛是如何影响其写出简单的代码和架构的能力的,以及简化是如何防止 bug 产生的),那么你们就没有掌握极限编程的要领。
- 畏惧变化的团队将会扼杀来自底层的创新。这倒不是说这种团队就没法开发出软件。他们能够开发出软件,甚至可能开发出优秀的软件。在需求先行的框架下工作,你能够达到“满足文档中的那些需求”这一目标,而这对很多团队来说已经很好了。但这里仍然存在问题。
- 极限编程还会改变团队中每个人对于质量的认识。质量并不仅仅是避免 bug。它更关乎满足用户需求,即便这个需求跟用户最开始要求的不一致
- 极限编程的秘诀在于,它强迫开发人员像用户那样去思考,而这要从与用户沟通开始。(什么?!)如果开发人员不具备与用户沟通的能力,那他们就得开始培养这种能力。极限编程的实践其实会有助于沟通能力的培养。拿测试驱动开发来说,开发人员需要首先编写测试,这能够防止 bug 的出现。同时这也可以强迫每一个开发人员在写下一段代码之前去思考编写代码的目的。如果习惯写每行代码前思考功能需求,那么程序员在每个周循环的计划会议上也会提出相同的问题。
- 极限编程还可以防止需求先行框架下消极的自我保护。在需求先行的框架下,一个开发人员可以完全按照规格文档逐字逐句地进行实现,完全不用去考虑规格文档中所写的内容是否真正解决了用户的需求。但是如果这个开发人员养成了写代码之前思考功能的习惯,他就会开始发现问题,而且会自然而然地要求得到回答,因为他写测试的时候需要知道问题的答案。他不会把责任推卸给撰写规格文档的人。当一个极限编程团队的每一位成员都做到这一点的时候,他们会不断地从用户那里寻求解释和澄清,尽最大努力去理解用户需要的到底是什么,以及软件的功能应当如何实现才能帮助用户完成他们的工作。他们直接与用户接触,而不需要通过一个中间人,这能够节省很多时间。
- 极限编程的价值观
- 沟通,每个团队成员都清楚其他人在做什么。
- 简化,开发人员尽量让写出的代码简单、直接。
- 反馈,不断进行测试和反馈,以保证产品的质量。
- 勇气,每个团队成员都应该专注于为项目作出更佳的选择,即便这意味着不得不抛弃失败的方案转而从不同的角度去解决问题。
- 尊重,每个团队成员对项目都是重要的、有价值的。
- 大多数培训人员的经验是,如果他们讲一堂专门针对价值观的课,来自开发人员的反馈往往是:课程“太理论化了”,没有什么他们能够在项目使用的实用信息。同样,这也是可以理解的反应,在你真正理解那些价值观之前,很难看到他们的实际效用,而且没有价值观支撑的实践无法真正起作用这一点也不是一目了然的,这就成了一个鸡生蛋、蛋生鸡的问题。
- 人性化,牢记软件是人创造出来的,要平衡团队成员的需要与项目的需要。
- 经济因素,软件的背后总是有人要掏腰包的,每个人都要考虑到预算问题。
- 共同利益,寻找那些能够使得个人、团队和客户都能受益的实践。
- 自相似,一个月度循环与一个周循环是一样的,与一个日循环也是一样的。
- 改进,今天尽你的全力,同时要知道你明天怎么能够做得更好。
- 多样性,大量持有不同意见和视角的人在一起工作,从而得出更好的解决方案。
- 反思,优秀的团队会持续反思他们软件开发过程中哪些做法有效,哪些做法没有。
- 流畅,不断地交付意味着连续的开发工作流程,而不是明晰的阶段性流程。
- 机会,团队碰到的每一个问题都是学习关于软件开发的新东西的一个机会。
- 人员冗余,即使乍看起来有点浪费,人员冗余确实能够避免大的质量问题。
- 失败,你可以从失败中学到很多东西。应该允许失败的尝试。
- 质量,降低质量标准并不能让你更快速地交付产品。
- 责任明确,如果某个人对某件事负责,那么他应当有足够的权威来完成它。
- 慢慢来,向着正确的方向缓步前进,在采用新的实践时,不要太过大刀阔斧。
- 在一个成熟的极限编程团队中,角色和分工不是一成不变的。团队的目标是让每个人都能够发挥他最大的潜力来帮助团队取得成功。在开始阶段,固定的角色分工有助于学习新的习惯,比如让技术人员做技术方面的决定,业务人员则决定业务方面的事务。在团队成员之间那种相互尊重的新关系建立起来之后,固定的角色分工就会妨碍每个人发挥他的最大潜力。
- 编写单元测试会占用开发人员的时间,而且对于没写过很多单元测试的程序员来说,常常会感觉这是对他们时间的一种浪费,或者这种低级工作不应该是他们做的。8 可是一旦程序员开始使用测试驱动的开发方法,总会有那么一天,一个单元测试没能通过,而这个失败的测试会揪出一个原本要花费好几个小时才能发现的难缠 bug。这样的事情发生上几次,一个原本对测试驱动充满怀疑的开发人员将变成它的真正信徒。很多敏捷开发人员都这样说,发生这样的事情会让你变得“被测试感染了”,而测试驱动开发也变成了软件开发的显而易见的方法。
- 这里面反映出以下两个重要的极限编程原则。
- 责任明确。一旦某一对程序员承接了一项任务,他们就应该全力投入把它完成。如果他们遇到问题,就应该全力克服。但是他们也会向团队中的其他人寻求帮助,即使这么做可能会有点挫伤他们的自尊。(由于大家彼此坐得都很近,最好是其他人无意中听到他们遇到了麻烦,从而主动提供帮助。)
- 机会。每个新任务都是一个学习新东西的机会。如果某项技术有人不懂,学习该技术就成为了任务的一部分。这有助于知识的分享,而且给未来提供了更多的机会。
- 这里还涉及另外一个概念:共同所有权。在前面提到的 13 项极限编程的主要实践之外,还有以下 11 项衍生实践
- 真正的客户参与。让客户参与到每季度和每周的计划会议中,并且真心倾听。
- 增量式部署。对系统的每一个小部分进行单独部署,而不是做大规模的一次性部署(同时,相信这种增量式部署方法是可行的)。
- 团队的连续性。让那些高效率的团队始终在一起工作,不要拆散他们。
- 缩小团队规模。随着团队的不断进步,他们完成工作的速度会越来越快;这个时候不要给他们增加工作量,而应该减少一个团队成员(让这个人把极限编程的文化带到其他团队中去)。
- 根本原因分析。出现问题的时候,找出问题所在,然后分析问题之所以产生的原因,并从根本上解决该问题。
- 共享代码。开发团队应该只维护生产代码和测试代码;文档应该从代码库中自动生成,而项目历史则通过口口相传的方式予以保存(因为即便有书面的项目计划,也很少有人去查阅)。
- 唯一的代码仓库。不要维护好几个不同版本的代码仓库。
- 每天部署。每天都将软件的一个新版本发布出去。
- 就合同范围进行谈判。作为一个咨询公司,不要把合同范围定死了,然后去就工期讨价还价(这样通常会导致为了按时完工而牺牲产品质量),相反,把工期先敲定,然后随着项目的推进逐步协商项目的范围。
- 按使用付费。这也是一个与咨询相关的实践,不要按开发工作量收费,按客户对系统的使用进行收费;这样你会得到实时的、不间断的反馈,从而了解用户使用了哪些功能,哪些功能没有用到。
- 值得反复强调的一点是:极限编程的一个重要目标是使得软件可以很容易地进行修改。如果软件修改起来很容易,那么开发团队就会更愿意拥抱变化。这一点对于优秀的极限编程团队如何设计软件、如何编写代码有着深远的影响。
- 事实上,很多开发人员(甚至一些非常聪明的开发人员)所做的正好相反。他们口口声声说要构建可以复用的代码,然后花大量时间去设计他们心目中完美的、高度可复用的模块。但是,你很难为将来未知的情形预先做好设计,反而很容易把代码搞得过于抽象和宽泛,结果造成用来构建框架的代码甚至与真正用来实现业务逻辑的代码量差不多了。说来让人吃惊,今天为了实现代码复用而做的聪明设计,可能明天就变成大家前进道路上不得不绕着走的绊脚石,碰都不敢碰。
- 代码当中也存在反模式,就像项目管理中的反模式一样,识别代码中的反模式是消除代码复杂性的第一步。当某个反模式直接与代码的架构或设计相关联的时候,我们就把它称为代码异味 (code smell)
- 我们强烈建议每个读者都多多了解不同种类的代码异味和反模式。在我们看来,这方面信息的最佳来源渠道之一依然是 Ward Cunningham 最初搭建的 Wiki(http://www.c2.com/cgi/wiki?CodeSmell )。
- 我们把它称为枪伤手术 (shotgun surgery)。这种代码异味是指:当你尝试对代码的某个部分做一个小修改时,却发现需要同时修改另外好几处似乎完全不相关或勉强有那么一点关联的代码;你尝试对它们进行修改,结果发现其中的一个地方又需要修改其他地方,而且又是显然不相关的修改。如果代码的异味很重,那么很常见的一种情况是:一个程序员尝试做一个应该很简单的修改,却不得不修改十好几个地方或者在代码文件中跳来跳去,并最终因为大脑无法跟上这不断扩展的环环相扣的修改而不得不放弃。
- 在一个开发人员进行枪伤手术时,他常常会发现代码中还存在另外一种代码异味:半成品代码 (half-baked code,或者半成品对象,这在面向对象编程中很常见)。所谓的半成品代码,是指当程序员需要使用某一个对象(或者模块、单元等)时,必须同时对其他对象以特定的、事先规定好的方式进行初始化。比如,在初始化了某个库之后,你还必须为某些特定的变量设置默认值,同时还必须初始化它所依赖的另外一个库。你知道应该这么做是因为有文档或者示例代码;如果初始化不正确,就会导致程序崩溃,或者更糟糕,产生不可预测的行为。
- 某些代码异味与代码本身的写法有关。当你的代码中存在巨型类 (very large class)或者非面向对象代码中的巨型方法、函数或模块等时,代码会很难阅读和维护。而更重要的是,这通常表示你的代码所做的事情过多,可以拆散成多个更小的、更容易理解的单元。另一方面,重复代码 (duplicated code)是指一段雷同的(或者几乎雷同的)代码在多个地方出现。这很可能成为 bug 的源头,尤其是当某个程序员修改了其中一个地方,却忘了对剩下的地方做修改时。
- 其他的代码异味涉及代码的整体设计(即代码的各个单元之间是如何交互的)。乱麻般的代码 ,或者说有着复杂而混乱结构的代码,是软件工程界最为古老的代码异味之一,甚至可以追溯到 20 世纪 60 年代。乱麻式代码很容易地识别,因为它常常是曾经尝试过清理该代码但最终失败的开发人员写的恐吓或致歉注释。乱麻式代码的小兄弟,千层饼式代码 (lasagna code),是更隐蔽的一种代码异味。现代软件设计一般会把代码进行分层,每一层有其特定的目的或行为。但是,当层级过多,而且各个层级缺乏一致的模式时,要理解每个层级要做什么就变得困难起来。这可能又伴随着层级之间的代码泄露 ,即,本应封装在一个层级的代码、类型或概念却泄露到临近的层级中去了。
- 很多开发人员深陷边界情况的泥潭无法自拔。有些时候确实需要处理边界情况,很多情形下也确实需要做一些特殊处理。但是每新增一个边界情况,你得到的回报就越小。有些程序员经常会进入一个误区,即花费跟编写普通代码相当的时间去编写针对边界情况的代码,美其名曰:“增强代码可靠性。”这时,对边界情况的考虑就变得与过度计划相似,同时开始转移你的注意力。说到底,处理边界情况的代码很少运行。如果代码中充斥着各种边界检查,那将使得它更加难以理解,也就更加难以修改。
- 这就要说到我们称之为框架陷阱 (framework trap)的代码异味了,这是一种源自于程序员聪明过头的反模式。所谓框架陷阱,是指当一个程序员需要解决一个问题或执行一项任务时,他不是写代码去完成要做的事情,而是编写了一个更为庞大的、可以在将来被用来解决类似问题或执行类似任务的框架。
- 如果你能把一个东西作为单一的独立部分整合到项目中,而不必为此引入很多其他组件,那么这个东西就是一个库。库和框架的区别在于,库是要把代码拆散成一个个小的、可复用的组件。如果你的目标是写出简单的代码,那么每个组件就应该只做一件事;功能繁多的组件应该被拆分成多个更小的单元。这种拆分称为关注点分离 (separation of concerns),极限编程开发人员都很清楚,这是实现代码简化很重要的一点。
- 换句话说,通过组合众多的组件构造出框架,本意是希望能够节省时间,但是造成的结果却是给自己套上了枷锁,对于新信息和新需求的响应受到了框架的限制。我们把这叫作“框架陷阱”,这对于理解“简化”这一思想及其对项目的影响是很重要的一个概念。我们之所以拿它来举例,是因为它反映了与极限编程相抵触的思维的一个重要方面:偏爱把功能组合成单一的大单元,而不是拆散成众多的小单元。
- 今天看似美好的框架,明天就可能成为负担
- 从传统设计向极限编程式设计的转变,就是做决定的时间的转变。设计决定被推迟到有了足够的经验且该决定能够马上被用到的时候。这就使得开发团队能够做到以下这些。
- 更早地部署软件。
- 做决定的时候更有把握。
- 避免被错误的决定束缚手脚。
- 在原有设计前提发生变化时保持开发速度。
- 这种策略的代价在于,开发团队必须在整个项目的生命周期里持续地投入时间和精力去做设计,并以小步慢走的方式去做大的变动,这样才能保证不断地交付有价值的新功能。
- 发布新代码就像借贷一样。背一点债务可以加速开发进程,只要能够及时地通过重写把债务还清就行。可是一旦债务没有被还清,危险就会来临。
- 差劲的软件设计会随着时间推移而聚沙成塔。就算是杰出的开发人员所写的代码也有改进的空间。设计和编码上的问题在代码中存在的时间越长,这些问题就越会累积,最终导致枪伤手术那样的麻烦。一般开发团队把这些遗留的设计和编码问题称为技术债务 (technical debt)。高效的极限编程团队会在每个周期中留出专门的时间来还债。这是丢车保帅 (即在每个周循环中加入一些次要的故事和任务,从而为计划外的工作留出缓冲空间)的一种很好的用法。
- 一体化设计常常伴随着紧耦合的代码 (tightly coupled code),也就是每个单元都跟很多个其他单元有联系。修改高度耦合的代码是很让人头疼的事情,耦合常常导致枪伤手术以及很多其他反模式。而各个单元之间的联系(常常是没有文档说明的)将导致重写过程中引入 bug。
- 正如代码可以耦合,它也可以进行解耦 (decouple),也就是打破单元之间的联系(或者更好的一种做法是,一开始就不加入这种联系)。
- 当一个系统由小型的、独立的单元构成时,是最容易维护的:每个部分的代码都与其他的部分尽可能地解耦(有时候能够做到的解耦程度令人吃惊),从而把彼此间的依赖关系降到最低。这是构建一个不需太多的返工就能够修改的系统的一个关键。如果你的设计是高度解耦的,那么你很少会去做枪伤手术或在一次变更中同时修改系统的多个部分。如果你已经养成了随时重构(而不是等到以后)以减少耦合的习惯,那么你的代码库会处于一个比以前更好、更加松耦合的状态:每个代码单元只做一件事,并且与不相关的其他单元解耦。
- 你能够在很多成熟、高质量的开源项目(如 Linux、Apache HTTP 服务器和 Firefox)中发现增量式设计的好例子。这些项目都是围绕着一个坚实的核心进行设计的;开发人员使用一种插件式的架构(或者其他把代码与核心隔离开来的方法)来开发附件功能,而只有那些最常用的、最稳定的功能才会被纳入到核心中去。这样的设计带来的就是高度解耦的代码,这些项目的开发人员也习惯于不断重构、编写大量的测试,并且持续地进行集成。(事实上,很多极限编程的实践都或者源自于开源项目,或者经过开源项目的打磨。)
- 这些开源项目的开发方式都是这样的:开发人员编写相对独立的松耦合的单元,这些单元能够与核心协同工作,而整个项目的代码库则围绕着核心有机地生长着。每个开发人员一般都是独立地添加他或她自己的单元,并且假定所有人编写的代码都是高度解耦的,这样就可以保持新单元与已有单元之间发生冲突的几率较低。但是,这不是凭空做到的,团队成员都很清楚这一点。所有对现有代码的丰富都发生在同一个代码库上,所以这些团队都有自动和手动两种持续集成机制。(今天的某些持续集成和构建服务器实际上正是源自于这些项目。)同时,每个开发人员也都对代码异味和反模式高度警惕,并且感觉到有责任在发现它们的时候尽快予以修复。重构几乎从来不会被留到项目的最后;每个程序员都感觉到一种责任,只把他或她写的最好代码添加进来,并依靠其他开发人员的眼睛来不断发现和纠正问题。
- 开源软件的一般做法是,先为某个特定的应用场景编写一个软件,然后,其他人把你的软件拿过去,进行修改以适应他的需求。复用是这样进行的。而一旦你这么做了(即先使用、再复用),你就会发现各应用场景间的共同点,然后你再通过重构,把通用的功能提取出来。
- 这种模式你会经常见到。为了复用而设计过于僵化,很少奏效,也不适合软件开发。这也许是一个值得追求的目标,但是在可以预见的未来,是不可行的。“使用 - 使用 - 复用”这种模式更加适合软件开发。
- Unix 操作系统的工具集是增量式设计的经典范例。该工具集的增量式设计原则使得数以千计的开发人员可以在很多年间为它贡献或大或小的代码片段。Unix 工具集包含很多独立开发出来的小组成单元 11 ,大多数是由需要解决某个具体问题(而不是要构建一个大型的、一体化的操作系统)的开发人员写出来的。我们可以从 Unix 工具集这里学习到增量式开发是如何让大量不同的人(他们中很多人从未见过面)同时向一个大型的、可靠的、高质量的系统贡献代码,并且让该系统在数十年间持续不断地成长
- 帮助团队通过增量式设计来构建系统的一个关键在于在系统的各个模块之间建立一套简单的协定。各个模块如何相互沟通?是否要相互传递消息?是否要调用函数或方法?是否要访问网络服务?模块间的沟通机制越是简单一致,新增一个模块就越容易。
- 如果有一个系统,它的行为似乎是从各个模块之间的交互中自然而然地呈现出来的,而不是源自某个单一的模块,我们就把这个系统的设计称为呈现式设计 (emergent design)。使用呈现式设计的系统几乎总是由小型的、独立的、解耦的模块构成的(如 Unix 工具集、蚁群)。这些模块可以通过组合来完成复杂的任务,而系统的行为和功能不仅来自每个单独的模块,更来自模块间的交互。
- 顶尖的极限编程团队会很自然地使用呈现式设计来开发软件。这要从简单开始:每个模块都是为一个特定的用途而设计的。测试先行编程可以保证每个模块都保持简单,并且只有一种用途。开发人员首先针对该用途编写模块的测试代码,然后再编写功能代码,当所有测试都通过时,就结束编码,这样就不会增加多余的功能,也就没有冗余的代码。模块中的所有代码都是不可或缺的。
- 极限编程团队会避免过深的调用栈(一个模块调用第二个,第二个再调用第三个,如此往复)。程序的调用结构都比较扁平,这就减少了模块间的相互依赖。模块间的交互尽量简单:一个模块需要数据时,它就从另一个模块那里获取数据,或者更好的做法是,通过一个消息队列来获取,这样它甚至不需要知道输入数据是从哪里来的。为保持系统简单,极限编程团队会避免涉及很多模块的、多层次的、复杂的互动(比如 Unix 工具集,它就只是把数据通过管道一个一个向下传递)。
- 保持代码简单,使得开发团队可以拥抱变化。这正是极限编程的关键所在。
精益
- 精益的价值观包括以下这些。
- 消除浪费。找出那些不能直接帮助你创造出有价值软件的工作,把它们从项目当中去掉。
- 增强学习。通过项目的反馈来改进你开发软件的方法。
- 尽可能延迟决定。每一个项目的重要决定都要等到你拥有了最大量信息的时候再做,也就是在最后责任时刻。
- 尽快交付。理解延期的代价,并且通过拉动式系统和队列来将这种代价最小化。
- 帮助团队成功。创建一个专注而高效的工作环境,创造一个由精力充沛的人组成的团队。
- 保证产品完善。软件应该符合用户直觉,并形成一个一致的整体。
- 着眼全局。全面理解项目中的工作——使用恰当的衡量指标来保证你了解全局,毫无遗漏。
- 使用集合式开发的团队会同时探索多个选项并从中选出最好的一个,像A/B测试 中在一个用户样本上测试多个用户界面选项并选择最佳的那些特性。
- 神奇思维并非管理者的专利,它同样存在于英雄这边,他愿意加班创造“奇迹”,目的是得到认可、获得某个领导职位或者也许是得到更高的工资。他成为了评价其他团队成员的标杆。老板并不会去关注软件的设计和开发细节,也不会去注意有多少有设计缺陷的权宜之计变成了长久的解决方案。于是,每周工作多少个小时成了衡量一个团队成员价值的唯一标准。
- 神奇思维让人感觉良好。无论是“激励”团队的管理者还是那个努力工作并交付软件的英雄员工,你都会感觉自己解决了一个大问题。但是用神奇思维开发出来的软件从长远看是弊大于利的:技术债务积少成多,软件从来不曾“真正完成”,而且质量和测试总是被当作“锦上添花”的东西:bug 经常被引入,而且通常都是在软件发布后由用户发现。最终,团队的开发速度变得奇慢无比,因为团队花费在修复 bug 和糟糕代码上的时间远远大于它开发新功能的时间。
- 那么我们如何避免神奇思维呢?这是精益的一个主要目标。不应该把团队视为“黑匣子”,精益思维帮助你理解团队每天的工作具体是什么。精益思维让你不只看到团队本身,它还让你清晰地看到项目从开始前到结束后的所有细节(神奇思维有时在项目结束后也会发生)。精益思维帮助你尽可能快地剥除那些善意的谎言,无论是老板对团队的、管理者之间的、还是我们自己对自己的那些妨碍我们构建最好软件的谎言。精益试图去除那些虚妄,并帮助团队共同协作,思考如何创造真正的价值,而不是单纯地蛮干。
- 为你的项目创建一个价值流示意图应该不会花费你超过半小时的时间。下面是具体的做法。首先从团队已经开发并交付了的一个较小的价值单元开始。试着找到一个尽可能小的单元,这是最小可销售特性 (minimal marketable feature,MMF)的一个例子,也就是整个产品中,客户愿意给予优先级的最小功能单元。确定了这个单元之后,回头想想这个功能单元从设想到交付经历了哪些步骤。在纸上为每一个步骤画一个方框,用箭头把各个方框连起来。因为你画的是一个真正的功能特性在你的项目中所走过的路径,所以这将是一条直线,整个路径上没有决策和分支点,因为它代表的是该特性的真实历史。
- 总结一下:开发团队从一个问题开始,这个问题是对用户需求响应不够及时。通过使用量化指标和寻找根本原因,它做到了着眼全局。开发团队理解了项目在整个公司中的位置,并且能够找出几种方案,来长远地解决交付时间过长的问题。最重要的是,开发团队与老板现在都能够得到客观的信息,并且可以一起作出决策。
- 有三种约束你的工作流的重要浪费:Muda(徒劳无益的事 )、Mura(不均衡 )和 Muri(不合理或不可能的事 )。
看板
- 看板方法的核心在于:帮助团队改进其开发软件的方法。使用看板方法的团队对以下几件事情十分清楚:它是如何开发软件的;它与公司的其他部分是如何互动的;它在哪些方面会碰到因无效率和不均衡而导致的浪费;以及如何通过去掉浪费的根源来不断改进。传统上,当一个团队改进其开发软件的方法时,我们称之为“流程改善”。看板方法是通过应用敏捷中的概念(如最后责任时刻)来创建一种简单直接的流程改善方法的例子。
- 看板方法提供了一系列实践,让你可以稳定住并改善你开发软件的系统。最新的实践集可以在看板方法的 Yahoo! Group(https://groups.yahoo.com/neo/groups/kanbandev/info )上找到。
- 首先要遵循基本的原则如下所示。
- 从你现在的做法开始
- 愿意追求增量式的、渐进的改变
- 在最一开始,要尊重现有的角色、职责和职位
- 然后采用以下核心实践。
- 视觉化限制进行中的工作
- 管理流程
- 让流程规则清楚明确
- 实现反馈循环
- 在协作中提高,在实验中演进(使用模型 / 科学方法)
- 不需要一开始就采用全部 6 项实践。仅采用部分实践的做法称为“浅实践”,并可以随着采用更多的实践和更好的实现逐渐地增加深度。
- 看板方法是一种过程改善方法,或者说是一种基于精益思维帮助团队改进其开发软件和协同工作的方式的一种方法。
- 看板团队以现在的做法为起点,看到当下的全局,并追求增量式的、演进的改变来逐渐地改进整个系统。
- 看板方法在协作中提高,在实验中演进这一实践指的是用指标测量,做渐进式的改进,并且使用量化指标来确认改进确实是有效的。
- 每个团队都有一个开发软件的系统(无论它自己是否意识到了),而精益的系统思维正是要帮助团队理解该系统。
- 所谓看板是指用一个白板来将看板团队的工作流程可视化。
- 看板有很多栏目,每一个代表工作流程中的一个容器;栏目中的贴纸表示处于工作流程中的工作项。看板上的是工作项,而不是具体任务,因为看板方法并非一个项目管理系统。
- 当看板团队限制进行中的工作 时,它给看板的一栏增加一个数字,表示工作流中的该容器所允许存在的工作项的最大数量。
- 看板团队与其用户、经理以及其他利益干系人协同工作,来保证所有人都同意当一个栏目达到了它的上限时,团队将把注意力转向项目的其他部分,且不再将更多的工作项推进到已达上限的工作流容器。
- 值得反复强调的是:当经理碰到工作上限时,她不会埋怨开发团队。她把这看作是系统的一个局限,并跟其他经理一起找出解决方案。这是系统思维很重要的一部分。当每个处于系统中的人都能够意识到系统的存在,他们就会在系统内寻求解决问题的方案。而且因为每个人都理解该系统,不均衡和超复杂就不再仅仅是开发团队的问题。它们是所有人的问题,包括经理在内。