世界级的软件开发大师,著名软件咨询公司 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 Episode。JavaEye 论坛上的一篇贴子“重游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); } }
附:保龄球规则概述:
- 敏捷软件开发:原则、模式与实践,[美]Robert C. Martin 著,邓辉译,清华大学出版社,2003年9月第1版
- 敏捷软件开发:原则、模式与实践(C#版),[美]Robert C. Martin、Micah Martin 著,邓辉、孙呜译,人民邮电出版社,2008年1月第1版