エラー処理・例外~種類や書き方~

プログラムを組んでいるとエラーが起こることもあるでしょう。しかし、一言にエラーといってもエラーにも種類がありますし起こったエラーに対しての対処の仕方も様々あります。今回はこのエラー処理と例外について学んでいきましょう。

吉田先生

エラーが起こった場合の対処を定義できるというのがエラー処理です。


エラー処理とは

まずは、エラーの種類についてみてみましょう。例えば店舗での売り上げを計算するときに、金額の欄に商品のサイズが記入されていると金額の計算でエラーが出ますね。このようなパターンや、誤字脱字など構文上のミスなどです。たとえば以下の様な構文があるとします。

"こんにちは".size
 #=> 5 

ミス等がなければ上記の様に文字数を返してくれますが、ミスがあると

"こんにちは"size                                                                  
#SyntaxError: 〇〇.rb:1: syntax error, unexpected tIDENTIFIER, expecting end-of-input

このようにメッセージが表示されます。上記の場合は「.」が抜けているというデータのエラーです。ですので、「SyntaxError」つまり構文エラーというメッセージとなっています。

システムのエラー

データベースなどのサーバーダウンや、CDがドライブに挿入されていないなどが例です。プログラムからはどうする事も出来ないような想定範囲外のエラーのことです。

プログラムのミス

呼び出せないメソッドを呼び出そうとしたり、誤った内容のデータを渡すなどプログラム内でのミスなどを指すエラーです。ではあえてメソッド名を間違えて書き、存在しないメソッドを呼び出している状況を作りエラーメッセージを見てみましょう。

"こんにちは".sizu
#NoMethodError: undefined method `sizu' for "こんにちは":String
#Did you mean?  size
        #from (〇〇.rb):1

「NoMethodError」メソッドがないという事ですね!他にも「undefined method `sizu' for "こんにちは":String」つまり、"こんにちは"(Stringクラスのインスタンス)は未定義の(存在しない)メソッド「sizu」を呼び出したというよな事が書かれていたり、3行目では「size」というメソッドの間違いではないかと教えてくれていますね。

では、このようなエラーが起こった場合の対処はどのような方法があるでしょうか。次は、対処方法についてみていきましょう。

エラーの原因を取り除く

上記例で言えば、誤字を修正すれば良いという事になりますね。他の例で言うと、対象のディレクトリ(フォルダのようなイメージ)に何か保存しようとした際に、ディレクトリが存在しなければエラーになります。この場合はディレクトリを作成すればエラーの原因を取り除くことが出来ます。

エラー前の状態を復元する

エラーが起きたことを確認し、エラーが起こる前の状態に復元します。その後の動作は指定する事が出来ます。

もう1度試してみる

時間をあけて、もう1度実行するとうまくいく場合があります。

プログラムを終了する

データの破損などの危険性もあるので気を付けましょう。

どの対処法が良いかは以下ポイントに注意し判断していきましょう。

・データを破壊してしまわないか

・エラーはどのような内容か、また原因を通知することは出来るか

データの破損はもちろん困るでしょうし、エラーが起きているのに何のメッセージも表示されなければサイトに訪れたユーザーに不便だと感じられてしまうかもしれません。

例外処理

例外とは、エラーの処理の手助けをしてくれる仕組みのことです。この仕組みはrubyであらかじめ用意されているものです。rubyでは、プログラムを実行した際にエラーが起こった場合、実行を一時的に中断し「例外処理」というものを探しに行きます。

例外処理とはエラーが起こった際にどのような対処・処理を施すかというものです。例外処理が書かれていればその処理を実行してくれます。書かれていない場合は、エラーメッセージを表示しプログラムを終了します。

前レッスンでの以下例は、この例外処理が記述されていなかった為エラーメッセージを表示し終了となっていますね。

"こんにちは".sizu
NoMethodError: undefined method `sizu' for "こんにちは":String
Did you mean?  size
        from (〇〇.rb):1

このようなエラーメッセージでは以下のような情報を知ることが出来ます。

・エラーが起こっている、ファイル名、行数、メソッド名

・エラー内容

