Most developers, when invoking the “Law of Demeter” or when pointing out a “Demeter Violation”, do so when a line of code has more than one dot:
person.address.country.code. Like the near-pointless SOLID Principles, Demeter, too, suffers from making a vague claim that drives developers to myopically unhelpful behavior.
Writing SOLID is not Solid, I found the backstory and history of the principles really interesting. They were far flimsier than I had expected, and much more vague in their prescription. The problem was in their couching as “principles” and the overcomplex code that resulted from their oversimplification. Demeter is no different. It aims to help us manage coupling between classes, but when blindly applied to core classes and data structures, it leads to convoluted, over-de-coupled code that obscures behavior.
What is this Law of Demeter?
Update Jan 24, 2020: My former collegue Glenn Vanderburg pointed me to what he believes it he source of the “Law of Demeter”, which looks like a fax of the IEEE Software magazine in which it appears! It’s on the Universitatea Politehnica Timisoara’s website.
It does specifically mention object-oriented programming, and it states a few interesting things. First, it mentions pretty explicitly that they have no actual proof this law does what it says it does (maybe then don’t call it law? I dunno. That’s just me). Second, it presents a much more elaborate and nuanced definition than the paper linked below. The definitions of terms alone is almost a page long and somewhat dense.
Suffice it to say, I stand even more firm that this should not be called a “Law” and that the way most programmers understand by counting dots is absolutely wrong. This paper is hard to find and pretty hard to read (both due to its text, but also its presentation). I would be surprised if anyone invoking Demeter in a code review has read and understood it.
It’s hard to find a real source for the Law of Demeter, but the closest I could find is this page on Northeastern’s webstie, which says:
End of Update
This page on Northeastern’s webstie, summarizes the Law as stated in the paper above:
Each unit should have only limited knowledge about other units: only units “closely” related to the current unit.
The page then attempts to define “closely related”, which I will attempt to restate without the academic legalese:
- A unit is some method
methof a class
- Closely related units are classes that are:
- other methods of
- passed into
- returned by other methods of
- any instance variables of
- other methods of
Anything else should not be used by
meth. So for example, if
meth takes an argument
arg, it’s OK to call a method
arg.other_meth), but it’s not OK to call a method on that (
It’s also worth pointing out that this “Law” was not developed for the sake of object-oriented programming, but for help defining aspect-oriented programming, which you only tend to hear about in Java-land, and even then, not all that much.
That all said, this advice seems reasonable, but it does not really allow for nuance. Yes, we want to reduce coupling, but doing so has a cost (this is discussed at length in the book). In particular, it might be preferable for our code’s coupling to match that of the domain.
It also might be OK to be overly coupled to our language’s standard library or to the framework components of whatever framework we are using, since that coupling mimics the decision to be coupled to a language or framework.
Code Coupling can Mirror Domain Coupling
Consider this object model, where a person has an address, which has a country, which has a code.
Suppose we have to write a method to figure out taxes based on country code of a person. Our method,
determine_tax_method takes a
Person as an argument. The basic logic is:
- If a person is in the US and a contractor, we don’t do tax determination.
- If they are in the US and not a contractor, we use the US-based tax determination, which requires a zipcode.
- If they are in the UK, we use the UK based determination, which requires a postcode.
- Otherwise, we don’t do tax determination.
Here’s what that might look like:
class TaxDetermination def determine_tax_method(person) case person.address.country.code when "US" if person.contractor? NoTaxDetermination.new else USTaxDetermination.new(person.address.postcode) end when "UK" UKTaxDetermination.new(person.address.postcode) else NoTaxDetermination.new end end end
code are all methods, according to the Law of Demeter, we have created a violation,
because we are depending on the class of an object returned by a method called on an argument. In this case,
the return value of
person.address is a
Country and thus not a “closely related unit”.
But is that really true?
Person has a well-defined type. It is defined as having an address, which is an
Address, another well-defined
type. That has a country, well-defined in the
Country class, which has a
code attribute that returns a
string. These aren’t objects to which we are sending messages, at least not semantically. These are data
structures we are navigating to access data from our domain model. The difference is meaningful!
Even still, it’s hard to quantify the problems with a piece of code. The best way to evaluate a technique is to compare code that uses it to code that does not. So, let’s change our code so it doesn’t violate the Law of Demeter.
A common way to do this is to provide proxy methods on an allowed class to do the navigation for us:
class TaxDetermination def determine_tax_method(person) case person.country_code # ^^^^^^^^^^^^ when "US" if person.contractor? NoTaxDetermination.new else USTaxDetermination.new(person.postcode) # ^^^^^^^^ end when "UK" UKTaxDetermination.new(person.postcode) # ^^^^^^^^ else NoTaxDetermination.new end end end
How do we implement
class Person def country_code self.address.country.code end def postcode self.address.postcode end end
country_code now contains a Demeter Violation, because it calls a method on the return type of a
closely related unit. Remember,
self.address is allowed, and calling methods on
self.address is allowed, but
that’s it. Calling
country is the violation. So…another proxy method.
class Person def country_code self.address.country_code # ^^^^^^^^^^^^ end end class Address def country_code self.country.code end end
And now we comply with the Law of Demeter, but what have we actually accomplished? All of the methods we’ve been dealing with are really just attributes returning unfettered access to public members of a data structure.
We’ve added three new public API methods to two classes, all of which require tests, which means we’ve incurred both an opportunity cost in making them and a carrying cost in their continued existence.
We also now have two was to get a person’s country code, two ways to get their post code, and two was to get the country code of an address. It’s hard to see this as a benefit.
For classes that are really just data structures, especially when they are core domain concepts that drive the reason for our app’s existence, applying the Law of Demeter does more harm than good. And when you consider that most developers who apply it don’t read the backstory and simply count dots in lines of code, you end up with myopically overcomplex code with little demonstrable benefit.
But let’s take this one step further, shall we?
Violating Demeter by Depending on the Standard Library
Suppose we want to send physical mail to a person, but our carrier is a horrible legacy US-centric one that requires
being given a first name and last name. We only collected full name, so we fake it out by looking for a space in
the name. Anyone with no spaces in their names is handled manually by queuing their record to a customer service
class MailSending def send_mailer(person) fake_first_last = /^(?<first>\S+)\s(?<last>.*)$/ match_data = fake_first_last.match(person.name) if match_data legacy_carrier(match_data[:first], match_data[:last]) else handle_manually(person) end end end
This has a Demeter violation. A
Regexp (created by the
/../ literal) returns a
MatchData if there is match. We can’t call methods on an object returned by one of our closely related units’ methods. We can call
match on a
Regexp, but we can’t call a method on what that returns. In this case, we’re calling
 on the returned
MatchData. How do we eliminate this egregious problem?
We can’t make proxy methods for first name and last name in
Person, because that method will have the same problem as this one (it also would put use-case specific methods on a core class, but that’s another problem). We really do need to both match a regexp and examine its results. But the Law does not allow for such subtly! We could create a proxy class for this parsing.
class LegacyFirstLastParser FAKE_FIRST_LAST = /^(?<first>\S+)\s(?<last>.*)$/ def initialize(name) @match_data = name.match(FAKE_FIRST_LAST) end def can_guess_first_last? !@match_data.nil? end def first @match_data[:first] end def last @match_data[:last] end end
Now, we can use this class:
class MailSending def send_mailer(person) parser = LegacyFirstLastParser.new(person.name) if parser.can_guess_first_last? legacy_carrier(parser.first, parser.last) else handle_manually(person) end end end
LegacyFirstLastParser was just plucked out of the ether. It definitely is not a closely-related unit
based on our definition. We’ll need to create that via some sort of private method:
class MailSending def send_mailer(person) parser = legacy_first_last_parser(person.name) # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ if parser.can_guess_first_last? legacy_carrier(parser.first, parser.last) else handle_manually(person) end end private def legacy_first_last_parser(name) LegacyFirstLastParser.new(name) end end
legacy_first_last_parser has the same problem as
send_mailer, in that it pulls
LegacyFirstLastParser out of thin air. This means that
MailSending has to be given the class, so let’s invert
class MailSending def initialize(legacy_first_last_parser_class) @legacy_first_last_parser_class = legacy_first_last_parser_class end def send_mailer(person) parser = legacy_first_last_parser(person.name) if parser.can_guess_first_last? legacy_carrier(parser.first, parser.last) else handle_manually(person) end end private def legacy_first_last_parser(name) @legacy_first_last_parser_class.new(name) end end
This change now requires changing every single use of the
MailSending class to pass in the
LegacyFirstLastParser class. Sigh.
Is this all better code? Should we have not done this because
MatchData are in the standard
library? The Law certainly doesn’t make that clear.
Just as with all the various SOLID Principles, we really should care about keeping the coupling of our classes low and the cohesion high, but no Law is going to guide is to the right decision, because it lacks subtly and nuance. It also doesn’t provide much help once we have a working understanding of coupling and cohesion. When a team aligns on what those mean, code can discussed directly—you don’t need a Law to help have that discussion and, in fact, talking about it is a distraction.
Suppose we kept our discussion of
send_mailer to just coupling. It’s pretty clear that coupling to the
language’s standard library is not a real problem. We’ve chosen Ruby, switching programming languages would be a
total rewrite, so coupling to Ruby’s standard library is fine and good.
Consider discussing coupling around
determine_tax_method. We might have decided that since people, addresses,
and countries are central concepts in our app, code that’s coupled to them and their interrelationship is
generally OK. If these concepts are stable, coupling to them doesn’t have a huge downside. And the
domain should be stable.
Damn the Law.