ruby, class variable or class instance variables 17 Nov 2006
This is in reference to the topic of class variables or class instance variables in ruby… it’s a posting based on the recent post of Stuart Halloway, click here to read it.

Why Not Class Variables?

Stuart Halloway hits the problem that class variables has perfectly, “They are shared across an inheritance hierarchy in a counterintuitive way.” Although he doesn’t specifically say ActiveSupport or “Rails” that is where this probably exists, and IMO it promotes bad design and a bad usage of class variables.

The problem is that since class variables are shared across the inheritance hierarchy, if any class in the hierarchy changes the value of the class variable, it changes throughout the whole hierarchy. This is not good.

It is better to use instance variables on the class, because they work intuitively across inheritance, and if you change the value of class-level instance variable it only changes for that class (it doesn’t affect the rest of the hierarchy).

Class variables that are used and relied on throughout a inheritance hierarchy is BAD design IMO. Even if you intend to use class variables for your class only you are forcing people who will extend your code to potentially give in to BAD design. It is better design to encapsulate class variables into class-level instance variables which are accessed via class-level instance methods. This allows any subclasses (or potential subclasses) to redefine implementation if they want to, and it won’t have negative effects on the the rest of the hierarchy.

Here’s a scenario of BAD use for class variables. This uses the example of keeping track of how many people you have:

class Person attr_accessor :name

 @@instances = \[\]
 
 def self.instances
   @@instances
 end
 
 def initialize( name )
   self.class.instances << self
   @name = name
 end

end

joe = Person.new( “Joe” ) zach = Person.new( “Zach” )

puts “You have #{Person.instances.size} people in existence!” # POINT A

class Paramedic < Person end

sally = Paramedic.new( “Sally” )

puts “You have #{Person.instances.size} people in existence!” # POINT B puts “You have #{Paramedic.instances.size} paramedics in existence!” # POINT C

  • At POINT A you have two people in existence, which is correct.
  • At POINT B you have three people in existence, which is correct.
  • At POINT C you have three paramedics in existence which is incorrect!

You only have one paramedic (Sally!), but since Person is using a class variable you are stuck with affecting the whole hierarchy.

Now this is easy to fix, by using class-level instance variables. Don’t be shocked by the “class « self”…

class Person
  attr_accessor :name

  class << self
    attr_accessor :instances

    def instances
      @instances ||= \[\]
      @instances
    end
  end

  def initialize( name )
    Person.instances << self
    @name = name
  end

end

joe = Person.new( "Joe" )
zach = Person.new( "Zach" )

puts "You have #{Person.instances.size} people in existance!" POINT C

class Paramedic < Person

  def initialize( name )
    super( name )
    Paramedic.instances << self
  end

end

sally = Paramedic.new( "Sally" )
puts "You have #{Person.instances.size} people in existance!" #POINT B
puts "You have #{Paramedic.instances.size} paramedics in existance!" POINT C
  • At POINT A you have two people in existence, which is correct.
  • At POINT B you have three people in existence, which is correct.
  • At POINT C you have one paramedic in existence, which is correct.

Now the “class « self” which looks mystikal really isn’t. It’s been called a classes singleton class, a metclass, an eigenclass, and the list goes on. _why gives a good description of it at http://whytheluckystiff.net/articles/seeingMetaclassesClearly.html

The beauty of a metaclass is that one exists for every class created. This is nice because you can inherit functionality created in a parent class’s metaclass, and you can redefine implementation of that functionality if you want to. If you decide to do that your changes will only affect that class and then everything else on down the hierarchy.

These is much more POLS then class variables, and it promotes better design, testability and extendability of your code.

Rails uses class variables to implement methods like cattr_reader, cattr_writer, and cattr_accessor.

class Class # :nodoc:
  def cattr_reader(*syms)
    syms.flatten.each do |sym|
      class_eval(<<-EOS, __FILE__, __LINE__)
       unless defined? @@#{sym}
          @@#{sym} = nil
       end

        def self.#{sym}
          @@#{sym}
        end

        def #{sym}
          @@#{sym}
        end
      EOS
    end
  end
# ....

Along with the use of class variables it defines a class method and an instance method. This bothers me, because it will overwrite an instance method with the same name. cattr_ which I’ve always took for “class attribute” should NOT create instance methods. What does an instance method have to do with “class attributes|variables”? This scares me and seems like a nasty side effect for someone unsuspecting.

The Opposing View

Reading up on an opposing view, click here to read

Ola Bini writes:

“Class have instance variables of themselves, these are rarely useful; they usually contribute to hard-to-find-errors. And don’t confuse them with class variables which is a totally different kind of beast.”

I disagree with this. I think modifying class variables throughout an inheritance hierarchy which may be spread out through many files and even directories (if you’re extending someone else’s library) is much harder to track down. Essentially you have to do the equivalent of:

find ./ -type f -exec grep -Hn "@@varname" {} \;

And thats just to find where it could have changed.

Since class variables are many times accessed directly it’s easy to lose the benefits of encapsulation. One debugging benefit I find with encapsulation is the ability to alter the method where something is changed, and you can check the caller’s backtrace to see where the erroneous value is coming from in your application.

I don’t see a very good use-case for why @@classvariables are useful. I much prefer class instance variables for what I consider obvious reasons… what do you folks out there think?


blog comments powered by Disqus