・エラー発生個所の呼び出し元(from~の部分)

例外処理を記述することで、エラーを自動で検出したり、エラーの起こっている箇所を知ることが出来たり、通常・エラーの際の処理を見やすく書くことが出来ます。では、次のセクションでは実際の書き方について学んでいきましょう。

例外処理の書き方について

例外処理の書き方は以下のようになります。

begin
 例外(エラー)が起こるかもしれないという処理
rescue
 例外(エラー)が起こった場合の対処(処理)
end

この構文を「begin~rescue~end文」と呼びます。

これまでのレッスンでrubyでは全てのデータがオブジェクトと学びましたね。ですので、この例外に関するデータもオブジェクトであり「例外オブジェクト」と呼ばれます。また、「begin~rescue~end文」の中で変数を指定すれば、例外オブジェクトを変数に代入する事が出来ます。その場合の構文は以下のようになります。

begin
 例外(エラー)が起こるかもしれないという処理
rescue  => 変数名
 例外(エラー)が起こった場合の対処(処理)
end

「rescue」の行で変数を指定します。この変数については、変数名を書いて指定しなくても、自動的に変数「$!」や「$@」に代入される仕組みになっています。ですが、変数名を指定した方がプログラムとして分かりやすく操作しやすくなるので指定した方がよいでしょう。

*「$!」は例外オブジェクト自体、「$@」は例外の位置情報が代入されます。代入されるのは、どちらも最後に起きた例外(エラー)のものになります。では、実際に実行してみましょう。

begin
  foo = "テスト"
  foo.lenght
rescue => msg
  puts "エラー"
  puts msg
end

#エラー
#undefined method `lenght' for "テスト":String
Did you mean?  length

このように、例外が起こった場合は例外処理の内容が返されます。このようなエラー処理は、必ず用意しなくてはいけないという訳ではありません。プログラム上必要性がある場合はこのような事も出来ますという事です。

メソッド

例外オブジェクトは以下のメソッドが使用できます。

・class   -例外の種類を調べる   

・message  -例外のメッセージを取得する

・backtrace -例外の起こった位置情報(変数「$@」=「$!.backtrace」)

先程「msg」という変数に例外オブジェクトを代入したので、こちらで試してみましょう。

msg.class
 #=> NoMethodError 

msg.message
 #=> "undefined method `lenght' for \"テスト\":String\nDid you mean?  length" 

どこでどんなエラーが起こっているのか、簡単に調べられますね。

後処理

先ほどは、例外(エラー)が起こった場合にしたい処理を書くという内容でした。こちらの後処理は、例外が起こっても起こらなくても実行したい処理のことをいいます。書き方は以下のようになります。

begin
 例外(エラー)が起こるかもしれないという処理
rescue => 変数名
 例外(エラー)が起こった場合の対処(処理)
ensure
  例外の有無は関係なく、実行したい処理
end
例外の有無は関係なく、実行したい処理の部分をensure節といいます。この部分に処理を書きます。では、以下例を見てみましょう。

begin file = File.open("test.txt", "r") rescue => msg puts "読み込みに失敗しました" ensure file.close end

まず「File.open("test.txt", "r")」としています。これは、ファイルオブジェクトを読み込むための記述です。そして、例外が起きればメッセージを表示し、例外の有無に関わらずファイルを閉じるとしています。

Fileオブジェクトはブロックの中でopenすれば勝手に閉じてくれますが、それ以外であれば自分で閉じる必要があります。こういった場合に上記構文を使うという事になります。

やり直し

rescue節に「retry」と記述すると、beginまで戻り処理をやり直すことが出来ます。処理が失敗し続けると永遠にループしてしまうので注意が必要です。

ただし、回数を指定する事も可能です。ですので、処理が成功するか回数を指定しない限り、ループするものという事になります。では、回数を指定する場合の書き方についてみていきましょう。

begin
  count += 1
  foo = こんにちは
rescue
  p count
  retry if count < 3
end
1
2
3
 #=> nil

