[comp.lang.ada] Exception usage design issues

jbg@sei.cmu.edu (John Goodenough) (03/22/91)

Seeing all this discussion about the philosophy of exception usage reminded me
of a short position paper I wrote a few years ago on this point.  I'm posting
it here because it presents some views that haven't been stated yet, and
someone once told me that it had been helpful when they were designing a real
system.



                    USING EXCEPTIONS: SOME DESIGN ISSUES

			     John B. Goodenough
		       Software Engineering Institute
	      Carnegie Mellon University, Pittsburgh, PA 15213

                             September 21, 1988


1 Summary

This paper addresses Topic 9 of the Application Software Design working
group:

     Discuss the best use of exceptions in the design of management
     information systems, their benefits and problems, including scope,
     propagation, control, and usage.

Only Ada and PL/I provide explicit language support for handling exceptions.
Since exceptions are not supported explicitly by the most commonly used
programming languages,(FORTRAN, COBOL, and C) few programmers and system
designers have experience in making effective use of this language feature.
It's easy to think of exceptions as a kind of ``add-on'' feature of a
software design, i.e., a facility whose use is left to the designers of
individual modules and routines.  In fact, exceptions are used most
effectively only if the strategy guiding their use is developed in the
earliest stages of a design.  Moreover, effective use of exceptions requires
attention throughout a design.

In this paper, I will discuss some of the questions that should be considered
by information system designers when deciding how to treat exceptions in
designs.


2 Definitions

First some definitions.  An exception situation arises when an operation is
invoked and cannot be completed in a ``normal'' fashion.  From a design
viewpoint, I think it is best to view an exception situation as a situation
that occurs when an operation is invoked and certain input predicates fail to
hold.  The invoked operation detects this failure and allows the invoker to
deal with the situation appropriately.  The advantage of identifying and
using exceptions is that it allows the effect of an operation to be extended
to cover a wider range of situations, since the invoker is given the
responsibility.

What are some examples of input predicates whose failure gives rise to an
exception situation?

   - For a POP operation of a stack or queue, an input predicate is
     ``the stack or queue has at least one element.''  An exception
     situation occurs when POP is invoked and this predicate does not
     hold.

   - For a READ operation on a file, an input predicate is ``there
     exists a record to be read.''

   - For a TABLE_LOOKUP operation, an input predicate is ``the
     sought-for value exists.''

   - For MATRIX_INVERSION, an input predicate is ``an inverse exists.''

An exception condition is signaled to the invoker of an operation when the
corresponding exception situation is detected.  A handler for the condition
specifies the invoker's response to the exception situation.

Exception situations are not always errors.  They may be boundary conditions
whose significance is known primarily to the operation's invoker.  Exceptions
serve to generalize operations (making them usable in a wider variety of
situations) because:

   - The response to an exception situation is provided by the invoker
     instead of by the operation itself.

   - An arbitrary, operation-defined response to the situation can be
     replaced by an appropriate user-defined response.


3 Design Issues

My definition of an exception situation leads to natural questions that
should be addressed during the design phase.

   - First, identify the input assertions associated with an operation,
     i.e., the assumptions and preconditions that allow the operation to
     complete in a ``normal'' manner.

   - Then evaluate each assertion, using criteria such as:

        * How easy is it for the operation's invoker to check the
          assertion before calling the operation?

        * When the check fails, is a fixed response (provided by the
          operation) always acceptable?

        * An operation's behavior may be undefined if an operation is
          invoked when an input assertion is unsatisfied.  Are the
          consequences of such behavior likely to be important?

Depending on the answers to these questions, a designer can decide whether an
exception situation should be handled by signalling an exception condition.

Given a decision to handle an exception situation, there are some other
design decisions that need to be considered:

   - Should an inquiry function be provided?

   - Should a boolean function be provided to check whether the
     exception situation will occur?

An inquiry function is called after an exception is raised and provides
additional information about the nature of the situation.  Such functions are
useful when the invoker's response to an exception situation might change
depending on specific details involved in the situation.  For example, if an
operation detects ill-formed terminal input, the most helpful response may
depend on the specific characters that were typed in.  Since an exception
handler does not, in general, have access to all the information available to
the operation that signals the exception, the needed additional information
must be provided to the invoker.  There are at least two ways of doing this:

   - If the programming language permits exceptions to be raised with
     parameters, the designer needs to decide what parameters should be
     provided.

   - If no parameters can be associated with exceptions, an inquiry
     function must be provided.

Providing an inquiry function extends the usability of an abstract data type,
but it can easily violate information hiding principles, since the details of
a particular exception situation might well reflect an implementation
approach.  Nonetheless, such functions are very useful when investigating
mysterious program behavior.  If they are not provided consistently
throughout a design, it can be very difficult to extract the needed
information by using a debugging tool.  The MULTICS operating system, written
in PL/I, was careful to provide such functions for all basic operations.  In
one case when I was using MULTICS, PL/I told me that I had encountered a
device error.  I would have been unable to diagnose the reason for the
problem if I hadn't been able to call a function returning the full status
word for the device.  Of course, such a function is device dependent, and
this is the disadvantage of such functions.

Whenever it's not too costly, it's a good idea to provide a boolean function
that checks for an exception situation.  For example, if a READ operation
will raise an exception when the end of a file is encountered, a function
should also be provided that checks for the end of file.  These functions
sometimes allow more readable programs to be written.  For example, if the
programmer is primarily interested in whether a file is at its end, it is
certainly more convenient to check this directly by calling the end-of-file
function rather than by calling the READ operation just to see if it raises
an exception!  (And if it doesn't, you need to preserve the value that was
read.)  In addition, some language constructs depend on the use of predicates
rather than exceptions.  For example, a guard in an Ada select statement is a
boolean expression.  If the value of a guard depends on the state of a file
and there is no end-of-file function, the programmer must go to considerable
trouble to obtain the correct boolean value.

Of course, it's not always appropriate to define an exception for every input
predicate.  For example, a precondition for the correct operation of a binary
search function is that the table being searched is ordered.  Checking that
the table is ordered each time the search is called would completely defeat
the purpose of the algorithm!  Here is a case where no exception should be
defined.

Similarly, it is not always appropriate to provide a function to check for an
exception situation independently of invoking the operation.  For example, a
matrix inversion operation should raise an exception when the matrix has no
inverse, but since it is almost as costly to check for this situation as to
attempt to find the inverse, it's probably not sensible to provide a function
in this case.


4 Example

As a simple example of these ideas, consider a simple function that is to
calculate the sum of a set of numbers presented in some input stream.  What
preconditions could lead to exception situations, and what inquiry functions
might be provided?  For which exception situations should corresponding
boolean functions be provided?

The preconditions could be:

   - There exists a sum (i.e., the sum does not overflow; it is
     representable).

   - The input stream is not empty.

   - The input is syntactically valid.

Possible inquiry functions and their results could be:

   - When overflow occurs, the inquiry function returns the sum just
     before the exception was raised, together with the number that was
     read.

   - When any exception occurs, an inquiry function returns how many
     numbers have been read, telling the programmer how much of the
     input stream has been processed.

   - When syntactically invalid input is read, an inquiry function can
     return the invalid string that was read.

                                 REFERENCES

Issues concerning the use of exceptions, particularly in Ada, are discussed
in some detail in pages 94-127 of Ada in Practice.(Ausnit, C. N., Cohen,
N. H., Goodenough, J. B., and Eanes, R. S, Springer-Verlag, 1985.)
-- 
John B. Goodenough					Goodenough@sei.cmu.edu
Software Engineering Institute				412-268-6391