33.3 デバッグの方法

自分の思い通りにプログラムを書くことは極めて難しく,書いたプログラムがそもそもエラーを吐いて動いてくれなかったり,自分の予想とは違う動きをすることはしょっちゅうあります.こういう状況でプログラムのバグを取り,正しく動くように直す作業をデバッグと言います.この節では,デバッグの基本的な手法を説明します.プログラムを書いていて行き詰まったら,このページの方法で頑張ってデバッグをしてください.

プログラムのエラーは,主に

  • プログラムがそもそも走りすらしない
  • プログラムは走るが,自分の予想とは違う挙動を示す

という 2 つに分かれます.順番に解決法を追いかけてみましょう.

プログラムが走らない

前節 hwb33.2 条件分岐とループ に従ってプログラムを作成して実行しようとしても,以下のようにエラーメッセージが表示されてしまい,プログラムが動かないことがあります.

ruby test.rb return2
test.rb:6: syntax error, unexpected kTHEN, expecting $end

エラーメッセージは,ソースコードの何行目でエラーが発見されたかと,エラーの内容を教えてくれます.エラーメッセージの冒頭には,エラーの起きたプログラムのファイル名と,エラーがプログラム中の上から何行目で起きたかが示されています.上の例では, “test.rb” の 6 行目でエラーが発見されたことが分かります.mi ではウィンドウの左部に行番号(長い行が自動改行されていると,設定によっては行番号がずれることがあります)が表示されていますから,対応する行に誤りがないか確認してみましょう.ただし,示された行はあくまでエラーであることが「判明した」場所なので,その場所に問題があるとは限りませんから,注意が必要です.

エラーメッセージの行番号や文面は,デバッグのヒントにはなりますが,一つのメッセージに対して定型的な解決法があるわけではありません.以下ではよく犯しがちなミスと,そのとき表示されるエラーメッセージを解説しますが,ここに示したミス以外の可能性も考慮に入れる必要があります.

文字コードと改行コードのチェック

まず最初にやるべきは,文字コードと改行コードが正しく設定されているかの確認です.hwb8.2.3 文字コード,改行コードの設定 で説明したように,mi は Mac のアプリケーションなので標準の改行コードとして CR を使いますが,ruby はターミナル上で走るので標準の改行コードとして LF を使います.もし mi の側で改行コードを CR にしていたら,mi で書いたプログラム中の改行を ruby が改行だと認識できず,その結果エラーが吐き出されます.

全角空白・記号のチェック

ソースコードの中の不適切な位置に全角空白が入ると,プログラムは走りません.しかし人間の目に全角空白は映らないので,探すのは中々厄介です.テキストエディタの検索機能を使って,全角空白が混ざっていないか探しましょう.また,ソースコード中で使う加減乗除やカッコなどの記号類も,半角文字でなければなりません.

このような全角文字が混入しているときには,例えば以下のようなエラーメッセージが表示されます.下線を引いた部分に注目してみましょう.”char” というのは,character つまり文字のことです.エラーメッセージは,「プログラムに使ってはいけない,無効な文字がある」と教えてくれています.

test.rb:6: Invalid char `\343' in expression

tips
Ruby では,変数名など,プログラムを書いている人が好きに決めることができる名前(識別子)に,日本語文字などアルファベット以外を使うことができる機能がありますが,デフォルトではオフになっています.

スペルミスのチェック

自分で決めた変数の名前や,Ruby であらかじめ決められている if や print などの単語のスペルを間違えてしまうと,プログラムは動かないか,おかしな動作を起こしてしまいます.このとき表示されるエラーは,大きく分けて2通りあります.

構文エラー (syntax error)

例えば if 〜 then と書かなければならないところを, “then” のスペルを間違えて “than” としてしまうと,以下のようなエラーメッセージが表示されます.

if 10 > 3 than print "10 is bigger than 3" end
test.rb:1: syntax error, unexpected tIDENTIFIER, expecting kTHEN or ':' or '\n' or ';' test.rb:3: syntax error, unexpected kEND, expecting $end

エラーメッセージを読むと, “then” (kTHEN) が期待されている場所に全く別の文字列が現れてしまっていることが, “syntax error” (構文エラー)として指摘されていることが分かります(2行目のエラーについては,次の「end の対応を調べる」で解説します).構文エラーは,ソースコードが Ruby の文法に反していて正しい解釈のしようがない,というときに生じます.

未定義の変数 (undefined local variable)

一方で,変数の名前は自分で決めて使うことができ, Ruby の文法で決められているわけではありませんから,これを間違えても構文エラーにはなりません.

var = 3 print vxx
test.rb:2: undefined local variable or method `vxx' for main:Object (NameError)

このエラーメッセージでは, “vxx” という名前の変数 (variable) が定義されていない,と指摘されています.変数は,値を代入することで作られますが,作られていない名前の変数を使おうとすると,このようなエラーになります.

ただし,スペルミスをしたのが,既存の変数に値を代入し直すコードの部分だと,エラーは出なくなってしまいます.正しい名前の変数と,スペルミスした名前の変数とが両方できてしまうからです.このときは,正しい変数には代入が行われないことになりますから,以降のプログラムの動きがおかしくなってしまうでしょう.

では,次のソースコードはどのようなエラーになるでしょうか.このコードは,3行目で “else” と書くべきところを “esle” とスペルミスしています.