まずやり直しの回数をカウントするためにcountという変数に回数を代入しています。そして、fooという変数に「"」をつけていない文字を代入していますがこれはエラーになりますね。そして、この構文を分かりやすくするために回数を表示させるようにし、「retry if count < 3」という部分で回数の指定をしていますね。

rescue修飾子

修飾子はif文やunless文についての項目で学んだと思います。if文は通常以下の様な構文ですが、

if  条件
 処理
end

これを以下のように1行で書くことが出来ます。

処理  if  条件

このような事がrescueでも可能です。以下構文を、「rescue修飾子」といいます。

式1  rescue  式2

式1の部分がbegin節、式2がrescue節に当たります。ですので、式1は例外が起こるかもしれない処理、式2が例外が起こった場合にしたい処理を書きます。

例えば、数値として返せる文字列であった場合は文字列を数値に変換して返し、正しくないフォーマットだとメッセージを返すというプログラムを、以下のようにrescue修飾子で書くことが出来ます。

「Integer」というメソッドは、数値の様だけれども文字列オブジェクトであるというようなものを渡された場合に、数値として返すことが出来れば数値を返すというメソッドです。ですので、以下例の「"20"」は数値となって返されています。

def age(str)
  Integer(str) rescue "正しくありません"
end
risa = "risa"

risa.age("二十歳")
 => "正しくありません" 
risa.age("20")
 #=> 20 

例外処理の構文

これまで例外処理を書く場合は、「begin~rescue~end」という形で書くとして学んできましたが、「処理 + rescue節 or ensure節」という形で省略して書くことが出来ます。この「処理 + rescue節 or ensure節」という形で書けるのは、メソッドやクラス内のみとなります。

ただし、クラス内の場合例外(エラー)が起こってしまった場合、それ以降のメソッドが実行されなくなってしまいますのでクラス内ではあまり使われません。では、書き方を見ていきましょう。

def  test
  メソッドの処理など
rescue
  例外処理
ensure
  後処理
end

上記はメソッドの場合ですが、クラスの場合も書き方は同じです。では、rescue修飾子の例を上記の形に直してみましょう。

def age(str)
  Integer(str)
rescue
  "正しくありません"
end

risa.age("20")
 #=> 20 

risa.age("二十歳")
 #=> "正しくありません"

例外クラス

例外にもクラスが存在しています。では、代表的な組み込み(rubyであらかじめ用意されている)例外クラスを見てみましょう。

 

 

スーパークラスは「Exception」ですね。以降は全てサブクラスとなります。エラーの内容がどんなものかによってどのクラスに属すのか決まります。

捕捉する例外の指定

これまでの例の例外は1種類でした。しかし、複数の種類の例外が起こってしまう可能性もあります。その様な場合でも以下の書き方でrescue節に記述すれば例外が起きた際の対処を例外の種類ごとに指定する事が可能です。

rescue Exception1, Exception2 => 変数

この例では、Exceptionという例外クラス名を書き、例外の種類を指定しています。このように例外クラスを指定すると、指定した例外クラスに属する種類のエラーを探しにいってくれます。また、指定した例外クラスだけではなくそのサブクラスからも探すことも行ってくれます。

上記例では例外クラス名を記述して指定していますが、指定しないことも可能です。ただし、指定しなかった場合はデフォルトでStandardErrorクラス&そのサブクラスから探されることとなります。

上記例では2つの種類の対処を書く場合としていますが、もっと多くの種類を記述したい場合は以下のように書くことが出来ます。

begin
  例外が起こるかもしれない処理
rescue StandardError1, StandardError2 => 変数
  1、2に対する処理(対処)
rescue NoMethodError=> 変数
  3に対する処理
rescue
  以外の例外に対する処理
end

このように読みやすく、また分かりやすく対処を振り分けることが出来るんですね。では、例を見てみましょう。

begin
  miku = "miku"
  puts mikuupcase
rescue SystemExit, SignalException => error
  p error
rescue => error 
  p error
  p "エラー(そのようなメソッドは存在しません)"
end
#&lt;NameError: undefined local variable or method `mikuupcase' for main:Object>
 #=> "エラー(そのようなメソッドは存在しません)" 

