CSC/ECE 517 Fall 2009/wiki1b 3 expHandle
Describing the exception handling concepts in various Object Oriented Languages and pointing the nuances of difference in their implementation
Introduction
Exception Handling
Every functionality or use case in any application can have multiple outcomes. Depending on the result of the operation performed we have a happy flow, error flow or exception flow. The exception flow is triggered when some unexpected or unusual flow is initiated. This can be due to failure of external system failure or some unexpected user inputs. This can also be due to bad coding practices followed by the developer. Every language has some support for this kind of exception scenario. Either the exceptions are handled, thrown or initiated in a method. The mechanism differs in different languages, like in some languages like Java the entire cause of exception is made available till the point the exception is interpreted into a response, in languages like C’s atoi (ASCII to Integer) function the response if zero (0) even if the parsing fails, thus making it difficult to distinguish between a correct parsing of 0 or a parsing failure error.
Scope
Here let’s try and understand the way Exception Handling is implemented in various languages and then compare them based on
1) Ease of development
2) Simplicity on understanding
3) Resources freeing up (You will definitely want your code to close/release the file it tried to open or close a db Connection to the pool)
4) Error handling and transporting
Process of Exception Handling
Exception Handling in Perl 5
Legacy Perl (Perl 5.5 and earlier) is being discussed about and illustrated here. The newer advances and modules added in Perl 6 and the fix done on the issues (memory leak fixes for instance) is not been described here. Perl technically has a support for error handling rather than exception handling. They eval function is used to check for errors and the based on the status of "$@" variable the error handlers where written. The below code sample shows a sample implementation of this:
eval{ #do some process # do some more code }; if ($@ == err1) { errorHandler1($@); }if ($@ eq err2){ errorHandler2($@); }
The code eval function in case of an exception populates the $@ variable with the error message and terminates the execution of the script and transfers the control to line immediately after the eval block. In case of success, the $@ variable is assured of having a null string value The issue with this way of implementation is that the value variable $@ which is a scalar variable, can be easily changed either knowingly or accidently by the programmer. To add to it, the $@ variable is a global variable so the error information is available even in places where there exception should not have been transmitted as an exception. Also in case if one of the error code values is not checked for then that error goes unhandled and the correct information is not processed. This way of error handling also fails to give the location/trace of the exception making it difficult to ascertain the root cause. There was another way of implementation in Perl 5.005. In this way one could create a custom exception class and also invoke its method using the same syntax as in the earlier case.
eval{ .... }; if ($@) { if ($@->isa('MyFileException')) { # Specific exception handler .... } else { # Generic exception handler .... } }
This also left some of the issues still unaddressed which are listed below:
- eval blocks can be used for both, building dynamic code snippets as well as for exception handling. Hence can be confusing to look at and figure out the purpose on an eval block
- No built in provision for cleanup handler a.k.a finally block. Hence may involve repetitive lines of code of same purpose.
- Stack trace if required, needs to be maintained by writing custom code.
To handle the issues, the CPAN (Comprehensive Perl Archive Network) added two modules Error.pm and Exception::Class. The Error.pm is used in approximately 80 CPAN distributions and Exception::Class in 60 CPAN distributions. Following is the code example illustrating the use of the Error.pm module.
use Error qw(:try); try{ some code; code that might thrown an exception; more code; return; } catch Error with { my $ex = shift; # Get hold of the exception object handle the exception; } finally { cleanup code; };
This implementation style is similar to JAVA. The first code line indicates that the particular class will be using the Error.pm module and the :try implies that the various modules for performing exception handling will be referenced and used. In this case the try block executes the code and exception if any will be caught in corresponding catch block. The catch block catches the corresponding exception. It internally invokes the ISA operation. The order of the catch block is important in the sense while catching the child exception class’s catch block should be first and then up the hierarchy to parent. There are two arguments that are sent to the catch block, one the exception and the second one isa scalar reference. If the scalar reference is set in the catch block, then the try block will treat the return from the catch block as if there is no error that has occurred. If the scalar variable is not set and the error is not thrown from catch, then the try block will return the result returned by the catch block. The finally block is the missing clean up block. The finally block is executed during the successful try processing and during an error scenario. There are two more blocks in the error handling mechanism “except” and “otherwise” block. The except block if found by the try block will be used to process the errors. The return value from this block should be a HASHREF or a list of key-value pairs, where the keys are class names and the values are CODE references for the handler of errors of that type. The otherwise block will catch any error that occurs in the try block. The only argument passed to this block is the type of error being processed. The exception handling mechanism provides a throws clause that allows one to raise an exception. The clause can be invoked from anywhere in the code. In case the try block is missing in the calling hierarchy, then the program will be terminated.
The key things here are:
1) OO-ish kind of error handling
2) The try block calls the catch block and the catch block returns the result to the try block.
3) The additional two blocks except and otherwise.
Exception Handling in Ruby
Like any other OO-language, Ruby too has an exception handling mechanism based on OO-ish concepts i.e. creation of an exception object of a particular class and allowing it a method to transfer the exception up the calling hierarchy. The exception handling in ruby contains a begin clause just like the try block where the code that can throw the exception is written, followed by the rescue clause to rescue the code from the exception with an ensure clause to ensure the tidying up job is done (close the file opened), ending with an end clause. There are some other options like raise and retry to raise an exception to be handled by caller or for re attempting the error cause with or without change in the input conditions. The code snippet below shows the way exception handling is implemented in ruby.
begin ifile = File.open(opName,"r") buffer = ifile.read(512) # code to work on the buffer # more code # .. oFile = File.open(opName, "w") oFile.write(buffer); rescue SystemCallError print "System Error Occured: " + $! raise rescue StandardError => stdErrObj print "A general system failure: " + stdErrObj + " occured" raise end ensure ifile.close unless ifile.nil? end
The key for error handling is the variable $! which holds the exception description as returned by the begin clause as a result of some failure. When an exception occurs, the rescue block corresponding to the exception is invoked. In the example above if there is a SystemCallError then, the first rescue block is invoked. This is done by comparing the raised exception against each of the parameters in turn. If the raised exception matches the parameter, Ruby executes the code and stops looking. The match is made by using parameter === $!, which generally will mean that the exception named in the rescue clause will match the exception that is being raised. In case there is no matching rescue clause, then Ruby will move up the calling hierarchy and will try to find an exception handler in the caller, then the caller’s caller, so on. In Ruby the rescue clause can have multiple exceptions to be caught. In the example above, while rescuing the StandardError, the line StandardError => stdErrObj creates a local variable of the exsception object to be used in the rescue block instead of the $! variable. The last block in the example is the ensure block. As the name suggests this block is for ensuring that the tidying up or any mandatory code that needs to be executed irrespective of the result of execution is written here. The ensure block can go even without the begin and rescue clauses. The other two clauses that form a part of the Ruby exception handling package are the retry and raise clauses. Consider a system where we need to try logging into the system using different secure connection methods. The first attempt say uses SSL and the second one uses TLS. In case the first attempt fails we may want to reattempt using the different method before asking the user to re-enter the credentials. In such a case we use retry clause. The following code snippet exemplifies it:
connType = SSL begin # First using SSL # Connection fails. Login fails rescue ProtocolError # Change the connection type If (connType == TLS) raise else connType = TLS retry end
The raise clause allows the developer to raise exceptions for the caller to handle it. The syntax for raise clause is as below
raise | # raises a Current exception or a RuntimeError if no current exception is present. |
raise “Wrong Connection” | #a RuntimeError with “Wrong Connection” as the error message. |
raise ConnectionException, “Wrong Connection type”, caller | #A ConnectionException is raised with message and the stack trace of the error. |
One can create custom exceptions and can have methods to add more information to it.
The key points in Ruby exception handling are:
- Use of a try/catch/finally type of mechanism.
- Use of $! variable to propagate the exception.
- One rescue clause can be used to rescue/catch multiple exceptions.
- Allows the flexibility to raise a RuntimeError exception with a message.
- Gives the level from which the trace should be created.
Exception Handling in JAVA
JAVA provides a powerful mechanism from handling exceptions. It provides a try/catch/finally set of blocks along with a throws clause. The way an exception is triggered in JAVA is by using the throws clause. All the exceptions in JAVA fall under the hierarchy of java.lang.Exception class which in turn extends from java.lang.Throwable. The Exception class hierarchy in splits into RuntimeException and other exceptions. The exceptions that will occur during the program execution and cannot be predicted before hand, like for instance during File operations, a method can tell that it can face an issue during the operation and notifies the caller method of the possible exception that it will be throwing. For a NullPointerException which a type of RuntimeException, there is no way for the method to inform its caller and hence is called as unchecked exception. The notification is carried out by the use of the throws clause by the called method. The syntax for it is
public fileReader (String fileName) throws FileNotFoundException
One doesn’t have to throw all the exceptions that can happen in the called method. Only the exceptions that cannot be handled and are known that can be thrown during method execution. The throws clause can be used when one has to throw a new Exception. To throw it in that case all one has to do is:
throw new EOFException()
The exception thrown is handled by use of the try/catch/finally mechanism. The code snippet shows the syntax used:
try { // open a file // read a file } catch (FileNotFoundException fNFExp) { // Get the exception message } catch (IOException ioExp) { // Get the exception message } finally { // close the file after opening it }
In the try block the code that can potentially throw a checked exception is written. The exception is transferred as an object of a particular class. Instead of storing it in a fixed variable, JAVA stores the information in a new object of the exception class which is transferred by reference as a parameter to the catch clause. Then there are multiple catch blocks based on the number of potential checked exceptions. The JRE does and instanceOf check and identifies the corresponding catch block. Note that like in Perl the order of the catch clauses is important starting with the child exception till the parent exception. The control breaks out of the try block once there is an exception that is reported in the code execution and the control ends up in the corresponding catch clause. The output of the method is called the output the try block in case of success or the output of the catch block in case of an exception. The catch block can re-throw an exception of process it into some meaningful less severe output. The last block is finally block which is always invoked. As in rest of the languages this block is used for tidying purpose.
For creating custom exceptions one should have one’s class extend java.lang.Exception class or one of its child classes.
The key points here are:
- The clear classification of exceptions based on if they can be predicted or not. This makes it easier during compile time for the JVM to identify the potential places where exception handler is needed.
- The use of new object instead of a global variable for the exception propagation makes if difficult to change the exception object making it possible to retain most of the information.
- In Java exceptions though powerful should be used cautiously as they are resource intensive.
- The stack trace is available for the entire exception calls till handled by using the getStackTrace() method.
- Since JAVA can use a lot of configuration, this can be coupled with the exception handling mechanism to get configure exception handlers.
Exception Handling in Smalltalk
Smalltalk is one of the earliest OO languages and has a mechanism similar to the other OO languages. The exceptions here are known as Signals and are handled by signal handlers. Unlike other languages which use the concept of try this and if fails error will be caught in a catch, Smalltalk first defines the handler and then asks the code to do something. The following in code snippet will help in understanding the concept more:
aSignal handle:[:ex | "this is the handler ... some handling code... ex is the exception object." ] do:[ "this is the computation" ]
In this case whenever there is a signal of the type aSignal is raised due to the computation in the do: block, then the handler is invoked to handle the signal. The handler has an option of return to the caller method with an error message or an integer or gives a retry to do: computation (in case the file may be unlocked in a while to get an access) or reject the exception. When rejected the exception is handled by the next handler in the caller method. If there are not handlers defined then a new signal noHandlerSignal is initiated to raise an unhandled exception by default handlers. If this no defaultHandler, then the EmergencyHandler is invoked which opens a notifier window giving an option to debug.
The order of invocation of the handlers is as below:
1) enclosing #handle:do: handler, if any then (if no handler or rejected):
2) next enclosing #handle:do: handler(s), if any then (if none or rejected):
3) per-signal static handler block, if any then (if none or rejected):
4) NoHandler-signal #handle:do: handlers, if any then (if none or rejected):
5) NoHandler static handler-block, if any then (if none or rejected):
6) per-process emergency-handler-block, if any finally:
7) emergencyHandler defined in Exception-class
In Smalltalk the signals fall under the tree of the Object errorSignal and all the custom signals have errorSignal as parent. Hence in the handler chain it is necessary to maintain the handler hierarchy based on the signal.
The key features in Smalltalk exception handling are:
- There is one handler block for sure for processing the error.
- There is no block for tidying up the resources and hence we need to fix the things manually once each of happy and exceptional flow.