假设我有这样的伪代码:

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


当前回答

在阅读了所有的答案之后,我想提供一种新的方法,它在适当的情况下可能非常清晰易读:状态模式。

如果你将所有的方法(executeStepX)打包到一个对象类中,它可以有一个属性getState()

class ExecutionChain
{
    public:
        enum State
        {
          Start,
          Step1Done,
          Step2Done,
          Step3Done,
          Step4Done,
          FinalDone,
        };
        State getState() const;

        void executeStep1();
        void executeStep2();
        void executeStep3();
        void executeStep4();
        void executeFinalStep();
    private:
        State _state;
};

这将允许你将你的执行代码平铺成这样:

void execute
{
    ExecutionChain chain;

    chain.executeStep1();

    if ( chain.getState() == Step1Done )
    {
        chain.executeStep2();
    }

    if ( chain.getState() == Step2Done )
    {
        chain.executeStep3();
    }

    if ( chain.getState() == Step3Done )
    {
        chain.executeStep4();
    }

    chain.executeFinalStep();
}

通过这种方式,它易于阅读,易于调试,你有一个清晰的流控制,还可以插入新的更复杂的行为(例如,只有在至少执行Step2时才执行Special Step)……

我的问题与其他方法,如ok = execute();如果(execute()),则代码应该清晰易读,就像正在发生的事情的流程图一样。在流程图中有两个步骤:1。2执行。基于结果的决定

因此,您不应该将重要的重载方法隐藏在if语句或类似语句中,它们应该独立存在!

其他回答

在阅读了所有的答案之后,我想提供一种新的方法,它在适当的情况下可能非常清晰易读:状态模式。

如果你将所有的方法(executeStepX)打包到一个对象类中,它可以有一个属性getState()

class ExecutionChain
{
    public:
        enum State
        {
          Start,
          Step1Done,
          Step2Done,
          Step3Done,
          Step4Done,
          FinalDone,
        };
        State getState() const;

        void executeStep1();
        void executeStep2();
        void executeStep3();
        void executeStep4();
        void executeFinalStep();
    private:
        State _state;
};

这将允许你将你的执行代码平铺成这样:

void execute
{
    ExecutionChain chain;

    chain.executeStep1();

    if ( chain.getState() == Step1Done )
    {
        chain.executeStep2();
    }

    if ( chain.getState() == Step2Done )
    {
        chain.executeStep3();
    }

    if ( chain.getState() == Step3Done )
    {
        chain.executeStep4();
    }

    chain.executeFinalStep();
}

通过这种方式,它易于阅读,易于调试,你有一个清晰的流控制,还可以插入新的更复杂的行为(例如,只有在至少执行Step2时才执行Special Step)……

我的问题与其他方法,如ok = execute();如果(execute()),则代码应该清晰易读,就像正在发生的事情的流程图一样。在流程图中有两个步骤:1。2执行。基于结果的决定

因此,您不应该将重要的重载方法隐藏在if语句或类似语句中,它们应该独立存在!

把有条件的东西移到else中怎么样?

if (!(conditionA = executeStepA()){}
else if (!(conditionB = executeStepB()){}
else if (!(conditionC = executeStepC()){}
else if (!(conditionD = executeStepD()){}

这确实解决了缩进问题。

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

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

根据我的经验,这种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,除非你(架构师)决定允许这样做 如果在此过程中某些事情失败,则完成清理(考虑到您正确地进行了反初始化) 改变整个应用程序中初始化的顺序只意味着改变枚举中的顺序

正如Rommik提到的,您可以为此应用设计模式,但我将使用Decorator模式而不是Strategy,因为您想要链式调用。如果代码很简单,那么我会选择一个结构良好的答案来防止嵌套。但是,如果它很复杂或者需要动态链接,那么Decorator模式是一个很好的选择。这是一个yUML类图:

下面是一个示例LinqPad c#程序:

void Main()
{
    IOperation step = new StepC();
    step = new StepB(step);
    step = new StepA(step);
    step.Next();
}

public interface IOperation 
{
    bool Next();
}

public class StepA : IOperation
{
    private IOperation _chain;
    public StepA(IOperation chain=null)
    {
        _chain = chain;
    }

    public bool Next() 
    {
        bool localResult = false;
        //do work
        //...
        // set localResult to success of this work
        // just for this example, hard coding to true
        localResult = true;
        Console.WriteLine("Step A success={0}", localResult);

        //then call next in chain and return
        return (localResult && _chain != null) 
            ? _chain.Next() 
            : true;
    }
}

public class StepB : IOperation
{
    private IOperation _chain;
    public StepB(IOperation chain=null)
    {
        _chain = chain;
    }

    public bool Next() 
    {   
        bool localResult = false;

        //do work
        //...
        // set localResult to success of this work
        // just for this example, hard coding to false, 
            // to show breaking out of the chain
        localResult = false;
        Console.WriteLine("Step B success={0}", localResult);

        //then call next in chain and return
        return (localResult && _chain != null) 
            ? _chain.Next() 
            : true;
    }
}

public class StepC : IOperation
{
    private IOperation _chain;
    public StepC(IOperation chain=null)
    {
        _chain = chain;
    }

    public bool Next() 
    {
        bool localResult = false;
        //do work
        //...
        // set localResult to success of this work
        // just for this example, hard coding to true
        localResult = true;
        Console.WriteLine("Step C success={0}", localResult);
        //then call next in chain and return
        return (localResult && _chain != null) 
            ? _chain.Next() 
            : true;
    }
}

恕我直言,关于设计模式最好的书是《Head First design patterns》。

另一种解决方案是通过宏hack定义习语。

 #define block for(int block = 0; !block; block++)

现在,“block”可以用break退出,与for(;;)和while()循环的方式相同。例子:

int main(void) {

    block {
       if (conditionA) {
          // Do stuff A...
          break; 
       }
       if (conditionB) {
          // Do stuff B...
          break; 
       }
       if (conditionC) {
          // Do stuff C...
          break; 
       }
       else {
         // Do default stuff...
       }
    } /* End of "block" statement */
    /* --->   The "break" sentences jump here */

    return 0;
} 

尽管使用了“for(;;)”结构,但“block”语句只执行了一次。 这些“块”可以用断句退出。 因此,if else if else if…避免使用句子。 最多,最后一个else可以挂在“块”的末尾,以处理“默认”情况。

该技术旨在避免典型的和丑陋的做{…} while(0)方法。 在宏块中,它定义了一个同样名为block的变量,该变量以这样一种方式定义,即恰好执行了一次for迭代。根据宏的替换规则,宏块定义中的标识符块不会被递归替换,因此block成为程序员无法访问的标识符,但在内部可以很好地控制for(;;)循环的“隐藏”。

此外:这些“块”可以嵌套,因为隐藏变量int块将有不同的作用域。