Cut With the Grain

April 4, 2004 10:51 PM

When possible, cut with the grain. The grain tells you which direction the wood wants to be cut. If you cut against the grain, you're just making more work for yourself, and making it more likely you'll spoil the cut.

I was working on a Java web application the other day, looking to move functionality out of base classes (where they were painfully tying unrelated actions into a rigid class hierarchy), and into the filter/interceptor chain that delivered data to the actions.

It's dependency injection again, but the inversion that gets its cue from the contents of the incoming request. So, for example, one thing the Interceptor could do is see that since the request has a personId parameter and the action has a setPerson() method, it should retrieve the appropriatePerson on the action's behalf.

This is actually pretty neat: it avoids cumbersome implementation inheritance, encourages you to use consistent parameter naming across the app, and makes actions a lot easier to test since now instead of having to mock out the retrieval of a person, you can just pass one in.

Java put one little obstacle in the way. Java has no simple way of asking "Does this class respond to this method?" I could instead try Class.getMethod(), but that quite annoyingly throws an exception if the method isn't found, rather than just returning null. Or I could search the array returned from Class.getMethods() to see if there is a setPerson() contained within.

We're only talking about a few lines of helper method here, of course. Apache Commons already has one ready-made and waiting for me. All this would be doing, though, is moving the ugly code into a ghetto so I wouldn't have to see it. It would still be there.

I was cutting against the grain of the language. Java is statically typed. Java doesn't want me to use the existence of a method to determine if I should call it. Philosophically, the language considers the existence of a method with the right name to be a coincidence, unless that name is backed up by a specific type. (Witness the objections to dynamic typing that start: "What if I had two methods called shoot, one on a Camera object, the other on a Gun?)

For Java, the correct way to do what I was trying to do was to avoid introspection entirely, and instead define a PersonAware interface that I could check for with instanceof and then cast to directly.

It's a pattern I follow quite often. Over the last few years I've come up with a series of very clever, rather complicated introspection-based solutions to problems. Invariably, a cooler head has later either deleted them, or convinced me to delete them because while the Java way might be slightly less powerful and not as conceptually clean, by moving the code back to cut with the grain of the language, it became more readable and more maintainable.

On the other hand, Ruby is very much a dynamic language. When I'm writing code in Ruby, I expect duck typing to be the norm. If it looks like a duck and quacks like a duck, we treat it like a duck.

Which is why I was annoyed the other day. Some Ruby library told me it needed a Hash passed in, and I made the quite justifiable assumption that this meant it wanted some object that implemented certain methods of the Hash class. As far as I could see, it only really needed a [] method, which my MultilayeredStuffHolder already implemented. When I passed my object into the library, however, I got an error back because the library explicitly checked the class of my StuffHolder, and complained that it was not a Hash.

Against the grain, again.

On the other hand, when carving meat you carve against the grain. The carving knife is stronger and sharper than your teeth: carving the meat against the grain leaves your teeth a much easier job, and the meat feels more tender as a result.

A lot of very useful Java libraries carve directly against the grain of the language. Hibernate. Groovy. Spring. Pretty much anything that makes use of cglib. Sometimes you have to carve against the grain to cut a large problem up into easily chewed pieces.

At this point, a Lisp programmer might attempt to convince me that the best path is a language with no grain at all. Such malleability can be hard to get one's teeth into, though.

Previously: The Art of the Shortcut

Next: Dear Microsoft