web-dev-qa-db-ja.com

ベストプラクティス-関数呼び出しを囲む場合のラッピングvs関数内のガードの場合の早期終了の追加

これは非常にユースケース固有のものになる可能性があることは知っていますが、私はあまりにも頻繁にこれに疑問を感じています。一般的に推奨される構文はありますか?.

私は関数の中で最善の方法を尋ねているのではなく、早く終了するか、関数を呼び出さないかを尋ねています。

ラップif関数呼び出しの周り


if (shouldThisRun) {
  runFunction();
}

関数にifguard)がある

runFunction() {
  if (!shouldThisRun) return;
}

後者のオプションは、この関数が複数回呼び出された場合にコードの重複を減らす可能性があることは明らかですが、single-responsibility-ness関数。


例はここにあります

何かのステータスを更新するupdateStatus()関数がある場合。ステータスが変更された場合にのみステータスを更新したい。ステータスが変更される可能性のあるコード内の場所を知っています。また、ステータスが変更された他の場所を知っています。

私だけかどうかはわかりませんが、この関数をできるだけ純粋にしたいので、この内部関数をチェックするのはやや汚い気がします。この関数を呼び出すと、ステータスが更新されることが期待されます。しかし、変更を加えられていない可能性があることがわかっているいくつかの場所をチェックで呼び出しに含める方が良いかどうかはわかりません。

9
Matthew Mullin

ifを関数呼び出しの周りにラップする:
これは、関数を呼び出すかどうかを決定するためのものであり、プログラムの意思決定プロセスの一部です。

関数のGuard句(早期復帰):
これは、無効なパラメーターで呼び出されるのを防ぐためです

この方法で使用されるガード句は、関数を「純粋」に維持します(ユーザーの用語)。これは、誤った入力データによって関数が中断しないようにするためにのみ存在します。

関数を呼び出すかどうかについてのロジックは、たとえそれがたとえあったとしても、より高い抽象化レベルにあります。関数自体の外部に存在する必要があります。 DocBrownが言うように、コードを簡略化するために、このチェックを実行する中間関数を持つことができます。

これは良い質問であり、抽象化レベルの認識につながる一連の質問の範囲に含まれます。各関数は単一レベルの抽象化で動作する必要があり、プログラムロジックと関数ロジックの両方が関数内にあると不快に感じる-これは、抽象化のさまざまなレベル。関数自体は下位レベルです。

これらを分離しておくことで、コードの書き込み、読み取り、保守がより簡単になります。

15
Baldrickk

あなたは両方を持つことができます-パラメータをチェックしない関数と、このようにチェックする別の関数です(おそらく、呼び出しが行われたかどうかについてのいくつかの情報を返します):

bool tryRunFunction(...)
{
    bool shouldThisRun = /* some logic using data not available inside "runFunction"*/;
    if (shouldThisRun)
        runFunction();
    return shouldThisRun;
}

このように、再利用可能なtryRunFunctionを提供することでロジックの重複を回避し、内部でチェックを行わない元の(おそらく純粋な)関数を保持できます。

tryRunFunctionのような関数が必要な場合があることに注意してください。統合されたチェックのみを使用して、チェックをrunFunctionに統合できます。または、プログラム内の任意の場所でチェックを再利用する必要はありません。その場合は、チェックを呼び出し側の関数に残しておくことができます。

ただし、関数に適切な名前を付けることにより、呼び出し側に何が起きるかを透過的にするようにしてください。したがって、呼び出し側は、自分でチェックを行う必要がある場合、または呼び出された関数がすでにチェックを行う場合、実装を推測したり調べたりする必要はありません。多くの場合、tryのような単純な接頭辞で十分です。

7
Doc Brown

誰が実行するかを決定することに関しては、答えは [〜#〜] grasp [〜#〜] から、誰が知っている「情報専門家」であるかです。

決定したら、わかりやすくするために関数の名前を変更することを検討してください。

このような何か、関数が決定した場合:

 ensureUpdated()
 updateIfDirty()

または、発信者が決定することになっている場合:

 writeStatus()
3
user949300

@Baldrickkの答えを詳しく説明します。

あなたの質問に対する一般的な答えはありません。呼び出される関数の意味(契約)と条件の性質によって異なります。

したがって、updateStatus()を呼び出す例のコンテキストでそれを説明しましょう。ステータスに影響を与える何かが発生したため、契約はおそらく一部のステータスを更新することです。そのメソッドへの呼び出しは、実際のステータス変更がなくても許可され、実際の変更がある場合に必要になることを期待します。

したがって、呼び出し側のサイトは、ドメインの範囲内で関連する変更が何もないことを知っている場合、updateStatus()の呼び出しをスキップできます。これは、呼び出しを適切なif構成体で囲む必要がある状況です。

updateStatus()関数内では、この関数が(ドメインホライズン内のデータから)何もすることがないことを検出し、そこから早期に戻る必要がある場合があります。

したがって、質問は次のとおりです。

  • 外部から見ると、関数のコントラクトを考慮して、関数を呼び出すことが許可/要求されるのはいつですか?
  • 関数内で、実際の作業なしで早期に戻ることができる状況はありますか?
  • 関数を呼び出すかどうか、早期に戻るかどうかの条件は、関数の内部ドメインまたは外部のどちらに属しますか?

updateStatus()関数を使用すると、両方が表示され、何も変更されていないことがわかっているサイトを呼び出し、呼び出しをスキップし、実装が「何も変更されていない」状況を早期にチェックします。状態の一部は、内部と外部の両方で2回チェックされます。

2
Ralf Kleberhoff

多くの良い説明があります。しかし、私は変わった見方をしたいのですが、次のように使用するとします。

if (shouldThisRun) {
   runFunction();
}

runFunction() {
   if (!shouldThisRun) return;
}

そして、次のようにrunFunctionメソッドで別の関数を呼び出す必要があります。

runFunction() {
   if (!shouldThisRun) return;
   someOtherfunction();
}

あなたは何をしますか?すべての検証を上から下にコピーしますか?

someOtherfunction() {
   if (!shouldThisRun) return;
}

そうは思いません。したがって、私は通常同じアプローチを行います:入力を検証し、publicメソッドの条件をチェックします。パブリックメソッドは独自の検証を行い、呼び出し元が行う場合でも必要な条件をチェックします。しかし、プライベートメソッドは、独自のビジネスを行うだけです。他の一部の関数は、検証や条件の確認を行わずにrunFunctionを呼び出すことがあります。

public someFunction() {
   if (shouldThisRun) {
      runFunction();
   }
}

private runFunction() {
 // do your business.
}
2
Engineert