Monday, September 21, 2009

Exception Handling: Part 03 of 05

Part 3: Post-Modern Exception Handling with Classes

Target Audience: This pitch would interest a developer interested in the Exception Handling Best Practices in Perl. This series starts with basic exception handling and adds complexity, ending with pragmatic Best Practices recommendation.

I have never met Larry Wall, the inventor of Perl. I don't know what he is like as a person. However, I like the approach he brings to the programming world. Specifically, I like that his approach is to lighten up programming in areas where there is no reason to be grim. While sharpening focus on that which really deserves the attention of a creative developer.

In a sense, Larry's Perl is like English - a bitch that isn't faithful to any one narrowly-defined culture, religion, nation or community but goes around mixing promiscuously, taking attractive elements from here and there, whatever works, to become more agreeable and less strait-laced. There are people who find English a terrible language. Those are the kind of people who probably don't find Perl endearing. Those are also probably the kind of guys (and girls) who take notes in hard-bound books in neat handwriting with pagination and time-date stamps. As for me, Perl is my kind of bitch.

The post-modern approach isn't hide-bound. It doesn't prize originality over all else and is accepting of human foibles. One can be rather ingenious in putting together ideas freely and flexibly. And that is where I'd rather invest my time than spend a lot of it reinventing the wheel and unimaginative book-keeping.

Onward, onto Exception Classes, with this code-snippet.



use Exception::Class (
'My::Exception::Base',
'My::Exception::Base::Outer' => {
isa => 'My::Exception::Base',
description => 'Error outside',
},
'My::Exception::Base::Outer::Inner' => {
isa => 'My::Exception::Base::Outer',
description => 'Error inside',
},
);

What if I needn't write the 'throw' sub each time? What if I could leave it to .. say a Class. I could then dervive other classes from the base class to customize behavior. These specialized classes would inherit all methods of the base class, including the handy 'throw' method.

Hmm, all the goodness of object-orientated programming, at the finger-tips..

That is exactly what Exception::Class does! Now are you convinced?

What I've done is I have defined a hierarchy of exception-handling classes from the base class. In case you still need convincing, now I can query an exception's class and handle the exception accordingly.

An exception is thrown as:

My::Exception::Base->throw ("Unable to fopen $fname");

and handled as:

eval {
$secret = outer($fname);
};
if ($@) {
print $@->trace;
}

for obtaining a stack-trace.

Want to dispatch handlers based on the class type? You can do that now! Follow the simple querying patter illustrated in the snippet:

eval {
$secret = outer($fname);
};
if ($@->isa('My::Exception::Base::Outer::Inner')) {
print $@->trace;
}

Note how easy stack-tracing is with the 'trace' method. There are other methods of the base Exception::Class which are just as useful.

But hang on before you break out that bottle of bubbly! All your exception-handling woes are fast disappearing.. but there is still one niggling little problem. That problem comes from classes. Can you guess?

Remember, while your code is now (nearly) fit for exhibition in any museum of post-modern art, not everyone else's code is so! Particularly, I direct your attention to all those modules from CPAN, judging by all the 'use' directives your code is liberally peppered with. What about those modules? - if any one of those doesn't implement exception-handling based on 'Exception::Class', that breaks our model. Can you see why?

That little bit takes us to $SIG{__DIE__} in the penultimate part of this series. Here, now, is the complete code. What is the handler of all 'die' calls upto? That answer is coming Part 4.

use strict;
$, = "\t", $\ = "\n";
use Exception::Class (
'My::Exception::Base',
'My::Exception::Base::Outer' => {
isa => 'My::Exception::Base',
description => 'Error outside',
},
'My::Exception::Base::Outer::Inner' => {
isa => 'My::Exception::Base::Outer',
description => 'Error inside',
},
);

sub main {
my $fname = 'secret.dat';
my $secret;

$secret = outer($fname);

print $secret;
};

eval {
main();
};
if ($@) {
print $@->trace;
}

sub outer {
my $fname = shift;
my $secret;
$secret = inner1($fname);
return $secret;
}

sub inner1 {
my $fname = shift;
my $secret;
$secret = inner2($fname);
return $secret;
}

sub inner2 {
my $fname = shift;
my $secret;
$secret = inner3($fname);
return $secret;
}

sub inner3 {
#My::Exception::Base->throw ("junk");
my $fname = shift;
my $secret;
open(SECRET, "<$fname")
|| My::Exception::Base->throw ("Unable to fopen $fname");
$secret = < SECRET >;
close(SECRET);

return $secret;
}

local $SIG{__DIE__} = sub {
my $e = shift;
if ($e->isa('My::Exception::Base')) {
die $e;
} else {
die My::Exception::Base->new($e);
}
};

No comments:

Post a Comment