Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

Rubyはあなたからエラーを隠している!?知っておきたい例外の落とし穴

Rubyは、一部のエラーや例外を意図的に隠すことがあります。

これには便利な側面もあります。たとえば Kernel#loop メソッドにブロックを渡して使う場合、StopIteration 例外が発生すると loop はそこで終了します。

しかし、それ以外の場面では、この仕様がデバッグ作業を一気に難しくしてしまうこともあるのです。

具体的な例を見ていきましょう。

隠される例外その1:Comparableモジュールと「<=>」メソッド

最初の例は、Comparable モジュールと <=> メソッドに関するものです。

以下がそのコードです

class MyObject
  attr_accessor :value

  include Comparable

  def initialize(value)
    @value = value
  end

  def <=>(other)
    raise ArgumentError, "can't compare #{other.class} with #{self.class}" unless other.is_a?(MyObject)

    value <=> other.valuee
  end
end

mo1 = MyObject.new(10)
mo2 = MyObject.new(10)

p mo1 == mo2

このコードを順番に見ていきましょう。

まずvalue というアクセサを持つ MyObject クラスを定義し、Comparable モジュールを include しています。Comparable を取り込むことで、==<> といった比較メソッドが使えるようになります。

これらの比較メソッドは、すべて <=> メソッドを基盤として動作しています。ちょうど Enumerable の各メソッドが each メソッドを基盤としているのと同じ関係です。

次に、同じ値(10)を持つ2つのオブジェクトを生成しています。ここで重要なのは、値が同じでも別々のオブジェクトだという点です。

さて、この2つのオブジェクト mo1mo2 を比較すると……結果は false になります。

なぜでしょうか?

実は <=> メソッドの中にバグがあるのですが、Rubyがそのエラーを隠してしまっているのです!

よく見てください。エラーを見つけられますか?

見つけられたなら素晴らしい!見つからなくても大丈夫です🙂

エラーの正体はこちら

value <=> other.valuee

valuee に気づきましたか?そう、タイプミスなのです!

通常であれば NoMethodError が発生するので、問題の原因にすぐ気づけるはずです。しかしこの例ではそうなりません。

朗報なのは、Ruby 2.3以降ではこの挙動が変わり、エラーが隠されなくなったことです。まだ古いバージョンのRubyを使っているなら、アップグレードを検討するもう一つの理由になるでしょう。

隠される例外その2:数値オブジェクトとcoerceメソッド

もう一つの例は、Numeric オブジェクト(FloatInteger)と coerce メソッドの組み合わせです。

以下がその例です

class MyObject
  attr_accessor :value

  def initialize(value)
    @value = value
  end

  def +(other)
    other = MyObject.new(other) if other.kind_of?(Numeric)

    value + other.value
  end

  def coerce(other)
    mo = MyObject.new
    mo.valuee = other

    [mo, self]
  end
end

mo1 = MyObject.new 10
mo2 = MyObject.new 10

p mo1 + mo2
# 20

p mo1 + 20
# 30

こちらも MyObject クラスですが、新たに +coerce というメソッドが追加されています。

coerce メソッドを使うと、互換性のない2つの型を同じ型に変換し、一緒に演算できるようにできます。

このクラスは、秒数や価格など、何らかの数値を表すものと考えてください。

そして、次のような演算ができるようにしたいわけです

mo1 + 20
20 + mo1

前者の mo1 + 20 は簡単です。自分のクラスの + メソッドは自由に実装できるからです。

しかし、Integer クラス側はどうでしょう?

Integer の + メソッドを書き換えて対応することも不可能ではありませんが、それはあまり良いアイデアとは言えません🙂

解決策は?

自分のクラスに coerce メソッドを実装することです。

Integer+ メソッドは、相手のオブジェクトが coerce を実装しているかどうかをチェックし、実装されていればそれを呼び出します。

さて、冒頭のコード例を思い出してください。次のコードを実行すると

20 + mo1

mo1 の値は 10 なので、結果は 30 になってほしいところです。しかし実際に表示されるのは、次のエラーです:

MyObject can't be coerced into Fixnum

先ほどと同じパターンですね!

coerce メソッドの中でエラーが隠されています。問題の箇所は mo.valuee = other の部分です。

ここでもまたタイプミスをしているのですが、エラーメッセージはそのことを教えてくれません!

MyObject can't be coerced into Fixnum

こちらにも朗報があります。この挙動はRuby 2.5で変更されるため、隠されたエラーがまた一つ減り、心配する必要がなくなります。

まとめ

このように、例外を隠すことの危険性を示す好例となっています。例外についてさらに深く学びたい方は、著者の書籍『Ruby Deep Dive』も参考にしてみてください。

この記事が役に立ったら、ぜひ友人にもシェアして、より多くの人に届けていただけると嬉しいです。

最後までお読みいただき、ありがとうございました!

  1. Rubyのエイリアス(別名定義)完全ガイド:aliasキーワードとalias_methodの違い

    Rubyでは、既存のメソッドに別名(エイリアス)を付ける方法が2つあります。 alias(キーワード) alias_method(メソッド) どちらも同じ目的で使えますが、挙動が微妙に異なるため、初心者にとって混乱しやすいトピックです。 本記事では、両者の違いを詳しく掘り下げ、しっかりと理解できるように解説していきます。 aliasキーワードとは まずはaliasから見ていきましょう。aliasはRubyのキーワードの一つです(ifやdef、classなどと同じ扱いです)。 基本的な書き方は以下の通りです。 alias print_something puts print_someth

  2. MacのSpotlight検索からフォルダーを非表示にする2つの方法

    Macの中には、他人に見られたくないファイルやフォルダーがあるものです。個人の資産関係の書類や、まあ…その他いろいろなものも含めて。こうしたファイルが第三者の目に触れる経路の一つが、AppleがmacOSに標準搭載しているシステム全体検索機能「Spotlight」です。ツールバーからすぐに呼び出せる便利な機能である反面、見せたくないものまで引っかかってしまうこともあります。 幸い、特定のフォルダーをSpotlightの検索結果に表示させないようにする方法はいくつかあります。個人情報の漏えいや思わぬ恥ずかしい場面を防ぐためにも、ぜひ覚えておきましょう。 方法1:システム環境設定のプライバシー機