[Chicago-talk] Errors. And the reporting thereof.

Steven Lembark lembark at wrkhors.com
Fri Nov 14 14:04:11 CST 2003

-- Jim Thomason <jthomasoniii at yahoo.com>

>> 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 still pretty soundly violates condition (1) (the
> piece-meal roll out). Even if we do use the itty bitty
> commandline doohicky to automate it for us, we're
> still changing a massive amount of code, which
> wouldn't be good.

All changes are incremental: the wrapper does not
interfere with any existing code at all. After that
you can go into the places that return undef and
modify them singly.

> Besides, exactly what good does the top level die
> handler do for us? Unless there's some slick approach
> I'm not considering, we'd end up zooming to the top
> handler (the sighandler or the global eval) just about
> instantly. And when that happens we choke with a very
> generic error.

You start by [incrementally] adding die's for places
where roadkill is inevitable, giving them more specific
messages. This solves the immediate problem of being
able to incrementally improve your message handling.

As time passes, you can add exception handlers to
the places that now deal with undefs.

> We'd basically need the global handler to catch the
> die, note it somewhere, and return back to where we
> were, returning an error code in the process. As the
> stopgap 'til things would be ported, of course.

Adding the top-level eval is about being able to catch
things that otherwise would have gone unnoticed. After
that you begin working your way incrementally down the
code. Exceptions allow you to dive out of code at the
point where an error is detected with a specific message,
which is what you wanted.

> Again, violates condition (1). :) There's an awful lot
> of code involved here, and this approach seems to
> hinge upon us modifying all of it to catch things,
> lest the user get kicked out with the global die
> handler implemented up above.

The modifications can be incremental, initially at the
points where error messages are not sufficient now. After
the poorly behaved points are handled the others can be

> It does and it is. But exceptions aren't my preferred
> approach in general, and not in this case
> specifically. First of all, like I said, the system
> doesn't use exceptions now. So if we took an
> exceptions based approach, we'd have both exceptions
> and errorcodes running around at the same time, at
> least temporarily, since we're not going to re-write
> everything in one swoop to use exceptions instead.

No matter what process you use you'll have some mixture
running around since you won't be doing it all at once.

> In general, like I said, it's a religious debate. I'm
> not a fan of exceptions for a few reasons. First of
> all, you can end up hiding the location of the error
> from the user. If foo() handles an error, and calls
> bar(), which calls baz() which throws the error,
> you'll jump out of bar when it blows up and back into
> foo() with little indication as to what's going on.
> It's confusing.

Only if you want to design your code that way. You can
put multiple levels of exception handling in place for
cases where the errors are not fatal. You can also
croak -- confess to get nice, point-specific errors
with a full stack trace (see perldoc Carp).

> To alleviate some of the confusion, you can trap the
> error a level above and then re-throw it if it's not
> something you handle. But at that point, you've
> effectively re-implemented an error code based
> approach. The only advantage is that you're forced to
> handle it at at least one level. I don't see any real
> advantage to typing:

Not really. You have advantages in being able to exit
the offending code immedately and save yourself oodles
of if-ladders checking return codes for every call.

You are also making the bad assumption that messages
given with die are not descriptive. Nothing prevents
you from die "bogus file handle in Bar::foo".

> eval (
> 	$obj->foo();
> }
> if ($@ =~ /exception1/) {
> 	#handle
> elsif ($@ =~ /exception2/) {
> 	#handle
> }

That or use a dispatch table:

	$handlerz->{$@} if exists $handlerz{$@};

The main difference here is that a single exception can
jump multiple levels of call stack, which can be useful
in avoding the if-logic at every level for handling
roadkill. Nothing says you need eval's at every level
of the code.

> instead of:
> unless (defined $obj->foo()) {
> 	if ($obj->err =~ /exception1/) {
> 		#handle
> 	}
> 	elsif ($obj->err =~ /exception2/) {
> 		#handle
> 	}
> 	.
> 	.
> 	.
> };
> Same thing, just slightly different syntax. The only
> real difference is that an errorcode approach forces
> you to always bubble up (exceptions can be caught at
> higher levels), and the exceptions force you to always
> catch it at least once.

Then why did you ask the question in the first place?
If you prefer using bubble-up logic with error strings
then stick with it and just break the fingers of anyone
who codes without valid error messages.

> But that's all philosophy. If exceptions look like the
> best approach to take, we'll consider them.  But I
> don't think this'll work w/o lots of code re-working,
> which we don't want to do at once.

Which is why exceptions would work: they allow incremental
modification of the existing code. Nothing keeps you from
testing $obj->error while testing $@. The main thing is
being able to wrap things once and them modify them
incrementally moving down the call stack.

Steven Lembark                               2930 W. Palmer
Workhorse Computing                       Chicago, IL 60647
                                            +1 888 359 3508

More information about the Chicago-talk mailing list