为什么全局状态不好?

omn*_*nse 2 ruby global-state

前几天我在 #ruby-lang 频道上与某人谈论了@@class_variables. 这一切都始于用户询问跟踪连接到其服务器的用户的最佳方式是什么(我稍微简化了它,但这就是要点)。

所以,我建议:

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
Run Code Online (Sandbox Code Playgroud)

然而,有人说在这里使用全局状态被认为是不好的做法。

我理解为什么全局状态对于依赖多个后端的东西来说是不好的,因为全局状态的全局部分不再那么全局,而是变得本地化到那个后端。或者它会干扰依赖注入。

不过,我真的想不出任何其他原因说明这很糟糕。而且,如果并发性成为问题(需要多个后端),那么我们可以更新代码以使用 Redis(或类似的东西)。

另外,我在programmers.sxc上发现了这个问题,但这并不能帮助我理解为什么上面的代码被认为如此糟糕?另外,还有什么选择呢?

Lin*_*ios 5

为什么不好?

由于您没有提到的几个原因,全局状态很糟糕:

  1. 它是不可靠的:因为程序中的任何内容(包括第三方代码)都可以更改变量,因此您永远不能依赖于将其放入后一秒就存在的东西。
  2. 它破坏了封装:如果有一个全局用户列表,程序的其他部分应该必须通过 User 类来访问它。否则,每个人都直接操纵数据,这是一个坏主意。
  3. 很难改变:如果你发现你的全局状态需要是一个数组而不是一个散列,那么你运气不好。您必须更改使用它的代码的每个部分,因为没有需要更改的访问器。
  4. 这个有点抽象,但仍然是:函数纯度:当引入全局状态时,许多以前的纯函数变得不纯。一般来说,这很糟糕,因为像 C 这样的编译语言可以大量优化纯函数,而在 Ruby 中也很糟糕,因为它使方法更难测试。

您提到的全局状态的特定形式(@@变量)对于 Ruby 特定的原因也是不好的:

  • 它有奇怪的继承语义@@Ruby 中的变量在类及其所有子类之间共享。因此,即使您认为它是封装的,但事实并非如此。如果有人对您的类进行子类化,并声明一个用于存储不相关数据的User变量,这尤其糟糕。@@listRuby 不会抱怨,整个 User 类树的状态都会受到损害。

还可以做什么?

  • 依赖注入:只需将数据传递给需要它的人,而无需全局维护它。
  • 类封装:如果您需要全局状态,请让一个类使用 setter 和 getter 来维护它。这使得第 2 点和第 3 点以及@@探针无效,因为它使用了类@变量。例子:

    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
    
    Run Code Online (Sandbox Code Playgroud)