Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

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.

share|improve this question
add comment

4 Answers

up vote 17 down vote accepted

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).

share|improve this answer
    
I would tend to agree with you, but he already said he cannot modify the service API. –  jhocking Sep 21 '11 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 '11 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 '11 at 16:23
    
agreed, the core issue is that location is not a string, it needs to be a Location (or some other thingie representing a location idea). –  Paul Nathan Sep 21 '11 at 18:21
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. –  Michael Kjörling Sep 22 '11 at 9:19
show 2 more comments

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.

share|improve this answer
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. –  Michael Kjörling Sep 21 '11 at 14:14
2  
+1 for being pragmatic ;-) –  Treb Sep 21 '11 at 14:17
add comment

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.

share|improve this answer
    
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. –  Michael Kjörling Sep 22 '11 at 9:11
add comment

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.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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