例外とはそれ以上継続して処理ができない際のエラーのことを意味します。
そのためプログラムにおいて、例外が発生すると途中で処理が終了してしまうため、例外が発生する可能性のある箇所には、例外処理を行う必要があります。
つまり例外処理とは何かしらの理由で処理が継続してできなくなった場合に、どういった処理をするかの実装に使用されます。
よく使用されるのはネットワークが不安定でデータベースからデータが取得できずにそれ以上処理ができなかったり、想定していないデータが返ってきたりなどがあります。
例外
例外が発生するとそれ以降の処理が実行されません。
例えば以下の例では0で割り算ができないため「ZeroDivisionError」という例外が発生し、それ以降の「puts ‘実行されない’」が実行されません。
value = 10 / 0
puts '実行されない'
->ZeroDivisionError: divided by 0
例外処理
上記で説明したように例外が発生した場合は、想定していたそれ以降の処理が実行されないため、例外が発生した場合は違う処理を実行してあげる必要があります。
例外処理を行うには「begin」以下から「rescue」の間に例外が発生する可能性がある処理を記載し、「rescue」以下から「end」の間に例外時に実行される処理を記載します。
以下の例では「begin」内の「value = 10 / 0」で例外が発生するので、「rescue」以下の処理が実行され「0で割り算はできません。」が表示されます。「puts value」は実行されません。
他の言語では「begin」が「try」、「rescue」が「catch」、またあとで説明しますが「ensure」が「finally」と記載されることが多いです。
begin
value = 10 / 0
puts value
rescue
puts '0で割り算はできません。'
end
#実行結果
->0で割り算はできません。
例外オブジェクトの取得
例外オブジェクトを取得するには以下のように記載します。
以下の例では「rescue」の横に「=> e」と記載し、例外オブジェクトを受け取り、pメソッドでオブジェクトを出力します。
pメソッドはオブジェクトをわかりやすい形で出力してくれるので、デバッグの際などに利用されます。
begin
value = 10 / 0
puts value
rescue => e
p e
end
#実行結果
#<ZeroDivisionError: divided by 0>
=> #<ZeroDivisionError: divided by 0>
例外オブジェクトのメソッド
例外オブジェクトでよく使用するメソッドをいくつか紹介しておきます。
backtraceメソッド
backtraceメソッドはどのような順序でエラーが発生したかを返してくれます。
こちらの出力結果は実行環境によって異なるので、ぜひ実際に実行してみて中身をみてみてください。
エラーログを読むのは大変重要なので、試してみましょう。
以下の例では70行目(※コンソールでの行数なのでこの場合「value = 10 / 0」が記載されている行)の「/」でエラーが発生しているということになります。
begin
value = 10 / 0
puts value
rescue => e
p e.backtrace
end
#実行結果
70:in `/'", "(pry):70:in `__pry__'", "/Users/user/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/pry-0.13.1/lib/pry/pry_instance.rb:290:in `eval'", "/Users/user/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/pry-0.13.1/lib/pry/pry_instance.rb:290:in `evaluate_ruby'", "/Users/user/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/pry-0.13.1/lib/pry/pry_instance.rb:659:in `handle_line'", "/Users/user/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/pry-0.13.1/lib/pry/pry_instance.rb:261:in `block (2 levels) in eval'", "/Users/user/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/pry-0.13.1/lib/pry/pry_instance.rb:260:in `catch'", "/Users/user/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/pry-0.13.1/lib/pry/pry_instance.rb:260:in `block in eval'",............
full_messageメソッド
full_messageメソッドは例外に関する整形されたメッセージを返すメソッドです。
こちらではどこで発生したかと、「ZeroDivisionError」が発生したことが記載されています。
begin
value = 10 / 0
puts value
rescue => e
p e.full_message
end
->
"\e[1mTraceback\e[m (most recent call last):\n\t22: from /Users/user/.rbenv/versions/2.6.6/bin/pry:23:in `<main>'\n\t21: from /Users/user/.rbenv/versions/2.6.6/bin/pry:23:in `load'\n\t20: from /Users/user/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/pry-0.13.1/bin/pry:13:in `<top (required)>'\n\t19: from /Users/user/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/pry-0.13.1/lib/pry/cli.rb:119:in `start'\n\t18: from /Users/user/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/pry-0.13.1/lib/pry/pry_class.rb:191:in `start'\n\t17: from /Users/user/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/pry-0.13.1/lib/pry/repl.rb:15:in `start'\n\t16: from /Users/user/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/pry-0.13.1/lib/pry/repl.rb:38:in `start'\n\t15: from /Users/user/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/pry-0.13.1/lib/pry/input_lock.rb:78:in `with_ownership'\n\t14: from /Users/user/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/pry-0.13.1/lib/pry/input_lock.rb:61:in `__with_ownership'\n\t13: from /Users/user/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/pry-0.13.1/lib/pry/repl.rb:38:in `block in start'\n\t12: from /Users/user/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/pry-0.13.1/lib/pry/repl.rb:67:in `repl'\n\t11: from /Users/user/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/pry-0.13.1/lib/pry/repl.rb:67:in `loop'\n\t10: from /Users/user/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/pry-0.13.1/lib/pry/repl.rb:77:in `block in repl'\n\t 9: from /Users/user/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/pry-0.13.1/lib/pry/pry_instance.rb:259:in `eval'\n\t 8: from /Users/user/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/pry-0.13.1/lib/pry/pry_instance.rb:259:in `catch'\n\t 7: from /Users/user/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/pry-0.13.1/lib/pry/pry_instance.rb:260:in `block in eval'\n\t 6: from /Users/user/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/pry-0.13.1/lib/pry/pry_instance.rb:260:in `catch'\n\t 5: from /Users/user/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/pry-0.13.1/lib/pry/pry_instance.rb:261:in `block (2 levels) in eval'\n\t 4: from /Users/user/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/pry-0.13.1/lib/pry/pry_instance.rb:659:in `handle_line'\n\t 3: from /Users/user/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/pry-0.13.1/lib/pry/pry_instance.rb:290:in `evaluate_ruby'\n\t 2: from /Users/user/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/pry-0.13.1/lib/pry/pry_instance.rb:290:in `eval'\n\t 1: from (pry):2:in `__pry__'\n(pry):2:in `/': \e[1mdivided by 0 (\e[1;4mZeroDivisionError\e[m\e[1m)\e[m\n"
例外の種類毎の処理
特定の例外毎に処理するには、例外を指定して以下のように記載します。
以下の例では「foo」が定義されていないので、エラーが発生し、エラー内容が「ZeroDivisionError」ではないため、「その他」が出力され、例外処理をしているため処理は中断せず「終了」まで出力されます。
begin
#fooでエラーが発生
foo
10 / 0
rescue ZeroDivisionError => e
puts '0で割らないでください'
rescue => e
puts 'その他'
end
puts '終了'
#実行結果
その他
終了
リトライ処理
途中でエラーが発生し、再度処理を実行したいときは「retry」を使用します。
以下の例では0で割り算をしているため「ZeroDivisionError」の例外が発生し、rescueブロックに入り、「num」に3が設定され、「retry」で再度beginブロックが実行され、2回目は成功し「3」が出力され、「終了」が出力されます。
ただし、rescueメソッドでエラーが解消されない場合は、永久にループが実行されますので、注意する必要があります。
無限ループは負荷が高くなり、サーバーダウンなどの危険がありますので、十分に注意してください。
num = 0
begin
p 10 / num
rescue ZeroDivisionError => e
p e
num = 3
retry
end
puts '終了'
#実行結果
#<ZeroDivisionError: divided by 0>
3
終了
【raise】明示的に例外を発生させる
明示的に例外を発生させたい場合は、「raise」を使用します。
以下のように「raise」と記載するだけだと「RuntimeError」が発生します。
begin
raise
rescue => e
p e
end
#実行結果
->
RuntimeError
以下のように任意の例外を発生させることもできます。
begin
raise ZeroDivisionError
rescue => e
p e
end
#実行結果
=> #<ZeroDivisionError: ZeroDivisionError>
独自で例外クラスを作成させることも出来ます。
一般的に独自のクラスにスタンダードエラークラスを継承させて例外クラスを作成します。
class SelfError < StandardError; end
begin
raise SelfError
rescue => e
p e
end
#実行結果
=> #<SelfError: SelfError>
【ensure】例外処理で必ず実行される処理
「ensure」は例外処理で例外が発生しても、しなくても必ず実行される処理を記載するブロックになります。
他の言語で言うと「finally」ですね。
以下の例は例外が発生する場合です。
begin
raise
rescue => e
p e
ensure
puts '必ず実行される'
end
#実行結果
->
RuntimeError
必ず実行される
例外が発生しない場合も実行されます。
begin
puts 'エラーなし'
rescue => e
p e
ensure
puts '必ず実行される'
end
#実行結果
->
エラーなし
必ず実行される
こちらはbeginで行った処理の後処理が必要な場合に実行されます。
例えば、beginブロックでファイルを開く処理を行ったとして、その後エラーが発生してしまうとファイルが開いたままになってしまうので、ensureブロックでファイルを閉じる処理を入れたりなどです。
コメント