Language Ref Manual references: 7.1 Package Structure, 11 Exceptions
In this section... 4.3.1 Using Exceptions to Help Define an Abstraction |
Summary of Guidelines from this section |
------------------------------------------------------------------------- generic type Element is private; package Stack is function Stack_Empty return Boolean; -- Raised when POP is used on empty stack. No_Data_On_Stack : exception; procedure Pop (From_Top : out Element); procedure Push (Onto_Top : in Element); end Stack; ------------------------------------------------------------------------- |
This example shows how to convert a predefined exception to a user-defined one:
... ---------------------------------------------------------------------- procedure Pop (From_Top : out Element) is ... if Stack_Empty then raise No_Data_On_Stack; else -- Stack contains at least one element Top_Index := Top_Index - 1; From_Top := Stack(Top_Index + 1); end if; end Pop; ---------------------------------------------------------------------- ... |
Exceptions are a good mechanism for reporting such errors because they provide an alternate flow of control for dealing with errors. This allows error-handling code to be kept separate from the code for normal processing. When an exception is raised, the current operation is aborted and control is transferred directly to the appropriate exception handler.
Several of the guidelines above exist to maximize the ability of the user to
distinguish and correct different types of errors. Providing a different
exception name for each error condition makes it possible to handle each error
condition separately. Declaring new exception names, rather than raising
exceptions declared in other packages, reduces the coupling between packages
and also makes different exceptions more distinguishable. Exporting the names
of all exceptions which a unit can raise, rather than declaring them
internally to the unit, makes it possible for users of the unit to refer to
the names in exception handlers. Otherwise, the user would be able to handle
the exception only with an others
handler. Finally, using comments to document
exactly which of the exceptions declared in a package can be raised by each
subprogram or task entry making it possible for the user to know which
exception handlers are appropriate in each situation.
Because they cause an immediate transfer of control, exceptions are useful for reporting unrecoverable errors which prevent an operation from being completed, but not for reporting status or modes incidental to the completion of an operation. They should not be used to report internal errors which a unit was able to correct invisibly to the user.
To provide the user with maximum flexibility, it is a good idea to provide
interrogative functions which the user can call to determine whether an
exception would be raised if a subprogram or task entry were invoked. The
function Stack_Empty
in the above example is such a function. It indicates
whether No_Data_On_Stack
would be raised if Pop
were called. Providing such
functions makes it possible for the user to avoid triggering exceptions.
To support error recovery by its user, a unit should try to avoid changing
state during an invocation which raises an exception. If a requested operation
cannot be completely and correctly performed, then the unit should either
detect this before changing any internal state information, or should revert
back to the state at the time of the request. For example, after raising the
exception No_Data_On_Stack
, the stack package in the above example should
remain in exactly the same state it was in when Pop
was called. If it were to
partially update its internal data structures for managing the stack, then
future Push
and Pop
operations would not perform correctly. This is always
desirable, but not always possible.
User-defined exceptions should be used instead of predefined or compiler-defined exceptions because they are more descriptive and more specific to the abstraction. The predefined exceptions are very general, and can be triggered by many different situations. Compiler-defined exceptions are nonportable and have meanings which are subject to change even between successive releases of the same compiler. This introduces too much uncertainty for the creation of useful handlers.
If you are writing an abstraction, remember that the user does not know about the units you use in your implementation. That is an effect of information hiding. If any exception is raised within your abstraction, you must catch it and handle it. The user is not able to provide a reasonable handler if the original exception is allowed to propagate out. You can still convert the exception into a form intelligible to the user if your abstraction cannot effectively recover on its own.
Converting an exception means raising a user-defined exception in the handler for the original exception. This introduces a meaningful name for export to the user of the unit. Once the error situation is couched in terms of the application, it can be handled in those terms.
Do not allow an exception to propagate unhandled outside the scope of the
declaration of its name, because then only a handler for others
can catch it.
As discussed under Guideline 5.8.2, a handler for others
cannot be written to deal
with the specific error effectively.
Language Ref Manual references: 6.1 Subprogram Declarations, 6.5 Function Subprograms, 7.2 Package Specifications and Declarations, 7.3 Package Bodies, 9.5 Entries, Entry Calls, and Accept Statements, 11.1 Exception Declarations, 11.2 Exception Handlers, 11.3 Raise Statements, 11.4 Exception Handling