![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
Up to this point of the tutorial, you used the original Smalltalk-80 error signalling mechanism:
check: num [ | c | c := history at: num ifAbsent: [ ^self error: 'No such check #' ]. ^c ] |
In the above code, if a matching check number is found, the method will answer the object associated to it. If no prefix is found, Smalltalk will unwind the stack and print an error message including the message you gave and stack information.
CheckingAccount new: 31 "<0x33788>" error: No such check # ...blah blah... CheckingAccount>>#error: [] in Dictionary>>#at:ifAbsent: Dictionary(HashedCollection)>>#findIndex:ifAbsent: Dictionary>>#at:ifAbsent: [] in CheckingAccount>>#check: CheckingAccount>>#check: UndefinedObject(Object)>>#executeStatements |
Above we see the object that received the #error: message, the message
text itself, and the frames (innermost-first) running when the error was
captured by the system. In addition, the rest of the code in methods
like CheckingAccount>>#check:
was not executed.
So simple error reporting gives us most of the features we want:
However, there is a more powerful and complex error handling mechanism,
that is exception. They are like "exceptions" in other programming
languages, but are more powerful and do not always indicate error
conditions. Even though we use the term "signal" often with regard
to them, do not confuse them with the signals like SIGTERM
and
SIGINT
provided by some operating systems; they are a different
concept altogether.
Deciding to use exceptions instead of #error:
is a matter of
aesthetics, but you can use a simple rule: use exceptions only if you want
to provide callers with a way to recover sensibly from certain errors,
and then only for signalling those particular errors.
For example, if you are writing a word processor, you might provide the
user with a way to make regions of text read-only. Then, if the user
tries to edit the text, the objects that model the read-only text can
signal a ReadOnlyText
or other kind of exception, whereupon the
user interface code can stop the exception from unwinding and report
the error to the user.
When in doubt about whether exceptions would be useful, err on the side
of simplicity; use #error:
instead. It is much easier to convert an
#error: to an explicit exception than to do the opposite.
6.11.1 Creating exceptions Starting to use the mechanism 6.11.2 Raising exceptions What to do when exceptional events happen 6.11.3 Handling exceptions The other side 6.11.4 When an exception isn't handled Default actions 6.11.5 Creating new exception classes Your own exceptions 6.11.6 Hooking into the stack unwinding An alternative exception handling system 6.11.7 Handler stack unwinding caveat Differences with other languages
GNU Smalltalk provides a few exceptions, all of which are subclasses of
Exception
. Most of the ones you might want to create yourself are
in the SystemExceptions
namespace. You can browse the builtin
exceptions in the base library reference, and look at their names with
Exception printHierarchy
.
Some useful examples from the system exceptions are
SystemExceptions.InvalidValue
, whose meaning should be obvious, and
SystemExceptions.WrongMessageSent
, which we will demonstrate below.
Let's say that you change one of your classes to no longer support #new for creating new instances. However, because you use the first-class classes feature of Smalltalk, it is not so easy to find and change all sends. Now, you can do something like this:
Object subclass: Toaster [ Toaster class >> new [ ^SystemExceptions.WrongMessageSent signalOn: #new useInstead: #toast: ] Toaster class >> toast: reason [ ^super new reason: reason; yourself ] ... ] |
Admittedly, this doesn't quite fit the conditions for using exceptions. However, since the exception type is already provided, it is probably easier to use it than #error: when you are doing defensive programming of this sort.
Raising an exception is really a two-step process. First, you create
the exception object; then, you send it #signal
.
If you look through the hierarchy, you'll see many class methods
that combine these steps for convenience. For example, the class
Exception
provides #new
and #signal
, where the
latter is just ^self new signal
.
You may be tempted to provide only a signalling variant of your own exception creation methods. However, this creates the problem that your subclasses will not be able to trivially provide new instance creation methods.
Error subclass: ReadOnlyText [ ReadOnlyText class >> signalOn: aText range: anInterval [ ^self new initText: aText range: anInterval; signal ] initText: aText range: anInterval [ <category: 'private'> ... ] ] |
Here, if you ever want to subclass ReadOnlyText
and add new
information to the instance before signalling it, you'll have to use
the private method #initText:range:
.
We recommend leaving out the signalling instance-creation variant in new code, as it saves very little work and makes signalling code less clear. Use your own judgement and evaluation of the situation to determine when to include a signalling variant.
To handle an exception when it occurs in a particular block of code,
use #on:do:
like this:
^[someText add: inputChar beforeIndex: i] on: ReadOnlyText do: [:sig | sig return: nil] |
This code will put a handler for ReadOnlyText
signals on the
handler stack while the first block is executing. If such an exception
occurs, and it is not handled by any handlers closer to the point of
signalling on the stack (known as "inner handlers"), the exception object
will pass itself to the handler block given as the do:
argument.
You will almost always want to use this object to handle the exception somehow. There are six basic handler actions, all sent as messages to the exception object:
return:
#on:do:
, returning the given value.
You can also leave out the argument by sending #return
, in which case
it will be nil. If you want this handler to also handle exceptions in
whatever value you might provide, you should use #retryUsing:
with a
block instead.
retry
#retry
is a good way to implement reinvocation upon recovery,
because it does not increase the stack height. For example, this:
frobnicate: n [ ^[do some stuff with n] on: SomeError do: [:sig | sig return: (self frobnicate: n + 1)] ] |
should be replaced with retry:
frobnicate: aNumber [ | n | n := aNumber. ^[do some stuff with n] on: SomeError do: [:sig | n := 1 + n. sig retry] ] |
retryUsing:
#retry
, except that it effectively replaces the original
block with the one given as an argument.
pass
#pass
instead of #signal
. This is just like rethrowing
a caught exception in other languages.
resume:
#signal
send.
Code that sends #signal
to resumable exceptions can use this
value, or ignore it, and continue executing. You can also leave out
the argument, in which case the #signal
send will answer nil.
Exceptions that want to be resumable must register this capability by
answering true
from the #isResumable
method, which is
checked on every #resume:
send.
outer
#pass
, but if an outer handler uses #resume:
,
this handler block will be resumed (and #outer
will answer the
argument given to #resume:
) rather than the piece of code that
sent #signal
in the first place.
None of these methods return to the invoking handler block except for
#outer
, and that only in certain cases described for it above.
Exceptions provide several more features; see the methods on the classes
Signal
and Exception
for the various things you can do
with them. Fortunately, the above methods can do what you want in almost
all cases.
If you don't use one of these methods or another exception feature to exit
your handler, Smalltalk will assume that you meant to sig return:
whatever you answer from your handler block. We don't recommend relying
on this; you should use an explicit sig return:
instead.
A quick shortcut to handling multiple exception types is the
ExceptionSet
, which allows you to have a single handler for the
exceptions of a union of classes:
^[do some stuff with n] on: SomeError, ReadOnlyError do: [:sig | ...] |
In this code, any SomeError
or ReadOnlyError
signals will
be handled by the given handler block.
Every exception chooses one of the above handler actions by default when
no handler is found, or they all use #pass
. This is invoked by
sending #defaultAction
to the class.
One example of a default action is presented above as part of the example
of #error:
usage; that default action prints a message, backtrace,
and unwinds the stack all the way.
The easiest way to choose a default action for your own exception classes is to subclass from an exception class that already chose the right one, as explained in the next section. For example, some exceptions, such as warnings, resume by default, and thus should be treated as if they will almost always resume.
Selecting by superclass is by no means a requirement. Specializing your
Error
subclass to be resumable, or even to resume by default,
is perfectly acceptable when it makes sense for your design.
If you want code to be able to handle your signalled exceptions, you will
probably want to provide a way to pick those kinds out automatically.
The easiest way to do this is to subclass Exception
.
First, you should choose an exception class to specialize. Error
is the best choice for non-resumable exceptions, and Notification
or its subclass Warning
is best for exceptions that should resume
with nil
by default.
Exceptions are just normal objects; include whatever information you think
would be useful to handlers. Note that there are two textual description
fields, a description and a message text. The description,
if provided, should be a more-or-less constant string answered from a
override method on #description
, meant to describe all instances
of your exception class. The message text is meant to be provided at
the point of signalling, and should be used for any extra information
that code might want to provide. Your signalling code can provide the
messageText
by using #signal:
instead of #signal
.
This is yet another reason why signalling variants of instance creation
messages can be more trouble than they're worth.
More often useful than even #on:do:
is #ensure:
, which
guarantees that some code is executed when the stack unwinds, whether
because of normal execution or because of a signalled exception.
Here is an example of use of #ensure:
and a situation where the
stack can unwind even without a signal:
Object subclass: ExecuteWithBreak [ | breakBlock | break: anObject [ breakBlock value: anObject ] valueWithBreak: aBlock [ "Sets up breakBlock before entering the block, and passes self to the block." | oldBreakBlock | oldBreakBlock := breakBlock. ^[breakBlock := [:arg | ^arg]. aBlock value] ensure: [breakBlock := oldBreakBlock] ] ] |
This class provides a way to stop the execution of a block without
exiting the whole method as using ^
inside a block would do.
The use of #ensure:
guarantees (hence the name "ensure") that even
if breakBlock
is invoked or an error is handled by unwinding,
the old "break block" will be restored.
The definition of breakBlock
is extremely simply; it is an
example of the general unwinding feature of blocks, that you have
probably already used:
(history includesKey: num) ifTrue: [ ^self error: 'Duplicate check number' ]. |
You have probably been using #ensure:
without knowing. For example,
File>>#withReadStreamDo:
uses it to ensure that the file is
closed when leaving the block.
One important difference between Smalltalk and other languages is
that when a handler is invoked, the stack is not unwound.
The Smalltalk exception system is designed this way because it's rare
to write code that could break because of this difference, and the
#resume:
feature doesn't make sense if the stack is unwound.
It is easy enough to unwind a stack later, and is not so easy to wind
it again if done too early.
For almost all applications, this will not matter, but it technically
changes the semantics significantly so should be kept in mind. One
important case in which it might matter is when using #ensure:
blocks and exception handlers. For comparison, this Smalltalk
code:
| n | n := 42. [[self error: 'error'] ensure: [n := 24]] on: Error do: [:sig | n printNl. sig return]. n printNl. |
will put "42" followed by "24" on the transcript, because the n :=
24
will not be executed until sig return
is invoked, unwinding
the stack. Similar Java code acts differently:
int n = 42; try { try {throw new Exception ("42");} finally {n = 24;} } catch (Exception e) { System.out.println (n); } System.out.println (n); |
printing "24" twice, because the stack unwinds before executing the catch block.
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |