Exceptions: Difference between revisions
(32 intermediate revisions by 5 users not shown) | |||
Line 1: | Line 1: | ||
Exceptions are a technique for handling unusual occurrences in the execution of a method call. This page discusses <var class="product"> | Exceptions are a technique for handling unusual occurrences in the execution of a method call. This page discusses <var class="product">SOUL</var> exception handling. | ||
==Background== | ==Background== | ||
A wide variety of errors can occur inside a program. | A wide variety of errors can occur inside a program. | ||
Many of these, such as syntax errors, or invalid variable or member | Many of these, such as syntax errors, or invalid variable or member | ||
names, can be caught at compile-time, so they produce compile-time | names, can be caught at compile-time, so they produce compile-time errors. | ||
errors. | Other errors can only be caught at run-time, so they produce run-time errors. | ||
Other errors can only be caught at run-time, so they produce run-time | |||
errors. | |||
For example, a reference to a member of a null object variable will | For example, a reference to a member of a null object variable will | ||
Line 20: | Line 18: | ||
program at the earliest point that the error was detected so that | program at the earliest point that the error was detected so that | ||
the program does not do damage because of the error. | the program does not do damage because of the error. | ||
In addition, if <var class="product">SirFact</var> is being used, the request cancelling error | In addition, if <var class="product">[[SirFact]]</var> is being used, the request cancelling error | ||
will produce a <var class="product">SirFact</var> dump at the time the error was first detected | will produce a <var class="product">SirFact</var> dump at the time the error was first detected | ||
(or at the very least, indicate the procedure and line number where the | (or at the very least, indicate the procedure and line number where the error was first detected). | ||
error was first detected). | |||
Such early detection of error greatly simplifies problem diagnosis. | Such early detection of error greatly simplifies problem diagnosis. | ||
Still other errors are actually quite common and might not even be | Still other errors are actually quite common and might not even be considered errors in most contexts in which they occur. | ||
considered errors in most contexts in which they occur. | For example, the <var>Stringlist</var> class <var>[[Locate (Stringlist function)|Locate]]</var> method might not locate any | ||
For example, the <var>Stringlist</var> class <var>Locate</var> method might not locate any | |||
items that match the search criterion. | items that match the search criterion. | ||
Since this is likely not to be a true error, the <var>Locate</var> method simply | Since this is likely not to be a true error, the <var>Locate</var> method simply | ||
Line 45: | Line 41: | ||
For example, a method that returns an object instance might return a | For example, a method that returns an object instance might return a | ||
<var>Null</var> object under certain error conditions that are not necessarily indicative | |||
of a programming error. | of a programming error. | ||
This allows the calling program to check for a | This allows the calling program to check for a <var>Null</var> and perform | ||
appropriate processing if a | appropriate processing if a <var>Null</var> is returned. | ||
If the program doesn't expect this error case, it might not check for | If the program doesn't expect this error case, it might not check for a <var>Null</var>. | ||
a | |||
If the program is correct, and the error case cannot happen in the | If the program is correct, and the error case cannot happen in the | ||
given context, the program behaves correctly with no unnecessary | given context, the program behaves correctly with no unnecessary error checking. | ||
error checking. | If, however, it is incorrect, and a <var>Null</var> can be returned, it is likely | ||
If, however, it is incorrect, and a | to get a <var>Null</var>-object request-cancelling error when it attempts to | ||
to get a | use the <var>Null</var> as an object reference. | ||
use the | |||
This is the best of all worlds — no error checking is required | This is the best of all worlds — no error checking is required | ||
Line 69: | Line 63: | ||
For example, an error happens inside a subroutine or inside the <var>Set</var> | For example, an error happens inside a subroutine or inside the <var>Set</var> | ||
method of a property. | method of a property. | ||
<li>The return value has no invalid values available to be used to | |||
indicate an unusual situation. | <li>The return value has no invalid values available to be used to indicate an unusual situation. | ||
For example, if a function returns a string, and a null value is sometimes | For example, if a function returns a string, and a null value is sometimes | ||
a valid output, there might be no reasonable string value that can be used | a valid output, there might be no reasonable string value that can be used | ||
to indicate the error. | to indicate the error. | ||
<li>Even if there is a reasonable value (such as a null string, | |||
<li>Even if there is a reasonable value (such as a null string, <var>Null</var> object | |||
reference, or zero or negative numeric value), such a value might not provide | reference, or zero or negative numeric value), such a value might not provide | ||
enough information about the nature of the error for the calling program to | enough information about the nature of the error for the calling program to deal with the error. | ||
deal with the error. | Obviously, a single string or numeric value, or a <var>Null</var> object reference, cannot | ||
Obviously, a single string or numeric value, or a | |||
provide a lot of information. | provide a lot of information. | ||
This is especially important if a method might encounter more than one unusual | This is especially important if a method might encounter more than one unusual | ||
Line 87: | Line 81: | ||
For example, a method places an order for a product, and given a product number, | For example, a method places an order for a product, and given a product number, | ||
it wants to indicate an error if the product number is invalid. | it wants to indicate an error if the product number is invalid. | ||
The number might be invalid because | The number might be invalid because the product number doesn't exist or because the product is out of stock. | ||
the product number doesn't exist or because the product is out of stock. | |||
Obviously, these are two very different errors, the former possibly being | Obviously, these are two very different errors, the former possibly being | ||
indicative of a programming error, the latter less likely to be. | indicative of a programming error, the latter less likely to be. | ||
Line 102: | Line 95: | ||
or might be set, might only think to look for assignment statements with | or might be set, might only think to look for assignment statements with | ||
the variable on the left side of the assignment. | the variable on the left side of the assignment. | ||
Certainly, life would be easier if that | Certainly, life would be easier if that is all one needed to look for. | ||
<li>Just because a variable is used for the output parameter, there is no | <li>Just because a variable is used for the output parameter, there is no | ||
guarantee that the program actually examines the output parameter value. | guarantee that the program actually examines the output parameter value. | ||
Line 108: | Line 102: | ||
certain error situation can't occur, the code might not bother to check the variable | certain error situation can't occur, the code might not bother to check the variable | ||
used as an output parameter and continue on, oblivious to an error. | used as an output parameter and continue on, oblivious to an error. | ||
<li>Output parameters cannot (currently) be optional. | <li>Output parameters cannot (currently) be optional. | ||
This means that a variable has to be specified for the output parameter, | This means that a variable has to be specified for the output parameter, | ||
Line 115: | Line 110: | ||
</ul> | </ul> | ||
An alternative to output parameters to handle unusual | |||
cases in method calls | cases in method calls is called '''exception handling'''. | ||
==Errors unsuitable for exception handling== | ==Errors unsuitable for exception handling== | ||
Exception handling consists of three parts: | Exception handling consists of three parts: | ||
<ol> | <ol> | ||
<li>The ability to define classes of exceptions and the information available | <li>The ability to define classes of exceptions and the information available for these classes. | ||
for these classes. | Unsurprisingly, classes of exceptions are defined in almost the same way as other classes. | ||
Unsurprisingly, classes of exceptions are defined in almost the same way as other | |||
classes. | |||
<li>The ability to indicate an unusual or exception situation. | <li>The ability to indicate an unusual or exception situation. | ||
This is referred to as ''throwing'' an exception. | This is referred to as ''throwing'' an exception. | ||
<li>The ability to detect a thrown exception and to perform special processing | <li>The ability to detect a thrown exception and to perform special processing | ||
for the exception. | for the exception. This is known as ''catching'' an exception. | ||
This is known as ''catching'' an exception. | |||
</ol> | </ol> | ||
Exception handling in <var class="product">SOUL</var> | |||
is very similar to many other | |||
object-oriented programming languages, such as Java or VB.Net. | object-oriented programming languages, such as Java or VB.Net. | ||
However, each language's implementation of exception handling is unique, each | However, each language's implementation of exception handling is unique, each | ||
with subtle or not so subtle differences from others. | with subtle or not so subtle differences from others. | ||
<var class="product"> | <var class="product">SOUL</var> is no exception (no pun intended), and significant departures from the way | ||
other languages support exceptions is indicated where applicable. | other languages support exceptions is indicated where applicable. | ||
A philosophical departure from the support of most other | A philosophical departure from the support of most other languages for exceptions is worth noting: | ||
languages for exceptions is worth noting: | |||
while many object-oriented languages make all (or almost all) errors exceptions, | while many object-oriented languages make all (or almost all) errors exceptions, | ||
<var class="product"> | <var class="product">SOUL</var> does not. | ||
This is partially because many object-oriented languages are not, strictly speaking, | This is partially because many object-oriented languages are not, strictly speaking, | ||
application languages — in addition | application languages — in addition | ||
Line 152: | Line 145: | ||
facilitate this by making all errors catchable. | facilitate this by making all errors catchable. | ||
<var class="product"> | <var class="product">SOUL</var> is an application development language. | ||
While it is theoretically possible to build a program environment in <var class="product"> | While it is theoretically possible to build a program environment in <var class="product">SOUL</var>, | ||
or even an operating system, this is not really the focus of <var class="product"> | or even an operating system, this is not really the focus of <var class="product">SOUL</var>, and it is | ||
not likely anyone would ever do this. | not likely anyone would ever do this. | ||
The <var class="product"> | The <var class="product">SOUL</var> programming environment (<var class="product">Model 204</var>, which is not itself written in <var class="product">SOUL</var>) | ||
is responsible both for the ultimate catching of errors and the providing of | is responsible both for the ultimate catching of errors and the providing of information about the nature of the errors so that they can be corrected. | ||
information about the nature of the errors so that they can be corrected. | |||
The kinds of errors that <var class="product"> | The kinds of errors that <var class="product">SOUL</var> does ''not'' turn into exceptions, | ||
and thus are not trappable, fall into two broad categories: | and thus are not trappable, fall into two broad categories: | ||
<ol> | <ol> | ||
<li>Pernicious environmental errors from which it would generally be | <li>Pernicious environmental errors from which it would generally be | ||
almost impossible to recover gracefully on an application level. | almost impossible to recover gracefully on an application level. | ||
<p> | |||
For example, if <var>CCATEMP</var> fills up, it is exceedingly unlikely that a | For example, if <var>CCATEMP</var> fills up, it is exceedingly unlikely that a | ||
request could recover gracefully. | request could recover gracefully. | ||
Line 174: | Line 167: | ||
<var>CCATEMP</var> could fill. | <var>CCATEMP</var> could fill. | ||
And even if it could anticipate the places, it would be very difficult to avoid | And even if it could anticipate the places, it would be very difficult to avoid | ||
doing anything that might also require <var>CCATEMP</var>. | doing anything that might also require <var>CCATEMP</var>.</p> | ||
<p> | |||
Similarly, a <var>PDL</var> (push-down-list) overflow can be detected at almost any | Similarly, a <var>PDL</var> (push-down-list) overflow can be detected at almost any | ||
method call, not to mention dozens of internal <var>PDL</var>-intensive routines | method call, not to mention dozens of internal <var>PDL</var>-intensive routines (such as, say, journal output). | ||
(such as, say, journal output). | |||
As such, it would be almost impossible for an application to anticipate | As such, it would be almost impossible for an application to anticipate | ||
all the places where a <var>PDL</var> overflow might be detected. | all the places where a <var>PDL</var> overflow might be detected. | ||
And even if it could anticipate the places, any method calls to handle the situation | And even if it could anticipate the places, any method calls to handle the situation | ||
would likely also encounter a <var>PDL</var> overflow. | would likely also encounter a <var>PDL</var> overflow.</p> | ||
<li>Errors that are indicative of logic errors in a program. | <li>Errors that are indicative of logic errors in a program. | ||
<p> | |||
For example, a class member reference via a | For example, a class member reference via a <var>Null</var> object variable is usually indicative of a programming error. | ||
indicative of a programming error. | |||
Similarly, a reference to an invalid collection item number is also generally | Similarly, a reference to an invalid collection item number is also generally | ||
indicative of a programming error. | indicative of a programming error.</p> | ||
<p> | |||
While there might be error-causing cases of "sloppy" references to | While there might be error-causing cases of "sloppy" references to | ||
object variables or collection items that you would want to simply catch | object variables or collection items that you would want to simply catch | ||
when they happen: | when they happen:</p> | ||
<ul> | <ul> | ||
<li>Most of these are handled well by existing facilities such as Auto New | <li>Most of these are handled well by existing facilities such as <var>[[Classes and Objects#Allow Auto and constructors|Auto New]]</var> | ||
and also UseDefault processing for collections. | and also <var>[[UseDefault (GenericNamedArraylist property)|UseDefault]]</var> processing for collections. | ||
<li>In the odd cases where such sloppy references are useful, it is trivial | <li>In the odd cases where such sloppy references are useful, it is trivial | ||
and efficient to check for the sloppy references ( | and efficient to check for the sloppy references (<var>Null</var> object variable, | ||
invalid collection item number), so an exception-handling paradigm | invalid collection item number), so an exception-handling paradigm adds little value. | ||
adds little value. | <p> | ||
Of course, there are always border-line cases where an error that is nearly | Of course, there are always border-line cases where an error that is nearly | ||
always indicative of a programming error, might not be in certain | always indicative of a programming error, might not be in certain | ||
Line 208: | Line 200: | ||
likely that many request cancelling errors in system methods will gradually | likely that many request cancelling errors in system methods will gradually | ||
be converted into catchable exceptions over time. | be converted into catchable exceptions over time. | ||
This is also quite likely to be the case for many errors in | This is also quite likely to be the case for many errors in user-written methods.</p> | ||
</ul> | </ul> | ||
</ol> | </ol> | ||
Note that <var class="product">Model 204</var> '''does''' provide <var class="product"> | Note that <var class="product">Model 204</var> '''does''' provide <var class="product">SOUL</var> facilities to catch even | ||
these types of untrappable errors. | these types of untrappable errors. | ||
These facilities include <var>On</var> units, especially <var>On Error</var> units, and <var>APSY</var> | These facilities include <var>On</var> units, especially <var>On Error</var> units, and <var>APSY</var> | ||
error procedures. | error procedures. | ||
These facilities make it possible for <var class="product"> | These facilities make it possible for <var class="product">SOUL</var> programs to, at least, | ||
provide nicer notification of an error to a client (web browser, 3270 | provide nicer notification of an error to a client (web browser, 3270 | ||
user, SOAP client) than a broken connection or a <var class="product">Model 204</var> error message. | user, SOAP client) than a broken connection or a <var class="product">Model 204</var> error message. | ||
Line 227: | Line 219: | ||
constraints that caused the problem in the first place. | constraints that caused the problem in the first place. | ||
This problem is not unique to <var class="product">Model 204</var> — severe environmental | This problem is not unique to <var class="product">Model 204</var> — severe environmental | ||
constraints can wreak havoc with the best laid error recovery plans | constraints can wreak havoc with the best laid error recovery plans in any environment. | ||
in any environment. | |||
==Exception class definitions== | ==Exception class definitions== | ||
Most error conditions have information associated with them. | Most error conditions have information associated with them. | ||
This information can be useful for correcting or recovering from | This information can be useful for correcting or recovering from the problem. | ||
the problem. | |||
For example, if an invalid character is detected in a stream of | For example, if an invalid character is detected in a stream of | ||
data, it might be useful to know the character offset in the stream, | data, it might be useful to know the character offset in the stream, | ||
the actual invalid character, and some indication of why the character | the actual invalid character, and some indication of why the character is invalid. | ||
is invalid. | It might also be useful to have a text description of the nature of the error. | ||
It might also be useful to have a text description of the nature of the | |||
error. | |||
The collection of data describing an error situation might be used | The collection of data describing an error situation might be used | ||
immediately after the error is detected, or it might be examined | immediately after the error is detected, or it might be examined elsewhere. | ||
elsewhere. | |||
As such, this error data should be able to persist indefinitely. | As such, this error data should be able to persist indefinitely. | ||
Given this requirement, it is clear that the logical place to | Given this requirement, it is clear that the logical place to | ||
Line 249: | Line 237: | ||
would, of course, be a class. | would, of course, be a class. | ||
Classes that describe trappable error situations are | Classes that describe trappable error situations are called '''exception classes'''. | ||
called '''exception classes'''. | To a large degree, exception classes are no different from any other class: | ||
To a large degree, exception classes are no different from any other | |||
class: | |||
<ul> | <ul> | ||
<li>They can have <var>Public</var>, <var>Private</var>, and <var>Shared</var> blocks; they | <li>They can have <var>Public</var>, <var>Private</var>, and <var>Shared</var> blocks; they | ||
can have variables, methods, and the same <var>Allow</var> and <var>Disallow</var> rules | can have variables, methods, and the same <var>Allow</var> and <var>Disallow</var> rules | ||
as any other class. | as any other class. | ||
<li>There can be system exception classes, | <li>There can be [[List of system exception classes|system exception classes]], | ||
which are | which are provided as part of the <var class="product">Model 204</var> installation, and there can be | ||
exception classes | exception classes which are defined and maintained by <var class="product">SOUL</var> code. | ||
<li>Exception classes can be used wherever non-exception classes are used, | <li>Exception classes can be used wherever non-exception classes are used, | ||
although the reverse is not true. | although the reverse is not true. | ||
Line 268: | Line 255: | ||
</ul> | </ul> | ||
User-defined exception classes are denoted by using the <var>Exception</var> | |||
keyword in the class header: | keyword in the class header: | ||
<p class="code">class tackyColor exception | <p class="code">class tackyColor exception | ||
Line 280: | Line 267: | ||
an exception class. | an exception class. | ||
If you want, you can put the Exception keyword on almost all class declarations; | If you want, you can put the <var>Exception</var> keyword on almost all class declarations; | ||
specifying it removes no capabilities from the class. | specifying it removes no capabilities from the class. | ||
Doing so, however, is misleading, since most classes are likely never to be used | Doing so, however, is misleading, since most classes are likely never to be used | ||
to indicate error situations. | to indicate error situations. | ||
In this regard, you can view the Exception keyword on a class declaration as | In this regard, you can view the <var>Exception</var> keyword on a class declaration as | ||
documentation that a class can be used as an exception class. | documentation that a class can be used as an exception class. | ||
While the compiler ''cannot'' ensure that a class with an Exception | While the compiler ''cannot'' ensure that a class with an <var>Exception</var> | ||
declaration will be used as an exception, it ''does'' ensure that a | declaration will be used as an exception, it ''does'' ensure that a | ||
class not declared as Exception is not used in exception contexts. | class not declared as <var>Exception</var> is not used in exception contexts. | ||
The one case where the Exception keyword is not allowed on class declarations | The one case where the <var>Exception</var> keyword is not allowed on class declarations | ||
is in declarations that extend non-exception classes — exception classes | is in declarations that extend non-exception classes — exception classes | ||
can only extend other exception classes. | can only extend other exception classes. | ||
Line 296: | Line 283: | ||
Some examples of system exception classes are: | Some examples of system exception classes are: | ||
<dl> | <dl> | ||
<dt<var>[[InvalidHexData class|InvalidHexData]]</var> | <dt><var>[[InvalidHexData class|InvalidHexData]]</var> | ||
<dd>Describes an error in converting hexadecimal data to some other datatype. | <dd>Describes an error in converting hexadecimal data to some other datatype. | ||
<dt<var>[[InvalidRegex class|InvalidRegex]]</var> | <dt><var>[[InvalidRegex class|InvalidRegex]]</var> | ||
<dd>Describes an error processing a regular expression. | <dd>Describes an error processing a regular expression. | ||
<dt<var>[[DaemonLost class|DaemonLost]]</var> | <dt><var>[[DaemonLost class|DaemonLost]]</var> | ||
<dd>Describes a situation where the thread doing processing for a <var>Daemon</var> object | <dd>Describes a situation where the thread doing processing for a <var>Daemon</var> object was lost (logged off). | ||
was lost (logged off). | |||
</dl> | </dl> | ||
Line 315: | Line 301: | ||
For links to the descriptions of the individual system exception classes, see the | For links to the descriptions of the individual system exception classes, see the | ||
[[Lists of classes and methods#except|"Lists of classes and methods"]]. | [[Lists of classes and methods#except|"Lists of classes and methods"]]. | ||
==Throwing exceptions== | ==Throwing exceptions== | ||
When an error situation occurs, the code that detects the situation can do | When an error situation occurs, the code that detects the situation can do | ||
Line 320: | Line 307: | ||
<ul> | <ul> | ||
<li>It can cancel the request. | <li>It can cancel the request. | ||
In <var class="product"> | In <var class="product">SOUL</var> this is done with an <var>Assert</var> statement. | ||
In system code, this is done by some equivalent of the <var>Assert</var> statement. | In system code, this is done by some equivalent of the <var>Assert</var> statement. | ||
This is the correct response to an error if the error is clearly indicative | This is the correct response to an error if the error is clearly indicative | ||
of a programming error or a severe environmental constraint that is likely | of a programming error or a severe environmental constraint that is likely | ||
to make request continuation unproductive. | to make request continuation unproductive. | ||
<li>It can ''throw'' an exception. | <li>It can ''throw'' an exception. | ||
In <var class="product"> | In <var class="product">SOUL</var> this is done with a <var>Throw</var> statement. | ||
In system code, this is done by some equivalent of the <var>Throw</var> statement. | In system code, this is done by some equivalent of the <var>Throw</var> statement. | ||
This is the correct response to an error if the error might occur in the | This is the correct response to an error if the error might occur in the | ||
normal course of processing and the code that called the method might be | normal course of processing and the code that called the method might be | ||
able to recover from the error. | able to recover from the error. | ||
< | <p class="note">'''Note:''' | ||
In the <var class="product"> | In the <var class="product">SOUL</var> implementation of exceptions, exceptions can only | ||
be thrown by methods. | be thrown by methods. </p> | ||
</ul> | </ul> | ||
In both system and | In both system and user-written methods, a thrown exception can only be handled | ||
(caught) by the code that called the method. | (caught) by the code that called the method. | ||
This is different from many other languages' implementations of exceptions | This is different from many other languages' implementations of exceptions | ||
Line 345: | Line 333: | ||
This is because a method's exceptions are part of the method's interface to | This is because a method's exceptions are part of the method's interface to | ||
its callers, as much as any input and output parameters, as much as output values. | its callers, as much as any input and output parameters, as much as output values. | ||
===Specifying a Throws clause=== | ===Specifying a Throws clause=== | ||
The exceptions thrown by a method are indicated by the <var>Throws</var> | The exceptions thrown by a method are indicated by the <var>Throws</var> | ||
clause in the method declaration and definition header. | clause in the method declaration and definition header. | ||
For example, if the method Paint might throw a TackyColor and InvalidHexData | For example, if the method <code>Paint</code> might throw a <code>TackyColor</code> and <code>InvalidHexData</code> | ||
exception, it should be declared and defined as: | exception, it should be declared and defined as: | ||
<p class="code"> subroutine paint(%hexColor is string len 6) - | <p class="code">subroutine paint(%hexColor is string len 6) - | ||
throws tackyColor and invalidHexData | |||
</p> | </p> | ||
Line 358: | Line 347: | ||
at a time, and in most cases, it will not throw an exception at all. | at a time, and in most cases, it will not throw an exception at all. | ||
The list of exceptions after a <var>Throws</var> keyword is the list of exception | The list of exceptions after a <var>Throws</var> keyword is the list of exception class names. | ||
class names. | These class names could be user-defined exception class names, or they could be system | ||
These class names could be | |||
exception class names. | exception class names. | ||
If the class is a system class name, it could be fully qualified with the | If the class is a system class name, it could be fully qualified with the <code>System:</code> namespace indicator. | ||
<code>System:</code> namespace indicator. | |||
For instance, the previous example could be written as | For instance, the previous example could be written as | ||
<p class="code">subroutine paint(%hexColor is string len 6) - | <p class="code">subroutine paint(%hexColor is string len 6) - | ||
throws tackyColor and | throws tackyColor and system:invalidHexData | ||
</p> | </p> | ||
This would only be necessary if there was a | This would only be necessary if there was a user-defined class name with the same | ||
name as the system class name. | name as the system class name. | ||
Of course, it | Of course, it is best to avoid such a situation as much as possible. | ||
As with most method descriptions, the <var>Throws</var> clause on a method declaration | As with most method descriptions, the <var>Throws</var> clause on a method declaration | ||
Line 400: | Line 386: | ||
Methods that implement an overridable method cannot throw any | Methods that implement an overridable method cannot throw any | ||
exceptions not thrown by the implemented method; however, they do not | exceptions not thrown by the implemented method; however, they do not | ||
necessarily have to throw all the exceptions thrown by the implemented | necessarily have to throw all the exceptions thrown by the implemented method. | ||
method. | |||
That is, the exceptions in a <var>Throws</var> list on an <var>Implements</var> method | That is, the exceptions in a <var>Throws</var> list on an <var>Implements</var> method | ||
must be a subset of the <var>Throws</var> list on the corresponding | must be a subset of the <var>Throws</var> list on the corresponding | ||
Line 411: | Line 396: | ||
</p> | </p> | ||
then it is valid for an implementing method to indicate | then it is valid for an implementing method to indicate: | ||
<p class="code">subroutine buy(%productCode is string len 8) - | <p class="code">subroutine buy(%productCode is string len 8) - | ||
implements buy in products - | implements buy in products - | ||
Line 417: | Line 402: | ||
</p> | </p> | ||
or even | or even: | ||
<p class="code">subroutine buy(%productCode is string len 8) - | <p class="code">subroutine buy(%productCode is string len 8) - | ||
implements buy in products | implements buy in products | ||
Line 428: | Line 413: | ||
It would be surprising, indeed, if the method threw some different | It would be surprising, indeed, if the method threw some different | ||
exception, simply because it had been overridden. | exception, simply because it had been overridden. | ||
===Using the Throw statement=== | ===Using the Throw statement=== | ||
Once a method declaration indicates the exceptions it might throw, | Once a method declaration indicates the exceptions it might throw, | ||
Line 438: | Line 423: | ||
</p> | </p> | ||
and there is a variable declaration in the method | and there is a variable declaration in the method: | ||
<p class="code">%tacky is object tackyColor | <p class="code">%tacky is object tackyColor | ||
</p> | </p> | ||
The method could do this: | |||
<p class="code">throw %tacky | <p class="code">throw %tacky | ||
</p> | </p> | ||
Line 471: | Line 456: | ||
But, even this is not quite right. | But, even this is not quite right. | ||
Usually, the exception objects will contain information to aid in problem | Usually, the exception objects will contain information to aid in problem determination and recovery: | ||
determination and recovery: | |||
<p class="code">if %color eq %yuckyGreen then | <p class="code">if %color eq %yuckyGreen then | ||
%tacky = new | %tacky = new | ||
Line 501: | Line 485: | ||
</p> | </p> | ||
Of course, for code readability, named parameters are preferable on | Of course, for code readability, named parameters are preferable on such constructors: | ||
such constructors: | |||
<p class="code">if %color eq %yuckyGreen then | <p class="code">if %color eq %yuckyGreen then | ||
throw %(tackyColor):new(reason='Yucky', alternative='00FF00') | throw %(tackyColor):new(reason='Yucky', alternative='00FF00') | ||
Line 514: | Line 497: | ||
of the classes listed in the method declaration's <var>Throws</var> clause results | of the classes listed in the method declaration's <var>Throws</var> clause results | ||
in a compilation error. | in a compilation error. | ||
===Exception classes extending other exception classes=== | ===Exception classes extending other exception classes=== | ||
Exception classes can extend other exception classes. | Exception classes can extend other exception classes. | ||
Line 523: | Line 507: | ||
match multiple classes in a method's <var>Throws</var> list. | match multiple classes in a method's <var>Throws</var> list. | ||
The thrown (and | The thrown (and therefore catchable) class is the first class in the <var>Throws</var> | ||
list that matches the object in the <var>Throw</var>. | list that matches the object in the <var>Throw</var>. | ||
That is, the first class in the <var>Throws</var> list that exactly matches the thrown | That is, the first class in the <var>Throws</var> list that exactly matches the thrown | ||
object's class or that is a base class of the thrown object is used as the thrown | object's class or that is a base class of the thrown object is used as the thrown class for the method caller. | ||
class for the method caller. | |||
Because of this, a <var>Throws</var> list must always specify extension classes before | Because of this, a <var>Throws</var> list must always specify extension classes before | ||
base classes. | base classes. | ||
Otherwise, the base class in the <var>Throws</var> list would always match any object | Otherwise, the base class in the <var>Throws</var> list would always match any object | ||
that would match an extension class, so the extension class would never be | that would match an extension class, so the extension class would never be used as the thrown class. | ||
used as the thrown class. | |||
For example, if exception class <code>ReallyNastyColor</code> extended exception class | For example, if exception class <code>ReallyNastyColor</code> extended exception class | ||
Line 543: | Line 525: | ||
</p> | </p> | ||
The new <code>ReallyNastyColor</code> exception object would be thrown as a | The new <code>ReallyNastyColor</code> exception object would be thrown as a <code>TackyColor</code> object. | ||
<code>TackyColor</code> object. | |||
That is, the callers of <code>Paint</code> would not be able to assign the thrown | That is, the callers of <code>Paint</code> would not be able to assign the thrown | ||
exception to a <code>ReallyNastyColor</code> object without the use of a narrowing | exception to a <code>ReallyNastyColor</code> object without the use of a narrowing assignment. | ||
assignment. | |||
==Try and Catch== | ==Try and Catch== | ||
Without any action on a method caller's part, a thrown exception is, | Without any action on a method caller's part, a thrown exception is, | ||
Line 555: | Line 536: | ||
A <var>Try/Catch</var> block consists of two parts. | A <var>Try/Catch</var> block consists of two parts. | ||
The first, the <var>Try</var> section contains one or more <var class="product"> | The first, the <var>Try</var> section, contains one or more <var class="product">SOUL</var> statements that | ||
might result in an exception. | might result in an exception. | ||
The <var>Try</var> section is then followed by one or more <var>Catch</var> sections, each | The <var>Try</var> section is then followed by one or more <var>Catch</var> sections, each | ||
one of them handling (catching) a particular class of exception. | one of them handling (catching) a particular class of exception. | ||
The following fragment | The following fragment shows the use of a <var>Try/Catch</var> block to trap | ||
an exception caused by invalid hexadecimal data: | an exception caused by invalid hexadecimal data: | ||
<p class="code">try | <p class="code">try | ||
Line 588: | Line 569: | ||
</ul> | </ul> | ||
===Try block syntax=== | |||
<p class="syntax"><span class="literal">Try</span> | |||
<span class="term">tStmt1</span> ; * Exceptions can | |||
<span class="squareb">[</span> <span class="term">tStmt2</span> <span class="squareb">]</span> ; * be caught in | |||
<span class="squareb">[</span> ... <span class="squareb">]</span> ; * these statements | |||
<span class="squareb">[</span> <span class="literal">Rethrow</span> <span class="term">rethClassA</span> <span class="squareb">[</span><span class="literal">And</span> <span class="term">rethClassB</span> <span class="squareb">]</span> ... <span class="squareb">]</span> | |||
<nowiki>...</nowiki> any number of <var>Rethrow</var> statements | |||
<span class="squareb">[</span> <span class="literal">Catch</span> <span class="term">catClassC</span> <span class="squareb">[</span><span class="literal">To</span> <span class="term">%objC</span><span class="squareb">]</span> <span class="squareb">[</span><span class="literal">And</span> <span class="term">catClassD</span> <span class="squareb">[</span><span class="literal">To</span> <span class="term">%objD</span><span class="squareb">]</span><span class="squareb">]</span> ... | |||
<span class="squareb">[</span> <span class="term">cStmtW</span> <span class="squareb">]</span> ; * These are executed if any | |||
<span class="squareb">[</span> <span class="term">cStmtX</span> <span class="squareb">]</span> ; * of the execptions on the | |||
<span class="squareb">[</span> ... <span class="squareb">]</span> ; * <var>Catch</var> statement are thrown | |||
<span class="squareb">]</span> | |||
<nowiki>...</nowiki> any number of <var>Catch</var> blocks | |||
<span class="squareb">[</span> <span class="literal">Success</span> | |||
<span class="squareb">[</span> <span class="term">sStmtY</span> <span class="squareb">]</span> ; * These are executed | |||
<span class="squareb">[</span> <span class="term">sStmtZ</span> <span class="squareb">]</span> ; * if no execptions | |||
<span class="squareb">[</span> ... <span class="squareb">]</span> ; * are thrown | |||
<span class="squareb">]</span> | |||
<span class="literal">End Try</span> | |||
</p> | |||
====Syntax terms==== | |||
The <var>Try</var> block is divided into two or more sections, which are separated by | |||
<var>[[#Rethrow|Rethrow]]</var>, <var>Catch</var>, and <var>[[#Success blocks|Success]]</var> statements. Multiple | |||
<var>Rethrow</var> and <var>Catch</var> statements can be mixed | |||
with each other and with the <var>Success</var> statement, in any order. | |||
There must be either one <var>Rethrow</var> or <var>Catch</var> statement in a <var>Try</var> block. | |||
<table> | |||
<tr><th>tStmt1 [tStmt2...]</th> | |||
<td>The first section in the <var>Try</var> block must contain one or more <var class="product">SOUL</var> statements. These statements are executed in turn, and if an exception is thrown within them, execution resumes at the <var>Rethrow</var> or <var>Catch</var> statement specifying that exception; if there are none, the request is cancelled. If no exceptions are thrown, execution resumes with the <var>Success</var> block statements, if any, and then after the <var>End Try</var> statement.</td></tr> | |||
<tr><th nowrap><var>Rethrow</var> rethClassA ...</th> | |||
<td>The <var>Rethrow</var> <b>statement</b> (not block) specifies one or more exception classes; if one of them is thrown, then the method containing the <var>Try</var> block throws that exception. Hence a <var>Rethrow</var> statement can only occur within a method which declares all of the <var class="term">rethClass</var> exceptions in its <var>Throws</var> clause. | |||
<p></p> | |||
Each <var class="term">rethClass</var> may not be the same as, nor an extension of, any exception class preceding it on the statement or on preceding <var>Rethrow</var> and <var>Catch</var> statements in the <var>Try</var> block.</td></tr> | |||
<tr><th><var>Catch</var> catClassC ...</th> | |||
<td>The <var>Catch</var> statement specifies one or more exception classes; if one of them is thrown, then the (optional) <var class="term">cStmt</var> statements in the <var>Catch</var> block are executed in turn, and then execution resumes after the <var>End Try</var> statement. | |||
<p></p> | |||
Each <var class="term">catClass</var> may not be the same as, nor an extension of, any exception class preceding it on the statement or on preceding <var>Rethrow</var> and <var>Catch</var> statements in the <var>Try</var> block.</td></tr> | |||
<tr><th><var>Success</var></th> | |||
<td>If no exceptions are thrown in the <var class="term">tStmt</var> statements, the (optional) <var class="term">sStmt</var> statements in the <var>Success</var> block are executed in turn, and then execution resumes after the <var>End Try</var> statement.</td></tr> | |||
</table> | |||
===Try block considerations=== | |||
A <var>Try</var> block can contain more than one statement: | A <var>Try</var> block can contain more than one statement: | ||
<p class="code">try | <p class="code">try | ||
Line 601: | Line 636: | ||
</p> | </p> | ||
While valid, this example is intended to | While valid, this example is intended to show that it might not | ||
be a good idea to put a lot of statements inside a <var>Try</var> block: | be a good idea to put a lot of statements inside a <var>Try</var> block: | ||
<ul> | <ul> | ||
Line 625: | Line 660: | ||
As a general rule of thumb, place as few statements inside | As a general rule of thumb, place as few statements inside | ||
a | a <var>Try</var> block as possible. | ||
To facilitate this, you can follow the <var>Try</var> statement by a <var class="product"> | To facilitate this, you can follow the <var>Try</var> statement by a <var class="product">SOUL</var> | ||
statement on the same line, as in: | statement on the same line, as in: | ||
<p class="code">try %binary = %input:hexToString | <p class="code">try %binary = %input:hexToString | ||
Line 634: | Line 669: | ||
</p> | </p> | ||
===Referencing a thrown exception object=== | |||
As noted in [[#Using the Throw statement|"Using the Throw statement"]], what gets thrown with an exception is | As noted in [[#Using the Throw statement|"Using the Throw statement"]], what gets thrown with an exception is | ||
an exception object that contains information about the nature of the | an exception object that contains information about the nature of the exception. | ||
exception. | |||
In the <var>Try/Catch</var> examples to this point, the thrown objects were ignored — | In the <var>Try/Catch</var> examples to this point, the thrown objects were ignored — | ||
only the class of the thrown exception was used. | only the class of the thrown exception was used. | ||
Line 642: | Line 677: | ||
<var>To</var> clause, followed by an object variable of the exception class being caught. | <var>To</var> clause, followed by an object variable of the exception class being caught. | ||
For example, if <code>%daemon</code> is a <var>Daemon</var> object and <code>%daemonLost</code> | For example, if <code>%daemon</code> is a <var>[[Daemon class|Daemon]]</var> object and <code>%daemonLost</code> | ||
is an object of the <var>DaemonLost</var> exception class, the following block | is an object of the <var>[[DaemonLost class|DaemonLost]]</var> exception class, the following block | ||
catches the exception thrown if the daemon thread was logged | catches the exception thrown if the daemon thread was logged | ||
off for some reason, and it displays the output of the last command | off for some reason, and it displays the output of the last command up to the point where it logged off: | ||
up to the point where it logged off: | |||
<p class="code">try %daemon:run('INCLUDE NASTY') | <p class="code">try %daemon:run('INCLUDE NASTY') | ||
catch daemonLost to %daemonLost | catch daemonLost to %daemonLost | ||
Line 654: | Line 688: | ||
</p> | </p> | ||
Presumably, this block would be useful in diagnosing the problem or even | Presumably, this block would be useful in diagnosing the problem or even correcting it. | ||
===<b id="Rethrow statement"></b>Rethrow=== | |||
====Syntax==== | |||
{{template:Rethrow statement syntax}} | |||
====Description==== | |||
<var class="product">Sirius Mods</var> 8.1 introduced the <var>Rethrow</var> clause in a <var>Try</var> block. <var>Rethrow</var> lets you propagate an exception without having to assign it to a local variable, probably eliminating the need to declare a local exception class object for the sole purpose of propagating an exception. | |||
The <var>Rethrow</var> clause must occur at the same level as a <var>Catch</var> clause in a <var>Try</var> block. But, unlike the <var>Catch</var> clause, no code is allowed "inside" the <var>Rethrow</var> "block." Because <var>Rethrow</var> causes control to pass immediately out of the current method, no code after <var>Rethrow</var> would ever be executed. | |||
A <var>Rethrow</var> clause can only be followed by an <var>End Try</var>, a <var>Catch</var> clause, or a <var>Success</var> block. Like the <var>Throw</var> statement: | |||
<ul> | |||
<li>The <var>Rethrow</var> clause must be invoked inside a method. | |||
<li>The class or classes specified on a <var>Rethrow</var> clause must be declared as being thrown by that method. | |||
</ul> | |||
====Example==== | |||
The following local subroutine illustrates a <var>Rethrow</var> of an <var>InvalidHexdata</var> exception. This subroutine sends a string converted from hex to binary on a socket. | |||
<p class="code">local subroutine (socket):sendHex(%hexdata is longstring) throws invalidHexData | |||
%sendData is longstring | |||
try %sendData = %hexdata:hexToString | |||
rethrow invalidHexdata | |||
end try | |||
%this:send(%sendData) | |||
end subroutine | |||
</p> | |||
<!--SOUL.DME.290.QA is an example. It differs from the above | |||
example. My feeling is that it would be a better doc example, | |||
although it should be simplified & just do a single (failing) | |||
invocation of the local function (i.e., remove the For loop). | |||
It's a bit more complete in that it shows the caller handling | |||
the exception (strictly speaking, the reader should understand | |||
this, but I think it's nice to be explicit). Also, I think the | |||
HexToString method is nice because it's so simple and the user | |||
could clip and play with it, without the "baggage" of Sockets. | |||
Dave Evans, 23 March 2020. | |||
--> | |||
===Mutiple classes on a Catch or Rethrow statement=== | |||
<var class="product">Sirius Mods</var> 8.0 introduced the ability to specify multiple exception classes on a single <var>Catch</var> statement. The class names are separated from each other by the <var>And</var> keyword. However, in <var class="product">Sirius Mods</var> 8.0, no <var>To</var> clause could be specified on a <var>Catch</var> statement with multiple classes. | |||
<var class="product">Sirius Mods</var> 8.1 added support for <var>To</var> clauses on a <var>Catch</var> statement with multiple classes as well as including support for multiple classes on the <var>[[#Rethrow|Rethrow]]</var> statememt. | |||
The following illustrates a <var>Catch</var> statement with multiple classes: | |||
<p class="code">try %foo:doSomethingCrazy | |||
catch invalidHexdata to %ivhexData and tooCrazy and notCrazyEnough to %notCrazyEnough | |||
end try | |||
</p> | |||
If the above were inside a method, the exceptions could be rethrown: | |||
<p class="code">try %foo:doSomethingCrazy | |||
rethrow invalidHexdata and tooCrazy and notCrazyEnough | |||
end try | |||
</p> | |||
Of course, if a <var>Catch</var> block catches multiple class exceptions, it might be important to determine which exception was actually thrown. In that case, a null test can be performed on the target objects: | |||
<p class="code">try %foo:doSomethingCrazy | |||
catch invalidHexdata to %ivhexData and and tooCrazy and notCrazyEnough to %notCrazyEnough | |||
... | |||
if %ivhexData is not null then | |||
... | |||
elseIf %notCrazyEnough is not null then | |||
... | |||
end if | |||
... | |||
end try | |||
</p> | |||
For this to work, one must be sure that the target catch objects are null before the <var>Try</var> block. And, unless there is a lot of common code for the classes in the <var>Catch</var> block, it probably makes more sense to just have a separate <var>Catch</var> block for each exception. | |||
There is no difference between rethrowing exceptions in a single <var>Rethrow</var> statement and multiple <var>Rethrow</var> statements, so it is largely a matter of style as to which approach is used. | |||
===Success blocks=== | |||
In cases where a <var>Try</var> block contains multiple statements, a | |||
<var>Success</var> block makes it clear in the code which statement is expected to produce the exceptions that are being caught. | |||
These blocks also protect you from an inadvertent exception thrown in an unexpected context. | |||
For example, consider the following scenario. | |||
You want to try statement <code><a></code> and, if no exceptions get Thrown, | |||
you want to do statements <code><nowiki><b></nowiki></code>, <code><c></code>, <code><d></code>, and <code><e></code>. | |||
Otherwise, if statement <code><a></code> throws an exception, | |||
you want to do statements <code><x></code>, <code><y></code>, or <code><z></code>, depending on the exception. | |||
You code your <var>Try</var>/<var>Catch</var> block like this: | |||
<p class="code">try | |||
<a>... | |||
<b>... | |||
<c>... | |||
<d>... | |||
<e>... | |||
catch foo | |||
<x>... | |||
catch bar | |||
<y>... | |||
catch another | |||
<z>... | |||
end try | |||
</p> | |||
If statement <code><a></code> does indeed throw an exception, statements <code><nowiki><b></nowiki></code> through <code><e></code> do not run, and the appropriate <var>Catch</var> statement takes effect. | |||
However, if statement <code><a></code> does not throw an exception, | |||
there might be no way to know that statement <code><nowiki><b></nowiki></code>, <code><c></code>, <code><d></code>, or <code><e></code> might | |||
throw an exception that is one of the exceptions in the subsequent <var>Catch</var> statements. | |||
Or you might be aware of their capacity to do so, | |||
but you might not expect an exception from any of them in this context. Prior to | |||
Version 7.8 of the <var class="product">Sirius Mods</var>, there was | |||
no good way of preventing the catches to also be in effect for these statements | |||
as well as for statement <code><a></code>. | |||
As of <var class="product">Sirius Mods</var> 7.8, a <var>Success</var> block inside the <var>Try</var> block | |||
resolves the problem by making it clear that the <var>Catch</var> statements do '''not''' apply to | |||
statements <code><nowiki><b></nowiki></code>, <code><c></code>, <code><d></code>, and <code><e></code>: | |||
<p class="code">try | |||
<a>... | |||
success | |||
<b>... | |||
<c>... | |||
<d>... | |||
<e>... | |||
catch foo | |||
<x>... | |||
catch bar | |||
<y>... | |||
catch another | |||
<z>... | |||
end try | |||
</p> | |||
The principle benefits of the <var>Success</var> statement are: | |||
<ul> | |||
<li>It makes it clear in the code which statement is expected to produce the exceptions being caught. | |||
<li>It prevents a catch from accidentally catching an exception from a statement that didn't really expect that exception. | |||
</ul> | |||
You can also reverse the order of the the <var>Success</var> and catches: | |||
<p class="code">try | |||
<a>... | |||
catch foo | |||
<x>... | |||
catch bar | |||
<y>... | |||
catch another | |||
<z>... | |||
success | |||
<b>... | |||
<c>... | |||
<d>... | |||
<e>... | |||
end try | |||
</p> | |||
===Some differences with other languages=== | ===Some differences with other languages=== | ||
This section describes some differences between the <var class="product"> | This section describes some differences between the <var class="product">SOUL</var> implementation | ||
of <var>Try/Catch</var> and implementations in other languages. | of <var>Try/Catch</var> and implementations in other languages. | ||
Outside of these differences, <var>Try/Catch</var> | Outside of these differences, <var>Try/Catch</var> | ||
support in <var class="product"> | support in <var class="product">SOUL</var> is very similar to that in other languages. | ||
<ul> | <ul> | ||
<li>Unlike Java, it is not necessary to provide a <var>Catch</var> for all exceptions | <li>Unlike Java, it is not necessary to provide a <var>Catch</var> for all exceptions | ||
Line 668: | Line 853: | ||
It seems unnecessary to add code to deal with (or ignore) an error that | It seems unnecessary to add code to deal with (or ignore) an error that | ||
cannot occur in a context. | cannot occur in a context. | ||
<li>Unlike many other languages, uncaught exceptions are not automatically | <li>Unlike many other languages, uncaught exceptions are not automatically | ||
propagated to higher level callers. | propagated to higher level callers. | ||
Partially, this is because the <var class="product"> | Partially, this is because the <var class="product">SOUL</var> environment is not written in <var class="product">SOUL</var>, so | ||
there is no need to propagate exceptions to some outer <var class="product"> | there is no need to propagate exceptions to some outer <var class="product">SOUL</var> environment | ||
which, presumably, would clean up the failing request and possibly provide | which, presumably, would clean up the failing request and possibly provide | ||
diagnostics about the error. | diagnostics about the error. | ||
Instead, clean-up and diagnostics are provided by the assembler environment. | Instead, clean-up and diagnostics are provided by the assembler environment. | ||
<p> | <p> | ||
Automatic propagation of errors is also a grand opportunity for bugs. | Automatic propagation of errors is also a grand opportunity for bugs. | ||
Since an automatically propagated error can happen anywhere in a method, | Since an automatically propagated error can happen anywhere in a method, | ||
Line 682: | Line 868: | ||
Updates to data-structures, including transactions that update files, might | Updates to data-structures, including transactions that update files, might | ||
be half-done at the time of the occurrence of an exception that gets | be half-done at the time of the occurrence of an exception that gets | ||
automatically propagated.</p> | automatically propagated.</p> | ||
<p> | <p> | ||
While languages that provide automatic exception propagation usually also | While languages that provide automatic exception propagation usually also | ||
provide a <code>Finally</code> clause to "ensure" that the method | provide a <code>Finally</code> clause to "ensure" that the method | ||
Line 689: | Line 875: | ||
clause that indicates whether the programmer anticipated the particular | clause that indicates whether the programmer anticipated the particular | ||
error being propagated. | error being propagated. | ||
And the absence of a Finally clause does not prevent error propagation, anyway.</p> | And the absence of a Finally clause does not prevent error propagation, anyway.</p> | ||
<p> | <p> | ||
If you want to propagate an exception in <var class="product"> | If you want to propagate an exception in <var class="product">SOUL</var>, you can simply catch it | ||
and re-<var>Throw</var> it:</p> | and re-<var>Throw</var> it:</p> | ||
<p class="code">catch tackyColor to %yuck | <p class="code">catch tackyColor to %yuck | ||
* let caller deal with this | |||
throw %yuck | |||
</p> | </p> | ||
<p> | <p> | ||
This makes it clear which error is being propagated and under what circumstances.</p> | This makes it clear which error is being propagated and under what circumstances.</p> | ||
<li>The absence of automatic exception propagation eliminates the utility of a | <li>The absence of automatic exception propagation eliminates the utility of a | ||
Finally clause, so no Finally clause is available in <var>Try/Catch</var> blocks in <var class="product"> | Finally clause, so no Finally clause is available in <var>Try/Catch</var> blocks in <var class="product">SOUL</var>. | ||
<li><var> | |||
<li><var>Catch</var> statements cannot catch locally thrown exceptions. | |||
That is, a <var>Throw</var> statement always results in immediate exit from the current | That is, a <var>Throw</var> statement always results in immediate exit from the current | ||
method, regardless of whether or not the <var>Throw</var> is inside of a <var>Try</var> block and | method, regardless of whether or not the <var>Throw</var> is inside of a <var>Try</var> block and | ||
whether or not there are <var> | whether or not there are <var>Catch</var> statements that correspond to the thrown exception. | ||
The <var class="product"> | The <var class="product">SOUL</var> view of exceptions is that they are part of the interface between | ||
a method and its callers, so they have no place in controlling local program flow. | a method and its callers, so they have no place in controlling local program flow. | ||
</ul> | </ul> | ||
===Nesting Try/Catch blocks=== | ===Nesting Try/Catch blocks=== | ||
Like in other languages, <var>Try/Catch</var> blocks can be nested. | Like in other languages, <var>Try/Catch</var> blocks can be nested. | ||
Line 731: | Line 919: | ||
</p> | </p> | ||
The | The <var>Catch</var> that applies to a particular thrown exception is the first <var>Catch</var> | ||
that either exactly matches the class of the thrown exception, or is a base | that either exactly matches the class of the thrown exception, or that is a base | ||
class of the thrown exception. | class of the thrown exception. | ||
Because of this, it is possible to catch many different exceptions with a single | Because of this, it is possible to catch many different exceptions with a single | ||
<var>Catch</var> if all the exceptions are extensions of a specified base class. | |||
For this reason, too, it is invalid to specify a <var>Catch</var> for an exception class | For this reason, too, it is invalid to specify a <var>Catch</var> for an exception class | ||
after a <var>Catch</var> for a base class of that exception class, since the base class | after a <var>Catch</var> for a base class of that exception class, since the base class | ||
<var>Catch</var> will always catch the exception class exception before the exception | <var>Catch</var> will always catch the exception class exception before the exception | ||
class <var>Catch</var> is processed. | class <var>Catch</var> is processed. | ||
If separate <var> | If separate <var>Catch</var> statements are required for base and extension classes, specify the | ||
extension class <var> | extension class <var>Catch</var> statements first. | ||
Because there is no single base exception class for all exceptions | Because there is no single base exception class for all exceptions | ||
Line 747: | Line 935: | ||
generically catch any and all exceptions with a single <var>Catch</var>. | generically catch any and all exceptions with a single <var>Catch</var>. | ||
While this ability might be convenient, it is also likely to encourage sloppy | While this ability might be convenient, it is also likely to encourage sloppy | ||
programming, where generic <var> | programming, where generic <var>Catch</var> statements obscure the possible errors in a context, | ||
and make it all too easy to catch unanticipated errors and do the wrong thing. | and make it all too easy to catch unanticipated errors and do the wrong thing. | ||
For <var>Try</var> blocks nested inside of other Try blocks, if no <var> | For <var>Try</var> blocks nested inside of other <var>Try</var> blocks, if no <var>Catch</var> statements that correspond | ||
to a thrown exception are found for the inner-most <var>Try</var> block, then the outer-most | to a thrown exception are found for the inner-most <var>Try</var> block, then the outer-most | ||
<var>Try</var> block's <var> | <var>Try</var> block's <var>Catch</var> statements are checked for a match, and they are used if a match is found. | ||
That is, when <var>Try</var> blocks are nested, all <var>Try</var> blocks are in effect for statements | That is, when <var>Try</var> blocks are nested, all <var>Try</var> blocks are in effect for statements | ||
inside the inner-most <var>Try</var> block. | inside the inner-most <var>Try</var> block. | ||
Note, however, that other <var> | Note, however, that other <var>Catch</var> statements for a <var>Try</var> block are no longer in effect | ||
inside any of the <var>Catch</var> blocks for the <var>Try</var>. | inside any of the <var>Catch</var> blocks for the <var>Try</var>. | ||
For example, if a request had a | For example, if a request had a <var>Daemon</var> object <code>%dmn</code>, and if <code>%errorhex</code> had invalid hexadecimal data in it in the following block: | ||
<p class="code">try | <p class="code">try | ||
%dmn:run('NASTY') | %dmn:run('NASTY') | ||
Line 767: | Line 955: | ||
end try | end try | ||
</p> | </p> | ||
The <code>Catch InvalidHexData</code> above would '''not''' catch the conversion error, because the conversion is not | |||
inside the <var>Try</var> block associated with that <var>Catch</var> — it is in a <var>Catch</var> block. | |||
==OnThrow and OnUncaught== | ==OnThrow and OnUncaught== | ||
An exception class might want to perform special processing at the time an | An exception class might want to perform special processing at the time an exception is thrown: | ||
exception is thrown: | |||
<ul> | <ul> | ||
<li>It might want to make sure the exception object has valid data. | <li>It might want to make sure the exception object has valid data. | ||
<li>It might want to record diagnostic information, perhaps to the audit | <li>It might want to record diagnostic information, perhaps to the audit | ||
trail or perhaps to some <var class="product">Model 204</var> file. | trail or perhaps to some <var class="product">Model 204</var> file. | ||
<li>It might want to derive some variable values that might not necessarily | <li>It might want to derive some variable values that might not necessarily | ||
have been derivable in the | have been derivable in the <var>Constructor</var>. | ||
</ul> | </ul> | ||
To provide this capability, <var class="product"> | To provide this capability, <var class="product">SOUL</var> special-cases two method names in a | ||
user-defined exception class: <var>OnThrow</var> and <var>OnUncaught</var>. | |||
Both of these methods must be <var>Subroutines</var> (as opposed to <var>Functions</var> or | Both of these methods must be <var>Subroutines</var> (as opposed to <var>Functions</var> or | ||
<var>Properties</var>) and cannot have parameters. | <var>Properties</var>) and cannot have parameters. | ||
Line 792: | Line 979: | ||
The <var>OnThrow</var> subroutine is automatically called whenever an exception | The <var>OnThrow</var> subroutine is automatically called whenever an exception | ||
of the containing class is thrown, and either the exception will not be | of the containing class is thrown, and either the exception will not be | ||
caught and there is no <var>OnUncaught</var> method in the class, or the exception | caught and there is no <var>OnUncaught</var> method in the class, or the exception will be caught. | ||
will be caught. | |||
These two method names have no meaning in non-exception classes. | These two method names have no meaning in non-exception classes. | ||
Line 816: | Line 1,002: | ||
The following illustrates an <var>OnUncaught</var> subroutine that logs information | The following illustrates an <var>OnUncaught</var> subroutine that logs information | ||
from the exception to the audit trail before allowing the request to be | from the exception to the audit trail before allowing the request to be cancelled: | ||
cancelled: | |||
<p class="code">class pratfall exception | <p class="code">class pratfall exception | ||
public | public | ||
Line 831: | Line 1,016: | ||
If <var class="product">SirFact</var> is available and capturing dumps for requesting cancelling | If <var class="product">SirFact</var> is available and capturing dumps for requesting cancelling | ||
errors, all the information one would need is likely to be in the dump, | errors, all the information one would need is likely to be in the dump, | ||
so there is probably little need to collect extra data in an <var>OnUncaught</var> | so there is probably little need to collect extra data in an <var>OnUncaught</var> subroutine. | ||
subroutine. | |||
There is no way for an <var>OnUncaught</var> or <var>OnThrow</var> subroutine to undo the | There is no way for an <var>OnUncaught</var> or <var>OnThrow</var> subroutine to undo the | ||
Line 860: | Line 1,044: | ||
It does this, however, by using the subroutine name (qualified | It does this, however, by using the subroutine name (qualified | ||
with the class name) rather than with the | with the class name) rather than with the superclass method (since | ||
no overriding is involved): | no overriding is involved): | ||
<p class="code">subroutine onThrow | <p class="code">subroutine onThrow | ||
Line 869: | Line 1,053: | ||
</p> | </p> | ||
In this example, the extension exception class's base class is | In this example, the extension exception class's base class is called <code>FooError</code>. | ||
called <code>FooError</code>. | |||
Of course, an <var>OnUncaught</var> in an extension class can also call an | Of course, an <var>OnUncaught</var> in an extension class can also call an | ||
<var>OnThrow</var> for a base class (or even for its own class). | <var>OnThrow</var> for a base class (or even for its own class). | ||
Line 883: | Line 1,066: | ||
class, regardless of whether there are any <var>OnThrow</var> subroutines in the | class, regardless of whether there are any <var>OnThrow</var> subroutines in the | ||
exception class or any of its base classes. | exception class or any of its base classes. | ||
[[Category: | [[Category:SOUL object-oriented programming topics]] |
Latest revision as of 21:58, 23 March 2020
Exceptions are a technique for handling unusual occurrences in the execution of a method call. This page discusses SOUL exception handling.
Background
A wide variety of errors can occur inside a program. Many of these, such as syntax errors, or invalid variable or member names, can be caught at compile-time, so they produce compile-time errors. Other errors can only be caught at run-time, so they produce run-time errors.
For example, a reference to a member of a null object variable will produce a request cancelling “reference to null object” error. Generally, such an error is indicative of a logic error in the program — most likely the program forgot to create an instance of the object, forgot to assign it to the object variable being used, or is mistakenly using the wrong object variable.
In any case, since such an error is indicative of a program logic error, the request cancellation is a benefit: it stops the program at the earliest point that the error was detected so that the program does not do damage because of the error. In addition, if SirFact is being used, the request cancelling error will produce a SirFact dump at the time the error was first detected (or at the very least, indicate the procedure and line number where the error was first detected). Such early detection of error greatly simplifies problem diagnosis.
Still other errors are actually quite common and might not even be considered errors in most contexts in which they occur. For example, the Stringlist class Locate method might not locate any items that match the search criterion. Since this is likely not to be a true error, the Locate method simply returns a 0 in such a case. The Locate method caller can then check for a zero value and do processing appropriate to the no-matching-item-found case.
If the Locate method caller assumes that the Locate always finds a matching item, it is likely to use the zero returned by the Locate method to reference a matched item, then get a request cancelling error when item zero is requested. This illustrates a general principle that methods that can produce unusual results or, at least, results that are significantly different from others, should try to set the unusual or result values to something that is likely to cause a request cancelling error if misused.
For example, a method that returns an object instance might return a Null object under certain error conditions that are not necessarily indicative of a programming error. This allows the calling program to check for a Null and perform appropriate processing if a Null is returned. If the program doesn't expect this error case, it might not check for a Null. If the program is correct, and the error case cannot happen in the given context, the program behaves correctly with no unnecessary error checking. If, however, it is incorrect, and a Null can be returned, it is likely to get a Null-object request-cancelling error when it attempts to use the Null as an object reference.
This is the best of all worlds — no error checking is required if the error is known not to occur in a context, but if that knowledge is incorrect, a request canceling error will quickly stop the program before any damage is done.
Unfortunately, there are some cases where the return value cannot be used to indicate an unusual situation:
- There is no return value. For example, an error happens inside a subroutine or inside the Set method of a property.
- The return value has no invalid values available to be used to indicate an unusual situation. For example, if a function returns a string, and a null value is sometimes a valid output, there might be no reasonable string value that can be used to indicate the error.
- Even if there is a reasonable value (such as a null string, Null object reference, or zero or negative numeric value), such a value might not provide enough information about the nature of the error for the calling program to deal with the error. Obviously, a single string or numeric value, or a Null object reference, cannot provide a lot of information. This is especially important if a method might encounter more than one unusual situation, and the unusual situations are different enough that one might be expected in a particular context, and the other not; or the unusual situations require very different corrective action. For example, a method places an order for a product, and given a product number, it wants to indicate an error if the product number is invalid. The number might be invalid because the product number doesn't exist or because the product is out of stock. Obviously, these are two very different errors, the former possibly being indicative of a programming error, the latter less likely to be.
One solution to such cases is to have an output parameter on the method calls. Unfortunately, this has several problems:
- Output parameters can be confusing in code. There's nothing that marks them as output parameters, so it's not obvious that's what they are. Also, someone looking for how a variable used as an output parameter was or might be set, might only think to look for assignment statements with the variable on the left side of the assignment. Certainly, life would be easier if that is all one needed to look for.
- Just because a variable is used for the output parameter, there is no guarantee that the program actually examines the output parameter value. This could mean that where it is incorrectly believed that a certain error situation can't occur, the code might not bother to check the variable used as an output parameter and continue on, oblivious to an error.
- Output parameters cannot (currently) be optional. This means that a variable has to be specified for the output parameter, even if the unusual situations indicated by the output parameter couldn't happen in a given context. This can be annoying, at best, and confusing, at worst.
An alternative to output parameters to handle unusual cases in method calls is called exception handling.
Errors unsuitable for exception handling
Exception handling consists of three parts:
- The ability to define classes of exceptions and the information available for these classes. Unsurprisingly, classes of exceptions are defined in almost the same way as other classes.
- The ability to indicate an unusual or exception situation. This is referred to as throwing an exception.
- The ability to detect a thrown exception and to perform special processing for the exception. This is known as catching an exception.
Exception handling in SOUL is very similar to many other object-oriented programming languages, such as Java or VB.Net. However, each language's implementation of exception handling is unique, each with subtle or not so subtle differences from others. SOUL is no exception (no pun intended), and significant departures from the way other languages support exceptions is indicated where applicable.
A philosophical departure from the support of most other languages for exceptions is worth noting: while many object-oriented languages make all (or almost all) errors exceptions, SOUL does not. This is partially because many object-oriented languages are not, strictly speaking, application languages — in addition to writing applications in some other object-oriented languages, one might write programming environments or even operating systems. As such, a programming environment or operating system must be able to intercept all errors and try to deal with them, so the object-oriented languages facilitate this by making all errors catchable.
SOUL is an application development language. While it is theoretically possible to build a program environment in SOUL, or even an operating system, this is not really the focus of SOUL, and it is not likely anyone would ever do this. The SOUL programming environment (Model 204, which is not itself written in SOUL) is responsible both for the ultimate catching of errors and the providing of information about the nature of the errors so that they can be corrected.
The kinds of errors that SOUL does not turn into exceptions, and thus are not trappable, fall into two broad categories:
- Pernicious environmental errors from which it would generally be
almost impossible to recover gracefully on an application level.
For example, if CCATEMP fills up, it is exceedingly unlikely that a request could recover gracefully. Since CCATEMP is used for so many different things (including record sets for finds, stringlists, collections, internal storage of objects swapped out of a server, transaction backout logs, and so on), it would be very difficult for an application to anticipate all the places that CCATEMP could fill. And even if it could anticipate the places, it would be very difficult to avoid doing anything that might also require CCATEMP.
Similarly, a PDL (push-down-list) overflow can be detected at almost any method call, not to mention dozens of internal PDL-intensive routines (such as, say, journal output). As such, it would be almost impossible for an application to anticipate all the places where a PDL overflow might be detected. And even if it could anticipate the places, any method calls to handle the situation would likely also encounter a PDL overflow.
- Errors that are indicative of logic errors in a program.
For example, a class member reference via a Null object variable is usually indicative of a programming error. Similarly, a reference to an invalid collection item number is also generally indicative of a programming error.
While there might be error-causing cases of "sloppy" references to object variables or collection items that you would want to simply catch when they happen:
- Most of these are handled well by existing facilities such as Auto New and also UseDefault processing for collections.
- In the odd cases where such sloppy references are useful, it is trivial
and efficient to check for the sloppy references (Null object variable,
invalid collection item number), so an exception-handling paradigm adds little value.
Of course, there are always border-line cases where an error that is nearly always indicative of a programming error, might not be in certain contexts. Since the initial Sirius Mods support for exceptions was not accompanied by a complete survey of all error returns by all system methods, it is quite likely that many request cancelling errors in system methods will gradually be converted into catchable exceptions over time. This is also quite likely to be the case for many errors in user-written methods.
Note that Model 204 does provide SOUL facilities to catch even these types of untrappable errors. These facilities include On units, especially On Error units, and APSY error procedures. These facilities make it possible for SOUL programs to, at least, provide nicer notification of an error to a client (web browser, 3270 user, SOAP client) than a broken connection or a Model 204 error message.
Even these facilities, however, are limited by severe environmental resource constraints. For example, if a CCATEMP or record-locking-table full error drives an APSY error procedure or an On Error unit, there might be little these error routines can do without bumping into the very same resource constraints that caused the problem in the first place. This problem is not unique to Model 204 — severe environmental constraints can wreak havoc with the best laid error recovery plans in any environment.
Exception class definitions
Most error conditions have information associated with them. This information can be useful for correcting or recovering from the problem. For example, if an invalid character is detected in a stream of data, it might be useful to know the character offset in the stream, the actual invalid character, and some indication of why the character is invalid. It might also be useful to have a text description of the nature of the error.
The collection of data describing an error situation might be used immediately after the error is detected, or it might be examined elsewhere. As such, this error data should be able to persist indefinitely. Given this requirement, it is clear that the logical place to hold error information is inside an object. And the description of the data associated with a particular error would, of course, be a class.
Classes that describe trappable error situations are called exception classes. To a large degree, exception classes are no different from any other class:
- They can have Public, Private, and Shared blocks; they can have variables, methods, and the same Allow and Disallow rules as any other class.
- There can be system exception classes, which are provided as part of the Model 204 installation, and there can be exception classes which are defined and maintained by SOUL code.
- Exception classes can be used wherever non-exception classes are used, although the reverse is not true. That is, there are certain statements that require an exception class or an object of an exception class. This is largely to prevent accidental misuse of a non-exception class in an exception class context.
User-defined exception classes are denoted by using the Exception keyword in the class header:
class tackyColor exception
In many object-oriented languages, a class is indicated as an exception class by extending some special base class, often called "Exception." One can think of the Exception keyword on the Class declaration as indicating that the class extends some hidden base class. This neither adds nor detracts from an understanding of the basic concept of an exception class.
If you want, you can put the Exception keyword on almost all class declarations; specifying it removes no capabilities from the class. Doing so, however, is misleading, since most classes are likely never to be used to indicate error situations. In this regard, you can view the Exception keyword on a class declaration as documentation that a class can be used as an exception class. While the compiler cannot ensure that a class with an Exception declaration will be used as an exception, it does ensure that a class not declared as Exception is not used in exception contexts.
The one case where the Exception keyword is not allowed on class declarations is in declarations that extend non-exception classes — exception classes can only extend other exception classes.
Some examples of system exception classes are:
- InvalidHexData
- Describes an error in converting hexadecimal data to some other datatype.
- InvalidRegex
- Describes an error processing a regular expression.
- DaemonLost
- Describes a situation where the thread doing processing for a Daemon object was lost (logged off).
This list is far from exhaustive, but it illustrates that system exception classes might be associated with a specific system class. For example, the DaemonLost class is clearly associated with the Daemon class. On the other hand, an exception class might be associated with a facility that's used by many different classes. For example, the InvalidRegex class is not associated with any specific class, because regular expressions are used in Stringlist and Intrinsic methods.
For links to the descriptions of the individual system exception classes, see the "Lists of classes and methods".
Throwing exceptions
When an error situation occurs, the code that detects the situation can do one of two things:
- It can cancel the request. In SOUL this is done with an Assert statement. In system code, this is done by some equivalent of the Assert statement. This is the correct response to an error if the error is clearly indicative of a programming error or a severe environmental constraint that is likely to make request continuation unproductive.
- It can throw an exception.
In SOUL this is done with a Throw statement.
In system code, this is done by some equivalent of the Throw statement.
This is the correct response to an error if the error might occur in the
normal course of processing and the code that called the method might be
able to recover from the error.
Note: In the SOUL implementation of exceptions, exceptions can only be thrown by methods.
In both system and user-written methods, a thrown exception can only be handled (caught) by the code that called the method. This is different from many other languages' implementations of exceptions where exceptions can be handled locally, that is, inside the same method that threw the exception. Like many other languages, the exceptions that might be thrown by a method must be documented in the method header. This is because a method's exceptions are part of the method's interface to its callers, as much as any input and output parameters, as much as output values.
Specifying a Throws clause
The exceptions thrown by a method are indicated by the Throws
clause in the method declaration and definition header.
For example, if the method Paint
might throw a TackyColor
and InvalidHexData
exception, it should be declared and defined as:
subroutine paint(%hexColor is string len 6) - throws tackyColor and invalidHexData
Of course, while the keyword And is used to separate the exceptions that might be thrown by a method, the method can only ever throw one exception at a time, and in most cases, it will not throw an exception at all.
The list of exceptions after a Throws keyword is the list of exception class names.
These class names could be user-defined exception class names, or they could be system
exception class names.
If the class is a system class name, it could be fully qualified with the System:
namespace indicator.
For instance, the previous example could be written as
subroutine paint(%hexColor is string len 6) - throws tackyColor and system:invalidHexData
This would only be necessary if there was a user-defined class name with the same name as the system class name. Of course, it is best to avoid such a situation as much as possible.
As with most method descriptions, the Throws clause on a method declaration and definition must match exactly. That is, the mismatch in the following definition and declaration is invalid:
class house public ... subroutine paint(%color is string len 6) - throws tackyColor and outOfPaint ... end public ... **** The following produces a compile error **** subroutine paint(%color is string len 6) - throws tackyColor ... end subroutine ... end class
It is also invalid if, in the method definition, the list of thrown exceptions contains the same exceptions as the method declaration, but in a different order.
Methods that implement an overridable method cannot throw any
exceptions not thrown by the implemented method; however, they do not
necessarily have to throw all the exceptions thrown by the implemented method.
That is, the exceptions in a Throws list on an Implements method
must be a subset of the Throws list on the corresponding
Overridable (or Abstract) method.
For example, if an overridable method in class Products
indicates:
subroutine buy(%productCode is string len 8) - overridable - throws tooExpensive and outOfStock
then it is valid for an implementing method to indicate:
subroutine buy(%productCode is string len 8) - implements buy in products - throws tooExpensive
or even:
subroutine buy(%productCode is string len 8) - implements buy in products
However, it is not valid for the implementing method to throw an
exception other than TooExpensive
or OutOfStock
.
This is because a caller of the base class only expects the Buy
method to throw one of these two exceptions.
It would be surprising, indeed, if the method threw some different
exception, simply because it had been overridden.
Using the Throw statement
Once a method declaration indicates the exceptions it might throw, that method could then throw the exception with the Throw statement. The Throw statement must be followed by an instance of the exception class being thrown. For example, if the method header contains
subroutine paint(%color is string len 6) throws tackyColor
and there is a variable declaration in the method:
%tacky is object tackyColor
The method could do this:
throw %tacky
Of course, the Throw statement is likely to be inside an If clause, since exceptions are generally thrown in unusual situations, not common ones:
if %color eq %yuckyGreen or - %color eq %grottyOrange then throw %tacky auditText Threw an exception end if
Note that in the above example, the AuditText statement after the Throw will never be executed. This is because the Throw either returns immediately to the method caller (if the method caller catches the exception), or it cancels the request immediately.
Another problem with the above example is that %tacky
was likely to never have
been set to reference a TackyColor
instance.
So, more correct would be something like:
if %color eq %yuckyGreen or - %color eq %grottyOrange then %tacky = new throw %tacky end if
But, even this is not quite right. Usually, the exception objects will contain information to aid in problem determination and recovery:
if %color eq %yuckyGreen then %tacky = new %tacky:reason = 'Yucky' %tacky:alternative = '00FF00' throw %tacky end if if %color eq %grottyOrange then %tacky = new %tacky:reason = 'Grotty' %tacky:alternative = 'FFA500' throw %tacky end if
This example illustrates the point that it is common for any Throw of a particular exception class to always return more or less the same information. As such, exception classes often have constructors that can specify all the information provided by the class. Then, no variables of the exception class need to be defined; the result of the constructor call can simply be thrown:
if %color eq %yuckyGreen then throw %(tackyColor):new('Yucky', '00FF00') end if if %color eq %grottyOrange then throw %(tackyColor):new('Grotty', 'FFA500') end if
Of course, for code readability, named parameters are preferable on such constructors:
if %color eq %yuckyGreen then throw %(tackyColor):new(reason='Yucky', alternative='00FF00') end if if %color eq %grottyOrange then throw %(tackyColor):new(reason='Grotty', alternative='FFA500') end if
An attempt to throw an exception object whose class does not match one of the classes listed in the method declaration's Throws clause results in a compilation error.
Exception classes extending other exception classes
Exception classes can extend other exception classes. As such, the class of an object specified in a Throw statement does not have to match any class in the method's Throws list exactly — it can be of an extension class of one of the Throws list classes. Because an extension class can, itself, be extended, and because of multiple inheritance, this means that a Thrown exception object might match multiple classes in a method's Throws list.
The thrown (and therefore catchable) class is the first class in the Throws list that matches the object in the Throw. That is, the first class in the Throws list that exactly matches the thrown object's class or that is a base class of the thrown object is used as the thrown class for the method caller. Because of this, a Throws list must always specify extension classes before base classes. Otherwise, the base class in the Throws list would always match any object that would match an extension class, so the extension class would never be used as the thrown class.
For example, if exception class ReallyNastyColor
extended exception class
TackyColor
, and method Paint
had this header:
subroutine paint(%color is string len 6) throws tackyColor
And if the following statement appeared inside the Paint
method;
throw %(reallyNastyColor):new(reason='Awful')
The new ReallyNastyColor
exception object would be thrown as a TackyColor
object.
That is, the callers of Paint
would not be able to assign the thrown
exception to a ReallyNastyColor
object without the use of a narrowing assignment.
Try and Catch
Without any action on a method caller's part, a thrown exception is, for all intents and purposes, a request cancelling error. To prevent the request cancellation, an exception must be "caught." This is achieved by the use of a Try/Catch block.
A Try/Catch block consists of two parts. The first, the Try section, contains one or more SOUL statements that might result in an exception. The Try section is then followed by one or more Catch sections, each one of them handling (catching) a particular class of exception.
The following fragment shows the use of a Try/Catch block to trap an exception caused by invalid hexadecimal data:
try %binary = %input:hexToString catch invalidHexData %binary = '???' catch invalidBase64Data %binary = '???' end try
This example illustrates a few points:
- The end of the statements whose exceptions are being caught is indicated by the first Catch block.
- The class of exceptions being caught follows the Catch statement.
- A catch block is terminated by another Catch statement or an End Try.
- One can catch multiple exception types, each within its own Catch block.
- There is no validation that the type of exception being caught might actually be thrown inside the Try block. In the preceding example, there would be no chance for an InvalidBase64Data exception inside the Try block since the HexToString method will not throw such an exception. Having a block to catch an exception that's not possible is no different from having an If/Else If block for an impossible condition — it simply adds some dead code to a request.
Try block syntax
Try tStmt1 ; * Exceptions can [ tStmt2 ] ; * be caught in [ ... ] ; * these statements [ Rethrow rethClassA [And rethClassB ] ... ] ... any number of Rethrow statements [ Catch catClassC [To %objC] [And catClassD [To %objD]] ... [ cStmtW ] ; * These are executed if any [ cStmtX ] ; * of the execptions on the [ ... ] ; * Catch statement are thrown ] ... any number of Catch blocks [ Success [ sStmtY ] ; * These are executed [ sStmtZ ] ; * if no execptions [ ... ] ; * are thrown ] End Try
Syntax terms
The Try block is divided into two or more sections, which are separated by Rethrow, Catch, and Success statements. Multiple Rethrow and Catch statements can be mixed with each other and with the Success statement, in any order.
There must be either one Rethrow or Catch statement in a Try block.
tStmt1 [tStmt2...] | The first section in the Try block must contain one or more SOUL statements. These statements are executed in turn, and if an exception is thrown within them, execution resumes at the Rethrow or Catch statement specifying that exception; if there are none, the request is cancelled. If no exceptions are thrown, execution resumes with the Success block statements, if any, and then after the End Try statement. |
---|---|
Rethrow rethClassA ... | The Rethrow statement (not block) specifies one or more exception classes; if one of them is thrown, then the method containing the Try block throws that exception. Hence a Rethrow statement can only occur within a method which declares all of the rethClass exceptions in its Throws clause. Each rethClass may not be the same as, nor an extension of, any exception class preceding it on the statement or on preceding Rethrow and Catch statements in the Try block. |
Catch catClassC ... | The Catch statement specifies one or more exception classes; if one of them is thrown, then the (optional) cStmt statements in the Catch block are executed in turn, and then execution resumes after the End Try statement. Each catClass may not be the same as, nor an extension of, any exception class preceding it on the statement or on preceding Rethrow and Catch statements in the Try block. |
Success | If no exceptions are thrown in the tStmt statements, the (optional) sStmt statements in the Success block are executed in turn, and then execution resumes after the End Try statement. |
Try block considerations
A Try block can contain more than one statement:
try %binary = %input:hexToString %bin = %input64:base64ToString %key = %inputKey:hexToString %foo:process(%binary, %bin, %key) catch invalidHexData %binary = '???' catch invalidBase64Data %bin = '???' end try
While valid, this example is intended to show that it might not be a good idea to put a lot of statements inside a Try block:
- It obscures which statements might be throwing which exceptions, and this makes the code harder to read.
- The more statements you have inside a Try block, the more
likely it is that you will accidentally catch an exception in a
statement in which you were not expecting an exception.
For example, in the above code, the
InvalidHexData Catch
block is clearly fixing a problem with invalid hexadecimal data in%input
. But, it will also catch an exception thrown by invalid hexadecimal data in%inputKey
, and in this case, probably it will do the wrong thing. - All the code in the Try block after the method that threw
the exception will not be executed.
While, in some cases, this is what would be intended, there are many
cases where this would not be intended.
In the above example, it is likely that one would want to try to
execute the Base64ToString method on
%input64
after "correcting" errors in executing HexToString on%input
.
As a general rule of thumb, place as few statements inside a Try block as possible. To facilitate this, you can follow the Try statement by a SOUL statement on the same line, as in:
try %binary = %input:hexToString catch invalidHexData %binary = '???' end try
Referencing a thrown exception object
As noted in "Using the Throw statement", what gets thrown with an exception is an exception object that contains information about the nature of the exception. In the Try/Catch examples to this point, the thrown objects were ignored — only the class of the thrown exception was used. If you want to reference the thrown exception object, you must specify a To clause, followed by an object variable of the exception class being caught.
For example, if %daemon
is a Daemon object and %daemonLost
is an object of the DaemonLost exception class, the following block
catches the exception thrown if the daemon thread was logged
off for some reason, and it displays the output of the last command up to the point where it logged off:
try %daemon:run('INCLUDE NASTY') catch daemonLost to %daemonLost printText Daemon died! Its last words were: %daemonLost:daemonOutput:print end try
Presumably, this block would be useful in diagnosing the problem or even correcting it.
Rethrow
Syntax
Rethrow exceptionName ...
Description
Sirius Mods 8.1 introduced the Rethrow clause in a Try block. Rethrow lets you propagate an exception without having to assign it to a local variable, probably eliminating the need to declare a local exception class object for the sole purpose of propagating an exception.
The Rethrow clause must occur at the same level as a Catch clause in a Try block. But, unlike the Catch clause, no code is allowed "inside" the Rethrow "block." Because Rethrow causes control to pass immediately out of the current method, no code after Rethrow would ever be executed.
A Rethrow clause can only be followed by an End Try, a Catch clause, or a Success block. Like the Throw statement:
- The Rethrow clause must be invoked inside a method.
- The class or classes specified on a Rethrow clause must be declared as being thrown by that method.
Example
The following local subroutine illustrates a Rethrow of an InvalidHexdata exception. This subroutine sends a string converted from hex to binary on a socket.
local subroutine (socket):sendHex(%hexdata is longstring) throws invalidHexData %sendData is longstring try %sendData = %hexdata:hexToString rethrow invalidHexdata end try %this:send(%sendData) end subroutine
Mutiple classes on a Catch or Rethrow statement
Sirius Mods 8.0 introduced the ability to specify multiple exception classes on a single Catch statement. The class names are separated from each other by the And keyword. However, in Sirius Mods 8.0, no To clause could be specified on a Catch statement with multiple classes.
Sirius Mods 8.1 added support for To clauses on a Catch statement with multiple classes as well as including support for multiple classes on the Rethrow statememt.
The following illustrates a Catch statement with multiple classes:
try %foo:doSomethingCrazy catch invalidHexdata to %ivhexData and tooCrazy and notCrazyEnough to %notCrazyEnough end try
If the above were inside a method, the exceptions could be rethrown:
try %foo:doSomethingCrazy rethrow invalidHexdata and tooCrazy and notCrazyEnough end try
Of course, if a Catch block catches multiple class exceptions, it might be important to determine which exception was actually thrown. In that case, a null test can be performed on the target objects:
try %foo:doSomethingCrazy catch invalidHexdata to %ivhexData and and tooCrazy and notCrazyEnough to %notCrazyEnough ... if %ivhexData is not null then ... elseIf %notCrazyEnough is not null then ... end if ... end try
For this to work, one must be sure that the target catch objects are null before the Try block. And, unless there is a lot of common code for the classes in the Catch block, it probably makes more sense to just have a separate Catch block for each exception.
There is no difference between rethrowing exceptions in a single Rethrow statement and multiple Rethrow statements, so it is largely a matter of style as to which approach is used.
Success blocks
In cases where a Try block contains multiple statements, a Success block makes it clear in the code which statement is expected to produce the exceptions that are being caught. These blocks also protect you from an inadvertent exception thrown in an unexpected context.
For example, consider the following scenario.
You want to try statement <a>
and, if no exceptions get Thrown,
you want to do statements <b>
, <c>
, <d>
, and <e>
.
Otherwise, if statement <a>
throws an exception,
you want to do statements <x>
, <y>
, or <z>
, depending on the exception.
You code your Try/Catch block like this:
try <a>... <b>... <c>... <d>... <e>... catch foo <x>... catch bar <y>... catch another <z>... end try
If statement <a>
does indeed throw an exception, statements <b>
through <e>
do not run, and the appropriate Catch statement takes effect.
However, if statement <a>
does not throw an exception,
there might be no way to know that statement <b>
, <c>
, <d>
, or <e>
might
throw an exception that is one of the exceptions in the subsequent Catch statements.
Or you might be aware of their capacity to do so,
but you might not expect an exception from any of them in this context. Prior to
Version 7.8 of the Sirius Mods, there was
no good way of preventing the catches to also be in effect for these statements
as well as for statement <a>
.
As of Sirius Mods 7.8, a Success block inside the Try block
resolves the problem by making it clear that the Catch statements do not apply to
statements <b>
, <c>
, <d>
, and <e>
:
try <a>... success <b>... <c>... <d>... <e>... catch foo <x>... catch bar <y>... catch another <z>... end try
The principle benefits of the Success statement are:
- It makes it clear in the code which statement is expected to produce the exceptions being caught.
- It prevents a catch from accidentally catching an exception from a statement that didn't really expect that exception.
You can also reverse the order of the the Success and catches:
try <a>... catch foo <x>... catch bar <y>... catch another <z>... success <b>... <c>... <d>... <e>... end try
Some differences with other languages
This section describes some differences between the SOUL implementation of Try/Catch and implementations in other languages. Outside of these differences, Try/Catch support in SOUL is very similar to that in other languages.
- Unlike Java, it is not necessary to provide a Catch for all exceptions that a method might throw. This seems antithetical to the idea that exceptions are unusual conditions and, in many instances, are known to be impossible. It seems unnecessary to add code to deal with (or ignore) an error that cannot occur in a context.
- Unlike many other languages, uncaught exceptions are not automatically
propagated to higher level callers.
Partially, this is because the SOUL environment is not written in SOUL, so
there is no need to propagate exceptions to some outer SOUL environment
which, presumably, would clean up the failing request and possibly provide
diagnostics about the error.
Instead, clean-up and diagnostics are provided by the assembler environment.
Automatic propagation of errors is also a grand opportunity for bugs. Since an automatically propagated error can happen anywhere in a method, there is no indication that the writer of a method considered the possibility of the propagated error. Updates to data-structures, including transactions that update files, might be half-done at the time of the occurrence of an exception that gets automatically propagated.
While languages that provide automatic exception propagation usually also provide a
Finally
clause to "ensure" that the method doesn't leave things in a half-done state, there is nothing in a Finally clause that indicates whether the programmer anticipated the particular error being propagated. And the absence of a Finally clause does not prevent error propagation, anyway.If you want to propagate an exception in SOUL, you can simply catch it and re-Throw it:
catch tackyColor to %yuck * let caller deal with this throw %yuck
This makes it clear which error is being propagated and under what circumstances.
- The absence of automatic exception propagation eliminates the utility of a Finally clause, so no Finally clause is available in Try/Catch blocks in SOUL.
- Catch statements cannot catch locally thrown exceptions. That is, a Throw statement always results in immediate exit from the current method, regardless of whether or not the Throw is inside of a Try block and whether or not there are Catch statements that correspond to the thrown exception. The SOUL view of exceptions is that they are part of the interface between a method and its callers, so they have no place in controlling local program flow.
Nesting Try/Catch blocks
Like in other languages, Try/Catch blocks can be nested. That is, a Try/Catch block can be inside the try or catch clause of another Try/Catch block:
try %dmn:run('I STEP1') try %str = %hex:hexToString %dmn:run('I STEP2 ' with %str) catch invalidHexdata %dmn:run('I STEP2 ???') end try catch daemonLost auditText My daemon's gone! try %str = %hex:hexToString catch invalidHexdata %str = '???' end try return %str end try
The Catch that applies to a particular thrown exception is the first Catch that either exactly matches the class of the thrown exception, or that is a base class of the thrown exception. Because of this, it is possible to catch many different exceptions with a single Catch if all the exceptions are extensions of a specified base class. For this reason, too, it is invalid to specify a Catch for an exception class after a Catch for a base class of that exception class, since the base class Catch will always catch the exception class exception before the exception class Catch is processed. If separate Catch statements are required for base and extension classes, specify the extension class Catch statements first.
Because there is no single base exception class for all exceptions (as there is in many other languages), however, it is not possible to generically catch any and all exceptions with a single Catch. While this ability might be convenient, it is also likely to encourage sloppy programming, where generic Catch statements obscure the possible errors in a context, and make it all too easy to catch unanticipated errors and do the wrong thing.
For Try blocks nested inside of other Try blocks, if no Catch statements that correspond
to a thrown exception are found for the inner-most Try block, then the outer-most
Try block's Catch statements are checked for a match, and they are used if a match is found.
That is, when Try blocks are nested, all Try blocks are in effect for statements
inside the inner-most Try block.
Note, however, that other Catch statements for a Try block are no longer in effect
inside any of the Catch blocks for the Try.
For example, if a request had a Daemon object %dmn
, and if %errorhex
had invalid hexadecimal data in it in the following block:
try %dmn:run('NASTY') %str = %hex:hexToString catch daemonLost %str = %errorhex:hexToString catch invalidHexdata %str = '???' end try
The Catch InvalidHexData
above would not catch the conversion error, because the conversion is not
inside the Try block associated with that Catch — it is in a Catch block.
OnThrow and OnUncaught
An exception class might want to perform special processing at the time an exception is thrown:
- It might want to make sure the exception object has valid data.
- It might want to record diagnostic information, perhaps to the audit trail or perhaps to some Model 204 file.
- It might want to derive some variable values that might not necessarily have been derivable in the Constructor.
To provide this capability, SOUL special-cases two method names in a user-defined exception class: OnThrow and OnUncaught. Both of these methods must be Subroutines (as opposed to Functions or Properties) and cannot have parameters.
The OnUncaught subroutine is automatically called whenever an exception of the containing class is thrown, and the exception will not be caught. The OnThrow subroutine is automatically called whenever an exception of the containing class is thrown, and either the exception will not be caught and there is no OnUncaught method in the class, or the exception will be caught.
These two method names have no meaning in non-exception classes.
These methods can be called explicitly, and they can be either Private or Public (though whether they are Public or Private is irrelevant for implicit calls when an exception is thrown).
The following illustrates an OnThrow subroutine that makes sure the exception data is valid at the time an exception is thrown:
class pratfall exception public variable sound is string len 32 subroutine onThrow end public subroutine onThrow assert %this:sound eq 'splat' or - %this:sound eq 'boing' end subroutine end class
The following illustrates an OnUncaught subroutine that logs information from the exception to the audit trail before allowing the request to be cancelled:
class pratfall exception public variable sound is string len 32 subroutine onUncaught end public subroutine OnUncaught auditText Taking a pratfall -- {%this:sound} end subroutine end class
If SirFact is available and capturing dumps for requesting cancelling errors, all the information one would need is likely to be in the dump, so there is probably little need to collect extra data in an OnUncaught subroutine.
There is no way for an OnUncaught or OnThrow subroutine to undo the effect of the exception, that is, to prevent a request cancellation if the exception is uncaught. Both routines, however, can force a request cancellation, perhaps by using an Assert statement, even if the exception would have been caught. If a request cancellation occurs inside on OnThrow subroutine for an exception that's about to be caught, the catching statements are not executed, because the request is cancelled before the OnThrow subroutine returns.
The Throws clause is invalid on an exception class's OnThrow and OnUncaught subroutines, so these subroutines cannot, themselves, throw an exception (for, hopefully, obvious reasons).
The Overridable and Implements clauses are also not valid in an exception class's OnThrow and OnUncaught subroutine declarations. However, their behavior is similar to overridable routines:
- If an extension exception class contains an OnUncaught or OnThrow routine, that routine will be called, when appropriate, rather than the base class routine.
- The extension class routine can call the corresponding
base class routines as it deems fit.
It does this, however, by using the subroutine name (qualified
with the class name) rather than with the superclass method (since
no overriding is involved):
subroutine onThrow auditText Calling base class OnThrow %this:(fooError)onThrow auditText returned from base class OnThrow end subroutine
In this example, the extension exception class's base class is called
FooError
. Of course, an OnUncaught in an extension class can also call an OnThrow for a base class (or even for its own class).
If an OnUncaught subroutine exists in a base class, but the extension class contains only an OnThrow subroutine, the base class OnUncaught routine will be called if an extension class object is thrown as an exception and the exception will not be caught. That is, an OnUncaught subroutine will always be called for uncaught exceptions if one is available in the exception's class or any base class, regardless of whether there are any OnThrow subroutines in the exception class or any of its base classes.