Beefy Boxes and Bandwidth Generously Provided by pair Networks
P is for Practical
 
PerlMonks  

How do I report an error back to the user of my object?

by SomeNetworkGuy (Sexton)
on Jun 04, 2012 at 20:52 UTC ( [id://974377]=perlquestion: print w/replies, xml ) Need Help??

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

I am writing an object. Upon initialization of the object, if there is a problem for whatever reason (loading data or anything) I want to return null and put an error in $!. Here is my object:

package coolobject; use strict; our @ISA = qw/Exporter/; our @EXPORTER = qw/new/; sub new{ my $class = shift; my $self; $self->{name} = 'new'; bless ($self); if ($self->loaddata()){ return $self; }else{ $! = "Error loading data!"; undef $self; return undef; } } sub loaddata{ my $self = shift; return undef; }

and here is where I am using the object

my $obj = coolobject->new or die "$!";

When I run it though, I don't get any value in $!. How am I supposed to do this?

Replies are listed 'Best First'.
Re: How do I report an error back to the user of my object?
by kcott (Archbishop) on Jun 04, 2012 at 22:32 UTC

    $! is a special global variable that you shouldn't be modifying yourself. Take a look at perlvar - Error Variables; in particular, the section describing when $! is meaningful and meaningless.

    The first thing I'd do would be to move the error handling code into the module by changing the if statement to:

    if (! $self->loaddata()) { die "Error loading data!"; } return $self;

    Now users of your module don't need to type  or die "$!" after every call to the constructor.
    ( Side issue: your hard-coded undef will evaluate to false but so will 0 (zero) and '' (empty string) - using defined will provide a more robust solution. )

    Your loaddata() routine may be performing operations which could also raise fatal exceptions, such as opening a file, connecting to a database, and so on. You can trap these errors with eval like this:

    eval { $self->loaddata() }; if ($@) { die "Error loading data! Reason: $@"; } return $self;

    You may want to use croak instead of die - see Carp. The first sentence of the description: "The Carp routines are useful in your own modules because they act like die() or warn(), but with a message which is more likely to be useful to a user of your module.".

    One final point related to the use of bless in your code. You have the single-argument form (bless $self); the two-argument form (bless $self => $class) is preferred.

    -- Ken

Re: How do I report an error back to the user of my object?
by kennethk (Abbot) on Jun 04, 2012 at 22:25 UTC
    See $! in perlvar. You can't reliably set $!. If you have warnings on, you'll get Argument "Error loading data!" isn't numeric in scalar assignment because $! isn't a normal variable, and expects a numerical system error code. (Re: warnings, see Use strict warnings and diagnostics or die).

    The usual way of indicating a failure in Perl is to die, which functions as an exception that can be caught downstream, e.g.

    sub new{ my $class = shift; my $self; $self->{name} = 'new'; bless ($self); if ($self->loaddata()){ return $self; }else{ die "Error loading data!"; } }
    which could be invoked as my $obj = eval{coolobject->new} or die "$@";

    If you absolutely want to pass an error and return an undef, you could set $@ manually, but this is probably poor form.

    If you are rolling your own objects for fun/education, I'd recommend you look at perltoot. If you are rolling objects for deployment, I'd look at (and use) some prior art like Moose, Mouse...


    <Node text goes above. Div tags should contain sig only>

    #11929 First ask yourself `How would I do this without a computer?' Then have the computer do it the same way.

Re: How do I report an error back to the user of my object?
by tobyink (Canon) on Jun 05, 2012 at 06:38 UTC
    our @ISA = qw/Exporter/; our @EXPORTER = qw/new/;

    If you're writing object-oriented code, you should not be exporting anything. And you should sure as hell not be exporting new ever.

    Don't use the one argument form of bless - use the two argument form. It's more friendly to people wanting to subclass your classes...

    bless $self, $class;

    Don't return undef to indicate a terminal failure. Just die with an error message. (Or better, Carp::croak or Carp::confess.)

    use Carp qw/confess/; sub new { my ($class, @args) = @_; my $self = bless { name => 'new' }, $class; $self->loaddata; return $self; } sub loaddata { my ($self) = @_; # stuff happens if ($something_bad_happened) { confess "Error loading data!"; } }

    Why? Mostly because it forces your caller to not ignore your error message. Right now you say you're calling your code like this...

    my $obj = coolobject->new or die "$!";

    ... but really the caller shouldn't have to remember to detect errors and die. The die should just happen. (For this reason the default behaviour of many Perl built-ins, such as open, is pretty dumb. The autodie pragma fixes them.)

    If the caller is really sure they want to ignore coolobject failures, then they can always wrap the object construction in eval like this:

    my $obj = eval { coolobject->new }; # $obj might be undef
    perl -E'sub Monkey::do{say$_,for@_,do{($monkey=[caller(0)]->[3])=~s{::}{ }and$monkey}}"Monkey say"->Monkey::do'
      use Carp qw/confess/;

      I might just point out that this code stops the importation of carp and croak.

      By default, Carp imports confess, carp and croak. So, unless you specifically wanted to exclude carp and croak, you can just write:

      use Carp;

      If you wanted to export a non-default symbol (e.g. cluck) but still include the defaults, you can write:

      use Carp qw{:DEFAULT cluck};

      See Exporter for a description of :DEFAULT and other Specialised Import Lists.

      -- Ken

        I might just point out that this code stops the importation of carp and croak.

        Indeed, but I was using neither of those so saw no need to import them.

        perl -E'sub Monkey::do{say$_,for@_,do{($monkey=[caller(0)]->[3])=~s{::}{ }and$monkey}}"Monkey say"->Monkey::do'
Re: How do I report an error back to the user of my object?
by NetWallah (Canon) on Jun 05, 2012 at 01:37 UTC
    Another option is to have a $self->{ERRORSTR} property that can be obtained via "sub errorstr{}" method.

    It should default to the empty string.

    Update: On second thoughts, it should also be available as a CLASS method - perhaps setup a private Class variable (our $ERRORSTR), that can be accessed via $ClassName::ERRORSTR
    This would be necessary/useful in the event that the "new" method failed to instantiate an object.
    I could see coding a method like this:

    our $ERRORSTR=""; sub new{ ... on failure, $ERRORSTR="*KAPUT*: Monkeywrench in our NEW method"; } sub OtherMethod{ .. on error, $self->{ERRORSTR}="*KAPUT* I really hate that parameter + you tried to pass to me"; return -1; } sub errorstr{ # Both class, and Instance Method my ($self)=@_; ref $self or return $ERRORSTR; # Class call return defined ($self->{ERRORSTR}) ? $self->{ERRORSTR} : $ERRORSTR; }
    Update> Add "ref" to sub errorstr, based on tobylink and polymorpheus's comments below.

                 I hope life isn't a big joke, because I don't get it.
                       -SNL

      I'd write that as...

      sub errorstr { # Both class, and Instance Method ref($_[0]) ? $_[0]{ERRORSTR} : $ERRORSTR; }

      Yours will die if the user calls ClassName->errorstr.

      perl -E'sub Monkey::do{say$_,for@_,do{($monkey=[caller(0)]->[3])=~s{::}{ }and$monkey}}"Monkey say"->Monkey::do'

      I would highly suggest you do not take an approach like this. Exception based errors (die/eval) are far superior in many respects. If you are not convinced yet, do some research (PBP, Modern Perl). Also look at all the monks! Most have already suggested die() and for good reasons!

      Also, in errorstr() I think I see a bug:
      $self or return $ERRORSTR; # Class call
      I think you meant to do this?
      ref $self or return $ERRORSTR; # Class call
      But more importantly, use exceptions instead!
        Agreed (and fixed). But I would suggest using Carp which has a "confess" method that can provide a stack trace to point out the callers evil ways.

                     I hope life isn't a big joke, because I don't get it.
                           -SNL

Re: How do I report an error back to the user of my object?
by 2teez (Vicar) on Jun 05, 2012 at 06:28 UTC

    Hi, Good advice has been given on how to get around your code issue, but what I just want to point out here is that as a general rule, if the module is trying to be object oriented as it is, then export nothing so,

    package coolobject; use strict; our @ISA = qw/Exporter/; our @EXPORTER = qw/new/; sub new{ my $class = shift; my $self; $self->{name} = 'new'; bless ($self); if ($self->loaddata()){ return $self; }else{ $! = "Error loading data!"; undef $self; return undef; } }

    could still be written as

    package coolobject; use strict; sub new{ my $type = shift; my $class = ref($type)|| $type; my $self; $self->{name} = 'new'; bless ($self,$class); if ($self->loaddata()){ return $self; ...... }

    And later called as with new() as occassion demands.

    Please check: perldoc Exporter

Re: How do I report an error back to the user of my object?
by thomas895 (Deacon) on Jun 05, 2012 at 06:46 UTC

    The above are great solutions, but there is one more.
    You may have seen modules that have something like $Foo::errstr or the likes. Setting up something like that isn't too hard:

    package Foo::Bar; our $error = ""; sub new { my( $class, @args ) = @_; my $self = bless( \@args, $class ); unless( $self->method_that_might_fail() ) { $Foo::Bar::error = "O noes"; return undef; } return $self; }

    Of course, you will have to change that to fit your style, and it is probably a good idea to push the error setting to a separate subroutine.

    ~Thomas~
    confess( "I offer no guarantees on my code." );
      I would highly suggest you do not take an approach like this. Exception based errors (die/eval) are far superior in many respects. If you are not convinced yet, do some research! Also look at all the monks! Most have already suggested die() and for good reasons!
Re: How do I report an error back to the user of my object?
by thargas (Deacon) on Jun 05, 2012 at 12:23 UTC

    You want to report an error during initialization. Personally, I consider that an error in creating an object should die, returning either an exception object or an error message in $@, according to your preference. This means that you'd have something like (untested):

    package CoolObject; sub new { my $class = shift; my $self = { name => 'new' }, $class; $self->loaddata() or die 'Error loading data'; return $self;

    and call it like

    my $co = CoolObject->new(); # or if you want to trap the error... my $co = eval { CoolObject->new() }; if ($@) { # do something about the error }
Re: How do I report an error back to the user of my object?
by sundialsvc4 (Abbot) on Jun 05, 2012 at 12:30 UTC

    My personal preference in such matters is that “instantiating an object” is one thing, and “loading it with data” is another thing entirely.   Therefore, I will define the constructor to initialize the object and to set a loaded property to False.   Then, I will define a separate load() method.   I will also define a must_be_loaded() method that returns “self” if the object is loaded (as expected), or will die if it is not.   This “pass-thru method” is used in every getter-method that retrieves data from the object.

Re: How do I report an error back to the user of my object?
by morgon (Priest) on Jun 05, 2012 at 17:13 UTC
    As already mentioned the proper way to communiate such problems is via an exception (i.e. calling "die").

    However usually the client not only needs to know that something has gone wrong, but also what has gone wrong in order to handle the error-situation.

    If you communicate that with a simple string (e.g. die "error1\n") then your client has to parse the caught exception (which is then just a string) and that is brittle and silly.

    A much better approach would be using exception-objects from one of the frameworks that do all the heavy lifting for you.

    I have made good experiences with Exception::Class.

      What has really worked for me lately are these:   Try::Tiny, Exception::Class, Exception::Caught, and Error::Return.   These take advantage of the fact that you can die with an object.   Therefore you can determine if what you have just “caught” is an instance of that particular error-class ... much better, as you have observed, than munching an error-string.

      As far as “philosophy” goes, my general practice is that if a method finds that it is able to return a value, it does so.   If not, it throws an exception.   Therefore, if the method returns to its caller, it has something to say.   The code that made the call doesn’t have to “check to see if it worked.”   If you survived the call, it worked.   I’m generally satisfied with the clean code that results from this approach.

      Within my application, I actually define a package that contains nothing but exception-class definitions, each of them arranged into a hierarchy.   Every exception that is thrown by the application code originates in this hierarchy, and somewhere there will be a “catcher of last resort” exception-handling block which will receive any exception that’s not otherwise trapped.

Re: How do I report an error back to the user of my object?
by bobf (Monsignor) on Jun 06, 2012 at 15:18 UTC

    Interesting thread. Thanks for launching it.

    I wrestled with this recently and, while I know a lot of monks more experienced than me would recommend just die-ing on error, I decided to use a softer approach. I created a few error-handling methods for my class that can be called as follows:

    $obj->set_err( 'Something went boom' ); my $msg = $obj->err_str; my $trace = $obj->err_trace; # error msg + full stack trace

    The main reasons I went with this approach are:

    1. I didn't want to wrap nearly every method call with an eval
    2. I consider very few of the possible errors to be fatal-worthy
    3. I wanted the methods to have the ability to pass information back to the caller as a warning even if the method succeeds (e.g., over-writing data may or may not be intentional)
    In short, I wanted my class to be able to warn (or carp) that something might be fishy (e.g., with the input data) without die-ing all the time, and I didn't want the behavior of the class to overly-influence how I wrote the calling program. (I work in research and it is common to write a one-off to explore an idea; in some cases this means using data that may cause the class methods to throw errors that I want to ignore, and continue processing without having to work around fatal exceptions.)

    I realize this may be a choice that goes against accepted conventions, and over time (as my modules mature and become more battle-tested) I may change the way errors are handled. For now, though, this approach works well.

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others having an uproarious good time at the Monastery: (7)
As of 2024-03-29 00:07 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found