Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Rails の try メソッドと Ruby のぼっち演算子 '&.' の違いを理解する(翻訳)

概要

元サイトの許諾を得て翻訳・公開いたします。

日本語タイトルは内容に即したものにしました。

Railsのtry メソッドとRubyのぼっち演算子 '&.' の違いを理解する(翻訳)

RubyやRailsで、メソッドチェインのどれかが万一nilを返すことがあっても、NoMethodErrorをraiseせずに安全にオブジェクトを扱う必要が生じることがよくあります。

原文お知らせ

Ruby T-Shirts logo
Developer, Movie & Gaming t-shirts printed (on demand) and sent around the world.\
Shop now →

RailsはActive Supportコア拡張でtryメソッドを提供していますが、Rubyにも&.という通称「ぼっち演算子」が存在します(Ruby 2.3以降)。

両者をどう使い分けるか、そして両者の微妙な違いがどこにあるかを理解することで、よりクリーンで堅牢なコードを書けるようになります。

🔗 以下のように書くよりも

条件をびっしりチェックしたり、rescueブロックを書く。

def display_username(user)
  if user && user.profile && user.profile.name
    user.profile.name
  else
    "Anonymous"
  end
end

# または(さらに悪い!)
def display_username(user)
  user.profile.name
rescue NoMethodError
  "Anonymous"
end

🔗 以下のように書こう

tryまたは&.で書く。

# tryで書く場合
def display_username(user)
  user.try(:profile).try(:name) || "Anonymous"
end

# &.で書く場合
def display_username(user)
  user&.profile&.name || "Anonymous"
end

# 両方使うことも可能!
def display_username(user)
  user.try(:profile)&.name || "Anonymous"
end

🔗 そうする理由

コード例で示したように、try&.は主にコードをわかりやすくするのに使われます。読みやすさを損なわずにコードが簡潔になります。

🔗 そうしない理由があるとすれば

どちらの構文を使ったとしても、メソッドチェインが長くなる場合があります。これは、try&.を使うよりも、コードをリファクタリングすべきであるという兆候です。

🔗 「結局どちらを使えばいいの?」

try&.も、nilを返す可能性のあるオブジェクトで安全にメソッドを呼び出すときに利用できる点は同じです。
ただし、重要な違いがいくつかあります。

  • Railsのtryは、メソッドが存在しない場合にnilを返します
  • Rubyの&.は、メソッドが存在しない場合にNoMethodErrorをraiseします
# Userクラスにemailメソッドがあるとする

User.new&.email
#=> ""

User.new.try(:email)
#=> ""

User.new&.email_2
#=> NoMethodError: undefined method `email_2' for an instance of User

User.new.try(:email_2)
#=> nil

これは見ようによってはこうも考えられます。

  • #tryは、NilClassに対してどんなメソッドを呼び出したとしても(メソッドが存在するかどうかにかかわらず)、nilを返す
  • &.は、レシーバーがnilの場合はnilを返す

つまり、存在しない可能性のあるメソッドを呼び出して発生するエラーを握りつぶしたい場合は、エラーに寛容な#tryを使う必要があります。

逆に、受け取るオブジェクトで指定のメソッドが定義されていない場合にNoMethodErrorをraiseしたければ、&.を使います。

どちらを選ぶかは、プロジェクトの規約やエラー処理戦略次第ですが、たとえRailsを使っているとしても、Rubyがネイティブでサポートしている &.の方が構文がシンプルなので基本的にはこちらを使う方がよいでしょう。
大事なのは、try&.の違いを理解したうえでユースケースに最適な方を選ぶことです。

try&.でパフォーマンスの違いが生じる可能性が高そうですが、実際のコードでベンチマークを行ったうえでどちらが速いかを決定しましょう。
どちらも長年に渡って最適化とテストが十分に行われているので、アプリケーションコードで測定値に現れるほどの違いが生じるには、よほど実行頻度の高い「ホットな」コードパスである必要があるでしょう。

🔗 構文の違い

#tryに引数を渡すと、その引数は今呼び出しているメソッドに渡されます。object.try(:method, arg1, arg2)の場合、arg1arg2:methodに渡されます。

一方、&.の場合は、object&.method(arg1, arg2)のように普通の(より魅力的な?)メソッド呼び出し構文を使うので、&.自身は引数を受け取りません。

また、#tryには引数のみならず、object.try { |obj| obj.do_something }のようにブロックも渡せますが、&.にはブロック渡しの構文はありません。

さらに、Railsには!付きの#try!メソッドという第3の選択肢もあり、存在しないメソッドを呼び出すと例外をraiseします。

🔗 こぼれ話

safe-navigation演算子と呼ばれている&.を、Rubyの作者であるMatzは「ぼっち演算子(lonely operator)」と呼んでいます。&.が並んでいる姿が、あたかもドットの横で人がさみしそうに床に座っているかのように見えるからだそうです。

関連記事

RailsのObject#tryがダメな理由と効果的な代替手段(翻訳)

Ruby: ぼっち演算子`&.`の落とし穴(翻訳)

Rubyのぼっち演算子はRailsの`Object#try`より高速(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。