🌟

[Ruby]メモ化の落とし穴

2025/01/19に公開

Daily Blogging29日目

普段プロダクトコードでメモ化してるメソッドをよく見かけるけど、ちゃんとメモ化について調べたことないぁ
ちょっと調べてみよう。

メモ化って何?

改めてメモ化がどういうものなのか調べてみる。

メモ化は、計算結果をキャッシュして再利用する手法です。
同じ計算を繰り返さずに済むため、処理速度が向上します。
特に再帰的な関数で効果を発揮し、効率的なプログラムを実現します。

リートンに3行でまとめてもらった。
要は同じ処理が何回も実行されないようにするためのテクニックの1つ

よくみるコード

よく見るというかこの書き方しか見てない

def current_user
  @current_user ||= User.find_by(email: @user_email)
  @current_user
end

「||=」を使ったメモ化
@current_userがnilなら、User.find_by(email: @user_email)を呼び出す。
つまり一回でもUser.find_by(email: @user_email)で値を取得していたら同じ処理が走らなくなってパフォーマンス上がるねぇってやつ

ここに落とし穴があるんですよ

言われてみれば確かにそりゃそうだって感じですが、ネットで調べるまで全然気づかなかった落とし穴がある。
「||=」って左辺がfalsyな値の時に、右辺の式を実行するんですよね

Rubyにおけるfalsyな値とは...

  • nil
  • false

さっきのcurrent_userの例でいうと、
User.find_by(email: @user_email)の処理でnilが返ってきた場合、
再度current_userメソッドを呼び出した時にまた同じDB操作が発生することになるっ!

つまり、
「メモ化してるからSQLは一回しか実行されないぜっ」なんて思ってたらそれは大間違いだということ

変数が定義済みかどうかで判定せよ

インスタンス変数の値を見て判定するとこうなっちゃうので
インスタンス変数がすでに定義されているのかどうかで判定しようっていうのが回避策
使えるメソッドは2つ

defined?

https://docs.ruby-lang.org/ja/latest/doc/spec=2fdef.html#defined
引数で渡したメソッドや変数が定義済みかどうかを返してくれる

def current_user
    unless defined?(:@current_user)
        @current_user = User.find_by(email: @user_email)
    end
    @current_user
end

instance_variable_defined?

https://docs.ruby-lang.org/ja/latest/method/Object/i/instance_variable_defined=3f.html
引数で渡した変数が定義済みかどうかを返してくれる

def current_user
    unless instance_variable_defined?(:@current_user)
        @current_user = User.find_by(email: @user_email)
    end
    @current_user
end

definedはメソッドにも使える+返す値はnilか変数の種類とかになるので
instance_variable_definedの方が用途的には合ってる気がする。

Discussion