XProgramming > XP Magazine > Adventures in C#: TDD by Intention?
COLLECTED TOPICS: Kate Oneal | Adventures in C# | Documentation in XP | Book Reviews
Adventures in C#: TDD by Intention?
Ron Jeffries
06/11/2005
Can Test-Driven Development and Programming By Intention play together? Or do they annihilate each other, like protons and antiprotons, or XPers and CMMers?

I was dozing on the red-eye, and started thinking about the bowling game programming exercise, since I had just done it at a client's shop during this week's gig. I got to thinking that the scoring algorithm might be expressed so clearly by intention that one wouldn't want to TDD it at all. So I'm wondering how to put programming by intention, and test-driven development, together.1

Let's try it. Just to make it harder, I'm going to do it in Java, with IntelliJ IDEA. IDEA is new to me, and as you probably know, I don't do much development at all in Java, though I pair and coach with it a fair amount.

First thing is a new project in IDEA, then Alt+Insert (New) a TestCase. It looks like this, and trying to run it will encourage me to figure out how to reference the jUnit stuff:

import junit.framework.TestCase;

public class BowlingTest extends TestCase {

    public void testHookup() {
        fail("Hookup");
    }
}

IDEA says it can't resolve junit.framework, so I need to set up a reference somewhere. Maybe it's in File/Settings ... ah, yes. in the Paths item, Libraries tab, it has a section "Used Global Libraries", with a place to check junit. I'll check that ... and now IDEA isn't whining any more. It still isn't offering me a way to run the test. I know there's an easy answer to this, but where ... OK, here's Edit Configurations, a junit tab, take the default, give it a name, tell it to test everything in the package ... that looks about right. Let's see now.

Cool. Now there's a configuration in the little pulldown next to the run button, and the run button is green. I bet this means I can run the test and get a failure.

Runs the tests and two fail. It has found a test in junit itself that fails. I've seen this before. Now how do I get rid of it?

I can't figure it out. I have to go back to a previous project and see how it was configured. Hmm. This one has a source tree com.xprogramming.folderfile. Do I always have to do it that way?? Arrgh. Now I am confused. I reopened the by intention project and now its run works as I'd expect. Settings for junit are "all in package", "in single module", and classpath and JDK of module "Bowling by Intention". Weird.

Anyway, my tests now run. I have this theory that there are three things you have to learn about every new system that you can only find out by having someone tell you. This must be one of them. Anyway, finally, we're ready to go.

The Basic Idea

Remember that I was thinking of an algorithm for bowling, written by intention, and I'm trying to see how I'd TDD it. Let me get a single test running, using Fake It Till You Make It, and then sketch the code. First the test ...

import junit.framework.TestCase;

public class BowlingTest extends TestCase {

    public void testEmptyGame() {
        BowlingGame game = new BowlingGame();
        assertEquals(0, game.score());
    }
}

... and the code that makes it green ...

public class BowlingGame {
    public int score() {
        return 0;
    }
}

Not much to it, but it gives me a place to type what I had been thinking. I was thinking that there'll be a method, scoreFrame(), that would look roughly like this:

    public int scoreFrame() {
        if (frameIsStrike)
            return tenPlusNextTwoRolls();
        else if (frameIsSpare)
            return tenPlusNextRoll();
        else
            return frameTotal();
    }

So the idea is that this method would just reflect the rules of bowling directly. It seems really straightforward and that it might lead to something suitable. I'm not sure what the parameters to the methods would be, but my rough guess is that an ArrayList of integers, or a stream of integers, might be close.

The question is, how can I get there quickly with TDD, following the rule that I write no line of code except in response to a failing test? Clearly I have a frame-oriented solution in mind. I think that's OK. But the input to the score() function: what should that be?

Historically, I've usually written the game to accept rolls one at a time. This time, just for something different, let's give it all the rolls at once. I think I'll use an ArrayList, just because I think it'll be easier to build on the testing side. So, let's see. We can change the first test to be one for gutter balls, passing in an array or arraylist of 20 zeros. That won't be very interesting. How about a game of open frames? Something like this:

    public void testOpenFrames() {
        BowlingGame game = new BowlingGame();
        assertEquals("OpenFrames", 90, game.score(openFrames));
    }

Now I need to create openFrames, which I'm picturing as an array or arraylist of numbers.

    public void testOpenFrames() {
        BowlingGame game = new BowlingGame();
        int[] openFrames = { 5,4, 5,4, 5,4, 5,4, 5,4, 5,4, 5,4, 5,4, 5,4, 5,4 };
        assertEquals("OpenFrames", 90, game.score(openFrames));
    }

That leads to this score(), which still fails of course:

    public int score(int[] openFrames) {
        int score = 0;
        return score;
    }

Now my plan is to score ten frames, somehow passing in that array:

    public int scoreFrame(int[] rolls) {
//        if (frameIsStrike)
//            return tenPlusNextTwoRolls();
//        else if (frameIsSpare)
//            return tenPlusNextRoll();
//        else
//            return frameTotal();
        return 0;
    }

That should keep my tests running the same way ... i.e. failing, so I need to go a bit further. As it stands now, I'm just using the score with the array for one test, so I could return 9 and I think that all tests would run. Let's try that. Yes, now my tests are green. I'll change the first one to use the array method. That will break, and make me figure out frame scoring.

    public void testGutterBalls() {
        BowlingGame game = new BowlingGame();
        int[] gutterBalls = { 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0 };
        assertEquals(0, game.score(gutterBalls));
    }

That fails, as intended, with expected 0 but was 90. Now I feel that I'm on the way.

Brief Reflection

That startup sequence felt a bit flaky. It didn't help that I had trouble with junit and IDEA, but the starting tests seemed a bit odd as well. And I feel a bit like I'm not really there yet, since I'm not sure that the array idea will hold up. It might, though. Somehow, I need to iterate through it. Let's start with a field in the Bowling Game object:

Calculating Frame by Frame

We'll just save a pointer to the current frame, and update it in the method. I expect that to be a bit awkward, but we're just TDDing here, with a place to wind up in mind.

public class BowlingGame {
    int frameStart;

    public int scoreFrame(int[] rolls) {
//        if (frameIsStrike)
//            return tenPlusNextTwoRolls();
//        else if (frameIsSpare)
//            return tenPlusNextRoll();
//        else
//            return frameTotal();
        int result =  rolls[frameStart] + rolls[frameStart+1];
        frameStart += 2;
        return result;
    }

    public int score(int[] openFrames) {
        int score = 0;
        int frameStart = 0;
        for (int frame = 1; frame <= 10; frame++)
          score += scoreFrame(openFrames);
        return score;
    }
}

Now both my tests run. But there are things not to like. I'm a bit unhappy with using that member variable to keep the frame start. But it would be worse to pass it as a var variable, I think. Maybe a Method Object is being called for here, or something. I think I'll press forward with the functionality, and see what happens.

Notice that we're working on what is in the "else" part of my commented-out if statement. Let's make the code look like what's in the comment. I'll ask IDEA to Extract Method on the highlighted bit below:

    public int scoreFrame(int[] rolls) {
//        if (frameIsStrike)
//            return tenPlusNextTwoRolls();
//        else if (frameIsSpare)
//            return tenPlusNextRoll();
//        else
//            return frameTotal();
        int result =  rolls[frameStart] + rolls[frameStart+1];
        frameStart += 2;
        return result;
    }

IDEA invites me to name the method, and I choose frameTotal. Then I can delete the comment, and wind up with this:

    public int scoreFrame(int[] rolls) {
//        if (frameIsStrike)
//            return tenPlusNextTwoRolls();
//        else if (frameIsSpare)
//            return tenPlusNextRoll();
//        else
        int result =  frameTotal(rolls);
        frameStart += 2;
        return result;
    }

    private int frameTotal(int[] rolls) {
        return rolls[frameStart] + rolls[frameStart+1];
    }

Tests still run. I think it's time for a new one. Let's do a spare.

    public void testSpare() {
        BowlingGame game = new BowlingGame();
        int[] spareFrames = { 6,4, 6,4, 6,4, 6,4, 6,4, 6,4, 6,4, 6,4, 6,4, 6,4 };
        assertEquals("Spare", 160, game.score(spareFrames));
    }

I'm not entirely sure about that 160, but I think it's right. We'll see ... the test doesn't run, of course ... as expected. Let's uncomment the next two lines and build them by intention. I'm thinking something like this:

    public int scoreFrame(int[] rolls) {
        int result;
//        if (frameIsStrike)
//            return tenPlusNextTwoRolls();
        /*else*/ if (frameIsSpare(rolls)) {
            result =  tenPlusNextRoll(rolls);
        }
        else
            result =  frameTotal(rolls);
        frameStart += 2;
        return result;
    }

