ume

Ruby インスタンス変数とローカル変数の使い分け

前書き

唐突ですがインスタンス変数とローカル変数みなさんどのように使い分けているでしょうか?

もし. インスタンス変数=インスタンス変数を定義しているメソッド(initialize),以外のメソッド(accelerateなど)からでも呼び出せる変数(下記のコードのように使う)と感じた方要注意です(自分自身にも言っていますw).

class Car
  def initialize(speed)
    @speed = speed
  end

  def accelerate(amount)
    @speed += amount
  end

  def stop
    @speed = 0
  end

  def print_speed
    puts "Current speed: #{@speed}"
  end
end

上記のようなインスタンス変数(@speed)の書き方を実務など大規模な開発で書くのは可読性と保守性の観点からタブー視されているそうです。

この記事では

  • インスタンス変数とローカル変数の使い分け.

  • なぜ実務ではタブー視されいるのかに絞って記事を作成します。

⚠️ローカル変数の説明は省略させていただきます。

インスタンス変数とローカル変数の使い方の結論

インスタンス変数=処理の最初(クラス名.new(引数))から最後(インスタンスメソッドで処理を実行する)まで値が変わらない時に使う. 要は各メソッド内で使いまわすとき

ローカル変数=1つのメソッド内で一時的に使う変数、戻り値と引数を使う.

インスタンス変数.

class Person
  attr_reader :name, :blood_type

  def initialize(name, blood_type)
    @name = name
    @blood_type = blood_type
  end
end

tanaka = Person.new('田中', 'AB')
takahashi = Person.new('高橋', 'A
')

tanaka.name       #=> "田中"
tanaka.blood_type #=> "AB"

takahashi.name       #=> "高橋"
takahashi.blood_type #=> "A"

ここの@nameとかblood_typeは更新されることはないような時はインスタンス変数で定義しても良い。 変更されたとしてもメソッド毎に@nameの値が違うと言う状態にならないならインスタンス変数を使っても良い.

ローカル変数

def run
  # メソッドの戻り値をローカル変数で受け取る
  data = collect_data
  # メソッドの引数としてデータを渡す
  display_data(data)
end

def collect_data
  # メソッドの戻り値としてデータを返す
  ['a', 'b', 'c']
end

def display_data(data)
  # 引数としてデータを受け取り、そのデータを画面に出力する
  data.each do |str|
    puts str.upcase
  end
end

run

display_data実行するためだけに使いたい一時的なデータを扱うときこのようにメソッド(collect_data)を変数に格納しメソット(display_data)の引数に渡す

なぜ実務ではインスタンス変数がタブー視されいるのか。

⇨ファイル数やモデル数、テーブル数,コントローラー数、メソッド数などが個人開発と比べて桁違いに多いので変数の中身にどんなデータが入っているかをすぐ理解できないと開発効率を落とす要因になるので可読性の高い、保守性の高いコードを書くことが求められる.
↓なぜインスタンス変数が可読性を下げるのかの例

class Car
  def initialize(speed)
    @speed = speed #@speedは0
  end

  def accelerate(amount)
    @speed += amount #@speedは1
  end

  def stop(amount)
    @speed -= amount. #@speedは0
  end

  def print_speed
    puts "Current speed: #{@speed}"
  end
end
car = Car.new(0) #@speedは0
car.accelerate(1) #@speedは1
car.stop(1)#@speed0

インスタンス変数はクラス内の全てのメソッド(accelerateやstopやprint_speed)の中で使用可能です。

  def accelerate(amount)
    @speed += amount
  end

  def stop(amount)
    @speed -= amount
  end

この2つのメソッドに注目してください.
各メソッド内でインスタンス変数の中身が更新されています。「変数」という名前がついているので「中身の値が変わっても良い」と判断されるかも知れませんがメソッド毎にインスタンス変数の中身が変更されると「メソッド毎にインスタンス変数の中身にどんなデータが入っているのかを把握する手間(追跡する手間)」が発生します。 これが可読性を下げる原因になります。
要は

  def stop(amount)
    @speed -= amount #このメソッド内での@speedは何から何に値が変わった?
  end

この@speedの中身を把握するためには

car = Car.new(0) #@speedはここでは0 
car.accelerate(1) #@speedはここでは1とメソッドの中身の処理を見て@speedの中身を追跡していく手間が発生する
car.stop(1)#@speed0

今回の例のようにメソッドが数個(個人開発)だったらいいのですがメソッドが数百〜数千(実務)だったら途方に暮れると思います。

インスタンス変数はなぜ保守性を下げるのか

保守性高い=変更や修正が容易であり、長期間にわたって維持管理しやすい状態=変更修正した時に想定外の挙動をしないコード

保守性の低いコード

class Car
  def initialize(make, model)
    @make = make
    @model = model
    @engine_started = false
  end

  def start_engine
    @engine_started = true
    puts "エンジンを始動しました。"
  end


  def drive
    if @engine_started
      puts "車が走行中です。"
    else
      puts "エンジンが始動していません。"
    end
  end
end


my_car = Car.new("Toyota", "Corolla")


my_car.start_engine
my_car.drive # 結果 車が走行中です

ここで例えば

def stop_engine
    @engine_started = false
    puts "エンジンを停止しました。"
  end

というメソッドを定義しインスタンス変数の中身を更新している。

class Car
  def initialize(make, model)
    @make = make
    @model = model
    @engine_started = false
  end

  def start_engine
    @engine_started = true
    puts "エンジンを始動しました。"
  end

  def stop_engine
    @engine_started = false
    puts "エンジンを停止しました。"
  end

  def drive
    if @engine_started
      puts "車が走行中です。"
    else
      puts "エンジンが始動していません。"
    end
  end
end

# Create a car instance
my_car = Car.new("Toyota", "Corolla")


my_car.start_engine
my_car.drive # Output: 車が走行中です。

my_car.stop_engine
my_car.drive # Output: エンジンが始動していません。

.drive メソッドを2回目呼び出した時 stop_engineを追加したことでdriveメソッドが1回目実行された時はtrueの処理に入ったが2回目はfalseに入っている。ここもメソッドが数個だったらメソッドを追加してもdriveメソッドでfalseの処理に入るように変わるだろうと想像できても何千個もメソッドがあると1つの変更するとどこまで影響があるのかを把握するのは不可能に近い.

上記の2つの理由でインスタンス変数は実務では敬遠されているそうです。

参考情報

改訂版・(あなたの周りでも見かけるかもしれない)インスタンス変数の間違った使い方 #Ruby - Qiita