Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl Monk, Perl Meditation
 
PerlMonks  

Error handling - how much/where/how?

by Eyck (Priest)
on Jun 12, 2005 at 18:36 UTC ( [id://465989]=perlquestion: print w/replies, xml ) Need Help??

Eyck has asked for the wisdom of the Perl Monks concerning the following question:

Are there any rules-of-the-thumb as to how much error handling in your code/library should be?

I've been told that comments should amount for the half of your code, and found that rule to be sensible in non-perl languages (and with time I found myself putting perl code in the comment and then carry on implementing it in language at hand), however I haven't heard about such simple rule conserning error handling, and I think error handling is crucial to correctnes/ease of use of your code.

When is

eval { main();#;) }; if ($@) { die "Problem: $@"; }
enough?

Is there anything but 'years of experience' to help me decide?

Replies are listed 'Best First'.
Re: Error handling - how much?
by salva (Canon) on Jun 12, 2005 at 18:59 UTC
    there is an easy response for that: handle errors when you know how, otherwise just die!

    There is only one thing worse than a script that doesn't work, and it is a script that seems to work but that actually doesn't.

      Amen brother.

      And I hit an excellent example of that recently. I was writing a webpage in which 70 odd input elements had to be submitted. Mozilla was GETting the result with no problems, but when I switched to Internet Explorer I had a problem. If I typed no text in my form I could submit it. But if I typed text in my form, IE would sit there blankly while I pressed my submit button.

      So die.. die ungracefully, or error someone.. let the world know that something unexpected happened!

Re: Error handling - how much?
by Ultra (Hermit) on Jun 12, 2005 at 19:55 UTC
    I can only agree with salva. Wrapping every sentence in eval will not only slow down your entire program, but it will obfuscate it to the point of no return.
    Instead, I think it's more useful to be aware of certain situations prone to generating errors.
    Here's an incomplete list:
    * use strict and warnings for typos and common mistakes
    * check the user's input using regular expressions (taint check is a big helper here)
    * be careful about arithmetic operations (divide by zero etc.)
    * check the return of the system calls and generally the retun values of all the functions you use (including yours), and when you can't continue, throw a useful message so that the user can easily spot the problem and eventualy contact you if it is the case...

    I don't think it's a matter of "how much" but of "when and how" to check for possible errors.

    Dodge This!
Re: Error handling - how much/where/how?
by thcsoft (Monk) on Jun 13, 2005 at 01:36 UTC
    in addition to what salva and Ultra stated:
    - you might want to take a look at Carp.pm
    - there's a CORE function named caller.
    - you can install hooks for every signal, even $SIG{__DIE__}.

    e.g.:
    local $SIG{__DIE__} = sub { my $i = -1; while (my @c= caller(++$i)) { print " at $c[1] line $[2]\n"; } };

    language is a virus from outer space.

      I used to hand-roll similar stuff, badly. These days I have learnt to become Lazy, and I now use Devel::SimpleTrace. That's usually good enough for me to figure out the course of events when Things Go Wrong.

      - another intruder with the mooring in the heart of the Perl

        another way is to use Carp::croak to report errors and then use...
        perl -MCarp=verbose foo.pl
        to get stack traces.
Re: Error handling - how much/where/how?
by mstone (Deacon) on Jun 13, 2005 at 03:51 UTC

    In general, it's a good idea to put your error handling as low as possible.

    The higher, more abstract layers of your code should be able to trust the lower layers. You shouldn't have to check for buffer overflows, data consistency, or other such problems in the "okay, now I want to get something done" parts of your code. Unfortunately, most programmers are used to writing incomplete functions, so they don't know how to make their low-level code trustworthy.

    'Completeness' is a mathematical idea. It means that the output of a function belongs in the same set as the input. Addition is complete across the positive integers: the sum of any two positive integers is also a positive integer. Subtraction, OTOH, is not complete across the positive integers: "2-5" is not a positive integer.

    Broadly speaking, incomplete functions will bite you in the ass. Every time you leave something out, you have to write nanny-code somewhere else to make sure the function is actually returning a good value. That's difficult, often redundant, wasteful, and confusing.

    It's a much better idea to design your code with complete functions at every level. Instead of allowing foo() to return any integer, then checking the result to make sure it falls between 0 and N, you break the problem into two parts:

    1. Write foo() so it only returns values in the set (0..N, "invalid"). Or better still, return the structure: { 'value'=>(0..N), 'is-valid'=>(0,1) }.
    2. Then write the client code so it Does The Right Thing for every possible output value.

    Yeah, you're still writing some output-testing code in the higher-level functions, but the code itself is much simpler. Instead of having to range-check the value, you just have to check 'is-valid' as a boolean. The low-level code does all the heavy lifting on deciding whether the output can be trusted. And in many cases, you can find default error values that work just fine in the main-line higher-level code.

    When you write code that way, you end up with each level carrying only the error-handling that makes logical sense at that level, and just enough error-handling to pass usable output along to the next layer up.

      If I were to use a module that returns your proposed { 'value'=>(0..N), 'is-valid'=>(0,1) } the very first thing I'd make would be a

      sub sanify { my $fun = shift; return sub { my $ret = $fun->(@_); if ($ret->{'is-valid') { return $ret->{'value'} } else { die "Well, something was incomplete. The module author gave no clue +s!\n"; } } }
      and ran all exported functions through this wrapper to get some sanely behaving ones. You have exceptions for Lary's sake! Use'm!

      Update: <quote>In general, it's a good idea to put your error handling as low as possible.</quote>
      Let me disagree. If you put the error handling too low, you end up with much longer code. And longer code takes longer to write and it means more bugs. So you should always try to find the right level to handle errors. Not too low nad not too high. I'm afraid apart from experience there is no way to tell what level is the right one. I just think you should not be afraid to say "I don't mind whether it's this or this or this operation that fails, if any of them does I want to handle the problem like this."

      Jenda
      XML sucks. Badly. SOAP on the other hand is the most powerfull vacuum pump ever invented.

        You're really gonna hate any() values (and traits) when Perl6 comes around. ;-)

        If you want more information about what went wrong, put more values into the return set. You know.. like NaN, positive and negative infinity, "Number outside representation range" and that sort of thing. You could also add utility values like positive and negative zero, or 'Infinitesimal' which make certain calculations easier.

        If you want even more information, you can choose which of:

        • NaN_divide_by_zero
        • +/-Infinity_divide_by_zero
        • or just plain Divide_by_zero

        fits your purposes best.

        You're also missing the point that value should always contain something consumable by the main-line client code. The goal is "usable but identifiably bogus" rather than "broken but correct."

        If I were writing a division operator, for instance, I'd probably have division by zero return:

        { 'value'=>1, 'is-valid'=>0, 'error'=>'Divide_by_zero' }

        The one in value is consumable by any other mathematical operation, even though it's totally bogus as an accurate result of the calculation. The boolean in is-valid tells you it's bogus, and the detail code in error tells you why. (Yeah, error is new. I added more information. We can do that)

        If I really wanted to get spiffy, I'd add still more information:

        { ... 'trace'=>"($a/$b)" }

        then write all my operators so they return progressively more complicated trace strings whenever they get invalid arguments. That way, I could see at a glance where the error occured, rather than having to fire up a debugger and step through the code until it bombs out again.

        IMO, the presence of exceptions is a code smell. It says that you'd rather use the quantum superposition of two (or more) possible code sequences in a "try it, then backtrack and see what went wrong" fashion rather than figuring out how to make the code work in the first place. And it's almost always a sign that the programmer is trying to use a data representation that's too primitive to handle all the results that are actually possible.

        So.. you can write Schrodinger-code to compensate for the bad decisions you made about data representation, or you can choose a data representation that actually does what it's supposed to, and handle the job correctly.

Re: Error handling - how much/where/how?
by tphyahoo (Vicar) on Jun 13, 2005 at 08:40 UTC
    "I've been told that comments should amount for the half of your code"...

    There's a style of programming where you make "executable comments" that also raise errors when something that should be true isn't: assertions in perl5.9.0.

    # Take the square root of a number. sub my_sqrt { my($num) = shift; # the square root of a negative number is imaginary. assert($num >= 0); return sqrt $num; }
    The above is from Schwern's Carp::Assert. I think there are a number of other modules that do this as well, and I'm not sure which is best, because I only started experimenting with assertions myself recently. But it might be something to learn about if you are spending a lot of time meditating about commenting and error checking.
Re: Error handling - how much/where/how?
by ruoso (Curate) on Jun 13, 2005 at 10:10 UTC

    It's an option to use a common use error handling library. As sometimes giving the error on the return of the sub is not a good option (for example a sub that can return any value).

    I use the Error library. It implements the try/catch syntax, enables printing the stack trace (as someone suggested on this thread) and doesn't require other things from people who use your module, since the exception is a regular die (with some info saved inside Error).

    I like this approuch because it's easier to handle errors from methods that can throw many types of exceptions, you can programatically do something when *this* error and otherthing when *that* error very easy.

    daniel

      You might want to be careful using Error. There can be some unexpected negative side-effects. Perrin's write up Re: Re2: Learning how to use the Error module by example has the details. (I've written Exception::Class::TryCatch to bring some try/catch sugar to the Exception::Class module, and you might consider that in place of Error.)

      -xdg

      Code written by xdg and posted on PerlMonks is public domain. It is provided as is with no warranties, express or implied, of any kind. Posted code may not have been tested. Use of posted code is at your own risk.

        Yeah, actually I had segfaults with Error prior to 0.15, but I'll consider switching to any other module, as Error is kindly to generate weird behaviour when you do something wrong... I just didn't know any good one...
        daniel
Re: Error handling - how much/where/how?
by Joe_Cullity (Beadle) on Jun 13, 2005 at 16:21 UTC

    IMHO – The amount of error processing really depend on where what and when.

    If the program is a simple batch job that runs at 2:00am, can be re-run if it fails and the output won’t be used till 9:00am in the morning, then maybe you can scrimp a bit on the error handling/reporting (But I bet after a few years of frantic 2:00am phone calls, you might change your mind.).

    If it’s a heavily used data entry screen that’s expected to protect the integrity of a production system from invalid data , then I’d do a bunch more error checking, input validation, and reporting in human readable format (Like “Zip Code must be in the format of NNNNN-NNNN).

    If the system handles 80% of the crude oil transactions in America in a real time highly visible environment, or monitors the pressure of a 747’s flaps as they extend down for landing, or perhaps handles the collision avoidance subsystem of a robotically controlled blood analyzer as strings of small glass veils containing potentially contagious sample whirl by…. I don’t think any amount of error handling/reporting is too much.

    After 35 years of people wakening me up at 2:00am because a downstream job failed due to my program missing an opportunity to report bad (or just perhaps very unusual) data, you’ll find that I put as much error catching/reporting code as possible into every program I write, and lots more into the critical ones.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://465989]
Approved by ysth
Front-paged by tlm
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others having an uproarious good time at the Monastery: (3)
As of 2024-04-24 22:15 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found