Skip to content

Latest commit

 

History

History
295 lines (239 loc) · 9.87 KB

README.md

File metadata and controls

295 lines (239 loc) · 9.87 KB

Error-Grudge

Perl mix-in for adding new methods for out-of-band error returns for your Perl OO module.

Right now, very much a work in progress. (Lots of code but it is from a generic template for a Perl OO module.) So nothing close to working yet. In the meantime, here is a bit of documentation from the module's POD for a little light reading.

SYNOPSIS

In your module:

    package Your::Cool::Module;
    use warnings;
    use strict;
    use Error::Grudge ":basic";

    # Your objects now have the hasErrorEvent() and setStatusEvent()
    # mix-in methods.

    sub readInputFile
    {
      my $self     = shift(@_);
      my $wantFile = shift(@_);

      # First make sure there are no previous, unexamined, errors.

      die($self->eventStackTrace()) if ($self->hasErrorEvent());

      # Now do stuff.  If we have a problem, report the error...

      if (not -e $wantFile)
        {
          $self->setStatusEvent
            (
              severity => 'ERROR',
               eventID => 'INPUT-FILE-LOOKUP',
               message => ['file lookup error', $wantFile, $!],
            );

         return();
        }

      # ... otherwise continue work with input file ...
    }

Meanwhile, in a program that does not check your return status...

   $myObj->readInputFile('no-such-file');     # Returns with error flag set.
   $myObj->readInputFile('this-one-exists');  # <-- BANG! Exception here,
                                              # but it is the location of
                                              # where the flag was set that
                                              # is reported.

Or in a program that does...

   $myObj->readInputFile('no-such-file');
   warn("skipping file: 'no-such-file'\n")    if ($myObj->hasErrorEvent());

   $myObj->readInputFile('this-one-exists');
   warn("skipping file: 'this-one-exists'\n") if ($myObj->hasErrorEvent());

OVERVIEW

This module provides a framework for returning out-of-band return status information to your caller, but at the same time it will automatically force an exception if a critical return status is being ignored.

This module can add the following mix-in methods to your objects for comprehensive status flagging, analysis, and reporting. Note that these verbose method names are designed to make it unlikely they will collide with your own object method identifiers.

Object Method Group Description
setStatusEvent() :basic Die if pending error, else set new status
hasErrorEvent() :basic Test/clear last error status event
eventStackTrace() :basic Return stack dump for last event
getStatusEvent() :basic Return list of all status field values
eventSeverityIs() :basic Confirm last status event against a list
haltIfPendingError() :all Throw exception now if pending error
resetStatusEvent() :all Disarm pending and set new status event
forgiveErrorEvent() :all Disarm if pending error
holdGrudge() :all Reinstate if last event was an error
queryStatusEvent() :all Return status field values as hash
reportStatusEvent() :all Event status info suitable for printing
eventSeverityExceeds() :all Quantify a status event
eventSeverityLessThan() :all Quantify a status event

The module comes out of the box with a predefined status event serverity scale:

    Error::Grudge->setSeverityScale
      (
        DEBUG => { level => 0, log => 1 }, # lowest severity
           OK => { level => 1, log => 0 }, # successful completion
         INFO => { level => 2, log => 0 }, # neutral diagnostic
         WARN => { level => 3, log => 1 }, # warning or advisory
        ERROR => { level => 4, log => 1 }, # recoverable error
        FATAL => { level => 5, log => 1 }, # non-recoverable error
        LOGIC => { level => 6, log => 1 }, # programmer logic error
      );

But as illustrated, a class method is provided allowing you to do a wholesale replacement of this default table with your own preferred status names and hierarchy.

Finally, a range of events can be configured to determine which should be flagged and returned as an error, and at what point should an event immediately cause an exception to be thrown.

    Error::Grudge->configThreshold
      (
             errorFloor => 'ERROR',  # ERROR and FATAL are flagged
         exceptionFloor => 'LOGIC',  # LOGIC halts immediately
      );

Again, these are the initial defaults, which can be changed to suit your coding needs using the illustrated class method.

DESCRIPTION

Damian Conway, in his book "Perl Best Practices", and in this Perl.com article, suggests that it is better to "throw exceptions instead of returning special values or setting flags". The reasoning is "developers can silently ignore flags and return values, and ignoring them requires absolutely no effort on the part of the programmer." In particular "Ignoring error indicators frequently causes programs to propagate errors in entirely the wrong direction." And finally "Constantly checking return values for failure clutters your code with validation statements, often greatly decreasing its readability."

While these are all valid points, the problem I have is that the responsibility for error handling is shifted completely to the caller, forcing them to wrap most if not all of your method calls in either an eval block, or using the "try-catch" block syntatic sugar provided by your choice of CPAN module.

This module is an attempt to solve the problem by providing a consistent framework to test for error conditions, allowing the caller to have fine grained control over probing and handling such states. But at the same time, it provides a safety feature where an ignored error condition will cause an exception to be thrown. The original locus of that error is reported accurately, even if the bug is surfaced much later in execution. We refer to this property as your object being able to "hold a grudge", with each object holding its own independent error grudge state. A feature is also provided that allows an object with a lingering unhandled error to be reported when the object finally goes out of scope.

Finally, we provide a convenient mechanism to automatically log status events, of all types, to an open log stream handle.

The mix-in methods added by this module should work with any type of blessed object. However be aware that the services provided by this module are NOT THREAD-SAFE. While a generous set of convenience methods are provided for examining and manipulating your object's event status, as few as three of these methods are needed to cover most basic use cases.

Our Error Handling Philosphy

  • Any and all logic errors should be thrown immediately. In practice this primarily applies to API usage bugs that would have been caught at compile time in a more strict language. The reasoning is that if the usage is incorrect, it is very unlikely that any resulting data/actions will be rational. With Perl, some logic errors may only be caught at runtime. It is expected that these will be surfaced by unit testing.

  • Non-logic errors should be reported to the caller, giving it the opportunity to handle the error gracefully. We want to avoid having diagnostic reporting that bypasses the caller. There is no point display warning or error messages that cannot be remedied or understood by the end user. The user interface of the application should be responsible for all messaging to its world, be that GUI, web browser, terminal, operator console, or batch log.

AUTHOR

Bill Costa [email protected]

LICENCE AND COPYRIGHT

Copyright (c) 2023, Bill Costa [email protected]. All rights reserved.

This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself.

DISCLAIMER OF WARRANTY

BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR, OR CORRECTION.

IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.