Navigation:  Programming Cookbook > Exception Handling >

Guidelines for Use

Previous pageReturn to chapter overviewNext page

As discussed in the introduction, exceptions have a number of advantages over alternative methods of reporting exceptional conditions (e.g. returning a distinguished value), and where at all possible should be used in preference to those alternative methods. The rule-of-thumb is: Use exceptions for exceptional conditions only. If a particular condition is expected to occur in normal execution, then it should probably not result in a raised exception.

In cases where one expects an error condition to arise frequently (e.g. 'not found' type errors), this is best handled by allowing the user to specify a block to be evaluated when that condition arises. The usual pattern is to then provide a simpler wrapper method that invokes the other, supplying a block that raises an exception (e.g. see Dictionary>>at:ifAbsent: and Dictionary>>at:).

As discussed in the implementation overview, raising and catching exceptions carries certain overheads:

The fixed overhead of setting up an exception handling context with #on:do: is not that great, though one should avoid it in tight loops (perhaps by establishing the handler outside the loop).
The variable overhead incurred when an exception actually occurs, and is handled, can be quite large.

Overhead should not prove to be an issue if exceptions are used appropriately, because, by definition, they are intended to represent exceptional conditions which happen infrequently, and which are, therefore, not part of mainstream execution.

Try not to design deeply nested hierarchies of exceptions, since the additional complexity resulting is rarely necessary. One cannot necessarily predict the exceptions that "user" code will want to catch and handle together, and carefully constructed taxonomies may prove inappropriate. Instead add specific exception classes that capture all the necessary information, and group these under a superclass only where there is a clearly identifiable need to catch and handle that group of exceptions with single handlers.

The exceptions raised by a method are an important part of its specification, since without this information one cannot handle specific errors. At the very least the exceptions that could be raised as the result of the invocation of a public method should be documented.

Resist the temptation to catch quite abstract classes of exception to save on programming, for example Error. In order that exception-handling code can usefully recover from specific and expected exceptional conditions, one should catch the most specific class of exception that enables one to recover from the exception. In particular never catch the abstract class Exception. If one catches broad classes of exception, then one may end up suppressing exceptional conditions that one hasn't actually handled. This can make systems difficult to debug in development, and can lead to undetected data corruption in production systems.

One may be tempted to code handler blocks that examine some detail of a caught exception to determine whether it should really have been caught in the first place. Indeed this is quite common practice in one existing Smalltalk implementation, which even compares against the message text in the exception. However, it is bad practice, defeating the object of a flexible class based exception handling mechanism. In this case the correct thing to do is to subclass the generic class of error, and then raise and catch the more specific subclass.

Groups of unrelated exceptions can be caught by using ExceptionSets, so they do not need to be related by hiearchy. One can even design one's own exception catchers, by implementing the ANSI protocol, <exceptionSelector>.

Multiple classes (or sets) of exceptions can be caught from the same try block and directed to separate handlers by making use of the BlockClosure>>#on:do:[on:do]+ series of messages. Listing multiple exception classes and handlers is the corect thing to do instead of specifying an ExceptionSet and then switching on the type of the exception in the handler block.

Raising and handling exceptions disrupts the normal flow of control. It also separates the point of detection of an exceptional condition, from the point where that exception is dealt with. These, useful, features can make it more difficult to determine the behaviour of a system by browsing the source code. Resuming after an exception is non-obvious (and also subject to further failures). Retrying after an exception is particularly esoteric technique and should be used sparingly.

In general Errors should not be resumable, since they are supposed to represent fatal conditions, which if resumed, would almost certainly result in further exceptions. In specific circumstances where recovery is possible, but the exception is in other respects and error condition, one may be able to make a case for permitting resumption.

In summary:

Exceptions should be raised in preference to returning distinguished error values which must be tested, but ...
... use exceptions for conditions which are truly exceptional, not for frequently expected errors.
Raise exceptions as soon as possible after detecting an exceptional condition so that all pertinent information relating to the condition can be included.
Catch exceptions at a point where it is possible to handle them effectively.
Do document which exceptions public methods might raise.
Avoid establishing exception handlers using #on:do: inside tight loops, or other frequently executed code, when concerned about performance.
Don't over engineer exception hierarchies - keep them shallow and simple.
Never catch Exception.
Think twice before catching Error, it is rarely appropriate outside development tools.
When catching Error is unavoidable, provide detailed logs so that unexpected conditions are not missed.
Examining the detail of an exception inside a handler block to see whether it can be handled is an indication that too generic a class of exception was raised and caught, and that further specialization may be necessary.
Don't switch on the type of exception in a handler block, instead use multiple catcher and handler pairs established with the BlockClosure>>#on:do:[on:do:]+ series of messages.
Avoid #retry and (especially) #retryUsing:.
Avoid defining resumable Errors.