Discovering Better Code: Bowling For Smalltalk

After a long time away, I had occasion to start using Smalltalk again. To get my chops back, I started with the Bowling Game exercise. My experience quickly reminded me why Smalltalk rocks. (Updated: includes changes and remarks about “==” vs “=”.)

The Problem

As always, I take a very simple definition of the problem: Write a BowlingGame object which, given a legal and complete series of rolls for a full game of bowling, will produce the correct total score. The program doesn’t need to score individual frames, deal with errors, or light up the beer sign. Just compute the total score.

I use this example because it’s about the right size for an hour-long Test-Driven Development demonstration in Java or C#. Maybe some day we’ll do an example where we create a bowling scoring thing that could maintain running scores and such. If so, I’m starting to suspect that the Smalltalk example here might serve as a better starting point.

Revisiting Smalltalk

I really haven’t used Smalltalk at all for a couple of years. There has been all this C# stuff in my life, and what little I did in other languages was done in Ruby. Ruby is cool, but Smalltalk rocks, because of its very sophisticated IDE as well as the simplicity of the language.

I downloaded Cincom’s VisualWorks Non-Commercial package and installed it on my laptop. Cincom makes their complete Smalltalk system available for non-commercial use. It’s quite likely the best Smalltalk out there, though there are some other important contenders. Object Arts’ Dolphin is a wonderful Windows-based Smalltalk, for example, with a free but limited version. I like Dolphin but had other reasons for going with VisualWorks this time. And there is Squeak. Based on the original Smalltalk-80, this package came out of Apple and then Disney, and has some of the top Smalltalkers in the universe behind it. I could have used any of these, but this time VisualWorks won the toss.

VisualWorks is big. Its code is made up of many packages arranged in a sort of hierarchic way. Some packages are separate and have to be brought in as needed. Frankly, at this writing, I don’t begin to understand all of how it’s organized. I’ve only been working with it for a few hours. Part of what’s interesting about this exercise is how much I got done in those few hours.

You may also notice things that I’ve done in entirely the wrong way. If so, drop me a line and point me in the right direction. It has been a while since I used VW and I can use all the help I can get.

Begin With a Test

As always in TDD, we begin with a test. Because you, the reader, may be unfamiliar with Smalltalk, I’ll be introducing the ideas of the language very gradually. A references section provided later will point you to some more formal language information. Here, just try to read the code and see what’s going on. In aid of simplicity, I’ll tell you what I’m doing, and not take you through the details except where it’s necessary.

Smalltalk has its code divided into “Packages”. I’ll begin by defining a new Package, RonBowling. Nothing special about this, you pop up a menu in the System Browser package pane, select New Package … and type its name. Now to build a test, I need a subclass of TestCase, the SUnit class from which tests are built. Click Find …, begin typing “testcase” and the find browser opens that package and class. Right-click the TestCase class name, select New … and Class, and type the name. “BowlingTest”. Click a few options for luck and Smalltalk creates the class.

Now one of the cool things about Smalltalk is that it is an incremental compiler. That means that as soon as you type something, it’s done. No compiling and linking delays at all. There’s rarely ever a delay when you define a new method … which we’ll do right now. I always begin with all gutter balls as my first test. Here’s the method definition:

testAllGutters
    | game |
    game := BowlingGame new.
    20 timesRepeat: [ game roll: 0].
    self should: [ game score = 0]

The first line, in bold, is the method name: testAllGutters. In Smalltalk, that name is a “Symbol” and would often be written as #testAllGutters. A Symbol is a string that is guaranteed unique: Every instance of #testAllGutters is guaranteed to be identical (not just equal) to every other. That’s not important to us right now, but we’ll see the # notation from time to time.

| game | defines a temporary variable named #game.

game := BowlingGame new. sends the message #new to the class BowlingGame, and stores the result, a new instance of BowlingGame, in the temp variable #game.

20 timesRepeat: [ game roll: 0]. does game roll: 0 twenty times, while game roll: 0 sends the game the message roll: 0.

Let’s explain just a bit. In Smalltalk,

game roll: 0

means just what Java would mean by

game.roll(0)

… we send the message “roll”, with the parameter zero, to the game. Smalltalk just uses fewer punctuation marks.

Does that mean that this code:

20 timesRepeat: [ game roll: 0]

must send the message #timesRepeat: to the integer 20? And that the #timesRepeat: message takes as its parameter that thing in the square brackets? Yes, it means exactly that. The message #timesRepeat:, sent to an integer, means to do whatever is in the block argument, that many times. Block argument? Basically its a bit like an inner function in Java or a delegate in C#. More accurately, those things are poor approximations of the “block” in Smalltalk. The block is a function with no name, written in line where we need it. We’ll see a few more of those as we go along, some even with parameters. For now, it’s sufficient to know what we said just above:

20 timesRepeat: [ game roll: 0]. does game roll: 0 twenty times, while game roll: 0 sends the game the message roll: 0.

Finally, self should: [ game score = 0] sends the #should: message to self (“this” in C# or Java). The #should: message takes a block, this one saying game score = 0. That means … game score equals zero! game score sends the parameterless message #score to game. That returns an integer. = sends the #= message to the integer, with the parameter zero. #= returns a boolean, true if the two are equal, false if not. #should: is one of the inherited methods of TestCase, and it’s the way we usually write tests in Smalltalk, unlike other languages where we say assert. We could have said assert as well — but we don’t.

