⚠️

【Ruby】例外処理について掘り下げて考えた

に公開

自己紹介

はじめまして、はると申します。完全異業種からのエンジニア転職を目指して学習をしています。

概要

Rubyの基礎を学習しているとき、いくつか理解が難しかったところがありますが、そのうちの一つが「例外処理」でした😣
そのまま苦手だなと思いながら放置してしまっていたので、今回改めて学習しました。

注意

私は前職が完全異業種であり、英語も苦手で、スクールに入って初めてプログラミングに触れました。
そんな自分が理解しづらかった部分を、同じように初めてプログラミングの概念に触れた人に向けてまとめました。
自分の復習も兼ねて、超超噛み砕いて書いているため、周りくどい書き方になっている箇所もあるかと思います🙇

例外処理とは

『例外処理(れいがいしょり、英語: exception handling)とは、IT業界で用いられる専門用語で、ある抽象レベルにおけるシステムの設計で想定されておらず、ユーザー操作によって解決できない問題に対処するための処理である。』1) とのことです。

なぜ例外処理が必要なのか

予想外のこと(例外)が起こった時にプログラムが異常終了してしまうと、アプリが使えなくなったり、ユーザーに不安を与えてしまいます。
そのため、例外処理をして、例外が発生した時にも何らかの形で(エラーメッセージを表示してトップページに戻すなど)プログラムの実行を継続できるようにすることが必要と考えます。

登場人物

  • raisefailも全く同じ動きをしますが、今回の記事では全てraiseで説明します)
  • begin
  • rescue
  • ensure

実は、整理すると4つだけでした!!◎
1つずつ見ていきましょう。

raise

raiseは、例外を発生させるメソッドです。

raise "例外です"
#=> Main.rb:1:in `<main>': 例外です (RuntimeError)

この1行のコードだけで、例外を発生させることができます。
"例外です"の部分を書き換えれば、好きな例外メッセージを表示させることができます。

begin

beginrescueensureは例外処理のための構文です。
1つずつどのような役割か見ていきます。

beginは、実行したいコードを書く部分です。例外が起こりうるコードの場合、beginendの例外処理の構文を使って書きます。

例えば、下記のようなメソッドがあった時、引数xには数値が渡されることを期待していますが、文字列が渡されるとエラーになってプログラムが終了してしまいます。(⚠️例外の発生)

def addition(x)
  x = x + 1
  puts x
end

addition(2)   #=> 3

addition("2") #=> no implicit conversion of Integer into String (TypeError)

このような例外をキャッチするため、beginを使ってメソッドを書き換えていきたいです。
rescueの説明に移ります。

rescue

rescueは、beginとセットで使われる構文です。
begin節の中で例外が発生したときには、例外が発生した時点でbegin節内の処理を中断してrescue節に飛び、
rescue節の処理が実行されます。

def addition(x)
  begin
    x = x + 1
    puts x
  rescue => evar   # everは省略してeと書くこともできます。また、どんな例外でもキャッチするようになっています。
    puts "例外が発生しました:#{evar.message}"  # begin内で例外が起こると直ちにrescue節に飛び、この行が実行される
  end
end

addition("2")
#=> 例外が発生しました:no implicit conversion of Integer into String

これを工夫すると以下のような使い方ができます。
TypeError(型が違う)の時には、指定された型の値を入力するように促すメッセージを表示することができます。
少し実用的なイメージがついてきました!🐣

def addition(x)
  begin
    x = x + 1
    puts x
  rescue TypeError => e   # TypeErrorの例外のみをキャッチするようにしました
    puts "xには数値を入力してください"
  end
end

addition("2")
#=> xには数値を入力してください

ensure

簡単なコードの例外処理はensureを使わなくても実行できるため、難しい方はこの部分は読み飛ばしても大丈夫です。
  
ensureは、beginrescueとセットで使われる構文です。
ensure節内に書かれたコードは、例外の有無にかかわらず、必ず実行されます。

実際に使うときの例としては、ファイルを読み込もうとするメソッドがあった時に、ファイルが存在しないなどの例外が発生すると、ファイルを開いている処理の途中でプログラムが終了してしまいます。
ensureは、何らかの理由で例外が起こった時でもファイルやネットワーク接続などを確実に終了するために使用されます。
  
先ほどまでの例のコードは、ensureを使う必要のない簡単なコードですが、
コード例として、ensureを無理やり追加してみると以下のようになります。

def addition(x)
  begin
    x = x + 1
    puts x
  rescue TypeError => e
    puts "xには数値を入力してください"
  ensure
    puts "この部分は例外が発生してもしなくても必ず実行されます"
  end
end

addition("2")
実行結果
xには数値を入力してください
この部分は例外が発生してもしなくても必ず実行されます

raiseとbeginの構文の組み合わせの例

上記の例だと、raisebeginrescueのそれぞれの役割や動きについてはわかりましたが、組み合わさるとよくわからなくなります😵‍💫

組み合わせる場合の例について考えてみます。
以下のコードでは、引数xが数値以外の時(unless x.is_a?(Integer))には、意図的にTypeErrorを引き起こさせrescue節に飛ぶようになっています。

def addition(x)
  begin
    raise TypeError, "xには数値を入力してください" unless x.is_a?(Integer)

    x = x + 1
    puts x
  rescue TypeError => e
    puts e
  end
end

addition("2")

簡単なコードなので、恩恵がわかりにくいですが、先ほどまではrescue節を実行するタイミングは「例外が発生した時」でしたが、
raiseを組み合わせることで、 意図したタイミングで 処理を中断しrescue節に飛ぶことができるようになりました。

まとめ

  • raiseは、意図的に例外を発生させるメソッド。
  • beginrescueensureは例外処理のための構文。
  • beginは、実行したいコードを書く部分で、begin節内で例外が発生したときにrescue節の処理が実行される。
  • ensure節内に書かれたコードは、例外の有無にかかわらず、必ず実行される。
  • raisebeginの構文を組み合わせることで、より意図的な動作を実現できる。

例外処理よくわからない〜が少し理解が進みました!!
ここまで読んでいただきありがとうございました🐥

参考・引用記事

引用記事

1)Wikipedia 例外処理
2)[初心者向け] RubyやRailsでリファクタリングに使えそうなイディオムとか便利メソッドとか Exceptionをrescueするのではなく、StandardErrorをrescueする

参考記事

Discussion