Description
"Try..Recover" provides structured exception handling. Exception
handling in other languages is sometimes called "Try..Throw..Catch". Although more involved than other control structures Try..Recover can simplify
application design. It is important to understand Try..Recover because many object methods use this structure and may expect applications calling these methods to respond appropriately.
An "exception" or "escape" is a system response to some problem that
occurs. For example getting a word from the user when a number
was explicitly requested generates an exception. Discovering that a
file contains no more information when more information was
expected generates an exception. Attempting to control an axis which
is currently being controlled by something else generates an exception. In a sense exceptions are problems which actively seek out their
solutions. Using Try..Recover involves making arrangements so that
exceptions will find their solutions.
The syntax of "Try..Recover" is:
Try
<statement>
Recover
<statement>;
In a manner similar to the "if..else" construct a semicolon should not be present
before the Recover keyword. Also like the if..else construct multiple statements
need to be enclosed in a begin..end compound statement.
During execution the Try keyword is encountered and the program
constructs a kind of "safety net" with one end pegged to the try keyword
and the other end of the net ?pegged? to the recover keyword. The statement (or multiple statements if within a begin..end) between the try and recover is then spanned by
this safety net. Execution of the try statement commences. If at any place in this group of statements an exception is generated the execution of the statements stops at that point
and execution "drops out" onto the safety net. Like the circus performing trapeze artist who misses the bar, lands on the safety net and
then rolls to the end of the net to get off, program execution is
captured by the net and "rolls" to the recover block end of the net
and continues execution with the instructions in the recover block. If
no problem was encountered during the try statements there is no
need for recovery and the statements in the recover block are ignored.
The following example would illustrate how a Try..Recover block
might be used:
Try
SpeedEditor.Read(aSpeed)
Recover
Prompter.Writeln(‘Speed value not a number’);
In this example the Read method is expecting to get from the editor
information compatible with the type of the Read parameter. If the
user types in a name instead of a number an exception is generated by
the Read method and execution moves to the recover block where an
error is displayed.
If there are several statements and different types of exceptions could
occur how are the exceptions distinguished? Exception types are
identified by unique numbers. When responding to an exception in a
recover block it is good practice to look at the number of the exception to gain some understanding as to what problem occurred and
what a suitable response might be. The exception number or "escape
code" is found with the global function EscapeCode. Typical recover
blocks have a set of tests that check if it is this problem or that problem. If the problem is beyond the capabilities of the recover block the
block ends by issuing an exception itself in the hopes that the procedure or function that called it may know how to handle the problem.
Exceptions "climb up" the procedure invocation ladder looking for a
recover block that is willing to handle the problem. This recover
block exception is created with the statement Escape(EscapeCode).
Escape is a procedure which causes an exception to be generated. The
value of the escape is EscapeCode, the currently unhandled error. If
no recover block handles the problem the exception eventually
makes its way back to Snap2Motion which has a default recover block that
displays the escape code on the screen.
An example of a more advanced try recover block might be the
following:
try
begin
DestinationEditor.Read(aDestination);
XAxis.BeginMoveTo(aDestination);
end
recover
begin
if EscapeCode=ReadEscapeCode then
Prompter.Writeln(‘Please provide a longint’)
else if EscapeCode=MotionOverrunEscapeCode then
Prompter.Writeln(‘Impossible move requested’)
else
Escape(EscapeCode);
end;
In this example there are two statements in the try block. The first
statement, the editor read, has the capacity to escape if the user
provides a type which is not suitable. The second statement, the x
axis movement, has the capacity to escape if while in motion a destination is given to the axis which it cannot perform. The recover
block checks for each of these conditions using the symbolic names
for escape codes. These names can be found in the help system by
looking for the commands used and seeing what exceptions they generate. If the escape was neither of these
two cases the recover block escapes with the value of EscapeCode so
that the routine that called it can possibly respond. Note that the chain of recourse for an escape is up the call ladder. An escape will
attempt to get an answer from the immediate routine, then the caller,
then the caller of that etc. until either it finds an answer or finds the
top level default recover block which displays the escape code on the
screen. Note that this ascent up the call chain is enabled by using
Escape(EscapeCode) in the recover block. If this escape is not included in the program
then the error handling terminates at that point and is not addressed or reported.
This is not a good practice because an important piece of
run-time information relating to something failing is not being
acknowledged. Always end recover blocks with a none-of-the-above response of Escape(EscapeCode).
The advantage of Try..Recover might not be clear compared to alternatives. It seems that there are several simpler alternatives for
accomplishing what Try..Recover is accomplishing such as using an
unconditional jump to an error handler or passing status parameters.
The next example makes an important distinction between
try..recover and these other techniques. Consider the following procedures:
procedure PerformMotion(Destination:longint);
begin
XAxis.BeginMoveTo(Destination);
end;
procedure PerformManyMoves
var scanner:longint;
begin
for scanner:=1 to 10 do
PerformMotion(scanner*1000);
end;
....
try
PerformManyMoves
recover
begin
if EscapeCode=MotionOverrunEscapeCode then
Prompter.Writeln(‘Motion Overrun Occurred’)
else
Escape(EscapeCode);
end;
The first procedure abbreviates an X axis motion. The second procedure performs many of these motions. The try statement invokes
PerformManyMotions however it is possible for the motor to be
actively moving to a destination from some previous activity. If the
BeginMoveTo which eventually gets called in PerformMotion
cannot successfully splice a new destination onto the current motion
a MotionOverrunEscapeCode will be generated.
PerformManyMoves contains no error management code at all.
Never the less, the MotionOverrunEscapeCode will travel through
this intermediate procedure and get caught in the recover block of
the routine which called PerformManyMoves. This emphasizes the
"safety net" model of try..recover. Escapes can traverse through
different call levels to find a solution to a problem.
The ability of Try..Recovers to be nested indirectly through procedure calls is a very powerful capability. This allows subordinate
routines to attempt recovery operations themselves, handling the
day-to-day problems as underling managers. However if major
problems come along that they cannot handle they have a way to
express that they cannot cope with the problem and request help
from their manager.
Exception handling allows a high level procedure to express responsibility for handling a particular class of problem and permits
intermediate-manager procedures from having to be concerned at all. If a
low level exception is generated execution goes right past the intermediate managers directly to the one that expressed responsibility
where it gets handled. This greatly simplifies error management.
One language design question with respect to exception handling
which comes up is "Should exceptions be resumable?" i.e. should
there be some way, after responding to an escape code in a recover
block to resume execution at the point where the escape occurred?
The language does not include resumable exceptions. Motion systems in particular tend to have a physical state as well as an
informational or program state. If something goes wrong it is usually necessary
not to go back to where things failed but rather to go back before
that point to some prior physical state to setup another attempt.
Rather than reproduce this setup in the recover block it is usually
better to take a fresh "full cycle" attempt through an enclosing repeat
loop or higher level repeat loop. For example:
repeat
try
begin
XAxis.BeginMoveTo(aDestination);
TroubleEncountered:=false;
end
recover
begin
TroubleEncountered:=true;
if EscapeCode=MotionOverrunEscapeCode then
XAxis.WaitForMoveToFinish {motion stop}
else
Escape(EscapeCode);
end;
until not TroubleEncountered;
When considering the requirement to leave a loop if successful the "
exit" command might be considered in the "try" section if the successful action was the last step in a procedure or function.
It is very important not to exit from the try or recover sections. There are run-time activities that occur to install and remove the
"safety nets". By exiting from the middle of this construct there is an unbalanced number of installations and removals resulting in
a recover stack underflow or overflow. If it is important to leave in the middle of the construct consider using an exception.
A discussion of multitasking capabilities will follow later in the
document however one point needs to be made now. Once a task is
started, even by another task, it is accountable to no one and operates
on its own. Escapes generated within a spawned task do not travel
back to recover blocks in the task which spawned the escaping task.
They will instead travel up to the default handler in Snap2Motion. Tasks need to take
responsibility for their own errors.
Error handling can be shared by writing a single handler routine
which is used by many different tasks, i.e.:
procedure ManageEscapeCode;
begin
if EscapeCode=MotionOverrunEscapeCode then
Prompter.Writeln(‘Motion Overrun’)
else if EscapeCode=ReadEscapeCode then
Prompter.Writeln(‘Problem with Read Operation’)
else
Escape(EscapeCode);
end;
...
try
<do task operations>
recover
ManageEscapeCode;
The routine ManageEscapeCode can be used by many different
tasks in top level recover blocks to avoid having to copy the recover
behavior into each one. However it's usually the case that the recover
block is targeting the handling of specific expect problems.