Sunday, July 20, 2008

The New Law of Demeter

Baby cat by fofurasfelinas from flickr (CC-NC-ND)
Once upon a time I've stumbled upon The Law of Demeter, which says that you can only call methods on:
  • self
  • self's fields
  • current method's parameters
  • objects you created
  • global objects

It's often shortened to never use two dots in a row. The implication of this law is that if you legitimately obtained order object, then order.customer and order.customer_name are valid, but order.customer.name is not.

At first I interpretted this law literally and (just like you did a moment ago) reached the obvious conclussion that the idea is total bollocks - writing (or even worse autogenerating) thousands of proxy methods like def customer_name; customer.name; end or even delegate :name, :to => :customer is not going to improve quality of your code at all.

It sounded like thousands of those little silly rules like "Use factories instead of constructors" that Java code monkeys make up all the time so they can think "If only everybody adhered to these rules, Java coding would be just fine" and keep blaming other people for their problems instead of blaming themselves for being so daft and sticking with Java. The same Java monkeys don't even adhere to their rules, and so Java coding isn't fine, but even if they did adhere it wouldn't help as you cannot fix technical problems on cultural level. So I forgot about the whole thing and moved along - another stupid rule for people who are afraid of the code.

Of course if this was how the story ended I wouldn't even bother blogging about it. On the contrary, I had a sudden realization - sure, the Law of Demeter has many obvious counterexamples of perfectly valid code that's illegal according to it, and would be made much worse if it was enforced. But most of them go away once it's subtly reinterpretted. There's a loophole for "objects you created" which doesn't have to mean "object you created by ClassName.new" - every object that was created at your request and the producer doesn't hold any reference to it any more can be considered to be "created by you" (old objects whose ownership was relinquished to you should also qualify, but this is a very rare case and I'm not going to talk about it any more). Let's call this interpretation "The New Law of Demeter" and apply it to an exaggerated example:
message = hash.keys.sort_by(&:abs).map(&:to_s).join(" ")

Naively interpretting the Law of Demeter it should not only be illegal but sent straight to the Guantanamo Bay - there are 4 explicit and 2 implicit dots in it, all in a single line.

But this is perfectly sensible piece of code. It does something useful and it's definitely far saner than hash.keys_sorted_by_absolute_value_stringified_and_joined_by_spaces, hash.keys_sorted_stringified_and_joined(&:abs, " ") or any other Demeter transmogrification I can think of.

But why is it really illegal? Every single dot creates a fresh object - Hash#keys does so, and so do Array#sort_by, Numeric#abs, Array#map, Numeric#to_s, and Array#join. The code doesn't violate the New Law of Demeter in any way!

The Law of Demeter is much more permissive when interpretted this way, but I wouldn't stop here yet. I really think order.customer.name is perfectly sensible even if customer is owned by the order. What counterargument some proponents of the Law make? Mostly that it makes customer's name harder to mock if you ever want to make order return customer name not tied to any genuine customer. But why should Order#customer be limited to returning real Customer objects? How about making it return read-only snapshots of current customer? This is exactly how order.customer.XXX is used vast majority of the time. As snapshots don't need any write behaviour, it's very easy to return a snapshot with overridden #name, and as a customer snapshot is a fresh object you've just created, you are allowed to call any methods on it you want.

So let's pretend that every time you do order.customer you really mean order.customer_snapshot, and code like this becomes legal:
puts order.customer.address.postcode

The New Law of Demeter lets you do a lot more than the old, including legalizing almost all sensible view code, but it still delegalizes a lot of dubious code. For example code like this is definitely not legal, as it expects read-write objects, not fresh snapshots:
order.customer.first_name = "Bob"

This is also not legal, as snapshots should be fresh objects and not be auto-updatable:
customer = order.customer
order.add(item)
puts customer.balance # item included or not?

but it can easily be transformed into this legal code:
order.add(item)
customer = order.customer
puts customer.balance # item definitely included

Reinterpretted this way, the New Law of Demeter is really useful at detecting code smell. Unfortunately it also becomes much more difficult to apply, as you need to look not only at the syntax but also at the sema an object to which the original creator still holds a reference; and does the method affect state or does it have snapshot access semantics and so on. I'd love to see a Demeter lint which would try to check compliance with The New Law of Demeter in a meaningful way.

6 comments:

  1. Giving simple containers (dictionaries, lists) a free pass on Demeter has been a standard practice for a while now in some corners. To not do so causes behaviors to be pushed into places they have no right being in.

    ReplyDelete
  2. Dave Smith: I'm curious, do you know anyone systematically applying Law of Demeter? I wrote this post as it seemed to me that it would be almost impossible without very serious reinterpretation. Could you provide more details, or even better write a blog post about it?

    ReplyDelete
  3. I wrote a post about this a year and a half ago, but mostly as an excuse to cite Brad Appleton's post on the XP list, which lays out the argument nicely.

    For more prior discussions, see the Demeter pages on the C2 Wiki.

    ReplyDelete
  4. Ah, it feels so good to call others stupid code monkeys, doesn't it?

    ReplyDelete
  5. Anonymous14:25

    Nice blog post. I just want to point out that for instance the Head First Design Patterns book call this 'Law' the principle of least knowledge, and even Martin Fowler says, that it should be called rather suggestion than law.

    I think both interpretation make the whole point behind this more clearly (which is exactly what Dave said here): You should do things where they belong. So for instance adding elements to a collection should be done through the object holding that collection (under the presumption adding and removing elements involves several steps).

    Nevertheless I think you should always think about those 'laws' if and how they can help to improve your code.

    ReplyDelete
  6. A DRY way to apply Law of Demeter with demeter gem

    http://github.com/emerleite/demeter
    http://gemcutter.org/gems/demeter

    ReplyDelete