I’ve been doing research for my RubyConf presentation on November 6th, and while digging through one of my old projects, I ran into an interesting bit of code.
The project in question was Needle, a result of a brief love affair with complexity. (This was before I met 37signals, and both 37signals and my wife have since forgiven me.) It was a fun project, but I added features because they “sounded cool”, not because I had any practical need for them.
Adding new features always results in increased complexity. (For some features, the added complexity may be fairly small, but there is not necessarily any relation between the perceived complexity of the feature and its implementation. That, however, is a topic for another dissertation.) In the case of Needle, I wanted (essentially) to be able extend objects on the fly, and I had two ideas for how to do this. In one, you specify an existing extension to append to the object. I called this the “with” approach, since the object is extended “with” another one. In the other approach, you specify an anonymous block of code to extend the object with. I called this the “doing” approach, since the object is extended by “doing” something.
Both sound cool, right? Yeah, man! Extend objects on the fly! Whee!
So I implemented both. The problem was that I couldn’t immediately see how to make both work together. What would happen if wanted to extend an object both “with” something, and by “doing” something? Adding the features themselves added complexity to the project, and I wasn’t willing to further complicate things by making the two features compatible, so I created an artificial constraint: the library would raise an error if you tried to use both features on the same object.
Now, constraints are good, and you should certainly seek to embrace yours. But creating artificial constraints because you’ve painted yourself into a corner is code smell, and the solution is not to accept your corner and wait for the paint to dry, it is to unpaint yourself out of that corner. In the case of Needle, I should have either worked harder to make the two features compatible, or removed one of them. (Or both of them!)
Ultimately, Needle failed because it didn’t fill a real-world need, and so its code is a graveyard full of things like these artificial constraints. Real projects need real applications. But complexity being what it is, eventually you’re almost certain to find yourself heading for that corner with a trail of paint chasing you. When you do, pause for a moment and consider whether this is a true constraint you’re embracing, or whether you can do with less complexity. Chances are, the prospects aren’t as bleak as you thought.
This is all related to a RubyConf presentation I’ll be giving in Orlando on November 6th, entitled Recovering from Enterprise: how to embrace Ruby’s idioms and say goodbye to bad habits. If you’re going to be there, do stop by and introduce yourself!