Self doesn’t have an exception handling scheme. That is, it doesn’t have the equivalent of Smalltalk, Javascript, Java etc where you can construct a try/catch structure and exceptions can be thrown up the stack until they find a suitable handler.
This is at least in part because no one has written one – Self certainly is flexible to accommodate a Smalltalk style exceptions system. But it is also a matter of choice. Instead of exceptions, Self encourages the pattern of passing an exception handling block to methods that need it, so an openResource
method could become openResourceIfFail: [|:e| doSomthingElse: e ]
Changing this would be a reasonably serious task. Exception systems are tricky things, which can cause confusion. They introduce complexity, and I’m not convinced (at least at the moment) that their benefits outweigh the added complexity. (If you want to convince me, a good way would be to write a nice idiomatic Self exception handling framework 🙂 )
But this certainly doesn’t mean that we can’t improve how Self handles exceptional circumstances.
One area of annoyance with the current setup is the error:
message. Very similar to the old Smalltalk-80 mechanism, sending error:
to most objects results in the process in question being suspending, and a debugger opened in the morphic desktop or on the command line interface.
Try getting 9 / 0
or doing error: 'Error'
to see what I mean.
This is necessary. Despite good intentions, there will always be situations where things break. But sometimes this isn’t the error handling behaviour that we want. For example, in a testing framework we want errors in our tests to be logged, not immediately debugged.
How to improve this? I’ve uploaded to Github a patch under an experimental error-handling
branch. You can read the diff online. In summary what it does is:
defaultBehavior
implements two important methods regarding errors.The first is the method
handleError: error
, which takes as its argument an error object. Prototypes for different error objects are found inprocessErrors
.The second is the more usually called
error: errorString
method, which takes a string description of the error and creates an error object and callshandleError:
handleError:
in turn calls the errorHandler code on its process object which sends the error object to the currently selected error handler, which could be a block or an object understandingvalue:
Some default error handling objects can be found in the
errorHandlers
global. They include thedefault
handler (which opens a debugger), thesilentSuspend
handler which silently suspends the process, thesilentAbort
handler which aborts the process and thereturnNil
handler which always returns nil.A handler can be installed by calling
process this errorHandler: myNewHandler
, but this is not recommended.Instead, you should call
[ myCode ] onError: myNewHandler
which installs the handler, runs the block and then cleans up after itself by reinstalling the original handler.As well, there is a global called
raiseError
, which is the equivalent to[|:e| error: e ]
and can be used in similar circumstances:something ifTrue: raiseError
This is *not* an exception system, despite its surface similarity. It does not deal with the stack or dispatch errors differently based on error type. It is not intended to be used for anything except situations where you need to change from the default behaviour of opening a debugger. The encouraged way of handling exceptional circumstances continues to be to pass an exception handler to the method which may require it. On the plus side, it is clean, simple and doesn’t require a lot of explaining.
What do you think about this approach?
One place where an exception system would seem to be needed is in the user interface. Self uses threads extensively instead, with a watchdog thread for every worker. This system gets the job done, so there is not much pressure to add exceptions.