I ran into an interesting scenario after revisiting the Bowling Game kata from memory. The exercise was a small reminder of the power of test-first development.
I worked through the kata as usual, but I was unable to recall each step, so I wrote the code fresh. I came to the following implementation, which passes the normal JUnit tests:
public int score() {
int score = 0;
for(int i = 0; i < 20; i++) {
if (scores[i] == 10) {
score += scores[i+1] + scores[i+2];
}
else if (scores[i] + scores[i+1] == 10) {
score += scores[i+2];
}
score += scores[i];
}
return score;
}
I realized that I’d forgotten to support the notion of frames. An example is the logic for 10 pins. A strike only occurs when the 10 pins are knocked down in the first try in the frame.
Unit tests are only as good as the use cases they cover. Because I hadn’t written a test that implemented the “strike” business rule, I had a faulty implementation.
Fortunately, it’s easy to resolve this situation with test-first development. Here’s the failing test I used to exploit this design weakness:
@Test
public void spareWithTenPins() {
g.roll(0);
g.roll(10); // spare, not strike
g.roll(2);
g.roll(1);
assertEquals(15, g.score());
}
Next, I fixed the implementation to take attempts in pairs – that is, a “frame” – and the spareWithTenPins
and other tests passed. Here’s the new score()
implementation:
public int score() {
int score = 0;
for(int f = 0; f < 10; f++) {
int i = f * 2;
if (scores[i] == 10) {
score += scores[i+1] + scores[i+2];
}
else if (scores[i] + scores[i+1] == 10) {
score += scores[i+2];
}
score += scores[i] + scores[i+1];
}
return score;
}
It’s worth noting that this is not clean code – I’m not using variables with intention-revealing names, for example. I found that I was somewhat lax about the refactoring step when performing this kata today.