KaQiita

新米エンジニアが適当なことを書いてます。温かく見守ってやってください。

【Ruby】カスタム例外クラスの活用法

はじめに

例外について、最近少し勉強しました。

特にプログラミングを学んだばかりの頃は、あまり例外に有り難みを感じなかったというか、重要性がよく分かっていなかったのですが、最近少しずつ分かってきました。

特にカスタム例外クラスを作る必要性を再認識したので、カスタム例外クラスについて今日は書こうと思います。

raise の挙動

まずは Ruby において例外を発生させる raiseソースコードを見てみます。

# raise
# raise(string)
# raise(exception [, string [, array]])
# 
# With no arguments, raises the exception in <code>$!</code> or raises
# a <code>RuntimeError</code> if <code>$!</code> is +nil+.
# With a single +String+ argument, raises a
# +RuntimeError+ with the string as a message. Otherwise,
# the first parameter should be the name of an +Exception+
# class (or an object that returns an +Exception+ object when sent
# an +exception+ message). The optional second parameter sets the
# message associated with the exception, and the third parameter is an
# array of callback information. Exceptions are caught by the
# +rescue+ clause of <code>begin...end</code> blocks.
# 
#    raise "Failed to create socket"
#    raise ArgumentError, "No parameters", caller
def raise(*several_variants)
  #This is a stub, used for indexing
end

使い方は以下の3通りがあるようです。

# raise
# raise(string)
# raise(exception [, string [, array]])

ソースコードや公式リファレンスに書いてある説明によると、1つ目の引数がない raise は同スレッドの同じブロック内で最後に rescue された 例外、もしくは RuntimeError を発生させます。

2つ目の string を引数に渡した raise は、引数に渡した string をエラーメッセージとした RuntimeError を発生させます。

3つ目の raise は、引数に渡した例外クラス exception を発生させます。ここで引数に渡す例外クラスは Ruby で既に用意されたものだけでなく、自分で作ったカスタム例外クラスを渡すこともできます。

カスタム例外クラスの作り方

では、そのようなカスタム例外クラスはどのように作るのでしょうか。その作り方は簡単です。

raise の引数に渡せるのは例外クラスなので、自分で作ったクラスを引数で渡すには、そのクラスは Ruby の標準例外クラスのどれかを継承している必要があります。

Ruby の標準例外クラスは全て Exception クラスを継承しているので、以下のようにとりあえず Exception クラスを継承させればカスタム例外クラスは一応完成です。

class TooYoungError < Exception
end
raise TooYoungError #=> TooYoungError (TooYoungError)

しかし一般的には Exception クラスではなく、以下のように StandardError クラスを継承させます。

class TooYoungError < StandardError
end
raise TooYoungError #=> TooYoungError (TooYoungError)

これは例外を捕捉したいときに使う rescue のデフォルトの挙動が StandardError かその子クラスを捕捉するためです。Exception クラスを継承したカスタム例外クラスは、rescue でデフォルトでは捕捉されません。

ただ、もうこれだけでカスタム例外クラスの完成です。

カスタム例外クラスの活用法

ただ単に StandardError を発生させるより、カスタム例外クラスを作って発生させる方がはるかにデバッグがしやすくなります。

これは例えば、カスタム例外クラスを使わなければ、以下のようにエラーログには

StandardError (StandardError)

としか表示されないのが、カスタム例外クラスを使うことで、

TooYoungError (TooYoungError)

と表示されて、どこでどんなエラーが発生しているのかが一目で分かるようになるためです。

またカスタム例外クラスを少し工夫して作ることで、さらに詳細なエラーログを出すことができます。

先程の raiseソースコードに書いてある説明によると、raise の第一引数はエラークラスだけでなく、エラークラスのオブジェクトも渡すことができるそうです。

例えば、TooYoungError を以下のように修正したとします。

class TooYoungError < StandardError
  attr_reader :age

  def initialize(age)
    @age = age
    super("too young age: #{@age}")
  end
end

すると、以下のように raise を使うことができます。

raise(TooYoungError.new(age)) if age < 18

age = 10 のとき、以下のようなエラーログが出力されます。

too young age: 10 (TooYoungError)

最初の StandardError (StandardError) だけのときより、はるかに詳細な情報が出力されていることが分かると思います。

終わりに

以上、カスタム例外クラスの活用法のお話でした。

作るのが面倒で、私も普段はよく考えずに StandardError を吐かせてしまいがちですが、これを機にちゃんとしようと思いました。

参考文献