EagleBear2002 的博客

这里必须根绝一切犹豫,这里任何怯懦都无济于事

软件工程管理-06-极限编程

摘要

开发周期

XP 的心跳——开发周期

这是程序员实现一个工程任务(最小的调度单位)并与系统其余部分集成的地方。

开发周期 (1)

  1. 1 张顶层任务卡:“用户管理:登录、登出、组。”
  2. 获取结对编程伙伴
  3. 讨论任务
  4. 测试用例是什么?如果对任何事情不确定,向他人寻求帮助。
  5. 编写测试用例

开发周期 (2)

  1. 测试失败
  2. 编写代码
  3. 运行所有测试用例
  4. 迭代测试用例和代码
  5. 如有需要,重构
  6. 集成,包括测试

注意事项

  • 程序员成对编程。
  • 开发由测试驱动。
  • 成对不仅仅是使测试用例运行。成对为系统的分析、设计、实现和测试增加了价值。
  • 集成紧随开发之后,包括集成测试。

变更成本

变更成本

在某些情况下,软件变更成本随时间指数上升的趋势可以被平缓化。如果我们能够平缓这个曲线,关于软件开发最佳方式的传统假设就不再成立。

变更成本的假设

软件工程的一个普遍假设是,随着时间的推移,修改程序的成本会指数上升。

“软件中修复一个问题的成本随时间指数上升。如果在需求分析阶段发现问题,修复成本可能只需要一美元,但一旦软件投入生产,修复成本可能高达数千美元。”

变更成本小——一个例子

人寿保险合同管理系统

  • 1700——一笔交易可以从多个账户中扣除并记入多个账户的功能实际上没有使用。每笔交易都只从一个账户扣除并记入另一个账户。
  • 1702——在系统中的 300,000 笔交易中,每笔交易都只有一个借方账户和一个贷方账户。
  • 1705——我们会更改交易的接口并更改实现。我们编写所需的四个方法并开始测试。
  • 1715——测试(超过 1,000 个单元和功能测试)仍然 100% 通过。
  • 1720——夜间批处理已完成,数据库已备份。我们安装了代码的新版本并运行迁移。
  • 1730 —— 我们想到的所有事情都正常工作。我们回家了。
  • 第二天——错误日志是清晰的。用户没有投诉。变更成功。

结论:在某些条件下,变更成本很小。

降低变更成本的技术

软件开发界在最近几十年投入了大量资源试图降低变更成本——更好的语言、更好的数据库技术、更好的编程实践、更好的环境和工具、新的符号。

变更成本可能不会随时间急剧上升

XP 的技术前提

如果变更成本随时间缓慢上升,你的行动方式将与在成本指数上升的假设下完全不同。

如果变更成本很小,你会怎么做

  1. 你会尽可能晚地做出重大决策,以推迟决策成本,并尽可能确保决策正确。
  2. 你只会实现你必须实现的部分,希望你预见到的明天的需求不会实现。
  3. 你只会在设计中引入简化现有代码或使编写下一段代码更简单的元素。

保持变更成本低

  • 在技术方面,对象是一个关键技术。
  • 简单的设计,没有额外的设计元素——没有尚未使用但预计将来会使用的想法。
  • 自动化测试,以便我们有信心知道我们是否意外改变了系统的现有行为。
  • 大量修改设计的实践,以便在需要更改系统时,我们不会害怕尝试。

学习驾驶

问题与解决方案资源

  • 问题——风险的巨大成本,以及通过选择来管理该风险的机会
  • 塑造解决方案所需的资源:在周期后期进行更改而不显著增加成本的自由。

小幅度调整与关注

  • 我们需要通过进行许多小幅度调整来控制软件的开发,而不是通过进行几次大幅度调整,有点像驾驶汽车。
  • 始终保持关注。

四个价值观

沟通、简单、反馈、勇气。

押题:四个价值观的关系。

沟通

  • 开发者与开发者之间
  • 开发者与客户之间
  • 开发者与管理层之间
  • XP 强制开发者进行沟通:
    • 单元测试
    • 结对编程
    • 任务估算

