in reply to Error handling in chained method calls

Under what circumstances are you likely to get an error from:$thing->do_something;?

  1. $thing could be undef.

    If you create $thing, and you error checked that creation my $thing = Some::Class->new(...) or die;, this can't happen.

    If your code is passed $thing and you validated your inputs:

    sub SomeSubOrMethod { my $thing = shift; croak 'Bad param' unless $thing and ref $thing; ...

    This can't happen.

  2. $thing might be a none ref, or non-blessed ref.

    If new() or similar constructor returns a non-ref or non-blessed ref, it is a fundamental coding error that will be discovered the first time class is used. Even the simplest of tests, during the development of the bad class, or when you first try to use that class will detect this. It couldn't possibly make it into production unless you are in the habit of putting code that has never even been run into production.

  3. $thing is of a class that doesn't have a do_something method; or equivalently, do_something is typed wrong.

    If you created $thing yourself, then you know what class it is, so you'll know what methods it has. Your testing should be sufficient to detect any such typos long before they become a production problem.

    If you are passed $thing, and you've validated your inputs, then you've ensured that it is of a type that you can reasonably expect to have a do_something method.

In short. DON'T perform such checks. Allow them to fail immediately and loudly.

This will ensure that if the circumstances arise, the clearest possible indication of what has gone wrong is given to the programmer who a) typoed the method name (usually you); or b) passed your code a non-reference, or reference to the wrong class of object.

The alternatives are:

In the end, even if you perform your blow by blow validation every time before you call a method--besides the horrible performance penalty you impose--there is no guarantee that the do_something method:

In vast majority of cases, the method will exist and do what is required. In the remaining percentage of cases, there's nothing you can reasonably do about it. So, just get on with it and let it fail on those rare occasions when it will.


Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
"Science is about questioning the status quo. Questioning authority".
In the absence of evidence, opinion is indistinguishable from prejudice.
RIP an inspiration; A true Folk's Guy

Replies are listed 'Best First'.
Re^2: Error handling in chained method calls
by LanX (Saint) on Oct 24, 2010 at 10:46 UTC
    > besides the horrible performance penalty you impose

    Not necessarily.

    If the methods are immutable and follow the "cascading convention" to always return $self it should be possible to be much faster by memoizing name and reference of allowed methods in a hash.

    The trick would be to use the $obj->$methref() syntax, compare

    Re: Error handling in chained method calls

    IIRC normal method calls are ten times slower than a function call.

    So if performance matters this could be a legitimate way to boost "alien" classes one isn't allowed to change.

    Cheers Rolf

      Hm. I'm pretty sure that Perl already does method caching, so adding your own would be redundant.

      But mostly, why? There's no good reason to test this, as there's nothing better you can do that fail anyway. So what would be the point of adding all the complexity, when all you can do if you do detect a problem is fail anyway. Its just pointless.


      Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
      "Science is about questioning the status quo. Questioning authority".
      In the absence of evidence, opinion is indistinguishable from prejudice.
        > Hm. I'm pretty sure that Perl already does method caching, so adding your own would be redundant.

        I wonder how. Perl would either need to be informed in advance that the methods are immutable or monitor the complete inheritance chain for new methods.¹

        But I haven't done any benchmarks yet ... IMHO there should be a notable performance gain.

        > But mostly, why?

        Well ask Gabor... I can only speculate that he wants to gather more detailed debug informations.

        ATM I can say more about the how than the why.

        Cheers Rolf

        1) of course I would appreciate to learn more about a nifty mechanism to improve performance...

Re^2: Error handling in chained method calls
by choroba (Cardinal) on Oct 25, 2010 at 11:03 UTC
    Well, $node->parent can either return another node, or undef if the node has no parent (e.g. it is a root of a tree). Thus, $node->parent->parent can sometimes work and sometimes not. The problem is the chaining.
      Thanx for this clarification of a use case. :)

      IMHO again this could be solved with a coderef wrapper $c_parent which calls the real method only on definedness of the object.

       $node->$c_parent->$c_parent

      Another (classical) approach would be defining a NULL-node object which is returned instead of undef and wouldn't cause an error.

      But the latter would only work if one has access to the design of underlying library.

      Cheers Rolf

      Thus, $node->parent->parent can sometimes work and sometimes not.

      Then the situation is flow control logic rather than error handling.

      If the node doesn't have a parent, you can't get it's grandparent, and so there is no logical option but to split the statement into parts. That isn't really a case of the chaining being the problem so much as simply a bad algorithm.

      Taking it back to the OPs example, if you're chaining your way back to the root, you have to check whether you've reached there or not at each iteration, but if you've successfully retrieved a defined something from if( defined obj->parent ), you don't need to then check that it is a reference, or whether that reference can( 'parent' ), because the parent method should not be able to return anything other than a valid node, or undef.

      A simple:

      sub root { my $self = shift; my $root = $self; $root = $_ while defined( $_ = $root->parent ); $root //= $self; return $root; }

      Or, if you're dealing with an algorithm that requires access to grandparent nodes, you have to deal with nodes that don't have a parent never mind a grandparent:

      sub grandparent { my $self = shift; my $parent = $self->parent // return; return $parent->parent; } ... if( my $gp = $obj->grandparent ) { ## use it; } else{ ## take a different path }

      But you don't (shouldn't) have to deal with getting a defined something that isn't a node.


      Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
      "Science is about questioning the status quo. Questioning authority".
      In the absence of evidence, opinion is indistinguishable from prejudice.
        ... there is no logical option but to split the statement into parts
        ...
        ... if you've successfully retrieved a defined something from if( defined obj->parent ), you don't need to then check that it is a reference
        Very true. I did not see chainig mentioned in your post, but this reply makes your comment complete.