银河

SKYIV STUDIO

  博客园 :: 首页 :: 博问 :: 闪存 :: :: :: 订阅 订阅 :: 管理 ::

世界级的软件开发大师,著名软件咨询公司 Object Mentor 公司的创始人和总裁 Robert C. Martin (“Bob大叔”) 撰写的 Agile Software Development, Principles, Patterns, and Practices 在2002年10月出版,并赢得了2003年的 Jolt 大奖。该书的中文版《敏捷软件开发:原则、模式与实践》在2003年9月由清华大学出版社出版。该书以真实案例为基础,通过真实开发场景再现的方式对软件开发中涉及的各种知识及其有效的运用方法进行了讲解。该书是使用 Java 和 C++ 作为编程语言进行编写的。虽然书中的原则、模式和实践与语言无关,但是案例研究却与语言相关。为了让 .NET 开发社团也能够像 Java 社团那样,学习到这些可以改善软件开发状况,并让程序员感受到开发乐趣的敏捷开发和敏捷设计的权威知识,“Bob大叔”于2006年7月推出一本新书 Agile Principles, Patterns, and Practices in C#。该书的中文版《敏捷软件开发:原则、模式与实践(C#版)》在2008年1月由人民邮电出版社出版。

这本书的第6章 “一次编程实践”讲述 Bob Koss (RSK) 和 Bob Martin (RCM,“Bob大叔”) 使用结对编程(pair programming)的方法编写一个计算保龄球比赛得分的小型应用程序,在创建该应用程序的过程中,会使用测试驱动开发以及大量的重构。这一章也发布在 Object Mentor 的 Web 站点上:Engineer Notebook: An Extreme Programming EpisodeJavaEye 论坛上的一篇贴子“重游BOB大叔的一次编程实践”是 lingate 网友重读这一章后的一个小小读后感。

最终,他们得到了以下的程序:

首先,是测试程序 GameTest.cs。(注意,需要安装 NUint 框架)

// GameTest.cs ----------------------------
using NUnit.Framework;

[TestFixture]
public class GameTest
{
  Game game;

  [SetUp]
  public void SetUp()
  {
    game = new Game();
  }

  [Test]
  public void TestTwoThrowNoMark()
  {
    game.Add(5);
    game.Add(4);
    Assert.AreEqual(9, game.Score);
  }

  [Test]
  public void TestFourThrowNoMark()
  {
    game.Add(5);
    game.Add(4);
    game.Add(7);
    game.Add(2);
    Assert.AreEqual(18, game.Score);
    Assert.AreEqual(9, game.ScoreForFrame(1));
    Assert.AreEqual(18, game.ScoreForFrame(2));
  }

  [Test]
  public void TestSimpleSpare()
  {
    game.Add(3);
    game.Add(7);
    game.Add(3);
    Assert.AreEqual(13, game.ScoreForFrame(1));
  }

  [Test]
  public void TestSimpleFrameAfterSpare()
  {
    game.Add(3);
    game.Add(7);
    game.Add(3);
    game.Add(2);
    Assert.AreEqual(13, game.ScoreForFrame(1));
    Assert.AreEqual(18, game.ScoreForFrame(2));
    Assert.AreEqual(18, game.Score);
  }

  [Test]
  public void TestSimpleStrike()
  {
    game.Add(10);
    game.Add(3);
    game.Add(6);
    Assert.AreEqual(19, game.ScoreForFrame(1));
    Assert.AreEqual(28, game.Score);
  }

  [Test]
  public void TestPerfectGame()
  {
    for (int i = 0; i < 12; i++) game.Add(10);
    Assert.AreEqual(300, game.Score);
  }

  [Test]
  public void TestEndOfArray()
  {
    for (int i = 0; i < 9; i++)
    {
      game.Add(0);
      game.Add(0);
    }
    game.Add(2);
    game.Add(8);
    game.Add(10);
    Assert.AreEqual(20, game.Score);
  }

  [Test]
  public void TestSampleGame()
  {
    game.Add(1);
    game.Add(4);
    game.Add(4);
    game.Add(5);
    game.Add(6);
    game.Add(4);
    game.Add(5);
    game.Add(5);
    game.Add(10);
    game.Add(0);
    game.Add(1);
    game.Add(7);
    game.Add(3);
    game.Add(6);
    game.Add(4);
    game.Add(10);
    game.Add(2);
    game.Add(8);
    game.Add(6);
    Assert.AreEqual(133, game.Score);
  }

  [Test]
  public void TestHeartBreak()
  {
    for (int i = 0; i < 11; i++) game.Add(10);
    game.Add(9);
    Assert.AreEqual(299, game.Score);
  }

  [Test]
  public void TestTenthFrameSpare()
  {
    for (int i = 0; i < 9; i++) game.Add(10);
    game.Add(9);
    game.Add(1);
    game.Add(1);
    Assert.AreEqual(270, game.Score);
  }
}

