[Chicago-talk] Errors. And the reporting thereof.
Steven Lembark
lembark at wrkhors.com
Fri Nov 14 11:18:06 CST 2003
> 1) We have to be able to roll it out piece-meal into
> the system. Anything that would require an immediate
> and total re-write of everything to handle something
> new really isn't appropriate.
> 2) It always has to report the correct error back.
> (duh, but I'll address that in a moment).
> 3) It can't break any of the existing code. Virtually
> all of the error handling is done by returning
> something true upon success and undef upon failure.
>
> Easy, right?
Yes, actually.
> So far, here's what I've investigated and my results.
>
> I looked into using Error.pm (and the equivalents) but
> have decided against it because it violates condition
> 2, unless we violate condition 1 as well. The issue is
> that it stores the error in a third-party location
> ("prior Error"), which is fine as long as everything
> is always setting an error there. But my concern is
> that a method will fail, set an error, and return
> undef. Then later, another method will fail, not set
> an error, and return undef. Then if I try to report on
> that second failure, I'll end up with the error
> message from the first. Very bad, since we're now
> reporting the wrong thing instead of something
> generic.
One thing you can do at the #! level is to trap the
unhandled exceptions so that you have a better idea
where they happen. This can be implemented in stages
by $SIG{__DIE__} = sub { ... } to existing pieces of
code you can trap aptosis in your sub's -- that or
wrapping the top-level call in a block eval.
You can then start adding die clauses where undef's
are assigned to the returns:
$foo = undef;
becomes
die "Undefined $foo";
At which point the die handler (or top-level eval) can
deal with reporting the cause of death.
One way to handle this is:
#!/blah/perl
eval
{
your old code here...
};
die "$0: $@" if $@;
exit 0;
[useing perl -i to automate this if you like :-].
This will not affect the running code since none if it
throws exceptions.
Now go in and either modify the routines that see undef
with something like:
croak "Undef returned from foobar call";
or replace the return undef's with similar croaks:
croak "Useful Error Message Here" unless $var;
to die instead of return undef at all.
Personally, I prefer using exceptions since it
simplifies the calling code also: all it gets back
are values the called function considers legit. You
can handle the exceptions if you like via:
sub foo
{
for(;;)
{
eval
{
some call;
another call;
};
if( $@ =~ /errstring 1/ )
{
# deal with whatever caused error
# number one.
}
elsif( $@ =~ /blah/ )
{
# deal with blah errors
}
elsif( $@ =~ /roadkill/ )
{
# redispatch the exception
die "Roadkill: foo unable to handle $@ from bar";
}
else
{
# no errors, we're done.
last
}
}
}
If this looks pretty much like textbook exception handling,
you're right.
The nice thing about it is that the top-level handler reports
all errors and you can update the existing code incrementally
to handle the errors properly.
> my $self = shift;
> return $self->error("We always fail. Sigh.") unless
> 0;
> };
sub slurp
{
my $item = shift or die "Bogus foo: false object";
my $path = $item->{path};
die "Nonexistant: $path" unless -e $path;
die "Unreadable: $path" unless -r _;
open my $fh, '<', $path or die "Roadkill: $path: $!";
local $/;
# caller gets back the item's buffer
$item->{buffer} = <$fh>
}
for my $file ( @filz )
{
# ignore anything other than roadkill.
eval
{
$file->slurp or warn "Empty $file->{path}";
};
die "Fatal read: $@" if $@ && $@ =~ /Roadkill:/;
}
The error method can be modified to die with a useful message
rather than return undef easily enough, after which you start
putting eval's in the proper places.
--
Steven Lembark 2930 W. Palmer
Workhorse Computing Chicago, IL 60647
+1 888 359 3508
More information about the Chicago-talk
mailing list