Object Oriented Exception Handling in Perl
by Arun Udaya ShankarNovember 14, 2002
The main goal of this article is to discuss in detail about exception handling in Perl and how to implement it using Error.pm. On our way, we'll be touching upon the advantages of using exception-handling over traditional error-handling mechanisms, exception handling with eval {}, problems with eval {} and the functionalities available in Fatal.pm. But by and large, our focus we'll be on using Error.pm for exception handling.
What Is an Exception ?
An exception can be defined as an event that occurs during the execution of a program that deviates it from the normal execution path. Different types of errors can cause exceptions. They can range from serious errors such as running out of virtual memory to simple programming errors such as trying to read from an empty stack or opening an invalid file for reading.
|
Related Reading
Computer Science & Perl Programming |
An exception usually carries with it three important pieces of information:
- The type of exception - determined by the class of the exception object
- Where the exception occurred - the stack trace
- Context information - error message and other state information
An exception handler is a piece of code used to gracefully deal with the exception. In the rest of article, the terms exception handler and catch block will be used interchangeably.
By choosing exceptions to manage errors, applications benefit a lot over traditional error-handling mechanisms. All the advantages of using exception handling are discussed in detail in the next section.
Advantages of Using Exception Handling
Object-oriented exception handling allows you to separate error-handling code from the normal code. As a result, the code is less complex, more readable and, at times, more efficient. The code is more efficient because the normal execution path doesn't have to check for errors. As a result, valuable CPU cycles are saved.
Another important advantage of OO exception handling is the ability to propagate errors up the call stack. This happens automatically without you, the programmer, explicitly checking for return values and returning them to the caller. Moreover, passing return values up the call stack is error prone, and with every hop there is a tendency to lose vital bits of information.
Most of the time, the point at which an error occurs is rarely the best place to handle it. So, the error needs to be propagated up the call stack. But by the time the error reaches the place where it can be handled suitably, a lot of the error context is lost. This is a common problem with traditional error-handling mechanisms (i.e. checking for return values and propagating them to the caller). Exceptions come to the rescue by allowing contextual information to be captured at the point where the error occurs and propagate it to a point where it can be effectively used/handled.
For instance, if you have a function processFile() that is the fourth method in a series of method calls made by your application. And also func1() is the only method interested in the errors that occur within processFile(). With a traditional error-handling mechanism, you would do the following to propagate the error code up the call stack until the error finally reaches func1().
sub func3
{
my $retval = processFile($FILE);
if (!$retval) { return $retval; }
else {
....
}
}
sub func2
{
my $retval = func3();
if (!$retval) { return $retval; }
else {
....
}
}
sub func1
{
my $retval = func2();
if (!$retval) { return $retval; }
else {
....
}
}
sub processFile
{
my $file = shift;
if (!open(FH, $file)) { return -1; }
else {
# Process the file
return 1;
}
}
With OO exception handling, all you need to do is wrap the function call to func2() within the try block and handle the exceptions thrown from that block with an appropriate exception handler (catch block). The equivalent code with exception handling is shown below.
sub func1
{
try {
func2();
}
catch IOException with {
# Exception handling code here
};
}
sub func2 { func3(); ... }
sub func3 { processFile($FILE); ... }
sub processFile
{
my $file = shift;
if (!open(FH, $file)) {
throw IOException("Error opening file <$file> - $!");
}
else {
# Process the file
return 1;
}
}
Since func1() is the only function to possess a catch block, the exception that was thrown in the processFile() function is propagated all the way up to func1(), where it would be appropriately dealt with, by the catch block. The difference in the bloat factor and code obfuscation level between these two error handling techniques is obvious.
Finally, exceptions can be used to group related errors. By doing this, you will be able to handle related exceptions using a single exception handler. The inheritance hierarchy of the exception classes can be used to logically group exceptions. Thus, an exception handler can catch exceptions of the class specified by its parameter, or can catch exceptions of any of its subclass.
Let's Do It in Perl
Perl's Built-In Exception Handling Mechanism
Perl has a built-in exception handling mechanism, a.k.a the eval {} block. It is implemented by wrapping the code that needs to be executed around an eval block and the $@ variable is checked to see if an exception occurred. The typical syntax is:
eval {
...
};
if ($@) {
errorHandler($@);
}
Within the eval block, if there is a syntax error or runtime error, or a die statement is executed, then an undefined value is returned by eval, and $@ is set to the error message. If there was no error, then $@ is guaranteed to be a null string.
What's wrong with this? Since the error message store in $@ is a simple scalar, checking the type of error that has occurred is error prone. Also, $@ doesn't tell us where the exception occurred. To overcome these issues, exception objects were incorporated in Perl 5.005.
From Perl 5.005 onward, you can do this:
eval {
open(FILE, $file) ||
die MyFileException->new("Unable to open file - $file");
};
if ($@) {
# now $@ contains the exception object of type MyFileException
print $@->getErrorMessage();
# where getErrorMessage() is a method in MyFileException class
}
The exception class (MyFileException) can be built with as much functionality as desired. For example, you can get the calling context by using caller() in constructer of the exception class (typically MyFileException::new()).
It is also possible to test specific exception types as shown below:
eval {
....
};
if ($@) {
if ($@->isa('MyFileException')) { # Specific exception handler
....
}
else { # Generic exception handler
....
}
}
If the exception object implements stringification, by overloading the string operations, then the stringified version of the object would be available whenever $@ is used in string context. By constructing the overloading method appropriately, the value of $@ in string context can be tailored as desired.
package MyException;
use overload ('""' => 'stringify');
...
...
sub stringify
{
my ($self) = @_;
my $class = ref($self) || $self;
return "$class Exception: " . $self->errMsg() . " at " .
$self->lineNo() . " in " .
$self->file();
# Assuming that errMsg(), lineNo() & file() are methods
# in the exception class
# to store & return error message, line number and source
# file respectively.
}
When overloading the string operator '"', the overloading method (stringify() in our case) is expected to return a string representing the stringified form of the object. The stringify() method can return various context/state information about the exception object, as part of the string.
Problems with eval
The following are some of the issues in using the eval {} construct:
- Similar looking syntactic constructs can mean different things, based on the context.
- eval blocks can be used for both, building dynamic code snippets as well as for exception handling
- No builtin provision for cleanup handler a.k.a finally block
- Stack trace if required, needs to maintained by writing custom code
- Aesthetically unappealing (Although this is very subjective)
Error.pm to the Rescue
The Error.pm module implements OO exception handling. It mimics the try/catch/throw syntax available in other OO languages like Java and C++ (to name a few). It is also devoid of all the problems that are inherent when using eval. Since it's a pure perl module, it runs on almost all platforms where Perl runs.
Use'ing Error.pm
The module provides two interfaces:
- Procedural interface for exception handling (exception handling constructs)
- Base class for other exception classes
The module exports various functions to perform exception handling. They will be exported if the :try tag is used in the use statement.
A typical invocation would look like this:
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;
}; # <-- Remember the semicolon
Don't forget to include the trailing semicolon (;) after the closing brace. For those of you who want to know why: All these functions accept a code reference as their first parameter. For example, in the try block the code that follows try is passed in as a code reference (anonymous function) to the function try().
try Block
An exception handler is constructed by enclosing the statements that are likely to throw an exception within a try block. If an exception occurs within the try block, then it is handled by the appropriate exception handler (catch block), associated with the try block. If no exceptions are thrown, then try will return the result of block.
The syntax is: try BLOCK EXCEPTION_HANDLERS
A try block should have at least one (or more) catch block(s) or one finally block.
Pages: 1, 2 |





