1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

早期リターンの好き嫌い

Last updated at Posted at 2021-07-02

コードレビューで自分が早期リターンがあんまり好きじゃないなぁという話をして、そういえばなんであんまり好きじゃないんだろうと自問自答してみたことをまとめてみます。

好き嫌いの範疇なので、早期リターンが絶対悪だとは思いませんし、絶対善だとも思いません。使おうが使うまいが好きにすればいいと思いますし、使うといいシチュエーションも、使うとイマイチなシチュエーションもあると思います。

自分なりにOKなパターン

ガード節

早期リターンするからこそのガード節ですから、これはOKです。

def func(params)
  return if (おかしなパラメータ判定)
  :
end

メモ化

メモ化も早期リターンするからこそ速度メリットがでるわけですから、まあOKですね。

def func
  return @_func if defined?(@_func) 
  @_func = (なんか処理)
end

でもこう書きたいですけどね。なんか処理 が nil 返すとメモ化出来ないって罠がありますけれど。

def func
  @_func ||= (なんか処理)
end

エラー処理

エラー時もそれ以上処理を続ける意味がありませんから早期リターンしていいと思います。普通は例外処理でやっちゃいますから、エラーでリターンするって事も今どきはないですけどね。

def func
  result = (なんか処理)
  return unless result
  :
end

自分なりにイマイチなパターン

同格の処理を同格に扱ってない

条件によって処理Aと処理Bのどちらかを実行する場合があったとします。

def func
  if (条件)
    (処理A)
  else
    (処理B)
  end
end

これを早期リターンでは以下のように書くわけです。

def func
  if (条件)
    (処理A)
    return
  end
  (処理B)
end

早期リターンだと処理Aと処理Bを同格に扱ってないのが自分としては好きではない。コードから、処理Aと処理Bが同格であるという情報が欠落してしまっている。

論理が反転する

def func
  :
  if (条件)
    (処理)
  end
end

を早期リターン使うと

def func
  :
  return unless (条件)
  (処理)
end

って書くわけですが、条件分岐の論理が反転するので、それを脳内で補完するのが面倒です。

条件と処理が結びついているとしたら、それが分離されてしまうのもイマイチです。

早期リターンのメリットに対して

インデント浅くできる

まあわかる。インデントがめっちゃ深くなってるなら、早期リターンで浅くするってのもやむ無しかな。でもそれってもしかしてメソッド分割するべきってサインなのかもしれないとも思ったり。

ここから先はもう読まなくていいと判断出来て脳の負担が軽くなる

早期リターンできる条件の処理だけを追ってるならそうだけど、メソッド全部を理解しなきゃいけないなら楽になってないと思う。

例を追加

(2021.10.1追記)

ただの蛇足ですが、まあ例は多い方が分かりやすいかなと。

例えば次のコードがあったとします。

def func n
  case n
  when 1
    "one"
  when 2
    "two"
  else
    "other"
  end
end

早期リターンだとこんな感じに書き変えるわけですが、個人的にはこれが読みやすくなったとは思えません。例が悪いかもしれませんが。

def func n
  return "one" if n == 1
  return "two" if n == 2
  "other"
end

そもそもこの例ですとハッシュテーブル使った方が分かりやすいとは思いますけれどね。

def func n
  {1 => "one", 2 => "two"}[n] || "other"
end

更に例を追加

(2022.4.29追記)

最後の条件にしか適用できない

def func
  if (条件A)
    (処理A)
  end
  if (条件B)
    (処理B)
  end
  if (条件C)
    (処理C)
  end
end

複数の条件/処理があっても、早期リターンは 条件C/処理C にしか適用できません。これ、ほんとにコードが見やすくなってますか。

def func
  if (条件A)
    (処理A)
  end
  if (条件B)
    (処理B)
  end
  return unless (条件C)
  (処理C)
end

後ろに処理を追加できない

def func
  if (条件)
    (処理)
  end
end

を早期リターンを使って書き換えたとします。

def func
  return unless (条件)
  (処理)
end

ここで func の最後に常に行わないといけない処理が追加される場合どうなるでしょう。元のコードではただ単に処理を追加するだけですね。

def func
  if (条件)
    (処理)
  end
  (追加処理)
end

一方、早期リターンを使ってる場合はこんなことになってしまいます。

def func
  unless (条件)
    (追加処理)
    return
  end
  (処理)
  (追加処理)
end

更に更に例を追加

(2022.5.19追記)

大域脱出は個人的にOKです。そもそも大域脱出を早期リターンとは言わないような気もしますけど。

def func
  10.times do |i|
    10.times do |j|
      return (なんか条件)
    end
  end
end

そして今回ぐぐって初めて知ったんですが、Ruby には大域脱出メソッドがあるんですね。

まとめ

(2022.5.19追記)

いろいろ例を挙げてみて自分なりの考えが少し整理できたのでまとめておきます。

まず前提としてあるのは、一つのメソッドは一つの機能だけを実現するということです。二つ以上の機能をまとめちゃうと、それはスパゲッティまっしぐらですので推奨されません。

一つの機能の前処理として、パラメータが不正であったりとか、準備処理に失敗したとかでリターンするのは構わないと思います。でもそれは自分の中では早期リターンとは別物という扱いです。

メソッドが一つの機能だけを実現しているのに途中でリターンできてしまうということは、それは実は機能は一つではなく複数あるのではないか、という疑いですね。実は複数機能あるのだとしたら、やるべきことは早期リターンではなくメソッド分割でしょう。

もちろんこれは新規に自分が書き下ろすコードの場合の話です。引き継いだコードが巨大な一枚岩メソッドになっていて、リファクタリングするためにとりあえず早期リターンを入れていくなどはアリだと思います。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?