Mining the Bowling Game
Bowling for Objects?
I often use the Bowling Game in demonstrations of Test-Driven Development, because it’s about the right size for an hour or two of discussion, and because it brings up enough ideas and questions to be interesting. This past week, the questions were enough to push me further in discovering what it’s all about.
For previous bowling game articles, and a look at the spec, check out:
A search for “bowling” will turn up even more articles, should these not be enough to sate your hunger for the subject. Be sure to leave room for what’s going to be served up in the next few articles.
At a client site last week, a programmer I’ll call “Lawson”, to protect the innocent [namely all the programmers who were not named Lawson], commented that the solution seemed rather procedural and that he’d prefer to see a more object-oriented solution. I suspect that he said that at least in part because I had been looking at some of the team’s code the day before and had said that it seemed procedural to me and that I’d rather see a more object-oriented solution. I could be wrong of course: maybe there was just an echo in the office. Those open workspaces can be like that.
Whether he was teasing me or not, Lawson was absolutely right: my solution was rather procedural, as my Bowling Game example often is when I demonstrate TDD1. Take a look. Here are the tests:
using System; using NUnit.Framework; namespace BowlingSolution { [TestFixture] public class BowlingTest { Game game; public BowlingTest() { } private void RollSame(int count, int pins) { for ( int roll = 0; roll < count; roll++ ) { game.Roll(pins); } } [SetUp] public void Setup() { game = new Game(); } [Test] public void GutterBalls() { RollSame(20, 0); Assert.AreEqual(0, game.Score()); } [Test] public void AllOpen() { RollSame(20, 4); Assert.AreEqual(80, game.Score()); } [Test] public void Spare() { game.Roll(7); game.Roll(3); game.Roll(6); RollSame(17,0); Assert.AreEqual(22, game.Score()); } [Test] public void Strike() { game.Roll(10); game.Roll(3); game.Roll(4); RollSame(16, 0); Assert.AreEqual(24, game.Score()); } [Test] public void PerfectGame() { RollSame(12, 10); Assert.AreEqual(300, game.Score()); } [Test] public void Alternating() { for ( int i = 0; i < 5; i++ ) { game.Roll(10); game.Roll(6); game.Roll(4); } game.Roll(10); Assert.AreEqual(200, game.Score()); } [Test] public void TwoScores() { RollSame(20,3); Assert.AreEqual(60, game.Score()); Assert.AreEqual(60, game.Score()); } } }
And here’s the code:
using System; using System.Collections; namespace BowlingSolution { public class Game { ArrayList rolls = new ArrayList(); int frameStart; public Game() { } public void Roll(int pins) { rolls.Add(pins); } private int FrameScore() { int result; if (IsStrike()) { result = 10 + Pins(frameStart+1) + Pins(frameStart+2); frameStart++; } else if (IsSpare()) { result = 10 + Pins(frameStart + 2); frameStart += 2; } else { result = Pins(frameStart) + Pins(frameStart+1); frameStart += 2; } return result; } private bool IsSpare() { return Pins(frameStart) + Pins(frameStart+1) == 10; } private bool IsStrike() { return Pins(frameStart) == 10; } private int Pins(int location) { return (int) rolls[location]; } public int Score() { int score = 0; frameStart = 0; for ( int frame = 0; frame < 10; frame++ ) { score += FrameScore(); } return score; } } }
Now in fact, Lawson has a darned good point. The highlighted FrameScore method is certainly procedural: it has two whole if statement and an else in it. I think it’s pretty clear, with the nice IsStrike and IsSpare methods, but still, there’s just one class involved, and we do have the if statements all bunched up there.
In thinking about the discussion with the client team, I was reminded that there’s a surprising amount of richness and potential learning in this little problem and its solution. I talked with Chet on the phone about the potential of a workshop or tutorial built around the bowling game. Our talk then and later led us to agree that there is a lot of interest in the game, but also gave us concern that in a workshop situation, if we split off pairs or teams to work together, some of them might go off the rails.
Our primary reason for fearing that people just learning TDD would go off the rails is that our experience with the bowling game is that we often go off the rails, especially when we set out to do a cool OO solution.
A Cunning Plan
Chet and I met today for lunch, and talked a bit more about the game and the idea of a tutorial. We believe that if one follows TDD “style” closely, one tends to wind up with the procedural solution. To do a solution that’s more “object-oriented” seems to require a bit more design up front. That’s not necessarily bad, mind you, though I usually prefer not to contaminate my mind with too many design ideas before I start coding. That’s just a personal preference, and I do it, in part, because I enjoy seeing what happens, and partly because I have enough design and code experience to be pretty confident that I’m not going to do anything really bad.
If you think about the design for bowling, pretty soon you realize that a given roll in the game can be referred to by up to three different Frames: it can be the first roll of the current Frame, the first bonus ball of a preceding strike frame, and the second bonus ball of a strike frame two frames back.
Worse yet, that’s not always the case. Sometimes a given roll applies to just one frame, sometimes to two, and sometimes, in the two strikes in a row case, it is looked at by three.
Furthermore, a Frame can consume either one or two rolls: that is, the roll in question does not affect any Frames further on. Each roll is consumed by exactly one Frame. So a given roll can be a bonus in zero, one or two Frames, and then is always consumed by one Frame. But some Frames consume two balls, and some consume just one.
Handling this situation can get really hairy, and it is easy to go astray. I’ve gotten it to work in a number of different ways. One that I like is for each Frame to have a reference to all the rolls of the game, and to know which roll is the first one it consumes. Then, each Frame refers to its first roll, and to one or two more, depending on whether it is strike, spare, or open. Those solutions all get a little complex, and today we were wondering whether they could be refactored back down to the procedural version. Maybe we’ll try that in this series.
But we were talking in a different direction today. I’ve always thought that the Game should just present the rolls to all the Frames, one after another, and the Frames should ignore the roll, treat it as a bonus, or consume it, depending on the Frame and its state. I drew a little picture of frames with holes in them for balls to fall into, or little flippers that the balls would tip over to register bonuses.
Chet offered the idea of a pinball machine. Pinball machines have holes in them that sometimes eat the balls, and other times run up the score and then kick the ball back out. So we imagined that the Frames of the game might be in a row. Each Frame would start in some standard state, and then balls would be sent down the line. A Frame would eat the ball, treat it as bonus, or ignore it, depending on the state of its kicker.
We decided that this “metaphor” could lead to an interesting design. We envision a fixed array of Frames, and that the Game would send the ball to each Frame consecutively, until one of them doesn’t return the ball, having eaten it. Our plan is that tomorrow we’ll give this design idea a try.
Stay tuned!
1Tim Haughton asked on the XP list whether I was saying that all solutions TDD’d come out procedural: I wasn’t; they mostly don’t. I do think that this one has a very nice procedural solution, which I actually prefer to most of the more “OO” solutions I’ve seen, but it’s a result of the very tiny spec: “Calculate the total score for a complete game of bowling with no errors in the input.” A more complex problem would drive out objects.
Tim went on to refer to a “tipping point”, where OO gains a foothold owing to removal of duplication and of code smells. That’s my experience as well. Each developer or pair will have different sensitivity to this tipping point, and will respond to it differently. Generally speaking, more OO is better. But remember the fourth rule of simple code: Minimize the number of classes and methods.
Thanks to Tim for the clarifying question and comments. We’ll be exploring a more OO-style solution in the next article.