Pregunta

I had a talk with someone on the #ruby-lang channel the other day about @@class_variables. It all started when a user asked what's the best way to keep track of connected users to his server (I slightly simplified it, but that's the gist of it).

So, I suggested:

class User
  @@list = {} #assuming he wants to look up users by some type of ID

  def initialize(user_id, ...)
    @@list[user_id] = self
    #...
  end
end

However, somone said that the use of global state here is considered bad practice.

I understand why a global state is bad for something that relies on multiple back-ends, because the global part of global state stops being so global, and becomes localised to that one back-end. Or that it interferes with dependency injection.

I really can't think of any other reason why this is bad, though. And, if concurrency ever becomes an issue (there's a need for multiple back-ends), then we can update the code to use Redis (or something similar).

Also, I found this question on programmers.sxc, but it doesn't help me understand why the above code is considered so bad? Also, what would be the alternative?

¿Fue útil?

Solución

Why is it bad?

Global state is bad for a couple of reasons you didn't mention:

  1. It's Unreliable: Because anything in your program, including third party code, can change the variable, you can never depend on something being there one second after you put it in.
  2. It breaks encapsulation: If have a global list of users, other parts of the program should have to go through the User class to access it. Otherwise, everyone is manipulating data directly, which is a bad idea.
  3. Hard to change: If you find out that your global state needs to be, say, an array instead of a hash, bad luck. You have to change every part of the code that uses it because there are no accessors to change.
  4. This one is a little more abstract, but still: Function Purity: When you introduce global state, many previously pure functions become impure. This is bad in general, because compiled languages like C can heavily optimize pure functions, and bad in Ruby because it makes methods much harder to test.

The specific form of global state you mention, @@ variables, is also bad for a Ruby specific reason:

  • It has weird inheritance semantics: @@ variables in Ruby are shared between a class and all it's subclasses. So even though you think that it is encapsulated, it isn't. This is especially bad if someone subclasses your User class, and declares a variable @@list for storing unrelated data. Ruby won't complain, and the state of the entire User class tree is compromised.

What else can be done?

  • Dependancy Injection: Just pass around the data to whoever needs it without ever maintaining it globally.
  • Class Encapsulation: If you need global state, have a class maintain it with setters and getters. This invalidates points 2 and 3, along with the @@ probelm because it uses class @ variables. Example:

    class User
      class << self
        @list = {}
        def add_user(uid, user)
          #Do validation here
          @list[uid] = user
        end
        #More methods like add_user
      end
      def initialize(user_id, ...)
        User.add_user(user_id, self)
      end
    end
    
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top