artisanal bytes

“Hand-crafted in San Francisco from locally sourced bits.”

Clever Is Not Always Better

Often times in code reviews, I will make a comment about a bit of code that catches my eye, because I am unsure about why the author chose to write what he did. Sometimes the answer to my question of “why” is that he thought it was clever. That is almost always a red flag that triggers further investigation on how the solution could have been coded. The focus of good coding should be to devise a solution so dead obvious that the next person who comes through to fix a bug will be able to figure it out without having to think about it too much. Devising a dead obvious solution can be very hard to do if the problem domain is difficult, if the feature is complex, or if the problem is truly a technical challenge. But most of the time, when writing business logic, we are just manipulating simple data structures, running through conditionals, or running queries against a database. No cleverness should be needed in these cases, and any clever solution may be detrimental to the business in the long run.

The culmination of being clever in code is the abundance of what I call “magic” that we see in frameworks and libraries these days. In Java, this shows up as the overuse of annotations; in Ruby, this is most clearly seen in Rails’ use of meta programming; and in the world of C, clever developers overload operators to give them new meaning. The intention of these libraries is to make developers’ lives easier, either by touting “convention over configuration” and by being “opinionated” or by allowing developers to achieve a lot of functionality with little typing. These are beneficial concepts in theory, but they break down against my argument of explicitly written code being easier to understand than code that is implicitly run under the covers.

When you first use a library built around this magic, the code you write is fairly small and easy to understand (provided you read all of the library’s documentation and comprehended all of the features). As you write more code using the library, you will inevitably hit a point where you need to work around its cleverness, or you need to debug a production defect. At this point, you may find out that you did not in fact understand exactly how the library works, but at the very least, you will find yourself diving down a pit of stack traces trying to determine at which abstraction layer of the framework the bug manifests so you can determine how to correctly use the framework to fix your bug.

On the surface, your code is easy to read, but the implementation behind it is incredibly complex to reason about when you need to debug or analyze it for performance or extend its functionality. The so-called productivity gained by not having to type much has been lost by the complexity brought about by the cleverness in use.