Module Boundaries and Demeter
I was doing a code review recently, and I saw something like this:
public class SomeService
{
public void Update(Customer customer)
{
//Do update stuff
}
public void Delete(int customerId)
{
//Do delete stuff
}
}
What would you say if you saw code like this? Do you see any problem in the vein of consistent abstraction or API writing? It’s subtle, but it’s there (at least as far as I’m concerned).
The problem that I had with this was the mixed abstraction. Why do you pass a Customer object to Update and an integer to Delete? That’s fairly confusing until you look at the names of the variables. The method bodies are elided because they shouldn’t matter, but to understand the reason for the mixed abstraction you’d need to examine them. You’d need to see that the Update method uses all of the fields of the customer object to construct a SQL query and that the corresponding Delete method needs only an ID for its SQL query. But if you need to examine the methods of a class to understand the API, that’s not a good abstraction.
A better abstraction would be one that had a series of methods that all had the same level of specificity. That is, you’d have some kind of “Get” method that would return a Customer or a collection of Customers and then a series of mutator methods that would take a Customer or Customers as arguments. In other words, the methods of this class would all be of the form “get me a customer” or “do something to this customer.”
The only problem with this code review was that I had just explained the Law of Demeter to the person whose code I was reviewing. So this code:
public void DeleteCustomer(int customerId)
{
string theSqlQuery = "DELETE FROM Customer WHERE CustomerId = " + customerId;
//Do some sql stuff...
}
was preferable to this:
public void DeleteCustomer(Customer customer)
{
string theSqlQuery = "DELETE FROM Customer WHERE CustomerId = " + customer.Id;
//Do some sql stuff...
}
The reason is that you don’t want to accept an object as a method parameter if all you do with it is use one of its properties. You’re better off just asking for that property directly rather than taking a needless dependency on the containing object. So was I a hypocrite (or perhaps just indecisive)?
Well, the short answer is “yes.” I gave a general piece of advice one week and then gave another piece of advice that contradicted it the next. I didn’t do this, however, because of caprice. I did it because pithy phrases and rules fail to capture the nuance of architectural decisions. In this case the Law of Demeter is at odds with providing a consistent abstraction. And, I value the consistent abstraction more highly, particularly across a public seam between modules.
What I mean is, if SomeService were an implementation of a public interface called ICustomerService, what you’d have is a description of some methods that manipulate Customer. How do they do it? Who knows… not your problem. Is the customer in a database? Memory? A file? A web service? Again, as consumers of the API we don’t know and don’t care. So because we don’t know where and how the customers are stored, what sense would it make if the API demanded an integer ID? I mean, what if some implementations use a long? What if Customers are identified elsewhere by SSN for deletion purposes? The only way to be consistent across module boundaries (and thus generalities) is to deal exclusively in domain object concepts.
The Law of Demeter is called the Principle of Least Knowledge. At its (over) simplest, it is a dot counting exercise to see if you’re taking more dependencies than is strictly necessary. This can usually be enforced by asking yourself if your methods are using any objects that they could get by without using. However, in the case of public facing APIs and module boundaries, we have to relax the standard. Sure, the SQL Server version of this method may not need to know about the Customer, but what about any scheme for deleting customers? A narrow application of the Law of Demeter would have you throw Customer away, but you’d be missing out by doing this. The real question to ask in this situation is not “what is the minimum that I need to know” but rather “what is the minimum that a general implementation of what I’m doing might need to know.”
I’ve never cared much for the Law of Demeter. To me the formal strict definition of the law prevents using data transfer objects, and other forms of categorizing data according to business scenarios (madness!).
I feel like programming in general could be improved by renaming the whole thing, “Code Smell of Demeter” and adding the caveat that if you see more than one dot in an expression, you’ve probably done something wrong.
I think you really nailed it by objecting to the term “law.” I’ve never thought of that, but you’re absolutely right. It’s too situational and subjective to make for a good “law.” It’s just a specific case of coupling and coupling, while best minimized, is unavoidable in applications.
Fowler calls it the “Occasionally Useful Suggestion of Demeter” 🙂 In any case, I would argue that customer.id doesn’t violate Demeter. Identity is a special case. Customer and customerId are both approximations of the actual person. The former is a representation that works well inside a OO codebase. The latter is a representation that works better when talking to databases, clients, or other systems. Throughout your system, it is implicitly agreed they are more or less interchangeable. They are both representations of the same idea, so there’s no transfer of knowledge. An example where Demeter would apply, is something like… Read more »
Thank you. I think this is an excellent explanation, and I feel good about working with it when assessing my code bases for design clarity. Interesting quote on Fowler, by the way — hadn’t heard that.
Posting images of incomplete lines of code with non-functioning scroll bars is pure evil… 😉
Apparently, there was a clash of plugins and one of the other ones was hosing Crayon Syntax highlighter. I shuffled things around and it looks now to be working in Firefox, Chrome, and Edge. Hopefully that does the trick, and sorry ’bout that.
Yay!