On Inject, Complexity, and George Carlin

Inject has been stalking me for over 2 years.

The year was 2005 and I had decided that I would write a website in this new fangled Rails thing. But while debugging some plugin code I ran into inject. To my java encrusted eyes, it looked like this:

weird_thing = dont_know.inject({}) {|m, o| m[o] = some_crazy_method o; m}

Holy crap. I had no freakin' clue. I looked up the docs on inject and saw that inject, from what I could understand, was used to add up numbers in an array. Which confused me even more. Of course since then I've come to understand inject, but have remained wary as it can be awful confusing to those new to Ruby. Just recently I wrote about how some teammates and I decided against using it to shorten a method, because the particular use sacrificed readability. Then Brian Dainton wrote about how he loves inject. And finally, my team got in a rather spirited discussion over its use. One side of the argument was that it should be used with care as it's hard to read, especially for Ruby newcomers (whom we will be handing this project over to sooner or later). And the other side pointed out, rightfully so, that the reason we use Ruby in the first place is precisely because of powerful methods like inject. If we have to stay away from inject, or method_missing, or metaprograming, or anything else that is deemed 'too hard' then why not just write in Java or C# or Visual Basic?

All this reminded me of one my favorite quotes form George Carlin:

Have you ever noticed that anybody driving slower than you is an idiot, and anyone going faster than you is a maniac?

The funny thing is that I can see both sides of this argument. Back in 2005 I would have thought you crazy to use such a method. Now it would take only a moment to figure out that in:

weird_thing = dont_know.inject({}) {|m, o| m[o] = some_crazy_method o; m}

dont_know is probably an array full of objects and the author of the code wants to pass each one of them into some_crazy_method, get the result, and then put that result in a hash with the original value as the key. When I was a slow driver in Ruby I thought all the other speed daemons were crazy to write such complex code. Now that I'm a budding speed racer myself, it's temping to see people who can't understand my code as stupid.

At this point it would be pretty easy to wrap up this post by saying something useless like: “Use your best judgment when adding complexity, making sure to balance blah, blah, blah...” You, like everyone else on the planet, think you know the difference between too slow (code for dummies) and too fast (code for computers only). Even if you're only wrong 10% of the time (which I doubt) you're going to write a lot of horrible code.

So how do you know when you're wrong (or right)? You don't. Let me try to explain though two examples of complexity: one good and one bad.

In our application, we have a number of different ways to go through a sign up process. So we created an object called Flow. And each flow contains a number of FlowPages that specify the order of the flow. With a little bit of Ruby magic, we made it so you can define a flow like this:

NormalFlow << FirstPage << SecondPage << ThirdPage

Which is damn cool. But there was a bunch of code needed to support this. Over time we came to realize that we weren't seeing a lot of benefit from this notation. We didn't define flows that often and even if we did, the much simpler:

NormalFlow.new(“FirstPage, SecondPage, ThirdPage”)

expresses the intent just as well. When the time came to play a story dealing with putting flows into the database so the business can try different paths, we saw that the complexity was actually making it harder to use and so we threw out the old code and went with the totally unsexy “pass in a string” constructor. Lame, but way easier to maintain.

In the other example, we had a requirement that the business should be able to change any of the words on the page quickly (without a release). This, of course, meant putting the page content into the database. We created a PageContent object that, through the magic of Ruby, gets created before any view is rendered, looks up all the content that it might need, and dynamically defines methods on itself that correspond to its contents. A given page may have a title or an address_line_one_label and to access these methods in the view you can call: content.title or content. address_line_one_label and you don't have to do a thing to set this up (other that put content in the db) -- it's available in every view. And over the months since we implemented this feature it's been easy to use, extend, and maintain.

And that's really my point here. I can't just say “use your best judgment” because you already are. Furthermore, when you've just learned about, say, metaprograming you're probably going to use it more than you should. You won't have any idea if that super cool bit of Ruby is good or bad complexity until you get back in there and try to make it do something new. If the complexity fights you at every turn, it's bad and you should get rid of it. Good code you won't even notice because the tasks that touch it go so smoothly.

Comments

Popular posts from this blog

What's a Good Flog Score?

SICP Wasn’t Written for You

Point Inside a Polygon in Ruby