Law of Demeter
04 Jan 2017A few times I’ve been told during code review that something violates the Law of Demeter. This method for example.
class User
def user_info
"#{user.name}, #{user.department.name}"
end
end
Which led me to ask: what is the Law of Demeter, why should I follow it, how do I know if I’m violating it, and how do I avoid violating it?
The Law of Demeter is formally defined as,
A method of an object may only call methods of:
The object itself.
An argument of the method.
Any object created within the method.
Any direct properties/fields of the object.
The above code sample violates the law with “user.department.name” because it’s calling name on the user’s department, and department is an entirely different object from a user.
In general it is better if one class does not have to know about methods from another class, in other words when the classes are “decoupled”. This is because it makes the code more flexible. For example in our code sample, there is only one department for a user. But what happens if things changed so that a user could have many departments and we wanted the only use the first one for our user_info method? We would then have to say:
class User
def user_info
"#{user.name}, #{user.departments.first.name}"
end
end
This seems reasonable for now but what if we used “user.department” in multiple places? We’d have to make the same change wherever it was used, which would be cumbersome.
So how do you avoid violating the law?
You could make a custom method in the User class called “department_name”:
class User
def user_info
"#{user.name}, #{user.department_name}"
end
def department_name
departments.first.name
end
end
Now the department_name is an immediate property of the user itself, satisfying the law and also making it easier to make future changes if we want to change what we use for the department’s name.
One sign that is often but not always an indication of Demeter violation is chaining of dots. For example “user.departments.first.name” had chained dots and indeed was a violation. However, sometimes dot chaining does not mean Demeter violation for example:
def slug(string)
string.strip.downcase.tr_s('^[a-z0-9]', '-')
end
The above complies with the Law of Demeter because each method is called on a String object. And the law doesn’t care about the number of methods called, but the number of types of methods called.
For more information see Demeter: It’s not just a good idea. It’s the law.