value objects and “equality”

Today Corey Haines posted another great video interview with JB Rainsberger. This time the topic under discussion was Value Objects. Well worth a listen (go ahead, I’ll wait).

The main gist of the discussion was JB’s assertion that the distinguishing feature of Value Objects is that their equality depends on their state, as opposed to depending on their identity. Thus two 1-Leu notes are “equal” if and only if they have the same monetary value, even though they have different identities (for example, their serial numbers differ). JB then proceeded to extol the virtues of imbuing Value Objects with an equality function and I agree, but only up to a point.

I don’t doubt that creating a new Value Object class to wrap a primitive yields many benefits, and in many cases one of those benefits is to allow the writing of an equality operator for that new class, thereby simplifying much code. But right now, this week, I’m in the throes of a refactoring whose sole purpose is to allow the removal of such an equality operator, because two different views need to compare these values differently.

Going back to real money for a moment, suppose two people looked in their wallets and discovered they had the same amount of money. Does that make their wallets “equal”? I’d say it depends on who’s asking, and why; you might be comparing for total value, whereas I might compare for total number of notes, for example. It doesn’t really make sense for wallets to have an equality operator, and yet in many respects they are interchangeable Platonic values.

That’s a bit contrived, but I do think it’s generally the case that objects with a multi-dimensional value can have multiple valid “definitions” of equality — and that therefore, in such cases, equality is in the eye of the beholder. Does that invalidate JB’s argument? No, I don’t think so. The various kinds of “equality” of objects with a multi-dimensional value still satisfy his definition, and they are still value objects. I’m just being a pedant. Again.

null parameters considered less harmful

Mike Feathers has posted an article in which he defends the use of null parameters, but only under certain circumstances: He likes them as parameters to constructors when building lightweight objects in tests.

As you know by now, dear reader, I have railed long and loud against the use of null parameters. And so I find myself disagreeing with Mike on this topic. [Mike, I would have commented directly on your blog, but why do I need an Artima account to do so?]

Mike suggests that it is always worth trying a line such as

ADBDocument doc = new ADBDocument(null, 0, null);

when writing a test. If it works, he says, you get a lightweight object that can be just good enough to write the test and continue. But is the code now truly intention-revealing? If it were indeed sensible or feasible to create a ADBDocument with no inputs to the constructor, perhaps a zero-argument constructor should have been provided; or a factory method called newUnprintableDocument()? It seems to me that there is now a brittle relationship between the test method and the implementation of at least one constructor for the ADBDocument class, and that makes me nervous.

avoid boolean parameters

You are tempted to create a method with a boolean argument. The caller will set the argument to true or false depending on which code path he wishes the called method to take. This is an extremely convenient way to add a small degree of configurability to an algorithm, and means that both paths through the called method can share setup and cleanup code.

class MyClass

    void dosomething(boolean followPathA) {
        // setup
        if (followPathA)
            // pathA
            // pathB
        // cleanup

However, the use of a boolean argument reduces readability. For example, in a method such as
workspace.createFile(name, true), what does “false” mean? It is much more intention-revealing to call a method such as workspace.createTempFile(name). Furthermore, boolean arguments always create duplication, either of code or of responsibility, and often of a very insidious nature. This is because the called method contains a runtime test to decide between two different code paths, despite thet fact that the calling method already knows which branch it requires! The duplication not only wastes a little runtime, it also spreads knowledge and responsibility further than is necessary. The resulting code is more closely coupled than the design it represents. These effects are magnified exponentially each time the boolean argument is passed on in a further method call.

Therefore avoid the need for a boolean argument by creating two versions of the method, in which the meaning of the boolean is reflected in the names of the new methods. For example, replace

File createFile(String name, boolean istemp)


File createFile(String name)
File createTempFile(String name)

If the resulting new methods have similar logic, extract the duplicate code into shared methods that can be called by each.