web-dev-qa-db-ja.com

単一の戻り関数でのnested-ifの代替

特にエラーコードが返される場合に、nested-ifsの代わりに前例があるかどうかを確認したいと思います。私の職場では、機能ごとに1回の返品が必要なので、早期終了することはできません。以下は、機密性を保護するために書き直したサンプルコードです。

static ErrorCode_t FooBar ( Input_t input ) {
    ErrorCode_t errorCode = FAIL;

    if ( condition1IsNotMet )
    {
        errorCode = ERROR_CODE_1;
    }
    else
    {
        doSomethingHere;

        if ( condition2IsNotMet )
        {
            errorCode = ERROR_CODE_2;
        }
        else
        {
            doAnotherThingHere;
        }
    }

    return errorCode;
}

これに代わるものを書くことへの私の試みを示したいと思います。とはいえ、率直に言って他には考えられません。 if、else-if、else-ifなどのチェーニングはできません。条件チェックの間にプロシージャを実行する必要があるためです。

任意の提案をいただければ幸いです。

編集:重複としてマークする前に、私の要件に注意してください。 シングルリターンが必要です。 条件チェックの間に動作が発生するが必要です。また、単一のリターンの背後にある理論的根拠(私は同意しませんが)は読みやすさのためであり、パフォーマンスではないことに注意してください。

3
Saxpy

複数回の投票に投票しますが、次のパターンが当てはまる場合があります。これは、null(または0)の「エラー」が「すべて順調」であることを前提としています。

static ErrorCode_t FooBar ( Input_t input ) {
  ErrorCode_t errorCode = null;

  if ( condition1IsNotMet )
  {
    errorCode = ERROR_CODE_1;
  }

  if (!errorCode) {
    doSomethingHere;

    if ( condition2IsNotMet )
    {
        errorCode = ERROR_CODE_2;
    }
  }

  if (!errorCode) {
    doAnotherThingHere;
  }

  return errorCode;
}

(後で追加)いくつかの小さな関数に分割すると、それぞれがErrorCode_tを返す場合、コードは次のようになります。

static ErrorCode_t FooBar ( Input_t input ) {

  ErrorCode_t errorCode = checkPreconditions(input);

  if (!errorCode) {
    doSomethingHere();
    errorCode = checkCondition2();
  }

  if (!errorCode) {
    errorCode  = doAnotherThing();
  }

  return errorCode;
}
3
user949300

ああ、切り捨てるにはあまりにも価値がある黄金の経験則。

私は、そのルールを実施している人を連れて、改善するためにこのコードを与えることをお勧めします。 ポップコーンをお持ちください。


あなたが書いているのは、論理的な実行が1行の簡潔な実装の数式ではありません。

あなたが書いているのはコマンドです。ゴールデンルールを最終的に達成するには、深刻なリファクタリングが必要になります。

static ErrorCode_t FooBar_impl_2( Input_t input )
{
    doAnotherThingHere;
    return FAIL;
}
static ErrorCode_t FooBar_impl_1( Input_t input )
{
    doSomethingHere;

    return ( condition2IsNotMet )
        ? ERROR_CODE_2
        : FooBar_impl_2(input)
        ;
}
static ErrorCode_t FooBar ( Input_t input )
{
    return ( condition1IsNotMet )
        ? ERROR_CODE_1
        : FooBar_impl_1(input )
        ;
}

それを理解してください...

これは、最適化コンパイラがサンプルプログラムを解釈する方法です。

シングルリターンの黄金律のチョーク1、プログラマはコンパイラです!


BUT ....私は不誠実です...

単一のリターンを持つという黄金律は、アセンブラーの時代に作り出されました(アセンブラーで書いている場合は今のところ)。当時、関数cough、coughそれらがまだ存在していなかったのは残念です...

私が意味したことは、当時の最初の命令が行番号または光沢のあるlabelのいずれかでラベル付けされたアセンブラコードのセクションは、それが何であれほとんど何でもできるということでした。

