使用编码招式(Coding Katas)、BDD和VS2010项目模板:第I部分

这一系列实践行为驱动开发的编码招式由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

This entry was posted in Agile, IDE. Bookmark the permalink.

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s