Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
315
Help us understand the problem. What is going on with this article?
@scivola

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

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

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

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

この記事は Ruby プログラミングの超初心者に向けて,とくに英語に苦手意識を持つ人に向けて,エラーメッセージの読み取り方の最初の一歩を解説しようと試みたものだ。本当はもっとちゃんと分かっている人に書いてほしい のだが,誰も書いてくれないので1

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

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

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

Syntax Error

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

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

以下の Ruby スクリプト(?)は Syntax Error,つまり構文エラーとなる。

* 3

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

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

end 抜けによるもの

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

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 抜け

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

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

またもや一箇所で endendd と打ってしまった(「こうの」の次の行)。

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

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

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

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

なぜこうなるのか。

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

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

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

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

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

(まつもとさんを)うらみ・ます

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

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

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

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

さきほどの例では,endendd と打ってしまったために,ifcase に対応する end が一つずつずれて解釈された。
その解釈だと,制御構造のネスト(入れ子)の深さとインデントの深さが対応しないわけだが,Ruby の処理系はそういうことに頓着しない

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

このオプションは

turn warnings on for your script

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

早速さきほどの例で,

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 の位置は見てくれないみたいだ。たとえばこれ

3.times do
  # なんとか
                   end

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

こまめに構文チェックを

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

つまり,

ruby -c -w sample.rb

とすればいい。

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

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

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

EmEditor でやる方法は拙文「EmEditor で今書いている Ruby スクリプトの文法をチェック」を参照。

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

さいごに

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

(2016年4月28日追記)「その 2」を書きました。予告どおり NoMethodError と NameError です。


  1. 本当に誰も書いてくれてないのか確かめてはいない。 

  2. 言語学における文法論には,構文を調べる「構文論」(統語論ともいう)のほかに,音の仕組みを調べる「音韻論」,単語の成り立ちを調べる「形態論」などがある。よく知らんけど。 

  3. Ruby 2.4 以降なら sum と書くべき。 

315
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
scivola
主に Ruby を使ってます。Rust に興味を持っています。校閲承ります。私信は Slack の ruby-jp からどうぞ。

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
315
Help us understand the problem. What is going on with this article?