is it bad form to use instance vars directly in ru

2019-07-25 08:24发布

问题:

Should you always create accesssors (for reading and/or writing) in ruby? If you have a class that's not meant to be reused outside, can't I just use instance variables directly?

One of the problems I ran into is that it's problematic to stub out @instance_vars in tests.

回答1:

Instance variables don't matter when it comes to testing. You should be testing your methods, in order to verify that they produce the correct results.

When you define a reader method for an attribute, you expose that attribute to the world. It doesn't matter if the value of the attribute comes from an instance variable, a database, a file, runtime computation or whatever. People can just call the method to obtain the value.

Similarly, when you define a writer method for an attribute, you are letting everybody know that they can set it, if they need to. It doesn't matter where the value goes to.

Only your methods define your public API. Everything else is an implementation detail.

Within your class definition, there is certainly no harm in accessing instance variables directly:

@variable = :value

There's no reason to call methods if simple assignments are all you need. Of course, sometimes you need more sophisticated functionality. Lazy initialization, for example:

def variable
  @variable ||= :value
end

# ...

variable.to_s

If your method is for internal use only, you should not include it in your public API. Mark it as private:

private :variable

To be honest, though, nothing is really locked down in Ruby. Even without setter methods, people can easily tamper with your object if they really want to:

class << (object = Object.new)
  private
  def variable; @variable end
end

object.variable
# NoMethodError: private method `variable' called

# send bypasses access control
object.send :variable
# => :value

object.instance_variables
# => [:@variable]
object.instance_variable_get :@variable
# => :value

object.instance_variables.each do |variable|
  object.instance_variable_set variable, nil
end
object.instance_variable_get :@variable
# => nil


回答2:

I think accessors are more something which could help you somedays, when you want to validate the new variables before setting or performing a task on getting / setting a new value. If you're sure you won't need it I think it's better to stay with the instance vars. Otherwise you could create those already at the beginning, so you won't have to create them later when you have large amounts of code and you can save a lot of time.



回答3:

Instance variables are meant to be used within the context of the instance in question. If you need to exchange data with other objects or even other instances you should be exposing them with attr_reader or attr_accessor as required if not writing your own methods to facilitate that.

Accessor methods act as a gatekeeper and provide an opportunity for you to validate that an external caller does not mess up your internal state. One principle of object-oriented design is that the object assumes responsibility for screening input. How you handle bad input is up to you, be it ignoring it, throwing an exception, or logging an error among other things.

If you don't handle bad input and later crash because of it, it ends up being your "fault" and you'll be at the top of the stack-trace. Rejecting bad values earlier would've shown the problem exactly when it occurred, not later on in the execution when you might've lost track of where that assignment came from.

Generally you do not want people accessing your data unless they have good reason to. Directly accessing and modifying another object's instance variables is bad form.

Some languages go so far as to make it nearly impossible to alter an object's internal state directly, but Ruby is quite casual in this regard. Still, just because something can be done doesn't mean it should.

When you define accessors it is up to you as to if you use them within the implementation of that instance. Sometimes it's more convenient to access them directly, calling @var instead of self.var, but there are occasions where using the accessor provides additional functionality that the instance variable does not. It can also make refactoring your application easier later since you have a single point of control.