A Rider to the Law of Demeter
In case you were wondering who is responsible for the bounty provided by harvests each year, the answer is the goddess Demeter. In the age of global transport, harvests have stabilized somewhat, but that wasn’t always the case. There was a time when Hades, the God of the Underworld, captured Demeter’s daughter, Persephone, and held her prisoner. A desperate Demeter responded to this calamity as any parent would, by quitting her job and committing herself to a rescue effort. Trouble for the world was that Demeter being absent at her post led to widespread famine, prompting Zeus to intervene and some sort of compromise to be reached. And so, it stands to reason that a principle of software development discouraging the use of statements like Hotel.Rooms[123].Bathroom.Sink was named for her.
I’m actually, totally kidding. To the best of my recollection, and born out by some quick research, the rule was named after the Demeter Project, during the course of which it was conceived and proposed. And so, Greek pantheon acquired a modern chapter in which Demeter, in addition to her harvest duties, also became the goddess of loose coupling.
Many of you reading are probably familiar with this concept already, either by name or convention. Having a method called RunTheWater() that invokes Hotel.Rooms[123].Bathroom.Sink.TurnOn() is a lot more brittle than it needs to be. All it wants is some water, but for some reason if the hotel’s room 123 is closed for renovation, we’re screwed. A better alternative is to have the RunTheWater() method depend only on its immediate collaborator: a sink. RunTheWater(Sink sink) that invokes sink.TurnOn() doesn’t care where the sink is located and won’t be broken if a specific room in some specific hotel is out of order. This isn’t particularly controversial. If you want something a little more controversial, feel free to check out the explanation of the Law of Demeter in which I liken it to removing your pants in a store when you’re at the cash register.
A lot of people like to point out that the Law of Demeter is also called, “The Principle of Least Knowledge,” and that it is more than a “dot counting” exercise. But today, I’m going to propose that this nearly 30 year old law be extended a bit to include a couple of edge cases specific(ish) to C#. I feel justified in doing so by interpreting the law in a loose sense around information hiding as a means to cut down on local brittleness.
Knowing Too Much about Return Type
The first case popped into my head as a result of a comment left on this old post about yield return. The commenter referenced the stylistic debate about whether and how much to use the syntactic sugar of implicitly typing variables with “var.” A lot of people have debated this for a long time, generally talking about readability and other subjective concerns. But as I pondered it today, a subtle benefit occurred to me. Consider the following code:
public class Printer
{
private readonly IProvideRecordCounts _provider = new RecordCountProvider();
public void WriteCustomerRecords()
{
int recordCount = _provider.GetRecordCount();
Console.WriteLine(string.Format("There are {0} records.", recordCount));
}
}
The method here creates a local variable for the sake of readability and then writes it to the console. It is worth noting that one could inline the “recordCount” variable, but nesting another call in the second line would probably tip it from “a little dense” to “too dense.” So, what’s the problem? Well, consider the interface method GetRecordCount(). What happens if that interface’s author has to cater to a number of implementors that are hitting a DB maximum and demanding that GetRecordCount() return a long instead of an int? I’m broken, that’s what. I have to go in and modify this method, now, and to what purpose? I don’t care at all whether I’m getting back an int or a long — whatever it is, I’m just dumping it to console.
I believe that this code is a subtle Law of Demeter violation. Any touching of existing code is a risk, and, here, it’s a risk that’s not needed. If I replace “int recordCount” with “var recordCount,” I can resume not caring what type of number comes back at me in a situation where I just want to turn around and dump it to console.
Knowing Too Much about Qualified Types
Now let’s take a look at another subtle problem. I’d like you to consider using fully qualified type names inline in methods. One might argue that this is similar to classic Law of Demeter violations, but it’s a bit different. Classic violations involve walking too much of the object graph at runtime, whereas this is a matter of walking the namespace structure at compile time.
I have this (rather obtuse, for the sake of simplicity) method whose whole purpose is to use some static method to parse this input string into an XDocument. But in this method, where all I want to do is a little parsing, I suddenly need to understand the organization of the LocalCode name-spacing scheme. Is that really essential to parsing the string?
public XDocument ParseXml(string xmlAsString)
{
varXDocument = LocalCode.Library.Parsers.XMLParser.parseToXDocument(xmlAsString);
}
If someone on the team decides to get rid of the Parsers namespace and flatten the hierarchy by putting them all in Library, I’m broken! That namespace’s organization is irrelevant to what I’m doing, but I’ve forced it to be relevant in a painful way.
Now, it’s easy to argue that, by moving the qualification of this type out of the method, there will still be a compiler error/breaking change caused in this file. And, while that’s true, I’d counter by asking you to consider two things: (1) this fully qualified type call may well be present in other methods, meaning extra errors, and (2) I still don’t care in this method. Yeah, there will be a compiler error, but when looking to conform to the principle of least knowledge, I don’t care about scope outside of the method. All I can do is keep my own house clean, and the way to do that is to punt that qualification to somewhere else.
Just Food for Thought
The last thing I’m aiming to do here is introduce two new rules that thou shalt follow to write good code. These are subtle cases, and there are always tradeoffs to consider. For instance, I wouldn’t suggest that you declare every local variable as Object to be as general as possible.
The bigger picture is that good architecture, at its core, is about risk reduction and dependency management. So, if you get into the habit of looking critically at ways that your surface area for potential problems may be bigger than necessary, you’re developing a valuable design habit. The Law of Demeter is not a dot counting exercise, nor, I would argue, is it even limited to avoiding baggage from invoking collaborator call chains. It’s about eliminating dependencies you don’t need. And, of course, it’s about getting Persephone home safely in time for the fall harvest.
By the way, if you liked this post and you're new here, check out this page as a good place to start for more content that you might enjoy.
‘If I replace “int recordCount” with “var recordCount,” I can resume not caring what type of number comes back at me’ — as long as you recompile. If you don’t, recordCount will be stuck declared as whatever type GetRecordcount() was returning when you last compiled.
That’s a good point and definitely worth mentioning. I was contemplating this from the perspective of maintaining the source code as changes are occurring rather than managing dependencies in built/deployed environments. Once everything is already built, the Law of Demeter is pretty academic, anyway. 🙂
I don’t think that is an issue. Or more appropriately, the results are a bad. If you don’t recompile then you will get the same run-time error. Furthermore, you should ALWAYS recompile assemblies that reference an assembly you change. The “big” issue I saw was that the return type might not even be a number. It could be a string. For example it might return the string “twelve” instead of the int 12. I would argue that Erik’s code is still correct in this case. The method still returns a count and string.Format is still able to handle it. Of… Read more »
What if we look at this from a Dependency Inversion Principle (DIP) point of view? The consumers of the IProvideRecordCounts interface need a method that returns an int. It should not matter what implementation of IProvideRecordCounts you use, it should always return an int. If a different implementation changes the return to long, or even a string, it is breaking DIP since now the abstraction depends on the detail.
I think the change to IProvideRecordCounts and its GetRecordCount() method should be driven by its clients, not the concrete implementation.
What I was trying to convey in the post was the idea that IProvideRecordsCounts authors’ had originally created a method that returned an int, and some number of clients, including our code here, came to depend on it. But after a while, ‘we’ were outvoted by ‘other clients’ who clamored for the value to be changed to a long. And so the interface return type changed, to our chagrin. So, it wasn’t that some specific implementation changed, but rather the interface itself. When coding the example, I probably should have added a constructor that injected IProvideRecordCounts, but I didn’t bother.… Read more »
Thanks for clearing that up – that makes sense.