Willard B. Trophy has asked for the wisdom of the Perl Monks concerning the following question:

Not being very object-orientated, I'd really appreciate the monastery's wisdom on this. What's the correct/best/usual way of handling error returns from a method?

As an example, say I had a very simple class that wasn't much more than a wrapper around the Unix time_t timestamp. I have a constructor that initialises the object to the current time, a couple of methods for returning the time as a string, and the following method for setting the object's value from a (nearly) arbitrary string:

sub from_string { use Date::Manip; my $self = shift; my $string = shift; my $time_t = &UnixDate($string, "%s"); $self->{NOW}=(defined($time_t))?$time_t:undef; return $self; }

If I feed the from_string method something that even Date::Manip can't handle, and then try to call one of the stringifying methods, is the correct thing for it to do to return an uninitialized value?

Would it be better to signal errors in an $object->error method? Or would a return code (as in DBI's $rv = $sth->execute convention) be better?

--
bowling trophy thieves, die!

Replies are listed 'Best First'.
Re: How to say 'oops' in OOPs?
by Abigail-II (Bishop) on Apr 08, 2003 at 14:49 UTC
    You should use what you think is best. Probably the most common methods are: returning a special value (undefined, 0, empty string, empty list, -1, MAX_INT) or performing a die. I fancy the latter, but I use the former often as well. The former has two drawbacks: you can't return a value that indicates "error" if there's no error, and you either have to reserve an range of special values, or you can't signal what went wrong, other than scribbling the reason away somewhere (compare open returning false, and using $! to indicate the reason).

    Throwing an exception (die) doesn't suffer from this. You don't "lose" possible return values, and you can do the "return" and reason all at once, just die with a reason. The caller still needs to do two things, using eval and checking $@, but the caller already needed to do two things.

    But please use whatever you are most comfortable with. And that might vary from project to project. Or even from method to method.

    Abigail

Re: How to say 'oops' in OOPs?
by hardburn (Abbot) on Apr 08, 2003 at 14:38 UTC

    I'm personally a fan of $object->errstr, with methods returning 0 on failure. You will either agree or you won't.

    ----
    I wanted to explore how Perl's closures can be manipulated, and ended up creating an object system by accident.
    -- Schemer

    Note: All code is untested, unless otherwise stated

      I'm personally a fan of $object->errstr, with methods returning 0 on failure. You will either agree or you won't.

      Almost the same here. I use error, not errstr (try pronouncing errstr). And I return nothingness on failure.

      That's a plain return;. It has the benefit of being undef in scalar context, but an empty list in list context.

      Juerd
      - http://juerd.nl/
      - spamcollector_perlmonks@juerd.nl (do not use).
      

        I like this as well, but I usually go one step futher.

        I leave it up to the user how to handle the error, so that they can write code according to how they want to.. I usually allow a definition via the new() method or say $obj->set(err_handler, 1)

        I usually roll my own error methods, and then if an error occurs I call the error method. Based on how the called function was called (the one that caputered the error), and the preferences defined in the object, I will return the error, return 2 elems first being undef and second the actual error, return undef and set error or errstr, or actually handle the via die/warn or croak (if Carp has been use()'d...

        Its alot of work to go through originally, but it allows the user of the package great flexibility.. The same package supports the following snippets..

        $obj->method || die "$obj->{errstr}\n"; # or even $obj->method || handle_errors; # where this is a custom sub # OR (@foo) = $obj->method; die "$foo[1]\n" unless ( defined($foo[0]) ); # OR @foo = $obj->method; # and I catch the error and die out...
        Its nice to leave the programming to the programmer, and not force them to jump through my particular flaming hoops, as I'm sure they have more than enough to jump through on thier own, or they probably wouldnt be using my module in the first place ;)

        /* And the Creator, against his better judgement, wrote man.c */
        (try pronouncing errstr)

        air-stir

        I agree though, error() is better.

        -sauoq
        "My two cents aren't worth a dime.";
        
Re: How to say 'oops' in OOPs?
by dragonchild (Archbishop) on Apr 08, 2003 at 14:51 UTC
    I would say that a lot depends on how OO your system is.

    A completely OO system would tend towards throwing and catching Error objects. Perl5 doesn't explicitly support this (but it can be done - q.v. Error for one way to do it).

    If you're using objects to encapsulate logic within a procedural system, I would go ahead and do something like what DBI does - return undef to indicate failure and set an error string method (called errstr in DBI). However, this requires a consistent API across all of your objects (because you don't want some doing one thing and others doing another).

    The procedural way to do it (like C) is to return 0 on success and some number on failure, that number being used to look up some error string. (This is also similar to the way eval uses $@.)

    Pick your poison, I guess.

    Update: As per Abigail-II, I stand corrected.

    ------
    We are the carpenters and bricklayers of the Information Age.

    Don't go borrowing trouble. For programmers, this means Worry only about what you need to implement.

    Please remember that I'm crufty and crochety. All opinions are purely mine and all code is untested, unless otherwise specified.

      Actually, C tends to return -1 on failure, setting errno. For functions returning pointers, a NULL return value is often used to indicate failure.

      Abigail

      I would say that a lot depends on how OO your system is.

      Exception throwing vs. returning error values is a separate issue from whether your system is OO or not. You can have non-OO systems that throw exceptions. You can have OO systems that return error objects.

Re: How to say 'oops' in OOPs?
by Jenda (Abbot) on Apr 08, 2003 at 16:18 UTC

    I used to return negative error codes from Mail::Sender's methods, but this leads to strange looking code. It's kinda hard to distinguish errors from valid responses. And I doubt anyone used the code returned anyway.

    I'd say either return; in case of errors and set some $obj->{error} or die($with_message). And if the method doesn't need to return any value (if it's a command, not a "question") return the object itself. So that the users may stack the commands like this:

    $obj->cmd1(params) ->cmd2(params) ->cmd3(params;

    Though this only works well if you die() on errors. Anyway you may leave the decision on the user. Current version of Mail::Sender let's you specify which style of error reporting to use:

    $sender = new Mail::Sender { ..., on_errors => 'die', }
    Whatever you choose DOCUMENT DOCUMENT DOCUMENT. Error handling is usualy the worst documented feature of modules.

    Jenda
    Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.
       -- Rick Osborne

    Edit by castaway: Closed small tag in signature

      Thanks go to all. I think I'll go with the blank return;/$obj->errstr approach. I can see why one might do the die/eval thing, but it's not really my flavour of doing things.

      And yes, there is documentation.

      --
      bowling trophy thieves, die!

        If the error is really unexpected, don't be afraid to croak or die. eval/die is comparable to try/throw in other languages. If the error is expected to occur, then undef is probably the best returned value.

        Personally, I think "return undef;" is more clear to the reader than "return;" even though they have an identical effect. Explicitly returning undef tells the reader that hey, dammit, the caller is probably watching for a false/undef return.

        You can always make the error handling behaviour an option. For example, DBI can be made to return errors, or throw an exception, depending on whether you pass the RaiseError option at object creation time.

        That way you can keep exception throwing freaks like me happy too :-)

        You might also find Best Practices for Exception Handling of interest.

Re: How to say 'oops' in OOPs?
by cbro (Pilgrim) on Apr 08, 2003 at 14:45 UTC
    If you're just looking for an opinion...
    I suggest taking the time to write your own error method. Manual exception handling usually ends up being more verbose/descriptive, helps you better understand your class interaction (supposing your going to be doing more than what is in your example), and makes maintenance and debugging an all around easier process. IMO ;-)