How to avoid “if” chains?

2020-01-25 02:50发布

Assuming I have this pseudo-code:

bool conditionA = executeStepA();
if (conditionA){
    bool conditionB = executeStepB();
    if (conditionB){
        bool conditionC = executeStepC();
        if (conditionC){
            ...
        }
    }
}

executeThisFunctionInAnyCase();

Functions executeStepX should be executed if and only if the previous succeed. In any case, the executeThisFunctionInAnyCase function should be called at the end. I'm a newbie in programming, so sorry for the very basic question: is there a way (in C/C++ for example) to avoid that long if chain producing that sort of "pyramid of code", at the expense of the code legibility?

I know that if we could skip the executeThisFunctionInAnyCase function call, the code could be simplified as:

bool conditionA = executeStepA();
if (!conditionA) return;
bool conditionB = executeStepB();
if (!conditionB) return;
bool conditionC = executeStepC();
if (!conditionC) return;

But the constraint is the executeThisFunctionInAnyCase function call. Could the break statement be used in some way?

30条回答
2楼-- · 2020-01-25 03:44

Old school C programmers use goto in this case. It is the one usage of goto that's actually encouraged by the Linux styleguide, it's called the centralized function exit:

int foo() {
    int result = /*some error code*/;
    if(!executeStepA()) goto cleanup;
    if(!executeStepB()) goto cleanup;
    if(!executeStepC()) goto cleanup;

    result = 0;
cleanup:
    executeThisFunctionInAnyCase();
    return result;
}

Some people work around using goto by wrapping the body into a loop and breaking from it, but effectively both approaches do the same thing. The goto approach is better if you need some other cleanup only if executeStepA() was successfull:

int foo() {
    int result = /*some error code*/;
    if(!executeStepA()) goto cleanupPart;
    if(!executeStepB()) goto cleanup;
    if(!executeStepC()) goto cleanup;

    result = 0;
cleanup:
    innerCleanup();
cleanupPart:
    executeThisFunctionInAnyCase();
    return result;
}

With the loop approach you would end up with two levels of loops in that case.

查看更多
\"骚年 ilove
3楼-- · 2020-01-25 03:44

If you dislike goto and dislike do { } while (0); loops and like to use C++ you can also use a temporary lambda to have the same effect.

[&]() { // create a capture all lambda
  if (!executeStepA()) { return; }
  if (!executeStepB()) { return; }
  if (!executeStepC()) { return; }
}(); // and immediately call it

executeThisFunctionInAnyCase();
查看更多
别忘想泡老子
4楼-- · 2020-01-25 03:45

The chains of IF/ELSE in your code is not the language issue, but the design of your program. If you're able to re-factor or re-write your program I'd like to suggest that you look in Design Patterns (http://sourcemaking.com/design_patterns) to find a better solution.

Usually, when you see a lot of IF's & else's in your code , it is an opportunity to implement the Strategy Design Pattern (http://sourcemaking.com/design_patterns/strategy/c-sharp-dot-net) or maybe a combination of other patterns.

I'm sure there're alternatives to write a long list of if/else , but I doubt they will change anything except that the chain will look pretty to you (However, the beauty is in the eye of the beholder still applies to code too:-) ) . You should be concerned about things like (in 6 months when I have a new condition and I don't remember anything about this code , will I be able to add it easily? Or what if the chain changes, how quickly and error-free will I be implement it)

查看更多
爱情/是我丢掉的垃圾
5楼-- · 2020-01-25 03:46

For C++11 and beyond, a nice approach might be to implement a scope exit system similar to D's scope(exit) mechanism.

One possible way to implement it is using C++11 lambdas and some helper macros:

template<typename F> struct ScopeExit 
{
    ScopeExit(F f) : fn(f) { }
    ~ScopeExit() 
    { 
         fn();
    }

    F fn;
};

template<typename F> ScopeExit<F> MakeScopeExit(F f) { return ScopeExit<F>(f); };

#define STR_APPEND2_HELPER(x, y) x##y
#define STR_APPEND2(x, y) STR_APPEND2_HELPER(x, y)

#define SCOPE_EXIT(code)\
    auto STR_APPEND2(scope_exit_, __LINE__) = MakeScopeExit([&](){ code })

This will allow you to return early from the function and ensure whatever cleanup code you define is always executed upon scope exit:

SCOPE_EXIT(
    delete pointerA;
    delete pointerB;
    close(fileC); );

if (!executeStepA())
    return;

if (!executeStepB())
    return;

if (!executeStepC())
    return;

The macros are really just decoration. MakeScopeExit() can be used directly.

查看更多
Root(大扎)
6楼-- · 2020-01-25 03:46

Why is nobody giving the simplest solution ? :D

If all your functions have the same signature then you can do it this way (for C language):

bool (*step[])() = {
    &executeStepA,
    &executeStepB,
    &executeStepC,
    ... 
};

for (int i = 0; i < numberOfSteps; i++) {
    bool condition = step[i]();

    if (!condition) {
        break;
    }
}

executeThisFunctionInAnyCase();

For a clean C++ solution, you should create an interface class that contains an execute method and wraps your steps in objects.
Then, the solution above will look like this:

Step *steps[] = {
    stepA,
    stepB,
    stepC,
    ... 
};

for (int i = 0; i < numberOfSteps; i++) {
    Step *step = steps[i];

    if (!step->execute()) {
        break;
    }
}

executeThisFunctionInAnyCase();
查看更多
【Aperson】
7楼-- · 2020-01-25 03:47

This looks like a state machine, which is handy because you can easily implement it with a state-pattern.

In Java it would look something like this:

interface StepState{
public StepState performStep();
}

An implementation would work as follows:

class StepA implements StepState{ 
    public StepState performStep()
     {
         performAction();
         if(condition) return new StepB()
         else return null;
     }
}

And so on. Then you can substitute the big if condition with:

Step toDo = new StepA();
while(toDo != null)
      toDo = toDo.performStep();
executeThisFunctionInAnyCase();
查看更多
登录 后发表回答