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

Dear monks,

I am asking for your wisdom in handling exceptions and best practices for dealing with them.

Yes, I know about Try::Tiny, Syntax::Feature::Try, Syntax::Keyword::Try, TryCatch and others modules. But I want to elaborate for myself best practices for dealing with exceptions with die/eval. I prefer to use Carp and its stacktrace and I want to use native language features despite they can be ugly.

What practices would you recommend for me as for module writer and creator of different applications?

Replies are listed 'Best First'.
Re: Best practices for handling exceptions in die/eval style
by choroba (Cardinal) on Aug 29, 2018 at 22:08 UTC
    See also Bug in eval in pre-5.14.

    ($q=q:Sq=~/;[c](.)(.)/;chr(-||-|5+lengthSq)`"S|oS2"`map{chr |+ord }map{substrSq`S_+|`|}3E|-|`7**2-3:)=~y+S|`+$1,++print+eval$q,q,a,

      I know it. That's why I currently came to code in this reply.

Re: Best practices for handling exceptions in die/eval style
by stevieb (Canon) on Aug 29, 2018 at 21:56 UTC

    I always do things like this:

    my $ok = eval { do_something(); 1 }; if (! $ok){ handle_exception(); # possibly use contents of $@ }

    If I'm only doing one thing in the if block, I often will write it more compactly:

    handle_exception() if ! $ok;

    Sometimes I just want to croak with a separate message instead of the one generated by (perhaps not even) my code:

    croak "damned module failed with a confusing error\n" if ! $ok;

    These types of things are found all over my test suites. Here's one example from one of my CPAN distributions.

    What this does, is if the function/method called inside the eval dies/croaks, eval returns undef. If it succeeds, it returns 1/true.

      I used similar tactics:

      eval { # here we try do_smth() } or { # here we catch ... }

      But what is about $@ clobbering? I read in MVP book about it and it seems legit that we can clobber global variable. Now I find their code right:

      # Try clause my ($error1, $error2); { local $@; unless (eval { ...; return 1 }) { $error1 = 1; $error2 = $@; } } # Catch clause if ($error1) { # handle exception }

        Clobbering is why, years ago, I changed to the tactic I use now. The $@ can be clobbered in some certain circumstances. The return 1; is the equivalent of just having 1;... the block will return the last expression evaluated (1) without the explicit return().

        If you get undef, you know that eval failed (ie. die()d), regardless of/if $@ was populated or not. Localizing $@ is definitely a wise idea if that's the route one wants to take, but it's too much code for me. Keep subs and tests tight and small, and you should be able to know what the problem is specifically, without the need of $@'s contents (that definitely isn't a hard rule by any stretch, so take with a grain of salt).

Re: Best practices for handling exceptions in die/eval style
by TheloniusMonk (Sexton) on Aug 30, 2018 at 08:32 UTC
    The only reason to prevent something dying is where its maintenance is, at least temporarily, beyond your control. So IMO best practice is, yes, patch around it with one of the techniques already mentioned, but I'd avoid too routine a habit of patch and forget - be sure to have bug-reporting/follow-up built in to your dev cycle.

    In regard to warnings, they should be corrected. If a CPAN module produces a warning and it is a result of giving it duff parameters, I'd be inclined to force it to die and fix the root cause in my code. 'use strictures 2;' is a shorthand for the following:-

    use strict; use warnings FATAL => 'all'; use warnings NONFATAL => qw( exec recursion internal malloc newline experimental deprecated portable );
    which is not necessarily the best match for either your code or a given CPAN module. So it is worth maintaining a site policy on these settings.

    Finally, if you are convinced a warning is wrong, best post it here for discussion.

Re: Best practices for handling exceptions in die/eval style
by Anonymous Monk on Aug 29, 2018 at 22:32 UTC
    Usually when modules complain about something trivial I try to suppress the noise with eval:
    eval { noisy_sub_or_method() };
    However when that does not work I use File::Spec and it never fails:
    open STDERR, '>', File::Spec->devnull(); noisy_sub_or_method();
    I don't know why eval fails sometimes or which way is more efficient to trash known and useless errors (I guess devnull).
      What eval doesn't hide is probably just a warning, not an error:
      eval { warn "Boo" };

      You can also hide them with

      local $SIG{__WARN__} = sub {};

      ($q=q:Sq=~/;[c](.)(.)/;chr(-||-|5+lengthSq)`"S|oS2"`map{chr |+ord }map{substrSq`S_+|`|}3E|-|`7**2-3:)=~y+S|`+$1,++print+eval$q,q,a,
        This doesn't work if something prints directly to STDERR, whether from perl or C or even a forked process, in which case the above mentioned redirection to devnull would be needed. However, that method is much more difficult to localize.
Re: Best practices for handling exceptions in die/eval style
by krautcat (Acolyte) on Sep 05, 2018 at 22:50 UTC

    Also, I recently discovered that Carp doesn't work with objects.

    Is there any better choice for creating exception hierarchy rather than Exception::Class for old plain objects and Throwable for applications that use Moo object system?