New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Prefer instance var over attr_* methods inside class #538
Comments
I'd argue against this. If you start out using an instance variable and then later change to a custom accessor, you now must go and replace every use of the ivar with the accessor. Risk of breakage increases as a power of the number of changes made, so this could simply inhibit needed refactoring later. EDIT: How much faster is "slightly faster"? If whether this change is made makes a difference that is detectable to the user, then your app is already amazingly performant or mind-numbingly trivial. Lower cost of maintenance should always trump excessive optimisation of initial implementation — a lesson which many of us take decades to fully appreciate. 😞 Also, is "slightly faster" true across all versions of all Ruby interpreters, or merely a specific version of MRI? |
@jdickey That's a really good argument against. |
+1 on jdickey's argument
class Foo
def call
user_ids = [1,2,3,4]
user_ids.each { |id| puts id }
end
end
# vs
class Bar
def call
@user_ids = [1,2,3,4]
@user_ids.each { |id| puts id }
end
end to class Foo # or Bar
def call
user_ids.each { |id| puts id }
end
def user_ids
[1,2,3,4]
end
end |
@jdickey @equivalent Would it be worth saying that all instance variables should be accessed with accessor methods? This would make for a consistent style, as well as easily allow for custom accessors if needed. |
@adsteel well yes and no. it really depends what do you want to achieve. I honestly never touch instance variable in a class I always use attr_reader. class Foo
attr_reader :foo
def initialize(foo)
@foo = foo
end
def do_stuff
foo + 'bar'
end
end But there are cases when you want to make the values private and here where controversy starts. I prefer using private class Foo
def initialize(foo)
@foo = foo
end
def do_stuff
foo + 'bar'
end
private
attr_reader :foo
end
Foo.new('hi').do_stuff # 'hibar' but lot of Ruby folks would complain that this may be actually against the guide as accessors should be at the top and therefore just doing this is better
And even if you convince the community you will have situations like this class Foo
attr_writer :foo
def do_stuff
foo + 'bar'
end
private
attr_reader :foo
end
Foo.new.tap { |f| f.foo = 'hi' }.do_stuff # 'hibar' So this is though sell, I would say "yes, everything should be accessed via accessors or readers or writers "but there might be fight against this as simple classes loosk stupid, but to be honest when you have large class it makes sence: class Foo
attr_writer :bar, :foo
attr_accessor :user
def do_stuff
foo + bar + long_name + some_other_stuff
end
private
attr_reader :bar, :foo
delegate :long_name, to: :user
def some_other_stuff
@some_other_stuff ||= user.some_complex_calculation_of_string
end
end I welcome the feedback on this |
If you'd like all your accessors at the top, you can do: class Foo
private
attr_reader :foo
public
def do_stuff
end
end Or: class Foo
attr_reader :foo
private :foo
def do_stuff
end
end |
👍 on the second case of update: I've just remembered, the first example don't comply with Scissors Rule http://www.eq8.eu/blogs/16-scissors-rule-in-coding (my favourite topic whit which I annoy people :) ) Basically the rule of thumb is (use to be) to have interface methods on top as if you would "cut the printed code with scissors" you will end up only with what you can use as an interface. discussion 1 discussion 2 |
👍 with @equivalent on the second case of @jonahb's class Foo < Magic::Base
attr_accessor :bar
attr_reader :foo
magic_thingie :baz
private :bar, :baz, :foo
def do_stuff
# Magic!
end
end This reflects our existing shop practice in three respects:
|
Using 2.1.5 :001 > class Foo
2.1.5 :002?> attr_accessor :bar
2.1.5 :003?> private :bar
2.1.5 :004?> end
=> Foo
2.1.5 :006 > foo = Foo.new
=> #<Foo:0x000000032fdf20>
2.1.5 :007 > foo.bar
NoMethodError: private method `bar' called for #<Foo:0x000000032fdf20>
from (irb):7
from /home/besen/.rvm/rubies/ruby-2.1.5/bin/irb:11:in `<main>'
2.1.5 :008 > foo.bar= 'nope'
=> "nope" |
Is there a cop to enforce the opposite of this? (e.g. prefer I couldn't find one in the list of style cops, but I might have missed it. |
This cop checks for instance variables used in classes outside of the initializer. It encourages using attr_reader or attr_accessor to set up safer methods to read from or write to your instance variable. inspired by the discussion in rubocop/ruby-style-guide#538
I'd vote for never using attr accessors within the class. Linking worth reading discussion in the topic https://forum.upcase.com/t/using-instance-variables-vs-attribute-accessors/1788/16. |
It took several reads to convince myself that Greg’s arguments in the discussion @giner linked to is not intended as satire. I wonder how large/old the biggest/oldest project he’s ever had to work on is? Point: “I know right away that I’m using the instance variable @item as opposed to guessing wether item method is doing something extra.” Point: “The argument is that readers are easier to re-factor is YAGNI, in my opinion. I’ve hardly ever seen that strategy being taken advantage of.” Point: “As for consistency, this is the same as our stance on single vs double quotes for strings. The argument there is that if you see double quotes you know something interesting is happening (e.g. interpolation, special characters). Say, for consistency, we decided to use instance variables, then if I encounter an item method, I know that something interesting is happening, and it’s not just a getter for the instance variable and I should probably go and look at that method. If we always used readers then it’s very easy to assume they all are just readers, and not a method that does something extra.” Point: “ Defining a reader requires more typing.” Sorry for the slow response; I’ve been sick for most of the last 3 months and have checked GitHub notifications sporadically at best. I still stand by my comment from 3-1/2 years ago, and am absolutely flabbergasted that this issue has yet to be shot down in the flames it so richly deserves. |
I'm a big fan of using attributes accessors methods but I don't think they should be enforced by Rubocop. Speaking of attibutes accessors I created feature request in Ruby tracker so that we could define visibility for attibutes like this: class Foo
private attr_accessor :foo, :bar
end Also I made a draft PR for that: ruby/ruby#3757. |
Done. |
A simple rule: use instance variables when they are available (i.e. in the same class or subclass), and use the accessors when you really need them to access the instance variables from an instance. A couple of points:
|
It is easier to understand and slightly faster to directly access the instance variable inside the class.
The other side of that is, if the accessor is explicitly defined, that custom accessor should be preferred over directly using the instance variable.
The text was updated successfully, but these errors were encountered: