http://www.perlmonks.org?node_id=930019

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

Also known as the "how high is up" question

I'm planning to re-write a module that's pretty old -- it dates from the 5.004 days, although it was upgraded to use some 5.6.0 constructs.

It currently has the bless-this-hash object creation, and it has a lot of object methods that I think would be better done as plugins. Despite that, it's not going to be a very large module.

I've never used Moose before, and I see there are stripped-down versions like Moo and Mo. Does it make sense to go with the smaller versions, or even with a different system altogether?

Replies are listed 'Best First'.
Re: How Large Does Your Project Have To Be to Justify Using Moose? (modular)
by tye (Sage) on Oct 06, 2011 at 19:14 UTC

    Sure, Moose requires way too much "buck" for the "bang" it gives (in many people's opinions). But I actually choose to not use Moose because I believe it fundamentally shifts design focus in the wrong direction and pushes designs in directions that are counter-productive in the long run.

    In order for Moose to be a complete OO system and for it to avoid requiring the writing of "ugly code", Moose pretty much requires (due to quirks of Perl 5) that everything be defined around methods. So Moose doesn't really allow you to talk about object attributes unless you write (or, much more likely, generate) accessor methods.

    So OO design using Moose will almost always revolve around, primarily focus on, what attributes you want your object to have and the accessor methods for them. And that leads to class design that is mostly glorified hashes (or Pascal 'record's, aka. C 'struct's). And I've seen that suck in the long run so many times.

    The point of good design is modularity. Modular design means defining a narrow, meaningful interface. Your focus should be on the methods you expose to the users of your class. Later you figure out what attributes are secretly used internally in order to implement that interface. (Never is when you worry about how inheritance plays into the design because inheritance is one of the worst tools you can use in design.)

    So I get more bang from trivial things I can roll myself quite easily than the bang I would get from the parts of Moose that I don't consider unwise to use.

    Implement attributes as constants so you get compile-time catching of typos on their names. Make internal accessing of attributes actually look different and a bit ugly, so you know the difference and you keep internal implementation details internal.

    Delegate rather than inherit when combining classes. Actually pay attention to the interfaces between your classes. Iterate the designs of those interfaces so that they stay relatively narrow, clean, logical. Achieve polymorphism via "duck typing". Allow future classes to easily slip in where your current classes are. Allow for dependency injection.

    Eschew the fall back to a C, C++, or Java style of "type system" that seems so essential to preventing simple mistakes when you are in a C, C++, or Java style of language where interfaces are only defined by positional parameter passing (and making methods more versatile via positional analysis of argument "types").

    You are using Perl where you can easily use named arguments when the arguments are even moderately non-trivial or non-obvious. Name matching beats the heck out of type matching in my long experience in both programming worlds.

    Types are very constrained objects. They are like objects with a single attribute, and that attribute is completely public. If you start slipping back into type-based validation (as you are almost pushed into doing as you use more Moose), you will find yourself more and more doing C++/Java-style design.

    You'll end up with classes as bags of attributes with accessors and then you'll try to put protections around those accessor by adding "types" and adding 'before' and 'after' wrappers. Your logic for a single class will be split apart into tons of tiny pieces where the order and interactions will become almost impossible to see, predict, adjust, and debug.

    But your code will look pretty. Just don't try to understand it on any sort of deep level (such as when you need to fix something).

    Yes, doing modular development this way is a little less convenient in the short term. Moose and inheritance allows you to bang out something simple-looking very quickly. It is hella convenient when you start. But it really reminds me of "goto": simple, powerful, convenient, and leads to spaghetti in the long run. It reminds me of "ORM is Vietnam". Moose is very appealing when you start out and even provides productivity wins at first.

    So, for me, the question would be more like "How small and short-lived does a project need to be for me to consider using Moose?".

    (Yes, I'm actually working on several simple tools to make good OO design more convenient in Perl 5. For example, delegation of a whole list of methods should be something trivial to write, almost as trivial to write as 'use base "Thingy";' or 'does CoolStuff;'. But even while it isn't trivial in Perl 5, it also isn't particularly hard. Just a bit inconvenient.)

    - tye        

      You do realize that you can write bad code with any module, not just Moose. Just because Moose makes things a little easier and standardizes/formalizes a few concepts does not mean it forces you to then go use those things badly. Seems to me that is the fault of someone not understanding good OO design and/or abusing the features provided by the tool. This really isn't Moose's fault, it is the users.

      So Moose doesn't really allow you to talk about object attributes unless you write (or, much more likely, generate) accessor methods.

      Actually, Moose advises you that accessors are a good way to encapsulate attribute access, but if you don't want to generate an accessor you don't have too. And if you want to then do direct HASH access internally, you can. Moose does not stop you from doing that, nor does is it impose any penalties and not allow you to take advantage of other things like managed instance construction, etc.

      So OO design using Moose will almost always revolve around, primarily focus on what attributes you want your object to have and the accessor methods for them. And that leads to class design that is mostly glorified hashes (or Pascal 'record's, aka. C 'struct's). And I've seen that suck in the long run so many times.

      Actually, if you do proper OO design, then no, this is not true. You are blaming the tool for the mess the user made with it.

      Implement attributes as constants so you get compile-time catching of typos on their names. Make internal accessing of attributes actually look different and a bit ugly, so you know the difference and you keep internal implementation details internal.

      I agree that making everything methods does kind of make everything blend together, and that Perl's highly "virtual" approach to method dispatching makes it very hard to catch typos. However the encapsulation is kind of a good thing.

      Delegate rather than inherit when combining classes.

      Sure, Moose makes this easy too with the handles attribute option.

      If you start slipping back into type-based validation (as you are almost pushed into doing as use more Moose), you will find yourself more and more doing C++/Java-style design.

      Again, you are not pushed into using types in Moose, it is an option.

      You'll end up with classes as bags of attributes with accessors and then you'll try to put protections around those accessor by adding "types" and adding 'before' and 'after' wrappers. Your logic for a single class will be split apart into tons of tiny pieces where the order and interactions will become almost impossible to see, predict, adjust, and debug.

      Again, don't blame the tool for someone being stupid. Nothing about Moose forces you into doing something as idiotic as you describe. Just because a feature exists doesn't mean you have to use it, and most certainly does not mean that you should abuse it.

      (Yes, I'm actually working on several simple tools to make good OO design more convenient in Perl 5. For example, delegation of a whole list of methods should be something trivial to write, almost as trivial to write as 'use base "Thingy";' or 'does CoolStuff;'. But even while it isn't trivial in Perl 5, it also isn't particularly hard. Just a bit inconvenient.)

      Where is the code? I would love to have a look.

      -stvn

        First, let me stress that I think you should be very proud of Moose. It is a stellar accomplishment. Really.

        Now, I'd be more convinced by the standard "don't blame the tool" arguments if I had argued that one shouldn't use Moose because it fails to prevent you from doing stupid things. I'm a Perl programmer; having the freedom to do things previously considered stupid is something I highly value.

        I'll go to a simple analogy. I was not arguing, "Please don't use Acme brand hammers because I've seen people trying to drive screws in with a hammer and that is a bad idea". I was more arguing "Nails relatively suck compared to screws when it comes to holding two pieces of wood together. Sure, it is much easier up front to pound a nail in with one good whack, but in the long run, you'll be happier with how well the two pieces of wood stay held together if you take the time to twist a good screw all of the way in. And this makes it not the best allocation of resources to set up to use an extremely well-made Acme pneumatic nail driver that can't really be used unless you also pull in a whole trailer full of gas generators and compressors. And I know this because I've seen smart, professional carpenters nail themselves into a corner in ways that were very easy for them because they didn't even have to do any pounding in order to toss another nail into the pieces of wood that they were trying to assemble."

        The worst part of that analogy (IMHO) is that if I set up an Acme pneumatic nail driver and use it to make a cheap little birdhouse, then I can give that birdhouse to my grandmother and she can just hang it up in her tree without even needing a K-Mart hammer. But if I build a module using Moose, then anybody who wants to use that module is expected to do the equivalent of setting up their trailer full of generators and compressors and then to wait for at least some of the compressors to come on-line each time they want to use my module.

        Nothing about Moose forces you into doing something as idiotic as you describe.

        That particular "idiotic" thing I described is pretty close to what was proposed very recently by somebody smart who I respect when they talked about how they would go about doing what we are instead doing much more "by hand" (in part because Perl seems to still lack a simple, small, rechargeable screwdriver and in part because, at a certain scale, a custom tool can fit so much better than an amalgam of general-purpose components, even when those components are quite well-built, and our problem is at that scale).

        Are you telling me that Moose documentation discourages the use of pre and post method wrappers and generated accessors with type-based constraints? Since you chose to describe that as "idiotic", surely the documentation discourages it. Most certainly, the documentation can't encourage it!

        If I go to pick up Moose (much as the original poster in this thread suggested), the documentation will naturally encourage me to think about design first in terms of the interface I want to provide and will surely give me good advice about keeping attributes hidden behind that interface.

        I'm not so foolish as to think that Moose forces bad design. But surely, Moose won't encourage bad design. Surely, it won't just immediately suggest to me that I should start with type-enforced, auto-generated accessors with before/after wrappers and inheritance! Why, that's most of the way to what you called "idiotic" combined with what every wise old hacker I check with knows is just plain a bad idea (inheritance).

        Well, let us start reading Moose documentation...

        NAME

        Moose - A postmodern object system for Perl 5

        VERSION

        version 2.0205

        SYNOPSIS

        package Point; use Moose; # automatically turns on strict and warnings has 'x' => (is => 'rw', isa => 'Int'); has 'y' => (is => 'rw', isa => 'Int'); sub clear { my $self = shift; $self->x(0); $self->y(0); } package Point3D; use Moose; extends 'Point'; has 'z' => (is => 'rw', isa => 'Int'); after 'clear' => sub { my $self = shift; $self->z(0); };

        Oops. Wow, the encouragement didn't take any effort at all to find.

        So, you can claim that only stupid people would do stupid things with Moose. But my experience is quite at odds with that (though that experience covers more than just Moose; it also covers other tools that make auto generation of accessors easy).

        Certainly, exceptionally smart and careful people can make good use of some features of Moose without ever being lured into the problematic but easy routes. But that doesn't make Moose the best nor cheapest way to accomplish those smart things.

        Where is the code? I would love to have a look.

        It is not like I'm hiding it. I'm working on it. I release parts as I am able to make them release-worthy. I discuss the ideas fairly often and provide example code as well. Some of the current bottleneck is figuring out which directions scale better to more complex designs. Plus the past couple of months have been less productive due to burn-out (which I seem to have finally broken out of just recently). Much of the code will likely hit CPAN as individual modules. I expect most of the modules to be useful in isolation (and to not require each other) even though I plan to use them together. I also expect them to be quite small. Some code experiments will get posted at PerlMonks before CPAN.

        But there are also some fundamental "direction" decisions that I have to sort out. And there is also the wordsmithing of prose and examples in hopes of framing some of the overarching principles to help people be more likely to understand what I'm trying to get at.

        - tye        

        If you don't use the accessor generation and attribute type-checking, what does Moose buy you to offset its considerable costs?

      So OO design using Moose will almost always revolve around, primarily focus on what attributes you want your object to have and the accessor methods for them.

      I don't have those problems. What am I doing right?


      Improve your skills with Modern Perl: the free book.

        That's funny, given that what else you said in this thread is only slightly truncated at:

        If I never have to write an accessor generator by hand again, great!

        I use Moose for anything with more than one object attribute.

        Otherwise, your question is obviously just rhetorical since I don't know what you do or don't do when you design. Though, certainly, one should take a self review of design prowess with a grain of salt.

        I also never claim that using Moose makes good OO design impossible. If you can fight the tendency that I pointed out, then good for you. Though, it doesn't really sound like it, even just in this thread. :)

        - tye        

      If I've understood you correctly, you make these three arguments against Moose:
      1. It encourages users to create "glorified hashes".
      2. Other techniques can give you syntax-checking of attributes.
      3. Moose type-checking can create more problems than it solves.

      I don't understand your argument against "glorified hashes." Are you criticising Moose for being unable to instantiate other kinds of references (array refs, scalar refs etc)?

        I'm criticizing Moose for nearly discouraging the designing of a class based on just the interface and not exposing the attributes (which should be internal to the implementation). Even if you don't have Moose generate accessors, then it generates a constructor that still exposes the attributes. And if you don't generate accessors then you can't make use of many roles.

        I'm starting to realize that there are people (I can't tell how many / how common yet) that can't even really conceive of designing a class without jumping straight to building a list of attributes, even after you explain to them that they should design the interface before designing the attributes (which are part of the implementation). Moose certainly encourages that mindset.

        And I keep finding more and more instances of bad class design that look to me to be primarily due to way too much focus on an object as "just a bag of attributes".

        When I design a class, the constructor(s) are just another part of the interface. They don't expose the internal attribute names. So I don't understand the mindset of making the constructor just be a list of attribute-name and attribute-value pairs, even if that is just the default that can be overridden. It leads to classes that poorly encapsulate the internal details. It is anti-modular.

        Worst of all, it encourages making classes that expose data structure which encourages encoding class-specific behavior outside of the class (the most common sign of "bad design" that I keep finding and then looking for the source of and tracking back to a class design concentrating on a list of attributes).

        # One reasonable constructor interface for a square: Square->new( center => [ $x, $y ], width => $w );

        But I'd likely implement the square as a list of 4 x/y coordinates. And I'm not sure how I want to store the coordinates (as pairs or as parallel arrays or ?). I don't even think too much about how I'm going to implement the square's data when I'm defining the interface for the square.

        I design the interface first and come up with my first stab at what attributes to use to implement it later. And changing my implementation shouldn't force me to change my interface.

        Providing so many ways to plumb attributes straight into the class's interface actually makes it hard to keep the interface separate. And then there are the add-on tools that become useless or much less useful if you don't plumb your attributes to the interface.

        I'm coming to realize that the benefit of modular programming practices is that they are slightly inconvenient and thus force you to see the lack of perfection in your first thought of a design and encourage you to spend some time re-thinking and iterating the design, often resulting in you improving the design (and sooner rather than later and thus saving much larger spans of time / effort).

        It should be slightly inconvenient to delegate a method. That way, you don't just blithely delegate way too many methods and end up delegating methods that are quite inappropriate and are going to lead to problems (I've seen several instances of that just this week due to old uses of inheritance). It should be more inconvenient to provide a bare accessor to an attribute. You should only provide accessors when they really make sense for the interface, completely ignoring the current implementation.

        The construction of an accessor should be motivated by the interface. When you go to implement that accessor, at that point you might decide that it also makes sense to make an attribute that simply mirrors the data plumbed via that accessor. You should not follow the design decision about including some attribute with trying to decide whether or not you want to have an accessor for it.

        Otherwise, your interface is being driven by your implementation, and that is a perfect example of anti-modular design.

        - tye        

Re: How Large Does Your Project Have To Be to Justify Using Moose?
by chromatic (Archbishop) on Oct 06, 2011 at 17:50 UTC

    If I never have to write an accessor generator by hand again, great!

    I use Moose for anything with more than one object attribute. If you're concerned about size or speed, Any::Moose is a nice tool to ease your project into the Moose world and pay only for what you need.


    Improve your skills with Modern Perl: the free book.

      That does look nicely flexible. I think I may try that out. Thanks.

Re: How Large Does Your Project Have To Be to Justify Using Moose?
by elTriberium (Friar) on Oct 06, 2011 at 23:45 UTC
    • Does the code use a lot of attributes with getters and setters?
    • Are you concerned that someone might break your code by inserting wrong values and you have to check for that?
    • Is it painful to add or modify some of the code?
    • Are you doing a lot of work manually that Moose could do for you?
    • Do you have multiple classes that need to communicate with each other?
    • Is there a lot of boilerplate-code that's there to glue the classes / methods / attributes together?
    • Would you benefit from things like Roles / Delegation / or any of the MooseX modules?
    In my opinion these are some of the questions you should ask yourself. I don't think there's any general rule at which point you should move to Moose. In my opinion it's a stable OO system that (nearly) only uses features you can also find in other OO languages (so no magic introduction of new things). I prefer writing code using it, but as can easily be seen in this thread and many others, there are of course also other opinions on that matter.
Re: How Large Does Your Project Have To Be to Justify Using Moose?
by Anonymous Monk on Oct 06, 2011 at 17:13 UTC
    Alaska
      Besides being snarky and without a meaningful legend that is wrong. If you actually had a project you could subjectively consider bigger than most countries then it would be the time to roll your own object|function system.

      Also: Rhode Island.
        Rhode Island is too far South to have moose.
Re: How Large Does Your Project Have To Be to Justify Using Moose?
by JavaFan (Canon) on Oct 13, 2011 at 06:34 UTC
    I've never used Moose before, and I see there are stripped-down versions like Moo and Mo. Does it make sense to go with the smaller versions, or even with a different system altogether?
    How can anyone possibly answer that without knowing the code?

    But I would start my questioning with "does it need re-writing"? What's the benefit? What's the cost?

Re: How Large Does Your Project Have To Be to Justify Using Moose?
by jdrago999 (Pilgrim) on Oct 08, 2011 at 20:04 UTC

    Have a look at Mo if you want Moose-like OO without all the weight.