Rubyのエラーを直して覚える
実際に出力されるメッセージを読みながら、空欄を埋めて直します。全170問。間違えた問題は翌日・3日後・7日後に自動で再出題されます。
エラー別に調べる
初級
- end忘れによる SyntaxError の原因と直し方def・if・class・while などのブロックはすべて end で閉じる必要があります。1つでも end を書き忘れると、Rubyはファイルの終わりに達するまで気づけず、末尾でエラーになります。
- 文字列の閉じクォート忘れによる SyntaxError文字列を囲むクォートの片方を書き忘れると、それ以降のコードすべてが文字列の中身として読み込まれ続け、閉じクォートが見つからないままエラーになります。
- 配列リテラルでカンマを付け忘れたときの SyntaxError配列の要素を並べる際、区切りのカンマを付け忘れると、次の要素が「予期しないトークン」として構文エラーになります。
- NameError: undefined local variable or method の原因と直し方宣言・代入していない変数名を参照したときに出るエラーです。変数名のタイプミスのほか、代入する前に参照してしまったときにも起こります。
- undefined method for nil の原因と直し方nilが入った変数に対してメソッドを呼び出そうとしたときに出るエラーです。値を取得し損ねた変数(見つからなかった検索結果など)をそのまま使うと、必ずここで止まります。
- 文字列と数値を + で連結すると TypeError になるRubyの+演算子は文字列同士、数値同士でしか使えません。文字列に数値を+で連結しようとすると、暗黙の型変換が行われずTypeErrorになります。
- ZeroDivisionError: divided by 0 の原因と直し方整数を0で割ると、ZeroDivisionErrorという例外が投げられプログラムが止まります。0で割られる可能性がある値は、事前にチェックする必要があります。
- 整数同士の割り算 / が小数にならず切り捨てられるRubyの/演算子は、両辺が整数の場合は商の整数部分だけを返します。小数点以下は切り捨てられ、割り切れなくても整数のままです。
- シングルクォートの文字列では #{} が展開されないダブルクォートの文字列内では#{式}がその評価結果に置き換わりますが、シングルクォートの文字列内では一切展開されず、そのままの文字として出力されます。
- 配列の範囲外アクセスはエラーにならず nil を返す存在しない添字で配列にアクセスしても、Rubyはエラーを出さずnilを返すだけです。その後nilに対してメソッドを呼び出すと、そこで初めてNoMethodErrorになります。
- ArgumentError: wrong number of arguments の原因と直し方メソッドが必要とする数と異なる数の引数を渡すと、ArgumentErrorになります。デフォルト値の無い引数を渡し忘れたり、余分な引数を渡したりすると発生します。
- メソッド名のタイプミスによる NoMethodError存在しない名前のメソッドを呼び出したときに出るエラーです。メソッド名を1文字でも間違えると、Rubyは似た名前を推測して実行してはくれず、その場でエラーになります。
- each は配列を変換しない(map との違い)eachブロックの中で要素を加工しても、eachの戻り値はレシーバ自身(変更前の配列)のままです。加工した結果からなる新しい配列が欲しい場合はmapを使う必要があります。
- if文の中で == のつもりで = を書くと常に真になるif文の条件に == の代わりに = を書くと、それは比較ではなく代入として実行されます。代入式の評価結果は代入した値そのものなので、0以外の値であれば条件は常に真になります。
中級
- FrozenError: can't modify frozen String の原因と直し方freezeしたオブジェクトを破壊的メソッド(<<やupcase!など)で変更しようとすると、FrozenErrorになります。定数として扱いたい文字列にfreezeを付けると、以後は書き換えが禁止されます。
- Hash.new([]) のデフォルト値は全キーで同じ配列を共有してしまうHash.new([])で作ったハッシュは、存在しないキーにアクセスするたびに同じ1つの配列オブジェクトを返します。その配列を<<で変更すると、まだ触っていないはずの他のキーの値まで変わってしまいます。
- gsub! は置換が発生しないと nil を返す(gsub との違い)破壊的メソッドのgsub!やsub!は、置換対象が見つかって実際に変更が起きた場合は変更後の文字列を、何も置換されなかった場合はnilを返します。戻り値をそのまま次のメソッドにつなげると、置換が起きなかった入力でNoMethodErrorになります。
- 引数なしの rescue は StandardError の子孫しか捕まえないクラス名を指定しない rescue は、暗黙のうちに StandardError とそのサブクラスだけを捕まえます。自作の例外クラスを Exception を直接継承させて定義すると、rescue で捕まえられずプログラムが止まります。
- 必須キーワード引数を渡し忘れると ArgumentError になるデフォルト値の無いキーワード引数(name: のようにコロンだけで値を書かない形)は必須です。呼び出し時に渡し忘れると、通常の引数と同じくArgumentErrorになります。
- private メソッドをレシーバ付きで呼ぶと NoMethodError になるprivateを付けたメソッドは、クラスの外からinstance.methodのようにレシーバを明示して呼び出すことができません。呼び出したい場合は、内部でそのメソッドを使うpublicなメソッド経由にする必要があります。
- each で配列を回しながら delete すると要素が飛ばされるeachで配列を反復しながらdelete_atやdeleteで要素を取り除くと、後続の要素が前に詰まってインデックスがずれ、次に処理されるはずだった要素が読み飛ばされます。
- attr_writer だけでは値を読み出せない(attr_accessor との違い)attr_writerは代入用のメソッド(name=)だけを定義し、読み出し用のメソッド(name)は定義しません。値を読み書き両方したい場合はattr_accessorを使う必要があります。
- 初期化前のインスタンス変数を参照すると警告なしで nil になるinitializeで値を設定し忘れたインスタンス変数は、エラーにも警告にもならず単にnilとして扱われます。存在しないローカル変数がNameErrorになるのとは対照的です。
- シンボルキーのハッシュを文字列キーで参照すると nil になる:name(シンボル)と"name"(文字列)は別のオブジェクトとして扱われるため、シンボルキーで作ったハッシュを文字列キーで参照すると見つからずnilが返ります。
- Comparable を include しても <=> が無いと比較演算子は使えないComparableモジュールをincludeするだけでは <、>、between? などのメソッドは動きません。これらのメソッドは内部で<=>(UFO演算子)を呼び出す仕組みなので、クラス自身に<=>を定義しておく必要があります。
- case文で該当するwhenが無いとnilが返るcase式はいずれのwhenにも一致しない場合、elseが無ければ例外を出さずnilを返します。switch文がエラーになる言語の感覚のままだと、この静かなnilに気づけません。
上級
- Proc.new の中で return すると LocalJumpError になることがあるProc(Proc.newやKernel#proc)の中でreturnを使うと、そのProcを囲んでいたメソッドから抜けようとします。呼び出し時にすでにそのメソッドの外にいる場合(トップレベルで呼んだ場合など)、戻る先が無くLocalJumpErrorになります。同じ書き方でもlambdaは自分自身から抜けるだけなので、この問題は起きません。
- def self.method と def method を混同すると NoMethodError になるdef self.methodはクラスメソッド(ClassName.methodで呼ぶ)を定義し、def methodはインスタンスメソッド(インスタンス.methodで呼ぶ)を定義します。どちらか一方しか定義していないのに、もう一方の呼び方をするとNoMethodErrorになります。
- coerce未実装のクラスと数値を演算すると TypeError になる自作クラスのインスタンスを 数値 + 自作オブジェクト の順で演算すると、Integer側の+が呼ばれます。Integerは自作クラスの値をどう数値として扱えばよいか分からないため、そのクラスにcoerceメソッドが定義されていない限りTypeErrorになります。
- dup はfreeze状態を引き継がない(clone との違い)freezeしたオブジェクトをdupで複製すると、複製後のオブジェクトはfreezeされていない通常の状態になります。freeze状態も含めて複製したい場合はcloneを使う必要があります。
- 可変長引数を委譲するメソッドで * を付け忘れると ArgumentError になる受け取った可変長引数(*argsで受けた配列)をそのまま別のメソッドに渡すには、呼び出し側でも*を付けて展開する必要があります。*を付けずに渡すと、配列がまるごと1個の引数として渡ってしまい、引数の数が合わずArgumentErrorになります。
- キーワード引数を委譲するメソッドで ** を付け忘れると ArgumentError になる受け取ったキーワード引数(**optionsで受けたハッシュ)をそのまま別のメソッドに渡すには、呼び出し側でも**を付けて展開する必要があります。**を付けずに渡すと、ハッシュが1個の位置引数として渡ってしまい、キーワード引数として認識されずArgumentErrorになります。
- クラス変数 @@var はサブクラスとも共有されてしまう@@で始まるクラス変数は、そのクラスだけでなく継承したサブクラスとも1つの値を共有します。サブクラスごとに独立したカウンタなどを持たせたいつもりで@@変数を使うと、片方のクラスでの変更がもう片方にまで影響します。
- method_missing で super を呼ばないと本来のエラーが消えるmethod_missingを独自定義すると、未定義のメソッド呼び出しをすべて横取りできます。想定していない呼び出しに対してsuperを呼ばずnilなどを返してしまうと、本来出るはずだったNoMethodErrorが出ず、バグが静かに握りつぶされます。