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条回答
smile是对你的礼貌
2楼-- · 2020-01-25 03:48

Just use an additional function to get your second version to work:

void foo()
{
  bool conditionA = executeStepA();
  if (!conditionA) return;

  bool conditionB = executeStepB();
  if (!conditionB) return;

  bool conditionC = executeStepC();
  if (!conditionC) return;
}

void bar()
{
  foo();
  executeThisFunctionInAnyCase();
}

Using either deeply nested ifs (your first variant) or the desire to break out of "part of a function" usually means you do need an extra function.

查看更多
来,给爷笑一个
3楼-- · 2020-01-25 03:49

There is actually a way to defer actions in C++: making use of an object's destructor.

Assuming that you have access to C++11:

class Defer {
public:
    Defer(std::function<void()> f): f_(std::move(f)) {}
    ~Defer() { if (f_) { f_(); } }

    void cancel() { f_ = std::function<void()>(); }

private:
    Defer(Defer const&) = delete;
    Defer& operator=(Defer const&) = delete;

    std::function<void()> f_;
}; // class Defer

And then using that utility:

int foo() {
    Defer const defer{&executeThisFunctionInAnyCase}; // or a lambda

    // ...

    if (!executeA()) { return 1; }

    // ...

    if (!executeB()) { return 2; }

    // ...

    if (!executeC()) { return 3; }

    // ...

    return 4;
} // foo
查看更多
干净又极端
4楼-- · 2020-01-25 03:49

Could break statement be used in some way?

Maybe not the best solution but you can put your statements in a do .. while (0) loop and use break statements instead of return.

查看更多
叼着烟拽天下
5楼-- · 2020-01-25 03:50

There's a nice technique which doesn't need an additional wrapper function with the return statements (the method prescribed by Itjax). It makes use of a do while(0) pseudo-loop. The while (0) ensures that it is actually not a loop but executed only once. However, the loop syntax allows the use of the break statement.

void foo()
{
  // ...
  do {
      if (!executeStepA())
          break;
      if (!executeStepB())
          break;
      if (!executeStepC())
          break;
  }
  while (0);
  // ...
}
查看更多
家丑人穷心不美
6楼-- · 2020-01-25 03:50

You could also do this:

bool isOk = true;
std::vector<bool (*)(void)> funcs; //vector of function ptr

funcs.push_back(&executeStepA);
funcs.push_back(&executeStepB);
funcs.push_back(&executeStepC);
//...

//this will stop at the first false return
for (auto it = funcs.begin(); it != funcs.end() && isOk; ++it) 
    isOk = (*it)();
if (isOk)
 //doSomeStuff
executeThisFunctionInAnyCase();

This way you have a minimal linear growth size, +1 line per call, and it's easily maintenable.


EDIT: (Thanks @Unda) Not a big fan because you loose visibility IMO :

bool isOk = true;
auto funcs { //using c++11 initializer_list
    &executeStepA,
    &executeStepB,
    &executeStepC
};

for (auto it = funcs.begin(); it != funcs.end() && isOk; ++it) 
    isOk = (*it)();
if (isOk)
 //doSomeStuff
executeThisFunctionInAnyCase();
查看更多
▲ chillily
7楼-- · 2020-01-25 03:50

You just do this..

coverConditions();
executeThisFunctionInAnyCase();

function coverConditions()
 {
 bool conditionA = executeStepA();
 if (!conditionA) return;
 bool conditionB = executeStepB();
 if (!conditionB) return;
 bool conditionC = executeStepC();
 if (!conditionC) return;
 }

99 times of 100, this is the only way to do it.

Never, ever, ever try to do something "tricky" in computer code.


By the way, I'm pretty sure the following is the actual solution you had in mind...

The continue statement is critical in algorithmic programming. (Much as, the goto statement is critical in algorithmic programming.)

In many programming languages you can do this:

-(void)_testKode
    {
    NSLog(@"code a");
    NSLog(@"code b");
    NSLog(@"code c\n");

    int x = 69;

    {

    if ( x == 13 )
        {
        NSLog(@"code d---\n");
        continue;
        }

    if ( x == 69 )
        {
        NSLog(@"code e---\n");
        continue;
        }

    if ( x == 13 )
        {
        NSLog(@"code f---\n");
        continue;
        }

    }

    NSLog(@"code g");
    }

(Note first of all: naked blocks like that example are a critical and important part of writing beautiful code, particularly if you are dealing with "algorithmic" programming.)

Again, that's exactly what you had in your head, right? And that's the beautiful way to write it, so you have good instincts.

However, tragically, in the current version of objective-c (Aside - I don't know about Swift, sorry) there is a risible feature where it checks if the enclosing block is a loop.

enter image description here

Here's how you get around that...

-(void)_testKode
    {
    NSLog(@"code a");
    NSLog(@"code b");
    NSLog(@"code c\n");

    int x = 69;

    do{

    if ( x == 13 )
        {
        NSLog(@"code d---\n");
        continue;
        }

    if ( x == 69 )
        {
        NSLog(@"code e---\n");
        continue;
        }

    if ( x == 13 )
        {
        NSLog(@"code f---\n");
        continue;
        }

    }while(false);

    NSLog(@"code g");
    }

So don't forget that ..

do { } while(false);

just means "do this block once".

ie, there is utterly no difference between writing do{}while(false); and simply writing {} .

This now works perfectly as you wanted...here's the output...

enter image description here

So, it's possible that's how you see the algorithm in your head. You should always try to write what's in your head. ( Particularly if you are not sober, because that's when the pretty comes out! :) )

In "algorithmic" projects where this happens a lot, in objective-c, we always have a macro like...

#define RUNONCE while(false)

... so then you can do this ...

-(void)_testKode
    {
    NSLog(@"code a");
    int x = 69;

    do{
    if ( x == 13 )
        {
        NSLog(@"code d---\n");
        continue;
        }
    if ( x == 69 )
        {
        NSLog(@"code e---\n");
        continue;
        }
    if ( x == 13 )
        {
        NSLog(@"code f---\n");
        continue;
        }
    }RUNONCE

    NSLog(@"code g");
    }

There are two points:

a, even though it's stupid that objective-c checks the type of block a continue statement is in, it's troubling to "fight that". So it's a tough decision.

b, there's the question should you indent, in the example, that block? I lose sleep over questions like that, so I can't advise.

Hope it helps.

查看更多
登录 后发表回答