デバッグの方法
デバッグ
デバッグとは、プログラム中の不具合を発見し、動作を正常にするまでの一連の作業のこと。
以前、ダウンロードしたファイルのうち、error_sample.rbをテキストエディタで開くと、こちらは、入力した数に対し1~100までの内で約数となっている数の個数を数えてくれるプログラムである。
実行すると入力待ちになり、そこで入力した数の約数を調べて表示する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class ErrorSample
def self.find_divi(p)
divi = 0
for num in 1..100 do
if p % num == 0
divi = divi + 1
end
show_divi(divi)
end
def self.show_divi
puts divi
end
end
p = gets.to_i
ErrorSample.find_divi(p)
|
ここで、見慣れない文法が出てきた。上記の5行目の部分は、for文
と呼ばれる構文。
for文
for
は、「while」と同じく繰り返しに関する書き方。以下のように記述する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
for num in 1..10 do
puts num
end
1
2
3
4
5
6
7
8
9
10
=> 1..10
|
上記の例では、変数numに1を代入して出力、2を代入して出力、3を・・・という処理を、10まで行なっている。1..10
は、[1,2,3,4,5,6,7,8,9,10]と書くのと同じ。
in
の後ろに書いたオブジェクトを順番にnumに入れて、endまでの処理を行う。
では、error_sample.rbを実行してみる。
~/error_question/error_sample.rbを実行。
エラーが起こる操作である。
すると、以下のようなエラーが出る。
1 2 |
$ ruby ~/error_question/error_sample.rb
/Users/koichiroabe/error_question/error_sample.rb:17: syntax error, unexpected end-of-input, expecting keyword_end
|
まずはエラー文
を読む。エラー文とは、あるファイルが正常に実行されなかった際の原因を報告してくれる文。多くの場合、エラー文はターミナル上でファイルを実行した直後の行に出力される。
今回の場合のエラー文は以下の部分。
1 2 |
/Users/koichiroabe/error_question/error_sample.rb:17: syntax error, unexpected end-of-input, expecting keyword_end
# エラー文
|
エラー文には以下の2つの重要な情報が含まれている。
-
どんな種類のエラーか
-
ファイルの何行目でエラーが起きたか
1.に関して
今回は以下の部分がエラーの種類を示す。
syntax error, unexpected end-of-input, expecting keyword_end
syntax error
は文法に関するエラーだということ。unexpected end-of-input, expecting keyword_end
は、構文を閉じるための<end>
の数がおかしいことを示している。
2.に関して
Rubyは、エラー文においてエラーが起こっている場所をちゃんと教えてくれる。
error_sample.rb:17:
、つまり、error_sample.rbの17行目でエラーが発生しているよ、ということ。しかし、今回のエラーは<end>
の数によるものである。このエラーの場合、該当の行でエラーが起きているわけではない。Rubyが、end
の必要な部分までコードを読んで、そこでエラーを検知するからである。ほとんどの場合はエラーが起こっている場所を正確に示しますが、そうでない場合もあるのでする。
以上の情報からわかることは、このファイルのどこかに<end>
の足りない構文があるということ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class ErrorSample
def self.find_divi(p)
divi = 0
for num in 1..100 do
if p % num == 0
divi = divi + 1
end
show_divi(divi)
end
def self.show_divi
puts divi
end
end
p = gets.to_i
ErrorSample.find_divi(p)
|
この中で<end>
が必要な箇所は全部で5箇所ある。まず、一番大きなくくりであるclassとメソッドに関してみてみる。
インデントが揃えてあるので、どの<end>
がクラスやメソッドに対応しているのかが明示的である。15行目がclassの閉じタグ、10行目と14行目がそれぞれのメソッドの閉じタグになっていることがわかる。
ここに関しては問題なく存在するようである。
続いて、クラスメソッド「find_divi」の中を見てみる。
1 2 3 4 5 6 7 8 9 10 |
class ErrorSample
def self.find_divi(p)
divi = 0
for num in 1..100 do
if p % num == 0
divi = divi + 1
end
show_divi(divi)
end
|
この中で<end>
が必要な箇所は2箇所ある。for文と、if文である。
しかし、<end>
は実際には1つしかない。どうやら、8行目と9行目の間に必要なfor文のための<end>
が抜けていたようである。
こちらを修正し、もう一度プログラムを実行する。
もう一度error_sample.rbを実行。
1 2 3 4 5 6 7 |
すると、またエラーが起こってしまう。
1 |
$ error_sample.rb:13:in `show_divi': wrong number of arguments (1 for 0) (ArgumentError)
|
今度はどこが悪いのか。またエラー文を読むことで解決を試みる。
まず、エラーが起きた場所だが、今回は13行目だと書かれている。
次にエラーが起きた原因だが、wrong number of arguments (1 for 0)
とある。
wrong number of arguments
wrong number of arguments とは、引数の数が違う、いう意味。(1 for 0)と後ろに書かれている場合、メソッドを呼ぶ際には1つの引数を渡しているのに、メソッドを定義している箇所では何も受け取っていないよ、という意味になる。
とりあえずエラー箇所である13行目を見てみると、一見普通にクラス定義がされているようである。
しかし、よく見ると間違いがあるのに気が付く。呼び出す箇所(10行目)では引数を書いているのに、13行目の関数定義には引数を受け取る記述がない。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class ErrorSample
def self.find_divi(p)
divi = 0
for num in 1..100 do
if p % num == 0
divi = divi + 1
end
end
show_divi(divi)
end
def self.show_divi
# => 変数diviを受け取っていない!
puts divi
end
end
p = gets.to_i
ErrorSample.find_divi(p)
|
これでは、14行目で変数diviを出力できない。
というわけで、13行目を修正する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class ErrorSample
def self.find_divi(p)
divi = 0
for num in 1..100 do
if p % num == 0
divi = divi + 1
end
end
show_divi(divi)
end
def self.show_divi(divi)
puts divi
end
end
p = gets.to_i
ErrorSample.find_divi(p)
|
これで、正常に動くようになったはず。再度ファイルを実行。
1 2 3 4 5 6 7 8 9 |
ここで、このプログラムがどう動くのかをひと通りさらってみる。
【1行目~16行目まで】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class ErrorSample
def self.find_divi(p)
divi = 0
for num in 1..100 do
if p % num == 0
divi = divi + 1
end
end
show_divi(divi)
end
def self.show_divi(divi)
puts divi
end
end
p = gets.to_i
ErrorSample.find_divi(p)
|
まず、1行目から16行目まででclassとそのクラス・メソッドを定義して、ここまでは定義を読み込むだけである。
【17行目】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class ErrorSample
def self.find_divi(p)
divi = 0
for num in 1..100 do
if p % num == 0
divi = divi + 1
end
end
show_divi(divi)
end
def self.show_divi(divi)
puts divi
end
end
p = gets.to_i
ErrorSample.find_divi(p)
|
pという変数に対して、右辺のgets.to_iの値を代入している。
getsメソッドによる入力待ちになるが、ここでは「4」と入力したことにする。
【18行目】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class ErrorSample
def self.find_divi(p)
divi = 0
for num in 1..100 do
if p % num == 0
divi = divi + 1
end
end
show_divi(divi)
end
def self.show_divi(divi)
puts divi
end
end
p = gets.to_i
ErrorSample.find_divi(p)
|
ErrorSampleクラスのクラス・メソッドであるfind_divi
メソッドが呼ばれている。この時、引数として17行目で定義したp
を利用している。処理の流れは呼び先であるfind_divi
メソッドの定義されている3行目に移動する。
【3行目】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class ErrorSample
def self.find_divi(p)
divi = 0
for num in 1..100 do
if p % num == 0
divi = divi + 1
end
end
show_divi(divi)
end
def self.show_divi(divi)
puts divi
end
end
p = gets.to_i
ErrorSample.find_divi(p)
|
3行目末尾にある(p)
は、18行目から引数として渡されてきた変数pをfind_diviメソッドの内部では「p」として扱うという宣言になる。つまり、ここでは17行目で定義したpをpとしてそのまま利用できる。
【4行目】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class ErrorSample
def self.find_divi(p)
divi = 0
for num in 1..100 do
if p % num == 0
divi = divi + 1
end
end
show_divi(divi)
end
def self.show_divi(divi)
puts divi
end
end
p = gets.to_i
ErrorSample.find_divi(p)
|
数字の0を、変数diviに代入する。
【5行目 〜 9行目】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class ErrorSample
def self.find_divi(p)
divi = 0
for num in 1..100 do
if p % num == 0
divi = divi + 1
end
end
show_divi(divi)
end
def self.show_divi(divi)
puts divi
end
end
p = gets.to_i
ErrorSample.find_divi(p)
|
for文の中身に入っていく。このfor文では9行目までの処理が100回、繰り返される。6行目にあるif文では、pでnumを割って余りが0になったらtrueになるので、pの約数の際に変数diviの数が1増える。つまりdiviは1〜100までの間にあるpの約数の数になる。
【10行目】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class ErrorSample
def self.find_divi(p)
divi = 0
for num in 1..100 do
if p % num == 0
divi = divi + 1
end
end
show_divi(divi)
end
def self.show_divi(divi)
puts divi
end
end
p = gets.to_i
ErrorSample.find_divi(p)
|
10行目では、13行目に定義されているクラスメソッドshow_diviを呼び出している。その際、変数diviを引数として利用し、処理は13行目に移る。
【13〜14行目】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class ErrorSample
def self.find_divi(p)
divi = 0
for num in 1..100 do
if p % num == 0
divi = divi + 1
end
end
show_divi(divi)
end
def self.show_divi(divi)
puts divi
end
end
p = gets.to_i
ErrorSample.find_divi(p)
|
13行目末尾で渡された変数diviをdiviとして扱うという宣言をしている。そして、14行目のputs
メソッドでdiviの値を出力してプログラムが終了する。
今回pには4を代入した。すると、約数diviは3になるはずなので、3と出力される。
問題
ダウンロードしたerror_questionフォルダの中にあるerror_question1.rb ~ error_question8.rbを実行すると、すべてエラーが起こる。先ほどのデバッグの例を参考にコードを修正し、プログラムを正常に動作させる。
以下にそれぞれのファイルが正常に動作した場合の挙動と、エラーの解決法を解説したリンクを記す。
error_question1.rb
正常な動作
1 2 3 4 5 6 7 |
解答解説
1 2 3 4 |
$ ruby error_question1.rb
#ファイルの実行
error_question1.rb:7: syntax error, unexpected tCONSTANT, expecting keyword_end
#エラー文
|
原因
5行目の閉じ「"」忘れ
修正箇所
5行目末尾に「"」を追加する
1 2 3 4 5 6 7 8 9 10 |
def myoji
name1 = "tanaka"
name2 = "suzuki"
name3 = "satoh"
puts "Member: #{name1}, #{name2}, #{name3}"
end
myoji
|
解説
syntax error, unexpected tCONSTANT, expecting keyword_end
こちらのエラーは、閉じタグが無いことが原因で起こることが圧倒的に多いエラー。今回の場合、該当ファイルを開くと色が変わっている。色が変わっている部分の前後をよく見ると、文字列の定義で利用する「"」が一つ足りないことに気づける。
error_question2.rb
正常な動作
1 2 3 4 5 6 7 |
解答解説
1 2 3 4 |
$ ruby error_question2.rb
#ファイルの実行
syntax error, unexpected end-of-input, expecting keyword_end
#エラー文
|
原因
9行目のwhile文に対応するend
が無い
修正箇所
12行目にend
を追加する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
def looper(a_sum, a_num1, a_num2)
n = 4
while n > 0
a_sum += a_num1
n -= 1
m = 2
while m > 0
a_sum += a_num2
m -= 1
end
end
puts a_sum
end
sum = 0
num1 = 10
num2 = 1
looper(sum, num1, num2)
|
解説
syntax error, unexpected end-of-input, expecting keyword_end
オーソドックスなend
忘れのエラー。このエラーを起こさないためには、end
を必要とする構文を書く際は必ず最初にendを書いてしまってからその中身を書くと良い。
error_question3.rb
正常な動作
1 2 3 4 5 6 7 |
解答解説
1 2 3 4 |
$ ruby error_question3.rb
#ファイルの実行
error_question3.rb:6:in `<main>': undefined local variable or method `diametar' for main:Object (NameError)
#エラー文
|
原因
5行目の変数名が間違っている
修正箇所
5行目の変数名を修正
1 2 3 4 5 6 7 8 9 |
解説
undefined local variable or method 'diametar' for main:Object (NameError)
エラーの最後に(NameError)とある。これは、変数名などが間違っていることを示唆している。実際に間違っている箇所まで教えてくれるかは場合によってまちまちだが、エラーの可能性として誤字脱字もあることは念頭に置いておく。
error_question4.rb
正常な動作
1 2 3 4 5 6 7 8 9 10 11 12 13 |
解答解説
1 2 3 4 5 6 7 8 9 10 11 12 13 |
$ ruby error_question4.rb
#ファイルの実行
0か1を入力してください
#0を入力
0
あなたの入力した数字は0です
error_question4.rb:5:in `ret': undefined method `elseif' for main:Object (NoMethodError)
#結果
0か1を入力してください
1
#1を入力
あなたの入力した数字は0でも1でもありません
#結果
|
原因
5行目、elsifとすべきところをelseifとしてしまっている
修正箇所
elseif → elsif
1 2 3 4 5 6 7 8 9 10 11 12 13 |
def ret(a_input)
if a_input == 0
puts "あなたの入力した数字は0です"
elsif a_input == 1
puts "あなたの入力した数字は1です"
else
puts "あなたの入力した数字は0でも1でもありません"
end
end
puts "0か1を入力してください"
input = gets.to_i
ret(input)
|
解説
undefined method 'elseif' for main:Object (NoMethodError)
構文の中の文法を間違えていると、NoMethodError
が出ることが多い。elsifは特に間違いやすいので、注意。
error_question5.rb
正常な動作
1 2 3 4 5 6 7 8 |
解答解説
1 2 3 4 |
$ ruby error_question5.rb
#ファイルの実行
error_question5.rb:4:in `+': String can't be coerced into Fixnum (TypeError)
#エラー文
|
原因
hashのscoreというキーのバリューの数字が文字列型になっており、4行目のsum = sum + a_score[:score]のところで数字+文字列の計算になっている
修正箇所
hash内の文字列オブジェクトで置かれている数字を、数字オブジェクトに変更。
または、数字の計算の時に文字列オブジェクトを数字オブジェクトに変換。
解答例1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
def cal(a_scorebox)
sum = 0
a_scorebox.each do |a_score|
sum = sum + a_score[:score]
end
puts "合計点は#{sum}です"
ave = sum / a_scorebox.length
puts "平均点は#{ave}です"
end
scorebox = []
scorebox << {name: 'tanaka', score: 90}
scorebox << {name: 'suzuki', score: 70}
scorebox << {name: 'satoh', score: 50}
cal(scorebox)
|
解答例2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
def cal(a_scorebox)
sum = 0
a_scorebox.each do |a_score|
sum = sum + a_score[:score].to_i
end
puts "合計点は#{sum}です"
ave = sum / a_scorebox.length
puts "平均点は#{ave}です"
end
scorebox = []
scorebox << {name: 'tanaka', score: '90'}
scorebox << {name: 'suzuki', score: '70'}
scorebox << {name: 'satoh', score: '50'}
cal(scorebox)
|
解説
error_question5.rb:4:in '+': String can't be coerced into Fixnum (TypeError)
こちらのエラーも、プログラミングを始めて間もないころには起こしがちである。しかし、エラー文をよく読めば何が間違っているのかわかりやすいエラーの一つでもある。
error_question6.rb
正常な動作
1 2 3 4 5 6 7 |
解答解説
1 2 3 4 |
原因
定義していないsumという変数を利用しようとしている
修正箇所
sum_aveメソッドの中で、変数sumを定義する
1 2 3 4 5 6 7 8 9 10 11 |
def sum_ave(a_ary)
sum = 0
a_ary.each do |num|
sum += num
end
ave = sum / a_ary.length
puts "合計は#{sum} 平均は#{ave}"
end
ary = [5,8,3,10,2]
sum_ave(ary)
|
解説
undefined method '+' for nil:NilClass
実は、+
もメソッドの一種である。
for nil:NilClass
というエラーが出た場合、すぐに該当するレシーバー(メソッドを呼んだオブジェクト)の状態を確かめる。レシーバーがnilになってしまっている可能性が非常に高い。
error_question7.rb
正常な動作
1 2 3 4 5 6 7 |
解答解説
1 2 3 4 |
原因
修正箇所
5行目を「while <= 3」に変更
1 2 3 4 5 6 7 8 9 |
ary = [1,2,3,4]
sum = 0
i = 0
while i <= 3 do
sum += ary[i]
i += 1
end
puts sum
|
解説
nil can't be coerced into Fixnum (TypeError)
今回のエラーは、エラー文だけではなかなか気が付きづらいので、まずはどこでこのエラーが起こっているか把握する。そのために、p
というメソッドを利用する。
pメソッド
pメソッドはputsメソッドと似ている。自身の右に書かれたインスタンスに関する出力するが、putsメソッドが返り値を出力するのに対し、pメソッドはそのままの形で出力する。以下に配列オブジェクトをそれぞれのメソッドで出力した例を示す。
【例】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
putsで配列オブジェクトを出力すると中身のオブジェクトを順番に1行ずつ出力する。対してpの場合は、そのオブジェクトの形のまま出力されているのがわかる。
変数の形がどうなっているのかを確かめたい場合は、pメソッドを利用すると良い。
このpメソッドを、問題のコードの以下の部分に挿入する。
1 2 3 4 5 6 7 8 9 10 |
ary = [1,2,3,4]
sum = 0
i = 0
while i <= 4 do
p ary[i]
sum += ary[i]
i += 1
end
puts sum
|
そしてもう一度実行すると、以下のような実行結果になる。
1 2 3 4 5 6 |
5行目から始まるwhileの繰り返しの中にpメソッドが入っているので、繰り返しの回数分出力が行われている。これによりわかることは、whileの繰り返し中にある6行目の出力のうち、5回目がnilになっているということである。
配列オブジェクトに格納された要素の取り出し方は、1番目なら[0], 2番目なら[1],
というように、取り出したい順番 - 1の数字を[]に入れる。また、この問題のコードでは、while文の中でi
という変数の数字が増えていき、そのi
をary[i - 1]として配列の中身を取り出すことに利用している。そして、iは1~4の値を取る。するとiが4になった瞬間、配列オブジェクトaryから5番目の要素を取得する、という意味になり、配列オブジェクトaryには5番目の要素が無いためにおかしなことになっているということのようである。
ここで新たに得た重要な知識としては、配列の中から存在しない順番のインスタンスを取得しようとするとその値はnilになるということ。
原因がわかったので、配列から要素を取り出す際に利用する部分を修正する。配列の1番目の要素が取得できるように、iにははじめ0を代入しておく。そして、whileの繰り返しの条件も「iが3以下の時」に変更する。
これで、望む結果が得られるはず。デバッグ用に書いた6行目を消すことを忘れないようにする。
error_question8.rb
正常な動作
1 2 3 4 5 6 7 |
解答解説
1 2 3 4 |
$ ruby error_question8.rb
#ファイルの実行
error_question8.rb:1:in `cal': wrong number of arguments (2 for 4) (ArgumentError)
#エラー文
|
原因
1行目の引数の数と、8行目の引数の数が一致しない
修正箇所
1 2 3 4 5 6 7 8 9 10 11 |
def cal(a_num1, a_num2, a_num3, a_num4)
sum = a_num1 + a_num2 + a_num3 + a_num4
return sum
end
num1 = 1
num2 = 2
num3 = 3
num4 = 4
sum = cal(num1, num2, num3, num4)
puts "合計は#{sum}です"
|
解説
wrong number of arguments (2 for 4) (ArgumentError)
メソッドを呼び出している部分の引数と、メソッドを定義している部分の引数の数は必ず一致していなければいけない。(2 for 4)というのは、メソッドが4つの引数を求めているのに2つしか渡せていないですよ、という意味になる。