How I Used Kata To Learn TypeScript Fast

I don’t know about you, but I’m not one to turn away early clues to the new direction from .NET A-listers whose lives have recently flashed before their eyes. So I sat up a bit when I came across the following sobering passage on Rocky Lhotka’s blog:

Sadly, as much as I truly love .NET and view it as the best software development platform mankind has yet invented, I strongly suspect that we’ll all end up programming in JavaScript–or some decent abstraction of it like TypeScript. As a result, I’m increasingly convinced that platforms like .NET, Java, and Objective C will be relegated to writing “toy” consumer apps, and/or to pure server-side legacy code alongside the old COBOL, RPG, and FORTRAN code that still runs an amazing number of companies in the world.

I blinked, reread the paragraph a few times, and decided (among other things) that it was high time that I dug in and learned this TypeScript of which Lhotka spoke.

Well. That, and the fact that I had just signed up to show it to Nashville’s .NET User Group within a matter of weeks.

Wax On, Wax Off

Now, I can read friendly, friendly Getting Started tutorials and watch Build talks all day long, but I find that I never really encounter all of the boogers and sharp corners on a new language until I’ve used it to solve a real problem or two. I had a good thing going in this respect for a while, by keeping a pet application. But that approach gets heavy once time has its way with you. I mean, I can’t recommend enough the experience of building something top to bottom on your own, at least once. But what can you do if the Internet is already full, and you just want to learn something?

Enter Kata. A practice borrowed from the martial arts, a kata is a discrete exercise designed to strengthen a skill through repetition. When applied in software, it is a means for nerds to write code in real-world conditions, without so much in the way of the commitment that typically accompanies code in the real world.

Naturally, I turned to kata to help me quickly wrap my head around TypeScript. The kata I chose for this occasion was the game of Fizz Buzz. Also, it’s me, so I came at it using Behavior-Driven Development. This helped me to lay out the game’s requirements, and to remain focused on meeting them as simply as possible.

Since TypeScript is really just JavaScript with a tie on, I was able to bring along my favorite JavaScript tooling–namely, the Jasmine testing framework, and the Chutzpah extension for integrating Jasmine with Visual Studio’s Test Explorer. And, of course, I had to install TypeScript itself, as it has not yet been mainstreamed into the out-of-box Visual Studio experience.

So, here’s how you can recreate my learning experience from the comfort of your own personal code laboratory.

kata

First, do the File, New Project shuffle, and choose the HTML Application with TypeScript template. Add the jasmine.js and jasmine.TypeScript.DefinitelyTyped NuGet packages to your new project, and round out the yak-shaving portion of the session by creating two TypeScript files, one christened fizzbuzz.spec.ts for the tests, and the other fizzbuzz.ts for the implementation.

For bonus #PROTIP points: open both of these and lay them out side by side using two vertical tab groups, and bring up the Test Explorer pane at the right edge of the Visual Studio window.

All righty then, tests first. The basic idea behind the game of Fizz Buzz, according to the Wikipedia article, is as follows.

Fizz buzz (also known as bizz buzz, or simply buzz) is a group word game for children to teach them about division.[1] Players take turns to count incrementally, replacing any number divisible by three with the word “fizz”, and any number divisible by five with the word “buzz”.

So, laying this out in discrete rules in order of increasing complexity, we get:

  1. The player says the next number.
  2. The player says ‘fizz’ if the next number is 3.
  3. The player says ‘buzz’ if the next number is 5.
  4. The player says ‘fizz’ if the next number is divisible by 3.
  5. The player says ‘buzz’ if the next number is divisible by 5.
  6. The player says ‘fizzbuzz’ if the next number is divisible by both 3 and 5.

Set up the spec file like so, adding a test for the first requirement using jasmine’s it() function, inside a describe function.

/// <reference path="scripts/typings/jasmine/jasmine.d.ts"/>
/// <reference path="fizzbuzz.ts"/>
 
describe("The fizz buzz player", () => {
 
    it("says the next number.", () => {
        var player = new FizzBuzzPlayer();
        expect(player.play(1)).toBe("1");
        expect(player.play(2)).toBe("2");
    });
});

Now save the file and run the tests. Since the FizzBuzzPlayer class does not exist yet, the TypeScript compiler will fail the build. Following the TDD cycle of red, green, refactor, this counts as our first red. Resolve this by creating the FizzBuzzPlayer class with in fizzbuzz.ts:

class FizzBuzzPlayer {
    play(n: number) {
        return '';
    }
}

Now that the build succeeds, the test runner should show that the spec fails–the play method does not return “1”. Let’s solve this in the simplest possible way:

play(n: number) {
    return n.toString();
}

The test should now pass. Now let’s write a test for the second requirement:

it("says 'fizz' if the next number is 3", () => {
    var player = new FizzBuzzPlayer();
    expect(player.play(3)).toBe("fizz");
});

This test will fail with the message, “Expected ‘3’ to be ‘fizz'”. Update the play method again to make this pass:

play(n: number) {
    if (n == 3)
        return 'fizz';
 
    return n.toString();
}

Let’s do a bit of cleaning up for the refactor stage. It’s repetitive to initialize the player in each spec, so using jasmine’s beforeEach function, initialize the player in one place, and remove that line from each of the specs:

var player;
beforeEach(() => {
    player = new FizzBuzzPlayer();
});
 
it("says the next number.", () => {
    expect(player.play(1)).toBe("1");
    expect(player.play(2)).toBe("2");
});
 
it("says 'fizz' if the next number is 3", () => {
    expect(player.play(3)).toBe("fizz");
});

Beautiful. Let’s make things red again by adding a test for the next requirement:

it("says 'buzz' if the next number is 5", () => {
    expect(player.play(5)).toBe("buzz");
});

Red, it is: “Expected ‘5’ to be ‘buzz'”. Make it go green by updating our play method:

play(n: number) {
    if (n == 3)
        return 'fizz';
 
    if (n == 5)
        return 'buzz';
 
    return n.toString();
}

There’s no refactoring to be done yet, so let’s move back to red with the next requirement:

it("says 'fizz' if the next number is divisible by 3", () => {
    expect(player.play(6)).toBe("fizz");
    expect(player.play(9)).toBe("fizz");
    expect(player.play(27)).toBe("fizz");
});

Red, indeed: “Expected ‘6’ to be ‘fizz'”. Make this pass by checking for a remainder of 0, rather than equality:

if (n % 3 == 0)
    return 'fizz';

Repeat this for the buzz case.

it("says 'buzz' if the next number is divisible by 5", () => {
    expect(player.play(10)).toBe("buzz");
    expect(player.play(25)).toBe("buzz");
    expect(player.play(100)).toBe("buzz");
});

And to make it pass:

if (n % 5 == 0)
    return 'buzz';

Now define the test for our last requirement:

it("says 'fizzbuzz' if the next number is divisible by both 3 and 5", () => {
    expect(player.play(15)).toBe("fizzbuzz");
    expect(player.play(30)).toBe("fizzbuzz");
    expect(player.play(60)).toBe("fizzbuzz");
});

Once this fails, we can update our play method to solve the problem.

play(n: number) {
    var value = '';
 
    if (n % 3 == 0)
        value = 'fizz';
 
    if (n % 5 == 0)
        value += 'buzz';
 
    if (value === '')
        value = n.toString();
 
    return value;
}

Green! Now, there looks to be a bit of refactoring to be done here. I mean, if you’re into the whole code doing one thing well thing. Which you should be. The first two if clauses are identical, except for the values involved–that looks like a function to me:

private getWordForMultiple(n: number, factor: number, word: string) {
    if (n % factor == 0)
        return word;
    else
        return '';
}

Now we can swap out our repetitive if clauses for calls to this function:

play(n: number) {
    var value = '';
 
    value += this.getWordForMultiple(n, 3, 'fizz');
    value += this.getWordForMultiple(n, 5, 'buzz');
 
    if (value === '')
        value = n.toString();
 
    return value;
}

Run the tests now to be sure that they still pass.

I think we can still go a little tighter than that. Let’s make a getWords method that handles all of the words-for-multiples business in one call:

private getWords(n: number) {
    var value = '';
    value += this.getWordForMultiple(n, 3, 'fizz');
    value += this.getWordForMultiple(n, 5, 'buzz');
    return value;
}

Now our play function can be reduced to:

play(n: number) {
    var value = this.getWords(n);
    if (value === '')
        value = n.toString();
 
    return value;
}

And the tests still pass. Well, that was just a smashing learning exercise, and I thought that the whole thing went smoothly as a–

Your manager just walked out of a meeting. He looks a little ill as he approaches your desk. “We have a new requirement”, he says. “The Fizz Buzz Player needs to be configurable, to accommodate any combination of factors and words.”

Zoinks. It’s a good thing we’ve been keeping our code ship-shape and tested–let’s see what we can do. First, add the new requirement to the specs:

it("can be configured to accommodate any combination of factors and words.", () => {
    var cp = new FizzBuzzPlayer(2, 'jack', 7, 'squat');
    expect(cp.play(1)).toBe("1");
    expect(cp.play(2)).toBe("jack");
    expect(cp.play(3)).toBe("3");
    expect(cp.play(4)).toBe("jack");
    expect(cp.play(5)).toBe("5");
    expect(cp.play(6)).toBe("jack");
    expect(cp.play(7)).toBe("squat");
    expect(cp.play(8)).toBe("jack");
    expect(cp.play(9)).toBe("9");
    expect(cp.play(10)).toBe("jack");
    expect(cp.play(11)).toBe("11");
    expect(cp.play(12)).toBe("jack");
    expect(cp.play(13)).toBe("13");
    expect(cp.play(14)).toBe("jacksquat");
    expect(cp.play(15)).toBe("15");
});

We’re back to build failure, because FizzBuzzPlayer doesn’t have a constructor defined. Let’s get things building again by adding one:

constructor(
    public fizzFactor: number = 3,
    public fizzWord: string = 'fizz',
    public buzzFactor: number = 5,
    public buzzWord: string = 'buzz') { }

Note that not only are we taking advantage of TypeScript’s automatic properties shorthand, but we’re also making these parameters optional by defining default values based on the original Fizz Buzz rules.

Our test still runs red. Let’s fix up the getWords code to actually use the new properties we’ve added:

private getWords(n: number) {
    var value = '';
    value += this.getWordForMultiple(n, this.fizzFactor, this.fizzWord);
    value += this.getWordForMultiple(n, this.buzzFactor, this.buzzWord);
    return value;
}

And we’re back in the green! Time to refactor. Using the right-click menu’s Refactor, Rename option (high-five, TypeScript!), let’s rename FizzBuzzPlayer to something less specific, and more appropriate to what it actually does now: NumberGamePlayer. While you’re at it, update the name in the jasmine describe call, too.

You look up at your manager, who is still standing over your shoulder and clutching his business papers anxiously.

“No problem, boss!” you exclaim, and check in your changes without breaking eye contact.

“Whatever,” he mutters as he shakes his head and walks off, failing to grasp exactly how easy TypeScript, Jasmine, Chutzpah, and you have made his life just now.

Advertisements

Leave a Reply

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 / Change )

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s