RubyonRailsでは似たようなメソッド(演算子)でtry、try!、&.の3種類を使用できます。
どれを使うか悩んでいる方のために結論から書くと、
以下の理由から「&.」を使用するのが最も良いと考えています。
- tryはNoMethodErrorを握りつぶしてしまうので、極力使用しないのがベター
- try!より&.の方が使える環境が多い(try!はRails、&.はRuby)
- try!より&.の方が高速
- try!より&.の方がコードの記述量が少なく可読性が高い
ではそれぞれのメソッド(演算子)とその理由について詳細を説明します。
try
tryはメソッドが定義されていない場合やオブジェクトがnilの場合に、その結果がnilとなるように返してくれます。オブジェクトがnilでなく、メソッドが定義されている場合はそのメソッドを呼び出します。
# Stringオブジェクトのto_iメソッドを呼び出している
irb(main):313:0> "hoge".try(:to_i)
=> 0
# Stringオブジェクトに定義されていないメソッドはnilになる
irb(main):314:0> "hoge".try(:to_fuga)
=> nil
# nilに対して行うとnilになる
irb(main):318:0> nil.try(:to_i)
=> nil
APIdoc: https://apidock.com/rails/Object/try
try!
try!は基本的にtryと同じですが、メソッドが存在しない場合にNoMethodErrorが発生します。
# tryと同じ挙動
irb(main):319:0> "hoge".try!(:to_i)
=> 0
# メソッドがない場合はNoMethodErrorが起きる
irb(main):322:0* "hoge".try!(:to_fuga)
NoMethodError: undefined method `to_fuga' for "hoge":String
Did you mean? to_f
from (irb):322
# tryと同じ挙動
irb(main):323:0> nil.try!(:to_i)
=> nil
APIdoc: https://apidock.com/rails/Object/try!
&. (ぼっち演算子)
&.はぼっち演算子と呼ばれています。挙動はtry!と同じです。
Ruby2.3で追加されました。
# try!と同じ挙動
irb(main):001:0> "hoge"&.to_i
=> 0
# try!と同じ挙動
irb(main):002:0> "hoge"&.to_fuga
NoMethodError: undefined method `to_fuga' for "hoge":String
Did you mean? to_f
from (irb):2
# try!と同じ挙動
irb(main):003:0> nil&.to_i
=> nil
考察
tryは使わない方が良さそう
「tryを使えばNoMethodErrorが出ないし安全!」とも思えますが、オブジェクトやメソッドがない場合にエラーとせずすべてnilとしてしまうので、一見正しく動いたように見え、バグに気づかない恐れがあります。
また、NoMethodErrorについてはそもそも存在しないメソッドを呼び出している時点で期待していたオブジェクトと異なるクラスのオブジェクトが設定されてたり、メソッドが実装されていなかったりとよろしくない状態なので、実装を見直した方が良いです。
よって、基本的にtryは使わない方が良いと考えています。
もちろんこれらを理解したうえでtryが適していると判断したのであれば問題ありませんが、思考停止になってとりあえずtryをつけているパターンは危ないので見直した方が良いでしょう。
try!と&.のどっちを使うか
どちらも同じ挙動ですが、&.はRailsを使わないRuby純正での開発でも使用できます。
また、性能面から見てもtry!よりも&.の方が高速なためオススメです。
適当に10000000回ループして掛かった時間を測るメソッドを作成しました。
# 10000000回ループしてtry!と&.のそれぞれの時間を測定する
def calc_try_time
roop_count = 10000000
p "try!の時間を測ります"
p "オブジェクトがnilでない場合"
start_time = Time.now
roop_count.times do
"hoge".try!(:to_i)
end
end_time = Time.now
p "掛かった時間: #{end_time - start_time}sec [start:#{start_time}][end:#{end_time}]"
p "オブジェクトがnilの場合"
start_time = Time.now
roop_count.times do
nil.try!(:to_i)
end
end_time = Time.now
p "掛かった時間: #{end_time - start_time}sec [start:#{start_time}][end:#{end_time}]"
p "======================="
p "&.の時間を測ります"
p "オブジェクトがnilでない場合"
start_time = Time.now
roop_count.times do
"hoge"&.to_i
end
end_time = Time.now
p "掛かった時間: #{end_time - start_time}sec [start:#{start_time}][end:#{end_time}]"
p "オブジェクトがnilの場合"
start_time = Time.now
roop_count.times do
nil&.to_i
end
end_time = Time.now
p "掛かった時間: #{end_time - start_time}sec [start:#{start_time}][end:#{end_time}]"
end
結果はこちら
"try!の時間を測ります"
"オブジェクトがnilでない場合"
"掛かった時間: 3.428868sec [start:2018-11-01 22:17:13 +0900][end:2018-11-01 22:17:17 +0900]"
"オブジェクトがnilの場合"
"掛かった時間: 1.61653sec [start:2018-11-01 22:17:17 +0900][end:2018-11-01 22:17:18 +0900]"
"======================="
"&.の時間を測ります"
"オブジェクトがnilでない場合"
"掛かった時間: 1.190159sec [start:2018-11-01 22:17:18 +0900][end:2018-11-01 22:17:19 +0900]"
"オブジェクトがnilの場合"
"掛かった時間: 0.40141sec [start:2018-11-01 22:17:19 +0900][end:2018-11-01 22:17:20 +0900]"
今回の場合、try!よりも&.の方が1/3程度高速なことが確認できました。
その他にも&.のメリットとしては、try!と比較してコードの記述量が少ないので、可読性が高いと言えます。
まとめ
以上から、特段な理由がない限り、&.を使用するのが良さそうです!