Object Oriented Exception Handling in Perl
by Arun Udaya Shankar
|
Pages: 1, 2
Catch Block
The try block associates the scope of its associated exception handlers. You associate exception handlers with a try block by providing one or more catch blocks directly after the try block:
|
Related Reading
Programming Perl |
try {
....
}
catch IOException with {
....
}
catch MathException with {
....
};
The syntax is: catch CLASS with BLOCK
This enables all errors that satisfy the condition $ex->isa(CLASS) to be handled by evaluating BLOCK.
The BLOCK receives two parameters. The first is the exception being thrown and the second is a scalar reference. If this scalar reference is set on return from the catch block, then the try block continues as if there was no exception.
If the scalar referenced by the second parameter is not set, and no exceptions are thrown (within the catch block), then the current try block will return with the result from the catch block.
In order to propagate an exception, the catch block can choose to rethrow the exception by calling $ex->throw()
Order of Catch Blocks Matter
The order of exception handlers is important. It's all the more critical if you have handlers at different levels in the inheritance hierarchy. Exception handlers that are built to handle exception types that are furthermost from the root of the hierarchy (Error) should be placed first in the list of catch blocks.
An exception handler designed to handle a specific type of object may be pre-empted by another handler whose exception type is a superclass of that type. This happens if the exception handler for that exception type appears earlier in the list of exception handlers.
For example:
try {
my $result = $self->divide($value, 0);
# divide() throws DivideByZeroException
return $result;
}
catch MathException with {
my $ex = shift;
print "Error: Caught MathException occurred\n";
return;
}
catch DivideByZeroException with {
my $ex = shift;
print "Error: Caught DivideByZeroException\n";
return 0;
};
Assuming the Inheritance hierarchy:
MathException is-a Error
[ @MathException::ISA = qw(Error) ]
DivideByZeroException is-a MathException
[ @DivideByZeroException::ISA = qw(MathException) ]
In the above code listing, the DivideByZeroException is caught by the first catch block instead of the second. That is because DivideByZeroException is a subclass of MathException. In other words, $ex->isa('MathException') returns true. Hence, the exception is handled by the code within the first catch block. Reversing the order of the catch blocks would ensure that the exception is caught by the correct exception handler.
Finally Block
The final step in setting up an exception handler is providing a mechanism for cleaning up before control is passed to different part of the program. This can be achieved by enclosing the cleanup logic within the finally block. Code in the finally block is executed irrespective of what happens within the try block. Typical use of the finally block is to close files or in general to release any system resource.
If no exceptions are thrown, then none of the code in the catch block(s) gets executed. But the code in the finally block is always executed.
If an exception is thrown, then code in the appropriate catch block is executed. Once the execution of that code is complete, the finally block is executed.
try {
my $file = join('.', '/tmp/tempfile', $$);
my $fh = new FileHandle($file, 'w');
throw IOException("Unable to open file - $!") if (!$fh);
# some code that might throw an exception
return;
}
catch Error with {
my $ex = shift;
# Exception handling code
}
finally {
close($fh) if ($fh); # Close the temporary file
unlink($file); # Delete the temporary file
};
In the above code listing, a temporary file is created in the try block and the block also has some code that can potentially throw an exception. Irrespective of whether the try block succeeds, the temporary file has to be closed and deleted from the file system. This is accomplished by closing and deleting the file in the finally block.
Remember, only one finally block is allowed per try block.
Throw Statement
throw() creates a new "Error" object and throws an exception. This exception would be caught by a surrounding try block, if there is one. Otherwise the program will exit.
throw() can also be called on an existing exception to rethrow it. The code listing below illustrates how to rethrow an exception:
try {
$self->openFile();
$self->processFile();
$self->closeFile();
}
catch IOException with {
my $ex = shift;
if (!$self->raiseException()) {
warn("IOException occurred - " . $ex->getMessage());
return;
}
else {
$ex->throw(); # Re-throwing exception
}
};
Building Your Own Exception Class
Setting the value of the $Error::Debug package variable to true, enables the capturing of stack trace for later retrieval using the stacktrace() method. (In case you are not familiar, stacktrace is a list of all the methods executed in sequence that lead to the exception).
The code snippet below creates the exception classes MathException, DivideByZero and OverFlowException. Where the latter two are subclasses of MathException and MathException by itself is derived from Error.pm
package MathException;
use base qw(Error);
use overload ('""' => 'stringify');
sub new
{
my $self = shift;
my $text = "" . shift;
my @args = ();
local $Error::Depth = $Error::Depth + 1;
local $Error::Debug = 1; # Enables storing of stacktrace
$self->SUPER::new(-text => $text, @args);
}
1;
package DivideByZeroException;
use base qw(MathException);
1;
package OverFlowException;
use base qw(MathException);
1;
And More ...
The error modules has other special exception handling blocks, such as except and otherwise. They are deliberately not covered here because they are specific to Error.pm, and you won't find them in other OO languages. For those who are interested, please refer the POD documentation that is embedded within Error.pm.
Fatal.pm
If you have some functions that return false on error and a true value on success, then you can use Fatal.pm to convert them into functions that throw exceptions on failure. It is possible to do this to both user defined functions as well as built-in functions (with some exceptions).
use Fatal qw(open close);
eval { open(FH, "invalidfile") };
if ($@) {
warn("Error opening file: $@\n");
}
....
....
eval { close(FH); };
warn($@) if ($@);
By default, Fatal.pm catches every use of the fatalized functions i.e.
use Fatal qw(chdir);
if (chdir("/tmp/tmp/")) {
....
}
else {
# Execution flow never reaches here
}
If you are fortunate enough to have Perl 5.6 or later, then you can circumvent this by adding :void in the import list. All functions named after this in that import list will raise an exception only when they are called in void context i.e. when their return values are being ignored.
By changing the use statement, as shown below we can be sure that the code in the else block is executed when chdir() fails
use Fatal qw(:void chdir);
The code listing below illustrates the use of Fatal.pm in conjunction with Error.pm.
use Error qw(:try);
use Fatal qw(:void open);
try {
open(FH, $file);
....
openDBConnection($dsn);
return;
}
catch DBConnectionException with {
my $ex = shift;
# Database connection failed
}
catch Error with {
my $ex = shift;
# If the open() fails, then we'll be here
};
The Perl6 Connection
Since the exception handling syntax in Perl 6 is expected to be modeled closely against Error.pm as detailed in the Perl 6 RFC 63 Exception handling syntax - http://dev.perl.org/rfc/63.pod, also in RFC 80 and RFC 88. It would make sense for developers to make use of OO-ish exception handling capabilities in their code and later on migrate to the exception handling syntax in Perl 6, as and when it is available.
Conclusion
The following are some of the key reasons to choose exception-handling mechanism over the traditional error-handling mechanisms:
- Error handling code can be separated from normal code
- Less complex, more readable and more efficient code
- Ability to propagating errors up the call stack
- Ability to persist contextual information for the exception handler
- Logical grouping of error types
So, stop returning error codes and start throwing exceptions.
Have an exceptional time !!

