Fri 15 Dec 2006
Law of Demeter, or How to avoid coding yourself into a corner in Rails
Posted by brian under Rails , Ruby , Philosophy , ProgrammingWhen coding there are many guidelines you might opt to follow, such as Convention Over Configuration, the Principle of Least Surprise, and others, all intended to prevent you from falling into certain pitfalls. One such pitfall is one that I myself often fall victim to, and involves spreading the knowledge of the internal workings of one component to several others. That is, if A, B, and C all know how D works, then there’s really little point in grouping functionality inside D in the first place. Avoiding this pitfall is what the Law of Demeter tells us to do. To quote the Wikipedia article:
When applied to object-oriented programs, the Law of Demeter can be more precisely called the “Law of Demeter for Functions/Methods” (LoD-F). In this case, an object A can request a service (call a method) of an object instance B, but object A cannot “reach through” object B to access yet another object to request its services. Doing so would mean that object A implicitly requires greater knowledge of object B’s internal structure. Instead, B’s class should be modified if necessary so that object A can simply make the request directly of object B, and then let object B propagate the request to any relevant subcomponents. If the law is followed, only object B knows its internal structure.
Unfortunately this is done all the time in Rails, partly because they make it so darn easy to access associations and their associations:
- shipment.user.profile.mailing_address
Looks innocent enough. But what happens when we sprinkle this around our codebase in a variety of forms, and then without warning the requirements change and all of a sudden the mailing address is now attached to the user rather than the profile. This will require going through all your code that might look for the mailing address and updating it. You’d better pray you have near 100% test coverage.
The unpredictable and despotic need for change that creeps into every project is the reason you should care about the Law of Demeter, also called the Principle of Least Knowledge. But what do we do about it? The Wikipedia excerpt above makes it pretty clear that we should define mailing_address on Shipment and User:
- class Shipment < ActiveRecord::Base
- …
- def mailing_address
- user.mailing_address
- end
- …
- end
- class User < ActiveRecord::Base
- …
- def mailing_address
- profile.mailing_address
- end
- …
- end
Okay, fine. Not the prettiest, but it does help us with refactoring. You may have noticed a problem with both the old code and the new version: what if one of the associations is nil? We’ll get a big fat NoMethodError of course! Let’s fix that:
- class Shipment < ActiveRecord::Base
- …
- def mailing_address
- user ? user.mailing_address : nil
- end
- …
- end
- class User < ActiveRecord::Base
- …
- def mailing_address
- profile ? profile.mailing_address : nil
- end
- …
- end
Getting kinda ugly, but it works better now. Both refactoring and nil problems are taken care of. Now that we’ve got that, we can rip it out. A while back Rails got a method called delegate that’ll let us do just this type of thing, providing both the refactoring safety and nil safety. Using this method we can change our code to this:
- class Shipment < ActiveRecord::Base
- …
- delegate :mailing_address, :to => :user
- …
- end
- class User < ActiveRecord::Base
- …
- delegate :mailing_address, :to => :profile
- …
- end
Isn’t that cool? Now it’s nice and semantic, safe, and refactorable!
December 16th, 2006 at 12.24
Nice post.
December 18th, 2006 at 15.47
Thanks !
January 2nd, 2007 at 23.14
I’ve been using this all over the place for a few months now and love it. I came across a small problem when delegating an instance method to a class method. You will need to use something like the following:
delegate :some_option, :to => ’self.class’
This has to do with the way the code is generated. :class generates an error as the keyword takes precedence over the instance method.
January 11th, 2007 at 16.15
Ryan, you changed my life with that ’self.class’ trick.
January 19th, 2007 at 19.48
I could be wrong… but this doesn’t appear to work with nil’s. I am using this in ActiveRecord classes and they are giving me “You have a nil object when you didn’t expect it!”, from (DELEGATION):20 in ‘send‘, etc.
Perhaps I’m doing something wrong… but it is just like your example.
If you have any suggestions, they would be much appreciated.
January 24th, 2007 at 00.30
I have to agree with Tyler, current implementation still tries to call the method even if the receiving object is nil. You would end up trying to call nil.mailing_address in your example
June 3rd, 2007 at 22.04
I think you guys are right. I just noticed earlier today that Hobo implements a version of delegate that has nil safety. I think I was thinking of a patch that courtn3y put in but was (apparently) never accepted.
June 3rd, 2007 at 22.06
Very handy post, of course it helped you were online when I asked about it.
Going through my App I can find a ton of areas where I can take advantage of this. Could I suggest you flesh out the tutorial a bit and show some code using it? I get it, someone else might not.
June 27th, 2007 at 23.10
[…] http://brian.maybeyoureinsane.net/blog/2006/12/15/law-of-demeter-or-how-to-avoid-coding-yourself-into-a-corner-in-rails/ http://www.surfscranton.com/architecture/LawOfDemeter.htm Filed under: RoR, 转贴 — axgle @ 3:10 pm […]
October 22nd, 2007 at 15.18
Software Development Guide…
I couldn’t understand some parts of this article, but it sounds interesting…
March 21st, 2009 at 17.49
This is barely describable as applying Law of Demeter. Read the book by Karl Lieberherr.
July 12th, 2009 at 05.13
Keep up the good work!
November 26th, 2009 at 18.30
A DRY way to apply Law of Demeter with demeter gem
http://github.com/emerleite/demeter
http://gemcutter.org/gems/demeter