プログラムのバグでエラーが出るとき,エラーメッセージがバグを見つけるための大きなヒントになるはずだ。
ところが初心者はメッセージを読み解こうとせずに,ただ行番号からコードの該当箇所を探し,そこをじーっと眺めてバグを探そうとしがち(私もそうだった)。
どうしてか。
一つには〈エラーメッセージの読み方が分からない〉ということがあるのだろう。
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 となる。
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
またもや一箇所で 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(:+)
すれば3琵琶湖の水量にも匹敵するのではないか。
(まつもとさんを)うらみ・ます
こんな恨み言が聞こえてくる。
まつもとさんが
if
でもwhile
でもブロックでも,何でもかんでもend
にしたのが悪いんだ。endif
やendwhile
にしてくれていれば良かったのにぃ。
ま,かつてはそういう議論もあったのだが,いまはあまり聞かない(気がする)。
end 抜け探しの救世主 -w オプション
さきほどの例では,end
を endd
と打ってしまったために,if
や case
に対応する 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
はスルーされる。どうやら,インデントをチェックするのは if
や while
のような制御構造だけのようだ。
こまめに構文チェックを
起動オプション -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 です。