5.8 When Things Go Bad
So far we've been working with examples which work the
first time. If you didn't type them in correctly, you probably
received a flood of unintelligible complaints. You
probably ignored the complaints, and typed the example
again.
When developing your own Smalltalk code, however, these
messages are the way you find out what went wrong. Because
your objects, their methods, the error printout, and your
interactive environment are all contained within the same
Smalltalk session, you can use these error messages to debug
your code using very powerful techniques.
5.8.1 A Simple Error
First, let's take a look at a typical error. Type:
This will print out:
| 7 did not understand selector 'plus:'
<blah blah>
UndefinedObject>>#executeStatements
|
The first line is pretty simple; we sent a message to the
7 object which was not understood; not surprising since
the plus: operation should have been + . Then there are
a few lines of gobbledegook: just ignore them, they reflect
the fact that the error passed throgh GNU Smalltalk's exception
handling system. The remaining line reflect the way the
GNU Smalltalk invokes code which we type to our command prompt; it
generates a block of code which is invoked via an internal
method executeStatements defined in class Object and evaluated
like nil executeStatements (nil is an instance of UndefinedObject).
Thus, this output tells you that you directly typed a line which sent an
invalid message to the 7 object.
All the error output but the first line is actually a
stack backtrace. The most recent call is the one nearer the
top of the screen. In the next example, we will cause an
error which happens deeper within an object.
5.8.2 Nested Calls
Type the following lines:
| Smalltalk at: #x put: (Dictionary new) !
x at: 1 !
|
The error you receive will look like:
| Dictionary new: 31 "<0x33788>" error: key not found
...blah blah...
Dictionary>>#error:
[] in Dictionary>>#at:
[] in Dictionary>>#at:ifAbsent:
Dictionary(HashedCollection)>>#findIndex:ifAbsent:
Dictionary>>#at:ifAbsent:
Dictionary>>#at:
UndefinedObject(Object)>>#executeStatements
|
The error itself is pretty clear; we asked for something
within the Dictionary which wasn't there. The object
which had the error is identified as Dictionary new: 31 .
A Dictionary's default size is 31; thus, this is the object
we created with Dictionary new .
The stack backtrace shows us the inner structure of how
a Dictionary responds to the #at: message. Our hand-entered
command causes the usual entry for UndefinedObject(Object) .
Then we see a Dictionary object responding to an #at: message
(the "Dictionary>>#at:" line). This code called the object
with an #at:ifAbsent: message. All of a sudden,
Dictionary calls that strange method #findIndex:ifAbsent: ,
which evaluates two blocks, and then the error happens.
To understand this better, it is necessary to know that
a very common way to handle errors in Smalltalk is to
hand down a block of code which will be called when an error
occurs. For the Dictionary code, the at: message passes
in a block of code to the at:ifAbsent: code to be called
when at:ifAbsent: can't find the given key, and
at:ifAbsent: does the same with findIndex:ifAbsent: .
Thus, without even looking at the code for Dictionary itself, we can
guess something of the code for Dictionary's implementation:
| findIndex: key ifAbsent: errCodeBlock
...look for key...
(keyNotFound) ifTrue: [ ^(errCodeBlock value) ]
...
at: key
^self at: key ifAbsent: [^self error: 'key not found']
|
Actually, findIndex:ifAbsent: lies in class HashedCollection,
as that Dictionary(HashedCollection) in the backtrace says.
It would be nice if each entry on the stack backtrace included
source line numbers. Unfortunately, at this point GNU Smalltalk doesn't
provide this feature. Of course, you have the source code
available...
5.8.3 Looking at Objects
When you are chasing an error, it is often helpful to
examine the instance variables of your objects. While
strategic calls to printNl will no doubt help, you can look at an
object without having to write all the code yourself. The
inspect message works on any object, and dumps out the
values of each instance variable within the object.(29)
Thus:
| Smalltalk at: #x put: (Interval from: 1 to: 5) !
x inspect !
|
displays:
| An instance of Interval
start: 1
stop: 5
step: 1
contents: [
[1]: 1
[2]: 2
[3]: 3
[4]: 4
[5]: 5
]
|
We'll finish this chapter by emphasizing a technique
which has already been covered: the use of the error:
message in your own objects. As you saw in the case of Dictionary,
an object can send itself an error: message with a
descriptive string to abort execution and dump a stack backtrace.
You should plan on using this technique in your own
objects. It can be used both for explicit user-caused
errors, as well as in internal sanity checks.
|