This section addresses the issues of how and when to avoid raising exceptions, how and where to handle them, and whether to propagate them. Information on how to use exceptions as part of the interface to a unit include what exceptions to declare and raise and under what conditions to raise them. Other issues are addressed in Guidelines 4.3 and 7.5.
Language Ref Manual references: 11 Exceptions
In this section... 5.8.1 Handling Versus Avoiding Exceptions 5.8.2 Handlers for others 5.8.3 Propagation 5.8.4 Localizing the Cause of an Exception |
Summary of Guidelines from this section |
NULL
when traversing a linked list of records connected by pointers. Also,
test an integer for zero before dividing by it, and call an interrogative
function Stack_Is_Empty
before invoking the POP
procedure of a stack package.
Such tests are appropriate when they can be performed easily and efficiently,
as a natural part of the algorithm being implemented.
However, error detection in advance is not always so simple. There are cases
where such a test is too expensive or too unreliable. In such cases, it is
better to attempt the operation within the scope of an exception handler so
that the exception is handled if it is raised. For example, in the case of a
linked list implementation of a list, it is very inefficient to call a
function Entry_Exists
before each call to the procedure Modify_Entry
simply to
avoid raising the exception Entry_Not_Found
. It takes as much time to search
the list to avoid the exception as it takes to search the list to perform the
update. Similarly, it is much easier to attempt a division by a real number
within the scope of an exception handler to handle numeric overflow than to
test in advance whether the dividend is too large or the divisor too small for
the quotient to be representable on the machine.
In concurrent situations, tests done in advance can also be unreliable. For example, if you want to modify an existing file on a multi-user system, it is safer to attempt to do so within the scope of an exception handler than to test in advance whether the file exists, whether it is protected, whether there is room in the file system for the file to be enlarged, etc. Even if you tested for all possible errors conditions, there is no guarantee that nothing would change after the test and before the modification operation. You still need the exception handlers, so the advance testing serves no purpose.
Whenever such a case does not apply, normal and predictable events should be handled by the code without the abnormal transfer of control represented by an exception. When fault handling and only fault handling code is included in exception handlers, the separation makes the code easier to read. The reader can skip all the exception handlers and still understand the normal flow of control of the code. For this reason, exceptions should never be raised and handled within the same unit, as a form of a goto statement to exit from a loop, if, case, or block statement.
Language Ref Manual references: 5.9 Goto Statements, 11.2 Exception Handlers, 11.4 Exception Handling, 11.4.1 Exceptions Raised During the Execution of Statements, 11.4.2 Exceptions Raised During the Elaboration of Declarations, 11.5 Exceptions Raised During Task Communication
others
others
.
others
in suitable frames to protect against
unexpected exceptions being propagated without bound, especially in safety
critical systems.
others
only to catch exceptions you cannot enumerate explicitly,
preferably only to flag a potential abort.
others
during development.
others
allows you to follow the other guidelines in
this section. It affords a place to catch and convert truly unexpected
exceptions that were not caught by the explicit handlers. While it may be
possible to provide "fire walls" against unexpected exceptions being
propagated without providing handlers in every block, you can convert the
unexpected exceptions as soon as they arise. The others
handler cannot
discriminate between different exceptions, and, as a result, any such handler
must treat the exception as a disaster. Even such a disaster can still be
converted into a user-defined exception at that point. Since a handler for
others
catches any exception not otherwise handled explicitly, one placed in
the frame of a task or of the main subprogram affords the opportunity to
perform final clean-up and to shut down cleanly.
Programming a handler for others
requires caution because it cannot
discriminate either which exception was actually raised or precisely where it
was raised. Thus, the handler cannot make any assumptions about what can be or
even what needs to be "fixed."
The use of handlers for others
during development, when exception occurrences
can be expected to be frequent, can hinder debugging. It is much more
informative to the developer to see a traceback with the actual exception
listed than the converted exception. Furthermore, many tracebacks do not list
the point where the original exception was raised if it was caught by a
handler.
Language Ref Manual references: 11.2 Exception Handlers, 11.4 Exception Handling
Some existing advice calls for catching and propagating any exception to the calling unit. This advice can stop a program. You should catch the exception and propagate it, or a substitute, only if your handler is at the wrong abstraction level to effect recovery. Effecting recovery can be difficult, but the alternative is a program that does not meet its specification.
Making an explicit request for termination implies that your code is in control of the situation and has determined that to be the only safe course of action. Being in control affords opportunities to shut down in a controlled manner (clean up loose ends, close files, release surfaces to manual control, sound alarms), and implies that all available programmed attempts at recovery have been made.
Language Ref Manual references: 11.2 Exception Handlers, 11.4 Exception Handling
User-defined exceptions can also be difficult to localize. Associating handlers with small blocks of code helps to narrow the possibilities, making it easier to program recovery actions. The placement of handlers in small blocks within a subprogram or task body also allows resumption of the subprogram or task after the recovery actions. If you do not handle exceptions within blocks, the only action available to the handlers is to shut down the task or subprogram as prescribed in Guideline 5.8.3.
Language Ref Manual references: 5.6 Block Statements, 11.2 Exception Handlers, 11.4 Exception Handling