NubyGems: Don't Use Class Variables!

by Gregory Brown

This was actually inspired by some thoughts at a new_haven.rb meeting I wasn't able to attend, reintroduced in a RubyTalk thread, and solidified in another blog entry that's worth a look.

The topic here is simple, and it's that for most common needs, there is absolute no reason to use class variables. I will show how you can use class instance variables to avoid the problems associated with class variables in most cases at the end of this article, but first, take a look at two compelling and mind melting reasons for *not* using class variables.

From Gary Wright:


>> class A
>> @@avar = 'hello'
>> end
=> "hello"
>> A.class_variables
=> ["@@avar"]
>> A.class_eval { puts @@avar }
NameError: uninitialized class variable @@avar in Object
from (irb):5
from (irb):5
>> class A
>> puts @@avar
>> end
hello
=> nil
>> class A
>> def get_avar
>> @@avar
>> end
>> end
=> nil
>> a = A.new
=> #
>> a.get_avar
=> "hello"
>> a.instance_eval { puts @@avar }
NameError: uninitialized class variable @@avar in Object
from (irb):16
from (irb):16


And the real scary one, from David A. Black:


@@avar = 1
class A
@@avar = "hello"
end
puts @@avar # => hello

A.class_eval { puts @@avar } # => hello


Yes, there are reasons why both of these problems occur. Yes, they are likely to give you a headache.

If you're just looking to store some data in a variable which is unique to your class, but accessible from instances or externally, why not just use instance variables?


>> class A
>> @foo = "bar"
>> class << self; attr_reader :foo; end
>> end
=> nil
>> A.foo
=> "bar"


Yup, classes are just regular old ruby objects, and class instance variables are just regular old instance variables. This avoids the above problems, as well as simplifies the concepts of where your variables actually live.

Yeah, maybe @@foo is easier to type than self.class.foo
But good luck debugging it! :)

11 Comments

Joe Grossberg
2007-01-05 11:30:56
Is the problem really the class variable? It seems to me like it's haphazard use of eval() variants that's the problem.
Greg
2007-01-05 11:41:41
Those are just shortcuts to show what's going on.


Sans any eval, the inherentence policy of class variables is scary:



class A
@@foo = "bar"
def bar
@@foo
end
def bang
@@baz
end


end


A.new.bar #=> "bar"


class B < A
@@foo = "baz"
@@baz = "boo"


def quux
[@@foo,@@baz]
end
end


B.new.quux #=> ["baz","boo"]
A.new.bar #=> "baz"
A.new.bang # throws an error because @@baz is uninitialized


Greg
2007-01-05 11:46:20
The above mentioned code works differently on 1.9, the scopes act normally. See the comments in this blog entry
JEG2
2007-01-08 07:18:55
Great tip! I couldn't agree more.
Who
2007-01-17 05:21:58
Perhaps it breaks the principle of Least Surprise, but I think, like anything else, we need to understand PLS as a goal. Sometimes, we are going to be surprised, and as a result we are going to have to fiddle with the language until it gives us what we want. As someone else commented Ruby 2.0 will fix this issue, and that will bring class variables more closely aligned with PLS.


I can't really think of it as broken if there is a clear enough work around and the main objections are based on surprise.

Greg
2007-01-17 08:01:59
Who-


[1] See POLS in this summary


http://rubyforge.org/pipermail/rubyweeklynews-newsletter/2005-May/000016.html


[2] I wrote this post and also mentioned the change in 1.9


[3] This post is intended as advice, not a RCR. Just as you shouldn't use globals unless you have a really good idea of why they should be used in certain situations, the same applies to class variables.


I hope this clarifies things a bit.


-greg

Tim O'Brien
2007-01-19 12:46:23
Dude this post is like perfect poetry. I was just doing battle with class variables when I stumbled upon it. Thanks for writing this.
Greg
2007-01-19 16:07:59
Tim,


Thanks for the kind words. This issue has just had one too many ruby threads dedicated to it to not deserve a quick summary.

Who
2007-01-25 06:23:58
Greg,


I think you may have not intended to sound so absolute but I think that I have adequately dissected the post you did write. First of all it is titled "Don't Use Class Variables!" It's imperative with emphasis.


Your points ranged from I tried this, and it didn't work (Surprising?) to I tried this other thing and IT didn't work either (Surprise x x ~ Frustration?). And end the post with: You have to do this (David Black's example).


My point was: that works, doesn't it. Then use that. So I believe that I adequately parsed your post. I'll give you that there's possibly a little levity in the post, but we can't clearly tell that.


What it sounds like you are saying is: Don't use a feature found in most OO languages--including smalltalk--because it won't work for the first few ways that you might try. (Again, what is this but surprise?)


My point is roughly Matz' point, but not as yielding. There is no reason to give up an adequate enough name for a design goal. Having struggled with Python right before I came to Ruby, think Ruby adheres to POLS better than Python adheres to "intuitive" which is what it is pumped up to be. At least Ruby does not tout "There is only Matz' way to do it" as a principle--or slag Perl quite so much.

Greg
2007-01-25 08:19:05
@Who


This post is directed towards Ruby newbies, and just as I'd advice them to not use global variables in a rather firm manner, the same goes for class variables.


It's a matter of complex and surprising rules that Ruby experts are thwarted by with class variables. The fact that scoping is going to be fixed in 1.9 is not very helpful to a new user of Ruby.


To me, it's important to understand the distinction, why one is better than the other, and what the real issue is with class variables. But perhaps it's hard to express that in a short, newbie oriented post.


It's not just surprise though, it's just in mose cases poor design to use class variables when class instance variables will do.

Greg
2007-01-25 08:24:29
@Who,


Also, David Black's example shows that class variables defined on the main object are shared with all descendants. It does *not* show that they work in a clean or easy to predict manner.


There are a few more folks talking about this, i've linked them here so you can check them out and maybe share what you think here.


- http://tinyurl.com/3xeef2
- http://tinyurl.com/2thspb
- http://tinyurl.com/2p3wc6