5

I see that with this code:

# filename: play.rb
class A
  attr_reader :a
  def initialize(num)
   @a=num # I have to use @ here
  end

  def m1
    p "---"
    p @a
    p a 
  end 
end

obj = A.new(1)
obj.m1

I get the output

$ ruby play.rb 
"---"
1
1

As you see I can refer to a as either @a or a in the m1 method and both work.

Which should I use when and why?

Michael Durrant
  • 93,410
  • 97
  • 333
  • 497
  • 2
    Besides the answer by mrzasa, notice that the "normal" purpose of an attr reader is to give external access to instance variables, which enables you to do `p obj.a`. Not sure this was clear, as you were more focused on internal usage in your question. – Casper Nov 22 '19 at 15:18

2 Answers2

4

In this case you don't use a local variable a but a getter method that gives you @a because that's have attr_reader :a. It generates a method #a() used as an getter.

What you really do is:

  def m1
    p "---"
    p @a
    p a() 
  end 

If I have the accessor, I use it, not the instance variable. It lets me change the getter later.

Sergio Tulentsev
  • 226,338
  • 43
  • 373
  • 367
mrzasa
  • 22,895
  • 11
  • 56
  • 94
  • So essentially the attr_reader lets me use a instead of @a right ? – Michael Durrant Nov 22 '19 at 14:47
  • 2
    right: https://stackoverflow.com/questions/4370960/what-is-attr-accessor-in-ruby – mrzasa Nov 22 '19 at 14:48
  • 1
    @MichaelDurrant Using the getter is preferred most of the time, since it defines an additional abstraction layer. Say you'd like to add some calculation before returning, you could simply remove `attr_reader :a` and define the method yourself `def a; @a * 2; end`. Everything using `a` is automatically updated, whereas you need to update `@a` usage by hand. See: https://stackoverflow.com/questions/1568091/why-use-getters-and-setters-accessors – 3limin4t0r Nov 22 '19 at 16:57
  • > Using the getter is preferred most of the time, since it defines an additional abstraction layer This is true, though I personally can't think of a case where I needed to take the getter/setter wrappers and somehow redefine their logic or decorate them later on. I don't disagree with the point, but IMO the difference between referencing an instance variable versus its getter method is so nitpicky as to be inconsequential. – David Bodow Nov 22 '19 at 23:06
  • @3limin4t0r, I don't buy your argument for reasons given in point #2 in my answer. – Cary Swoveland Nov 23 '19 at 04:17
3

I find this an interesting question, one that I had not given much thought to before today. I consulted the Ruby Style Guide, expecting it would provide some good advice, but it is curiously mute on the subject.

I make four suggestions below. These concern setters as well as getters. Others may disagree and have good reasons for doing so. Let's discuss! I look forward to reading comments.

In reading my remarks it may be helpful to put yourself in the place of someone reading code they wrote some time ago or someone reading someone else's code for the first time.

1. Getters used exclusively within a class should be private

I expect this is my least debatable suggestion, as I can see only disadvantages to making any method public when there is no reason to do so.

2. Methods named after instance variables should not have side effects

By this I mean if there is an instance variable @quantity, a method named :quantity should do no more than return the value of @quantity (i.e., be a getter) and a method named :quantity= should do no more than assign a value to @quantity (i.e., be a setter). That is, such methods should not have side effects.

Suppose, for example, a ("pseudo-") setter were defined to assign a value to an instance variable after accounting for a 10% spoilage factor:

def quantity=(q)
  0.9 * q
end

If the reader of the code were to miss this definition, but knew there was an instance variable @quantity, the natural assumption would be that :quantity= was a setter, which might mask errors or waste time in testing/debugging. Better, I think, would be to write:

class C
  attr_writer :quantity
  def initialize
  end
  #...     
  def adj_quantity_for_spoilage
    self.quantity *= 0.9
  end
  #...
end

c = C.new
c.quantity = 100
c.adj_quantity_for_spoilage

3. Do not use setters within a class

For example, I suggest writing:

class C
  attr_accessor :quantity
  def change(x)
    @quantity = x
  end
end

rather than:

class C
  attr_accessor :quantity
  def change(x)
    self.quantity = x
  end
end

The main reason is that it is all-to-easy to inadvertently omit self. in self.quantity = x, in which case x would be incorrectly and silently assigned to a newly-created local variable quantity.

There is a secondary reason in the case where there is no need to access the setter from outside that class. As with getters, we would want setters to be private in this situation, but that is not possible since they must have the explicit receiver self. Recall that private methods, by definition, do not have explicit receivers.

Lastly, I see no argument for using self.quantity over @quantity, particularly in view of the fact that the former requires the keying of four additional characters.1

4. Do not use getters within a class

I expect this to be my most controversial suggestion and I will be the first to admit that if one rejects it the earth will no doubt continue to orbit the sun. Moreover, I concede that using getters in this way does save the typing of one character, the hard-to-reach-without-looking "@".

When I read:

x = m(@quantity)

I know immediately that @quantity is an instance variable, regardless of my familiarity with the code. If I've forgotten (or never knew) what the variable contains or how it is used, the path to my elucidation is clear. True, if I know there is an instance variable @quantity, the above has no advantage over:

x = m(quantity)

If, however, I don't know if there is an instance variable @quantity, I would be faced with three possibilities: quantity is a local variable, a getter or a method that is not a getter. The time required to complete my investigation should not take much longer than if I were tracking down @quantity, but those seconds do add up.

Let's consider another thing: what are the consequences of misspelling the name of an instance variable versus misspelling its getter (something I do frequently):

x = m(@qauntity)

versus:

x = m(qauntity)

@qauntity will return nil, which may lead to an exception being raised, but possibly not soon or not at all.

qauntity will almost certainly raise an "no method or variable" exception immediately, giving it the edge in this situation.


In sum, I am suggesting that it generally is best to use getters and setters outside of class definitions only, and that they have no side effects.

Your thoughts?

1. Over a lifetime of coding, typing those four extra characters could amount to hours of time wasted that could otherwise be used productively (e.g., playing pong).

Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100