if 10 > 3 then print "Yes. 10 is bigger than 3" esle print "No. 10 is NOT bigger than 3" end
ruby test.rb return2
test.rb:3: undefined local variable or method `esle' for main:Object (NameError) Yes. 10 is bigger than 3

“else” は Ruby で決められている単語ですから,これを間違えたら Ruby のプログラムとして成立しなくなる「構文エラー」になりそうですが,実際は「未定義の変数」エラーになりました.ここでは “else” はあってもなくても,(文法としては)正しいプログラムであるからです.Yes… を出力し終えた段階で,”esle” という変数が現段階までで定義されていないことが発覚し,エラーになったのです(プログラム出力とエラーメッセージとの順番は,場合によって前後します).

仮に “esle” という変数をはじめに定義していたら,このプログラムはエラーにならず,Yes… と No… の両方を出力します(変数名だけを書いた行は,特に何も起こりません).

end の対応を調べる

test.rb:10: syntax error, unexpected $end, expecting kEND

上の例と同じ syntax error ですが,”unexpected $end” となっています.こちらは原因箇所の特定が少し難しいかもしれません.

プログラムの最終行にこのようなエラーが表示されたときは,たいていは if や for に対応するぶんだけの end が書かれていないことが原因です.このようなソースコードは,閉じカッコのない文章のようなものですが,コンピュータはソースコードの末尾まで “end” を探してはじめてエラーを出します.つまり,エラーメッセージで示されている行番号と,実際に end が抜けている箇所が一致しないことになりますから,頑張って問題の箇所を探さなければなりません.

このエラーメッセージの意味は,”end”(エラーメッセージ中では “kEND” と表記されています)を期待していたのに,ファイルの終わり(同じく “$end”)が来てしまった,というものです.

逆に,

test.rb:8: syntax error, unexpected kEND, expecting $end
というのは,もうファイルの終わりがあるべきなのに,まだ “end” が書いてある,ということで,”end” が多すぎるのです.上で “then” をスペルミスしたときにこのエラーが出てきたのは,開きカッコが正しく書かれていないのに,閉じカッコだけ書かれていたから,という状況に対応します.

プログラムを途中で切る

もしプログラムのエラーがある場所が分からないときは,ソースコードを途中で打ち切ってみましょう.ある部分より先のソースコードを削除して ruby でプログラムを走らせてみてプログラムが動いたなら,エラーが削除した部分のどこかにあると分かります.逆にソースコードの一部を削除してもプログラムが走らなければ,削除しなかった部分のどこかにエラーがあると分かります.もちろん打ち切る場所が悪いと (たとえばループの do と end の間で打ち切ると) 新たなエラーになります.そうならないよう,打ち切っても大丈夫そうな場所をうまく選んで下さい.

通常は一箇所の打ち切りでエラーが見つかることは希です.しかしプログラムの打ち切りを使ってエラーのある箇所を半分ずつに絞っていけば,いつかはエラーを起こした場所を突き止められるはずです.

プログラムの動きを追いかける

ここから先は,プログラムが動きはするものの思い通りに動いてくれない場合の対処法を紹介します.以下に挙げる 3 つの方法を併用することで,いつかバグが取れるでしょう.

変数の値を追跡する

プログラムが予期せぬ挙動を示す場合,ほとんどの場合はプログラム中の変数の値が期待するものとは違う値になっています.そこでプログラムの何ヶ所かで print メソッドを使い,変数の値を出力してみましょう.変数の値が狂う場所を見つけられたら,バグ取りの作業が大きく進みます.

それから,変数の値を出力するときは単に print i などとするのではなく,print “i=”, i , “\n” などとしましょう.プログラムが画面に何かを出力する場合,単に変数の値だけを出力していては意味が分からなくなってしまいます.人間が読めるような形で変数の値を出力することで,作業がはかどります.

プログラムを途中で止める

Ruby にはキーボードからの入力を受け取るための gets というメソッドがあります.たとえばプログラム内で a = gets と書いておくと a = gets のところで一旦プログラムが止まり,ユーザからの入力を待ちます.その後ユーザが文字列を入力して return を押すと,その文字列が a に代入されてプログラムが再び走ります.

この「ユーザからの入力を待つ」という機能があるので,プログラム中に gets を挟むとその位置でプログラムを一時停止させることができます.特に,上で紹介した変数の値の出力と gets メソッドを組み合せれば,プログラムを動かしながら変数の値を追跡することができます.ソースコードに怪しいところがあったら,その部分で一時停止をしてみましょう.

プログラムの流れを追跡する

プログラムの中で条件分岐をしている場合,条件分岐に失敗してプログラムが正しく動かなくなることがあります.その場合,きちんと条件分岐しているかを print メソッドで簡単に確かめられます.たとえば if … then の直後に print “hoge” と書いてあれば,画面に hoge と表示されるかどうかで,if 文の中が実行されたかどうか分かります.上手い位置に print 文を挟むことにより,自分の思い通りに条件分岐ができているかを確かめましょう.

プログラムが終了しない

実行を開始したプログラムがいつまでたっても終了しないときは, C(コントロールキー+C)を押すことで強制的に終了することができます.強制終了したときに実行されていたコードの位置が,エラーメッセージと同様に行番号で表示されます.

^Ctest.rb:3:in `block in <main>': Interrupt from test.rb:2:in `each' from test.rb:2:in `<main>'
一番上の行に表示されているのが中断時の位置です.

原因としては,for ループで繰り返し回数を指定する変数の計算が誤っており,とても大きな数になってしまったことや,(これまでに説明した文法では作ることができませんが)永久にループを抜け出せない無限ループのプログラムを作ってしまったことが考えられます.上でみたメッセージの行が含まれているループの指定で使われている変数の値が期待したものになっているかどうか,を確認してみましょう.