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 , Programming[10] Comments
When 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!