クラス変数 @@var はサブクラスとも共有されてしまう
@@で始まるクラス変数は、そのクラスだけでなく継承したサブクラスとも1つの値を共有します。サブクラスごとに独立したカウンタなどを持たせたいつもりで@@変数を使うと、片方のクラスでの変更がもう片方にまで影響します。
なぜエラーが出ないのか
出力: 2
(エラーなし)- Rubyは何も報告しません。文法として正しいためです
出力: 2- 実際の挙動 — 期待した結果と食い違っている箇所
見つけ方- エラーが出ないので、出力を目で確かめるしかありません。この種の誤りが最も発見が遅れます
このエラーが出る典型パターン
パターン1
1 class Counter 2 @@count = 0 def self.increment @@count += 1 end def self.count @@count end ^ 3 end 4 5 class SpecialCounter < Counter 6 end 7 8 Counter.increment 9 Counter.increment 10 puts SpecialCounter.count
@@countは継承関係にあるクラス全体で1つの値を共有します。Counterへの操作がSpecialCounterのcountにまで現れてしまいます。
直し方: @@count = 0
def self.increment
@@count += 1
end
def self.count
@@count
end を def self.increment
@count = count + 1
end
def self.count
@count || 0
end にします。
パターン2
1 class Visitor 2 @@count = 0 def self.increment @@count += 1 end def self.count @@count end ^ 3 end 4 5 class VipVisitor < Visitor 6 end 7 8 Visitor.increment 9 Visitor.increment 10 puts VipVisitor.count
サブクラスごとに独立した集計をしたいつもりでも、@@変数は継承元とサブクラスの間で1つの実体を共有します。
直し方: @@count = 0
def self.increment
@@count += 1
end
def self.count
@@count
end を def self.increment
@count = count + 1
end
def self.count
@count || 0
end にします。
パターン3
1 class IdGenerator 2 @@count = 0 def self.increment @@count += 1 end def self.count @@count end ^ 3 end 4 5 class TempIdGenerator < IdGenerator 6 end 7 8 IdGenerator.increment 9 IdGenerator.increment 10 puts TempIdGenerator.count
クラスごとに独立させたい値は、@@ではなくクラスインスタンス変数(@で始まりクラス自身に属する変数)を使う必要があります。
直し方: @@count = 0
def self.increment
@@count += 1
end
def self.count
@@count
end を def self.increment
@count = count + 1
end
def self.count
@count || 0
end にします。
パターン4
1 class Logger 2 @@count = 0 def self.increment @@count += 1 end def self.count @@count end ^ 3 end 4 5 class DebugLogger < Logger 6 end 7 8 Logger.increment 9 Logger.increment 10 puts DebugLogger.count
「クラス変数はクラスごとに独立しているはず」という思い込みは誤りです。継承関係にある全クラスで1つの実体を共有します。
直し方: @@count = 0
def self.increment
@@count += 1
end
def self.count
@@count
end を def self.increment
@count = count + 1
end
def self.count
@count || 0
end にします。
パターン5
1 class RequestCounter 2 @@count = 0 def self.increment @@count += 1 end def self.count @@count end ^ 3 end 4 5 class ApiRequestCounter < RequestCounter 6 end 7 8 RequestCounter.increment 9 RequestCounter.increment 10 puts ApiRequestCounter.count
継承元だけを操作したつもりでも、@@変数を使っている限りサブクラス側の値も一緒に動いてしまいます。
直し方: @@count = 0
def self.increment
@@count += 1
end
def self.count
@@count
end を def self.increment
@count = count + 1
end
def self.count
@count || 0
end にします。
よくある誤解
「クラス変数はクラスごとに独立しているはず」という思い込みは誤りです。@@変数は継承関係にあるクラス全体で1つの実体を共有するため、独立させたい場合はクラスインスタンス変数(class << selfの中の@varやself.class.instance_variable相当)を使う必要があります。
まとめ
クラス変数 @@var はサブクラスとも共有されてしまうは上級でつまずきやすい項目です。上の5パターンを実際に手で直すと、エラーメッセージのどこを読めばよいかが掴めます。