あなたが「呼ばれた」とき-私はジャンプしたことを意味します-その命令は「呼ばれた人」に戻る必要はありませんでした-私はジャンプしたことを意味します-それ。

当時の慣例では、「戻りアドレス」を特殊レジスター、またはスタックの現在のスタックの最上部からの特定のオフセットに配置していました。一連の命令にジャンプすると、それが行ったことを実行し、そのアドレスにジャンプするという考えです。

smartプログラマが何をしなかったと思いますか?

そして、それが単一のリターンを持つというルールが生まれた方法です。コードにジャンプしたセクションは、指定されたアドレスに戻る必要があり、それ以外の場所には戻らない必要があります。

最新の関数はそれを正確に行います。それを呼び出した人は、関数が何をしても(実際に関数が実際に終了するかどうかは別として)関数が実行されるときに戻ります。

それを念頭に置いて、私は次のようにリファクタリングします:

static ErrorCode_t FooBar ( Input_t input )
{
    if ( condition1IsNotMet )
        return ERROR_CODE_1;

    doSomethingHere;

    if ( condition2IsNotMet )
        return ERROR_CODE_2;

    doAnotherThingHere;

    return FAIL;
}

そして、コンパイラは経験則を保証します。

6
Kain0_0

あなたは間違った質問をしています

ネストされたifの代替について質問しましたが、これは XY質問 の場合だと思います。

XY問題は、実際の問題ではなく、試みた解決策について尋ねています。

つまり、問題Xを解決しようとしていて、解決策Yがうまくいくと思うが、問題が発生したときにXについて尋ねるのではなく、Yについて尋ねる。

あなたが直面している本当の質問は、クリーンなコード/コード品質に関するものだと思います。

ネストされたifを書き換える方法を尋ねることは、FooBar関数をクリーンアップしようとする現在の試みにすぎません。その結果、私は次の質問に答えようとします:

FooBar関数のコード品質を改善するにはどうすればよいですか?

私の答えとして、ロバートC.マーティンの見事な必読本を参照し、引用します クリーンコード:アジャイルソフトウェアの職人のハンドブック


関数は1つのことを行う必要があります

あなたの例を見てみましょう。 1つのことだけを実行しているように見えますか?

static ErrorCode_t FooBar (Input_t input)
{
    ErrorCode_t errorCode = FAIL;
    if (condition1IsNotMet) {
        errorCode = ERROR_CODE_1;
    } else {
        // doSomethingHere
        if (condition2IsNotMet)
            errorCode = ERROR_CODE_2;
        else
            // doAnotherThingHere
    }

    return errorCode;
}

そうではありません。

そして、コードを大きなチャンクに分割するさまざまなif-elsesは、それを示す良い指標です。コードには明らかにsectionsがあり、本質的に理由により複雑になります。

関数は1つのことを行う必要があります。彼らはそれをうまくやるべきです。彼らはそれだけをすべきです。


あなたの機能は小さいはずです

関数は20行にすべきではありません。

実際、ほとんどの関数は10行未満、さらには5行未満であると予想されます。待つ!数行の入れ子になったifをどのように記述しますか?ロバートC.マーティンはこう書いています:

(...)ifステートメント、elseステートメント、whileステートメント内のブロックは、1行の長さである必要があります。おそらくその行は関数呼び出しでなければなりません。これにより、囲んでいる関数が小さく保たれるだけでなく、ブロックとともに呼び出される関数にわかりやすい名前を付けることができるため、記録的な価値が追加されます。

これは、関数がネストされた構造を保持するのに十分な大きさであってはならないことも意味します。 (...)


コードのリファクタリング

これら2つの原則を使用すると、doSomethingHeredoAnotherThingHereは独立した関数である必要があり、ネストされたifを解除する必要があることは明らかです。最終的には次のようになります。

