I had this cool idea for another way for Bowling to work. I'm snowed in, so I tried it. The results were good ... and very thought-provoking.

An Idea ...

I have this vision in my mind of how frames “ought” to work. Basically I’m envisioning a sort of “rack” of individual frames. A roll comes in (I’m picturing something like a lottery ball, with a number written on it, and starts rolling down the rack of frames. Some frames are done: they already know their score. The ball rolls right past them. Some frames are still unsatisfied: they don’t have all their own rolls yet, or they are still waiting for a bonus roll. These frames do one of two things. If the roll “belongs” to them, they grab the lottery ball, and accumulate its value into their score. If it’s one they are just interested in, they accumulate its value and pass it on.

So the scoring balls kind of roll down this row of Frames, ticking over accumulators in frames waiting for a bonus, and finally slotting into the frame that is waiting for them. It looks like this:

image

In the picture above, the player has rolled 6, 4, 5, 2. When the six came along, it fell into the first hole, and registered six. Then the four came along, bounced over the six, fell into the next hole, registered four for a total of ten. Because that’s a spare, the next hole gets that green power gate, and the next one the red gate that keeps everything out. The five rolls by, bouncing over the 6 and 4. The power gate copies the five into the hole, making the frame total 15. The ball rolls on, falling into the first hole in the next frame (total 5). Then the two comes, bounces over the 6, 4, 5, and red gate, bounces over the 5, falls into the hole. Frame score 7. And so on. I hope you can catch the idea. I think it’s kind of cool.

My plan for today is to TDD that new frame object, and then fit it into the bowling game. Along the way, we’ll see what we learn.

Begin With a Test

I’ll create a new TestCase subclass, FrameTest:

image

I’m planning to test just one frame at a time, and I have in mind that if there are several, they will be a linked list, so for now I’m just assuming a frame instance variable. What shall we test first? I’m not sure yet whether I want unused frames to score zero or what, so I think I’ll start with an open frame. That’ll be a little more complicated than my usual tests … unless I fake it. Yeah, I’ll do that:

testOpen
  frame roll: 4.
  frame roll: 5.
  self should: [ frame score = 9].

That should get me some errors. frame isn’t initialized, so I’ll have to create a setUp or something, and create the new class. What shall we call it? Frame is taken, at least right now, and I don’t want to remove that class. Hmm. RackFrame, I guess. We can rename it later when we figure out what it really is.

This should be enough code to get the TestRunner to discover the test, let’s see. Yes, if we press the little Refresh button, it finds FrameTest. (Remind me to rename that RackFrameTest.) I notice that the TestRunner refers to the test classes separately. There’s probably some way of having a “test suite” that includes both. We’ll probably have to learn that pretty soon. For now, I’m just going to test the frame anyway.

image

Quelle surprise, UndefinedObject doesn’t understand #roll:. We know what that is. I’m tempted not to use a setUp method, because later on I’ll have whole lists set in there. That’s thinking too far ahead. Maybe setUp is as well, but I don’t think so, since I already know what the next test is going to be. (Open with different numbers.) So …

setUp
  frame := RackFrame new

Smalltalk offers us “Undeclared” as a choice for the new word RackFrame, and we accept that choice. When we define the class in a moment, it’ll hook up automatically. And I hope you’ll forgive me for defining the class without running the tests again, since we know what’s going to happen. Of course, if I’m wrong, I’ll tell you about it.

image

Now I’ll run the tests, expecting that RackFrame doesn’t understand #roll: either:

image

We’ll define #roll: to do nothing, to get the next error, which will be that #score is undefined.

roll:

#roll: has no body, does nothing. (Returns self, actually.) That fails as expected. Therefore:

score
  ^9

#score returns 9. Test runs. Time for a new test:

testOpen8
  frame roll: 3.
  frame roll: 5.
  self should: [ frame score = 8 ]

That means we’d better do something in Frame. I’m thinking an instance variable “score” and #roll: accumulates into it:

Smalltalk defineClass: #RackFrame
  superclass: #{Core.Object}
  indexedType: #none
  private: false
  instanceVariableNames: 'score '
  classInstanceVariableNames: ''
  imports: ''
  category: 'RonBowling'

roll: anInteger
  score := score + anInteger

score
  ^score

My plan is that both these tests should run. (Oops, I never actually ran the new one, I was so sure that it wouldn’t work. My apologies.) Teach me a lesson, won’t it, if the failure is anything surprising? But it isn’t. “Message not understood: #+” That means that I didn’t initialize score to zero. That requires a constructor or a lazy init. Constructor seems right:

RackFrame class>>new
  ^super new initialize  

initialize
  score := 0

I reflect, in passing, on how cool it is that I don’t have to declare the score variable, just set it. I expect the tests to work now:

image

(I didn’t really do all that work in four seconds. I pressed the button a few times to get a jolt of testadrenaline.) OK, what now? I usually do spare next, on the grounds that it’s the next least difficult, so we can do that now. Watch for a surprise, though.

testSpare
  frame roll: 9.
  frame roll: 1.
  frame roll: 5.
  self should: [ frame score = 15 ]

That should be hard enough … except that, unfortunately, the test runs! Since the frame just accumulates, spare and strike scoring will work reasonably well. We need a harder test. One would be to add another roll. The first frame’s score should stay 15, but it won’t. Let’s just improve the same test:

testSpare
  frame roll: 9.
  frame roll: 1.
  frame roll: 5.
  frame roll: 4.
  self should: [ frame score = 15 ]

Cool, that fails as expected, frame score 19.

Now, you may be thinking that it isn’t really fair to send four rolls to this one frame. But remember, this is a RackFrame, and the idea is that all the rolls pass through it and get correctly classified. So it’s fair to expect this frame to have enough state to know what to do with the third and fourth rolls. When we’re done, it should accumulate the third and pass it on, and ignore the fourth and pass it on. How to do this? Well, how about if the RackFrame starts out with some kind of interest count, the number of balls it’s interested in? That won’t hold us forever, but it should work for now.

But I’m concerned. I could just set an interest count to 3. Then all my tests would work, but the open frames would work improperly. I could make a note of that, and beef up those tests later. Or I could just beef them up now. If I do that, I’ll have more than one test failing, but they will all be failing for approximately the same reason. I’ll do that. I’m going to add another roll or two to the other tests:

testOpen
  frame roll: 4.
  frame roll: 5.
  frame roll: 1.
  frame roll: 2.
  self should: [ frame score = 9].

testOpen8
  frame roll: 3.
  frame roll: 5.
  frame roll: 3.
  frame roll: 4.
  self should: [ frame score = 8 ]

I expect all my tests to fail now. And they do. Now if I add the interest count instance variable, set it to 2, and use it, I should be able to make the opens work.

Smalltalk defineClass: #RackFrame
  superclass: #{Core.Object}
  indexedType: #none
  private: false
  instanceVariableNames: 'score interestCount '
  classInstanceVariableNames: ''
  imports: ''
  category: 'RonBowling'

 initialize
  interestCount := 2.
  score := 0.

roll: anInteger
  interestCount > 0 ifTrue: [
    score := score + anInteger.
    interestCount := interestCount - 1]

I expect this to make the two open frame tests work. And it does. Now to make the spare work, we need to increase interest count by one if we have a spare. Hmm, how to do that? The naive approach is to implement isSpare, something about interestCount = 0 and score = 10, then increase interestCount by one. But then if the guy rolls a zero, and then a value, the spare will score incorrectly. I’m going to write another test to express that concern. Might as well keep the list of tests in the test class, I’m thinking.

testSpareWithZero
  frame roll: 9.
  frame roll: 1.
  frame roll: 0.
  frame roll: 4.
  self should: [ frame score = 10 ]

Unfortunately, that test actually works right now. This might be a good reason to keep notes on tests and not write them, which is the conventional wisdom. I’m experimenting with writing tests as I think of them, and I’m starting to think that it will confuse me. We’ll see. Anyway, let’s make the original spare test work:

roll: anInteger
  interestCount > 0 ifTrue: [
    score := score + anInteger.
    interestCount := interestCount - 1.
    self checkSpare ]

checkSpare
  (interestCount = 0 and: [score = 10] ) ifTrue: [ interestCount := 1 ]

So if it’s a spare, I’ll set interestCount back to 1. I expect this to make the first spare test work, and the second fail. Let’s hope. Sure enough, the first spare works, and the second fails this way, as expected.

image

Reflection

We’re on a red bar, but we know why. Let’s reflect a bit on how this is going.

The main thing I’m doing differently is to record test ideas as tests. The good thing about this is that I’m not likely to forget an issue. The bad things are that I get multiple tests failing, which might be confusing, and that I don’t really give very much thought to the new tests, since my mind is trying to stick to the main theme. I’m not sure how much I like this, but my feeling is that the “standard” practice of writing test ideas on a card might be better. We’ll see what I do.

Also, I’m getting concerned that this code is going to get weird with this “interestCount” idea. It seems like we are going to wind up with at least two counts, one for rolls to consume vs rolls to pass on, and one about rolls to score and rolls not. I think I’ll just brute this through for a while, then see what happens. Once this next test works we should see something, and I’m sure that the strike feature will bring out some issues as well. We’ll press on.

The Complex Spare

The testSpareWithZero fails. Let’s look at the code to see why:

roll: anInteger
  interestCount > 0 ifTrue: [
    score := score + anInteger.
    interestCount := interestCount - 1.
    self checkSpare ]

checkSpare
  (interestCount = 0 and: [score = 10] ) ifTrue: [ interestCount := 1 ]

What happens is that two rolls come in, scoring ten, and the interestCount is zero. So we check spare, and it is. So we tick interestCount back up. Then a zero comes in, which we duly add into score. But then we checkSpare again, and the score is still ten, and of course the count is down again. So we tick it up. Then the four comes in and we incorrectly count it. Arrgh.

What can we do about this? There are two “states”. In one state, the frame is counting the rolls that belong in that frame, and in the other, it’s counting rolls that belong downstream, as bonuses. Once the frame decides it has a spare (or strike, I suppose), it puts itself in “bonus mode” and can never again tick its bonus count up. But I don’t want to build a complex state machine or anything like that here.

... after a short delay ...

Or do I? I took time for a nap and a trip out for pizza. (I’m snowed in here, so of course I had to go out, so that I could bring back food. That makes sense, doesn’t it?) In thinking about how this frame works, it seems to me that it really has these discrete states:

  1. First: waiting for the first of its own rolls.
  2. Second: waiting for the second roll.
  3. Satisfied: ignoring all rolls that may come by.
  4. First bonus: waiting for the first of two bonus balls.
  5. Last bonus: waiting for the last of one or two bonus balls.

The actions for all these are pretty straightforward. I’m going with a state machine approach. I’ll get rid of the interestCount, add an instance variable state, initialize it, and sketch the methods one after another. Maybe I’ll just do a couple, but I’m of a mind to do them all at once. See what you think:

Smalltalk defineClass: #RackFrame
  superclass: #{Core.Object}
  indexedType: #none
  private: false
  instanceVariableNames: 'score state '
  classInstanceVariableNames: ''
  imports: ''
  category: 'RonBowling'

initialize
  state := #firstRoll:.
  score := 0.

firstRoll: anInteger
  score := anInteger.
  state := #secondRoll:. "intentionally ignoring strike for now"

secondRoll: anInteger
  score := score + anInteger.
  state := #satisfied:. "ignoring spare for now"  

roll: anInteger
  self perform: state with: anInteger

OK, those # things are the names of methods, you’ve seen me using them in the text. But what’s this #perform:with:? It’s pretty much what it looks like: if state = #firstRoll:, this code will call self firstRoll: anInteger. This is the reason I didn’t back away from doing a state machine: I remembered how easy it is in Smalltalk. I expect some of the tests to run now. Let’s see. Oops, well, no. I forgot #satisfied:

satisfied: ignored
  ^self

Two coding details there. First, I like to call an ignored parameter “ignored”, though sometimes in these articles I’ve forgotten to do that. Second, when a method does nothing, it is conventional to explicitly write ^self. That’s the default for any method anyway, but writing it explicitly shows that we didn’t forget to give the method content. I should have done that in the empty #roll: method above, but I’m out of practice. Now how about those tests? Ah, that’s more like it. Three run! And, as before, #testSpare is failing, which means that the other spare test, testSpareWithZero, is probably running for the wrong reason: it’s not checking the zero bonus ball, but since it’s zero, no harm done. We’ll get to some more robust tests real soon now. I’m going to fill in some more of the state code:

secondRoll: anInteger
  score := score + anInteger.
  score = 10
    ifTrue: [ state := #lastBonus: ]
    ifFalse: [ state := #satisfied: ]

lastBonus: anInteger
  score := score + anInteger.
  state := #satisfied:

Seems straightforward. If it’s a spare, you get one bonus roll (the last bonus), and otherwise you’re satisfied. And when you get the bonus roll, you add it in and then you’re satisfied. Do the tests agree? They do!

image

Let’s do a strike example. Then we’ll need to move on, and we should talk about how to do that. New test:

testStrike
  frame roll: 10.
  frame roll: 10.
  frame roll: 10.
  frame roll: 5.
  self should: [ frame score = 30 ]

I expect this to fail, with a score of 20, as if we had rolled two strikes in the one frame. Let’s see …

image

Sure enough, it fails, just as I planned. And the implementation:

firstRoll: anInteger
  score := anInteger.
  score = 10
    ifTrue: [ state := #firstBonus: ]
    ifFalse: [state := #secondRoll: ]

firstBonus: anInteger
  score := score + anInteger.
  state := #secondBonus:

I expect this to work … but it doesn’t. Oh, the dummy called it #secondBonus: not #lastBonus:. Actually that might be better, but first let’s fix it to be #lastBonus:, which was my intention.

firstBonus: anInteger
  score := score + anInteger.
  state := #lastBonus:

Now then, do the tests run? They do.

But that mistake made me think. I wasn’t happy with the bonus names, nor was I entirely comfortable that the spare jumped to #lastBonus:. Let’s break out the strike from the spare, and rename the bonuses. VW Smalltalk includes a rename: operation on methods, and it will rename them everywhere. Actually I wonder how deeply that goes … it might rename them everywhere in the system. Anyway:

firstRoll: anInteger
  score := anInteger.
  score = 10
    ifTrue: [ state := #firstStrikeBonus: ]
    ifFalse: [state := #secondRoll: ]

firstStrikeBonus: anInteger
  score := score + anInteger.
  state := #secondStrikeBonus:

secondStrikeBonus: anInteger
  score := score + anInteger.
  state := #satisfied:  

secondRoll: anInteger
  score := score + anInteger.
  score = 10
    ifTrue: [ state := #secondStrikeBonus: ]
    ifFalse: [ state := #satisfied: ]

The first three look good, but the second roll’s spare handling isn’t so good. I’m going to create #spareBonus:. It will look exactly like secondStrikeBonus:

spareBonus: anInteger
  score := score + anInteger.
  state := #satisfied:

Tests should still run … and they do. Let’s reflect.

Reflection

This state thing turned out pretty neat. I’ll leave it to you to figure out how to do that in Java or C#. It’s certainly doable, but not that simple. One of the things I’m learning from this example is that Smalltalk enables me – almost induces me – to create solutions that are more object-oriented, and that do things in ways that would trigger my “too complex” warnings in another language, but that remain simple enough in Smalltalk for my taste. This is the first time that I’ve done this Bowling example that I’ve evolved the solution this far.

The good news is that I can create this solution which is arguably “better”. The bad news might be that Smalltalk sucks me in to doing just a little bit more to make things better, and I might make them better than they need to be. Right now, I’ve got more mechanism than I really need to score a full game of bowling. If that was the real goal, I should have stopped four articles ago!

Now the purpose of this RackFrame exercise was to work on this vision I had of a string of frames working in concert to accumulate their scores as the rolls go by. I could do a couple more single-frame tests, but I’m not inclined to do that. What I’m more inclined to do is to move to the multi-frame situation, write some more robust tests, and get this RackFrame thing done.

I could produce a new test case class and work there on the string of RackFrame objects. But I think instead I’ll just extend the initializer in the test to create the string. To do that, I’ll have to have a factory method on RackFrame that creates a string of ten frames. For that, I need a test:

testFrameString
  "up until now, frame has had one frame instance in it. now, I'm expecting
   a string of ten. So I'll count them."
  | count currentFrame |
  count := 1.
  currentFrame := frame.
  currentFrame isNil whileFalse: [
    count := count + 1.
    currentFrame := currentFrame next ]

Saving that method pops up a message I’ve never seen before:

image

I’m not sure but I think what’s happening here is that the Smalltalk compiler knows how to optimize simple whileFalse: blocks, and this one, with assignments in it, is confusing it, but that that’s harmless. I’m hoping that’s the case. If I’m wrong, I could be in trouble here. Ah. No. Now I remember. #whileFalse: needs to be sent to a block not a constant. Sheesh:

testFrameString
  "up until now, frame has had one frame instance in it. now, I'm expecting
   a string of ten. So I'll count them."
  | count currentFrame |
  count := 1.
  currentFrame := frame.
  [ currentFrame isNil ] whileFalse: [
    count := count + 1.
    currentFrame := currentFrame next ]

That compiles as expected. What’s going on, of course, is that we want to execute the isNil test over and over. Therefore it has to be in a block: otherwise it would be an unchanging result. Maybe I’ll discuss that further later. For now, let’s get our test running. Now it fails looking for next. Good point, there isn’t one. We’ll add it:

Smalltalk defineClass: #RackFrame
  superclass: #{Core.Object}
  indexedType: #none
  private: false
  instanceVariableNames: 'score state next '
  classInstanceVariableNames: ''
  imports: ''
  category: 'RonBowling'

 next
  ^next

Now we have #next, and it will init to nil, so our test should fail with a count of one. Er, uh, it would if there was a #should in it. Where was my pair?

testFrameString
  "up until now, frame has had one frame instance in it. now, I'm expecting
   a string of ten. So I'll count them."
  | count currentFrame |
  count := 1.
  currentFrame := frame.
  [ currentFrame isNil ] whileFalse: [
    count := count + 1.
    currentFrame := currentFrame next ].
  self should: [ count = 10 ]

Now then. Well, it fails, but with count = 2, not 1. That’s because I initialized count to 1 and then counted the first frame. Fix the test:

testFrameString
  "up until now, frame has had one frame instance in it. now, I'm expecting
   a string of ten. So I'll count them."
  | count currentFrame |
  count := 0.
  currentFrame := frame.
  [ currentFrame isNil ] whileFalse: [
    count := count + 1.
    currentFrame := currentFrame next ].
  self should: [ count = 10 ]

OK, that’s better, it fails with count = 1. Now I’ll change the init on the test to get a ten frame list, and then provide the #tenFrameList factory method on RackFrame:

FrameTest>>setUp
  frame := RackFrame tenFrameList

RackFrame class>>tenFrameList
  | frame |
  frame := self new.
  9 timesRepeat: [ frame := self new: frame ].
  ^frame

new: aFrame
  ^self new setNext: aFrame

setNext: aFrame
  next := aFrame

The #tenFrameList code above is based on an idea Bill Wake sent me in a private email about this series. We are creating the last list element first, then creating new ones pointing to it, returning the last created, which shall be first, just as the prophecy says. I think this’ll work. Do you think it’s an odd way to create a list? So do I, a little bit. That’s why I wanted to try it. What do the tests think? They like it! They all pass. All of ‘em. Now, y’know what? I think this set of tests might correctly score whole games if only there was a message on RackFrame for totalScore. Let’s test that:

testTenOpenFrames
  10 timesRepeat: [
    frame roll: 4.
    frame roll: 3].
  self should: [ frame totalScore = 70 ]

That seems challenging enough, doesn’t it? Jumping right to that big a capability? But that’s what I envisioned for the RackFrame, it would just ripple all the balls down stream. Of course, they’re not rippling … yet. First time, the test fails for lack of totalScore, which we’ll implement:

totalScore
  ^score + next isNil
    ifTrue: [ 0 ]
    ifFalse: [ next totalScore ]

That looks familiar. Now the test will get the wrong answer. I expect … hmm, what do I expect? I’m not sure. Run the test. OK, first I expect to get a syntax error. We need parens:

totalScore
  ^score + (next isNil
    ifTrue: [ 0 ]
    ifFalse: [ next totalScore ] )

OK, that fails, because the downstream frames don’t have any scores yet. I think this test is too hard. I’m going to remove it, write a simpler one, then move on.

ignoredtestTenOpenFrames
  10 timesRepeat: [
    frame roll: 4.
    frame roll: 3].
  self should: [ frame totalScore = 70 ]

testSecondFrame
  frame roll: 5.
  frame roll: 4.
  frame roll: 3.
  frame roll: 2.
  self should: [ frame score = 9 ].
  self should: [ frame next score = 5 ]

That should be easier, and it will force me to send the rolls downstream. Test fails, I predict, with score nil instead of 5. (Recall that I took out the init to zero.) Yes, that’s what happens. Now let’s push all the appropriate rolls downstream in RackFrame:

firstStrikeBonus: anInteger
  score := score + anInteger.
  next roll: anInteger.
  state := #secondStrikeBonus:

 secondStrikeBonus: anInteger
  score := score + anInteger.
  next roll: anInteger.
  state := #satisfied:

spareBonus: anInteger
  score := score + anInteger.
  next roll: anInteger.
  state := #satisfied:  

satisfied: anInteger
  next roll: anInteger

I really expect this to start working. So far, I’m not feeling in trouble, but it has been a while since things worked, and I did get a couple of little surprises along the way. Let’s see … Yes!! The little test works. Now what about the ten opens?

testTenOpenFrames
  10 timesRepeat: [
    frame roll: 4.
    frame roll: 3].
  self should: [ frame totalScore = 70 ]

Does it run? Yes, it does! Now I expect everything to run. How about a perfect game?

testPerfect
  12 timesRepeat: [ frame roll: 10 ].
  self should: [ frame totalScore = 300 ]

Oops, not quite. What happens in the last frame is that it tries to pass the bonus rolls on to the eleventh frame. We need to change those next roll: statements to test for nil. One good way to do this in Smalltalk is to ask to see local senders of roll. When you do that, you get a special browser popped up, with just the methods that might be interesting in it:

image

I’m going to change them all to say self nextRoll: anInteger, and implement that method:

firstStrikeBonus: anInteger
  score := score + anInteger.
  self nextRoll: anInteger.
  state := #secondStrikeBonus:

satisfied: anInteger
  self nextRoll: anInteger.

secondStrikeBonus: anInteger
  score := score + anInteger.
  self nextRoll: anInteger.
  state := #satisfied:

spareBonus: anInteger
  score := score + anInteger.
  self nextRoll: anInteger.
  state := #satisfied:

nextRoll: anInteger
  next isNil ifFalse: [ next roll: anInteger ]

And the tests all run. Even the perfect game test! Should we do another test, or instead, should we just plug this new object into the other set of tests? Let’s do that, then whether it works or not, reflect. It used to say:

BowlingTest>>setUp
  game := RackFrame BowlingGame new

And now …

BowlingTest>>setUp
  game := RackFrame tenFrameList

Doesn’t work, because the old BowlingGame used score, and here we are using totalScore to mean the game score. I’m going to rename score in RackFrame to frameScore, and then totalScore to score … No. That’s a bad idea, it will break all my FrameTest tests. Hold on a moment here.

I really want a way to plug my new frame thingie in quickly, to see if it supports all the tests, because I’m sure it does. But I’ve changed the protocol between the two frame sets, it seems. Well, not really! The old Frame also responds to totalScore: it’s BowlingGame that uses score. The BowlingGame sets up its own frame list, using the old frames, and passes things into the list. Let’s change BowlingGame to use the new RackFrames and test it that way. Since the BowlingGame accumulates all the rolls, and then sets up its frames, let’s change the #frames method. The old #frames method in BowlingGame is:

frames
  | firstFrame |
  firstFrame := self firstFrame.
  9 timesRepeat: [ firstFrame addFrame ].
  ^firstFrame

We’ll use the new tenFrameList, but we’ll have to feed in all the rolls. That’s OK for now, this is just a test:

frames
  | firstFrame |
  firstFrame := RackFrame tenFrameList.
  rolls do: [ :each | firstFrame roll: each ].
  ^firstFrame

I’m expecting this to work … but it doesn’t. Why not? Oh. I changed the test to use the RackFrames directly. Changing it back:

BowlingTest>>setUp
  game := BowlingGame new

Now the tests run. So our new RackFrame is handling all the tests of the original BowlingGame! It’s so good, in fact, that if we fiddled our first set of tests to say totalScore instead of #score, we could drop the BowlingGame class altogether, and replace it with RackFrame. We need to think about this, and I bet you need to look at the code. Bear with me for a moment on that, however. You’ll see why. For now, let’s reflect.

Reflection - Good News

Well, first the good news. We evolved a new object, RackFrame, from some simple tests. It works entirely differently from our other Frame: it doesn’t record individual rolls at all: instead it just accumulates their total, frame by frame. And, instead of looking at rolls provided as a group, and indexing into them, it responds to a single roll at a time. And, starting with a batch of ten RackFrames, it just passes rolls down the list until the game is over. Then the total score is just the sum of the scores in the individual frames.

This implementation even has the rather neat property that any given frame has its score complete part way through the game, as soon as all its balls are provided, and the frame actually knows whether it is satisfied or not! We weren’t asked for this, but people who see my test-driven demo always ask what I’d do to deliver that feature, which every bowling alley really needs. Now we know.

Equally interesting is the fact that our new RackFrame object plugs into eht BowlingGame object very easily, with just one method change. (It did take me a couple of tries to see where that one change was: we’ll talk about that.)

I’d love to know what’s necessary to make all the transformations we’ve gone through here, in Java or C#. But I wouldn’t love to do it. Some weird delegate thing would handle the #perform:with:, with a bunch of declarations. And “all” we’d have to do to plug the RackFrame in would be to extract an interface, indicate that both classes implemented it, and a few little things like that. Oh, it’s easy enough, I hear all you Java and C# people saying. Is it? Would you have done it, given a little idea for something that might work? I know that I wouldn’t have: it would have seemed not to be worth it.

Would we have even done the RackFrame experiment in C#? I believe that I would not. I would have felt that even if it worked, it would be tricky to plug in, even tricky to program the experiment, and generally too much work to be worthwhile. And I might well be wrong when I make that kind of decision. In Smalltalk, I was induced to try it. I took a couple of hours and built something. It looked good, so I plugged it into the real system and all the tests still ran.

Smalltalk is changing the way I make tradeoffs in what to try and what not to. It is pushing me in the direction of code improvements that I wouldn’t even attempt in C#. Now if I were solely interested in code improvement, that might be a totally good deal. But there’s more …

Reflection - Bad News

I had a perfectly good BowlingGame four or five articles ago. Even two or three articles ago I had a better design than I usually get in C#. Yet I kept on improving. For my purposes, learning about Smalltalk and sharing what I find out, that’s fine. On a real project, it might get me in trouble. I might spend too much time improving, and not enough time shipping BowlingGames. That would be bad.

But I don’t think that would happen. I think that the customer would request a BowlingGame, and I’d estimate that I could do that in “two articles”. I’d sign up for that and the rest of the week’s work. In order to make my commitment, I’d have to keep focused on the customer’s real needs, not my cool ideas for new and better bowling. That would be good … mostly.

Mostly good … unless this new design is really better. Then what? Should we have estimated six articles, and done things this cool new way? I would argue not.

The original two-article approach is good enough. It passes all the tests. If and when the customer comes back and asks for individual frame scores to build up incrementally, or for some other feature like that, we can improve the design in the direction we improved it over the last four articles. Until then, first-order XP would suggest YAGNI. We’re not gonna need this neat RackFrame.

What do I mean by “first-order” XP? Well, in early days with most teams, I ask the team to focus as entirely as possible on customer stories, and very little on infrastructure. I do this to balance the usual plan of “We’ll build infrastructure for a few years, and then your features will go in real easy.” When a team works on customer stories for a while, they learn how to break features down, how to start with simple design, how to refactor solutions as they go. However, they almost invariably get behind on their refactoring. Then, they do a big refactoring that takes too long and makes their customer nervous. Then – and perhaps only then – they’re in a position to learn a better balance between “good enough” and “needs improvement”.

In Java and C#, a needed improvement simply takes longer and is harder to do than in Smalltalk. This means that it’s easier for a Smalltalk team to make a “needed improvement” investment. We can grab a couple of hours during the week, or after work, or at home, come up with something interesting, then complete it and plug it in without impacting the schedule.

Does that mean that the BowlingGame estimate should have been six articles instead of two? Certainly not. It does mean that the average story estimate might be more like three articles than two, and that the team’s overall speed and quality might improve thereby.

Most Important Learning So Far

For me, the most important learning for me – a rediscovery – is that Smalltalk invites design improvements by making change easier. Equivalent amounts of functionality require less code, and changes require less fiddling with declarations, interfaces, parameter definitions and the like. For me, well, I appreciate them, but I don’t need those things enough to prefer a language that has them. If all other things were equal, I might prefer to have them, But all other things are decidedly not equal. I’ve been around that world, and back, and for me, Smalltalk is a great place to be.

Now if I can just figure out a way to make a living doing it. I’m not the first or last person to ask that question, I’ll wager. That’s enough for now. Next article I’ll include the full listings, after plugging the RackFrame in a little more tightly. Stay tuned, and keep those cards and letters coming in.