简单

  • “能够起作用的最简单的事情是什么?”
  • XP 认为,今天做一件简单的事情,明天如果需要再花一点代价去改变它,比今天做一件可能永远都不会用到的更复杂的事情要好。

沟通与简单

  • 你沟通得越多,就越能清楚地看到究竟需要做什么,对那些真正不需要做的事情就越有信心。
  • 你的系统越简单,需要沟通的内容就越少,这会导致更完整的沟通,特别是如果你能简化系统到只需要更少的程序员时。

反馈

  • 关于系统当前状态的具体反馈是绝对无价的。
  • 首先,反馈在分钟和天的尺度上起作用。
    • 开发者的单元测试
    • 开发者对客户的即时估算
    • 进度跟踪人员向整个团队提供反馈
  • 反馈也在周和月的尺度上起作用。
    • 客户的功能测试
    • 运行软件

反馈、沟通与简单

  • 你拥有的反馈越多,沟通就越容易。
  • 简单的系统更容易测试(反馈)。
  • 编写测试为你提供了系统可以有多简单的焦点。

勇气

例子

  • 在 30 周中的 25 周,存在一个大问题
  • 架构缺陷
  • 修复缺陷。这使得 50% 的测试无法通过。

丢弃代码

完全重来。

爬山算法

XP 的设计策略类似于爬山算法。你先得到一个简单的设计,然后使其稍微复杂一些,再稍微简单一些,然后再稍微复杂一些。爬山算法的问题在于达到局部最优解,此时没有小的改变可以改善情况,但大的改变可以。

勇气与其他价值观

  • 如果没有前三个价值观,勇气本身只是单纯的黑客行为。
  • 沟通支持勇气,因为它打开了进行更多高风险、高回报实验的可能性。
  • 简单支持勇气,因为你可以负担得起在一个简单的系统中更加勇敢。
  • 具体反馈支持勇气,因为如果你可以按下一个按钮并在最后看到测试变为绿色,你会感到更安全地对代码进行激进的手术。

价值观的实践——尊重

  • 一个隐藏在其他四个价值观之下的——尊重。
  • 如果团队成员彼此不在乎以及他们正在做的事情,XP 就注定失败。
  • 如果团队成员不在乎项目,没有什么可以拯救它。

回归基础

押题:四个基本活动之间的关系。

必须做的事情

  • 我们希望做我们必须做的一切,以实现稳定、可预测的软件开发。
  • 开发的四个基本活动是编码、测试、倾听和设计。

编码

  • 它是你的工作成果。
  • 其他方面:
    • 学习
    • 沟通:精确

测试

  • “无法测量的东西就不存在”——科学哲学(对编程也成立)
  • 测试让我有机会在实现方式之外思考我想要的东西。然后测试告诉我我是否实现了我认为自己实现的东西。

测试感染

  • 测试告诉你何时完成——当测试运行时,你暂时完成了编码。
  • 当你想不到任何可能失败的测试来编写时,你就完全完成了。

为什么测试?

  • 长期答案是测试可以让程序更长久地运行(如果测试被运行和维护)。你可以更长时间地修改程序。
  • 短期原因:信心
  • 编程和测试结合在一起也比单纯编程更快。
    • 生产力的提高来自于减少调试所花费的时间。

什么类型的测试?

  • 我们将有程序员编写的单元测试,以说服他们自己的程序按照他们认为的方式工作。
  • 我们还将有客户编写(或至少由客户指定)的功能测试,以说服他们整个系统按照他们认为整个系统应该的方式工作。

倾听

  • 程序员向业务人员请教以获得项目的业务视角。
  • 程序员帮助业务人员了解软件中什么是容易的,什么是困难的。
  • 相互倾听(程序员之间、程序员与客户之间)

设计

  • 仅仅倾听、编写测试用例、使其运行、再倾听、编写测试用例、使其运行是不够的吗?不是的
  • 唯一使下一个测试用例运行的方法是破坏另一个。
  • 或者唯一使测试用例运行的方法是远比值得的麻烦。
  • 良好的设计:
    • 良好的设计将逻辑组织起来,使得对系统一个部分的更改不总是需要对系统的另一个部分进行更改。
    • 良好的设计确保系统中的每一段逻辑都有且只有一个归属地。
    • 良好的设计将逻辑放在它操作的数据附近。
    • 良好的设计允许通过仅在一个地方进行更改来扩展系统。

