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

Jim Thomason jthomasoniii at yahoo.com
Thu Nov 13 22:36:30 CST 2003


Alrighty then. I've been lurking for far too long and
I figure I should start participating again.
Particularly since I could use some suggestions. Yes,
yes, I'm selfish that way.

Okay, here's the issue. Our application is trusting.
Very trusting. It just assumes that a lot of things
won't go wrong. That's bad. Further, even when things
*do* go wrong, it tends not to report them. That's
worse. Beyond that, a lot of the time even if you
-wanted- to report them, you can't, because the buried
deep down module that actually has the error still
doesn't tell you. It's quite literally. "Oh no!
Something bad happened. Well, I'll forget about it and
return undef!"

And this is becoming a problem. People are starting to
request more specialized error messages. Spitting back
"An error occurred" for every occurence only gets you
so far, after all.

So, we'd like to get in some form of error handling
and do so relatively painlessly. Because this is, of
course, no longer a small application, with line
counts well into 5 digits and easily breaking 6 if you
count the template and HTML code.

Which brings us to some of the caveats that will
hinder  the "relatively painlessly" part.

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?

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.

The next thing we considered was linking that third
party error information to package or object metadata.
So you could say "recent_error_for($obj) or
recent_error_for($methodref)" or whatever. This has
some immediate concerns.

First of all, it still fails the same way in a
dispatch method. if foo() calls bar() which fails and
sets an error, then foo() reports it and we're all
fine. But if foo() then calls baz() which fails and
doesn't set an error, we'll probably end up reporting
back the earlier "bar" error. I can get around this by
explicitly wiping out the error message each time we
enter foo(), but that seems hack-ish.

The bigger issue is memory. Sure, when we first enter
a method, we know the object calling it, but that
object is not necessarily passed along to submethod
calls (and we can't go adding it in to all submethod
calls because we rapidly start to violate condtion 1)
). So the alternative was to have the error recording
actually populate all parents in the full caller()
stack. So foo() calls bar() calls par() calls tee()
and tee() has an error, then the error reported in
tee() stores elsehwere that same error linked to all
of the parent functions. This'll work, but it's
~extremely~ memory intensive. And memory is always a
concern, especially since we deploy under mod_perl.

So I think that approach is out as well.

But here, some happy news. I have one idea I've been
kicking around, and a friend on another system
suggested another.

My idea is as follows - create an uberclass that all
modules will eventually inherit from (everything's
pretty distinct now), and stick some error handling
routines in there. My personal favorite has always
been an error method that when called as an accessor
returns the error string and as a mutator returns
undef. So you can do:

sub foo {
  my $self = shift;
  return $self->error("We always fail. Sigh.") unless
0;
};

$obj->foo() || die $obj->error();

So we put that in the superclass and gradually
re-write things to inherit from it and use it. While
we do that, we tweak the modules as necessary so they
bubble up the errors from down below.

Now, naturally, normally bubbling up like that would
kill us, because the other modules wouldn't
necessarily have the error method, which brings us
into trouble with possibly violating (1). So the
solution? Hack UNIVERSAL. Give UNIVERSAL an error()
method that just returns something generic. That way,
as we enhance modules to use the uberclass, they can
bubble up generic errors to start with, which we'll
eventually replace with specific ones. 

Of course, hacking UNIVERSAL isn't very high on my
list of Good Things To Do, so I'm leary of it.


The other suggestion I received was to use exceptions.
Now, of course, exceptions would probably be a bad
thing since we'd have to run around and make sure that
they wer ehandled all over the app which would
instantly violate (1). But his suggestion was to write
some fairly beefy try/throw/catch/finally handler
blocks. Try would register the exceptions it'll catch,
and then throw will only die if something currently
says it'll handle it. Otherwise it'll return undef.
And then finally pops off the handling of that
particular exception. Messy, but we could probably get
it to work.

I'm more hesitant about this one for some other
reasons. First of all, I just don't like exceptions. I
know this is a religious debate, but I don't care for
them. Your code jumps around, there's no indication of
what's happening, and it ends up hiding the location
of the error from you. Yes, yes, some people think
that's good, but I don't. Like I said, it's a
religious debate. Further, we're not using exception
handling in the rest of the app, so I'm leary about
sticking it in here when it's so completely different.

Effectively what I'm saying here is that I'll use
exceptions if it's really the best and only choice,
but I'd really rather not unless I'm backed into a
corner.

And that's the dilemma. Anybody out there have any
suggestions? Again, we want to do it while:

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. 
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.

Any suggestions/recommendations/ideas/questions will
be appreciated. 

Thanks,

-Jim..........

__________________________________
Do you Yahoo!?
Protect your identity with Yahoo! Mail AddressGuard
http://antispam.yahoo.com/whatsnewfree



More information about the Chicago-talk mailing list