A coworker commented to me today "what's up with all these libraries that encourage method chaining? ;-)" when we were talking about FEST. To stay in context, we are talking about this kind of thing:
assertThat(yoda).isInstanceOf(Jedi.class)
.isEqualTo(foundJedi)
.isNotEqualTo(foundSith);
This, of course, has also been called nice things like "train wreck" and is frequently seen to be a brittleness inducer in code. On the other hand, I encourage the heck out of it in libraries I write, from jDBI for example:
handle.prepareBatch("insert into something (id, name) values (:id, :name)")
.add(1, "Brian")
.add(2, "Keith")
.add(3, "Eric")
.execute();
On yet another hand, I pointed out that it was a bad practice to someone in a code review just last week. So, when is it a good fluent interface, and when is it a train wreck? Good question. My first reaction is "I know it when I see it" but that isn't very useful. So, to take a stab at a description...
Method chaining makes a good interface when the chained methods all
come from the same module, are part of the published API, and when taken
together represent a single logical action. In the first example, they are
all on the published interface of FEST-Assert and are asserting that
yoda is correct. In the second, they all come from the
published interfaces of jDBI and form one batch statement.
For a negative example, let's take data access traversal:
yoda.getMidiclorians().getForce().getDarkSide().getLightningFromFingers();
Here, even if the interfaces for all the intervening classes are in the same module, and are very stable, it sure as heck isn't a single logical unit.
Anyway, gotta run, lunch is done. If I think of a better way to describe it will do so this evening!
writebacks...
While I hate fluent APIs with a passion on a stylistic level, the only non-stylistic objection I can think of is that they make it very difficult to track down null return value issues that occur somewhere in the chain, so I'll throw that down as a necessary and sufficient design constraint for a fluent API -- throw an exception instead of returning null.
Practical vs. Philosophical, Imperative vs. Functional
From a practical point of view, "what Paul said": just don't return any nulls anywhere along the way. From a theoretical hand-waving philosophical point of view: the question is what is going on with each call. If each call is modifying the original object in an imperative way, then I have a philosophical objection because I don't like to see return values from methods that mutate the object because it violates the principle of Command/Query Separation. If, however, each call is returning a new object which is like the previous one but changed (FP-type handling of immutable objects), then this is a reasonable way to go—it just looks funny to us imperative coders. So if obj.func1().func2() is identical to obj.func1(); obj.func2() then philosophically I'm not thrilled. But if result=obj.func1().func2() is identical to newObj=obj.func1(); result=newObj.func2() then that's okay. Again, with the proviso that func1 never returns a null.
No nulls if it's really "fluent"
On a "fluent" interface you should never get a null because, by definition, methods of such an interface always return "this" (the receiver object). So, if 'handle' is not null in Brian's 2nd snippet, none of the method calls will return null. Anyway, I bet you'd like Groovy's "safe navigation" operator: [http://groovy.codehaus.org/Operators#Operators-SafeNavigationOperator%28%3F.%29].
exactly what I wanted to say: chaining methods is bad when there is a chance to get a NullPointerException, because then u have no chance to see which method returned null value. in that case all methods will be in one line and the stacktrace won't help.
Fluent Interfaces don't always return "this"
Brian, Thanks a lot for the reference to FEST. If I understood your post correctly, you consider FEST-Assert a good example of a fluent interface. If I misunderstood you, please let how we can improve it. Fernando, Fluent interfaces don't always return "this". There are cases that we need to return "intermediate" objects that hold state when we have a fluent interface that behaves like a state machine, following a strict path depending on the choices made by the user (in this case the developer.) (Sorry for the SPAM) I wrote an article that describes fluent interfaces as a way to create internal DSLs for the Java language. You can find it at http://www.infoq.com/articles/internal-dsls-java Thanks, -Alex.
comment...