Contents

General Exception Handling Facillities


The Use of General Exception Handling

To evaluate a statement or a sequence of statements under exception handling, one has to put the statement into a block that is evaluated by message on:do:. The argument following the on: is either an exception class or a set of exception classess that the handler should handle. The expression that follows the do: is the exception handler, which is a block with one formal argument. Exception handling uses this template:

<protected block>
     on: <exception class>
     do: <exception handler>

Here is our last example rewritten with general exception handling:

| dict |
dict := Dictionary new.
dict at: 'dog' put: 'Hund'.
[dict at: 'cat']
    on: Error
    do: [:exception | 
          exception return: '* unknown word *']

The element search in a collection can be written with a general exception handler in this way:

  | collection foundItem |
collection := #(#('dog' 'Hund')
                #('cat' 'Katze')
                #('snake' 'Schlange')).
foundItem := 
   [collection detect: [:item | item first = 'bird']
   ]
     on: Error
     do: [:exception |
	     Transcript show: exception class name;
	          show: ' was thrown for this reason: ''';
	          show: exception messageText; 
	          show: ''''; cr.
	     exception return: nil
         ].
foundItem 

When sent to an exception, the message return: defines the result value of the protected block and terminates its evaluation.

The Rules of General Exception Handling

The use of exceptions has two aspects:

Exceptions are handled on top of the call stack. This property of exception handling allows for program continuation with a suitably choosen value that replaces the result of the failed expression. This handling policy is called resumption; for resumption, the handler sends one of the messages resume or resume: to the signalled exception.

When program continuation in the most recently called method is not desired, it is necessary to continue program execution in an earlier called method. This is possible, but requires a cleanup procedure that is called stack unwinding. Stack unwinding drops the activation records of all methods that are abandoned. You can use the methods ensure: and ifCurtailed: to add your own code to that clean up procedure.

The Hierarchy of the Exception Classes

Exceptions are subclasses of class Exception. The class hierarchy is used for exception handling: A handler for an exception class handles also all exceptions that are subclasses of the exception class.

The complete exception hierarchy in a basic Squeak 3.7 image:

Exception
  Abort
  Error
    ArithmeticError
      FloatingPointException
      ZeroDivide
    AttemptToWriteReadOnlyGlobal
    BlockCannotReturn
    CRCError
    EndOfStream
    FTPConnectionException
    FileStreamException
      CannotDeleteFileException
      FileDoesNotExistException
      FileExistsException
    InvalidDirectoryError
    InvalidSocketStatusException
    MessageNotUnderstood
    MyResumableTestError
    MyTestError
    NetworkError
      ConnectionClosed)
      ConnectionRefused
      ConnectionTimedOut
      NameLookupFailure
      NoNetworkError
    NonBooleanReceiver
    ProtocolClientError
      LoginFailedException
      POP3LoginError
      TelnetProtocolError
    SmaCCParserError
  Halt
    AssertionFailure
    BreakPoint
  IllegalResumeAttempt
  Notification
    ExceptionAboutToReturn
    InMidstOfFileinNotification
    MyTestNotification
    OutOfScopeNotification
    ParserRemovedUnusedTemps
    PickAFileToWriteNotification
    ProgressNotification
    ProgressTargetRequestNotification
    ProjectEntryNotification
    ProjectPasswordNotification
    ProjectViewOpenNotification
    RequestAlternateSyntaxSetting
    SyntaxErrorNotification
    Warning
      Deprecation
  ProgressInitiationException
  TestFailure
    ResumableTestFailure
  UnhandledError

The exception hierarchy was changed frequently and further changes are quite possible.

While it is possible to provide an exception handler for Error, it is often better to provide specific handlers for more special exceptions. Most collection errors are signalled as instances of Error. Your handler for Error will therefore always catch errors like 'subscript out of bounds', 'key not found' and others. (The use of more specific exception classes for collection errors would be advantageous.)

A handler for Error handles a lot of specialized exceptions, a handler for FileStreamException handles also the exceptions CannotDeleteFileException, FileDoesNotExistException and FileExistsException.

Example:

[12 / (4 - 4)]
    on: ZeroDivide
    do: [:exception |
         Transcript show: 'division by Zero'; cr.
	   exception resume: Float nan]. 

exception class returns the class of the exception. This is useful in handlers for very general exceptions like Error.

Example: Attempt to open a file that does not exist

In the following example, the file name 'hello.txt' should be replaced with a name that is not the name of a file in the working directory. The attempt to open a non-existing file will fail:

  | file |
  file := FileStream oldFileNamed: 'hello.txt'

You see a menu that offers you three different activities. This is the default action of FileDoesNotExistException. This default action is coded in the instance method defaultAction. This default action is executed when we do not handle the exception. To suppress the default action, we have to handle the exception:

  | file |

 [file := FileStream oldFileNamed: 'hello.txt'.]
    on: FileDoesNotExistException
    do:
      [:ex | PopUpMenu inform:
               'This file does not exist'.
      ].
 file.

Detection of an existing file:

The existence of a file raises the exception FileExistsException when an attempt is made to create this file.

Here, the exception is handled; the handler displays a notification and terminates.

  | file |
 [file := FileStream newFileNamed: 'SQUEAK.ini'.]
    on: FileExistsException
    do:
      [:ex | PopUpMenu inform:
               'This file exists'.
      ].
 file.

It is also possible to handle the exception FileExistsException with a handler for FileStreamException:

  | file |
  [file := FileStream newFileNamed: 'SQUEAK.ini'.]
     on: FileStreamException
     do:
       [:ex | PopUpMenu inform:
	     'This file exists'.
       ].
  file.

When we use a handler for FileStreamException, it is a good idea to check the class of the exception:

  | file |
  [file := FileStream newFileNamed: 'SQUEAK.ini'.]
     on: FileStreamException
     do:
       [:ex | 
        ex class = FileExistsException
          ifTrue: [PopUpMenu inform:
                        'This file exists'].
       ].
  file.

When a file exists, it is often desirable to ask the user for a different file name. A handler that does this, has to retry the protected statement with the new name:

  | file fileName |
  fileName := 'SQUEAK.ini'.
  [file := FileStream newFileNamed: fileName.]
     on: FileExistsException
     do:
       [:ex | 
         fileName := FillInTheBlank
                       request: 'Enter a different file name, please:'.
         ex retry].
  file.

A more sophisticated handler displays the name of the existing file. That name is kept in the FileExistException and can be accessed with the method fileName:

  | file fileName |
  fileName := 'SQUEAK.ini'.
  [file := FileStream newFileNamed: fileName.]
     on: FileExistsException
     do:
       [:ex | 
        fileName := 
           FillInTheBlank
              request: 'This file name exists. Enter a different file name, please:'
	      initialAnswer: ex fileName.
        ex retry].
  file.

The user may choose to cancel the prompter. When this happens, the prompter returns an empty string. As an empty string is not a useable file name, we should check the user's answer. In the following example, the protected block is evaluated again when the user entered an useable filename. Otherwise, the error handler signals the exception Abort.

  | file fileName |
  fileName := 'SQUEAK.ini'.
  [[file := FileStream newFileNamed: fileName.]
     on: FileExistsException
     do:
       [:ex | 
        fileName :=
           FillInTheBlank
             request: 'This file name exists. Enter a different file name, please:'
	     initialAnswer: ex fileName.
        fileName ~= ''
          ifTrue: [ex retry]
          ifFalse: [Abort new signal]
       ]
   ]
    on: Abort
    do: [PopUpMenu inform: 'Your activity was aborted'.
         file := nil
        ].
  file.

The last example may give you the impression, that programming with exceptions is difficult, and that is true.

Handling of Exceptional Arithmetic Results

Handling of exceptional arithmetic results is complicated by the fact that arithmetic overflow caused by a non-zero divisor does not signal an exception. Division by zero signals the exception ZeroDivide. To handle both cases, one can write:

[result := a / b] on: ZeroDivide do: [:ex | ex return: nil].
(m isNil or: [m isInfinite])
   ifTrue: ["exceptional case"]
   ifFalse: ["finite result"]

It may be more convenient to write

[result := a / b] 
    on: ZeroDivide
    do: [:ex | 
          ex return: (a negative 
                        ifTrue: [Float infinity negated]
                        ifFalse: [Float infinity])
        ].
m isInfinite
   ifTrue: ["exceptional case"]
   ifFalse: ["finite result"]

The hierarchy of exceptions that relate to arithmetic errors is this one:

Error
  ArithmeticError
    FloatingPointException
    ZeroDivide

The exception FloatingPointException is signalled when an atempt is made to compute the square root from a negative floating value. (Note that exception handling is needed here only because the standard image does not offer support for complex numbers.)

Other arithmetic operations signal the exception Error. You have then to look at the message text to find out what happened.

The functions arcSin and arcCos use the message text 'Value out of range' to inform about an unsuitable argument value:

[1.4 arcSin]
   on: Error
   do: [:ex | ex messageText = 'Value out of range'
                ifTrue: [ex return: #argumentOutOfRange].
       ]

The function sqrt uses the message text 'undefined if less than zero.' to tell you the same thing:

[-5 sqrt]
   on: Error
   do: [:ex | ex messageText = 'undefined if less than zero.'
                ifTrue: [ex return: #undefined].
       ]

This error is signalled with an instance of FloatingPointException:

[-5 sqrt]
   on: FloatingPointException
   do: [:ex | ex messageText = 'undefined if less than zero.'
                ifTrue: [ex return: #undefined].
       ]

Discussion

It is not a very convenient approach to signal arithmetic errors with instances of Error. It would be much better to have more specific exceptions. Some aditional subclasses of ArithmeticError would certainly help:

Error
  ArithmeticError
    ArgumentOutOfRangeError
    FloatingPointException
    ZeroDivide

It would then be possible to handle all arithmetic errors with a handler for ArithmeticError. (In VisualWorks for Smalltalk that exception is named .)


What we have learned:


Contents