Doug Swartz reminds me1 to mention that in Smalltalk we have two kinds of equal comparisons, #= and #==. In the first version of these articles, I was saying #== when I would have done better to use #=. In Smalltalk, #== means “is identical to”, and #= means “is equal to”. Identical means “is the very same object”, while equal has a more relaxed meaning, generally something like “all the instance variables of this object match that one.” As Doug refers to in the footnote below, Smalltalk’s small integers are all identical owing to details of how it works internally. He’s right that I do know that, and kind to put it that way, since clearly my “==” here is a vestige of that “other” language. Thanks, Doug!

OK, one more time for review:

testAllGutters
    | game |
    game := BowlingGame new.
    20 timesRepeat: [ game roll: 0].
    self should: [ game score = 0]

is the method definition for #testAllGutters. It defines a temp named #game, puts a new BowlingGame instance into it, rolls twenty zeros, and asserts that the game score should be zero. Neat, reet, complete.

When we save this method, Smalltalk tells us that it doesn’t know what BowlingGame is. We tell it “undefined”. That lets the method compile. Then we define the BowlingGame class. Smalltalk hooks the new definition up with the undefined value it was using. Now with any luck, that should be enough to let us run the test and see what happens. (I’m still not entirely sure about all the name spaces and such in VisualWorks. I’m trying to spare you some of my confusion, but I do have some confusion right now.) Let’s launch the TestRunner and see what happens.

We open a “WorkSpace” … an empty window, type “TestRunner new open”, and type control-D, which means “Do It”. We get an empty TestRunner gui:

imagePulling the pulldown at the top, we find our test class. If there had been several, they would all have been listed.

imageWe click Run and voila! It breaks!!!

imageJust what we expected, an error. The reason, of course, is that while we have defined the BowlingGame class, we have given it no methods. We can use the pulldown at the bottom of the TestRunner to get into the debugger to see what happened:

image imageWe really don’t need to go beyond the exception window. The message is clear: we need a method named #roll:. If we go into the debugger, it will give us a bit more information but mostly we’ll do it to show you how Smalltalk helps with things like this. Here’s a fragment of the debugger window:

imageIf we right-click on the highlighted method, #doesNotUnderstand:, one of the menu items offered is “Define Method”, and if we choose it, Smalltalk defines the method with a halt in it, restarts the code stack, and gives us a halt in the undefined method:

imageWe just type the desired definition right on top of the halt:

imageAs soon as we save it, Smalltalk is ready to proceed to success or the next error. Of course in our case, it’s an error:

imageNow we haven’t defined #score. We repeat the process: debug, define method, proceed. The method this time looks like this:

imageWe just define score to return zero. (The caret (^) means “return” in Smalltalk.) When we proceed from this halt, there are no more errors. We go back to the TestRunner, press Run, and voila:

imageOur program runs! Ship it!!

A Brief Retrospective

OK, what has happened here? We’ve defined our one test (score for twenty rolls of zero equals zero). We defined the BowlingGame class, and ran the test. The TestRunner showed an error. We asked it to debug, and the debugger told us that #roll: was undefined. We asked it to define, and it did so, running the code again and putting us right in the inserted definition, which was just a halt. We changed the method right in the debugger to ignore the input, returning “self”. Proceeding, we found that the method #score was not defined. Repeating the process, we defined it to return zero. At that point, no more errors occurred. We ran the test again and it was green.

Now, as always, ignoring the input and returning a literal zero for the result is just a stopgap. Kent Beck calls it “Fake it till you make it”. What we have done is to move as quickly as possible from no code to working code, that just works in the single case we have, all gutter balls.

What I’ve learned — relearned really — is how easy Smalltalk makes all this. There are no delays for compilation, ever. Not only can I change code in the debugger and pick up where I left off automatically, Smalltalk will even define a missing method for me and give me a breakpoint in it so that I can fill it in in the right context. It’s much faster than writing about it, and it’s much faster than would be possible in Java or C#, even with the excellent IDE tools that are out there today. It worked so well that I’m going to celebrate by going to lunch. Zukey Lake Tavern, I believe. See you soon …


1Doug Swartz helps me get my Smalltalk chops back when he writes:

Occasionally, I wonder if you’ve been coding in some other language too long when I see the method:

          spare
            ^(rolls at: startAt) + (rolls at: startAt+1) == 10

You and I both know this code works fine in every Smalltalk I know of because of the way small integers work. However, we wouldn’t want all those new Smalltalk converts you are creating to confuse equality with identity. I think you want = instead of ==. The same comment in isStrike, isSpare, ….

Posted on:

Written by: Ron Jeffries

Categorization: Articles

Recent Articles

Refactoring — Not on the backlog!

There has recently been a lot of noise on the lists, and questions at conferences, about putting refactoring “stories” on the backlog. This is invariably an inferior idea. Here’s why:


Ref01

When our project begins, the code is clean. The field …

Build it right to build the right thing

You can’t build the right product if you can’t build the product right.

I tweeted that this morning, in response to a Twitter-vibe that was flitting around. I’d like to follow it up here with a bit more meat.

The …

Estimate This! (or not)

My friends and colleagues in the #NoEstimates circles have some good ideas, as I've commented elsewhere. I'm left, though, with a couple of concerns.