この記事は,Ruby プログラミングの超初心者に向けて,とくに英語に苦手意識を持つ人に向けて,エラーメッセージの読み取り方の最初の一歩を解説しようとする試みの第 2 回目。
過去の記事はこちら
「その 1」では,スクリプトを解釈するときに出る SyntaxError(構文エラー)を取り上げた。今回は実行時エラーである NoMethodError と NameError。
NoMethodError
たとえば "Ruby"
という文字列を小文字化したものを表示しようとして,
puts "Ruby".dawncase
としたら,
NoMethodError: undefined method `dawncase' for "Ruby":String
というエラーが出た。down
を誤って dawn
(夜明け)と打ってしまったんである。
NoMethodError は文字どおり「メソッドがねえよ」というエラーだ。
そのあとの,「"Ruby":String
」は「String
クラスのインスタンスである "Ruby"
」という意味だ。
全体を意訳すると
"Ruby"
という String オブジェクトにdawncase
というメソッドは定義されていない。
となる。
ん? なぜ
String クラスには
dawncase
というメソッドは定義されていない。
と言わないのだろう?
それは "Ruby"
という文字列のメソッドは String クラスに定義されているとは限らないからだ。String の上位クラスである Object クラスかもしれないし,String に include されている Comparable モジュールかもしれないし,"Ruby"
の特異クラスに定義されているかもしれないのだ。(それ以外の可能性もある)
あちこち駆けずり回ってそういう名前のメソッドを探したけど(これをメソッド探索という;本当は決まった順序で淡々と見ていくだけ),どこにも無かったぜっ!というのが NoMethodError だ。
ともかく,大事なことは,エラーメッセージの中にレシーバーの情報が書かれていることだ。少なくともレシーバーのクラスは分かるし,レシーバーを Object#inspect
で文字列化したものも見せてくれる。
これはバグを探る大きなヒントになる。
NoMethodError はメソッド名の書き間違いでも起こるが,レシーバーが考えてたのと違う場合にも起こるからだ。
nil
の NoMethodError
レシーバー情報があまり役に立たない場合もある。典型的なのが nil
についての NoMethodError だ。
Ruby の組込みのメソッドの中には,〈ハズレのときに nil
を返す〉ようなものがけっこうある。
たとえば,String#slice
は,与えた正規表現にマッチする最初の部分文字列を返すメソッドだが,マッチする箇所が無かった場合は nil
を返す。
p "03-1234-5678".slice(/\d+/) #=> "03"
p "foo".slice(/\d+/) #=> nil
これが落とし穴になる。
あなたは市外局番の長さ(桁数)を返す次のようなメソッドを書いた。
def area_code_length(tel)
tel.slice(/\d+/).length
end
引数は必ず電話番号を表す文字列という前提だったが,想定外の事情により,空文字列が渡るケースがあった。すると,slice
が返す nil
に .length
しようとして
undefined method `length' for nil:NilClass
が出る。
上の例だと,行番号から該当箇所を探せば,slice
が nil
を返したな,とすぐに分かるが,もし,slice と length が離れていたら,ちょっと分かりにくくなる。
def area_code_length(tel)
area_code = tel.slice(/\d+/)
# 長大なコード
area_code.length
end
現実には,slice
と length
が別のメソッドに分かれていたりする。つまり,想定外の nil
を返しちゃった箇所と,そのオブジェクトで何かしようとした箇所が,コードをあっちこっち辿っていかないとつながらない,ということがあるのだ。
レシーバーがあらわに書かれていない場合
別の例を出そう。
円周率を表示しようとして,
put Math::PI
としたら,
NoMethodError: undefined method `put' for main:Object
というエラーが出た。puts
の「s」が抜けていたんである1。
レシーバーは Object
クラスのインスタンスである main
だ,と言っているが,main
て何だ?
main は「トップレベル」における self
のこと。
トップレベルというのは,平たく言えばスクリプトのいちばん上位,つまりクラス定義・モジュール定義・メソッド定義やブロックなどの外のこと2。
Ruby のメソッドには必ずレシーバーがある。レシーバーをあらわに書かずにメソッドを呼び出したときは self
がレシーバーとなる。
クラス定義の中ではそのクラス自身が self
だし,メソッド定義の中ではそのメソッドのレシーバーが self
だ。
main は Object のインスタンスであるため,Object に定義されたメソッドはすべて呼び出せる。
puts
は Object
クラスに include されている Kernel
モジュールで定義されているので,どんな場所で
puts Math::PI
とやっても,標準出力に円周率が表示されることになるのだ。
Ruby 2.3 なら「ひょっとしてこれ?」が出る
喜べ。Ruby 2.3 には did_you_mean という gem が組み込まれている。これの何が嬉しいかというと,NoMethodError のとき,「ひょっとしてこれ?」と正しいメソッド名の候補を挙げてくれるのだ。
先ほどの
put Math::PI
の場合,
NoMethodError: undefined method `put' for main:Object
Did you mean? puts
putc
と出る。
よしっ! 今すぐ Ruby 2.3 に移行だ!
(あ-,Windows の人はまだいろいろ障害があると思うけどね)
NameError
次は NameError 行きましょ。
processor = "Z80"
puts professor
ローカル変数名 processor
を professor
(教授)に打ち間違えてしまった。
この場合,
undefined local variable or method `professor' for main:Object (NameError)
が出る。
professor
のことを「定義されていないローカル変数またはメソッド」と言っているぞ。
なんか煮え切らないな。ローカル変数かメソッドかくらい分かんねーのかよ?
分からないんである。
Ruby では,メソッド呼出しのとき,引数を囲む ( )
は省略することができる。そして,レシーバーがあらわに書かれておらず,かつ引数が存在しない場合,メソッド呼び出しなのかローカル変数参照なのか区別が付かない3。
ただ,実は上の例では,professor
に対する代入が存在しないので,Ruby がこのスクリプトを解釈し終えた時点でローカル変数の可能性は排除されている。Ruby の処理系はこれをメソッド呼び出しだと決めつけているのだ。ただ,メソッドは実行時に定義されるので,メソッドが存在するかどうかは実行してみるまで分からない。
メソッド呼び出しと決めてつけているにも関わらず,「定義されていないローカル変数またはメソッド」というエラーを出すのは,ローカル変数のタイプミスかもしれないと気を利かせてのことだろう。
さいごに
「その 1」が思ったほど受けなかったので,なかなか続きが書けなかった。今回もあまり良い出来じゃないな。
めげずに「その 3」も書きたいと思います。