这一系列实践行为驱动开发的编码招式由 3 部分组成,由已故的 Jamie Phillips 撰写,他是波士顿敏捷社区和.NET 社区的知名成员。当我们看到这篇文章的首稿时,我们都迫切想要发布它,但在我们完成编辑工作前,他就去世了。在得到他妻子 Diana 的同意后,我们自豪地交付他最后的工作。
我很优秀,但我能变得更好吗?
使用编码招式、BDD 和 VS2010 项目模板
无论你的技术专长、知识和经验如何,总会有机会增强你自己的技能,成为编码道场(coding dojo)的黑腰带。通过使用编码招式来实践行为驱动开发,并实现一个新的 VS2010 项目模板,这可以磨练我们的技能,让我们变得更好。
这篇文章的三部分涵盖编码招式、行为驱动开发(BDD)以及 VS2010 中项目模板向导的话题,想带领读者进入我最近的一段发现之旅:即便已经编写了 8 年 C#代码,使用编码招式依然提高了我的技能。
第 I 部分:编码招式
对于任何指定的需求,当我们想去实现它、实际编码去实现设计时,即使是经验丰富的软件开发人员,也总有可以改进的地方。事实上是,很多时候,我们想对进入生产环境的代码做一些调整,使用我们最佳的编码实践;这在我们的日常工作中是很自然的事情。但是,如果你把这个想法投射到完全不同的场景中,并从武术的角度去看待它,那么在遇到的防御性场景中,你不会真地想去开始调整你独特的武术风格!你可能会比你自己想象的更糟。相反,你会反复实践和完善你的动作,并磨练你的技能,让他们成为你的第二天性。你将不再需要思考风格或形式,而是着手于眼前的局势。在许多武术中,练习者会反复练习相同的招式,以此把那些招式深深地印在头脑里,这样慢慢地这些招式就会成为练习者的第二天性。
编码招式(Dave Thomas 提出的一个术语,他是《程序员修炼之道》的合著者),在软件开发里是一个类似的理论。开发人员拿到一个简单的问题(比如斐波纳契数列),不断抛开能解决问题的代码,更多地将精力集中在使用什么风格和技术上,而不是问题的实际解决方案上。有人指出,这种自我提高的方法适用于“软件世界中穿着凉鞋的嬉皮士”。也许他们是对的,原先我也持有怀疑态度,但当我开始实践 Katas 的时候,立竿见影的回报让我感到惊讶——尽管作为一名太极练习者,我能理解这个想法。那么,在哪种情况下,BDD 和 VS2010 项目模板向导适合所有这一切?嗯,很简单,可以说这是一段旅程、一段进化的过程。
在 2010 年 TechEd 大会上我看到了相关的演示,那是我第一次接触编码招式的概念以及保龄球招式(Bowling Kata)的实现。David Starr 和 Ben Day 举办了一个非常好的交互式会议,详细研究了保龄球招式,并把这个过程切割成小块,让观众能够理解相关的测试、代码和重构过程。就是那样!作为单元测试的传道,它让人感觉非常完美!有什么更好的方式去逐步形成优秀的工作实践呢,而不要真正去实践那些会产生强壮代码的步骤!我马上开始了尝试(实际上就在会议期间)。我的第一次尝试并不好,因为我仍然专注于问题本身,而不是如何编写代码,那就是我的第一课。为了真正从 Katas 中获益,你要重复相同的 Katas,直到你觉得总体来说测试、编码和重构这一循环过程达到了你预期的效果。一旦你完成这些,你就可以转向另一个招式了。
我尝试的第一个招式是保龄球招式,来自于 Uncle Bob (Robert C. Martin),Dave 和 Ben 在 TechEd 上向我展示过。目标是为 10 瓶保龄球的游戏建立一个计分机制。无需了解太多计分细节(你可以很容易在网络上搜索到),玩家每轮有 2 次机会去击倒所有的木瓶(确切地说是 10 个木瓶——因此叫“10 杆保龄球”)。如果他们一次就把所有的球都击倒,那么这称之为全中(Strike)。如果他们在一轮中两次出球将所有球击倒,那么这叫补中(Spare)。一局比赛有 10 轮,在第 10 轮时,如果玩家打出一次全中或者扔出两次补中,那么他就可以发 3 次球。我设计了下面这个场景“矩阵”,帮我弄清楚如何为游戏计分:
根据这个矩阵,我就可以开始看看用例场景,为编写 10 杆保龄球游戏的计分引擎做好准备:
- 用例 1:没有击中球时,分数为 0。
- 用例 2:每轮都击倒一个球时,分数为 20。
(在最后一轮中没有扔出全中或补中——因此没有奖励球) - 用例 3:当扔出的都是全中时,分数为 300。
- 用例 4:当扔出的都是补中时,分数为 150。
- 用例 5:在第一轮扔出一次全中,第二轮击倒 8 个球,而其它几轮都没有击倒球时,分数为 26。
对那些熟悉 SCRUM 和 TFS 的人,这通常会作为验收条件列在产品 Backlog 项目上。现在我们有了用例,我们可以开始对每个我们想要创建的场景编写单元测试,并且用真正的 TDD 方式去做,此时我们还未编写引擎本身的代码。Kata 的起点是创建一个单元测试项目,从第一个场景着手,并为其编写测试:
/// <summary> /// Unit test methods for testing the bowling game engine /// </summary> [TestClass]
public class BowlingTests { /// <summary> /// Given that we are playing bowling
/// When I bowl all gutter balls
/// Then my score should be 0 /// </summary> [TestMethod] public void Bowl_all_gutter_balls() { // Arrange // Given that we are playing bowling Game game = new Game(); // Act
// when I bowl all gutter balls for (int i = 0; i < 10; i++) { game.roll(0); game.roll(0); } // Assert
// then my score should be 0 Assert.AreEqual(0, game.score()); } }
为了让这段代码通过编译,需要创建游戏引擎类(Game)以及其方法 roll 和 scroll(因此在代码片段中它们显示为红色)。这些方法不需要做什么事情,也不应该做什么事情——毕竟,在测试驱动开发中,我们会先让它们通不过。
编码提示:
在 VS2010 中,当光标停留在未定义的关键字后(在上面的例子中,就是关键字 Game),我们可以在代码中简单地按下 CTRL + .(稍等片刻),来创建相关类。
用这种方法创建类的好处在于,相对其他强制你专注于新建类的方法而言,这种方法让你可以继续专注于当前的代码。同一键盘快捷键也适用于方法的创建:
因此如果我们采用真正意义上的 TDD 方法,我们的实现类看起来应该是这样的:
public class Game { public void roll(int p) { throw new NotImplementedException(); } public int score() { throw new NotImplementedException(); } }
当然当我们执行单元测试时,会通不过(记得吗?要先通不过):
因此现在我们回过头去,仅仅实现通过测试所需的部分:
public class Game { public void roll(int p) { // throw new NotImplementedException();
} public int score() { return 0; } }
现在,我们执行所有的单元测试,应该都是绿的:
然后我们接着处理下一个场景,先编写测试:
/// <summary> /// Given that we are playing bowling /// When I bowl all single pins /// Then my score should be 20 /// </summary> [TestMethod] public void Bowl_all_single_pins() { // Arrange
// Given that we are playing bowling
Game game = new Game(); // Act
// when I bowl all single pins for (int i = 0; i < 10; i++) { game.roll(1); game.roll(1); } // Assert
// then my score should be 20 Assert.AreEqual(20, game.score()); }
当我们执行该测试时,显而易见它会失败:
我们回到刚才的实现代码中,做所需的更改,确保我们不会影响先前的测试:
public class Game { private int[] _rolls = new int[21]; private int _currentFrame = 0; public void roll(int pinsKnockedDown) { _rolls[_currentFrame++] = pinsKnockedDown; } public int score() { int retVal = 0; for (int index = 0; index < _rolls.GetUpperBound(0); index++) { retVal += _rolls[index]; } return retVal; } }
运行所有的测试,检查我们所做的更改:
这个过程在 Kata 中不断进行,直到完成所有场景的编码,并且所有的测试都通过:
在这个阶段,如果你尽可能多地进行了重构、删除重复代码,同时保留完整的功能(即你的测试在每次重构后保持通过),那么你就可以认为你的 Kata 完成了。
要真正从 Kata 中受益,就要反复练习同样的 Kata,直到确信你已尽可能多地重构优化了你的代码。你必须总是从头开始(或至少从你最基本的环境开始),原因是我们正在练习 Katas,借此增强编码能力。为此,你必须仔细琢磨所有进展,以获取你最终的产品——可运行的方案——因此在你的方法中跳步会适得其反,因为你没有从练习中充分受益。你会看到,就像我稍后在这一系列中解释的那样,当你改变 Katas 时,会有一些共性,那些共性确实会通过通用的宏或者更好的方法——项目模板,融入到你的方法中。
查看英文原文: Using Coding Katas, BDD and VS2010 Project Templates: Part 1
感谢陈宇对本文的审校。
给 InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家加入到 InfoQ 中文站用户讨论组中与我们的编辑和其他读者朋友交流。
评论