然后,计算得分的 Scorer 类:

// Scorer.cs ----------------------------------------------------------------------
class Scorer
{
  int ball;
  int[] throws = new int[21];
  int currentThrow;

  int NextBallForSpare { get { return throws[ball + 2]; } }
  int NextTwoBallsForStrike { get { return throws[ball + 1] + throws[ball + 2]; } }
  int TwoBallsInFrame { get { return throws[ball] + throws[ball + 1]; } }

  bool Strike()
  {
    return throws[ball] == 10;
  }

  bool Spare()
  {
    return throws[ball] + throws[ball + 1] == 10;
  }

  public void AddThrow(int pins)
  {
    throws[currentThrow++] = pins;
  }

  public int ScoreForFrame(int theFrame)
  {
    ball = 0;
    int score = 0;
    for (int currentFrame = 0; currentFrame < theFrame; currentFrame++)
    {
      if (Strike())
      {
        score += 10 + NextTwoBallsForStrike;
        ball++;
      }
      else if (Spare())
      {
        score += 10 + NextBallForSpare;
        ball += 2;
      }
      else
      {
        score += TwoBallsInFrame;
        ball += 2;
      }
    }
    return score;
  }
}

最后,是表示保龄球比赛的 Game 类:

// Game.cs -------------------------------------------------------------
public class Game
{
  int currentFrame = 0;
  bool isFirstThrow = true;
  Scorer scorer = new Scorer();

  public int Score { get { return ScoreForFrame(currentFrame); } }

  public void Add(int pins)
  {
    scorer.AddThrow(pins);
    AdjustCurrentFrame(pins);
  }

  void AdjustCurrentFrame(int pins)
  {
    if (LastBallInFrame(pins)) AdvanceFrame();
    else isFirstThrow = false;
  }

  bool LastBallInFrame(int pins)
  {
    return Strike(pins) || !isFirstThrow;
  }

  bool Strike(int pins)
  {
    return isFirstThrow && pins == 10;
  }

  void AdvanceFrame()
  {
    currentFrame++;
    if (currentFrame > 10) currentFrame = 10;
  }

  public int ScoreForFrame(int theFrame)
  {
    return scorer.ScoreForFrame(theFrame);
  }
}

“Bob大叔”和他同伴的这次编程实践到此就结束了。

 

 


在 Game 类中,表示投球所在轮的 currentFrame 字段已经经过多次重构,引用 RSK 话:“该死,你是对的。我们在这上面反复多少次了?”和 RCM 的话:“哦。你是想说我们一直为之困扰,而我们所要做的就是把限制从 11 改为 10 ,并且移走 -1。天哪!”。(《敏捷软件开发:原则、模式与实践(C#版)》第70页)

实际上,Game 类中的 currentFrame 字段完全可以取消,并随之取消 isFirstThrow 字段以及 AdjustCurrentFrame、LastBallInFrame、Strike 和 AdvanceFrame 方法,如下所示:

// Game.cs -------------------------------------------------------------
public class Game
{
  Scorer scorer = new Scorer();

  public int Score { get { return ScoreForFrame(10); } }

  public void Add(int pins)
  {
    scorer.AddThrow(pins);
  }

  public int ScoreForFrame(int theFrame)
  {
    return scorer.ScoreForFrame(theFrame);
  }
}

附:保龄球规则概述:

保龄球是一种比赛,比赛者把一个哈密瓜大小的球顺着一条窄窄的球道投向10个木瓶。目的是在每次投球中击倒尽可能多的木瓶。

一局比赛由10轮组成。每轮开始,10个木瓶都是竖立摆放的。比赛者可以投球两次,尝试击倒所有木瓶。

如果比赛者在第一次投球中就击倒了所有木瓶,称之为“全中”,并且本轮结束。

如果比赛者在第一次投球中没有击倒所有木瓶,但在第二次投球中成功击倒了所有剩余的木瓶,称之为“补中”。一轮中第二次投球后,即使还有未被击倒的木瓶,本轮也宣告结束。

全中轮的记分规则为:10,加上接下来的两次投球击倒的木瓶数,再加上前一轮的得分。

补中轮的记分规则为:10,加上接下来的一次投球击倒的木瓶数,再加上前一轮的得分。

其他轮的记分规则为:本轮中两次投球所击倒的木瓶数,加上前一轮的得分。

如果在第10轮全中,那么比赛者可以再多投球两次,以完成对全中的记分。同样,如果第10轮为补中,那么比赛者可以再多投球一次,以完成对补中的记分。因此,第10轮可以包含3次投球而不是2次。

  • 保龄球:bowling
  • 木瓶:ball
  • 局:game
  • 轮:frame
  • 全中:strike
  • 补中:spare

posted on 2009-10-15 17:28  银河  阅读(2816)  评论(11编辑  收藏  举报