假设我有这样的伪代码:

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语句可以以某种方式使用吗?


当前回答

为什么没有人给出最简单的解决方案?: D

如果你所有的函数都有相同的签名,那么你可以这样做(对于C语言):

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

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

    if (!condition) {
        break;
    }
}

executeThisFunctionInAnyCase();

对于简洁的c++解决方案,您应该创建一个接口类,其中包含一个执行方法,并将步骤包装在对象中。 然后,上面的解看起来像这样:

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

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

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

executeThisFunctionInAnyCase();

其他回答

在我看来,函数指针是最好的方法。

这种方法之前已经提到过,但是我想更深入地讨论一下对箭头类型的代码使用这种方法的优点。

根据我的经验,这种if链发生在程序的某个操作的初始化部分。程序在尝试启动之前需要确保一切正常。

通常情况下,在许多do stuff函数中一些东西可能会被分配,或者所有权可能会改变。如果你失败了,你会想要反转这个过程。

假设你有以下3个函数:

bool loadResources()
{
   return attemptToLoadResources();
}
bool getGlobalMutex()
{
   return attemptToGetGlobalMutex();
}
bool startInfernalMachine()
{
   return attemptToStartInfernalMachine();
}

所有函数的原型将是:

typdef bool (*initializerFunc)(void);

如上所述,您将使用push_back将指针添加到一个向量中,并按顺序运行它们。但是,如果您的程序在startInfernalMachine上失败,您将需要手动返回互斥量并卸载资源。如果在RunAllways函数中执行此操作,则会遇到麻烦。

但是等等!函子是非常棒的(有时),你可以只改变原型如下:

typdef bool (*initializerFunc)(bool);

为什么?好的,新函数现在看起来像这样:

bool loadResources(bool bLoad)
{
   if (bLoad)
     return attemptToLoadResources();
   else
     return attemptToUnloadResources();
}
bool getGlobalMutex(bool bGet)
{
  if (bGet)
    return attemptToGetGlobalMutex();
  else
    return releaseGlobalMutex();
}
...

所以现在,整个代码看起来就像这样:

vector<initializerFunc> funcs;
funcs.push_back(&loadResources);
funcs.push_back(&getGlobalMutex);
funcs.push_back(&startInfernalMachine);
// yeah, i know, i don't use iterators
int lastIdx;
for (int i=0;i<funcs.size();i++)
{
   if (funcs[i](true))
      lastIdx=i;
   else 
      break;
}
// time to check if everything is peachy
if (lastIdx!=funcs.size()-1)
{
   // sad face, undo
   for (int i=lastIdx;i>=0;i++)
      funcs[i](false);
}

因此,自动清理项目绝对是向前迈出的一步,并通过这个阶段。 然而,实现有点尴尬,因为您需要反复使用这个推回机制。如果你只有一个这样的位置,我们说它是可以的,但如果你有10个位置,有一个振荡的函数数量……这可不好玩。

幸运的是,还有另一种机制可以让您实现更好的抽象:可变函数。 毕竟,有许多不同数量的函数需要仔细研究。 变进函数是这样的:

bool variadicInitialization(int nFuncs,...)
{
    bool rez;
    int lastIdx;
    initializerFunccur;
    vector<initializerFunc> reverse;
    va_list vl;
    va_start(vl,nFuncs);
    for (int i=0;i<nFuncs;i++)
    {
        cur = va_arg(vl,initializerFunc);
        reverse.push_back(cur);
        rez= cur(true);
        if (rez)
            lastIdx=i;
        if (!rez)
            break;
    }
    va_end(vl);

    if (!rez)
    {

        for (int i=lastIdx;i>=0;i--)
        {
            reverse[i](false);
        }
    }
    return rez;
}

现在你的代码将被缩减(在应用程序的任何地方)为:

bool success = variadicInitialization(&loadResources,&getGlobalMutex,&startInfernalMachine);
doSomethingAllways();

通过这种方式,你可以用一个函数调用来完成所有那些讨厌的if列表,并确保当函数退出时,你不会有任何初始化的残留物。

您的团队成员将非常感激您在1行代码中实现了100行代码。

但是等等! 还有更多! 箭头类型代码的主要特征之一是需要有特定的顺序! 并且这个特定的顺序在整个应用程序中需要是相同的(多线程死锁避免规则1:在整个应用程序中始终以相同的顺序接受互斥) 如果有一个新来者,把函数按随机顺序排列呢?更糟糕的是,如果要求您将其公开给java或c#,该怎么办?(是的,跨平台是一种痛苦)

幸运的是,有一种方法可以解决这个问题。 以下是我的建议:

创建一个枚举,从第一个资源开始到最后一个资源 定义一个pair,从枚举中获取一个值,并将其与函数指针配对 把这些对放在一个向量中(我知道,我只是定义了一个映射的使用:),但我总是用向量表示小的数字) 将可变宏从函数指针改为整数(这很容易在java或c#中公开;))) 在变进函数中,对这些整数进行排序 运行时,运行分配给该整数的函数。

最后,您的代码将确保以下内容:

只要一行代码就可以初始化,不管需要多少东西 强制执行调用顺序:你不能在loadResources之前调用startInfernalMachine,除非你(架构师)决定允许这样做 如果在此过程中某些事情失败,则完成清理(考虑到您正确地进行了反初始化) 改变整个应用程序中初始化的顺序只意味着改变枚举中的顺序

Don't. Sometimes you need the complexity. The trick is how you do it. Having the "what you do when the condition exists" may take up some room, making the if statement tree appear larger than it really is. So instead of doing things if a condition is set, just set a variable to a specific value for that case( enumeration or number, like 10,014. After the if tree, then have a case statement, and for that specific value, do whatever you would have done in the if tree. It will lighten up the tree. if x1 if x2 if x3 Var1:=100016; endif endif end if case var=100016 do case 100016 things...

在这种情况下,老式的C程序员使用goto。这是goto的一种用法,实际上是Linux样式指南鼓励的,它被称为集中函数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;
}

有些人使用goto的方法是将body包装成一个循环并将其断开,但实际上这两种方法做的是同一件事。如果你只在executeStepA()成功时才需要一些其他的清理,那么goto方法会更好:

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;
}

使用循环方法,在这种情况下,您将得到两级循环。

在c++中,实际上有一种方法可以延迟操作:使用对象的析构函数。

假设你可以访问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

然后使用这个工具:

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

    // ...

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

    // ...

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

    // ...

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

    // ...

    return 4;
} // foo

给定函数:

string trySomething ()
{
    if (condition_1)
    {
        do_1();
        ..
            if (condition_k)
            {
                do_K();

                return doSomething();
            }
            else
            {
                return "Error k";
            }
        ..
    }
    else
    {
        return "Error 1";
    }
}

我们可以通过反转验证过程来摆脱语法嵌套:

string trySomething ()
{
    if (!condition_1)
    {
        return "Error 1";
    }

    do_1();

    ..

    if (!condition_k)
    {
        return "Error k";
    }

    do_K();

    return doSomething ();
}