static ErrorCode_t FooBar(Input_t input)
{
    if (condition1IsNotMet)
        return ERROR_CODE_1;
    return DoSomethingHere(input);
}

static ErrorCode_t DoSomething(Input_t input)
{
    // doSomethingHere
    return HandleMoreThings(input);
}

static ErrorCode_t HandleMoreThings(Input_t input)
{
    if (condition2IsNotMet)
        return ERROR_CODE_2;
    return DoAnotherThing(input);
}

static ErrorCode_t DoAnotherThing(Input_t input)
{
    // doAnotherThingHere
    return FAIL;
}

関数を分割する方法は、関数が実行する1つのことによって異なります

私はあなたのコードの動作について推測することしかできません。しかし、今度は小さな関数のそれぞれに質問をすることができます:

static ErrorCode_t FooBar(Input_t input);
static ErrorCode_t DoSomething(Input_t input);
static ErrorCode_t HandleMoreThings(Input_t input);
static ErrorCode_t DoAnotherThing(Input_t input);

それらのそれぞれについて、期待される結果は何ですか?最終的なコードは、コードの実際の目的によって異なる場合があります。

たとえば、コードは次のように記述する必要があると主張できます。

static ErrorCode_t FooBar(Input_t input)
{
    if (condition1IsNotMet)
        return ERROR_CODE_1;

    result = DoSomethingHere(input);

    if(result == SUCCESS);
        return ERROR_CODE_2
    else
        return FAIL;
}

static AnotherErrorCode_t DoSomething(Input_t input)
{
    // doSomethingHere
    return HandleMoreThings(input);
}

static AnotherErrorCode_t HandleMoreThings(Input_t input)
{
    if (condition2IsNotMet)
        return SUCCESS;
    DoAnotherThing(input);
    return FAIL;
}

static void DoAnotherThing(Input_t input)
{
    // doAnotherThingHere
}

詳細が分からないとわかりません。

1つのことコードは、条件ステートメントを見ただけではわかりません。したがって、最善の判断を使用して、この答えをコードに適合させる必要があります。


「唯一のreturnステートメント」制約に関する注意

質問の最後に、以下を追加しました:

重複としてマークする前に、私の要件に注意してください。一回の返品が必要です。条件チェックの間に動作を発生させる必要があります。また、単一のリターンの背後にある理論的根拠(私は同意しませんが)は読みやすさのためであり、パフォーマンスではないことに注意してください。

実際に関数を小さいチャンクに分割することにより、「最後に1つのリターンしか持たない」という人為的なルールがなくても読みやすくなります。実際、そのルールは「悪いコード」の問題を解決するための人為的な試みのようです。

また、エンドルールで1つのリターンに従う必要がある場合でも(おそらく会社のガイドラインのため)、次の場合ははるかに簡単ですコードをより小さなチャンクに分割します。


ボトムライン

問題は、複数のリターンがあることも、nested-ifを適切に書き込むことでもありません。

問題は、複数のリターンが、コードが1つのことだけを実行しないことを示していることです。ネストされたifについても同様です。

コードを小さなチャンクに整理することにより、より良いコードが得られます。

2
Albuquerque

user949300の提案は非常に優れていますが、エラーが発生するとすぐにすべての「オプション」ステップをスキップして、「初期の成功」をより面倒にすることを想定しています。

これらはどちらもほぼ常に有効な仮定だと思いますが、そうでない場合に備えて、最終的に返されるステータスコードの値に関係なく、いくつかのステップをスキップする2つの方法を示します。

  1. (Ab)do ... whileswitchなどの組み込みの制御フロー構造を使用する
  2. キープゴーイング変数を使用してください。マクロの背後に隠れている可能性があります。

do ... whileループを使用した例を次に示します。

// foo_do_while.c
#include <stdio.h>
#include <stdbool.h>

#define CONDITION_1 true
#define CONDITION_2 true

