We build a simple alarm clock object, applying the XP rules of simplicity, just to see how it will turn out.

I arrived in Cagliari, Sardinia for XP 2000 without an alarm clock. There was no way I could be sure of being up in time to set up for my 8 AM tutorial. I didn’t trust the wakeup call from the desk - or even my ability to communicate what time I wanted to be waked. So I did what any red-blooded XPer would: I programmed an alarm clock on my laptop.

In this article, we’ll turn that hack into an object, using the XP rules of simplicity: a program is simple enough when

  1. It runs all the tests;
  2. It communicates every idea you want to communicate;
  3. It contains no duplicate code;
  4. It contains as few classes and methods as possible.

Here’s the code, as set up to wake me at 3 AM on the last day I was in Italy, for the drive from Siena to Firenze, to catch a plane home:

[[Processor sleep: 15000. Time now hours = 3] whileFalse: []. 30 timesRepeat: [(Sound fromFile: ‘C:/windows/media/Musica Exclamation.wav’) woofAndWait.] ] fork

This code is plainly a hack, but the point was to wake me up. Now we’ll turn it into an object. Let’s start, though, with some explanation.

“Processor sleep: 15000” causes the current thread to sleep for 15 seconds.

“Time now hours = 3” answers true or false, depending on whether the hour of the current time is 3.

“whileFalse: []” repeatedly checks the thing on the left, and then does the thing on the right. So it sleeps, checks the time, and loops, doing nothing else, until 3. I note as I look at this that the code might be more clear with the test on the left and the sleep on the right.

“(Sound fromFile: ‘C:/windows/media/Musica Exclamation.wav’)” answers a Smalltalk Sound object, pointing to the given file, which makes a noise that I thought, repeated 30 times, would wake me up.

“woofAndWait”, sent to a sound, plays the sound and waits till it is complete.

“30 timesRepeat: […]” repeats whatever is in the block (the square brackets) 30 times.

”[…] fork” starts a separate thread running, doing whatever is in the block. In this case, the call to fork starts the alarm clock thread going.

Everything else above is just reference to objects and methods that are built into Dolphin Smalltalk. I found them by browsing around, based on my general knowledge of the class library and some judicious searching.

OK, now let’s make some objects. I envision an AlarmClock object that knows a time, and that once it is started, plays a sound when the time comes. Let’s write an AlarmClock object and an AlarmClockTest.

Now a worry I have in the back of my mind is that tests are supposed to run quickly. So I can’t write a test that runs for several hours. How about setting the alarm clock for the current time, and then seeing whether it comes back. I’m worried that a possible error is that the clock might never return. Then a test might look like this:

testReturn clock := AlarmClock hour: Time now hours minute: Time now minutes. clock run. self should: [true] “got here”

This test won’t compile until I define AlarmClock, which I do. I can’t resist giving it the hours and minutes:

Object subclass: #AlarmClock instanceVariableNames: ‘hour minute’ classVariableNames: ‘’ poolDictionaries: ‘’

Now I’ll run the test. It’s not that I don’t know what will happen, it’s that I want to reinforce my habit of running the tests very frequently. Sure enough, AlarmClock doesn’t understand the creation method #hour:minute:. I’ll build it, using a standard pattern, and also build the initialization method because my fingers know to do them both.

AlarmClock class>>hour: hourInteger minute: minuteInteger ^self new setHour: hourInteger minute: minuteInteger

AlarmClock>>setHour: hourInteger minute: minuteInteger hour := hourInteger. minute := minuteInteger

The first method, on the class side, just creates a new AlarmClock and initializes it. The second method is the init. It just sets up the hour and minute. Let’s run the test. Now AlarmClock doesn’t understand #run. No surprise there. We have to do some work now.

By cutting and pasting from the original clock, and by adding in some stuff about minutes, I get this:

AlarmClock>>run [[Processor sleep: 15000. Time now hours = hour and: [Time now minutes >= minute]] whileFalse: []] fork.

This isn’t very clear, but it is pretty direct. Sleep for 15 seconds (I’m worried about that slowing down the test, by the way.) Then check the time. I checked the minute for >= instead of = because I’m afraid the sleep might not end within the same minute, and we want to get the clock to fire even if the minute rolls over.

Let’s run the test. I expect it to work now. And it does! Gimme a high five!

