早期リターンの好き嫌い

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

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

## 自分なりにOKなパターン

### ガード節

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

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

### メモ化

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

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

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

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

### エラー処理

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

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

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

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

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

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

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

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

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

### 論理が反転する

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

を早期リターン使うと

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

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

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

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

> インデント浅くできる

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

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

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

## 例を追加

(2021.10.1追記)

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

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

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

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

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

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

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

## 更に例を追加

(2022.4.29追記)

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

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

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

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

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

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

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

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

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

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

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

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

## 更に更に例を追加

(2022.5.19追記)

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

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

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

## まとめ

(2022.5.19追記)

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

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

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

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

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