Some Thoughts on Ruby's attr_reader

When implementing Plotrb, I often encounter the situation in which a class has many properties/attributes. For example, a Visualization would have attributes such as name, format, value, source, etc. Since these classes wrap corresponding components in Vega, each attribute needs to be validated according to different rules defined by Vega, before they can be stored and later converted into JSON specification.

At first, I didn't choose to use Ruby's attr_accessor, although I knew it's a convenient and conventional way to define setter/getter for attributes. The reason was that because of the validation needed for each attribute, I would have to write my own setters, and thus override the ones generated by attr_accessor. Then I took a step further to write my own three-line getter as well, and thus ditch attr_accessor altogether. For each attribute I would have these two methods,

def name
  @name
end

def name=(name)
  # some complex validation logic
  # ...
  @name = name
end

I didn't give many thoughts to it until Claudio brought up an issue regarding the use of attr_accessor, which made me start to rethink about the approach. Here are two alternatives I could have besides defining my own getter and setter.

  1. As mentioned before, use attr_accessor and override the setter.
  2. Use attr_reader for getter, and provide custom setter.

The first seemed a bit redundant to me, and the second is not that appealing either. The problem with using attr_reader and a custom setter is that I feel the code might give people the wrong impression about the attributes. Since I usually define all attr_readers at the beginning of the class, others reading that code might have the impression that these attributes are read-only to the outsiders. But in fact they are not! Only until you read through the entire code of the class, you'll realize there are setters as well. Isn't the whole point of having attr_reader, attr_writer, and attr_accessor to differentiate the intention and usage for the instance variables?

However, I have to admit that this is my own understanding, without enough experience or knowledge about whether they are common use cases in the Ruby community. John later commented that they are in fact quite common, and perhaps I was being prejudiced on this matter.

Okay, so I realized that my own getters were indeed unnecessary, but are there any other advantages of using attr_accessor or attr_reader, besides a shorter and cleaner code? What about performance? Let's see some benchmarks.

require 'benchmark'

class Test

  attr_accessor :test_accessor
  attr_reader   :test_reader

  def test_manual
    @test_manual
  end

  def initialize
    @test_accessor = @test_reader = @test_manual = 1
  end

end

n = 10000000 # that's a ridiculous 10 million
t = Test.new

Benchmark.bm(7) do |x|
  x.report('attr_accessor') { n.times { t.test_accessor } }
  x.report('attr_reader')   { n.times { t.test_reader } }
  x.report('manual')        { n.times { t.test_manual } }
end

And here's the result obtained on my machine.

                user        system      total         real
attr_accessor   1.230000    0.010000    1.240000 (  2.103052)
attr_reader     1.220000    0.010000    1.230000 (  1.825171)
manual          1.630000    0.010000    1.640000 (  2.087668)

A simple benchmark is sufficient to tell the difference. As it turned out, Ruby's attr_accessor and attr_reader not only shorten the code, but also bring performance improvements. I should try to use them whenever I can.

Although realistically the changes probably does not have a huge impact on Plotrb, the experience itself is very rewarding. Code review is a thought-provoking opportunity to let us pay attention to details which might otherwise be overlooked.


comments powered by Disqus