OK, but maybe it just always returns. We don’t really know that the loop check is working. I’ll write a test with a delay in it, and check the time.

testDelay | time hour minute | time := Time now. hour := time hours. minute := time minutes + 1. clock := AlarmClock hour: hour minute: minute. clock run. self should: [Time now minute >= minute]

This should work. But it doesn’t! The reason is the last line: Time doesn’t understand #minute, it understands #minutes. I’m getting the scent here that I should call my variables hours and minutes, but I don’t think of them as plural, which suggests collection to me. For now I’ll just fix it:

testDelay | time hour minute | time := Time now. hour := time hours. minute := time minutes + 1. clock := AlarmClock hour: hour minute: minute. clock run. self should: [Time now minutes >= minute]

The test fails, which surprises me a lot. I also notice that by the time I get to the debugger, the Time now is different, so I should cache the time of the return from the #run. Test is now

testDelay | time hour minute returnTime | time := Time now. hour := time hours. minute := time minutes + 1. clock := AlarmClock hour: hour minute: minute. clock run. returnTime := Time now. self should: [returnTime minutes >= minute]

Wow, I’m startled! When I come back from #run, minute is set to 56, but the returnTime minute is only 55. What’s happening???

I’ll inspect the clock I set up. Maybe it doesn’t have the right time. But it does. Oh … I see … The run method starts the fork OK … but then it just returns, leaving the clock running. That’s what should happen, but it doesn’t work for my tests. Gotta think a minute here …

OK. I’ll give the clock an inner method that does the loop, and do the fork separately. Then I can test the inner method. I’ll call the method loop. Changing the tests:

testReturn clock := AlarmClock hour: Time now hours minute: Time now minutes. clock loop. self should: [true] “got here”

testDelay | time hour minute returnTime | time := Time now. hour := time hours. minute := time minutes + 1. clock := AlarmClock hour: hour minute: minute. clock loop. returnTime := Time now. self should: [returnTime minutes >= minute]

I’ll implement loop right now also, since it’s on my mind …

run [self loop] fork

loop [Processor sleep: 15000. Time now hours = hour and: [Time now minutes >= minute]] whileFalse: []

I’m a bit worried about this, but the test should work now. My worry is that now I’m not testing the forking at all. The code is pretty simple though. We’ll see how it goes. Let’s run the tests.

Woo! That scared me for a while, until I realized this test is guaranteed to run about a minute. I was afraid it was in a loop. But both tests run now. Here’s my plan, though. I could enhance the AlarmClock to work down to seconds, but that’s off point. Instead, now that #testDelay works, I’m going to comment it out. I’ll uncomment it when I change things, but for now I trust that the loop obviously works, and I’ll rerun it ifit looks iffy. We’ll also do at least one manual test of the whole thing, because my computer can’t hear the sound. But first, this version of the AlarmClock is running all its tests. Let’s see about making it simpler, since that’s the point of this article.

The loop method is a bit unclear:

loop [Processor sleep: 15000. Time now hours = hour and: [Time now minutes >= minute]] whileFalse: []

To begin with, I think it’d be more expressive if the delay was in the loop body, leaving only the test on the left. We’ll rewrite:

loop [Time now hours = hour and: [Time now minutes >= minute]] whileFalse: [Processor sleep: 15000]

Gotta run the tests again … be back in a minute! OK, they worked. Now there are some intentions I’d like to communicate here. First the easy one. That code on the right side is just there to wait a while. Let’s change it:

loop [Time now hours = hour and: [Time now minutes >= minute]] whileFalse: [self wait]

wait Processor sleep: 15000

And because the test takes so long, I’m going to take another bite and express intention on the left as well. (See how insidious long-running tests are?!?! Already I’m cutting corners.)

loop [self isTimeForAlarm] whileFalse: [self wait]

isTimeForAlarm ^Time now hours = hour and: [Time now minutes >= minute]

Hey, now that’s pretty clear. Let’s run the tests again. Back in a minute … Wahoo, they ran!

Here’s the whole operation of the object for our review:

run [self loop] fork

loop [self isTimeForAlarm] whileFalse: [self wait]

isTimeForAlarm ^Time now hours = hour and: [Time now minutes >= minute]

wait Processor sleep: 15000

Now this is pretty clear. We run the clock by starting a thread on the loop. The loop waits until it is time for the alarm. It’s time for the alarm when the hours and minues match. And we wait by sleeping for 15 seconds.

So, on review, I think the object communicates all my intentions so far. There’s no method I can eliminate without losing that communication. So I’m done.

Just for completeness, let’s do the sound. Clearly the idea will be to play the sound at the end of the loop.

loop [self isTimeForAlarm] whileFalse: [self wait]. self playSound

I don’t see how to write a test for this unless I wanted to write a stub object for playing the sound. If I were uncertain of what I’m about to do, I would. But the rule is that I have to test everything that could possibly break. Let’s see whether we need that test.

At this point, I’m going to comment out testDelay, because I’m sure the loop terminates properly. I’ll use the other test to check the sound.

testDelay “Runs for a minute. Commented out. Uncomment if you change the looping or time checking. | time hour minute returnTime | time := Time now. hour := time hours. minute := time minutes + 1. clock := AlarmClock hour: hour minute: minute. clock loop. returnTime := Time now. self should: [returnTime minutes >= minute]”

The other test fails: AlarmClock does not understand playSound. No surprise. We’ll cut and paste it in:

playSound 30 timesRepeat: [(Sound fromFile: ‘C:/windows/media/Musica Exclamation.wav’) woofAndWait.]

Running the test, I find that it works. It woofs 30 times - I counted them. And it’s loud and irritating enough to wake me up. But the method isn’t very communicative. Let’s improve it:

playSound 30 timesRepeat: [self woof]

woof (Sound fromFile: ‘C:/windows/media/Musica Exclamation.wav’) woofAndWait.

This is still a bit grubby, so let’s refactor again …

woof self sound woofAndWait

sound ^Sound fromFile: ‘C:/windows/media/Musica Exclamation.wav’

Time to test. Hold your ears. Wow, that’s irritating. Let’s make the object more testable by adding a count for the repeat …

Object subclass: #AlarmClock instanceVariableNames: ‘hour minute count’ classVariableNames: ‘’ poolDictionaries: ‘’

… initialize it to 30 …

setHour: hourInteger minute: minuteInteger hour := hourInteger. minute := minuteInteger. count = 30

… and use it …

playSound count timesRepeat: [self woof]

Now a special setter for the test to use …

countForTest: anInteger count := anInteger

And use it in the test. We’ll just woof three times:

testReturn clock := AlarmClock hour: Time now hours minute: Time now minutes. clock countForTest: 3. clock loop. self should: [true] “got here”

Hah! Works like a charm, and ten times less irritating.

Let’s look back at the sound method a moment:

sound ^Sound fromFile: ‘C:/windows/media/Musica Exclamation.wav’

We could break out the file name as a separate method, even make it an initializable variable. I had intended to do that, but I don’t think it’d add much in the way of clarity. Maybe if we get a story that calls for variable sounds in the clock.

I notice one more thing. The sound is being initialized from the file every time it is used. I’m tempted to cache it. But since the program runs, and it only creates the sounds when the alarm goes off, the optimization isn’t worth much. I’d rather stop here.

We’ve evolved the clock a long way. We went from this one blob of code …

[[Processor sleep: 15000. Time now hours = 3] whileFalse: []. 30 timesRepeat: [(Sound fromFile: ‘C:/windows/media/Musica Exclamation.wav’) woofAndWait.] ] fork

… into an object with nine methods. And we have tests! For an object this small, maybe overkill, but maybe not. My next trip to Sardinia isn’t for a year yet, and in that time I might forget how it works. This one, I can understand!

And everything came about from the first two of the rules, keeping the tests running, and expressing everything that needs it. We didn’t have any duplication to remove, and the requirement to communicate prohibits us from reducing the number of classes or methods. (Of course, if we wanted to communicate less, we could recombine some of the methods. YMMV, and the decision for every object is up to you.)

Here, for the record, is the clock operation in logical order:

run [self loop] fork

loop [self isTimeForAlarm] whileFalse: [self wait]. self playSound

isTimeForAlarm ^Time now hours = hour and: [Time now minutes >= minute]

wait Processor sleep: 15000

playSound count timesRepeat: [self woof]

woof self sound woofAndWait

sound ^Sound fromFile: ‘C:/windows/media/Musica Exclamation.wav’