Beefy Boxes and Bandwidth Generously Provided by pair Networks
P is for Practical

Re: Dealing with errors in subroutines

by ELISHEVA (Prior)
on Sep 09, 2009 at 09:04 UTC ( #794303=note: print w/replies, xml ) Need Help??

in reply to Dealing with errors in subroutines

Great question!

I find myself debating over this one a lot. In theory, throwing exceptions should create cleaner code because callers don't have to worry about processing garbage if they ignore an error. Die will cause them to promptly exit. And if you don't want to promptly exit the subroutine, you can always stop the die request using an eval block.

In practice, I find exceptions hard to use in Perl. Perl's syntax for handling exceptions is nowhere near as concise as its string processing. This can sometimes lead to a lot of noisy boiler plate code when you do want to handle exceptions. For example, you can't just check $@ to see if there is an error. $@ is a global variable. If code is attached to a DIE signal or a DESTROY method, dying will trigger the code. That code can easily reset $@ (see Devel::EvalError for an example and discussion). Instead you need to test the return value of the eval block, like this:

# be nice to callers - don't wipe out $@ for them local $@; eval { # ... do some stuff ... # make sure we only return false if we die before # the end return 1; } or do { #handle errors here }

Clean exception handling code needs to be able to pick out the exceptions it cares about with the least amount of verbage. When you process errors as return values, you can generally assume a very limited range of possible errors. However, if methods are allowed to ignore exceptions and just die, this will mean that your highest level code should expect that exceptions are coming from anywhere. To handle exceptions that may have been triggered 10 stack frames down, one needs a good way of classifying exceptions and selecting the exceptions you care about.

Languages like Java do this using a combination of exception classes and try {...} catch (SomeClass e) {...} catch (SomeOtherClass e) {...}. Exceptions are organized into a standard hierarchy and there is provision to extend that class hierarchy with exceptions of your own. When you write a method, you document which class of exceptions it throws and callers can catch anything belonging to that class with a minumum of fuss. Selecting exceptions by class makes it easy to control whether one processes the specialized IO exception thrown by package FOO, or a general purpose IO exception.

But in Perl there are no standard exception classes. There are several modules on CPAN that have tried to fill in that gap, but classes alone do not make exception handling easy. To make exception handling relatively clean one needs a combination of a well defined exception class hierarchy and a class oriented try {...} catch {...} syntax.

Perl doesn't have native class based syntax for exception handling so you have to do something like this, if you want to pick out exceptions by classes:

local $@; eval { # ... do something ... return 1; } or do { my $err=$@; #prevent reset while evaluating if conditions if (!$err) { die Exception::Unknown->new(); } elsif (!Scalar::Util::blessed($err)) { #ugly string regex processing here } elsif ($err->isa('BadParameterException')) { #handle bad parameters } elsif ($err->isa('IOException')) { #handle IO exceptions } elsif #... yada yada } else { die Exception::Unknown->new(); } }

CPAN, of course, has modules that try to make this cleaner as well. Some of these modules create syntactic sugar using source filters (for example, Error::TryCatch). The written code looks prettier, but there is hell to pay come debugging time. The actual line numbers in the code no longer match up with the line numbers spit out by Perl error messages. Others, like Error try to emulate try {...} catch {...} with closures, but as Perrin points out in Re: Re2: Learning how to use the Error module by example, this can lead to unreliable results.

Whether we code it out the long way or use some module to provide syntactic sugar, all of this boiler plate code is going to be for naught if $@ gets reset by code that is triggered while dying. Localizing $@ doesn't really solve the problem of $@ being reset. It helps our own callers, but doesn't protect us from the effects of things we call. If a third party object or function lower down in the stack forgets to localize their own copy of $@, our localized $@ might still get reset.

There are always new things I'm learning about Perl, but based on what I know so far, there isn't a really clean and reliable way to know both the fact and kind of an exception passed up through several stack frame. Given that, I generally find myself handling problems close to the source via return values.

But I don't really like doing that at all. I would much prefer using exceptions. In most cases it is very hard to know the meaning of an error in a low level API function or the best way to communicate it. If the low level subroutine is part of a back-end process, then English technical messages might be best. But if that very same low level API function is used in a GUI application, I might want a non-technical message in the user's native language. Those different messaging scenarios can be handled very easily if error data is stored in fields within an error object that is passed up the stack frame until it gets to an application level caller.

Best, beth

Replies are listed 'Best First'.
Re^2: Dealing with errors in subroutines
by moritz (Cardinal) on Sep 09, 2009 at 13:31 UTC
    I agree that Perl 5's exception system is somewhat clumsy, and makes it hard to filter by exception type.

    On the other hand, in my mostly small, mostly fun projects I haven't needed that feature at all - either I just hand on exceptions, or drop them, or warn and continue - so far I always left the interpretation of the error to the user, which worked pretty well for me.

    That said in Perl 6 we will have something like an exception hierarchy (except that it won't be exactly tree like, because we don't just rely on inheritance but also on role composition). But the problem - as always - is that somebody needs to actually do it: define a structure, default error messages and so on, and spec that.

    So please take this as a call for volunteers. If you are (like me) interested in Perl 6 having a better exception system than Perl 5 (and one that makes translations to other languages easy without breaking any code), consider contributing.

    (I'm glad to help where I can, but I won't be the driving force - I have too many other Perl 6 related projects I'm working on).

    Perl 6 - links to (nearly) everything that is Perl 6.

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: note [id://794303]
and all is quiet...

How do I use this? | Other CB clients
Other Users?
Others avoiding work at the Monastery: (7)
As of 2018-05-21 13:59 GMT
Find Nodes?
    Voting Booth?