结论

  • 因此,你编码是因为如果你不编码,你什么也没做。
  • 你测试是因为如果你不测试,你就不知道何时完成了编码。
  • 你倾听是因为如果你不倾听,你就不知道要编码或测试什么。
  • 你设计是为了能够无限期地继续编码、测试和倾听。

快速概述

介绍

我们将依赖于简单实践之间的协同作用,这些实践通常在几十年前被认为不切实际或天真而被放弃。

计划游戏

  • 商业考虑和技术考虑都不应占据主导地位。
  • 软件开发始终是可能与理想之间不断演变的对话。
  • 商业不能在真空中做出这些决定。开发需要做出技术决策,为商业决策提供原材料。
商业人员决定 技术人员决定
范围:为使系统在生产中具有价值,必须解决多少问题?
优先级:如果最初只能选择 A 或 B,你想要哪一个?
发布的组成:在软件比没有软件更好之前,需要做多少或做多少?
发布的日期:哪些重要日期软件(或部分软件)的存在会带来很大不同?
估算:实现一个功能需要多长时间?
后果:有些战略性的商业决策只有在了解技术后果后才能做出。
过程:工作和团队将如何组织?
详细计划:在一个发布中,哪些故事将首先完成?

小规模发布

  • 每次发布都应该尽可能小,包含最有价值的业务需求。
  • 发布作为一个整体必须有意义。
  • 计划一个月或两个月的时间要比计划六个月或一年的时间要好得多。

隐喻

  • 每个 XP 软件项目都由一个单一的总体隐喻指导。
  • XP 中的隐喻取代了其他人所说的“架构”的大部分内容。
  • 架构并不一定使系统具有任何意义上的内聚性。
  • 选择一个系统隐喻,通过一致地命名类和方法来保持团队在同一页面上。
  • 例如,克莱斯勒的工资系统是作为生产线构建的。在另一家汽车制造商中,汽车销售被构建为材料清单。
  • 搜索引擎是一大群蜘蛛,在网上四处寻找要捕捉的东西,然后把东西带回巢穴。

简单设计

  1. 运行所有测试。
  2. 没有重复的逻辑。警惕隐藏的重复,如平行类层次结构。
  3. 表达程序员认为重要的每一个意图。
  4. 拥有尽可能少的类和方法。

反对:“为今天实现,为明天设计。”

测试

  • 没有自动化测试的任何程序功能实际上都不存在。
  • 程序员编写单元测试,以便他们对程序运行的信心可以成为程序本身的一部分。
  • 客户编写功能测试,以便他们对程序运行的信心也可以成为程序的一部分。

重构

  • 在实现程序功能时,程序员总是会问是否有办法改变现有程序,使添加功能变得简单。
  • 你不会基于猜测进行重构;当系统要求你这样做时,你才会进行重构。
    • 当系统要求你复制代码时,它就是在要求重构。

结对编程

所有生产代码都是由两个人看着一台机器编写的,使用一个键盘和一个鼠标。

  • 一个伙伴,拥有键盘和鼠标的人,正在思考如何最好地实现这个方法。
  • 另一个伙伴则更具战略性地思考:
    • 这种整体方法会起作用吗?
    • 还有哪些测试用例可能还没有通过?
    • 是否有办法简化整个系统,使当前问题消失?
  • 如果两个人在早上结对,下午他们可能很容易与其他同事结对。
  • 如果你负责一个你不熟悉的领域的任务,你可能会请最近有经验的人与你结对。

集体所有权

任何看到有机会为代码任何部分增加价值的人都被要求随时这样做。

  • 没有所有权:在过去,没有人拥有任何特定的代码部分。如果有人想更改一些代码,他们会根据自己的目的进行更改,不管它是否与已有的代码很好地结合。
  • 个人代码所有权:只有代码的官方所有者才能更改代码的一部分。任何其他看到代码需要更改的人都必须向所有者提交请求。人们不愿打扰代码所有者。

