Ruby のエラーメッセージを読み解く(初心者向け)その 2

More than 1 year has passed since last update.

この記事は,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

が出る。

上の例だと,行番号から該当箇所を探せば,slicenil を返したな,とすぐに分かるが,もし,slice と length が離れていたら,ちょっと分かりにくくなる。

def area_code_length(tel)

area_code = tel.slice(/\d+/)
# 長大なコード
area_code.length
end

現実には,slicelength が別のメソッドに分かれていたりする。つまり,想定外の 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 に定義されたメソッドはすべて呼び出せる。

putsObject クラスに 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

ローカル変数名 processorprofessor(教授)に打ち間違えてしまった。

この場合,


undefined local variable or method `professor' for main:Object (NameError)


が出る。

professor のことを「定義されていないローカル変数またはメソッド」と言っているぞ。

なんか煮え切らないな。ローカル変数かメソッドかくらい分かんねーのかよ?

分からないんである。

Ruby では,メソッド呼出しのとき,引数を囲む ( ) は省略することができる。そして,レシーバーがあらわに書かれておらず,かつ引数が存在しない場合,メソッド呼び出しなのかローカル変数参照なのか区別が付かない3

ただ,実は上の例では,professor に対する代入文が存在しないので,Ruby がこのスクリプトを解釈し終えた時点でローカル変数の可能性は排除されている。Ruby の処理系はこれをメソッド呼び出しだと決めつけているのだ。ただ,メソッドは実行時に定義されるので,メソッドが存在するかどうかは実行してみるまで分からない。

メソッド呼び出しと決めてつけているにも関わらず,「定義されていないローカル変数またはメソッド」というエラーを出すのは,ローカル変数のタイプミスかもしれないと気を利かせてのことだろう。


さいごに

その 1」が思ったほど受けなかったので,なかなか続きが書けなかった。今回もあまり良い出来じゃないな。

めげずに「その 3」も書きたいと思います。





  1. Ruby を初めて触った日,puts を見て「へえ,Ruby のメソッド名って動詞の命令形の三人称単数現在形にするのかあ」と派手な勘違いをしてしまった。「puts」は本当は「put string」から来ているぽい。 



  2. ほんとは「~などの外のコンテキストのこと」と書くべきなのだろうけど,そうすると「コンテキストって何だよ」という話になって,私にはちゃんと説明できないし,話がややこしくなる。 



  3. 厳密に言えば区別できる場合もある。メソッド名は「!」や「?」で終わる名前が許されるが,ローカル変数の名前では許されない。