Friday, August 7th, 2009
I recently heard of an interviewing tactic that goes like this: Present the candidate with a couple of slightly obfuscated methods. The challenge is to read through the code and name the variables and methods. This is a very straight-forward task, but the more I thought about it, the more powerful I realize it is.
How and when and why a developer applies a specific name to a variable, method, class, etc. speaks volumes to that person’s abilities. Does he understand what the method does? Is he clearly communicating this to others? Is he cognizant of idioms and other common usage patterns for this specific scenario?
In a strongly-typed language like Java, moving data around from type to type is the name of the game. Transformational and coercive methods are ubiquitous. And yet, it is so easy to misapply even the simplest of words and, in doing so, convey an entirely different message than your intention.
Let’s say you have class A and you need to add a method to it that returns an instance of class B based on A. A very natural thought process for this might go something like: “I need to convert A to B.” And so you write this in your code:
class A {
...
/**
* Converts this instance to an instance of B.
*/
B toB() { ... }
}
Simple enough. But our javadoc is somewhat vague: If I modify the result of this method call, will my original instance of A be modified? In other words, is this a copy of the instance of A, or is it the actual instance of A masquerading as an instance of B? Despite the vagueness, let’s say you publish this method, and people start using it. How can they expect it to behave? We should have thought through the exact requirements first. But we also needed to take extra care in naming the method.
The Java libraries have already established a convention for how we should interpret “to” in front of a method name. Object defines toString(), which returns a String representation of the object. Since all Strings are immutable, it is very clear that we cannot alter the originating instance by munging the String.
On the other hand, Arrays defines asList(T… a). Using “as” for the prefix in the above method may have been just as valid, depending on what our true intentions were. But the conventional meaning is completely different. This method is described in the javadoc as follows:
Returns a fixed-size list backed by the specified array. (Changes to the returned list “write through” to the array.)
So we see here that we’re dealing with the originating array, but we are able to access it as if it were a List. Here are their conventional meanings in the general sense:
- asType — returns an instance of Type backed by the original instance; modifications to the returned value are reflected on the originating object.
- toType — returns an instance of Type not backed by the original instance; modifications to the returned value are not reflected on the originating object.
Personally, I recall the distinction between the two by remembering the Java Object and Arrays APIs — these APIs are common enough that I don’t have to think about it much.
Now let’s go back to our toB method. A perfectly valid interpretation, were someone attempting to call that method, would be to assume the convention holds true. If we take it at face value, we should expect that we are given an object that, when modified, will have no effect on the original instance of A. If this is indeed the case, we should update our javadoc to be more specific. Even though conventions are useful, not everyone obeys them. Your javadoc should clearly support the typical notion of this usage.
Here’s the updated code, with an example of an “as” method thrown in, while we’re at it:
class A {
...
/**
* Generates an instance of B based on the current object.
* Mutative operations on the returned value will have no
* effect on the current object.
*/
B toB() { ... }
/**
* Returns an instance of B backed by the current object.
* Mutative operations on the returned value will be
* reflected on the current object.
*/
B asB() { ... }
}
Again, it’s easy to remember the difference: Just think asList vs. toString.
