Beefy Boxes and Bandwidth Generously Provided by pair Networks
No such thing as a small change
 
PerlMonks  

Error Reporting from Module

by hornpipe2 (Acolyte)
on Oct 09, 2019 at 05:45 UTC ( #11107229=perlquestion: print w/replies, xml ) Need Help??

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

I released my first module last month and I've already got a user :) ... who already has an issue :( ...

The problem they're having is: my module deals with remote service errors by warn() and return undef; however, the user want to be able to catch the errors and do something with the result. In other words, this pattern isn't working for them:

eval { $webhook->execute("Test message."); } or do { print "Found error: it was $@...\n"; }

... because this would only catch fatal errors, not warnings.

I could go replace warn with die throughout, but I wanted the wisdom of the Perl Monks. Is this wise? Is there a better way - maybe, set an internal $self->{error} with a copy of the error, or maybe add a $self->{autodie} flag, or... something else?

This is my first "real" module so I want to make sure I get a sensible design. For now I've suggested overriding %SIG{__WARN__} until I can come up with a better, permanent solution.

Replies are listed 'Best First'.
Re: Error Reporting from Module
by Corion (Pope) on Oct 09, 2019 at 07:23 UTC

    Personally, I like to use Carp::croak over die because it reports the problem on a line number that the module user can affect, instead of reporting the problem within a module that the module user will not edit/change.

    I think your approach of having a reporting scheme that stores the error message and returns undef or (if "autodie" is set) croaks is the best approach:

    sub _error { my( $self, $message ) = @_; $self->{last_error} = $message; croak $message if $self->{autodie}; }; sub do_something { my $res = eval { $webhook->execute(...); }; if( my $err = $@ ) { $self->_error( $err ); }; }
Re: Error Reporting from Module
by daxim (Curate) on Oct 09, 2019 at 07:58 UTC
    The underlying question here is how should abnormal situations (server responses) be handled. It's tricky. Because Perl is flexible and does not prescribe much dogma, prefers to provide mechanism over policy, you might ask three programmers and get five opinions.

    After looking at your module's code, here's mine. I subscribe to the school of thought that says that exceptions should be for exceptional circumstances, and a server returning an error within its protocol falls under normal operation. I also think that stuffing useful information into a side-channel (like warnings or a special error attribute that needs to be checked each time) is a smell, and shifts into the territory of bad code when the methods' regular return value is merely an anaemic boolean. A counter-example of rich return values is ReturnValue.

    The following also applies to your code in special and is tangentially relevant:

    • Stringly typed warnings and exceptions are outright bad because they are difficult to reuse, couple way too tightly (fix a typo? you just broke the API…) and require parsing to get at information – use objects instead. I like failures because it's very light-weight and throwing is not required, so the object can easily be used in a different way, e.g. for warnings or monadic error handling.
    • The API is not fully documented, diagnostics are missing – see M::S::PBP for a template.

    If you decide to keep the code mostly as it is, look into registering warnings categories. That allows the user to selectively fatalise warnings and you don't need to mess with a instance-global autodie flag.

    That was all rather theoretic and vague. If I have some time today, I would sketch out some real code for W::D::W. Is there a public test server I can use?

Re: Error Reporting from Module
by rjt (Deacon) on Oct 09, 2019 at 09:59 UTC

    Congratulations on your first module!

    I've taken a look through your documentation and code, in that order. The main issue I have with the error reporting that I have is it's somewhat inconsistent. You croak() in a bunch of places (e.g., for invalid parameters), but return undef in others (with a warning), so the programmer has to check for both. And warnings/carp() have their place, but can be annoying to deal with if they are "expected" in normal operation. Overriding $SIG{__WARN__} is technically workable, but obviously not ideal, especially when they also have to deal with the other error modes.

    In your case, I would suggest picking one option and sticking with it, unless you have very good reason, and even better documentation. The exception model isn't always the best, but seems reasonable in your case. When using exceptions, use carp() sparingly, and only for things that require a warning, but do not indicate a significant deviation from normal program flow, in my opinion. (carp() followed by an early return is usually a bad sign.) (Minor edit for clarity)

    This is just one tired old dev's opinion, but you usually can't go too far wrong with exceptions for a module like yours. It gives the caller the option to easily pass through (and thus exit on error), catch and handle, catch and ignore, and all of that is easy to do with basic Perl code, or even add a little syntactic sugar and extra features like Try::Tiny if desired.

    Now, I have a few other comments for your code, in the readmore, if you like unsolicited advice. :-)

    use strict; use warnings; omitted for brevity.
Re: Error Reporting from Module
by tobyink (Canon) on Oct 09, 2019 at 11:55 UTC

    I've looked at your code. My recommendation would be to replace things like this:

    carp "xyz"; return;

    With this:

    return $self->log_error("xyz");

    And then define this method:

    sub log_error { carp $_[1]; return; }

    Then anybody who needs different error handling can subclass your class.

    use Subclass::Of "WebService::Discord::Webhook", -as => "MyWebhook", -methods => [ error_log => sub { my ($self, $message) = @_; # alternative error handling here }, ]; ...; my $webhook = MyWebhook->new(...);

    If you have different severities of error message, you may wish to provide multiple methods like log_unrecoverable_error and log_recoverable_error. Whatever makes sense to you.

    Document these methods, how and when they will be called, etc. This way, people subclassing your module will know that they're not some kind of internal thing that might disappear in the next release, but are a supported part of the API.

      If you go that route you might look at Log::Log4perl or Log::Dispatch rather than reinventing existing wheels.

      The cake is a lie.
      The cake is a lie.
      The cake is a lie.

        I've always found those sorts of things are more oriented around application-level logging rather than a module reporting errors to its caller, but my experience with them is pretty minimal.

Re: Error Reporting from Module
by Bloodnok (Vicar) on Oct 09, 2019 at 12:43 UTC
    It's probably all wrong and may not be exactly what you want, but having wanted something similar, I discovered this (Google _is_ your friend) ...

    use warnings FATAL => 'all';

    Just my 10 penn'orth...

    A user level that continues to overstate my experience :-))

      warnings' FATAL has lexical scope, so unless the module provides a custom sub import that allows the person useing the module to turn them on, the user of the module can't do so. And they also don't fatalize warn statements.

      $ perl -Mstrict -Mwarnings=FATAL,all -le 'warn "Hello";my $x=1+"";prin +t $x' Hello at -e line 1. Argument "" isn't numeric in addition (+) at -e line 1.

Log In?
Username:
Password:

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

How do I use this? | Other CB clients
Other Users?
Others examining the Monastery: (8)
As of 2019-10-14 17:54 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    Notices?