I plug in details and complete with this:

    public int scoreFrame(int[] rolls) {
        int result;
//        if (frameIsStrike)
//            return tenPlusNextTwoRolls();
        /*else*/ if (frameIsSpare(rolls)) {
            result =  tenPlusNextRoll(rolls);
        }
        else
            result =  frameTotal(rolls);
        frameStart += 2;
        return result;
    }

    private int tenPlusNextRoll(int[] rolls) {
        return 10 + rolls[frameStart+2];
    }

    private boolean frameIsSpare(int[] rolls) {
        return rolls[frameStart] != 10 && frameTotal(rolls) == 10;
    }

I really expected that to work, but it is throwing an exception, running off the end of the array!

Fortunately, I took a break to watch the Belmont, and that let me remember that I had forgotten the bonus roll in the test. Bad test, good code. With the fixed test below, we're green:

    public void testSpare() {
        BowlingGame game = new BowlingGame();
        int[] spareFrames = { 6,4, 6,4, 6,4, 6,4, 6,4, 6,4, 6,4, 6,4, 6,4, 6,4, 6 };
        assertEquals("Spare", 160, game.score(spareFrames));
    }

Now the Strike

I'm going to get the functionality right, then see what refactoring is called for. The code is pretty clean, just a few things that I'm not comfortable with. So let's write a strike test and see what happens. Let's go all the way to a perfect game.

    public void testPerfect() {
        BowlingGame game = new BowlingGame();
        int[] perfectFrames = { 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10 };
        assertEquals("Perfect", 300, game.score(perfectFrames));
    }

This test of course blows out of the water, running off the end of the array. That's because it is reading two balls per frame, and it shouldn't. We need to increment frameStart by only one, as well as identify the strike and so on. Let's see:

    public int scoreFrame(int[] rolls) {
        int result;
        if (frameIsStrike(rolls))
            return tenPlusNextTwoRolls(rolls);
        else if (frameIsSpare(rolls)) {
            result =  tenPlusNextRoll(rolls);
        }
        else
            result =  frameTotal(rolls);
        frameStart += 2;
        return result;
    }

    private boolean frameIsStrike(int[] rolls) {
        return rolls[frameStart] == 10;
    }

    private int tenPlusNextTwoRolls(int[] rolls) {
        return 10 + rolls[frameStart+1] + rolls[frameStart+2];
    }

I just ran the tests, and they are green. How can that be? Why didn't the game run off the end. Oh, I see it, do you?

The strike case is doing "return", not setting result. That's the bug: it means that frameStart never gets incremented. We scored the first frame ten times. When we change that, we'll run off the end like I expected.

    public int scoreFrame(int[] rolls) {
        int result;
        if (frameIsStrike(rolls))
            result = tenPlusNextTwoRolls(rolls);
        else if (frameIsSpare(rolls)) {
            result =  tenPlusNextRoll(rolls);
        }
        else
            result =  frameTotal(rolls);
        frameStart += 2;
        return result;
    }

There, that's "better". It explodes according to plan. Now we have to do the incrementation. This will get a bit ugly but I'm going to let it happen for now.

    public int scoreFrame(int[] rolls) {
        int result;
        if (frameIsStrike(rolls)) {
            result = tenPlusNextTwoRolls(rolls);
            frameStart++;
        }
        else if (frameIsSpare(rolls)) {
            result =  tenPlusNextRoll(rolls);
            frameStart += 2;
        }
        else {
            result =  frameTotal(rolls);
            frameStart += 2;
        }
        return result;
    }

The tests are green. Now, my favorite, the alternating strikes and spares, which, I'm told, always total 200.

    public void testAlternating() {
        BowlingGame game = new BowlingGame();
        int[] perfectFrames = { 10, 6,4,  10, 6,4, 10, 6,4, 10, 6,4, 10, 6,4, 10};
        assertEquals("Alternating", 200, game.score(perfectFrames));
    }

That one runs. I think we're good. But I feel a bit uncertain for some reason. I'm hungry and need a bite; will think on it and we'll reflect when I get back.

Reflection

Here's all the code, as it stands right now:

import junit.framework.TestCase;

import java.util.ArrayList;

public class BowlingTest extends TestCase {

    public void testGutterBalls() {
        BowlingGame game = new BowlingGame();
        int[] gutterBalls = { 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0 };
        assertEquals(0, game.score(gutterBalls));
    }

