Exception Hierarchies

Alan Lovejoy sourcery at pacbell.net
Tue Aug 17 03:01:54 UTC 1999

> ** Original Sender: Allen Wirfs-Brock <Allen_Wirfs-Brock at Instantiations.com>
> I designed the VSE exception handling system and specified the ANSI
> Smalltalk exception system so I suspect that some of my experience may be
> relevant to this discussion. I have just a few points that may be helpful:
> "NoHandlerSignal" vs #defaultAction
> One of the most significant differences between the VisualWorks exception
> system and the VSE/ANSI exception systems is what occurs if no explicit
> handler exists. The VSE/ANSI systems does not really have the concept of an
> "unhandled exception". Every exception class implements or inherits a
> #defaultAction method that is used to handle the exception if no explicit
> handler is found. Most exceptions are defined to do something reasonable as
> their #defaultAction. Error and its subclasses should open an error
> notifier. Notification does nothing but resume execution. Because of the
> default actions there is really little need for a "NoHandlerException". If
> you really wanted one I would implement it in the #defaultAction method of
> Exception. Perhaps something like:
> 	defaultAction
> 		"nobody handled the exception, turn it into a NoHandlerException"
> 		self resignalAs: (NoHandlerException new originalException: self)
> In this case NoHandlerException darn well better over-ride #defaultAction
> to do something different then the above.
> Its not clear to me why you might want to introduce NoHandlerExceptions
> into this system. The first thing that comes to mind would be for some sort
> of debugging support where you wanted to perform special handling for Error
> exceptions. For this type of thing I think a better solution might be to
> some sort of pluggable mechanism for changing the default behavior.

There are two problems with the "defaultAction" mechanism. One is that it is not 
context sensitive. The other I will discuss later.

The NoHandlerException makes it possible to provide a "defaultAction" that is 
context sensitive, without having to worry about the multithreading issues that 
would overwhelm a changeable, but global, "defaultAction."

When an exception is handled at a low level of the system, the semantic information
concerning what is really happening at a high level is missing.  Higher-level handlers
can usually make more intelligent decisions about how to handle an exception.  So
a low-level system method may want to handle an exception only if there is no higher
level handler (which cannot be statically determined, since there may always be any
number of call paths to a low level method).

> Inadvertently catching default actions.
> When an exception hierarchy is filled out with default actions, the
> unintentional catching of exceptions can introduce subtle bugs. Here is a
> real life example:

This is not really a problem with "defaultActions," per se.  It arises any
time that there is more than one handler for the same exception in the call
chain.  Having a "defaultAction" merely guarantees that there is at least
one handler in the call chain, thus leading to the problem you describe
potentially arising whenever any handler for the exception is defined.
The NoHandlerException is one useful mechanism for dealing with

> You are implementing FileStreams using native OS file operations. In the
> native interfacing level you define a subclass of Error, NativeIOError,
> that is signaled whenever a system call fails. One of the failure
> conditions is end-of-file so the defaultAction for NativeIOError is defined
> to resignal a StreamAtEnd exception in that particular case. A database
> subsystem then used FileStreams and explicitly deals with StreamAtEnd in a
> manner that is transparent to the clients of the database subsystem.
> Somebody writes some code that uses the database subsystem and it works
> fine. Then, they decide they want to control how errors are reported to
> users so they wrap the database client code like this: [perform a
> transaction] on: Error do:. As soon as they do this, the system begins to
> fail with mysterious NativeIOErrors. Why? Because their Error handler was
> catching the NativeIOError and thus preventing its #defaultAction from
> taking place and hence the NativeIOError never got converted into a
> StreamAtEnd for the database subsystem to deal with.

The preceding is an excellent case against having a (potentially different) 
"defaultAction" for each type of exception.  In VisualWorks, which has
only three global default actions (one for halts, one for user interrupts,
and one for everthing else), it is easy to change the way errors are reported
to users--without breaking things as described above.  The fact that
user interrupts and halts shouldn't be reported to (end) users at all, and that
there is one place where all other error reporting is accomplished, 
is a big win, IMHO.  And of course, the NoHandlerSignal (NoHandlerException)
is part of the reason why.

Perhaps exceptions should be partioned into two groups: those where the
default action is to report the exception and suspend the thread, and those where 
the default action is to handle the exception "silently," without stopping
the thread.

> There are several lessons to learn from the above. First, Error is intended
> for "end user" programmer use. It's perfectly reasonable for somebody to
> code [...] on: Error do:. If you are implementing "system" code, don't
> depend upon defaultActions that might be over-ridden by an user's  handler
> for Error. If you want to use this implementation style, create your own
> exception hierarchy subclassing Exception instead of Error.

I typically categorize all errors as follows, and rarely need to define or use any
other sort of error-related exceptions:

	SystemError (the system could not do what was requested;
		e.g., "message not understood", "insufficient memory to allocate object")
	SystemDomainError (system domain object constraints have been violated; 
		e..g, "zero divide", "symbols are not mutable")
	ApplicationDomainError(application domain constraints have been violated; 
		e.g., "once set, primary key cannot be changed")
	ApplicationError (business rules have been violated; e.g., "Sale cannot be 
		finalized until credit approval is granted")
	UserError (the user has done something not allowed; e.g., "You have to create an 
		open edition before you can make changes")

And I don't handle exceptions at all when all I want to do is make sure that
some action takes place.  I use #ifCurtailed: or #ensure:, instead.

> Don't define handlers for Exception or Notification
> You almost never want to define an explicit handler for either of these
> Exceptions. They exist primarily to provide default behaviors within the
> exception implementation hierarchy. Here's an example of what can go wrong:
> Somebody was building a QA test harness for Smalltalk programs. They wanted
> to log any "unexpected" exceptions that occurred during a test. So they
> wrapped every test with code something like:
> 	[aTest value] on: Exception do: [:e| e printOn: Transcript]
> Some tests that worked before stopped working because default actions were
> no longer executing. This is probably one of the few cases where handling
> Exception is actually a reasonable thing to do. To make it work correctly
> they should have coded it as:
> 		[aTest value] on: Exception do: [:e| e printOn: Transcript. e pass]

Well, had they instead used NoHandlerException, thusly:

	[aTest value] 
		on: NoHandlerException
		do: [:ex | 
			unhandledExceptionLog recordException: ex.
			ex pass]

Then as long as there were handlers for the exceptions, they would be both
unseen and undisturbed. And if exceptions that don't require a handler
don't raise NoHandlerException when there is no handler, then they
won't be a problem either.  And if you really need to catch ALL exceptions
(perhaps for translation from one exception system to another), you can still 
use Exception (ooh, "Danger, Will Robinson...").

[Much other agreeable stuff snipped]


More information about the Squeak-dev mailing list