Since the dark ages of yesteryear Squeak has had a very interesting button in its Debugger – “create”. Today we’re going to teach it a new trick.
Suppose you write a test, showing how
Foo
s
bar
. Your very first test case is simply
FooTest >> testFoosCanBar
Foo new bar.
You try save the method. Of course,
Foo
doesn’t exist yet, so Squeak prompts you to define the new class:
Image may be NSFW.
Clik here to view.
OK, you can now save the method. You run the test. Of course, it fails. So you click on the failing method, and see that you haven’t yet taught a
Foo
how to
bar
.
Image may be NSFW.
Clik here to view.
Ah, there’s a “Create” button. What’s it do? Ah, I see. It asks us which object in
Foo
’s hierarchy ought to understand this message. Let’s keep it at
Foo
for now. OK, and now we see a stub implementation where we can enter our real code.
Image may be NSFW.
Clik here to view.
So far so good. Now suppose we need
Foo
to supply a template method for its subclasses to impleement. In C# we’d mark the method as
abstract
. In Smalltalk we mark the method:
Foo >> templateMethod: anObject
self subclassResponsibility
Now we need to implement this behaviour in a subclass,
Bar
. We write a trivial test, run the test, see it fail. If we click on the failing test we see a decent error message…
Image may be NSFW.
Clik here to view.
… but we have to leave the debugger to fix this. Can’t have that! Alright, let’s see what we can do. First we need to distinguish this kind of error from other kinds of errors:
Error subclass: #SubclassResponsibility
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'Exceptions-Kernel'.
Then we must actually throw the error:
subclassResponsibility
"This message sets up a framework for the behavior of the class' subclasses.
Announce that the subclass should have implemented this message."
SubclassResponsibility
signal: ('My {1} subclass should have overridden {2}'
format: {self className. thisContext sender selector}).
Next we need to teach the
Debugger
how to respond to this special error:
Debugger >> buildNotifierWith: builder label: label message: messageString
"Snip a whole pile of stuff for brevity's sake"
"This stanza drives the first bit of JIT coding we saw"
(self interruptedContext selector == #doesNotUnderstand:) ifTrue: [
quads := quads copyWith:
{ 'Create'. #createMethod. #magenta. 'create the missing method' }
].
"And now we recognise when we're dealing with an override method."
(self interruptedContext selector == #subclassResponsibility) ifTrue: [
quads := quads copyWith:
{ 'Create'. #createOverridingMethod. #magenta. 'create the missing overriding method' }
"Snip a whole bunch more"
Debugger >> createOverridingMethod
"Should only be called when this Debugger was created in response to a
SubclassResponsibility exception. Create a stub for the method that was
missing and proceed into it."
| err msg |
err := self contextStackTop exceptionMessage.
msg := Message selector: err selector arguments: err calledArguments.
self implement: msg inClass: err offendingClass inCategory: msg selectorCategory.
Here we see the other side of the comment in
Object >> #error:
. A
ContextPart
stores the variables relevant to that frame, and in a parameterless method, the first temp will be the first local variable, containing our exception. And we can simply reuse the (mildly extended) existing mechanisms for creating a stub method.
Image may be NSFW.
Clik here to view.
Hey, presto! Another reason not to leave the debugger!