    public void testOpenFrames() {
        BowlingGame game = new BowlingGame();
        int[] openFrames = { 5,4, 5,4, 5,4, 5,4, 5,4, 5,4, 5,4, 5,4, 5,4, 5,4 };
        assertEquals("OpenFrames", 90, game.score(openFrames));
    }

    public void testSpare() {
        BowlingGame game = new BowlingGame();
        int[] spareFrames = { 6,4, 6,4, 6,4, 6,4, 6,4, 6,4, 6,4, 6,4, 6,4, 6,4, 6 };
        assertEquals("Spare", 160, game.score(spareFrames));
    }

    public void testPerfect() {
        BowlingGame game = new BowlingGame();
        int[] perfectFrames = { 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10 };
        assertEquals("Perfect", 300, game.score(perfectFrames));
    }

    public void testAlternating() {
        BowlingGame game = new BowlingGame();
        int[] perfectFrames = { 10, 6,4,  10, 6,4, 10, 6,4, 10, 6,4, 10, 6,4, 10};
        assertEquals("Alternating", 200, game.score(perfectFrames));
    }
}

public class BowlingGame {
    int frameStart;

    public int score(int[] openFrames) {
        int score = 0;
        int frameStart = 0;
        for (int frame = 1; frame <= 10; frame++)
          score += scoreFrame(openFrames);
        return score;
    }

    public int scoreFrame(int[] rolls) {
        int result;
        if (frameIsStrike(rolls)) {
            result = tenPlusNextTwoRolls(rolls);
            frameStart++;
        }
        else if (frameIsSpare(rolls)) {
            result =  tenPlusNextRoll(rolls);
            frameStart += 2;
        }
        else {
            result =  frameTotal(rolls);
            frameStart += 2;
        }
        return result;
    }

    private boolean frameIsStrike(int[] rolls) {
        return rolls[frameStart] == 10;
    }

    private int tenPlusNextTwoRolls(int[] rolls) {
        return 10 + rolls[frameStart+1] + rolls[frameStart+2];
    }

    private int tenPlusNextRoll(int[] rolls) {
        return 10 + rolls[frameStart+2];
    }

    private boolean frameIsSpare(int[] rolls) {
        return rolls[frameStart] != 10 && frameTotal(rolls) == 10;
    }

    private int frameTotal(int[] rolls) {
        return rolls[frameStart] + rolls[frameStart+1];
    }
}

On the positive side, this implementation has gone rather smoothly, and I seem to be winding up where I was heading, namely with the code that expresses my original intention. (I have wound up with very similar code other times, by refactoring.) So nothing terrible seems to have happened due to having a goal in mind.

I kind of like the idea of commenting out the code and then typing it in as the tests call for it. For something this small, it works fine. For something larger, if I had programmed it all by intention, I might have been tempted to just try to make it work. I think that might have led to problems, mostly reflected in time in the debugger. Anyway, the process felt good, and went as rapidly and without error as it ever does.

There were some design expectations that were not met. I was thinking that I'd use an ArrayList, and wound up using arrays instead. That's a simplification, in this case, and I like it. I had other thoughts, like creating a Frame object, and using some kind of a Stream, that didn't show up in the code. But we have some refactoring to do, and they still might appear. We'll see.

What's not to like? Well, there is that odd member variable, frameStart. That doesn't feel right to me. Basically we want two results back from scoreFrame, namely the frame score, and the incrementing of the start. The object kind of limps.

Another thing is that, except for that variable, we don't really have any state in the game object. We score directly from an array. That seems a bit odd as well, though I have an idea how that might be changed. Let's get more specific and do some refactoring.

Improving the Design

I'm just going to look at things and fix what I don't like. Stand back.

That frameStart thing irritates me. How about this? We'll have it as an int in score(), and we'll pass it into scoreFrame(). We'll have a second method, frameSize(), that we'll use to update the variable. Like this:

public class BowlingGame {

    public int score(int[] openFrames) {
        int score = 0;
        int frameStart = 0;
        for (int frame = 1; frame <= 10; frame++) {
          score += scoreFrame(frameStart, openFrames);
          frameStart += frameSize(frameStart, openFrames);
        }
        return score;
    }

    public int scoreFrame(int frameStart, int[] rolls) {
        int result;
        if (frameIsStrike(frameStart, rolls)) {
            result = tenPlusNextTwoRolls(frameStart, rolls);
        }
        else if (frameIsSpare(frameStart, rolls)) {
            result =  tenPlusNextRoll(frameStart, rolls);
        }
        else {
            result =  frameTotal(frameStart, rolls);
        }
        return result;
    }

