in reply to Carping about object destruction when errors are unprocessed?

My first thought when looking at this is that you seem to be mixing "errors" and "warnings". To me, an error is something that generally needs to be handled right away (I.E. anything that dies).

If you can really avoid handling these errors until object destruction, and even then you are OK "gently proding" developers into handling them by just printing the "error" as a warning of sorts (via carp), are they really errors at all? If they really must be handled, I would lean towards a more heavy handed approach. It's hard to ignore a die/croak/confess, but all too easy to ignore a warn/carp/cluck.

  • Comment on Re: Carping about object destruction when errors are unprocessed?

Replies are listed 'Best First'.
Re^2: Carping about object destruction when errors are unprocessed?
by radiantmatrix (Parson) on Sep 05, 2006 at 18:33 UTC

    you seem to be mixing "errors" and "warnings"

    Yes, I've mentioned that a couple of times during the design meetings, but I think we came to a reasonable understanding: there are "failures" (die/croak), there are "non-fatal errors" (error), and there are "warnings" (warn/carp).

    Examples:

    • failure: unable to connect to a required database. The software can't proceed, so it croaks an exception.
    • non-fatal error: user input failed to validate. For example:
      if ( $obj->validate($q->param('new_userid')) ) { create_new_user( $q->param('new_userid')) } else { # tell the user why it was invalid and ask again send_reprompt( "UserID was not valid for the following reasons:" $obj->error ); }
    • warning: couldn't connect to the primary database, had to fall back to the secondary.

    Things like the second case often generate several errors simultaneously, since (for example) a userID can be invalid in several ways -- a situation that the die class of functions doesn't handle elegantly. That's the purpose of the error list in this core module.

    Additionally, it provides a compromise to things like:

    eval { $obj->get_stuff_a() }; if ($@) { $obj->try_a_backup() } eval { $obj->get_stuff_b() }; if ($@) { $obj->try_b_backup() } eval { $obj->get_stuff_c() }; if ($@) { $obj->try_c_backup() }
    Allowing, instead:
    $obj->get_stuff_a() || $obj->try_a_backup || die $obj->error; $obj->get_stuff_b() || $obj->try_b_backup || die $obj->error; $obj->get_stuff_c() || $obj->try_c_backup || die $obj->error; my @err = $obj->error; if (@err) { warn 'Had to use one or more backup sources because:'."\t\n" .join("\t\n", @err)."\n"; }

    The latter is more useful for logging (and, IMO, more understandable when read).

    Often, our developers will approach cases like this by simply dying on the first error they encounter. While that's "correct" behavior, it frustrates users and other developers when they have to try an action repeatedly instead of finding out everything that's wrong in one swell foop {sic}.

    Given that further information, would you still advise me to croak instead of carp on DESTROY?

    <radiant.matrix>
    A collection of thoughts and links from the minds of geeks
    The Code that can be seen is not the true Code
    I haven't found a problem yet that can't be solved by a well-placed trebuchet