Stories
Slash Boxes
Comments
NOTE: use Perl; is on undef hiatus. You can read content, but you can't post it. More info will be forthcoming forthcomingly.

All the Perl that's Practical to Extract and Report

use Perl Log In

Log In

[ Create a new account ]

TheNomad (7014)

TheNomad
  (email not shown publicly)

Journal of TheNomad (7014)

Wednesday December 06, 2006
07:21 AM

Thinking about Accessors

[ #31824 ]

I've been trying to maintain some legacy code that badly needs updating. One of the main sins committed in this code is direct object access, so $obj->{attribute} = 'value', rather than $obj->set_attribute('value');

To fix this, I need to choose a way to implement accessors. There are a few problems. Firstly, the 'perl' way of doing this seems to be a moving target. A few years ago, people used Class::MethodMaker, but the essential (in my opinion) 'new_with_init' option has gone. Now, people seem to be choosing either Class::Accessor. Of course, what's really trendy now is inside-out objects, and the module of choice (because it's mod_perl compatible) is Object::InsideOut.

I like Object::InsideOut. I like the interface. The problem is, because I'm using legacy code, I will need to assume the objects are blessed hashes for some time yet. Object::InsideOut wants the objects to be blessed scalars and croaks if they are anything else.

Class::Accessor's new method at least returns a hash. At the moment it's probably what I'll have to use. But it doesn't automatically sanity check. For example:

package GoodbyeThere;
use base 'Class::Accessor';
__PACKAGE__->follow_best_practice;
__PACKAGE__->mk_accessor s(qw/a b c/);

use strict;
use warnings;

sub do_stuff {
    my $self=shift;

    $self->set_b('ssss');
    $self->set_c('tttt');
    print "Output " . $self->get_a . $self->get_b. $self->get_c . "\n\n";

}
1;

If you go:

perl -MGoodbyeThere -e '$h = GoodbyeThere->new({a => 1}); print "$h\n";$h->do_stuff'

The output is:

GoodbyeThere=HASH(0x814f750)
Output 1sssstttt

So far so good. But if you go:

perl -MGoodbyeThere -e '$h = GoodbyeThere->new({a => 1, query => 9}); print "$h\n";$h->do_stuff; print $h->{query}'

The output is:

GoodbyeThere=HASH(0x81b9d44)
Output 1sssstttt

9

See 'query' is still there. It would be better if it just threw it away. Which brings me to another point. With Class::Accessor, 'super' doesn't work as you might expect, so you need to write an accessor that sanity checks the input hash. I suppose there are worse things in life though...

The Fine Print: The following comments are owned by whoever posted them. We are not responsible for them in any way.
 Full
 Abbreviated
 Hidden
