http://qs321.pair.com?node_id=11102455

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

Hi

Colleague asked me for general advice/arguments for exception handling with Try::Tiny .

Policy so far was to return complicated error codes in a C style.

Told him that I'm not aware of a general best approach, while I like catching errors, there are too many different use cases.

For instance one of my styles for handling simple routines is to write

sub complicated_flow { ... my ($res1,$res2, ...) = routine() or return; # ERROR skips rest ... } sub routine { … return if (ERROR); ... return 1,2,3; }

Please note that because of the list assignment (@result) = on the LHS the or will only fire in case of a blank return. (the list assignment returns the number of assigned values and 0 is false)

This doesn't exclude catching internal errors with try/catch aka eval BLOCK but is more concise for simple cases and follows the " open ... or ERROR " modell.

Are there best practices listing the cases where throwing an error message via die is more appropriate?

update meditation

Cheers Rolf
(addicted to the Perl Programming Language :)
Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

Replies are listed 'Best First'.
Re: Error codes vs die exceptions vs ...
by choroba (Cardinal) on Jul 05, 2019 at 21:36 UTC
    If the failure happens in a subroutine that's deeply nested in many other subroutines, but you need to catch the error somewhere near the top (e.g. to retry the whole operation), it's hard to propagate the error correctly. Throwing an exception (preferably an exception object for easy logging) solves the problem. It doesn't clutter the code, but it should still be used with caution, as the flow is much harder to follow - throwing an exception is just a goto in disguise!

    map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]
      > but it should still be used with caution, as the flow is much harder to follow - throwing an exception is just a goto in disguise!

      IMHO from inside deeply nested routines you can only to create a report, which is a $SIG{__DIE__/__WARN__} in disguise.

      Anything else is maintenance hell.

      Exceptions - which are not necessarily errors - should be handled as close as possible/reasonable otherwise you get "global" programming where you need to handle non local events.

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

Re: Error codes vs die exceptions vs ...
by haj (Vicar) on Jul 05, 2019 at 22:11 UTC

    In my opinion, the relevant question is: What information do you need to pass so that someone (not necessarily the direct caller) can fix the error?

    With a return code, the direct caller must check the result, but with an exception he can chose to do it, but somebody up the calling hierarchy ought to do it. However, this difference vanishes if the direct caller either can actually handle the error by itself, or needs to add diagnostics to make the exception meaningful to his callers. In both these cases she would need to catch the exception, which requires more typing than checking a return code. Exceptions are needed as a method to get "out of band" only in the (exotic, IMHO) case where every value, including undef, is a valid return value.

    Some prominent CPAN modules (e.g. DBI and Template) solve the problem of passing diagnostics by returning undef on error, and then providing a special method to retrieve details. The open function uses undef and $! for the same purpose, and can serve as a bad example because of insuffient diagnostics: "No such file or directory" doesn't tell us what the operation was, nor what path was tried (autodie nicely fixes that).

    Good interfaces can be designed either way. Bad interfaces, too. Every level of error handling in the call stack can chose to convert an exception to a return code or vice versa.

      > What information do you need to pass so that someone (not necessarily the direct caller) can fix the error?

      Thanks for nailing it.

      In the demonstrated case the direct caller must react and the details of the error are only relevant for STDERR.

      And it doesn't stop me catching fatals on a higher call level with try/catch.

      > only in the (exotic, IMHO) case where every value, including undef, is a valid return value.

      Please note that that's not a problem in the demonstrated model with list assignments.

      One can safely return undef.

      I.e. only an empty return counts as error.

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

Re: Error codes vs die exceptions vs ...
by GrandFather (Saint) on Jul 05, 2019 at 23:46 UTC

    I echo choroba's suggestion that propagating errors from nested code can be cleanly solved by throwing and catching exceptions using die and eval or tools like Try::Tiny (which wraps die/eval into a nice package). There is more to it than that though. Good exception handling can make a huge difference in cleaning up error management in code. It removes the need for most code to be aware of errors. The propagation techniques that use special sauce for returning error cases from subs require every use of the sub to manage error handling. Exception handling allows error handling in localised and easily recognised places.

    Full on exception handling can use an exception object that provides as much or as little of the error context as needed from an error string or code to a full dump of the call stack! As a taste consider:

    use strict; use warnings; package Error; sub new { my ($class, $errorStr, @params) = @_; my @context = caller; return bless {err => $errorStr, context => \@context, params => \@ +params}, $class; } sub Dump { my ($self) = @_; my ($package, $file, $line) = @{$self->{context}}; print "Exception: Pkg $package in file '$file' at line $line: '$se +lf->{err}'\n"; } package main; eval { DoStuff(); return 1; } or do { my $err = $@; $err->Dump() if $err->isa("Error"); }; sub DoStuff { die Error->new("Something went wrong."); }

    Prints:

    Exception: Pkg main in file 'delme.pl' at line 32: 'Something went wro +ng.'
    Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond
      Thanks!

      ... what I like about your approach is that you can distinguish between different exception classes ( literally so)

      > Exception handling allows error handling in localised and easily recognised places

      Well I seem to remember that exception handling needs to propagate too, if the class can't be handled locally.

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

        Exceptions propagate until they are handled which may be at the outermost level by Perl handling the die. Nothing between the code throwing the exception and the node handling it sees the exception.

        Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond

      This sounds very "vernünftig". What about putting this stuff into some fancy little role? See What About Providing Constructors in Roles? for some ideas for roles providing constructors. Callow thought: May be it should be a singleton 🤪. Best regards, Karl

      P.S.: I‘m on vacation and unfortunately there is no Perl on the iPad and i don‘t like blindfold mode.

      «The Crux of the Biscuit is the Apostrophe»

      perl -MCrypt::CBC -E 'say Crypt::CBC->new(-key=>'kgb',-cipher=>"Blowfish")->decrypt_hex($ENV{KARL});'Help

Re: Error codes vs die exceptions vs ...
by daxim (Curate) on Jul 08, 2019 at 08:43 UTC
    I'm not aware of a general best approach
    Give monadic error handling a try. You know already it composes nicely even when there are an arbitrary amount of frames between complicated_flow() and routine() and does not suffer from the downsides of C-style retval (extra code for checking) or exceptions (possibly global jumps in control flow).

    It's maybe not appropriate for your current use case because it would require a bit of code to be changed and it does not look much perlish at first glance, but perhaps your colleague okay with that.