23

Browsing through some code I've written, I came across the following construct which got me thinking. At a first glance, it seems clean enough. Yes, in the actual code the getLocation() method has a slightly more specific name which better describes exactly which location it gets.

service.setLocation(this.configuration.getLocation().toString());

In this case, service is an instance variable of a known type, declared within the method. this.configuration comes from being passed in to the class constructor, and is an instance of a class implementing a specific interface (which mandates a public getLocation() method). Hence, the return type of the expression this.configuration.getLocation() is known; specifically in this case, it is a java.net.URL, whereas service.setLocation() wants a String. Since the two types String and URL are not directly compatible, some sort of conversion is required to fit the square peg in the round hole.

However, according to the Law of Demeter as cited in Clean Code, a method f in class C should only call methods on C, objects created by or passed as arguments to f, and objects held in instance variables of C. Anything beyond that (the final toString() in my particular case above, unless you consider a temporary object created as a result of the method invocation itself, in which case the whole Law seems to be moot) is disallowed.

Is there a valid reasoning why a call like the above, given the constraints listed, should be discouraged or even disallowed? Or am I just being overly nitpicky?

If I were to implement a method URLToString() which simply calls toString() on a URL object (such as that returned by getLocation()) passed to it as a parameter, and returns the result, I could wrap the getLocation() call in it to achieve exactly the same result; effectively, I would just move the conversion one step outward. Would that somehow make it acceptable? (It seems to me, intuitively, that it should not make any difference either way, since all that does is move things around a little. However, going by the letter of the Law of Demeter as cited, it would be acceptable, since I would then be operating directly on a parameter to a function.)

Would it make any difference if this was about something slightly more exotic than calling toString() on a standard type?

When answering, do keep in mind that altering the behavior or API of the type that the service variable is of is not practical. Also, for the sake of argument, let's say that altering the return type of getLocation() is also impractical.

4 Answers 4

34

The problem here is the signature of setLocation. It's stringly typed.

To elaborate: Why would it expect String? A String represents any kind of textual data. It can potentially be anything but a valid location.

In fact, this poses a question: what is a location? How do I know without looking into your code? If it were a URL than I would know a lot more about what this method expects.
Maybe it would make more sense for it to be a custom class Location. Ok, I wouldn't know at first, what that is, but at some point (probably before writing this.configuration.getLocation() I would take a minute to figure out what it is this method returns).
Granted, in both cases I need to look some place else to understand, what is expected. However in the latter case, if I understand, what a Location is, I can use your API, in the former case, if I understand, what a String is (which can be expected), I still don't know what your API expects.

In the unlikely scenario, that a location is any kind of textual data I would reinterpret this to any kind of data, that has a textual representation. Given the fact, that Object has a toString method, you could go with that, although this demands quite a leap of faith from the clients of your code.

Also you should consider, that this is Java you're talking about, which has very few features by design. That's what's forcing you to actually call the toString at the end.
If you take C# for example, which is also statically typed, then you would actually be able to omit that call by defining behavior for an implicit cast.
In dynamically typed languages, such as Objective-C, you don't really need the conversion either, because as long as the value behaves like a string, everybody is happy.

One could argue, that the last call to toString is less a call, than actually just noise generated by Java's demand for explicitness. You're calling a method, that any Java object has, therefore you do not actually encode any knowledge about a "distant unit" and thereby don't violate the Principle of Least Knowledge. There is no way, no matter what getLocation returns, that it doesn't have a toString method.

But please, do not use strings, unless they are really the most natural choice (or unless you're using a language, that doesn't even have enums ... been there).

8
  • I would tend to agree with you, but he already said he cannot modify the service API.
    – jhocking
    Sep 21, 2011 at 15:48
  • 1
    @jhocking: He didn't say he can't. He said it's impractical. I disagree. Trying to apply best practices to code that works around flaws in API design really only makes sense if such workarounds are the only possible option. However here setting the API straight is the best option. Removing deficiencies is always preferable to working around them.
    – back2dos
    Sep 21, 2011 at 15:54
  • well +1 anyway for "is less a call than actually just noise generated by Java's demand for explicitness"
    – jhocking
    Sep 21, 2011 at 16:23
  • 1
    I'd give this +1 even if just for "stringly typed". In my code, I try to pass types that express meaning/intent as much as possible, but when working with third-party libraries and APIs, sometimes you are kind of stuck using what the authors of that API decided on. And IIRC, a location can indeed be "any kind of textual data", but for the implementation I am working on, a non-URL location is totally meaningless.
    – user
    Sep 22, 2011 at 9:19
  • 1
    As for changing the API; this is computer software, so it is virtually always possible to change things. (If nothing else you can always write an abstraction layer.) However, sometimes doing so involves way too much effort both short- and long-term to be justifiable. Then, it might be perfectly possible to make such changes, but still impractical.
    – user
    Sep 22, 2011 at 9:23
21

Demeter's law is a design guideline, not a law to be followed religiously.

If you feel that your classes are enough decoupled than there's nothing wrong with the line this.configuration.getLocation() especially if, as you say, it is impractical to change other parts of the API.

I'm pretty sure that the client will be perfectly happy even if you make the Demeter's Law to pieces, as long as you deliver what he wants and on time. Which is not an excuse to make bad software, but a reminder to be pragmatic when developing software.

3
  • 1
    Since this.configuration is an instance variable of a type of a known interface, calling a method on it that is defined by that interface seems fine even according to a strict interpretation. Yes, I know that it's a guideline, just like KISS, SOLID, YAGNI and so on. There are very few if indeed any "laws" (in the legal sense) in general software development.
    – user
    Sep 21, 2011 at 14:14
  • 4
    +1 for being pragmatic ;-)
    – Treb
    Sep 21, 2011 at 14:17
  • 1
    I don't think the Law of Dementer should even apply in a case like this--configuration is basically a container. In every API I have ever worked with looking inside containers is normal and expected behavior. Aug 13, 2015 at 3:35
2

The only thing I can think of not writing code like this is what if this.configuration.getLocation() returns null? It depends on your surrounding code and target audience using this code. But as Marco say - Demeter's law is a rule of thumb - it's good to follow, but don't break your back doing so unnecessarily.

1
  • If this.configuration.getLocation() would return null, then we either (a) most likely never would have got that far, or (b) something catastrophic has happened in the interim, in which case I would want the code to fail. So while this is definitely a valid point in general, in this particular case it's fairly safe to say that it does not apply. Also, surrounding all this and much more is an exception handler specifically designed to deal with that sort of unexpected failures.
    – user
    Sep 22, 2011 at 9:11
2

Strictly following the Law of Demeter would mean you should implement a method in the configuration object like:

function getLocationAsString() {
  return getLocation().toString();
}

but personally I wouldn't bother because this is such a small situation, and anyway your hands are kinda tied because of the API you can't change. Programming rules are about what you should do when you have a choice, but sometimes you don't have a choice.

1
  • 1
    That's the same suggested here: c2.com/cgi/wiki?TrainWreck "Create a method which represents the desired behavior and tells the client what to do. This follows the "tell, don't ask" principle. " Aug 12, 2015 at 23:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.