public void testTopOnEmptyDeck() {
<o:p> </o:p>
Deck deck = new Deck();
<o:p> </o:p>
try {
<o:p> </o:p>
deck.top();
<o:p> </o:p>
fail("IllegalStateException not thrown");
<o:p> </o:p>
} catch(IllegalStateException e) {
<o:p> </o:p>
assertEquals("Cannot call top on an empty deck", e.getMessage());
<o:p> </o:p>
}
<o:p> </o:p>
}
<o:p> </o:p>
public void testRemoveOnEmptyDeck() {
<o:p> </o:p>
Deck deck = new Deck();
<o:p> </o:p>
try {
<o:p> </o:p>
deck.remove();
<o:p> </o:p>
fail("IllegalStateException not thrown");
<o:p> </o:p>
} catch(IllegalStateException e) {
<o:p> </o:p>
assertEquals("Cannot call remove on an empty deck", e.getMessage());
<o:p> </o:p>
}
<o:p> </o:p>
}
<o:p> </o:p>
上面都是异常测试(Exception Test2)的例子。我们再一次运行这些测试看它们失败,然后加入实现让它们通过。
<o:p> </o:p>
public int top() {
<o:p> </o:p>
if(isEmpty())
<o:p> </o:p>
throw new IllegalStateException("Cannot call top on an empty deck");
<o:p> </o:p>
return ((Integer) cards.get(0)).intValue();
<o:p> </o:p>
}
<o:p> </o:p>
public void remove() {
<o:p> </o:p>
if(isEmpty())
<o:p> </o:p>
throw new IllegalStateException("Cannot call remove on an empty deck");
<o:p> </o:p>
cards.remove(0);
<o:p> </o:p>
}
<o:p> </o:p>
尽管guard语句有重复,但是我们决定不去管它,没有将它们简化成一个共同的方法。这是因为沟通的价值超过了重复的代价,当然这只是一个个人的选择。
<o:p> </o:p>
一手牌
我们已经完成了对牌桌和投注台面的测试和实现,现在就到了创建一手牌的时候了。待办事项列表再一次发挥其作用,我们得到了下面这样一个列表:
<o:p> </o:p>
创建一个一开始没有纸牌的空手
向手上加入纸牌
检查一只手是否击败了另一手
检查一只手是否爆了
<o:p> </o:p>
<o:p> </o:p>
为空手增加一个测试很简单,我们继续到给手上加入纸牌。
<o:p> </o:p>
public void testAddACard()
{
Hand hand = new Hand();
hand.add(10);
assertEquals(1, hand.size());
hand.add(5);
assertEquals(2, hand.size());
}
<o:p> </o:p>
我们运行测试,然后加入实现。
<o:p> </o:p>
public void add( int card )
{
cards.add(new Integer(card));
}
<o:p> </o:p>
测试通过了,我们没有看到Hand类里有任何重复。但是我们刚刚给Hand加上的实现和给Deck加上的方法极其相似。回头看看牌桌的待办事项列表,我们记得必须检查牌桌(上纸牌的张数)是否正确,我们最后也对手做同样的事情。
<o:p> </o:p>
public void testAddInvalidCard() {
<o:p> </o:p>
Hand hand = new Hand();
<o:p> </o:p>
try {
<o:p> </o:p>
hand.add(1);
<o:p> </o:p>
fail("IllegalArgumentException not thrown");
<o:p> </o:p>
} catch(IllegalArgumentException e) {
<o:p> </o:p>
assertEquals("Not a valid card value 1", e.getMessage());
<o:p> </o:p>
}
<o:p> </o:p>
try {
<o:p> </o:p>
hand.add(12);
<o:p> </o:p>
fail("IllegalArgumentException not thrown");
<o:p> </o:p>
} catch(IllegalArgumentException e) {
<o:p> </o:p>
assertEquals("Not a valid card value 12", e.getMessage());
<o:p> </o:p>
<o:p> </o:p>
}
<o:p> </o:p>
}
<o:p> </o:p>
我们加入了下面的实现来通过测试。
<o:p> </o:p>
public void add( int card )
{
if(card < 2 || card > 11)
throw new IllegalArgumentException("Not a valid card value " + card);
cards.add(new Integer(card));
}
<o:p> </o:p>
但是现在我们在Deck和Hand里有相同的guard语句,用来检查该自变量是否代表着正确的纸牌值。简单性的原则要求我们删除重复,但是在这里情况并不像Extract Method重整6这么简单。如果我们看到多个类之间存在重复,这意味着我们缺失了某种概念。在这里我们很容易就看到Card类担负起了判断什么值是有效的责任,而Deck和Hand作为Card的容器变得更具沟通性。
<o:p> </o:p>
我们引入了Card类以及相应的Deck和Hand重整,如列表B:
<o:p> </o:p>
public class Card {
<o:p> </o:p>
private final int value;
<o:p> </o:p>
public Card( int value ) {
<o:p> </o:p>
if( value < 2 || value > 11 )
<o:p> </o:p>
throw new IllegalArgumentException( "Not a valid card value " + value );
<o:p> </o:p>
this.value = value;
<o:p> </o:p>
}
<o:p> </o:p>
public int getValue() {
<o:p> </o:p>
return value;
<o:p> </o:p>
}
<o:p> </o:p>
}
<o:p> </o:p>
public class Deck {
<o:p> </o:p>
private static final int[] NUMBER_IN_DECK = new int[] {0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 16, 4};
<o:p> </o:p>
…
<o:p> </o:p>
public void add( Card card ) throws IllegalStateException {
<o:p> </o:p>
if(NUMBER_IN_DECK[card.getValue()] == countOf(card))
<o:p> </o:p>
throw new IllegalStateException("Cannot add more cards of value " + card.getValue());
<o:p> </o:p>
cards.add(card);
<o:p> </o:p>
}
<o:p> </o:p>
public Card top() {
<o:p> </o:p>
if(isEmpty())
<o:p> </o:p>
throw new IllegalStateException("Cannot call top on an empty deck");
<o:p> </o:p>
return (Card) cards.get(0);
<o:p> </o:p>
}
<o:p> </o:p>
…
<o:p> </o:p>
private int countOf(Card card) {
<o:p> </o:p>
int result = 0;
<o:p> </o:p>
for(Iterator i = cards.iterator(); i.hasNext(); ) {
<o:p> </o:p>
Card each = (Card) i.next();
<o:p> </o:p>
if(each.getValue() == card.getValue())
<o:p> </o:p>
result++;
<o:p> </o:p>
}
<o:p> </o:p>
return result;
<o:p> </o:p>
}
<o:p> </o:p>
}
<o:p> </o:p>
public class Hand {
<o:p> </o:p>
…
<o:p> </o:p>
public void add( Card card ) {
<o:p> </o:p>
cards.add(card);
<o:p> </o:p>
}
<o:p> </o:p>
…
<o:p> </o:p>
}
<o:p> </o:p>
测试-编码-重整循环的每一阶段都涉及不同类型的思想。在测试阶段,重点放在了被实现的类的接口上。编写代码是为了让测试尽可能快地通过测试。而重整阶段可以被当作是使用简单性原则进行指导的微型代码审查。有没有重复的或者看起来类似的代码,不仅仅是在当前的类里,而且是在系统的其他类里?现在的实现可能会出现什么问题,类的用户能够与之顺利沟通吗?
重要的成功因素
<o:p> </o:p>
小步前进——TCR对于开发人员来说不是一个很容易的转换。一次只进行一个步骤,同时还要明白它学习起来有一定难度。
严格遵守原则——只进行TDD或者只进行重整并不能让整个TCR循环一蹴而就。给自己足够的时间来尝试,并取得效果。压力和最终期限会迫使小组回到原来的习惯上——一定要小心!
重整过程——与小组的所有成员交换意见,了解一下他们的反馈
理解——确保整个小组都完全理解TCR循环是什么,如何实现它。考虑一下就此主题进行员工培训和讲座。
<o:p> </o:p>
配对编程——第二步
TCR循环可以由某个开发人员独自完成,但是敏捷开发和TCR循环的真正威力来自于配对编程(pair programming)。在敏捷开发里,开发人员每两人一组编写所有的生产代码,其中一人担当“驱动者(driver)”(负责操作鼠标和键盘),而另一个人同驱动者一道解决问题和规划更大的图景。编程配对里的这个驱动者可以按需要进行轮换。配对让你能够实现眼前的目标,同时确保不会忽略项目的整体目标。它会保证有人在考虑下一步的走向和下一个要解决的问题。
<o:p> </o:p>
虽然配对编程引起了很多争议,但是大多数优秀的开发人员还是在按照这一方法进行开发,至少有的时候是这样的。管理人员们可能会相信配对编程降低了生产效率,然而尽管开发小组的生产效率在一开始会有所降低,但是研究已经表明从质量和增加的生产效率的角度来看,配对编程远远超过了开发人员单独工作的质量和效率7。而另一方面,开发人员可能会觉得配对编程非常困难,因为它需要与人们更多的交互过程,并与另一个开发人员一起编写代码。但是这也是建立一种相互学习的环境的最好方法。
<o:p> </o:p>
<o:p> </o:p>
<o:p> </o:p>
实施配对编程
<o:p> </o:p>
1.不要独断专行——要讨论。与你的小组成员讨论配对编程的思想及其优劣,而不是独断专行地给他们定规则。配对编程是开发人员相互学习的绝好机会。
<o:p> </o:p>
2.确定你的小组需要多少配对。配对编程是一项工作强度很大但是令人满意的工作方式。
<o:p> </o:p>
3.不要让配对编程人员每天连续工作八个小时——否则你的小组会吃不消的。从较短的时间开始——每天一到两个小时,看看它是如何进展的,然后随着小组信心的增强而延长时间。
<o:p> </o:p>
4.定期检查。如果你已经决定尝试再次进行敏捷开发,你就需要确保为小组营造了正式的环境,以便(定期)就项目进度进行反馈。
<o:p> </o:p>
重要的成功因素
<o:p> </o:p>
尝试它——如果你不去尝试,你就永远不了解它。
时间——给你小组(足够的)时间来尝试,并一步一步来完成。
沟通——配对编程会暴露一些有争议的问题——要保证沟通的渠道畅通。
配对恐惧症——你可能会碰到拒绝或者不希望与别人搭配工作的人。通常情况都是有别的原因驱使他们这样做,所以你需要找出并解决这些原因。
花时间思考——开发人员需要时间来思考并想出主意——确信给他们留出了时间做别的事情。
从
相关推荐
敏捷软件开发是一种以人为核心、迭代、循序渐进的软件开发方法。它强调快速和灵活地响应变化,以适应不断变化的需求。敏捷方法反对繁重的文档和过度的预设计,提倡可适应性、可持续性和持续的客户合作。 书中,...
《敏捷软件开发原则、模式与实践》一书是由著名软件开发专家、软件工程大师Robert C. Martin所著。这本书自出版以来,就被视为敏捷开发领域内的经典之作,对于软件开发人员、项目经理以及软件项目领导者来说,它提供...
Scrum是一种敏捷软件开发框架,它强调灵活性、协作和快速响应变化的能力。Scrum的核心理念是通过短期迭代(称为Sprints)和跨职能团队的工作来不断交付可用的软件,并在整个过程中密切与利益相关者合作。 **敏捷...
敏捷软件开发书籍合集,包括: [Scrum敏捷软件开发] [The.Pragmatic.Bookshelf开发丛书-敏捷开发回顾:使团队更强大] [The.Pragmatic.Bookshelf开发丛书-敏捷开发指导] [敏捷开发修炼之道] [用户故事与敏捷方法]
本书《敏捷软件开发:原则、模式与实践》是由全球知名的软件开发专家和软件工程大师Robert C. Martin所著,该书是关于敏捷开发与极限编程的综合性、实用性指南。书中深入探讨了软件开发人员、项目经理以及软件项目...
《敏捷软件开发:原则、模式与实践》是一本深度探讨敏捷开发理念和技术的权威著作,由业界知名专家Robert C. Martin(简称Uncle Bob)撰写。这本书不仅提供了丰富的理论知识,还结合实际案例,深入浅出地介绍了如何...
《敏捷软件开发实践估算与计划》是Mike Cohn的一部著作,由清华大学出版社于2016年出版。这本书深入探讨了在敏捷开发环境中如何进行有效的估算和计划,旨在帮助团队提升开发效率和项目成功率。 1. **敏捷开发**:...
敏捷软件开发知识体系是中国敏捷软件开发联盟在2011年推出的一项重要工作,旨在采集国内企业敏捷成功实践,对敏捷软件开发进行深入研究。在软件开发领域,敏捷方法提供了一种与传统瀑布式方法不同的开发哲学和实践,...
《敏捷软件开发:原则、模式与实践》是Robert C. Martin(简称Uncle Bob)的一部经典著作,这本书深入探讨了敏捷开发的理念、方法和工具,尤其针对C#编程语言进行了详细阐述。作为一本实践导向的技术书籍,它旨在...
《敏捷软件开发:原则、模式与实践》是Robert C. Martin(也被业界称为Uncle Bob)的经典著作,这本书深入探讨了敏捷开发的核心理念,并通过实际案例介绍了如何在项目中运用这些原则、模式和最佳实践。这本书分为两...
敏捷软件开发:原则、模式与实践(全) 敏捷软件开发:原则、模式与实践(全) 敏捷软件开发:原则、模式与实践(全) 敏捷软件开发:原则、模式与实践(全) 敏捷软件开发:原则、模式与实践(全)
"敏捷软件开发Agile介绍PPT课件.pptx" 敏捷软件开发是当前软件开发中最流行的开发方法之一,旨在快速响应客户需求,提高软件开发效率和质量。该PPT课件对敏捷软件开发进行了详细的介绍,涵盖了敏捷软件开发的历史...
在本资源中,我们主要探讨的是敏捷软件开发的原则、模式与实践,特别是在C++编程语言中的应用。这一主题源于《敏捷软件开发》一书的第19章,该章节通过一个具体的薪水支付案例来阐述敏捷开发的方法。在这个案例中,...
敏捷软件开发是一种以人为核心、迭代、循序渐进的软件开发方法。Robert C. Martin,作为敏捷开发的权威和实践者,通过《敏捷软件开发:原则、模式与实践》一书,向读者展示了一系列核心原则、模式和实践经验,旨在...
《敏捷软件开发:原则、模式与实践》是一本深度探讨敏捷开发理念、方法和技术的权威著作。这本书由著名软件开发专家Robert C. Martin撰写,旨在帮助开发者和团队更有效地进行软件开发,提升软件项目的成功率。书中...
《敏捷软件开发——原则、模式与实践》是软件工程领域一本经典的著作,它深入探讨了敏捷方法论在软件开发中的应用。源代码是书中理论与实践相结合的重要载体,提供了具体的实现示例,帮助读者更好地理解和掌握敏捷...