Intention Revealing Argument
Video transcript & code
Pop quiz: in this code, what does the
true passed as the argument to
require 'pstore' store = PStore.new('data.pstore') store.transaction(true) do # ... end
If you happen to be very familiar with Ruby's
PStore library, you might know that this boolean argument is a flag indicating whether to start a read-only instead of a read/write transaction. Otherwise, it's not at all clear what
true means in this context. In fact, even though I am pretty familiar with this library, I still have to look up the meaning of that argument every time, because I can never remember if
true means read-only, or read-write.
If I controlled the
PStore library I'd rewrite this method to accept an options hash (or keyword arguments, in Ruby 2.0), so that we could call
#transaction it like this:
require 'pstore' store = PStore.new('data.pstore') store.transaction(read_only: true) do # ... end
But even though we don't control the library, we can clarify this message send by introducing a variable whose sole purpose is to reveal our intent. To do this we define a variable named
read_only, assign it the value
true, and then pass that variable to
require 'pstore' store = PStore.new('data.pstore') read_only = true store.transaction(read_only) do # ... end
I find the addition of a line of code here very worth it for the increase in readability.
There's a alternative technique to this one that I've seen advocated in a few places. Instead of a local variable, we use a symbol in place of a
true value. Here, we'll pass the symbol
store.transaction. Since symbols are always "truthy", it works just as well as passing a literal
require 'pstore' store = PStore.new('data.pstore') store.transaction(:read_only) do # ... end
I have some reservations about this technique. First of all, I find it a little bit misleading. If I was unfamiliar with
PStore and I came across this code, I'd probably assume that the symbol
:read_only was a specific special flag that the method recognized. If I wanted to learn more about it, I'd probably try and look up that symbol… and I'd hit a dead end, because there's actually nothing special about the choice of the symbol
:read_only other than readability. We could have used the symbol
:chunky_bacon and it would have worked just as well.
Second, it's only applicable to boolean arguments, and even then it works better for the
true case than for the
false case. Consider the
Class#instance_methods method that lists a class's instance methods. It takes an optional boolean argument indicating whether to include methods defined on ancestor classes.
Array.instance_methods(true).count # => 167 Array.instance_methods(false).count # => 88
Let's say we wanted to use the symbol-as-boolean technique to turn off inclusion of superclass methods. A symbol is always true, so in order to use a symbol to pass
false we have to negate it.
Array.instance_methods(!:include_super).count # => 88
I don't know about you but I don't find this bang-colon business terribly readable. Whereas with the intention-revealing argument, it's just as clear as when passing a true flag.
include_super = false Array.instance_methods(include_super).count # => 88
There is one variation on this idiom that is worth talking about. If we object to the added line of code, we can actually use an inline variable assignment inside the argument list to achieve the same effect.
Array.instance_methods(include_super = false).count # => 88
This has a couple of things to recommend it. It's more concise, obviously. Not only that, but it actually gives the appearance of a keyword argument.
And this last point is actually why I shy away from using it. I've seen programmers who were newer to Ruby get confused by constructs like this because they believed they were actually dealing with keyword arguments. There's also the fact that some development tools may flag this as an "unused variable". So while I don't feel strongly about it one way or the other, I lean towards using the multi-line version.
That's all for today. Happy hacking!