假设我有这样的伪代码:

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

executeThisFunctionInAnyCase();

函数executeStepX当且仅当前一个成功时执行。 在任何情况下,executeThisFunctionInAnyCase函数都应该在最后被调用。 我在编程方面是一个新手,所以很抱歉提出一个非常基本的问题:有没有一种方法(例如在C/ c++中)以代码易读性为代价,避免长if链产生那种“金字塔式代码”?

我知道如果我们可以跳过executeThisFunctionInAnyCase函数调用,代码可以简化为:

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

但是约束是executeThisFunctionInAnyCase函数调用。 break语句可以以某种方式使用吗?


当前回答

假设你不需要单独的条件变量,反转测试并使用else-falthrough作为“ok”路径,将允许你获得更垂直的if/else语句集:

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();

省略失败的变量使代码在我看来有点太晦涩了。

在里面声明变量就可以了,不用担心= 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();

这很模糊,但很紧凑:

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

runThisFunctionInAnyCase();

其他回答

到目前为止,有50多个答案,但没有人提到我在这种情况下通常会怎么做!(例如,一个由几个步骤组成的操作,但使用状态机或函数指针表将是多余的):

if ( !executeStepA() )
{
    // error handling for "A" failing
}
else if ( !executeStepB() )
{
    // error handling for "B" failing
}
else if ( !executeStepC() )
{
    // error handling for "C" failing
}
else
{
    // all steps succeeded!
}

executeThisFunctionInAnyCase();

优点:

最终不会有巨大的缩进水平 错误处理代码(可选)出现在失败函数调用之后的那几行中

缺点:

如果有一个步骤不是仅仅包含在一个函数调用中,会变得很糟糕吗 如果除了“按顺序执行步骤,如果一个失败就终止”之外,还需要任何流,那么就会变得很糟糕。

只是一个边注;如果if作用域总是导致返回(或循环中断),则不要使用else语句。这可以节省大量的缩进。

关于当前的代码示例,本质上是第二个问题,

[...block of code...]
bool conditionA = executeStepA();    
if (conditionA){
    [...block of code...]
    bool conditionB = executeStepB();
    if (conditionB){
        [...block of code...]
        bool conditionC = executeStepC();
        if (conditionC){
            ...other checks again...
        }
    }
}

executeThisFunctionInAnyCase();

除了将函数结果存储在变量中之外,这是典型的C代码。

如果布尔函数导致信号失败,那么c++的方法是使用异常,并将其编码为

struct Finals{ ~Finals() { executeThisFunctionInAnyCase(); } };

Finals finals;
// [...block of code...]
executeStepA();
// [...block of code...]
executeStepB();
// [...block of code...]
executeStepC();
//...other checks again...

然而,根据实际问题的不同,细节可能会有很大差异。

当我需要这样的通用最终操作时,我通常使用通用范围保护类,而不是当场定义一个自定义结构。作用域保护是由Petru Marginean为c++ 98发明的,然后使用临时生命周期扩展技巧。在c++ 11中,一般的范围保护类可以基于提供lambda表达式的客户端代码简单地实现。

在问题的最后,你建议了一个很好的C方法来做到这一点,即使用break语句:

for( ;; ) // As a block one can 'break' out of.
{
    // [...block of code...]
    if( !executeStepA() ) { break; }
    // [...block of code...]
    if( !executeStepB() ) { break; }
    // [...block of code...]
    if( !executeStepC() ) { break; }
    //...other checks again...
    break;
}
executeThisFunctionInAnyCase();

或者,对于C,将代码块中的代码重构为一个单独的函数,并使用return而不是break。因为它支持嵌套循环或开关,所以这更清楚也更通用。然而,你问的是休息。

与基于异常的c++方法相比,这种方法依赖于程序员记得检查每个函数的结果,并做正确的事情,这两者在c++中都是自动化的。

已经有很多不错的答案了,但大多数答案似乎都牺牲了一些(诚然,很少)灵活性。一种不需要这种权衡的常见方法是添加状态/持续变量。当然,价格是需要跟踪的额外价值:

bool ok = true;
bool conditionA = executeStepA();
// ... possibly edit conditionA, or just ok &= executeStepA();
ok &= conditionA;

if (ok) {
    bool conditionB = executeStepB();
    // ... possibly do more stuff
    ok &= conditionB;
}
if (ok) {
    bool conditionC = executeStepC();
    ok &= conditionC;
}
if (ok && additionalCondition) {
    // ...
}

executeThisFunctionInAnyCase();
// can now also:
return ok;

我认为c++ 23的可选的一元操作会做得很好,尽管函数需要做一些改变。

and_then()方法执行break或调用下一个函数操作,链接该方法允许逐个调用函数,直到其中一个返回false。

举个简单粗暴的例子:

#include <iostream>
#include <optional>
#include <cstdlib>

using namespace std;

optional<bool> func1() {
    cout << "func1\n";

    if (rand() % 2)
        return true;
    else
        return nullopt;
}

optional<bool> func2(optional<bool> v) {
    cout << "func2\n";

    if (rand() % 2)
        return true;
    else
        return nullopt;
}

optional<bool> func3(optional<bool> v) {
    cout << "func3\n";

    if (rand() % 2)
        return true;
    else
        return nullopt;
}

void func4() {
    cout << "func4\n";
}

int main() {
    srand(time(NULL));

    func1()
      .and_then(func2)
      .and_then(func3);

    func4();

    return 0;
}