More | Login | Reply
Loading... please wait.
  • I've been trying to maintain some legacy code that badly needs updating. One of the main sins committed in this code is direct object access, so $obj->{attribute} = 'value', rather than $obj->set_attribute('value');

    Is this actually broken? In that, is this direct access causing you sufficient hassles that it's worth the code churn to change it?

    Change means the potential for breakage, and clouds the revision history given by your version control change annotation. There is a great temptation as the

    • Is this actually broken? In that, is this direct access causing you sufficient hassles that it's worth the code churn to change it?

      It's caused a little bit of trouble as some coders have lazily stored values in the objects that shouldn't be there. It took me ages to debug these things (although it's easy now I know about them). If they'd been forced to used accessors the design would have been tweaked and would be much better as a result.

      Anyway, I don't intend to change all the code at once. Instead

      • Instead, any new functionality or any rewrites should use the accessors.

        Is that a requirement specifically for this project, or do you always want this?

        I don't often write accessors. I only write an accessor if I explicitely want to expose an attribute to the outside world. Otherwise, I won't provide an accessor. The problem, IMO, is that there's no such thing as private methods - any method is automatically part of the API. Which means that whenever your removing an attribute that has an accessor fro

        • The problem, IMO, is that there's no such thing as private methods - any method is automatically part of the API.

          I disagree that this is always the case.

          Your API is what you document it to be. It's perfectly valid to either document "private" methods elsewhere, or put them in a part of the documentation that has the disclaimer "The behavior of the following is unspecified. This is how these might work now, these are all subject to change without notice, I guarantee nothing."

          I suspect that you know that, and are instead arguing "In practice, people will use methods as if they were a documented part of the API."

          • I disagree.

            The problem is just as I said. There are no private methods. Suppose class 'A' defines a method 'foo', and class B inherits A. No matter how often A documents 'foo' to be private, B is robbed from having a method 'foo' and do away with it freely. That's the essense of not having private methods - all methods will be inherited by a subclass. Programmers (can) read the documentation, but perl will not.

        • Is that a requirement specifically for this project, or do you always want this?

          It's for this project. There are good reasons why accessors are needed for the project.

          However, generally I'm lazy and can't be bothered with any sort of accessors. And in small projects direct access hasn't created any problems.

          When I first heard about inside-out objects, I thought they were solving problems that hardly ever arose. I've become a convert though!

  • You don't have to have a blessed hash; you only need something that supports hash dereferencing. (Of course, then you have the joy of fixing all of the code that assumes that ref returns something meaninful.)

    • You don't have to have a blessed hash; you only need something that supports hash dereferencing. (Of course, then you have the joy of fixing all of the code that assumes that ref returns something meaninful.)
      Brilliant! I might have a play with that and see what I manage to come up with.
      • Perl Hack #87 has another trick, this time using restricted hashes to catch people poking in object innards unwisely.

  • I 've been trying to maintain some legacy code that badly needs updating. One of the main sins committed in this code is direct object access, so $obj->{attribute} = 'value', rather than $obj->set_attribute('value');

    This is one of those areas where Ruby's Uniform Access Principle demonstrates just how clunky Perl's OO and accessors really are. To wit:

    class Foo
       attr_accessor :attribute
    end

    obj = Foo.new
    obj.attribute = 'value'

    "But, wait!", you cry. "Accessing the attribute like that is an encapsulation violation!"

    You're not accessing the attribute directly. The syntax just makes it *look* like you are. In reality you're calling the Foo#attribute= method. This longhand example should clarify things:

    • def initialize
          @attribute = 7
      end

      This is one of those areas where Perl 6's Uniform Access Principle demonstrates just how clunky and non-uniform Ruby's instance attributes are.

      • I assume you mean the fact that Perl 6 provides default read-only (though configurable) accessors on attributes. That's what I get from Exegesis 12, anyway, unless something has changed.

        Personally, I think it's a misfeature, because I don't always want users to have access to the instance variables I'm using. I'm sure that's configurable, too, but I'd rather be explicit than implicit in this case. Or, just write a custom 'is' method that lets me imitate the Perl 6 syntax.

        The topic of autogenerating a

        • I actually meant that most of the Ruby code I've seen accesses instance variables directly within the class, despite the presence of an autogenerated accessor. There's a subclassing problem there. (Actually it's not only a problem for subclassing, but I use that as a convenient shorthand because I don't have a good way to say "allomorphic instance modification".)

          The fact that there has never been a clamoring for it among the Ruby community ought to be telling, though.

          I've never seen a clamoring for

          • I actually meant that most of the Ruby code I've seen accesses instance variables directly within the class, despite the presence of an autogenerated accessor. There's a subclassing problem there.

            I'm afraid I don't follow. Unless you have a custom writer, doing "self.attribute = 'value'" instead of "@attribute = 'value'" is a wasted method lookup. I fail to see what subclassing problem you would have.

            I've never seen a clamoring for a saner require either, but I don't take that as evidence that it's a particularly clear, clean, or well-defined feature.

            My only gripe with require is that it doesn't look in its own directory first, and so all the library authors end up doing $LOAD_PATH.unshift(File.dirname(__FILE__)) in each file. Otherwise, I'm not sure what you're referring to.

            • I fail to see what subclassing problem you would have.

              It's only a problem if a subclass wants to do something different with an attribute. I run into that once in a while.

  • Object::InsideOut wants the objects to be blessed scalars and croaks if they are anything else.

    Object::InsideOut does way too many things and breaks a major principle of inside-out objects (the exact one that is causing you to disregard it). Take a look at Class::InsideOut instead, which does only the minimum needed for a solid, proper inside-out implementation.