パフォーマンス向上のためにメモ化を利用するケースがあります。
メモ化とは、処理結果を保存しておき、同じ処理を再実行しないようにするテクニックです。
例えば以下のようなインスタンスメソッドを定義することで、処理の実行回数を1回におさえることができます。
def mails
@mails ||= fetch_mails
end@mails が nil または false の場合に fetch_mails を実行し、その結果を @mails に代入します。@mails はインスタンス変数なので、宣言していなくてもアクセスができ、nil が返されます。
よって mails メソッドは以下のように動作し、何回呼び出しても fetch_mails は1回しか呼び出されず、パフォーマンス向上がはかれます。
- 初回:
@mailsがnilを返すことからfetch_mailsが実行される - 2回目以降:
@mailsには値があるので、その値を返しfetch_mailsは実行されない
一見シンプルで読みやすいこの実装ですが、実は落とし穴があります。
||= の落とし穴
それはfetch_mails の結果が nil か false である場合です。@mails が falsy な値になってしまうので、||= では毎回再評価されてしまいます。
なので、メモ化したい場合は defined? や instance_variable_defined? を使うようにしましょう。
defined?
defined? は Ruby のキーワードであり、定義されていればその種別の文字列を返します。
defined? @hoge
=> nil
@hoge = nil
=> nil
defined? @hoge # ? がついてますが、定義されていたら文字列を返します!
=> "instance-variable"そのため、以下のようにメモ化できます。
def mails
return @mails if defined? @mails
@mails = fetch_mails
end参考: https://docs.ruby-lang.org/ja/latest/doc/spec=2fdef.html#defined
instance_variable_defined?
こちらは Object クラスに定義されているメソッドで、インスタンス変数として宣言されているかを bool 値で返します。
instance_variable_defined? :@hoge
=> false
@hoge = nil
=> nil
instance_variable_defined? :@hoge
=> trueよってこちらも以下のようにメモ化できます。
def mails
return @mails if instance_variable_defined? :@mails
@mails = fetch_mails
end参考: https://docs.ruby-lang.org/ja/latest/method/Object/i/instance_variable_defined=3f.html
どちらを使うべきか
どちらでも ||= この問題は解決できます。
強いて挙げるとするならば defined? の方が個人的にはおすすめです。
- よりシンプルに書ける
- キーワードなのでメソッド呼び出しのオーバーヘッドがない(大差ないと思いますが…)

