はじめに
メソッドの外にある変数を利用したいがために、グローバル変数を使っているコードを見かけた。
今回はこういったコードに対してグローバル変数を使わずに済む方法を考える。
題材となるコード
題材となるのは以下のコードだ。
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_time
をprocess_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_Processorのrunメソッドのタイムアウトの検証
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