I’ve occasionally come across advice to wrap instance variables in
attr_reader. There are two supposed benefits of this practice, which I’ll describe shortly. First I’ll provide a piece of example code.
Example of wrapping instance variables in attr_reader
Here’s a tiny class that has just one instance variable,
@name. You can see that
@name is used in the
@name = name
Here’s that same class with an
attr_reader added. Notice how
loud_name now references
name rather than
@name = name
The purported benefits
Advocates of this technique seem to find two certain benefits in it. (I’ve also come across other supported benefits but I don’t find them strong enough to merit mentioning.)
It makes refactoring easier
Rationale: If you ever want to change
@name from being defined by an instance variable to being defined by a method, then you don’t have to go changing all the instances of
This reasoning isn’t wrong but it is weak. First, it’s very rare that a value will start its life as an instance variable and then at some later point need to change to a method. This has happened to me so few times that I can’t recall a single instance of it happening.
Second, the refactoring work that the
attr_reader saves is only a trivial amount of work. The cost of skipping the
attr_reader is that you have to e.g. change a handful of instances of
@name, in one file, to
name. Considering that this is a tiny amount of work and that it needs to happen perhaps once every couple years per developer, this justification seems very weak.
It saves you from typo failures
Rationale: If you’re using instance variables and you accidentally type
@nme instead of
@nme will just return
nil rather than raising an error. If you’re using
attr_reader and you accidentally type
nme instead of
nme will in fact raise an error.
This justification is also true, but also weak. If typo-ing an instance variable allows a bug to silently enter your application, then your application is not tested well enough.
I would be in favor of saving myself from the typo problem if the
attr_reader technique hardly cost anything to use, but as we’ll see shortly, the
attr_reader technique’s cost is too high to justify its benefit. Since the benefit is so tiny, the cost would have to be almost nothing, which it’s not.
Reasons why the attr_reader technique is a bad idea
Adding a public attr_reader throws away the benefits of encapsulation
Private instance variables are useful for the same reason as private methods: because you know they’re not depended on by outside clients.
If I have a class that has an instance variable called
@price, I know that I can rename that instance variable to
@cost or change it to
@price_cents (changing the whole meaning of the value) or even kill
@price altogether. What I want to do with
@price is 100% my business. This is great.
But if I add
attr_reader :price to my class, my class suddenly has responsibilities. I can no longer be sure that the class where
@price is defined is the only thing that depends on
@price. Other clients throughout my application may be referring to
@price. I’m no longer free to do away with
@price or change its meaning. This makes my code riskier and harder to change.
You can add a private attr_reader, but that’s unnatural
If you want to make use of the
attr_reader technique but you don’t want to throw away the benefits of encapsulation, you can add a private
attr_reader. Here’s what that would look like.
# attr_reader version
@name = name
This solves the encapsulation problem, but what have we really gained on balance? In exchange for not having to change
name on the off chance that we change
name from an instance variable to a method, we have to pay the price of having this weird private
attr_reader :name thing at the bottom of our class.
And consider that we would have to do this on every single class that has at least one instance variable!
Don’t wrap instance variables in attr_reader
Wrapping your instance variables in a public
attr_reader changes your instance variables from private to public, increasing the public surface area of your class’s API and making your application a little bit harder to understand.
Wrapping your instance variables in a private
attr_reader adds an unnatural piece of boilerplate to all your Ruby classes.
Given the tiny and dubious benefits that the
attr_reader technique provides, this cost isn’t worth it.
attr_reader is good and necessary for values that really need to be public. As a default policy, wrapping instance variables in
attr_reader is a bad idea.