    public int frameSize(int frameStart, int[] rolls) {
        if (frameIsStrike(frameStart, rolls))
            return 1;
        else
            return 2;
    }

    private boolean frameIsStrike(int frameStart, int[] rolls) {
        return rolls[frameStart] == 10;
    }

    private int tenPlusNextTwoRolls(int frameStart, int[] rolls) {
        return 10 + rolls[frameStart+1] + rolls[frameStart+2];
    }

    private int tenPlusNextRoll(int frameStart, int[] rolls) {
        return 10 + rolls[frameStart+2];
    }

    private boolean frameIsSpare(int frameStart, int[] rolls) {
        return rolls[frameStart] != 10 && frameTotal(frameStart, rolls) == 10;
    }

    private int frameTotal(int frameStart, int[] rolls) {
        return rolls[frameStart] + rolls[frameStart+1];
    }
}

The tests still run. The change was easy enough, except that we had to add a parameter to some seven methods and a bunch of other lines. That's the kind of "big refactoring" that one likes to avoid. Let's back that out, and see whether we can find a better way to get the same effect without all that work. I think IDEA has a way of reverting to previous green bars. Let's see ... well, the first thing I figure out is that IDEA thinks I'm on Pacific time. It's 1030 here, and the "Local History" window is listing the last tests as run at 730. Weird ... but anyway, I just select the version and roll it back. Very nice. While I was at it, I asked it to save 30 days of history. I have a big hard drive, what harm can it do? Now the code is as it was at the beginning of this section. Let's see if there's a better way to get rid of that annoying frameStart member variable.

Improving the Design ... Search for an Easier Way

I want to get rid of the frameStart variable without having to edit so many methods. My guess is that's going to be difficult. We can at least update it separately from the scoring, which will simplify things a little bit:

    public int score(int[] rolls) {
        int score = 0;
        int frameStart = 0;
        for (int frame = 1; frame <= 10; frame++) {
          score += scoreFrame(rolls);
          frameStart += frameSize(rolls);
        }
        return score;
    }

    private int frameSize(int[] rolls) {
        if (frameIsStrike(rolls))
            return 1;
        else
            return 2;
    }

    public int scoreFrame(int[] rolls) {
        int result;
        if (frameIsStrike(rolls)) {
            result = tenPlusNextTwoRolls(rolls);
        }
        else if (frameIsSpare(rolls)) {
            result =  tenPlusNextRoll(rolls);
        }
        else {
            result =  frameTotal(rolls);
        }
        return result;
    }

(The frameStart updates are removed from scoreFrame()). I renamed the parameter in the score() method as well, I hadn't noticed that the name IDEA proposed wasn't very good. Now I can remove some redundant brackets:

    public int scoreFrame(int[] rolls) {
        int result;
        if (frameIsStrike(rolls))
            result = tenPlusNextTwoRolls(rolls);
        else if (frameIsSpare(rolls))
            result =  tenPlusNextRoll(rolls);
        else
            result =  frameTotal(rolls);
        return result;
    }

That may not make you happier, but it makes me happier. And you're not here to argue about the XProgramming.com coding standard.

But we're still using that field frameStart as a parameter to all those methods that look at rolls. The code is half parameters, and half member fields: that's not good style. And there are a bunch of references to frameStart. Let me think ...

Well, OK, here's an idea. We are using frameStart to say where the first element of the frame is. We could, instead, slice the array so that the frame's first element is always at the beginning. This would simplify all the called methods, and would remove their reference to frameStart. (It wouldn't reduce the number of methods I have to edit, though.) Still, it seems like an interesting idea. Let's try it. Turns out there is a System.arraycopy method. First, we'll make a copy of the rolls array to edit. (I'm not sure this is necessary but it seems it would be rude to do otherwise.)

    public int score(int[] inputRolls) {
        int[] rolls = new int[inputRolls.length];
        System.arraycopy(inputRolls, 0, rolls, 0, inputRolls.length);
        int score = 0;
        int frameStart = 0;
        for (int frame = 1; frame <= 10; frame++) {
          score += scoreFrame(rolls);
          frameStart += frameSize(rolls);
        }
        return score;
    }

