ベストプラクティス-関数呼び出しの周りをラップするvs関数内でガードする場合は早期終了を追加する


9

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

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

関数呼び出しの周りをラップする


if (shouldThisRun) {
  runFunction();
}

持っている場合ガード)機能で

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

後者のオプションは、この関数が複数回呼び出された場合にコードの重複を減らす可能性があることは明らかですが、関数の単一の責任性を失う可能性があるため、ここに追加すると間違っていると感じる場合があります。


例はこちら

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

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



3
@gnatいいえ、その質問は本質的に「早期終了で推奨される構文は何ですか」ですが、私の質問は「早期終了するか、関数を呼び出さないでください」
Matthew Mullin

4
あなた自身さえ、開発者を信頼して、関数が呼び出されるすべての場所で関数の前提条件を正しくチェックすることはできません。このため、関数が内部的に検証できる場合は、必要な条件を検証することをお勧めします。
TZHX

「ステータスが変更された場合にのみステータスを更新したい」-同じステータスが変更された場合にステータスを更新(=変更)したいですか?かなり円形に聞こえます。これが何を意味するのかを明確にしていただけますか?これに関する私の回答に意味のある例を追加できますか?
Doc Brown、

@DocBrownたとえば、2つの異なるオブジェクトのステータスプロパティを同期させたいとしましょう。いずれかのオブジェクトが変更されると、syncStatuses()を呼び出しますが、これは、さまざまなフィールドの変更(ステータスフィールドだけでなく)でトリガーされる可能性があります。
マシュー・マリン

回答:


15

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

関数内のガード句(早期復帰):
これは、無効なパラメーターで呼び出されないようにするためです。

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

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

これは良い質問であり、抽象化のレベルの認識につながる一連の質問に含まれます。各関数は、抽象化の一つのレベルで動作し、プログラムロジックと機能における機能ロジックの両方を持つことはあなたに間違って感じている必要があります-彼らはので、これはある抽象化のさまざまなレベルで。関数自体は下位レベルです。

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


素晴らしい答え。これについて明確に考える方法が得られるという事実が気に入っています。関数を最初に呼び出す必要があるかどうかについては、「意思決定プロセスの一部」であるため、ifは外部にある必要があります。そして、本質的に機能自体とは何の関係もありません。意見の回答を正解としてマークするのは奇妙に感じますが、数時間後にもう一度見て、そうします。
Matthew Mullin

それが役立つ場合、私はこれを「意見」の答えとは見なしません。私はそれが間違っていると「感じる」ことに注意しますが、それは抽象化のさまざまなレベルが分離していないためです。私があなたの質問から得たのは、それが「正しくない」ことを理解できるということですが、抽象化のレベルについて考えていないので、定量化するのは難しいため、言葉で表現するのに苦労します。
Baldrickk

7

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

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

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

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

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


1
「tryXXX()」のイディオムは常に少しずれているように見え、ここでは不適切です。エラーの可能性を予期して何かをしようとしているのではありません。汚れている場合は更新しています。
user949300

@ user949300:適切な名前または命名方式の選択は、実際の使用例、実際の関数名に依存しrunFunctionます。のような関数には、のようなupdateStatus()別の関数が伴う場合がありますupdateIfStatusHasChanged()。しかし、これは100%のケースに依存するものであり、これに「万能」の解決策はないので、はい、私は同意します。
ドク・ブラウン

「dryRun」という名前はありませんか?多かれ少なかれ、副作用のない通常の実行になります。副作用を無効にする方法は別の話です
Laiv

3

実行するかどうかを誰が決めるかについては、答えはGRASPから、誰が知っている「情報専門家」であるかです。

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

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

 ensureUpdated()
 updateIfDirty()

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

 writeStatus()

2

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

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

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

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

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

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

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

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


2

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

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.
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.