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

プログラムのバグでエラーが出るとき，**エラーメッセージがバグを見つけるための大きなヒントになる**はずだ。

ところが初心者はメッセージを読み解こうとせずに，ただ行番号からコードの該当箇所を探し，そこをじーっと眺めてバグを探そうとしがち（私もそうだった）。

どうしてか。
一つには〈エラーメッセージの読み方が分からない〉ということがあるのだろう。
Ruby のエラーメッセージは必ずしも**分かりやすくはない**。
それに，相手がナニ人だろうとかまわず**英語で出る**。私のように英語が苦手だとそれだけで萎えてしまう。「ノー，アイ ドント イングリッシュ！」と叫びたくなる。


この記事は Ruby プログラミングの超初心者に向けて，とくに英語に苦手意識を持つ人に向けて，エラーメッセージの読み取り方の最初の一歩を解説しようと試みたものだ。[**本当はもっとちゃんと分かっている人に書いてほしい**](https://github.com/rubima/rubima-support/issues/8) のだが，誰も書いてくれないので[^書いてくれない]。

[^書いてくれない]: 本当に誰も書いてくれてないのか確かめてはいない。

また，デバッグのコツなども少し書いてみた。

タイトルに「その 1」とあるとおり，連載になる予定だ。初回は Syntax Error 行ってみよう！

（2020-07-23 追記：エラーや警告のメッセージを Ruby 2.7.1 に合わせて書き直した）

# Syntax Error

syntax [síntæks] は構文のこと。言語学で言う「構文」は，文法の一部分であって，文の組み立ての仕組みのこと。つまり，語がどのように組み合わさって文を作るか，という原理のことだ[^文法]。

[^文法]: 言語学における文法論には，構文を調べる「構文論」（統語論ともいう）のほかに，音の仕組みを調べる「音韻論」，単語の成り立ちを調べる「形態論」などがある。よく知らんけど。

プログラミング言語における構文も，識別子や演算子，文字列・数値などのリテラル，括弧類などの記号，といったものをどう並べてプログラムを組み立てるかという規則のことだと思っていい（たぶん）。

以下の Ruby スクリプト（？）は Syntax Error，つまり構文エラーとなる。

```rb
* 3
```

Ruby の構文上あり得ないから。

では次節から典型的な Syntax Error の例を見ていこう。

## end 抜けによるもの

次のスクリプトは Syntax Error となる。

```rb:sample.rb
x = 3
unless x.zero?
  puts x
endd
```

`end` と打ったつもりが，キーを叩きすぎて `endd` になってしまった。

エラーメッセージはこうなる：

```
sample.rb:4: syntax error, unexpected end-of-input, expecting `end'
```

最初なので丁寧に見ていく。

`sample.rb:4` は，`sample.rb` というファイルの第 4 行で問題が見出されたことを意味している。

`syntax error` は，既に見たように構文エラーを意味する。

問題は次だ：

```
unexpected end-of-input
```

unexpected とは「予期せぬ」「期待せぬ」という意味の形容詞。end-of-input は「input の終わり（終端）」ということだが，この input は今の場合，スクリプトファイルのこと。
意訳すれば「いきなりスクリプトの終わりに来ちゃったじゃねえか！」と怒っているんである。

その次：

```
expecting `end'
```

「`end' を期待してたんだが」と言っている。

Ruby の構文エラーには，このように

> unexpected XXX, expecting YYY

という形のものがよくあるので，英語が超苦手な人も覚えておいてくれ。

ここまでを総合すると，

> sample.rb の第 4 行で，スクリプトの終わりに来ちまったぜ。どこかに end があるはずだったんだが。

ということになろう。これでエラーメッセージの内容は分かった。

Ruby の構文規則として，`unless` があるならそれと対をなす `end` が必ずある。その規則を破っているから「構文エラー」なのだ。

### もっとストレートにバグを指摘してくれないか？

さきほどのようなエラーメッセージは，ちょっと不親切じゃないだろうか。やっぱり

> end を期待したんだが endd があったぜ。

と言ってほしくはないか。

しかし，**それは無理な注文**というもの。
Ruby の文法として，この「endd」は〈`endd` というメソッドの呼び出し〉と解釈されるからだ。
（詳しく言えば，もう一つの可能性として，〈`endd` というローカル変数の参照〉も考えられるのだが，ここ以前に `endd` への代入が存在しないことにより，その線は無いことになる）

もちろん `endd` なんてメソッドは無い（定義してない）んだが，いまの場合，「そんなメソッドねぇよ」というエラーは出ない。
メソッドが無いことが分かるのは，この第 4 行を**実行する時点**である。
しかし構文エラーは，Ruby の処理系が sample.rb を読み込んで**解釈する時点**で出るのだ。つまり，実行が始まる前に終了しちまう，ということ。

### 楽勝？

長々と書いたけど，あまり難しくはなかったね。
でも，この例だけ見て「end 抜けのバグなんて**楽勝**～」などと思ってはいけない。

## もうちょっと複雑な end 抜け

次のような例はどうだろう。

```rb
x = 3
unless x < 2
  case x
  when 4, 6
    # なんとか
  else
    if x % 7 == 0
      # どうの
    else
      # こうの
    endd
  end
  unless x.odd?
    # ほにゃ
  end
end
```

またもや一箇所で `end` を `endd` と打ってしまった（「こうの」の次の行）。

エラーメッセージはこうなる：

```
sample.rb:16: syntax error, unexpected end-of-input, expecting `end'
```

メッセージは，さきほどと行番号（スクリプトの最終行）しか違っていない。

その行番号だが，打ち間違えたのは第 11 行（「こうの」の次行）なのに，スクリプトの終端（第 16 行）まで行ってエラーを出している。この「16」という情報は**デバッグの役に立たない**。

なぜこうなるのか。

既に見たように，「`endd`」自体はメソッド呼び出しと解釈されるので，これ自体は構文エラーを引き起こさない。

そして，`endd` の次の `end`（本来は `case` の `end`）が `if` に対応する `end` と見なされてしまう。
同様に，最終行の `end`（本来は `unless` の `end`）が `case` に対応する `end` と見なされてしまう。
結局，Ruby 処理系にしてみれば，「**`unless` に対応する `end` が見つからない**」ということになる。

間違えた場所とエラーの場所がここまで離れると，バグの箇所を特定するのが難しくなってくる。

上の例はまだ単純なほうだ。現実には制御構造やブロックがもっと深く入れ子になっていて，行数も多かったりする場合があるだろう。

**たかが `end` 抜け，されど `end` 抜け**，である。
`end` 抜けがどれほど Ruby 初心者を苦しめてきたことか。彼ら／彼女らの流した涙を `inject(:+)` すれば[^sum]琵琶湖の水量にも匹敵するのではないか。

[^sum]: Ruby 2.4 以降なら `sum` と書くべき。

### （まつもとさんを）うらみ・ます

こんな恨み言が聞こえてくる。

> まつもとさんが `if` でも `while` でもブロックでも，何でもかんでも `end` にしたのが悪いんだ。`endif` や `endwhile` にしてくれていれば良かったのにぃ。

ま，かつてはそういう議論もあったのだが，いまはあまり聞かない（気がする）。


## end 抜け探しの救世主 -w オプション

さきほどの例では，`end` を `endd` と打ってしまったために，`if` や `case` に対応する `end` が一つずつずれて解釈された。
その解釈だと，**制御構造のネスト（入れ子）の深さとインデントの深さが対応しない**わけだが，**Ruby の処理系はそういうことに頓着しない**。

そこで，だ。
Ruby 1.9 で `-w` という起動オプションが導入された。

このオプションは

> turn warnings on for your script

と説明されている。ええと，直訳すると「オマエのスクリプトのために警告を ON にする」かな。要するに，これを付ければいろいろ警告を出してくれるようになるわけだ。

早速さきほどの例で，

```bsh
ruby -w sample.rb
```

と実行してみると，

```
sample.rb:12: warning: mismatched indentations at 'end' with 'else' at 9
sample.rb:16: warning: mismatched indentations at 'end' with 'case' at 3
sample.rb:16: syntax error, unexpected end-of-input, expecting `end'
```

のように，エラーの前に二つの warning が出た。
warning [wɔ́ːrniŋ] とは警告のこと。エラーではないけど「マズくね？」と言ってくれているんである。

一つ目の警告を見てみよう。

`mismatched indentations at 'end'` は「`end` における合ってないインデント」だ。
`'else' at 9` は，「第 9 行の `else`」。
間にある `with` は何かというと，「mismatch with X」で「X と釣り合ってない」というときの with だ。

まとめると，

> sample.rb の第 12 行で，`end` のインデントが第 9 行の `else` と合ってない。

となる。

スゴいぞ！　これで間違えた箇所が特定しやすくなった。上の例でも，実際に間違えた箇所（第11行）と警告が出た箇所（第12行）は違うのだが，それでもこの警告は非常に有益と言えるだろう。

普段から正確なインデントを心がけておいてこそ，この警告が生きてくる。

ただ，ブロックの `end` の位置は見てくれないみたいだ。たとえばこれ

```rb
3.times do
  # なんとか
                   end
```

はスルーされる。どうやら，インデントをチェックするのは `if` や `while` のような制御構造だけのようだ。

## こまめに構文チェックを

起動オプション `-c` を付けると，スクリプトを（実行はせずに）構文チェックしてくれる。そのとき `-w` も付けておけば警告も出してくれる。

つまり，

```bsh
ruby -c -w sample.rb
```

とすればいい。

プログラムを書き終わってから実行してエラーに遭遇するよりも，書いている途中でときどきチェックしたほうがいいと思う。

ただ，いちいちコマンドラインで `ruby -c -w sample.rb` とやるのは面倒だ。

プログラマーが使うテキストエディターにはたいてい，今開いているファイルを外部プログラムでモニョモニョする，という機能がついているから，それを使えばいい。

EmEditor でやる方法は拙文「[EmEditor で今書いている Ruby スクリプトの文法をチェック](http://qiita.com/scivola/items/2427c0f3bd0114c262f7)」を参照。

ただのテキストエディターでなくいわゆる統合開発環境（IDE: Integrated Development Environment）を使っているなら，スクリプトの編集中にリアルタイムで構文エラーやインデントのおかしな所を指摘してくれたりするだろう。

# さいごに

次回は NoMethodError と NameError を書こうと思う。そのあとは TypeError，ArgumentError とか。スタックトレースの見方とか，例外クラスとかも知らなくちゃ。

（2016年4月28日追記）「[その 2](http://qiita.com/scivola/items/77017693de371ab49667)」を書きました。予告どおり NoMethodError と NameError です。