Code Fix

上級

クラス変数 @@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
出力: 2

@@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
出力: 2

サブクラスごとに独立した集計をしたいつもりでも、@@変数は継承元とサブクラスの間で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
出力: 2

クラスごとに独立させたい値は、@@ではなくクラスインスタンス変数(@で始まりクラス自身に属する変数)を使う必要があります。

直し方: @@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
出力: 2

「クラス変数はクラスごとに独立しているはず」という思い込みは誤りです。継承関係にある全クラスで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
出力: 2

継承元だけを操作したつもりでも、@@変数を使っている限りサブクラス側の値も一緒に動いてしまいます。

直し方: @@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パターンを実際に手で直すと、エラーメッセージのどこを読めばよいかが掴めます。

演習をはじめる

関連するエラー

広告
広告スロット(未設定)