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

この記事は，Ruby プログラミングの超初心者に向けて，とくに英語に苦手意識を持つ人に向けて，エラーメッセージの読み取り方の最初の一歩を解説しようとする試みの第 2 回目。

過去の記事はこちら

* [Ruby のエラーメッセージを読み解く（初心者向け）その 1](http://qiita.com/scivola/items/3017068a354892b239f4)

「その 1」では，スクリプトを解釈するときに出る SyntaxError（構文エラー）を取り上げた。今回は実行時エラーである NoMethodError と NameError。

# NoMethodError

たとえば `"Ruby"` という文字列を小文字化したものを表示しようとして，

```rb
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` を返す。

```rb
p "03-1234-5678".slice(/\d+/) #=> "03"

p "foo".slice(/\d+/) #=> nil
```

これが落とし穴になる。

あなたは市外局番の長さ（桁数）を返す次のようなメソッドを書いた。

```rb
def area_code_length(tel)
  tel.slice(/\d+/).length
end
```

引数は必ず電話番号を表す文字列という前提だったが，想定外の事情により，空文字列が渡るケースがあった。すると，`slice` が返す `nil` に `.length` しようとして

```
undefined method `length' for nil:NilClass
```

が出る。

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

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

現実には，`slice` と `length` が別のメソッドに分かれていたりする。つまり，想定外の `nil` を返しちゃった箇所と，そのオブジェクトで何かしようとした箇所が，コードをあっちこっち辿っていかないとつながらない，ということがあるのだ。


## レシーバーがあらわに書かれていない場合

別の例を出そう。

円周率を表示しようとして，

```rb
put Math::PI
```

としたら，

> NoMethodError: undefined method `put' for main:Object

というエラーが出た。`puts` の「s」が抜けていたんである[^puts]。

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

レシーバーは `Object` クラスのインスタンスである `main` だ，と言っているが，`main` て何だ？

main は「トップレベル」における `self` のこと。

トップレベルというのは，平たく言えばスクリプトのいちばん上位，つまりクラス定義・モジュール定義・メソッド定義やブロックなどの**外**のこと[^toplevel]。

[^toplevel]: ほんとは「～などの外の**コンテキスト**のこと」と書くべきなのだろうけど，そうすると「コンテキストって何だよ」という話になって，私にはちゃんと説明できないし，話がややこしくなる。


Ruby のメソッドには必ずレシーバーがある。レシーバーをあらわに書かずにメソッドを呼び出したときは `self` がレシーバーとなる。
クラス定義の中ではそのクラス自身が `self` だし，メソッド定義の中ではそのメソッドのレシーバーが `self` だ。

main は Object のインスタンスであるため，Object に定義されたメソッドはすべて呼び出せる。

`puts` は `Object` クラスに include されている `Kernel` モジュールで定義されているので，どんな場所で

```rb
puts Math::PI
```

とやっても，標準出力に円周率が表示されることになるのだ。

## Ruby 2.3 なら「ひょっとしてこれ？」が出る

喜べ。Ruby 2.3 には did_you_mean という gem が組み込まれている。これの何が嬉しいかというと，NoMethodError のとき，「ひょっとしてこれ？」と正しいメソッド名の候補を挙げてくれるのだ。

先ほどの

```rb
put Math::PI
```

の場合，

```
NoMethodError: undefined method `put' for main:Object
Did you mean?  puts
               putc
```

と出る。

よしっ！　今すぐ Ruby 2.3 に移行だ！
（あ－，Windows の人はまだいろいろ障害があると思うけどね）


# NameError

次は NameError 行きましょ。

```rb
processor = "Z80"

puts professor
```

ローカル変数名 `processor` を `professor`（教授）に打ち間違えてしまった。

この場合，

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

が出る。

`professor` のことを「定義されていないローカル変数**または**メソッド」と言っているぞ。
なんか煮え切らないな。ローカル変数かメソッドかくらい分かんねーのかよ？

分からないんである。

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

[^localvar-or-method-call]: 厳密に言えば区別できる場合もある。メソッド名は「`!`」や「`?`」で終わる名前が許されるが，ローカル変数の名前では許されない。

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

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

# さいごに

「[その 1](http://qiita.com/scivola/items/3017068a354892b239f4)」が思ったほど受けなかったので，なかなか続きが書けなかった。今回もあまり良い出来じゃないな。

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