#define ERROR_CODE_INVALID (-1)
#define ERROR_CODE_1 1
#define ERROR_CODE_2 2

void do_something(void) {
    printf("do_something\n");
}

int no_nest(void) {
    int error_code = ERROR_CODE_INVALID;
    do {
        if (CONDITION_1) {
            printf("1\n");
            error_code = ERROR_CODE_1;
            break;
        }
        do_something();
        if (CONDITION_2) {
            printf("2\n");
            error_code = ERROR_CODE_2;
            break;
        }
    } while(0);
    return error_code;
}

int main() {
    printf("no_nest status: %d\n", no_nest());
    return 0;
}

switchステートメントを使用します。

// foo_switch.c
#include <stdio.h>
#include <stdbool.h>

#define CONDITION_1 true
#define CONDITION_2 true

#define ERROR_CODE_INVALID (-1)
#define ERROR_CODE_1 1
#define ERROR_CODE_2 2

void do_something(void) {
    printf("do_something\n");
}

int no_nest(void) {
    int error_code = ERROR_CODE_INVALID;
    switch (0) {
    case 0:
        if (CONDITION_1) {
            printf("1\n");
            error_code = ERROR_CODE_1;
            break;
        }
        do_something();
        if (CONDITION_2) {
            printf("2\n");
            error_code = ERROR_CODE_2;
            break;
        }
    }
    return error_code;
}

int main() {
    printf("no_nest status: %d\n", no_nest());
    return 0;
}

ここでは、keep-going変数を明示的に使用しています。

#include <stdio.h>
#include <stdbool.h>

#define CONDITION_1 false
#define CONDITION_2 true

#define ERROR_CODE_INVALID (-1)
#define ERROR_CODE_1 1
#define ERROR_CODE_2 2

void do_something(void) {
    printf("do_something\n");
}

int no_nest(void) {
    bool keep_going = 1;
    int error_code = ERROR_CODE_INVALID;
    if (keep_going) {
        if (CONDITION_1) {
            printf("1\n");
            error_code = ERROR_CODE_1;
            keep_going = 0;
        }
    }
    if (keep_going) {
        do_something();
    }
    if (keep_going) {
        if (CONDITION_2) {
            printf("2\n");
            error_code = ERROR_CODE_2;
            keep_going = 0;
        }
    }
    return error_code;
}

int main() {
    printf("no_nest status: %d\n", no_nest());
    return 0;
}

マクロが複数の戻りを禁止する同じ標準によって禁止されていないと仮定して、keep_goingチェックをマクロの後ろに隠すこともできます。この例は、ブロックをとるマクロと、マクロの開始/終了ペアを示しています。

#include <stdio.h>
#include <stdbool.h>

#define CONDITION_1 false
#define CONDITION_2 true

#define ERROR_CODE_INVALID (-1)
#define ERROR_CODE_1 1
#define ERROR_CODE_2 2


#define CHK_KG(block) \
    do { \
        if (keep_going) { \
            block \
        } \
    } while (0);

#define BEGIN_CHK_KG \
    do { if (keep_going) {   {{{{{{{{{{

#define END_CHK_KG \
    }}}}}}}}}} } } while(0);

void do_something(void) {
    printf("do_something\n");
}

int no_nest(void) {
    bool keep_going = 1;
    int error_code = ERROR_CODE_INVALID;
    CHK_KG(
        if (CONDITION_1) {
            printf("1\n");
            error_code = ERROR_CODE_1;
            keep_going = 0;
        }
    )
    BEGIN_CHK_KG
    do_something();
    END_CHK_KG
    if (keep_going) {
        if (CONDITION_2) {
            printf("2\n");
            error_code = ERROR_CODE_2;
            keep_going = 0;
        }
    }
    return error_code;
}

int main() {
    printf("no_nest status: %d\n", no_nest());
    return 0;
}
0
Gregory Nisbet