持续集成

  • 代码在几小时后集成和测试——最多一天的开发。
  • 一种简单的方法是专门有一台机器用于集成。
  • 当机器空闲时,有代码要集成的结对会坐下来,加载当前版本,加载他们的更改(检查并解决任何冲突),并运行测试直到通过(100% 正确)。
  • 一次集成一组更改效果很好,因为谁应该修复失败的测试显而易见——应该是我们,因为我们肯定破坏了它,因为上一对离开时测试是 100%。

40 小时工作制

  • 加班是项目上严重问题的症状。
  • 是否将其转化为每周在工作场所正好 40 小时并不太重要。

现场客户

  • 真正的客户必须与团队坐在一起,随时回答问题,解决争端,并设定小规模优先级。
  • 对此规则的主要反对意见是,正在开发的系统的真正用户太有价值,不能交给团队。
    • 管理者将不得不决定哪个更有价值——让软件更早、更好地工作,还是拥有一个人或两个人的输出。

编码标准

如果你将让所有这些程序员从系统的一个部分换到另一个部分,每天换几次伙伴,并不断重构彼此的代码,你根本无法负担拥有不同的编码实践。

这样如何能行?

这些实践相互支持。一个实践的弱点被其他实践的优势所弥补。

本章如何组织?

  • 上述实践没有一个是独特或原创的。
  • 它们自编写程序以来就已经被使用。
  • 大多数这些实践由于其弱点变得明显而被更复杂、开销更高的实践所取代。
  • 如果这些弱点现在被其他实践的优势所弥补会怎样?

计划游戏

  • 你不可能仅凭一个粗略的计划就开始开发。
  • 你不能不断更新计划——那会花费太多时间并让客户不安。
    • 客户根据程序员提供的估算自己更新计划。
    • 你一开始有足够的计划,让客户对接下来几年可能实现的内容有一个大致的了解。
  • 你进行短周期发布,因此计划中的任何错误最多只会影响几周或几个月。
  • 你的客户与团队坐在一起,因此他们可以迅速发现潜在的变化和改进机会。

短周期发布

  • 你不可能在几个月后就投入生产。
    • 计划游戏帮助你专注于最有价值的故事,因此即使是小型系统也具有商业价值。
    • 你持续集成,因此打包发布成本很小。
  • 你的测试将缺陷率降低到足够低,因此你不需要在允许软件发布之前进行漫长的测试周期。
  • 你可以进行简单的设计,足以应对此次发布,而不是永远。

隐喻

你不可能仅凭一个隐喻就开始开发。那里没有足够的细节,而且,如果你错了怎么办?

  • 你很快就能从实际代码和测试中获得关于隐喻是否在实践中有效的具体反馈。
  • 你的客户能够用隐喻来谈论系统。
  • 你通过重构不断细化对隐喻在实践中意义的理解。

简单设计

你不可能仅凭今天的代码就拥有足够的设计。你会将自己设计到一个死胡同,然后你将无法继续系统的发展。

  • 你习惯于重构,因此进行更改不是问题。

简单设计

  • 你有一个清晰的总体隐喻,因此你确信未来的更改会沿着一个收敛的方向发展。
  • 你与伙伴一起编程,因此你有信心你正在做出一个简单的设计,而不是愚蠢的设计。

测试

你不可能编写所有这些测试。那会花费太多时间。程序员不会编写测试。

  • 设计尽可能简单,因此编写测试并不那么困难。
  • 结对编程——伙伴的压力
  • 当你看到所有测试都在运行时,你会感觉很好。
  • 当客户看到他们所有的测试都在运行时,他们会感觉系统很好。

重构

  • 你不可能一直重构系统的设计。这会花费太长时间,难以控制,而且很可能破坏系统。
    • 你习惯于集体所有制,所以你不在意在需要的地方进行更改。
    • 你有编码标准,所以在重构之前不需要重新格式化。
  • 你成对编程,因此你更有可能有勇气进行艰难的重构,而且你不太可能破坏东西。
  • 你有简单的结构,因此重构更容易。
  • 你有测试,因此你不太可能在不知情的情况下破坏东西。
  • 你持续集成,因此如果你不小心破坏了远处的东西,或者你的重构与他人的工作冲突,你将在几小时内知道。
  • 你休息得很好,因此你更有勇气,也更不可能犯错。

结对编程

  • 你不可能成对编写所有生产代码。那会太慢。如果两个人不和怎么办?
    • 编码标准减少了琐碎的争吵。
    • 每个人都休息得很好,进一步减少了无利可图的……呃……讨论的机会。
  • 结对编写测试,让他们在着手实现核心内容之前有机会对理解进行对齐。
  • 结对有隐喻来指导他们关于命名和基本设计的决策。
  • 结对在简单的结构中工作,因此他们都能理解发生了什么。

集体所有权

  • 你不可能让每个人都有可能更改任何地方的东西。人们会到处破坏东西,集成的成本会大幅上升。
    • 你在足够短的时间内集成,因此冲突的机会减少。
  • 你编写并运行测试,因此意外破坏东西的机会减少。
  • 你成对编程,因此你不太可能破坏代码,程序员更快地了解他们可以有利地更改的内容。
  • 你遵循编码标准,因此你不会陷入可怕的花括号大战。

持续集成

  • 你不可能在仅工作几小时后就进行集成。集成花费的时间太长,冲突太多,破坏东西的机会也太多。
  • 你可以快速运行测试,因此你知道你没有破坏任何东西。
  • 你成对编程,因此需要集成的更改流减少了一半。
  • 你重构,因此有更多的小块,减少了冲突的机会。

40 小时工作制

你不可能每周工作 40 小时。

  • 计划游戏为你提供了更有价值的工作。
  • 计划游戏和测试的结合减少了你比预期有更多的事情要做的糟糕惊喜的频率。
  • 整体实践帮助你以最快速度编程,因此你无法更快。

现场客户

你不可能让真正的客户全职坐在团队中。他们可以在其他地方为业务创造更多的价值。

  • 他们可以通过编写功能测试为项目创造价值。
  • 他们可以通过为程序员做出小规模的优先级和范围决策为项目创造价值。

编码标准

你不可能要求团队按照共同的标准编码。程序员非常个人主义,宁愿辞职也不愿将花括号放在其他地方。

  • 整个 XP 让他们更有可能成为获胜团队的一员。

结论

测试策略

概述

我们将在编码之前编写测试,每分钟都进行。我们将永久保留这些测试,并经常一起运行它们。我们还将从客户的角度推导出测试。

独立且自动

  • 首先,每个测试不与其他你编写的测试交互。
  • 测试也是自动的。
  • 当压力水平上升,当人们工作过度,当人类判断开始失效时,测试最有价值。因此,测试必须是自动的——给出一个明确的系统是否按预期运行的指示。

无法绝对测试一切

  • 你应该测试可能会出错的事情。
  • 测试是一种赌博。
  • 测试可以通过的一种方式是当你没有预料到会成功的测试却成功了。
  • 你只会编写那些能够带来回报的测试。

谁编写测试?——程序员

  • 如果方法的接口有任何不清晰之处,你在编写方法之前编写测试。
  • 如果接口清晰,但你认为实现会有一点复杂,你在编写方法之前编写测试。
  • 如果你想到了代码应该按预期工作的一个不寻常的情况,你编写一个测试来传达这种情况。
  • 如果你后来发现一个问题,你编写一个测试来隔离这个问题。
  • 如果你即将重构一些代码,并且你不确定它应该如何表现,并且没有针对所讨论的行为方面的测试,你先编写测试。

程序员测试

  • 程序员逐方法编写测试。
  • 程序员编写的单元测试始终运行在 100%。
  • 因为程序员控制单元测试的编写和执行,他们可以保持测试完全同步。

谁编写测试?——客户

  • 客户逐故事编写测试。
  • 他们需要问自己的问题是,“在我对这个故事完成有信心之前,需要检查什么?”他们想到的每个场景都会变成一个测试,在这种情况下是一个功能测试。

客户测试

  • 功能测试不一定始终运行在 100%。
  • 客户通常不能自己编写功能测试。
  • 这就是为什么任何规模的 XP 团队至少需要一名专职测试人员。

其他测试

  • 平行测试——旨在证明新系统与旧系统完全相同的一种测试。
  • 压力测试——旨在模拟最糟糕的负载的一种测试。压力测试适用于性能特性不易预测的复杂系统。
  • 猴子测试——旨在确保系统在面对无意义输入时表现得合理的一种测试。

