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
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.