3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

RubyAdvent Calendar 2021

Day 8

【Ruby】グローバル変数を使わずにメソッドの外のスコープにあるローカル変数を使う方法

Last updated at Posted at 2021-12-07

はじめに

メソッドの外にある変数を利用したいがために、グローバル変数を使っているコードを見かけた。
今回はこういったコードに対してグローバル変数を使わずに済む方法を考える。

題材となるコード

題材となるのは以下のコードだ。

def test_Processor_runメソッドのタイムアウトの検証
  p = Processor.new

  # 重い処理にする
  $process_time = 5
  def p.process
    sleep $process_time
  end

  start_time = Time.now
  p.run(4) # 4秒でタイムアウトする
  end_time = Time.now

  # タイムアウトになるのは重い処理にかかる時間よりも早いはず
  assert((end_time - start_time) < $process_time)
end

Processor#runに指定したタイムアウトの時間で処理が終わるかどうかを確認しているようだ。

Processor#runからはProcessor#processが呼ばれるため、そのメソッドを特異メソッドで再定義し、長時間のsleepを入れることで擬似的に重い処理を行うようにしているようだ。

$process_timeというグローバル変数が使われており、今回はこのグローバル変数を使わないようにしたい。

なぜグローバル変数を使っているのか

そもそもなぜグローバル変数を使っているのだろうか。
$process_timeprocess_timeに変更してローカル変数にしてみよう。

def test_Processor_runメソッドのタイムアウトの検証
  p = Processor.new

  # 重い処理にする
  process_time = 5 # グローバル変数をやめてローカル変数にした
  def p.process
    sleep process_time
  end

  start_time = Time.now
  p.run(4) # 4秒でタイムアウトする
  end_time = Time.now

  # タイムアウトになるのは重い処理にかかる時間よりも早いはず
  assert((end_time - start_time) < process_time)
end

これを実行すると以下のようなエラーが表示される。

undefined local variable or method `process_time' for #<Processor:0x0000000000a7b520> (NameError)

特異メソッド内で参照しているprocess_timeがメソッドとして解釈されてしまっている。
これはRubyではdefからendまでの間がスコープと解釈され、そのスコープ外のローカル変数を参照できないからだ。

以下のようなサンプルコードで確認してみよう。

my_var1 = "1"
def sample_method
  my_var2 = "2"
  local_variables
end

p local_variables
p sample_method

実行結果

[:my_var1]
[:my_var2]

Kernel#local_variablesを呼び出すことで、現在のスコープで定義されているローカル変数を確認することができる。
実行結果を見てわかる通り、sample_methodというメソッドのスコープではその外にあるmy_var1はローカル変数に存在しないことが分かる。

グローバル変数を使わずにメソッドの外にあるローカル変数を使う

defを使ってしまうとそこからスコープが始まってしまうため、ローカル変数にアクセスできない。
こういう場合はdefine_methodを使ってブロックを使ってメソッドを定義するようにする。(今回の場合は特異メソッドを定義するため、define_singleton_methodを使う。)

def test_Processorrunメソッドのタイムアウトの検証
  p = Processor.new

  # 重い処理にする
  process_time = 5 # グローバル変数をやめてローカル変数にした
  p.define_singleton_method(:process) do
    sleep process_time
  end

  start_time = Time.now
  p.run(4) # 4秒でタイムアウトする
  end_time = Time.now

  # 重い処理にかかる時間よりも早く終わるはず
  assert((end_time - start_time) < process_time)
end

こうすることで、processメソッドを定義する際はprocess_timeが定義されたスコープと同じスコープとなるため、ローカル変数のまま使用できる。

まとめ

グローバル変数を使わずにメソッドの外のスコープにあるローカル変数を使う方法についてまとめた。

グローバル変数は無闇に使い出すと、どこで変更されているのか分からない保守性の低いコードができてしまうため、グローバル変数を使わない方法があるのであればそちらを採用してほしいと思う。

それではまた。
TomoProg

参考

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?