クラス変数の甘い罠

 最近クラス変数を使ったプログラムで大きくハマったのでメモ。
 クラス変数にアクセスする方法としては、@@の修飾子を使ったアクセス方法とclass_variable_set, class_variable_getを使ったアクセスがあるのだが、この2つのアクセスの意味が微妙に異なることを最近まで知らずに、大きくハマってしまった。

 下記のプログラムでは、クラス内でクラス変数にアクセスするものと、モジュール内でクラス変数にアクセスするものを用意して、クラスからモジュールをincludeするようなプログラムを書く。

 module C 
   def self.included(mod) 
     mod.class_eval do 
       @@c = 5 
       class_variable_set(:"@@cc", 6) 
     end 
   end 
 end 
  
 class A 
   include C 
   @@a = 1 
   class_variable_set(:"@@aa", 2) 
 end 
  
 p A.class_variables 
 p C.class_variables 

 この時の実行結果は以下のようになる。


[:@@cc, :@@a, :@@aa]
[:@@c]

 これはclass_variable_setによるアクセスは呼び出されたレシーバのクラスに紐付くのに対して、@@によるアクセスは記述されたクラス・モジュールに紐付くからだと思われる。
 つまり、A.class_eval{ @@c }はNameErrorとなってしまう。

 しかも、上記の挙動は1.9.2のものだが、ruby 1.8.7ではまた違う挙動になることが分かった。こっちの場合は、更に良く分からない……。


["@@cc", "@@aa", "@@a", "@@c"]
["@@c"]

 有効な解決策としては、クラス変数を使わず、クラスのコンテキストに紐付くクラスインスタンス変数を使うのが一般的には良いとされている。プログラム上パッと見分けがつく、クラス変数の方が扱いやすいんだけど、どうにかならないものか……。