Instance Variables, Methods, and the Public API
A post the other day on /r/ruby asked about the reasoning behind the recommendation that instance variables be accessed through methods instead of directly throughout code where they are in scope. Most of the responses dealt the fact that this helps make your code more maintainable, which is true, but I think there’s a more significant reason for this: it adds data to your class’s public interface, so it encourages healthy design habits and forces some introspection.
In a carefully designed class, changes to interface are taken seriously. Before you add, remove, or change it, you should ask some questions, the most important (in my opinion) of which are, “Is this method reasonable, given the stated purpose of this class?” and “Is this a method something that users can trust to be there and work properly in future releases of this library?” When you expose an instance variable as a method, you implicitly respond to both of those statements in the affirmative, so think carefully about whether those questions are also true of that instance variable!
When you find yourself in the position that you have an instance variable that shouldn’t be exposed as part of the public API, consider that you might have a class with too much responsibility or an instance variable that you don’t need. Unneeded instance variables often come from doing this:
def my_process
get_foo
use_foo_here
use_foo_again
return_value
end
def get_foo
@foo = ObjectLookup.find_by(quality: 'foo')
end
def use_foo_here
@bar = @foo + 10
end
def use_foo_again
@baz = @bar + 20
end
def return_value
@bar > 40 ? @bar : @foo
end
…when something like this is healthier in every way:
def my_process
foo = get_foo
bar = plus_10(foo)
baz = plus_20(bar)
return_value(foo, baz)
end
def get_foo
ObjectLookup.find_by(quality: 'foo')
end
def plus_10(obj)
obj + 10
end
def plus_20(obj)
obj + 20
end
def return_value(starting, ending)
ending > 40 ? ending : starting
end
Yes, there are times whenyou want to reuse an expensive query multiple times internally within an object and exposing it to the public API would be inappropriate. That’s fine, just mark that method private. But that should be your last result, not an excuse for extra data. Changes to the public API are an opportunity to question your approach and improve the quality of your code, so face them head on!