设计已死

XP 中的设计?

对于许多初次接触极限编程(Extreme Programming, XP)的人来说,XP 似乎宣告了程序设计的死亡。不仅许多设计行为被嘲笑为“冗余的前期设计”(Big Up Front Design),甚至像 UML、灵活的框架(Framework)、模式(patterns)这些设计技巧也被轻视乃至被完全忽视。

实际上,XP 中包括很多设计,只是不同于以往软件开发流程中的做法。XP 通过允许进化的实践技巧使演进式设计(evolutionary design)成为一种可行的设计策略。它还为设计人员(Designers)提供了新的挑战与技巧,让他们学习如何使设计简单,如何利用重构保持设计的整洁,如何在一个演进的形式下使用模式。

演进式设计

演进式设计。它的本质是系统的设计随着软件开发的过程增长。设计(design)是撰写程序代码过程的一部分,随着程序代码的发展,设计也跟着调整。

在常见的使用中,演进式设计实在是彻底的失败。设计的结果其实是一堆为了某些特殊条件而巧妙安排的决定所组成,每个条件都会让程序代码更难修改。

设计

所谓的设计(design)是要能够让你可以长期很简单地修改软件。当设计(design)不如预期时,你应该能够做有效的更改。一段时间之后,设计变得越来越糟,你也体会到这个软件混乱的程度。

计划式设计

计划式设计的做法正好相反(借鉴其他工程学科)。

在设计图中确定所有的细节,一部分使用数学分析,但大部分都是使用建筑规范。所谓的建筑规范就是根据成功的经验(有些是数学分析)制定出如何设计结构体的法则。当设计图完成,她们公司就可以将设计图交给另一个施工的公司按图施工。

计划式设计在软件开发中的应用

计划式设计将同样的方式应用在软件开发。

Designer 先定出重要的部分,程序代码不是由他们来撰写,因为软件并不是他们“建造”的,他们只负责设计。所以 designer 可以利用像 UML 这样的技术,不需要太注重撰写程序代码的细节问题,而在一个比较属于抽象的层次上工作。一旦设计的部分完成了,他们就可以将它交给另一个团队(或甚至是另一家公司)去“建造”。因为 designer 朝着大方向思考,所以他们能够避免因为策略方面不断的更改而导致软件的失序。Programmer 就可以依循设计好的方向(如果有遵循设计)写出好的系统。

计划式设计的缺点

  • 计划式设计方法从 70 年代出现。
  • 在很多方面它比 code and fix 渐进式设计要来的好。

缺点:

  • 第一个缺点是当你在进行设计时,你不可能同时把所有必须处理的问题都想清楚。所以将无可避免的遇到一些让人对原先设计产生质疑的问题。可是如果 designer 在完成工作之后就转移到其他项目,那怎么办?Programmer 开始迁就设计来写程序,于是软件开始趋于混乱。就算找到 designer,花时间整理设计,变更设计图,然后修改程序代码。但是必须面临更短的时程以及更大的压力来修改问题,又是混乱的开端。
  • 第二:软件开发文化方面的问题。Designer 因为专精的技术和丰富的经验而成为一位 designer。然而,他们忙于从事设计而没有时间写程序代码。但是,开发软件的工具发展迅速,当你不再撰写程序代码时,你不只是错失了技术潮流所发生的变化,同时也失去了对于那些实际撰写程序代码的人的尊敬。

建造者与设计者的关系

建造者和设计者之间这种微妙的关系在建筑界也看得到,只是在软件界更加凸显而已。之所以会如此强烈是因为一个关键性的差异。在建筑界,设计师和工程师的技术有清楚的分野;在软件界就比较分不清楚了。任何在高度注重 design 的环境工作的 programmer 都必须具备良好的技术,他的能力足够对 designer 的设计提出质疑,尤其是当 designer 对于新的发展工具或平台越来越不熟悉的情况下。

变更需求

处理变更需求的方式之一是做有弹性的设计,于是当需求有所更改,你就可以轻易的变更设计。然而,这是需要先见之明去猜测将来你可能会做怎样的变更。一项预留处理易变性质的设计可能对于将来的需