🌊

早期リターンを実践する

2023/02/06に公開

TL;DR

  • 出口は一か所にすべきは古い。
  • 条件がそろっていればなるべく早くreturnを実施する。

具体的な例

極端な例を次に示します。
こんな実装はしないと感じるかもしれませんが、実際に現場では散見されます。

public String returnStuff(SomeObject argument1, SomeObject argument2) {
  if (argument1.isValid()) {
    if (argument2.isValid()) {
      SomeObject otherVal1 = doSomeStuff(argument1, argument2)

      if (otherVal1.isValid()) {
        SomeObject otherVal2 = doAnotherStuff(otherVal1)

        if (otherVal2.isValid()) {
          return "Stuff";
        } else {
          return "None";
        }
      } else {
        return "None";
      }
    } else {
      return "None";
    }
  } else {
    return "None";
  }
}
  1. argument1が有効な場合は処理を継続
  2. argument2が有効な場合は処理を継続
  3. 引数を関数に渡し、otherVal1を作成
  4. otherVal1が有効な場合は処理を継続
  5. otherVal1を関数に渡し、otherVal2を作成
  6. otherVal2が有効な場合は"Stuff"を返却
  7. 各条件に適合しない場合は"None"を返却

早期リターンの実践

処理を整理して早期リターンを実装してみましょう。

public String returnStuff(SomeObject argument1, SomeObject argument2){
  if (!argument1.isValid()) {
    return "None";
  }

  if (!argument2.isValid()) {
    return "None";
  }

  SomeObject otherVal1 = doSomeStuf(argument1, argument2);

  if (!otherVal1.isValid()) {
    return "None";
  }

  SomeObject otherVal2 = doAnotherStuff(otherVal1);

  if (!otherVal2.isValid()) {
    return "None";
  }

  return "Stuff";
}
  1. argument1無効な場合は"None"を返却
  2. argument2無効な場合は"None"を返却
  3. 引数を関数に渡し、otherVal1を作成
  4. otherVal1無効な場合は"None"を返却
  5. otherVal1を関数に渡し、otherVal2を作成
  6. otherVal2無効な場合は"None"を返却
  7. すべての処理を通過した場合は"Stuff"を返却

処理を整理することで何が行われているか、わかりやすくなりました。

関数の出口は一か所であるべき?

早期リターンの話をすると必ずこの議論になります。
Cやアセンブリのような明示的なリソース管理を行う言語の場合、出口を実装するたびにリソースの解放が必要でした。
そのため、出口を一か所にする必要がありました。
モダンな言語ではこのような問題は起こりません。
リソースの管理(ファイルを開いて、処理して、閉じるなど)がある場合は、Javaであればfinally、Pythonであればwithなどを活用できます。
また、デバッグがしにくい等の指摘も正しくありません。
コードの理解しやすさを重視し、適切にreturnを実施しましょう。

どのような時に適用すべきか

とはいえ、闇雲にreturnを実装するというのは誤りです。
いくつかの考え方を示します。

Fail Fast

早期リターンの基礎となっている考え方で終了する条件をみつけることにフォーカスします。

ガード節

前述の例のように後続の処理が失敗する条件の判定を先に実施し、早期リターンを行います。

やりすぎに思えてきたら設計を見直す

たとえば、200行の関数に10個のreturnがあることを想像してみましょう。
とても理解しやすいコードとは思えません。
これは早期リターンを実施し無くても10個のreturnの代わりに10個のエラー処理が紛れ込んでいるので、同じことです。
このような場合は適切に関数の役割が分割されていなかったりすることが原因です。
引数のチェックは別の関数に切り出すなど設計を見直すことを検討してください。

まとめ

  • 「関数の出口は一か所であるべき」はモダンな言語では適用されない
  • 条件を整理し、ガード節を実装する
  • 出口が増えすぎるなら関数の設計が適切か、分割できなかなど検討する
株式会社ソルクシーズ(事業戦略室)

Discussion