Basic Readability

Jon Yurek

Is this simple code? It’s an example from the docs on Symbol#to_proc:

people.select(&:manager?).collect(&:salary)

It’s certainly readable. It’s elegant and expressive. But making it possible employs a number of fairly advanced topics relating to automatic conversion of method arguments as well as the ability to extend existing classes—not easy, and not something that someone new to the language would think of. Hell, it’s not something that people familiar with the language would necessarily think of. But when that was pointed out as a new addition to ActiveSupport, rubyists all over the world joined each other in a collective “Why didn’t I think of that?”

Even if you don’t know how or why it works, you can tell what it’s doing. But is it simple?

We recently got into a bit of a row here in the office over the idea that for code to be optimally maintainable, anyone who has a passing familiarity with the concepts of a language should be able to understand what’s being done. The intended result being that if anyone can understand it, then the logic is simple enough for anyone to hold in their head and therefore refactor without distorting its goal. To this end, it was recommended that all advanced features of the language should be shunned, because, by definition, novices wouldn’t be able to understand what’s going on. Going to the logical extreme of the argument, learning how to use those advanced features is a waste of time, since code written using them is unmaintainable.

The other side of the argument was that given the use of only the standard tools, you will only ever make standard code, not great code; it’s argument being that if you can create great code that is elegant and reads well, then you have both made your code easy to maintain by virtue of being readable, and you have made your code powerful and expressive by virtue of using features that wouldn’t be available had you kept to “standard” techniques. This comes at the cost of potentially writing code that no one else can understand or maintain.

I think Symbol#to_proc is a good focal point of contention for this debate. The actual definition of it is in vendor/rails/activesupport/lib/active_support/core_ext/symbol.rb (which, for the sake of reference, I’m posting here).

class Symbol
  def to_proc
    Proc.new { |*args| args.shift.__send__(self, *args) }
  end
end

The standard techniques side does not like it, because a novice does not understand why it works. The other side likes it because it allows expressiveness in a small package, and the novice can still understand what even if they don’t understand how. The actual code required, given basic code formatting, is 5 lines (and a require if it’s in a different file). But the knowledge required behind it is much higher: You need to know that prefixing the final argument of a method call with an ampersand will tell Ruby to try to convert it to a block using the to_proc method, that you can open existing (and even standard) classes to add methods, and that you can call methods on objects without knowing the methods’ names (via send). It is a very elegant piece of code, to be sure, but does it have any place in the code you write on a day to day basis?

Even if you consider the code at the beginning of the article to be simple, an important question is whether or not the cost of having simple code is justified by having a complex, even tricky, bit of code elsewhere that allows the simplicity in the first place.