Tests pass. Now we'll slice the array inside the loop. I hope this breaks a lot of the tests, but since my tests all use the same pattern throughout the rolls array, I'm a bit worried that it won't. Let's find out:

    public int score(int[] inputRolls) {
        int[] rolls = new int[inputRolls.length];
        System.arraycopy(inputRolls, 0, rolls, 0, inputRolls.length);
        int score = 0;
        int frameStart = 0;
        for (int frame = 1; frame <= 10; frame++) {
          score += scoreFrame(rolls);
          int frameSize = frameSize(rolls);
          System.arraycopy(rolls, frameSize, rolls, 0, inputRolls.length - frameSize);
        }
        return score;
    }

Irritatingly enough, that does run. Let's write a test that shouldn't run:

    public void testVarying() {
        BowlingGame game = new BowlingGame();
        int[] variedFrames = { 1,0, 1,1, 1,2, 1,3, 1,4, 1,5, 1,6, 1,7, 1,8, 2,2};
        assertEquals("Varying", 49, game.score(variedFrames));
    }

Oddly enough, that one is green too! I've done something odd. One quick look, then I'm backing it out. Ah. One thing is that I'm still using frameStart in all the under-methods. That's clearly not right. But I'm still wondering why that last test runs. I think I'll not worry about it and make the changes to remove frameStart references. First step, remove the variable and recompile. (Actually, no need to recompile ... IDEA highlights all the places I need to change. Cool.)

    private boolean frameIsStrike(int[] rolls) {
        return rolls[0] == 10;
    }

    private int tenPlusNextTwoRolls(int[] rolls) {
        return 10 + rolls[1] + rolls[2];
    }

    private int tenPlusNextRoll(int[] rolls) {
        return 10 + rolls[2];
    }

    private boolean frameIsSpare(int[] rolls) {
        return rolls[0] != 10 && frameTotal(rolls) == 10;
    }

    private int frameTotal(int[] rolls) {
        return rolls[0] + rolls[1];
    }

This all works, even the testVarying(). I removed the arraycopy, and that one failed, so I think this is good. I hate the arraycopy, though, don't you? It just seems clunky and somewhat inefficient as well, though done ten times it would be unmeasurable. How about ArrayLists, might they help us?

Well, after a bunch of messing around, the answer appears to be "no". Getting Java to process the ArrayList and index into it is too hard. C# will let me say arrayList[0], but Java apparently won't. Maybe I'm missing something. Anyway, it's a NOGO for tonight. I'm reverting, back to the arraycopy version with the integer subscripts:

public class BowlingGame {

    public int score(int[] inputRolls) {
        int[] rolls = new int[inputRolls.length];
        System.arraycopy(inputRolls, 0, rolls, 0, inputRolls.length);
        int score = 0;
        int frameStart = 0;
        for (int frame = 1; frame <= 10; frame++) {
          score += scoreFrame(rolls);
          int frameSize = frameSize(rolls);
          System.arraycopy(rolls, frameSize, rolls, 0, inputRolls.length - frameSize);
        }
        return score;
    }

    public int scoreFrame(int[] rolls) {
        int result;
        if (frameIsStrike(rolls))
            result = tenPlusNextTwoRolls(rolls);
        else if (frameIsSpare(rolls))
            result =  tenPlusNextRoll(rolls);
        else
            result =  frameTotal(rolls);
        return result;
    }

    private int frameSize(int[] rolls) {
        if (frameIsStrike(rolls))
            return 1;
        else
            return 2;
    }

    private boolean frameIsStrike(int[] rolls) {
        return rolls[0] == 10;
    }

    private int tenPlusNextTwoRolls(int[] rolls) {
        return 10 + rolls[1] + rolls[2];
    }

    private int tenPlusNextRoll(int[] rolls) {
        return 10 + rolls[2];
    }

    private boolean frameIsSpare(int[] rolls) {
        return rolls[0] != 10 && frameTotal(rolls) == 10;
    }

    private int frameTotal(int[] rolls) {
        return rolls[0] + rolls[1];
    }
}

I really like this IDEA reversion feature. It lets me experiment much more readily than I can in C#. I'd like my design better, though, if these "rolls" were some kind of useful class that I could remove things from, or something like that. I wonder if there are any actually useful collections in Java? A queue or something? Looks like I've got some reading to do.

In any case, good enough for now. I'll sum up tomorrow, as it is nearing midnight, but my feeling right now is that the TDD By Intention experiment worked pretty well.

A Little Tweaking

