web-dev-qa-db-ja.com

引数を検証するためのより良いパターン(コーディングスタイル)はどれですか-ハードル(バリア)またはフェンス?

これらのパターン(またはアンチパターン)に受け入れられる名前があるかどうかはわかりませんが、ここでは、これらの名前を呼びます。実際、それは質問1:これらのパターンに受け入れられる名前があれば、それは何ですか?

一連のパラメーターを受け入れるメソッドがあり、実際のメソッドコードを実行する前に無効な入力を確認する必要があるとします。

public static void myMethod (String param1, String param2, String param3)

ハードルスタイル

トラックランナーがフィニッシュラインに到達するためにジャンプしなければならないハードルのようなものだからです。それらを条件付きバリアと考えることもできます。

{
    if (param1 == null || param1.equals("")) {
        // some logging if necessary
        return; // or throw some Exception or change to a default value
    }

    if (param2 == null || param2.equals("")) {
        // I'll leave the comments out
        return;
    }

    if (param3 == null || param3.equals("")) {
        return;
    }

    // actual method code goes here.
}

チェックが大きなメソッドの特定の小さなセクションに対するものである場合(およびセクションを小さなプライベートメソッドに移動できない場合)、breakステートメントを含むラベル付きブロックを使用できます。

{
    // method code before block

    myLabel:
    {
        if (param1 ... // I'll leave out the rest for brevity
            break myLabel;
        if (param2 ...
            break myLabel;
        ...

        // code working on valid input goes here

    } // 'break myLabel' will exit here

    // method code after block
}

フェンススタイル

これは、コードにアクセスする前に開く必要がある条件付きゲートを持つフェンスでコードを囲みます。ネストされたフェンスは、コードに到達するためのより多くのゲートを意味します(ロシアの人形のように)。

{
    if (param1 != null && !param1.equals("")) {
        if (param2 != null && !param2.equals("")) {
            if (param3 != null && !param3.equals("")) {

                // actual method code goes here.

            } else {
                // some logging here
            }
        } else {
            // some logging here
        }
    } else {
        // some logging here
    }
}

次のように書き直すこともできます。ログステートメントは、実際のメソッドコードの後ろではなく、チェックのすぐ横にあります。

{
    if (param1 == null || param1.equals("")) {
        // some logging here

    } else if (param2 == null || param2.equals("")) {
        // some logging here

    } else if (param3 == null || param3.equals("")) {
        // some logging here

    } else {

        // actual method code goes here.

    }
}

質問2:どちらのスタイルが優れているのか、そしてその理由は?

質問3:他のスタイルはありますか?

私は個人的にハードルスタイルを好みます。これは、見た目が簡単で、新しいパラメーターがあるたびにコードを右にインデントし続けることがないためです。チェックの間で断続的なコードを許可し、それはきちんとしていますが、維持するのも少し難しいです(いくつかの出口点)。

fence styleの最初のバージョンは、パラメーターを追加するとすぐに醜くなりますが、理解しやすいと思います。 2番目のバージョンの方が優れていますが、将来のコーダーによって誤って壊れる可能性があり、条件チェックの間で断続的なコードを許可しません。

10
ADTC

最初の「ハードル」スタイルは、どの点でも優れています。

  • インデントの多くのレベルを節約します。これだけで理解がはるかに簡単になります。
  • それはdiffフレンドリーです:別の制約を追加することで、すべてのコードを再度インデントする必要はありません。
  • 検証はone place –関数の上部にあります。他のスタイルでは、メインコードの前後です。すべての検証コードを1か所に配置すると、認知負荷が軽減されます。
  • ガベージコレクションを備えた言語では、複数の出口点はそれほど重要ではありません。単一のreturnを目指すことは、すべてを手動で割り当て解除する必要があった昔からの遺物です。
21
amon

あなたの例は、呼び出しコードが contract に準拠しているかどうかのチェックを具体的に扱っています。その場合、通常は例外がスローされるため、ハードルスタイルはより明確で読みやすくなります。そのため、メソッド内に、実行が失敗するような他のいくつかのコードがあるとは予想されません。

関数を2つの部分に分解する別のケースがあります(関数型言語でのパターンマッチングと同様)。再帰関数の場合を考えてみましょう:

int sum(int upTo)
{
    if(upTo<= 0) return 0;

    int previousSum = sum(upTo-1);
    return previousSum + upTo;
}  

停止条件のみをチェックする場合でも、ハードルスタイルは一般的です。

決定後に別のことをしなければならない場合もあります(これはあまり一般的ではありません)。

int sum(int upTo)
{
    int result;
    if(counter <= 0) 
    {
        result = 0;
    }
    else
    {
        int previousSum = sum(upTo-1);
        result = previousSum + upTo;
    }

    Console.WriteLine("Intermediate sum result: {0}", result);

    return result;
}  

その場合、フェンススタイルを使用するのが一般的です。ここでハードルスタイルを使用すると、Console行を2回書き込む必要があるか、値がゼロの場合に行を書き出さないため、異なる結果が生成されることに注意してください。

選択したスタイルは、それがどのタイプの機能であるかを読者に伝えます。関数またはメソッドが長すぎて明確ではない場合は、それを複数の関数に分割して明確にすることを検討してください。

通常、これはヘルパー関数を持つハードルスタイルに変更する必要があります。

int sum(int upTo)
{
    int result = sumHelper(upTo);
    Console.WriteLine("Intermediate sum result: {0}", result);
    return result;
}  

int sumHelper(int upTo)
{
    if(upTo<= 0) return 0;

    int previousSum = sum(upTo-1);
    return previousSum + upTo;
}

TL; DR-ハードルが最も一般的ですが、特定の状況に応じて適切なスタイルを選択し、関数がどちらの方法でも明らかに正しくなるように短く/シンプルであることを確認してください。

7
Scott Whitlock