敏捷软件开发

 作为三篇系列文章的第一篇,我们将带你了解敏捷软件开发的重要做法——如何使用它们、你可能会碰到什么样的问题,以及你将从它们那里获得什么。

敏捷软件开发不是一个具体的过程,而是一个涵盖性术语(umbrella term),用于概括具有类似基础的方式和方法。这些方法,其中包括极限编程(Extreme Programming)、动态系统开发方法(Dynamic System Development Method)、SCRUM、Crystal和Lean等,都着眼于快速交付高质量的工作软件,并做到客户满意。

尽管构成这个敏捷开发过程的每种方法都具有类似的目标,但是它们实现这个目标的做法(practice)却不尽相同。我们把在自己完成所有过程中经历过的最佳做法集中到了本系列的文章里。

下面的图表基本勾画出了我们提炼出来的这些敏捷开发最佳做法。最中间的圆环代表一对程序员日常工作的做法。紧接着的中间一个圆环表示开发人员小组使用的做法。最外面的一个圆环是项目所涉及的所有人的做法——客户、开发人员、测试人员、业务分析师等等。

这些圆环里的所有做法都直接与四个角上显示的敏捷开发的核心价值相关:沟通(Communication)、反馈(Feedback)、勇气(Courage)和简单(Simplicity)。也就是说,每个做法都给予我们一条实现敏捷开发价值并让它们成为该过程一部分的具体方法。



在理想状况下,如果决定采用敏捷软件开发的方法,你就应该在一个经过管理层许可的敏捷开发实验项目里尝试所有的作法。这是掌握敏捷开发的最好方法之一,因为这样能保证得到支持,为你的努力提供更多的回报,帮助捕捉学习到的东西,这样你才能让敏捷开发过程来适应你独特的环境。

然而,这并不总是可行的,所以有的时候最好采用步步为营的方法。在这种情况下,我们建议从最里面的圆环向外面的圆环推进。也就是从开发人员实践开始,然后是小组这一层次的做法,最后再融入“统一小组(one team)”的概念。

为技术优势设个限——开发人员做法
技术优势是敏捷开发过程的核心。为了让其他的做法真正生效,我们必须在开发人员中进行技术优势的培训。从表面上看,技术优势可能看起来并不是核心优先对象,但是如果把我们注意力都放在上面,它将确保我们编写出不同寻常的优秀代码。这反过来同样会给予公司、客户,以及用户对软件和对我们交付能力的信心。

开发人员做法(developer practice)是我们推动技术优势的切实可行的方法。即使是独立完成,而没有其他敏捷开发做法的介入,开发人员做法也能够给你的软件带来巨大的收益。

开发人员做法可以被分解为四个做法(如果你把实际的编写代码的过程加上去就是五个做法)。它们分别是测试-编码-重整循环(Test-Code-Refactor cycle)、配对编程(Pair Programming)和简单设计(Simple Design)等。

测试-编码-重整(TCR)循环——第一步
由测试驱动的开发和重整常常被当作是各自独立做法,但是它们事实上是TCR循环的一部分。要建立我们正在寻求的紧密反馈循环,我们就需要把它们放在一起。

我们在这里的目标有两层:测试让我们对代码质量的充满信心,并能表明我们加入新代码的时候没有破坏任何东西;重整和测试有助于让代码变成我们就代码实际在做什么而进行沟通的最真实形式——任何人都应该可以看到它,并知道什么是什么。

由测试驱动的开发(TDD)是一个循环,它从测试失败开始,然后是编写足够的代码通过测试,再是重整代码,使得代码在实现系统当前功能的条件下尽可能地简单。

测试-编码-重整循环非常短暂——也就几分钟。如果超出这个时间范围那就意味着测试的级别过高,有可能加入了未经测试的实现代码。

在本文的开始部分,我们不会举出TDD的例子,有关的内容会在后面2, 3, 4详细讨论。在这里,从整体上把握并把重点放在TCR循环更有趣的方面上会更加有用。

