[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;


	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:


		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
				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";
				# no errors, we're done.


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.

			$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