laziness, impatience, and hubris | |
PerlMonks |
Re: Dealing with errors in subroutinesby ELISHEVA (Prior) |
on Sep 09, 2009 at 09:04 UTC ( [id://794303]=note: print w/replies, xml ) | Need Help?? |
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:
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:
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
In Section
Seekers of Perl Wisdom
|
|