Ruby のエラーメッセージをわかりやすく表示する gem「Eturem」を作った

プログラミング初心者に Ruby を教える活動をしているのですが、Ruby って初心者にオススメするのに非常に良い言語だと思っています。環境さえ整えてしまえば「とりあえず動くプログラム」を書くまでのハードルが低いし、やろうと思えばゲームだって Web アプリだって何だって作れるし、日本語の情報源が多いし…

ですが、エラーへの対処は（Ruby に限った話ではないでしょうが）初心者には非常に難しいと感じています。英語で表示される時点で苦手な人には非常にハードルが高くなってしまいますし、そうでなくとも Ruby のエラーメッセージが初心者にとってわかりやすいものとは正直思えません。

[Ruby のエラーメッセージを読み解く（初心者向け）](https://qiita.com/scivola/items/3017068a354892b239f4)などの良記事を書かれている方もいますが、初心者が自分でこの記事に辿り着けるとは限りませんし、そもそもエラーが起きるたびに Google 検索するのも手間がかかる。

そこで、Ruby のエラーメッセージを初心者にわかりやすく表示するための gem「[Eturem](https://github.com/nodai2hITC/eturem)」をつくってみました。ちなみに Easy To Understand Ruby Error Message の略。

# インストールと使用方法

    $ gem install eturem

でインストールし、

    $ ruby -returem/ja <your_script.rb>

または

    $ eturem lang=ja <your_script.rb>

と使用すればよい[^1]のですが、最初に書いたとおり初心者が使用することを想定した gem ですので、そんなことを初心者に強いるのは酷というもの。だれか詳しい人が、事前に ```gem install eturem``` した上で、環境変数 RUBYOPT に ```-returem/ja``` を追加しておいてあげましょう。

# 使用するとどうなるか

* エラーメッセージが日本語で表示される。
* エラー箇所周辺が表示される。
* エラーの種類によっては、原因を特定するためのさらなる情報が表示される。

## 例１：SyntaxError

```ruby:example1.rb
if gets.to_i == 1
  if gets.to_i == 2
    puts "なんたらかんたら"
  # 内側の if に対応する end を忘れてしまった！
end
```

通常の環境で実行すると、次のようなエラーが表示されます。

```
example1.rb:5: syntax error, unexpected end-of-input, expecting keyword_end
```

英語の苦手な人ではこの時点で拒否反応が出るでしょうし、そうでなくとも「end-of-input」や「keyword_end」[^2]が何を意味しているのか、初心者には掴みにくいのではないでしょうか。

Eturem を使用すると、次のようなエラー表示になります。

![example1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180658/2350fd03-de87-42b1-fdef-b2c2083812f2.png)

このように、日本語でわかりやすくエラーを表示してくれます。

なお、図のように本来の英語のエラーメッセージも併せて表示されるので、やがてこの gem を使わなくなった際にも英語のエラーメッセージを読めるようになるのではないでしょうか。

## 例２：NameError

```ruby:example2.rb
prayer_life = 100
# ↑スペルミス！
# 中略
# ↓このスペルは正しいが、上でミスしたことでエラー発生。
if player_life > 0
# 後略
```

通常の環境で実行すると、次のようなエラーが表示されます。

```
example2.rb:5:in `<main>': undefined local variable or method `player_life' for main:Object (NameError)
Did you mean?  prayer_life
```

did_you_mean のおかげで昔より格段にわかりやすくなったとはいえ、それでも英語に壁を感じる人はいますし、またこの例の場合実際にミスをしたのは1行目にもかかわらず「5行目でエラー」と表示されてしまうため、「え？5行目を何度見てもミスなんて無いよ？」と困ってしまう人もいるでしょう。

Eturem を使用すると、次のようなエラー表示になります。

![example2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180658/a2be9273-0282-1e45-e5cf-76db36b4093e.png)

このように、エラー発生箇所周辺だけではなく、did_you_mean がサジェストしてくれた変数の使用行も同時に表示してくれるので、ミスをしたのが実は1行目であることに気付きやすくなるのではないでしょうか。

## 例３：ArgumentError

```ruby:example3.rb
def foo(a, b)
end
# 中略
foo(1)
```

通常の環境で実行すると、次のようなエラーが表示されます。

```
Traceback (most recent call last):
        1: from example3.rb:4:in `<main>'
example3.rb:1:in `foo': wrong number of arguments (given 1, expected 2) (ArgumentError)
```

このように、ArgumentError のエラー発生行は、メソッド定義行（この場合1行目）になってしまいます。しかし実際に ArgumentError が発生するときの原因は、メソッド定義部分ではなく、呼び出し部分ではないでしょうか？

この例の場合 Traceback に「from example3.rb:4」と表示されてはいますが、初心者にはやはりわかりにくいのではないかと思われます。

Eturem を使用すると、次のようなエラー表示になります。

![example3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180658/066a6871-3540-defe-226e-ee140497b93e.png)

このように、呼び出し行をエラー発生行として表示してくれます。

## 例４：warning

0.4.0 から、warning に対してもサポートができるようになりました。

![example4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180658/f8192f0f-ee6c-0802-ccc2-66f1fcc0322c.png)

現状「条件式内での = 使用」など ごく一部の警告にしか対応していませんが、上記のように日本語での警告に加えて該当行を表示することができます。

## 例５：エラー発生時に自動で binding.irb

変数の値が不適切だったためにエラーが起きた可能性がある場合、エラーが起きた瞬間の変数の値を調べる必要があります。そうした際には「エラー発生箇所の直前に binding.irb を入れて調べる」という手がありますが、そうするとたまにしか起きないエラーの場合何度も binding.irb が動いてしまって面倒です。

そこで、設定ファイル .eturem で設定するか、あるいはプログラム中に `$eturem_repl = "irb"` と入れておくことで、エラー発生時に自動的に binding.irb することができます。

なお、この機能だけを切り出した gem「[autoirb](https://github.com/nodai2hITC/autoirb)」もあります。

## その他細かなこと

### 全角空白・全角記号による NameError

日本人ならきっと誰もがやったことがあり、そして意外と気付けない全角空白や全角記号によるエラー。通常の NameError の場合とは異なるメッセージでわかりやすくしています。

![example5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180658/68eac7ab-13f1-ba4d-c81c-ad29e31f802c.png)

### 大文字・小文字を間違えた場合の NameError

did_you_mean は、例えば `Time.now` と書くべきところを `time.now` と間違えて小文字で書いてしまった場合、 `Time` をサジェストしないようになっています。（サジェスト範囲を広げると誤反応が多くなるため、敢えてそうしているのだそうです。）

とはいえ、初心者は大文字小文字が区別されるという意識が無く、つい小文字で入力してしまうことが少なからずあるように思えます。そこでスペルが同一で大文字小文字のみが異なる場合に限り、定数もサジェスト対象になるようにしています。

![example6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180658/7a2c545c-edf1-965d-b8f6-fbe62a6883ee.png)

# 簡単な仕組み

```ruby
begin
  load($PROGRAM_NAME)
rescue Exception => e
  # エラー表示処理
end
exit
```

実際はもう少しいろいろやっていますが、このように本来のスクリプト実行が始まる前に `-returem` オプションで eturem を呼び出し、そこから `load` でスクリプトを実行しているのがポイントです。これにより、SyntaxError 等も取得できるようになっています。

# 今後の展望

* end の過不足以外の原因による SyntaxError への対応が今ひとつなので、もう少しいろいろなパターンの SyntaxError に対応するようにする。
* SyntaxError が起きたときに、パーサでエラーの起きたスクリプトを読みこみ、さらに詳細なエラーの原因（たとえば end 不足にしても、どの部分に対応する end が足りないのか、など）を表示する。
* 日本語以外への対応。

などなど。

他にも、「こう表示した方がもっとわかりやすいのでは？」等のご意見ありましたらよろしくお願いします。

[^1]: ```-returem/ja``` ではなく ```-returem``` にすると、エラーメッセージを英語で表示します。が、作者の英語力不足により、日本語版ほど詳細なエラー表示はできません。
[^2]: 最近の Ruby では `keyword_end` の部分が単に `end` または `` `end' `` に変わったようです。