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:37

Assuming you don't need individual condition variables, inverting the tests and using the else-falthrough as the "ok" path would allow you do get a more vertical set of if/else statements:

bool failed = false;

// keep going if we don't fail
if (failed = !executeStepA())      {}
else if (failed = !executeStepB()) {}
else if (failed = !executeStepC()) {}
else if (failed = !executeStepD()) {}

runThisFunctionInAnyCase();

Omitting the failed variable makes the code a bit too obscure IMO.

Declaring the variables inside is fine, no worry about = vs ==.

// keep going if we don't fail
if (bool failA = !executeStepA())      {}
else if (bool failB = !executeStepB()) {}
else if (bool failC = !executeStepC()) {}
else if (bool failD = !executeStepD()) {}
else {
     // success !
}

runThisFunctionInAnyCase();

This is obscure, but compact:

// keep going if we don't fail
if (!executeStepA())      {}
else if (!executeStepB()) {}
else if (!executeStepC()) {}
else if (!executeStepD()) {}
else { /* success */ }

runThisFunctionInAnyCase();
查看更多
The star\"
3楼-- · 2020-01-25 03:37

Another approach - do - while loop, even though it was mentioned before there was no example of it which would show how it looks like:

do
{
    if (!executeStepA()) break;
    if (!executeStepB()) break;
    if (!executeStepC()) break;
    ...

    break; // skip the do-while condition :)
}
while (0);

executeThisFunctionInAnyCase();

(Well there's already an answer with while loop but do - while loop does not redundantly check for true (at the start) but instead at the end xD (this can be skipped, though)).

查看更多
Animai°情兽
4楼-- · 2020-01-25 03:40

To improve on Mathieu's C++11 answer and avoid the runtime cost incurred through the use of std::function, I would suggest to use the following

template<typename functor>
class deferred final
{
public:
    template<typename functor2>
    explicit deferred(functor2&& f) : f(std::forward<functor2>(f)) {}
    ~deferred() { this->f(); }

private:
    functor f;
};

template<typename functor>
auto defer(functor&& f) -> deferred<typename std::decay<functor>::type>
{
    return deferred<typename std::decay<functor>::type>(std::forward<functor>(f));
}

This simple template class will accept any functor that can be called without any parameters, and does so without any dynamic memory allocations and therefore better conforms to C++'s goal of abstraction without unnecessary overhead. The additional function template is there to simplify use by template parameter deduction (which is not available for class template parameters)

Usage example:

auto guard = defer(executeThisFunctionInAnyCase);
bool conditionA = executeStepA();
if (!conditionA) return;
bool conditionB = executeStepB();
if (!conditionB) return;
bool conditionC = executeStepC();
if (!conditionC) return;

Just as Mathieu's answer this solution is fully exception safe, and executeThisFunctionInAnyCase will be called in all cases. Should executeThisFunctionInAnyCase itself throw, destructors are implicitly marked noexceptand therefore a call to std::terminate would be issued instead of causing an exception to be thrown during stack unwinding.

查看更多
我只想做你的唯一
5楼-- · 2020-01-25 03:42

Assuming the desired code is as I currently see it:

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

I would say that the correct approach, in that it's the simplest to read and easiest to maintain, would have fewer levels of indentation, which is (currently) the stated purpose of the question.

// Pre-declare the variables for the conditions
bool conditionA = false;
bool conditionB = false;
bool conditionC = false;

// Execute each step only if the pre-conditions are met
conditionA = executeStepA();
if (conditionA)
    conditionB = executeStepB();
if (conditionB)
    conditionC = executeStepC();
if (conditionC) {
    ...
}

// Unconditionally execute the 'cleanup' part.
executeThisFunctionInAnyCase();

This avoids any need for gotos, exceptions, dummy while loops, or other difficult constructs and simply gets on with the simple job at hand.

查看更多
倾城 Initia
6楼-- · 2020-01-25 03:42

Have your execute functions throw an exception if they fail instead of returning false. Then your calling code could look like this:

try {
    executeStepA();
    executeStepB();
    executeStepC();
}
catch (...)

Of course I'm assuming that in your original example the execution step would only return false in the case of an error occuring inside the step?

查看更多
我欲成王,谁敢阻挡
7楼-- · 2020-01-25 03:42

If your code is as simple as your example and your language supports short-circuit evaluations, you could try this:

StepA() && StepB() && StepC() && StepD();
DoAlways();

If you are passing arguments to your functions and getting back other results so that your code cannot be written in the previous fashion, many of the other answers would be better suited to the problem.

查看更多
登录 后发表回答