Rubyで任意のオブジェクトを例外としてraiseする方法
Rubyのraise構文には、発生させたいエラーの種類を指定するための書き方がいくつかあります。以下のコードでは、RuntimeErrorを発生させる3つの方法を紹介します。
raise "hello"
raise RuntimeError, "hello"
raise RuntimeError.new("hello")
# ...上記はすべて "RuntimeError: hello" という結果になる
ここまでは便利ですが、例外以外のものを発生させたい場合はどうなるでしょうか?たとえば数値をraiseしたいとしたら?残念ながら、Rubyはそれを許してくれません。次のようなエラーメッセージが表示されます。
raise 1
# TypeError: exception class/object expected
このエラーメッセージを見ると、raiseは例外クラスまたは例外オブジェクトを引数として受け取る仕組みのように思えます。しかし、実はそれは誤解なのです!
exceptionメソッドの紹介
raise fooと書いた場合、raiseメソッドが期待しているのは、foo自体が例外オブジェクトであることではなく、foo.exceptionを呼び出した結果が例外オブジェクトであることです。
覚えておくべき重要なポイントは、exceptionメソッドが例外を返すのであれば、raiseには「何でも」渡せるということです。
つまり、気合を入れれば、Rubyの数値クラスにモンキーパッチを当てて、数値をraiseできるようにすることも可能です。そのコードは以下のようになります。
class Fixnum
def exception
RuntimeError.new("I'm number: #{ self }")
end
end
raise 42
# ...raise_number.rb:7:in `<main>': I'm number: 42 (RuntimeError) となる
※なお、FixnumはRuby 2.4以降Integerに統合されたため、現在の環境で試す場合はIntegerを使用してください。
これは面白いパーティー芸ではありますが、実際の開発で役立つ場面はあるのでしょうか?筆者が考えるこのテクニックの主な実用用途は、「例外を組み立てるロジック」と「例外を発生させるかどうかを判断するロジック」を分離することです。確かにかなり特殊なユースケースではありますが、具体的にどのような形になるのか見ていきましょう。
実用的になり得る具体例
何らかのIOから1行分のデータを読み込みたいとします。ネットワークIOかもしれませんし、ファイルかもしれません。どちらでも構いません。ここでは単純に、データを読み込んでそれが有効かどうかを確認したいだけです。
読み込んだデータが無効だった場合に例外を発生させたいのですが、その例外は入力元に応じて適切な内容にする必要があります。ネットワーク接続には、ローカルファイルとは異なるデバッグ情報が求められるからです。これを実現するには、入力クラスごとにカスタムのexceptionメソッドを定義します。擬似Rubyコードで表すと、以下のようなイメージです。
# これら3つのクラスは、それぞれ異なる例外を持つ異なる種類のIOを表す
class NetworkConnection
...
def exception
NetworkConnectionError.new(url: url, ...)
end
end
class LocalFile
...
def exception
FileError.new(path: path, ...)
end
end
class UnixPipe
...
def exception
PipeError.new(...)
end
end
def read_all(*items)
items.each do |item|
if item.readline != "foo"
# item を raise すると、それぞれに応じた適切な例外クラスが使われる
raise item
end
end
end
read_all(NetworkConnection.new(url: "example.com"), LocalFile.new("/something"), UnixPipe.new)
このアプローチの利点は、read_all側が入力元の種類を意識しなくても済む点にあります。例外の詳細(URLやファイルパスなど)は各クラス自身が最もよく知っているため、例外生成の責務を各クラスに委譲できるのです。raiseの柔軟な仕様を理解しておくと、こうした設計の選択肢が広がります。
-
【Ruby入門】any?・all?・none?・one?メソッドで条件判定をスマートに書く方法
今回は、配列やハッシュなど、Enumerableモジュールをincludeしているオブジェクトに対して条件判定を行う際に役立つ、4つの便利なメソッド「all?」「none?」「any?」「one?」について解説します。 これらのメソッドは、いずれもtrueまたはfalseを返します。 それでは始めましょう! Rubyのall?メソッド 例えば、「配列内のすべての文字列が特定のサイズであるか」を確認したい場面を考えてみましょう。 素朴に書くと、このようになります: def all_words_have_specific_size?(words) return false if words.
-
RubyのStructとOpenStructの使い方を徹底解説!値オブジェクトを簡単に作る方法
Rubyには、関連する属性をまとめて保持する「値オブジェクト」を手軽に作れるStructという組み込みクラスが用意されています。本記事では、Structの基本的な使い方から注意点、そしてOpenStructとの違いまで詳しく解説します。 そもそもStructとは何か? StructはRubyに組み込まれたクラスで、新しいクラスを動的に生成し、値オブジェクト(Value Object)を作成するために使われます。値オブジェクトとは、関連性のある複数の属性をひとまとめにして扱うためのオブジェクトです。 具体例を挙げてみましょう。 2つの座標(xとy)を持つPointというデータを表したい場合、この