I'm not in the mood to sum up yet. But I just took a look at this code:

    public int scoreFrame(int[] rolls) {
        int result;
        if (frameIsStrike(rolls))
            result = tenPlusNextTwoRolls(rolls);
        else if (frameIsSpare(rolls))
            result =  tenPlusNextRoll(rolls);
        else
            result =  frameTotal(rolls);
        return result;
    }

We can do better than that -- again, by XProgramming.com standards -- you might not prefer the following, but I do:

    public int scoreFrame(int[] rolls) {
        if (frameIsStrike(rolls))
            return tenPlusNextTwoRolls(rolls);
        else if (frameIsSpare(rolls))
            return tenPlusNextRoll(rolls);
        else
            return frameTotal(rolls);
    }

That arraycopy thing is still bugging me. It let me not pass the frameStart around, and that had some value, but I think it's overly tricky, and kind of weird. We drop down to some odd array-moving thing right in the middle of otherwise rather abstract code.

Some days, I'd build a little object that included the arry and the start, and then increment the object. But ... here's an idea. This object, the BowlingGame, could be that object. Suppose we make both the array and the frameStart into the member variables of this object. Right now, it has no member variables at all. How about if we just create our BowlingGame with the array, and go from there?

That sounds interesting. I think I'll do it in two phases: first, I'll move the rolls array, and the frameStart to member variables, and make everything work. Then, if it seems to make sense, I'll change score() and the constructor to build the Game with its rolls already set.

I think it would be best to roll back again, to before the arraycopy trick. I'll be glad to get rid of it. I'll look for a good rollback point. I hate to lose that neat little edit I just did to scoreFrame() though ... but it'll be easy to do again if need be. Here's where I rolled back to:

public class BowlingGame {
    int frameStart;

    public int score(int[] rolls) {
        int score = 0;
        frameStart = 0;
        for (int frame = 1; frame <= 10; frame++) {
          score += scoreFrame(rolls);
          frameStart += frameSize(rolls);
        }
        return score;
    }

    private int frameSize(int[] rolls) {
        if (frameIsStrike(rolls))
            return 1;
        else
            return 2;
    }

    public int scoreFrame(int[] rolls) {
        int result;
        if (frameIsStrike(rolls)) {
            result = tenPlusNextTwoRolls(rolls);
        }
        else if (frameIsSpare(rolls)) {
            result =  tenPlusNextRoll(rolls);
        }
        else {
            result =  frameTotal(rolls);
        }
        return result;
    }

    private boolean frameIsStrike(int[] rolls) {
        return rolls[frameStart] == 10;
    }

    private int tenPlusNextTwoRolls(int[] rolls) {
        return 10 + rolls[frameStart+1] + rolls[frameStart+2];
    }

    private int tenPlusNextRoll(int[] rolls) {
        return 10 + rolls[frameStart+2];
    }

    private boolean frameIsSpare(int[] rolls) {
        return rolls[frameStart] != 10 && frameTotal(rolls) == 10;
    }

    private int frameTotal(int[] rolls) {
        return rolls[frameStart] + rolls[frameStart+1];
    }
}

Tests are green, after I made sure that score() wasn't using its own copy of frameStart! That new test, testVarying, saved me. Remind me not to use the same values in all the frames of the tests when next I set out to do this.

The idea is to make the rolls also a member variable. That looks like this:

public class BowlingGame {
    int frameStart;
    int[] rolls;

    public int score(int[] inputRolls) {
        rolls = inputRolls;
        int score = 0;
        frameStart = 0;
        for (int frame = 1; frame <= 10; frame++) {
          score += scoreFrame(rolls);
          frameStart += frameSize(rolls);
        }
        return score;
    }

Tests are green, of course. Now I can remove the rolls parameter from as many of the methods as I wish, and IDEA or the compiler will help me figure what's up. Just for fun, I'll do a few at a time.

I'm using IDEA's "Change Method Signature" refactoring to remove the parameter, though it would be just as easy to click there and remove the contents of the parens. With a few other little tweaks, we wind up here:

public class BowlingGame {
    int frameStart;
    int[] rolls;

    public int score(int[] inputRolls) {
        rolls = inputRolls;
        frameStart = 0;
        int score = 0;
        for (int frame = 1; frame <= 10; frame++) {
          score += scoreFrame();
          frameStart += frameSize();
        }
        return score;
    }

    private int frameSize() {
        if (frameIsStrike())
            return 1;
        else
            return 2;
    }