就同任何极限编程/敏捷开发项目一样,要做的第一个素材(story)是一个经过简化的应用程序,用来完整地说明程序的功能。在本文里,这样的应用程序是一个二十一点纸牌游戏。在经过简化的第一个素材里,只有一个玩家外加一个发牌人,每个玩家只会得到两张牌,获胜者是两张牌发完后点数最大的人。

素材/要求
一个简单的二十一点纸牌游戏

玩家下注
给玩家和发牌人每人两张牌
给获胜者支付奖金(玩家获胜的机会为2:1)
验收测试
要知道我们的素材什么时候完成就需要经过一系列验收测试。我们这个简单游戏的验收测试如下:

玩家获胜
发牌人获胜
平局

玩家赌注总额=100
玩家赌注总额=100
玩家赌注总额=100

发牌人赌注总额=1000
发牌人赌注总额=1000
发牌人赌注总额=1000

玩家下注10
玩家下注10
玩家下注10

玩家发到10 & 9
玩家发到8 & 9
玩家发到8 & 9

发牌人发到8 & 9
发牌人发到10 & 9
发牌人发到8 & 9

玩家赌注总额=110
玩家赌注总额=90
玩家赌注总额=100

发牌人赌注总额=990
发牌人赌注总额=1010
发牌人赌注总额=1000



任务
素材往往单独解决起来往往非常困难,所以在一般情况下我们都把它分解为一系列任务来完成。在本文的二十一点纸牌游戏里,需要进行下列任务:

创建一副牌
创建一个投注台面
创建一手牌
创建游戏
创建一副牌
在把素材分解成为任务的时候,我们可以把各个任务再分解成一系列待办事项,从而指导我们进行测试。这让我们可以保证在通过所有测试之后完成这个任务。对于这一副牌,我们有下列事项需要完成。

向牌桌上放一张纸牌
在发牌的同时将其从牌桌上移走
检查牌桌是否为空
检查牌桌上纸牌的张数
将牌桌上的一副牌的张数限制为52张(如果超过,就要显示异常)
不断发牌,直到发完
洗牌
检查牌桌上纸牌的张数是否正确
在进行过第一轮的几个简单测试之后,我们的待办事项列表就像下面这样了:

向牌桌上放一张纸牌
在发牌的同时将其从牌桌上移走
检查牌桌是否为空
检查牌桌上纸牌的张数
将牌桌上一副牌的张数限制为52张(如果超过,就要显示异常)
不断发牌,直到发完
洗牌
检查牌桌上纸牌的张数是否正确
下一个要进行的测试是从牌桌上发牌。当我们在为测试方法编写代码的时候,我们所扮演的角色就是将要编写的应用程序的用户。这就是为什么我们给自己的类创建的接口要与给用户的接口像类似的原因。在本文的这个例子里,我们将按照命令/查询分离原则(Command/Query Separation Principle5)编写出下面这样的代码。

Deck类。如列表A所示。

列表A
[ocdes=java]
import java.util.List;

import java.util.ArrayList;

public class Deck {

 private static final int CARDS_IN_DECK = 52;

 private List cards = new ArrayList();

 public boolean isEmpty() {

   return size() == 0;

 }

 public int size() {

   return cards.size();

 }

 public void add(int card) throws IllegalStateException {

 if(CARDS_IN_DECK == size())

   throw new IllegalStateException("Cannot add more than 52 cards");

   cards.add(new Integer(card));

 }

 public int top() {

   return ((Integer) cards.get(0)).intValue();

 }

   public void remove() {

   cards.remove(0);

 }

}

[/codes]
我们所有的测试都通过了,而且我们没有看到任何重复或者其他必要的重整,所以应该是时候进行下面的测试了。然而事实却不是这样的。我们top和remove方法的实现里有一个潜在的问题。如果对一个空的Deck调用它们,会发生什么?这两个方法都会从纸牌的内部列表里跳出一个IndexOutOfBoundsException异常,但是目前我们还没有就这个问题进行沟通。回头看看简单性的原则,我们知道自己需要沟通。我们的类的用户应该知道这个潜在的问题。幸运的是,我们将这种测试当作是一种沟通的方式,因此我们增加了下面的测试。

原文地址:https://www.cnblogs.com/xieyunc/p/2793799.html