Beefy Boxes and Bandwidth Generously Provided by pair Networks
Don't ask to ask, just ask
 
PerlMonks  

novice 'die' help requested

by nmerriweather (Friar)
on Jan 11, 2007 at 19:36 UTC ( [id://594215]=perlquestion: print w/replies, xml ) Need Help??

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

i make frequent use of die in eval blocks to trap errors.

my code often looks like this:
sub check_id { my ( $id )= @_; my $res; eval { my $obj= ObjClass::Obj->new(); $obj->id( $id ); $obj->load_via_id() ; $res= $obj->id ; die "invalid user" unless $obj->activated_account ; } if ($@) { DEBUG && log_error( $@ ); return -1; } return $res; }
note, thats a simplified version.

my problem lies in these 2 lines:
$obj->load_via_id() ; die "invalid user" unless $obj->activated_account ;
load_via_id could die and toss an exception - at which point things work as they should. i want to catch that die.

my issue is that if i die on "invalid user" (note, this is a simple version, in practice, there could several of these custom die items chained, i want to treat that differently : i still want to die and exit the block and return an error , but I don't want to do the logging.

can anyone suggest a way to do that and keep a similar syntax/code ? the only thing i could figure out was to have a '$supress_warning' variable before the block, and set it for custom dies -- but that means i'd have to write the dies as
if ( !$obj->activated_account ) { $suppres_warning++; die "invalid account"; }
someone out there with more experience than me must have a suggestion! i hope!

Replies are listed 'Best First'.
Re: novice 'die' help requested
by friedo (Prior) on Jan 11, 2007 at 19:50 UTC

    It sounds like it's time to move from simple error strings to exception objects. I like to use Exception::Class. Then you can do stuff like

    eval { my $obj= ObjClass::Obj->new(); $obj->id( $id ); $obj->load_via_id() ; $res= $obj->id ; Exception::ActivatedAccount->throw unless $obj->activated_account; }; if( my $e = Exception::Class->caught ) { # handle exception and decide about logging here }

    You can put any data you like into the exception object, including diagnostic messages and a flag which indicates whether or not to log it. Because you can create them in class hierarchies, you can make relatively clean exception handling code by checking if the exception object isa something you can deal with, and rethrowing it if it's not.

Re: novice 'die' help requested
by jettero (Monsignor) on Jan 11, 2007 at 20:03 UTC
    I like the solution above mine (never heard of Exception::Class before), but the method I usually use is the following.
    eval { open my $thingy, "something" or die $!; die "something\n" if $something; }; if( $@ ) { if( $@ eq "something\n" ) { # logging here } else { die $@; } }

    I actually got that from the alarm(3) manpage, so don't hold it against me.

    -Paul

Re: novice 'die' help requested
by muba (Priest) on Jan 11, 2007 at 21:53 UTC

    In addition to responses above me (which all point you in the direction you probably want to go in), I'd like to tell you something interesting about the behaviour of die I recently discovered.

    This behaviour is documented in the description of die. Allow me to quote.

    die LIST
    ... Inside an eval(), the error message is stuffed into $@ and the eval is terminated with the undefined value. This makes die the way to raise an exception.
    ...
    If the last element of LIST does not end in a newline, the current script line number and input line number
    ...
    (paraphrased: "input line number" means whatever the current value of $/ is. Usually just \n, though.)

    Good. Now the underlined part is important there. It implies that if you include $/ at the end of your die message, you can prevent the at line X of Y to be added to the die message. That raises an interesting use of die. Let's see.

    my $codeblock = "Important and unimportant stuff"; eval { doImportantStuff; #if anything goes wrong, it goes `die "invalid important stuff\n"; +` #This should be logged doUnimportantStuff; # `die "invalid unimportant stuff\n";` # Should not be logged. moreImportantStuff; #`die "more invalid important stuff\n";` # Should be logged }; log_error("$@", $codeblock) if $@; sub log_error { return unless DEBUG; my $error = shift @_; my $place = shift @_; my @logthese = ( "invalid important stuff\n", "more invalid important stuff\n", ); for my $check (@logthese) { if ($error eq "$check") { write_to_log("Error '$error' at $place"); last; # found! No need to continue searching } } }

    Code is only intended to illustrate a point and therefore untested. Use at own discretion.

    Update: spotted a stupidity in the code, fixed that.

      warn works the same way (w.r.t. newlines and line numbers.)
        For one reason or another, I didn't know that yet. However, it suprises me neither :)
      see also http://perlmonks.org/?node_id=537806 for a regex to 'clean' the line number from die. personally, I use a constant/var to toggle whether or not i want line numbers to be shown ( show on dev, hide on prod )
Re: novice 'die' help requested
by ikegami (Patriarch) on Jan 11, 2007 at 20:35 UTC
    In my experience, you shouldn't pass $@ to functions. Pass a copy of it instead
    log_error( "$@" );

      ... Ofcourse, $@ gets stringafied here, so if it is an Exception object (or, if you want to be perverse, a hashref) it'll get magled, with differing degress of success. (Although most exceptions stringify nicely, so that you can read if they're uncaught, and reach your terminal.)

      perhaps log_error( my $something_else = $@ ) to copy it? ...

      @_=qw; ask f00li5h to appear and remain for a moment of pretend better than a lifetime;;s;;@_[map hex,split'',B204316D8C2A4516DE];;y/05/os/&print;
Re: novice 'die' help requested
by sfink (Deacon) on Jan 12, 2007 at 17:38 UTC
    Your actual code may make this unworkable, but for the specific example you gave, you could do:
    sub check_id { my ( $id )= @_; my $res; eval { my $obj= ObjClass::Obj->new(); $obj->id( $id ); $obj->load_via_id() ; $res= $obj->id ; if (! $obj->activated_account) { DEBUG && warn "invalid user"; return -1; } } if ($@) { DEBUG && log_error( $@ ); return -1; } return $res; }
    That changes the behavior, though. To get your original behavior, change warn to mywarn. I have no idea what mywarn() would do, because it seems like in your example code the string "invalid user" is never used for anything. So I'm unclear as to what it's for.

    Slightly more generally, you could allow multiple of these eval blocks in a single routine with (excuse the funny indentation):

    sub check_id { my ( $id )= @_; my $res; eval { { my $obj= ObjClass::Obj->new(); $obj->id( $id ); $obj->load_via_id() ; if (! $obj->activated_account) { warn "invalid user"; # or not... $res = -1; last; } $res= $obj->id ; } } if ($@) { DEBUG && log_error( $@ ); return -1; } return $res; }
    I haven't thought that through to what it would mean for chained blocks, partly because I'm not sure whether you mean nested blocks or a series of independent blocks.

    Your return values seem a little odd, though. This routine can return three things: undef, -1, or a valid id, where -1 seems to mean either an expected or unexpected error and undef means... well, I don't know. There must be some reason why you're doing $res = $obj->id. Is there some reason why it might not return a valid id, other than the activated_account test failing? If not, then why not just set $res to -1 if activated_account fails? Why throw an exception at all if it isn't really an exceptional case? If there is some subset of cases where an undef return value is meaningful, then it seems like the caller of this function has to do too much work to decipher all the possible things the return value might mean.

Re: novice 'die' help requested
by TOD (Friar) on Jan 17, 2007 at 18:20 UTC
    emm... maybe i didn't understand this discussion properly. but what if you simply localized a $SIG{__DIE__} hook in your eval blocks?
    sub check_id { [...] eval { my sig_die_handler = sub { ... }; local $SIG{__DIE__} = \&sig_die_handler(shift); [...] } }
    all of these java-like class constructs are unperlish in my opinion. think perl if you want to avoid code overheads.

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others browsing the Monastery: (6)
As of 2024-04-18 00:14 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found