这一系列实践行为驱动开发的编码招式由3部分组成,由已故的Jamie Phillips撰写,他是波士顿敏捷社区和.NET社区的知名成员。当我们看到这篇文章的首稿时,我们都迫切想要发布它,但在我们完成编辑工作前,他就去世了。在得到他妻子Diana的同意后,我们自豪地交付他最后的工作。
无论你的技术专长、知识和经验如何,总会有机会增强你自己的技能,成为编码道场(coding dojo)的黑腰带。通过使用编码招式来实践行为驱动开发,并实现一个新的VS2010项目模板,这可以磨练我们的技能,让我们变得更好。
这篇文章的三部分涵盖编码招式、行为驱动开发(BDD)以及VS2010中项目模板向导的话题,想带领读者进入我最近的一段发现之旅:即便已经编写了8年C#代码,使用编码招式依然提高了我的技能。
对于任何指定的需求,当我们想去实现它、实际编码去实现设计时,即使是经验丰富的软件开发人员,也总有可以改进的地方。事实上是,很多时候,我们想对进入生产环境的代码做一些调整,使用我们最佳的编码实践;这在我们的日常工作中是很自然的事情。但是,如果你把这个想法投射到完全不同的场景中,并从武术的角度去看待它,那么在遇到的防御性场景中,你不会真地想去开始调整你独特的武术风格!你可能会比你自己想象的更糟。相反,你会反复实践和完善你的动作,并磨练你的技能,让他们成为你的第二天性。你将不再需要思考风格或形式,而是着手于眼前的局势。在许多武术中,练习者会反复练习相同的招式,以此把那些招式深深地印在头脑里,这样慢慢地这些招式就会成为练习者的第二天性。
编码招式(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杆保龄球游戏的计分引擎做好准备:
对那些熟悉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