Pivotal Labs

Lovely Demeter, Meter Maid

edit Posted by Alex Chaffee on Sunday August 05, 2007 at 03:08PM

Wes and Parker pointed us to this article:

Misunderstanding the Law of Demeter by Dan Manges

which is a very nice discussion of the "law" (actually just a suggestion, but a very strong one) that encourages your objects, like small children, not to talk to strangers. Some people seem uneasy with the LoD since it requires them to refactor their objects to have proxy methods all over them. Instead of Paperboy calling customer.wallet.cash you have to put an extra method on Customer -- either cash (attribute delegation -- check out Forwardable in Rails btw) or pay (behavior delegation). But these proxy methods are not clutter, they're the essence of encapsulation. Do not fear encapsulation. Fear is the mind killer.

Anyway, Dan does a great job explaining this concept, until the very end of the article, when he totally chickens out.

Let's say we're developing a Rails view to display order information, and this should include details on the customer. We might write the view like this:

<%= @order.customer.name %>

...Our view now becomes:

<%= @order.customer_name %>

We've traded a dot for an underscore. And thinking about this further, why should an order have a customer_name? We're working with objects, an order should have a customer who has a name. Adding these attribute delegations also decreases maintainability.

The crux of this is that webpage views aren't domain objects and can't adhere to the Law of Demeter. Clearly from the examples of behavior delegation the Law of Demeter leads to cleaner code. However, when rendering a view, it's natural and expected that the view needs to branch out into the domain model.

This is dead wrong. Ading delegation -- for getters, at least -- increases maintainability, and especially mockability. It makes perfect sense for an order to "have" a customer name -- that is, respond to a message querying it for its customer's name -- since now the order can do things like decorate the customer's name, or fake one up if there's no customer object set on the order. And if the interface of Customer changes then none of the clients (including tests) of Order need to change at all.

[ Invoice (view) ] -> [ Order ] -> [ Customer ]

The crux of this is that webpage views aren't domain objects and can't adhere to the Law of Demeter.

Wow again. This is circular reasoning (the "begging the question" fallacy). Why aren't webpage views domain objects? Why did Rails make the bizarre decision to make one of the MVC Holy Trinity be just plain text files, and to share all instance variables (ugh) with the controller? Why can't views adhere to reasonable object-oriented semantics and support encapsulation, interface-based design, etc. etc.?

See my post "Life Without Getters" for an alternate vision... Or (thanks Wes) the Presenter Pattern...

...but even without going down those roads, the LoD provides a great way of keeping views isolated from a tangled nest of model objects, all pointing to each other. I try to make my views connected to a small set of objects -- often just one -- and to make sure each of those objects' public APIs exposes everything the view needs.

Clearly from the examples of behavior delegation the Law of Demeter leads to cleaner code. However, when rendering a view, it's natural and expected that the view needs to branch out into the domain model. Also, anytime something in a view dictates code in models, take caution. Models should define business logic and be able to stand alone from views.

More FUD. A view is a perfectly valid client of a model object, and if some of its logic makes more sense encapsulated in the model -- say, as a "display_name" property on a Car object which concatenates make, model, and year -- then that's where it should go. Put the behavior with the data, tell don't ask, and don't peep into your neighbor's windows.

Comments

  1. Alex Chaffee Alex Chaffee on August 05, 2007 at 03:23PM

    Jim:

    From Wikipedia: For the Greeks, Demeter was still a poppy goddess.

    I've always thought that Lieberherr was a crackpot. Calling a simple "best practice" a law so overstates its usefulness. I don't ask a drawer to give me a fork; I go into the drawer to get a fork. I don't much care what the drawer thinks of that.

    Alex:

    Semantics. The reason it's a law is that for the system named Demeter, it was a law. As it is it's just a damn good idea and if you want to make an OO program that doesn't obey the... Suggestion of Demeter... then you'd better have a good reason why not.

  2. Alex Chaffee Alex Chaffee on August 05, 2007 at 03:24PM

    One of the Ruby idioms that always give me a chuckle, just 'cause I can do it, is:

    <code> hash.keys.sort.reverse.join(',')   # not necessarily a real example
    </code>

    Because these are utilities, I expect I can do this without much future trauma.

    I also think the general problem is more nuanced than the solution Demeter provides. Back in the day (putting on my old fart hat!), C programmers didn't use collections because there weren't any (except built-in arrays). Until decent garbage-collected languages came along ('decent' doesn't include C++ STL), you couldn't reasonably put something into a collection and trust someone else to (or not to) free it for you. The above example uses arrays, which throw away their contents when finalized, so the hash doesn't mind copying its keys into an array and then giving me the array to do whatever I want with because he knows that his keys will hang around until BOTH of us (and our transitive closure) free them.

    So Demeter (pre-C++) was designed around the problem of abstractions being unable to manage their own memory while simultaneously passing objects around in a reasonable fashion, which is a problem we don't have in GC-based languages.

    Demeter was also developed at a time where our sense of what constitutes a "type" was not so nuanced. The above example works, not because I'm lazy, but because the Hash and Array classes are not precisely typed, but simply honor Enumerable and Comparable, which are much looser constraints. It's much easier for the classes' authors to change the behavior and still honor the contract than it would be in a language that didn't have named interfaces (not to mention duck typing). So as long as I can iterate the collection and sort the elements, I don't much care how they're implemented.

    And lastly, we have TDD now; Lieberherr didn't. So if we change something that messes up its callers, the tests will fail.

  3. Alex Chaffee Alex Chaffee on August 05, 2007 at 03:26PM

    abstractions being unable to manage their own memory

    Memory is one of the many things abstractions (objects) (obstractions?) should be able to manage. Demeter ensures they will keep control of all their private stuff (like instance variables, decorations, relationships to other objects, persistence, transactions, threading, etc.). Just because memory is less important these days doesn't mean Demeter is obsolete.

    hash.keys.sort.reverse.join(',') # not necessarily a real example

    Because these are utilities, I expect I can do this without much future trauma.

    Yes, there's a sneaky exception to the LoD involving library classes. But even the above is not a true violation of the LoD, since the methods are returning immutable copies of internal state, not live references to nested objects.

    And lastly, we have TDD now; Lieberherr didn't. So if we change something that messes up its callers, the tests will fail.

    TDD is indeed teh awsum, but it's not a panacea, nor an excuse for sloppy programming practices. Tests support refactoring to patterns (like LoD) that make the code clearer and easier to maintain.

  4. Nick Kallen Nick Kallen on August 06, 2007 at 06:29AM

    The Crux of your argument is that

    delegation -- for getters, at least -- increases maintainability, and especially mockability. It makes perfect sense for an order to "have" a customer name -- that is, respond to a message querying it for its customer's name -- since now the order can do things like decorate the customer's name, or fake one up if there's no customer object set on the order.

    You make a mistake here in thinking that the Order must be the Decorator of the Customer. An Order could return a Decorated Customer when asked for a Customer. And voila all of your difficulties are solved.

    Yes, the 'ripple-effect' of a change in Interface to Customer is broad. But your solution is to provide numerous facade's to Domain objects that otherwise should have a consistent interface. You've gotten loose coupling along with low cohesion.

    Suppose you have a CustomerRenderer. it is now no longer usable in your design; you cannot compose an OrderRenderer out of a CustomerRenderer. If you enforce the cohesion with an Interface, well you have your ripple-effect again. To me, the claim that

    And thinking about this further, why should an order have a customer_name? We're working with objects, an order should have a customer who has a name.

    --this is sufficiently persuasive. This is the argument from Ontology and it trumps the argument from Mockability any day.

  5. Zak Tamsen Zak Tamsen on August 06, 2007 at 01:51PM

    you make some good points. it is refreshing to finally see a discussion about views and LoD.

    but you are also "dead wrong".

    "Ading [sic] delegation -- for getters, at least -- increases maintainability."

    this an oversimplification and shows a misunderstanding of LoD. LoD is NOT about delegation.

    in addition, the tone of your post is unnecessarily aggressive. I could continue to pick it apart and be pedantic as you have with Dan's but I find that childish and trite.

    so instead, I dare you to suggest that Dan is a chicken to his face... when I'm around.

    sincerely, Zak Tamsen

  6. Clint Bishop Clint Bishop on August 06, 2007 at 02:41PM

    kick his ass, Seabass!

  7. Alex Chaffee Alex Chaffee on August 08, 2007 at 12:35AM

    Whoa! Am I being called out on my own blog? This is exciting!

    First off, I want to apologize to Dan Manges for implying that he's a coward. I've never met him but I'm sure he's got the heart of a lion. I loved his description of LoD and my saying he "chickened out" was a clumsy joke referencing my mention of Fear Of Encapsulation. I foolishly reckoned that citing Dune's Litany Against Fear would serve as a virtual smiley, but I guess I needed to be clearer about that. And I guess calling his arguments "FUD" and "circular" was a bit over the top, but I meant it in the spirit of honest debate.

    But second... Zak, I cannot let this lie. At the risk... nay, the certainty of fanning a bloody and wasteful flame war... Oh, it's on!

    I often find myself in code that has not been flawlessly designed and could use some improvement. I'm sure your object trees are shallow as your musical preferences and as easy to mock as your hairdo, but in my experience most people these days don't design objects, they design data models, and writing tests on top of deeply nested domain models is a major PITA. You painstakingly write one mock only to find that it references another object, and you have to start all over again and mock that one out too! I'm trying, in my clumsy and pedantic way, to make the point that there's another way to do things. And this way -- inspired by LoD -- can be used in all layers of your app, views not excluded.

    By the way, my referring to your personal grooming and musical tastes was an example of the ad hominem fallacy. Pedantic enough?

    Pedantically yours,

    • Alex

    P.S. In all seriousness, Zak, if you're still reading this, can you tell me why delegation is not a useful part of LoD? Can you describe an alternative to my display_name example?

    P.P.S. Fight! ThoughtWorks vs. Pivotal, tomorrow after school, in the smoking area.

  8. Steven Wisener Steven Wisener on August 10, 2007 at 01:58PM

    This is dead wrong. Ading delegation -- for getters, at least -- increases maintainability, and especially mockability.

    Prove it. If you aren't actually decorating anything (which is 99% the time in the business applications I've written), all you are doing is adding an extra line of code to account for the 1% of the time you want to do something fancy. Making the correct tradeoff is what separates an engineer from a scientist.

    Why aren't webpage views domain objects? Why did Rails make the bizarre decision to make one of the MVC Holy Trinity be just plain text files, and to share all instance variables (ugh) with the controller?

    Dan didn't make the statement that webpages shouldn't be domain objects. He said that views aren't domain objects. Since he's talking about Rails, he's correct. Quit being difficult.

    Life Without Getters is a pipe dream. In actual business applications, the view is separated from the model so that people with different skill sets can work on them independently. Again, this is a tradeoff based on pragmatism. You can't seriously expect a web page designer to open up some Java or Ruby file to hack up an HTMLPersonRenderer just because it gives you and Allen Holub OOrgasms. That's why views are different in Rails.

    A view is a perfectly valid client of a model object, and if some of its logic makes more sense encapsulated in the model -- say, as a "display_name" property on a Car object which concatenates make, model, and year

    That makes sense if the data is always viewed the same way, perhaps for some basic reference data; for your core domain objects, however, it is quite likely that there are many different views of the object in the same application. What if sometimes you want to include the trim in the car description? What if the view wants to break the make and model on separate lines? Or put them in table columns? That's the reason that MVC separates the model and the view.

  9. Alex C Alex C on August 10, 2007 at 05:37PM

    You can't seriously expect a web page designer to open up some Java or Ruby file to hack up an HTMLPersonRenderer just because it gives you and Allen Holub OOrgasms.

    OOrgasms! I love it!

    By the way, this deserves to be the subject of another blog post, but I have yet to meet this hypothetical web page designer who is smart enough to understand CSS, modular decomposition via partials, embedded scripts and variables, and JavaScript, but is unable to learn how to edit source code that uses print statements or do blocks instead of angle brackets.

    My current project is using Markaby and the designer has no problem at all writing view code, or even some model code if he pairs with another developer.

    Quit being difficult.

    I might as well quit breathing :-)

    Dan didn't make the statement that webpages shouldn't be domain objects. He said that views aren't domain objects. Since he's talking about Rails, he's correct. ... Life Without Getters is a pipe dream.

    You're right, it is a dream. And to quote myself, "...but even without going down those roads, the LoD provides a great way of keeping views isolated from a tangled nest of model objects, all pointing to each other. I try to make my views connected to a small set of objects -- often just one -- and to make sure each of those objects' public APIs exposes everything the view needs." My architectural thoughts these days tend to favor reducing coupling -- perhaps at the expense of cohesion -- because I've been burned more often by having too many objects than by having too many methods or properties on a single object. YMMV, but I find myself feeling the most comfortable about objects with the fewest dependencies on other objects.

    Maybe once I finally get into a system with a really shallow domain graph, I'll rediscover all those problems that lead one to increase cohesion by splitting objects back up again. It's a pendulum, and I'm a natural contrarian (as if you couldn't tell), so I'm reacting to what I see as a troublesome trend towards deep structural object graphs and a loss of encapsulation.

  10. Zak Tamsen Zak Tamsen on August 13, 2007 at 08:17PM

    so I said

    "LoD is NOT about delegation." and you asked "can you tell me why delegation is not a useful part of LoD?"

    perhaps. though I'm uncomfortable with your wording.

    anyway, let's start with some background.

    the always reliable Wikipedia says

    "[LoD] can be succinctly summarized as 'only talk to your immediate friends'. The fundamental notion is that a given object should assume as little as possible about the structure or properties of anything else (including its subcomponents). and Eric is a fag."

    I like to think of it as the principle of shy objects. and it's two-way shyness: I should be shy to whom I talk AND reluctant to share those conversations with others.

    the Prags have a cute simile that plays well with your masochistic schoolyard fight fantasy:

    "the best code is very shy. Like a four-year old hiding behind a mother's skirt, code shouldn't reveal too much of itself and shouldn't be too nosy into others affairs."

    thus, we have a new acronym and Manges-ism: SHY (Skirt Hiding Youth).

    enough background. let's look at an example....

    the paperboy example (mentioned in Dan's blog) is better for illustrating this than the order-customer_name example (though I'll come back to that and your display_name query).

    so the paperboy must be paid.

    the customer has a wallet. the wallet has a withdraw method.

    maybe the paperboy has a method like this:

    <code>def collect_money(due_amount)
      @collected_amount += customer.wallet.withdraw(due_amount)
    end
    </code>

    in this case (but not all) the periods (or train wreck) give the LoD violation away.

    we know something is rotten in the state of Holland. (there's only two things I hate in this world: people who are intolerant of other people's cultures and the Dutch.)

    the paperboy shouldn't know about the customer's wallet, let alone that it has a withdraw method. (one must wonder why that's part of the customer's published api.)

    often interpretation of Demeter makes it sound as though this situation is the paperboy's fault, but it's not. he just wants his two dollars. and the customer is giving him no choice but to dip his grimy five-fingered paws directly into the customer's man purse.

    the customer is the scofflaw here. he is the one not being shy with his friends. namely his chum, Mr. Wallet.

    thus, the solution isn't so much changing the paperboy, but rather the customer because it is the customer that is being unnecessarily aggressive. as you were in your initial post.

    so we fix our LoD violation by simply adding a "pay" method to customer and calling this new method in the paperboy.

    <code>class Customer
      def pay(amount)
        @wallet.withdraw(amount) 
      end
    end
    
    class Paperboy
      def collect_money(customer, due_amount)
        @collected_amount += customer.pay(due_amount)
      end
    end
    </code>

    now is a good time to ask ourselves , " what exactly is the benefit of abiding by this demeter law?"

    more from wikipedia:

    "The advantage of following the Law of Demeter is that the resulting software tends to be more maintainable and adaptable. Since objects [paperboy] are less dependent on the internal structure of other objects [customer], object containers [customer contains a wallet] can be changed without reworking their callers [paperboy]."

    so LoD is all about reducing coupling via encapsulation. (which misguided as you are, you seem to agree with.)

    in OO, objects have state and behavior. the rule of encapsulation dictates that we enclose our state while providing behavior via messages.

    we want the paperboy to mind his own beeswax and not pickpocket the customer. this means the customer should be shy with his wallet, i.e. his internal state, and provide behavior, i.e. the pay method.

    so instead of

    <code>customer.internal_state.message
    </code>

    we want

    <code>customer.message
    </code>

    but couldn't we have used method delegation to implement the pay method? yes, in this simple case. we could have used Forwardable like so

    <code>class Customer
      extend Forwardable
      def_delegator :@wallet, :withdraw,  :pay
    end
    </code>

    however, delegation does not necessarily provide encapsulation nor adhere to LoD (<-- bookmark that).

    it's true that method delegation can help us, but only in the simplest case. namely when the missing method (pay) signature of the object container (customer) matches exactly with the "violated method" (withdraw) signature of the contained (wallet). furthermore, attribute delegation requires that our "method" be entirely uninteresting and fortuitously named.

    but let's not get ahead of ourselves and confuse method delegation with attribute delegation....

    what we're interested in is information hiding. the important part is that a pay method exists and provides adequate shyness. the delegation is merely an implementation detail.

    if method delegation is really fixing your LoD violations, your so-called data models must be as shallow as the kiddie pool. hope you packed your floaties.

    which brings us to your specious claim that "attribute delegation fixes LoD" and more insanely that such delegations are "the essence of encapsulation."

    let's look at the order-customer_name example.

    presumably we want to avoid a view from the train wreck order.customer.customer_name.

    so we use attribute delegation. your hero.

    <code>class Order 
      extend Forwardable
      def_delegator :customer, :name, :customer_name
    end
    </code>

    eek! this is breaking the shyness principle. twice.

    I know you have a look-at-how-many-fallacies-I-can-name-fetish, but how good are you at literary devices? I previously said LoD is two-way shyness; that's called "foreshadowing".

    the order is talking (via delegation) to a stranger (customer#name) thus violating the "only talk to your immediate friends" rule. it no longer knows as little as possible about the structure of customer. shyness violation 1.

    and then, to make matters worse, order is sharing this customer name conversation with the whole world. not only vitiating its published interface, but being nosy with the customer's affairs. definitely not minding its own beeswax. shyness violation deux.

    I'll say it again: eek!

    this is tantamount to not fixing the customer object, but instead, letting the customer spread its wallet like a centerfold for all the world to see. can you name that fallacy? what about literary device?

    this is the kind of attribute delegation that Dan was advising against. it's good advice for teens everywhere.

    if you use attribute delegation loosely, you're not only talking to strangers, but you're having unprotected sex with their data. you're not shy. you're a whore.

    according to LoD you shouldn't be getting a customer's name from an order, you should be getting it from a customer. it's the customer's responsibility to be shy with its data. not order's. not the paperboy's.

    now, in some context, it may actually make sense for order to have a customer_name method . and I'm not saying that attribute delegation doesn't have its place. but it absolutely does not solve LoD violations. suggesting that it does shows a misunderstanding of data hiding and therefore the very essence of encapsulation.

    let's get back to that bookmark.

    as I said before, I'm uncomfortable with your word trickeration. you asked me

    "why delegation is not a useful part of LoD?"

    but I didn't say delegation is entirely not useful with respect to LoD. I said "LoD is NOT about delegation". I've already agreed that in the simplest cases, method delegation can be used to achieve the encapsulation that rectifies violations of LoD. but that is merely one mechanism to achieve less coupling through encapsulation.

    more importantly, we've seen that delegation does not necessarily provide encapsulation nor adhere to LoD.

    in fact, attribute delegation can be a violation of LoD . in addition, just because we don't have a train wreck doesn't mean we've solved our LoD problems.

    a quick gripe before moving on. you say

    "I try to make my views connected to a small set of objects."

    LoD isn't about the number of objects you're connected to. it's how shy you are with your connections. number of connections is a different rule. and a good rule. but not LoD.

    which segues nicely to my next point. you're supposed to be discussing LoD. and you're bumbling along, picking at fallacies as if they were daisies, and then suddenly what do we stumble upon?

    this little gem:

    "Ad[sic]ing delegation -- for getters, at least -- increases maintainability, and especially mockability."

    sure. but this is an irrelevant topic which only diverts attention from the original issue (LoD). aka, a red herring fallacy.

    for shame. I wonder if you are a member of the fallacy-a-month-club. pervert.

    but I'll play along. does attribute delegation really increase maintainability as you suggest? hardly. well, at least not necessarily.

    as LoD is all about shyness. the question of maintainability is all about popularity. let's look at the order-customer_name example.

    sure, if the customer's name method changes its signature, clients of order#customer_name don't need to change. but all the clients of customer#name do. not just order. all the clients. so it's a question of popularity. are more people using an order to get a customer's name or a customer? I hope it's the customer. otherwise strange things are afoot at the Circle K.

    but it is true that delegation can increase mockability (and therefore maintainability) in some cases.

    with regard to LoD, however, mockability is a nice side-effect of an implementation choice. it's not the raison d'etre. encapsulation is.

    I wish we were done. but we're not. because you extend the discussion to internal-state-displaying-views.

    but this is where we finally get to the interesting part of this LoD discussion. if only you hadn't been sniffing glue with that misguided attribute delegation crap.

    you ask

    "Why can't views adhere to reasonable object-oriented semantics and support encapsulation"

    because they don't. the primary job of the views (under discussion) is to expose the internal state of objects for display purposes. that is, they are expressly for data showing, not data hiding.

    and there's the rub: these kind of views flagrantly violate encapsulation, LoD is all about encapsulation, and no amount of attribute delegation can reconcile this.

    a rose by any other name would smell as sweet. whether you call the methods with dots or underscores, it smells just the same.

    one last loose end to tie up. you asked:

    "can you describe an alternative to my display_name example?"

    well, nothing comes to mind. but this isn't relevant to my point that LoD is not about delegation.

    obviously you can do things to help views adhere to reasonable OO semantics. like ensure they have a narrow set of display responsibilities. or, as you suggest, put behavior with data. it seems reasonable to give your Car a display_name method that concatenates make, model, and year.

    but that is decidedly not attribute delegation. and thus, not relevant to the discussion.

    a more apropos question would have been "can you describe an alternative to Order#display_name?"

    to which I would say "yes." don't bother with delegation. if your view is meant to expose the viscera of some domain objects, don't sweat the train wrecks.

    and don't worry about mocking to test views. views are often most easily tested by classical state-based means. if you're using mocks to stub data (which is what attributes are), I have to wonder if you understand how to use mocks. see Fowlers mocks aren't stubs. (can you name that fallacy?).

    with Rails development, instead of traditional view tests, I suggest massively parallelized Selenium tests. but that's a different discussion.

    as for the ad hominem. it needs work. I could not possibly care less what some ugly nerd thinks of my musical preferences or hairstyle choices.

    but once again, I'll humor you all the same... among my preferences listed are Beethoven and The Beatles. if you can't appreciate them, I have no choice but to let it be.

    oh, a word of advice: when you've got a receding hairline and your pate looks a bit like your neck threw up , I wouldn't go mocking people's hair.

    if I knew you better I would riposte with a joke about how deep my object graph has been in your mom. but I won't.

    zak

    P.S. why you thanking Wes for a blog by Jay Fields?

  11. Chad Woolley Chad Woolley on August 14, 2007 at 04:52AM

    @Zak - man, that is some funny stuff. Well done. LOL.

    One thing, though - you said "with Rails development, instead of traditional view tests, I suggest massively parallelized Selenium tests. but that's a different discussion."

    I'd like to hear more about the massively parallelized part, especially gritty details of how you make this work easily on a day-to-day basis. We've been having fun with rspec view tests, but I'd love to hear your selenium-based alternatives. That's not a challenge, it's an honest request for enlightenment. Please don't make fun of my hairline.

    TIA, -- Chad

  12. Wes Maldonado Wes Maldonado on August 14, 2007 at 05:25PM

    @Zak - The reason Alex was thanking me was because I pointed him at Jay's posts. Great comment.

  13. Brian Takita Brian Takita on August 14, 2007 at 05:26PM

    Boys, put away your object graphs. There are ladies present.

  14. Hammer Hammer on August 22, 2007 at 06:19PM

    Why would I want to bother writing Selenium tests when I can just pay someone a nickel to manually test it for me.

    Haven't you heard of Mechanical Turk?

  15. Zak Zak on September 10, 2007 at 05:02AM

    I said "with Rails development, instead of traditional view tests, I suggest massively parallelized Selenium tests. but that's a different discussion."

    and Chad wanted more detail.

    well, what we use for massively parallelized Selenium tests isn't public yet. but it should be within the next 48 hours. I'll come back in a few.

    in the meantime, may I suggest massively parallelized functional tests with deepTest.

  16. Chad Woolley Chad Woolley on September 10, 2007 at 07:16AM

    Thanks. deep_test looks very interesting...

Add a Comment (MarkDown available)