この例の場合は、puts mikuupcaseという部分で、「.」を書けていません。ですので、メソッドが存在しないというエラーになることが予想できますね。1つ目のrescue節では、SystemExitというクラスとSignalExceptionというクラスを指定しています(この2つはStandardErrorクラスのサブクラスではありません)。

そして2つめ目のrescue節ではクラス名の指定をしていないので、変数errorにはStandardErrorが入ると学びましたね。結果を見てみると2つ目のrescue節の対処がされていることがわかります。

ちなみに、自分で例外クラスを定義する事も可能となっています。自分で定義する際はStandardErrorクラスを継承して定義するとよいでしょう。書き方は以下のようになります。

class 〇〇(例外クラス名) &lt; StandardError
 メソッドの定義
end

この構文はメソッドの定義をしている場合の書き方ですが、メソッドの定義が必要ないという場合であるならば以下のように1行で書くことが出来ます。

〇〇(例外クラス名) = Class.new(StandardError)

この2つの構文の意味は同じです。

例外の発生

プログラムが上手くいかないときなどに、あえて自分で例外を起こすこともできます。このとき「raise」メソッドというものを使います。この「raise」メソッドには4種類の形式が存在します。

raiseメッセージ

RuntimeErrorを発生させるものです。RuntimeErrorとはStandardErrorのサブクラスであり、指定の例外クラスには当てはまらない例外のことです。このraiseメッセージとは、発生した例外にメッセージ、つまり文字列を渡すという事です。

raise "エラー発生"
#RuntimeError: エラー発生

raise例外クラス

自分で指定した例外を発生させることが出来ます。

raise NoMethodError
#NoMethodError: NoMethodError

raise例外クラス&raiseメッセージ

上記2つの両方のセットです。

raise NoMethodError, "そのようなメソッドはありません"
#NoMethodError: そのようなメソッドはありませ

raise

rescue節外ではRuntimeErrorを、rescue節内では1番最後に発生した例外を再度発生させることが出来ます。

raise
#RuntimeError: 

begin
  "abc"size
  "def".size
rescue
  raise
end
#SyntaxError: (〇〇.rb):3: syntax error, unexpected tIDENTIFIER, expecting keyword_end

では、どのような時に使うのかを例を使って実行してみましょう。

def foo
  a.size
  raise
end

foo
#NameError: undefined local variable or method `a' for main:Object

この場合最初に「a.size」としていますが「a」という変数には何も代入されていませんので「size」メソッドは実行できません。このように何らかのミスでプログラムが上手くいかないとき、raiseを使えば呼び出し元に戻って(最後のエラー発生個所に戻る)くれるので、どこでどんなエラーが発生しているのかを知ることが出来ます。

エラーメッセージ

冒頭でご紹介しましたが、エラーメッセージの中には上記のような例外クラス名が記載されています。ではいくつか例を見てみましょう。

Syntax Error(構文エラー)

〇〇.rb:行数: syntax error,  unexpected $end, expecting ')'

この例の場合は、')' を書き忘れているという事を教えてくれています。

NameError / NoMethodError

〇〇.rb:行数:in `メソッド名' : undefined local varuable or method
`メソッド名' for main:Object (NameError / NoMethodError)

存在しないメソッド及び変数を呼び出したときなどに出るエラーです。

ArgumentError

〇〇.rb:1:in `メソッド名' : wrong number of arguments (数字 for 数字)

期待される引数の数と実際に渡した引数の数が異なる場合や、値が期待されるものと異なっている場合のエラーです。数字の部分は、左が実際渡した数で、右が期待される数です。

まとめ

・エラーが起こった場合の対処を定義できる(begin~rescue~end)

・エラーには種類があり例外クラスごとに分類されている(例外のクラスを「class」で調べられる)

・例外もオブジェクトである(rescue => 変数名とすれば変数に代入できる)

・あえて自分で例外を起こすこともできる(raise)

無料ビデオ講座のお知らせ

Skillhub [スキルハブ]では無料の動画講座を多数公開しています。他校だと数万円するような講座が無料で受講できます。

無料講座一覧を見る

×