    public int scoreFrame() {
        if (frameIsStrike())
            return tenPlusNextTwoRolls();
        else if (frameIsSpare())
            return tenPlusNextRoll();
        else
            return frameTotal();
    }

    private boolean frameIsStrike() {
        return rolls[frameStart] == 10;
    }

    private int tenPlusNextTwoRolls() {
        return 10 + rolls[frameStart+1] + rolls[frameStart+2];
    }

    private int tenPlusNextRoll() {
        return 10 + rolls[frameStart+2];
    }

    private boolean frameIsSpare() {
        return rolls[frameStart] != 10 && frameTotal() == 10;
    }

    private int frameTotal() {
        return rolls[frameStart] + rolls[frameStart+1];
    }
}

What Now?

We seem to have arrived at one of the simpler solutions we've seen in this series, though we approached it from a different angle. And there have been a few digressions that I'll want to reflect on in a moment. What I'd like to try now, though, more to exercise IDEA than anything else, is to inline some of those nice mnemonic methods just to see what it looks like. Here's a great example, the scoreFrame() method. First, with the methods:

    public int scoreFrame() {
        if (frameIsStrike())
            return tenPlusNextTwoRolls();
        else if (frameIsSpare())
            return tenPlusNextRoll();
        else
            return frameTotal();
    }

Now, with everything inlined:

    public int scoreFrame() {
        if (rolls[frameStart] == 10)
            return 10 + rolls[frameStart+1] + rolls[frameStart+2];
        else if (rolls[frameStart] != 10 && rolls[frameStart] + rolls[frameStart+1] == 10)
            return 10 + rolls[frameStart+2];
        else
            return rolls[frameStart] + rolls[frameStart+1];
    }

Wow, look at that. I hate it!! All that intention, all those ideas, just gone. Works just as well, and slightly faster I suppose ... but much harder to understand. That's out of here: I'm going back to the first one!

Whew! That's better. Now a break and a summary.

Let me explain. No, there is too much. Let me sum up.

I've decided not to move the injection of the rolls array into the constructor. I see no real advantage to it. Let's look at the bigger picture.

The experiment of doing Test-Driven Development and Programming By Intention together seems to me to have gone fairly well. It was helpful to know where I was heading, as it always is. This time, instead of discovering the commonality, as one might if one were thinking procedurally, I was able to move pretty directly to where I wanted to wind up. After only one experiment, though, I'm not sure where I'd put the balance between TDD and PBI. I've come to like them both, but have taken to using PBI while making specific tests run, but not when laying in an entire class or method. The main reason is that when I do TDD, I evolve the classes and methods, rather than laying them in in overview and filling in the details. It's going to take more practice to learn whether to move my balance, and where to move it.

I feel that there was too much fiddling around with the "rolls" array to be efficient. I went through too many options given the simplicity of the example and my familiarity with it. Part of this was just me fiddling with IDEA, but the whole process felt a little fuzzy to me. I'm not sure what caused it, but I'll stay alert in my next few coding sessions to see whether it happens again, and to see if I can figure out something that needs improvement.

The use of repetitive patterns in the tests was a mistake. The problem is that an implementation that fails to increment the frame start can accidentally get the right answer. I haven't usually done that in the past, and I'll make a note not to do it again. The reason I did it was that, since I was sending in the entire array or rolls, I found a cute way to copy and paste to make each frame's rolls. A little too simple.

I like the bowling game for its simplicity. But, despite the fact that I got a lot of mileage out of it even in this example, I can feel that its lack of depth is limiting its usefulness. I'm thinking about extending the idea to a "bowling controller" that manages all the aspects of scoring, pin-setting, and score display. Maybe next time ...

All in all, an interesting experiment. I'll try working with different combinations of TDD and PBI a bit more, and see what I come up with.


1Test-Driven Development, the notion of Programming by Intention, the pattern Fake It Till You Make it, and many of the other ideas here, are based on the work of others, most notably Kent Beck and Ward Cunningham. It is my pleasure to honor them, and all the other great people from whom I have learned. The style of development here is my own mix of practices from all sources. Any errors you find in this are the work of Secret Villains, whose mad schemes will soon be revealed.2

2The last sentence above is stolen from Wil McCarthy.

XProgramming > XP Magazine > Adventures in C#: TDD by Intention?
COLLECTED TOPICS: Kate Oneal | Adventures in C# | Documentation in XP | Book Reviews