変数のスコープ

例題設定

次のような簡単なお財布プログラムを考える。

  1. 最初の所持金は1000円
  2. ユーザからの入力を繰り返し、その数を所持金に足し続ける(マイナスなら減る)。

最も簡単に作ると以下のようになる。

inout.rb

#!/usr/bin/env ruby
fund = 1000
while true
  printf("残高%d円です。何円出しますか(負の数は入金, 0で終了): ", fund)
  inout = gets.to_i
  break if inout == 0		# ifを文の後ろにつけると1行で書ける
  fund -= inout
end

実行例は以下のとおり。

./inout.rb
残高1000円です。何円出しますか(負の数は入金, 0で終了): 30
残高970円です。何円出しますか(負の数は入金, 0で終了): 50
残高920円です。何円出しますか(負の数は入金, 0で終了): -100
残高1020円です。何円出しますか(負の数は入金, 0で終了): [C-d]

入出金は -= の1語で書けるが仮にこれが複雑な処理だと仮定して、 メソッドを使うように以下のように書き改めてみた。

inout-bad.rb

#!/usr/bin/env ruby
fund = 1000

def purse(withdraw) # 引き出し額を受け取る fund -= withdraw # その分だけfundから引く……つもりだが end # メソッドを囲む壁があり変数名などはこれを越えられない
while true printf("残高%d円です。何円出しますか(負の数は入金, 0で終了): ", fund) inout = gets.to_i break if inout == 0 # (ifを文の後ろにつけると1行で書ける) purse(inout) end

動かすとエラーになる。

./inout-bad.rb
残高1000円です。何円出しますか(負の数は入金, 0で終了): 40
./inout-bad.rb:5:in `purse': undefined method `-' for nil
(NoMethodError)

  fund -= withdraw
       ^
        from ./inout-bad.rb:12:in `
'

これは変数fundがメソッドの中では隠蔽されて見えないことが 原因である。メソッドには、それを囲む壁のようなものがあり、その中と外では 変数やメソッドの名前は別空間で管理され、共有できない。

したがって、purseメソッドの 外で代入したfund変数は、メソッド内部では使えない。

一般的にほとんどのプログラミング言語の変数にはスコープ が存在する。スコープとはある場所で設定した変数が有効な範囲のことであり、 Rubyなど多くの言語ではメソッド(関数)の中と外では世界が違うものとして 処理される。

改良案

def purse....end 内部では、外にある fund 変数にアクセスできない。メソッドは、一つの完結した処理を定義するものであり、 その部分だけで明確に動きが分かることが重要である。

メソッドを用いて正しく処理するための解決策は2つ。

  1. 重要な変数の値をメソッドの引数と返却値でやり取りする
  2. クラス定義を用いて変数と手続きをカプセル化する

2のオブジェクト指向的アプローチは次項で説明する。

1の方法を以下に示す。

inout-byarg.rb

#!/usr/bin/env ruby
fund = 1000

def purse(total, withdraw)	# 今の総額と引き出し額を受け取る
  total - withdraw		# その分だけ総額から引く
end

while true
  printf("残高%d円です。何円出しますか(負の数は入金, 0で終了): ", fund)
  inout = gets.to_i
  break if inout == 0		# ifを文の後ろにつけると1行で書ける
  fund = purse(fund, inout)	# 総額と支出をpurseに渡し、結果を受け取る
end

この書き方で、purse メソッドの独立性は高くなる。 たとえばメソッドの外側でfund変数の名前を変えたとしても purseメソッド自体は一切書き換えなくてもよい。 ただ、この例のように何らかの状態(この場合残高)を保持しつつ、 入れたり出したりと行った様々な付随処理をまとめて面倒を見るかたまり(装置) を考えるとより効率的である。

そのための考え方の一つ、オブジェクト指向を次の節で説明する。

練習問題

次のプログラムは変数のスコープが外れていてうまく動かない。 *変数を引数で渡す形にする* ことで、これを修正せよ。

varerr.rb



本日の目次