次のような簡単なお財布プログラムを考える。
最も簡単に作ると以下のようになる。
#!/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語で書けるが仮にこれが複雑な処理だと仮定して、 メソッドを使うように以下のように書き改めてみた。
#!/usr/bin/env ruby fund = 1000def 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つ。
2のオブジェクト指向的アプローチは次項で説明する。
1の方法を以下に示す。
#!/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
メソッド自体は一切書き換えなくてもよい。
ただ、この例のように何らかの状態(この場合残高)を保持しつつ、
入れたり出したりと行った様々な付随処理をまとめて面倒を見るかたまり(装置)
を考えるとより効率的である。
そのための考え方の一つ、オブジェクト指向を次の節で説明する。