如何避免“如果”链?

时间:2014-06-26 12:25:26

标签: c++ c if-statement control-flow

假设我有这个伪代码:

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语句吗?

51 个答案:

答案 0 :(得分:484)

您可以使用&&(逻辑AND):

if (executeStepA() && executeStepB() && executeStepC()){
    ...
}
executeThisFunctionInAnyCase();

这将满足您的两个要求:

  • executeStep<X>()只有在前一个成功时才会评估(这称为short circuit evaluation
  • executeThisFunctionInAnyCase()将在任何情况下执行

答案 1 :(得分:358)

只需使用其他功能即可使您的第二个版本正常工作:

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

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

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

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

使用深度嵌套的ifs(您的第一个变体)或者想要突破&#34;功能的一部分&#34;通常意味着你需要额外的功能。

答案 2 :(得分:164)

在这种情况下,旧学C程序员使用goto。 Linux样式指南实际上鼓励了goto的一种用法,它被称为集中式函数退出:

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的问题,但实际上两种方法都做同样的事情。如果仅在goto成功时需要进行其他清理,executeStepA()方法会更好:

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

使用循环方法,在这种情况下最终会有两级循环。

答案 3 :(得分:128)

这是一种常见的情况,有许多常见的方法可以解决它。这是我尝试一个规范的答案。如果我遗漏了任何内容,请发表评论,我会及时更新这篇文章。

这是一个箭头

您正在讨论的内容称为arrow anti-pattern。它被称为箭头,因为嵌套ifs链形成的代码块向右和向后扩展得越来越远,形成一个可视箭头,指向&#34;指向&#34;在代码编辑器窗格的右侧。

用卫兵将箭头展平

讨论了一些避免箭头的常见方法here。最常见的方法是使用guard模式,其中代码首先处理异常流,然后处理基本流,例如,而不是

if (ok)
{
    DoSomething();
}
else
{
    _log.Error("oops");
    return;
}

......你使用....

if (!ok)
{
    _log.Error("oops");
    return;
} 
DoSomething(); //notice how this is already farther to the left than the example above

当有一长串防护装置时,这会使代码变得相当扁平,因为所有防护装置一直显示在左边,而你的ifs没有嵌套。此外,您可以直观地将逻辑条件与其相关错误配对,这样可以更容易地判断出发生了什么:

箭头:

ok = DoSomething1();
if (ok)
{
    ok = DoSomething2();
    if (ok)
    {
        ok = DoSomething3();
        if (!ok)
        {
            _log.Error("oops");  //Tip of the Arrow
            return;
        }
    }
    else
    {
       _log.Error("oops");
       return;
    }
}
else
{
    _log.Error("oops");
    return;
}

后卫:

ok = DoSomething1();
if (!ok)
{
    _log.Error("oops");
    return;
} 
ok = DoSomething2();
if (!ok)
{
    _log.Error("oops");
    return;
} 
ok = DoSomething3();
if (!ok)
{
    _log.Error("oops");
    return;
} 
ok = DoSomething4();
if (!ok)
{
    _log.Error("oops");
    return;
} 

这是客观且可量化的,因为

  1. 给定逻辑块的{和}字符更靠近
  2. 理解特定行所需的心理背景量较小
  3. 与if条件关联的整个逻辑更可能在一个页面上
  4. 编码器滚动页面/眼睛轨迹的需求大大减少
  5. 如何在末尾添加公共代码

    守卫模式的问题在于它依赖于所谓的&#34;机会主义回归&#34;或者&#34;伺机退出。&#34;换句话说,它打破了每个函数应该只有一个退出点的模式。这是一个问题,原因有两个:

    1. 它以错误的方式揉搓某些人,例如:学会了在Pascal上编码的人已经知道一个函数=一个退出点。
    2. 它不提供退出时执行的代码部分,无论是什么,这是当前的主题。
    3. 下面我提供了一些解决此限制的选项,无论是使用语言功能还是完全避免这个问题。

      选项1.您无法执行此操作:使用finally

      不幸的是,作为一名c ++开发人员,你无法做到这一点。但这是包含finally关键字的语言的头号答案,因为这正是它的用途。

      try
      {
          if (!ok)
          {
              _log.Error("oops");
              return;
          } 
          DoSomething(); //notice how this is already farther to the left than the example above
      }
      finally
      {
          DoSomethingNoMatterWhat();
      }
      

      选项2.避免问题:重组功能

      您可以通过将代码分解为两个函数来避免此问题。该解决方案具有适用于任何语言的优势,此外它还可以减少cyclomatic complexity,这是一种可靠的方法,可以降低您的缺陷率,并提高任何自动化单元测试的特异性。

      以下是一个例子:

      void OuterFunction()
      {
          DoSomethingIfPossible();
          DoSomethingNoMatterWhat();
      }
      
      void DoSomethingIfPossible()
      {
          if (!ok)
          {
              _log.Error("Oops");
              return;
          }
          DoSomething();
      }
      

      选项3.语言技巧:使用假循环

      我看到的另一个常见技巧是使用while(true)和break,如其他答案所示。

      while(true)
      {
           if (!ok) break;
           DoSomething();
           break;  //important
      }
      DoSomethingNoMatterWhat();
      

      虽然这不那么诚实&#34;而不是使用goto,它在重构时不太容易被搞砸,因为它清楚地标记了逻辑范围的界限。切割和粘贴标签或您的goto语句的天真编码员可能会导致重大问题! (坦率地说,这种模式是如此常见,现在我认为它清楚地传达了意图,因此根本不是&#34;不诚实&#34;)。

      此选项还有其他变种。例如,可以使用switch代替while。任何带有break关键字的语言结构都可能有效。

      选项4.利用对象生命周期

      另一种方法利用对象生命周期。使用上下文对象来携带你的参数(这是我们天真的例子可疑的缺点)并在你完成后处理它。

      class MyContext
      {
         ~MyContext()
         {
              DoSomethingNoMatterWhat();
         }
      }
      
      void MainMethod()
      {
          MyContext myContext;
          ok = DoSomething(myContext);
          if (!ok)
          {
              _log.Error("Oops");
              return;
          }
          ok = DoSomethingElse(myContext);
          if (!ok)
          {
              _log.Error("Oops");
              return;
          }
          ok = DoSomethingMore(myContext);
          if (!ok)
          {
              _log.Error("Oops");
          }
      
          //DoSomethingNoMatterWhat will be called when myContext goes out of scope
      }
      

      注意:确保您了解所选语言的对象生命周期。为此需要某种确定性的垃圾收集,即你必须知道何时调用析构函数。在某些语言中,您需要使用Dispose而不是析构函数。

      选项4.1。利用对象生命周期(包装模式)

      如果您要使用面向对象的方法,也可以正确使用。这个选项使用一个类来包装&#34;需要清理的资源以及其他操作。

      class MyWrapper 
      {
         bool DoSomething() {...};
         bool DoSomethingElse() {...}
      
      
         void ~MyWapper()
         {
              DoSomethingNoMatterWhat();
         }
      }
      
      void MainMethod()
      {
          bool ok = myWrapper.DoSomething();
          if (!ok)
              _log.Error("Oops");
              return;
          }
          ok = myWrapper.DoSomethingElse();
          if (!ok)
             _log.Error("Oops");
              return;
          }
      }
      //DoSomethingNoMatterWhat will be called when myWrapper is destroyed
      

      同样,请确保您了解对象的生命周期。

      选项5.语言技巧:使用短路评估

      另一种技巧是利用short-circuit evaluation

      if (DoSomething1() && DoSomething2() && DoSomething3())
      {
          DoSomething4();
      }
      DoSomethingNoMatterWhat();
      

      此解决方案利用了&amp;&amp; amp;&amp;操作员工作。左右边的&amp;&amp;评估为false,从不评估右侧。

      当需要紧凑代码并且代码不太可能看到太多维护时,这个技巧最有用,例如,您正在实现一个众所周知的算法。对于更一般的编码,此代码的结构太脆弱;即使是对逻辑的微小改动也可能触发完全重写。

答案 4 :(得分:60)

只做

if( executeStepA() && executeStepB() && executeStepC() )
{
    // ...
}
executeThisFunctionInAnyCase();

就这么简单。


由于三次编辑,每次从根本上更改了问题(如果将修订版重新计入版本#1,则有四个),我将包含我正在回答的代码示例:

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

executeThisFunctionInAnyCase();

答案 5 :(得分:35)

实际上有一种方法可以推迟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

答案 6 :(得分:34)

有一种很好的技术,它不需要带有return语句的附加包装函数(Itjax规定的方法)。它使用do while(0)伪循环。 while (0)确保它实际上不是循环但只执行一次。但是,循环语法允许使用break语句。

void foo()
{
  // ...
  do {
      if (!executeStepA())
          break;
      if (!executeStepB())
          break;
      if (!executeStepC())
          break;
  }
  while (0);
  // ...
}

答案 7 :(得分:19)

你也可以这样做:

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

通过这种方式,您可以获得最小的线性增长大小,每次调用+1行,并且可以轻松维护。


编辑 :(谢谢@Unda)因为你的能见度不高而不是大粉丝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();

答案 8 :(得分:18)

这会有用吗?我认为这与您的代码相同。

bool condition = true; // using only one boolean variable
if (condition) condition = executeStepA();
if (condition) condition = executeStepB();
if (condition) condition = executeStepC();
...
executeThisFunctionInAnyCase();

答案 9 :(得分:14)

假设所需的代码是我目前看到的:

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

我会说正确的方法,因为它是最简单的阅读和最容易维护,会有较少的缩进程度,这是(目前)问题的既定目的。

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

这可以避免任何goto,异常,虚拟while循环或其他困难结构的需求,并且只需简单的工作就可以了。

答案 10 :(得分:12)

您可以将所有if条件按照您想要的格式放在自己的函数中,返回时执行executeThisFunctionInAnyCase()函数。

从OP的基础示例中,条件测试和执行可以这样分开;

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

然后这样称呼;

InitialSteps();
executeThisFunctionInAnyCase();

如果C ++ 11 lambdas可用(OP中没有C ++ 11标签,但它们可能仍然是一个选项),那么我们可以放弃单独的函数并将其包装成lambda。

// Capture by reference (variable access may be required)
auto initialSteps = [&]() {
  // any additional code
  bool conditionA = executeStepA();
  if (!conditionA)
    return;
  // any additional code
  bool conditionB = executeStepB();
  if (!conditionB)
    return;
  // any additional code
  bool conditionC = executeStepC();
  if (!conditionC)
    return;
};

initialSteps();
executeThisFunctionInAnyCase();

答案 11 :(得分:12)

  

可以以某种方式使用break语句吗?

也许不是最佳解决方案,但您可以将语句放在do .. while (0)循环中,并使用break语句代替return

答案 12 :(得分:10)

如果您不喜欢goto并且不喜欢do { } while (0);循环并喜欢使用C ++,您也可以使用临时lambda来产生相同的效果。

[&]() { // create a capture all lambda
  if (!executeStepA()) { return; }
  if (!executeStepB()) { return; }
  if (!executeStepC()) { return; }
}(); // and immediately call it

executeThisFunctionInAnyCase();

答案 13 :(得分:9)

你这样做..

coverConditions();
executeThisFunctionInAnyCase();

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

100次的99次,这是唯一的方法。

永远不要尝试做某事&#34;棘手&#34;在计算机代码中。


顺便说一句,我非常确定以下是您所考虑的实际解决方案......

continue 语句在算法编程中至关重要。 (就像goto语句在算法编程中一样重要。)

在许多编程语言中,您可以这样做:

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

(首先请注意:像这个例子的裸体块是编写漂亮代码的关键和重要部分,特别是如果你正在处理&#34;算法&#34;编程。)

再说一遍,正是你头脑中的那个,对吗?这就是写它的美妙方式,所以你有很好的直觉。

然而,不幸的是,在当前版本的objective-c (旁白 - 我不了解Swift,对不起),有一个可行的功能,它检查封闭块是否是一个循环。

enter image description here

以下是你如何解决这个问题......

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

所以不要忘记......

do {} while(false);

只是意味着&#34;执行此操作一次&#34;。

即,写do{}while(false);和简单地写{}之间完全没有区别。

这现在可以完美地按照您的意愿运行......这里是输出......

enter image description here

所以,您可能会在脑海中看到算法。你应该总是试着写下你脑子里的东西。 (特别是如果你不清醒,因为当漂亮的时候出现!:))

&#34;算法&#34;在这种情况下发生了很多的项目,在objective-c中,我们总是有一个像...这样的宏。

#define RUNONCE while(false)

...所以你可以这样做......

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

有两点:

a,尽管愚蠢的是,objective-c检查了一个继续语句所在的块的类型,但它正在努力解决这个问题&#34;。所以这是一个艰难的决定。

b,在示例中,您应该缩进该块吗?我在这样的问题上失眠了,所以我无法建议。

希望它有所帮助。

答案 14 :(得分:9)

代码中的IF / ELSE链不是语言问题,而是程序的设计。如果您能够重新计算或重写您的程序,我建议您查看设计模式(http://sourcemaking.com/design_patterns)以找到更好的解决方案。

通常,当你看到很多IF&amp;在你的代码中,它是实现策略设计模式(http://sourcemaking.com/design_patterns/strategy/c-sharp-dot-net)或其他模式组合的机会。

我确信有一些替代方案可以写出一长串的if / else,但我怀疑它们会改变什么,除了链条看起来很漂亮(然而,美丽在旁观者的眼中仍然存在)也适用于代码:-))。您应该关注这样的事情(在6个月内,当我遇到新情况并且我不记得有关此代码的任何内容时,我能够轻松添加它吗?或者如果链条发生变化,速度和无错误怎么办?我会实施吗)

答案 15 :(得分:8)

已经有很多好的答案,但是他们中的大多数人似乎在一些(实际上很少)的灵活性上进行权衡。不需要这种权衡的常见方法是添加 status / keep-going 变量。当然,价格是追踪的一个额外值:

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;

答案 16 :(得分:8)

如果执行函数失败而不是返回false,则抛出异常。那么你的调用代码可能如下所示:

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

当然,我假设在原始示例中执行步骤只会在步骤内发生错误的情况下返回false?

答案 17 :(得分:6)

如果您的代码与示例一样简单,并且您的语言支持短路评估,那么您可以尝试这样做:

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

如果您将参数传递给函数并返回其他结果,以致您的代码无法以先前的方式编写,那么许多其他答案将更适合此问题。

答案 18 :(得分:6)

为什么没有人提供最简单的解决方案? :d

如果您的所有功能都具有相同的签名,那么您可以这样做(对于C语言):

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

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

    if (!condition) {
        break;
    }
}

executeThisFunctionInAnyCase();

对于干净的C ++解决方案,您应该创建一个包含 execute 方法的接口类,并将您的步骤包装在对象中。
然后,上面的解决方案将如下所示:

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

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

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

executeThisFunctionInAnyCase();

答案 19 :(得分:6)

对于C ++ 11及更高版本,一个不错的方法可能是实现类似于D's scope(exit)机制的范围退出系统。

实现它的一种可能方法是使用C ++ 11 lambda和一些辅助宏:

template<typename F> struct ScopeExit 
{
    ScopeExit(F f) : fn(f) { }
    ~ScopeExit() 
    { 
         fn();
    }

    F fn;
};

template<typename F> ScopeExit<F> MakeScopeExit(F f) { return ScopeExit<F>(f); };

#define STR_APPEND2_HELPER(x, y) x##y
#define STR_APPEND2(x, y) STR_APPEND2_HELPER(x, y)

#define SCOPE_EXIT(code)\
    auto STR_APPEND2(scope_exit_, __LINE__) = MakeScopeExit([&](){ code })

这将允许您从函数中提前返回并确保您定义的任何清理代码始终在范围退出时执行:

SCOPE_EXIT(
    delete pointerA;
    delete pointerB;
    close(fileC); );

if (!executeStepA())
    return;

if (!executeStepB())
    return;

if (!executeStepC())
    return;

宏真的只是装饰。 MakeScopeExit()可以直接使用。

答案 20 :(得分:6)

在C ++中(问题被标记为C和C ++),如果你不能改变函数来使用异常,你仍然可以使用异常机制,如果你写一个小辅助函数,如

struct function_failed {};
void attempt(bool retval)
{
  if (!retval)
    throw function_failed(); // or a more specific exception class
}

然后您的代码可以如下所示:

try
{
  attempt(executeStepA());
  attempt(executeStepB());
  attempt(executeStepC());
}
catch (function_failed)
{
  // -- this block intentionally left empty --
}

executeThisFunctionInAnyCase();

如果您使用的是精确的语法,则可以通过显式强制转换来使其工作:

struct function_failed {};
struct attempt
{
  attempt(bool retval)
  {
    if (!retval)
      throw function_failed();
  }
};

然后您可以将代码编写为

try
{
  (attempt) executeStepA();
  (attempt) executeStepB();
  (attempt) executeStepC();
}
catch (function_failed)
{
  // -- this block intentionally left empty --
}

executeThisFunctionInAnyCase();

答案 21 :(得分:5)

假设您不需要单独的条件变量,反转测试并使用else-falthrough作为&#34; ok&#34;路径将允许您获得更多垂直的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();

省略失败的变量会使代码过于模糊IMO。

声明里面的变量很好,不用担心= 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();

答案 22 :(得分:5)

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

yUML class diagram

以下是一个示例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

答案 23 :(得分:5)

这看起来像状态机,因为您可以使用state-pattern轻松实现它。

在Java中,它看起来像这样:

interface StepState{
public StepState performStep();
}

实施可以如下工作:

class StepA implements StepState{ 
    public StepState performStep()
     {
         performAction();
         if(condition) return new StepB()
         else return null;
     }
}

等等。然后你可以用:

替换大if条件
Step toDo = new StepA();
while(toDo != null)
      toDo = toDo.performStep();
executeThisFunctionInAnyCase();

答案 24 :(得分:4)

有几个答案暗示了我多次看到和使用的模式,特别是在网络编程中。在网络堆栈中,通常存在很长的请求序列,其中任何一个都可能失败并将停止该过程。

常见的模式是使用do { } while (false);

我使用while(false)的宏来制作do { } once;常见的模式是:

do
{
    bool conditionA = executeStepA();
    if (! conditionA) break;
    bool conditionB = executeStepB();
    if (! conditionB) break;
    // etc.
} while (false);

这种模式相对容易阅读,允许使用正确破坏的对象,并避免多次返回,使步进和调试更容易。

答案 25 :(得分:4)

为了改进Mathieu的C ++ 11答案并避免因使用std::function而产生的运行时成本,我建议使用以下

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

这个简单的模板类将接受任何可以在没有任何参数的情况下调用的函子,并且没有任何动态内存分配,因此更好地符合C ++的抽象目标,而不会产生不必要的开销。附加功能模板用于简化模板参数推导(不适用于类模板参数)的使用

用法示例:

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

就像Mathieu的回答一样,这个解决方案是完全异常安全的,并且在所有情况下都会调用executeThisFunctionInAnyCase。如果executeThisFunctionInAnyCase本身抛出,则析构函数被隐式标记为noexcept,因此将发出对std::terminate的调用,而不是在堆栈展开期间引发异常。

答案 26 :(得分:3)

一种有趣的方式是处理异常。

try
{
    executeStepA();//function throws an exception on error
    ......
}
catch(...)
{
    //some error handling
}
finally
{
    executeThisFunctionInAnyCase();
}

如果你编写这样的代码,你会以某种方式走错方向。我不会把它视为&#34;问题&#34;拥有这样的代码,但拥有如此混乱的架构&#34;。

提示:与您信任的经验丰富的开发人员讨论这些案例; - )

答案 27 :(得分:3)

另一种方法 - do - while循环,即使之前已经提到过没有它的例子可以显示它的样子:

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

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

executeThisFunctionInAnyCase();

(那里已经是while循环的答案,但do - while循环不会冗余地检查是否为真(在开始时),而是在结束时xD(这可以但是,跳过))。

答案 28 :(得分:3)

好像你想从一个区块做所有的呼叫。 正如其他人提出的那样,您应该使用while循环并使用break或使用return留下的新功能(可能更干净)。

我亲自驱逐goto,即使是功能退出。调试时很难发现它们。

适用于您的工作流程的优雅替代方案是构建一个函数数组并对此进行迭代。

const int STEP_ARRAY_COUNT = 3;
bool (*stepsArray[])() = {
   executeStepA, executeStepB, executeStepC
};

for (int i=0; i<STEP_ARRAY_COUNT; ++i) {
    if (!stepsArray[i]()) {
        break;
    }
}

executeThisFunctionInAnyCase();

答案 29 :(得分:2)

这是我在C-whatever和Java中使用过的几个技巧:

do {
    if (!condition1) break;
    doSomething();
    if (!condition2) break;
    doSomethingElse()
    if (!condition3) break;
    doSomethingAgain();
    if (!condition4) break;
    doYetAnotherThing();
} while(FALSE);  // Or until(TRUE) or whatever your language likes

为了清晰起见,我更喜欢嵌套ifs,尤其是在为每种条件准确定义了明确注释的情况下。

答案 30 :(得分:2)

一个简单的解决方案是使用条件布尔变量,并且可以反复重复使用同一个布尔变量,以便按顺序检查正在执行的步骤的所有结果:

    bool cond = executeStepA();
    if(cond) cond = executeStepB();
    if(cond) cond = executeStepC();
    if(cond) cond = executeStepD();

    executeThisFunctionInAnyCase();

并非事先没有必要这样做:bool cond = true; ...然后是if(cond)cond = executeStepA(); cond变量可以立即分配给executeStepA()的结果,从而使代码更简单,更易于阅读。

另一个更奇特但有趣的方法就是这个(有些人可能认为这是IOCCC的一个很好的候选人,但仍然):

    !executeStepA() ? 0 :
      !executeStepB() ? 0 :
      !executeStepC() ? 0 :
      !executeStepD() ? 0 : 1 ;

    executeThisFunctionInAnyCase();

结果与OP发布的内容完全相同,即:

    if(executeStepA()){
        if(executeStepB()){
            if(executeStepC()){
                if(executeStepD()){
                }
            }
        }
    }

    executeThisFunctionInAnyCase();

答案 31 :(得分:2)

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

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

现在,&#34;块&#34;可以使用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;
} 

尽管&#34; for(;;)&#34;建筑,&#34;块&#34;声明只执行一次 这个&#34;阻止&#34;能够以break个句子退出 因此,避免了if else if else if...句子的链 最多,最后一个else可以挂在&#34;块&#34;的末尾,以处理&#34;默认&#34;案例。

此技术旨在避免使用典型且丑陋的do { ... } while(0)方法 在宏block中,定义了一个名为block的变量,该变量以这样一种方式定义,即执行1次迭代。根据宏的替换规则,宏block定义中的标识符block不会被递归替换,因此block成为程序员无法识别的标识符,但内部效果很好控制de&#34;隐藏&#34; for(;;)循环。

此外:这些&#34;块&#34;可以嵌套,因为隐藏变量int block具有不同的范围。

答案 32 :(得分:2)

因为在执行之间你也有 [...代码块...] ,我猜你有内存分配或对象初始化。通过这种方式,您必须关心清除已经在退出时初始化的所有内容,并在遇到问题时清除它,并且任何函数都将返回false。

在这种情况下,我的经验(当我使用CryptoAPI时)最好的是创建小类,在构造函数中初始化数据,在析构函数中你取消初始化它。每个下一个函数类都必须是前一个函数类的子函数。如果出现问题 - 抛出异常。

class CondA
{
public:
    CondA() { 
        if (!executeStepA()) 
            throw int(1);
        [Initialize data]
    }
    ~CondA() {        
        [Clean data]
    }
    A* _a;
};

class CondB : public CondA
{
public:
    CondB() { 
        if (!executeStepB()) 
            throw int(2);
        [Initialize data]
    }
    ~CondB() {        
        [Clean data]
    }
    B* _b;
};

class CondC : public CondB
{
public:
    CondC() { 
        if (!executeStepC()) 
            throw int(3);
        [Initialize data]
    }
    ~CondC() {        
        [Clean data]
    }
    C* _c;
};

然后在你的代码中你只需要打电话:

shared_ptr<CondC> C(nullptr);
try{
    C = make_shared<CondC>();
}
catch(int& e)
{
    //do something
}
if (C != nullptr)
{
   C->a;//work with
   C->b;//work with
   C->c;//work with
}
executeThisFunctionInAnyCase();

我想这是最好的解决方案,如果每次调用ConditionX初始化一些东西,分配内存等等。最好确保一切都将被清理。

答案 33 :(得分:2)

只是旁注;当if范围始终导致return(或循环中断)时,请勿使用else语句。这可以为您节省大量的缩进。

答案 34 :(得分:1)

您可以使用“switch statement”

switch(x)
{
  case 1:
    //code fires if x == 1
    break;
  case 2:
    //code fires if x == 2
    break;

  ...

  default:
    //code fires if x does not match any case
}

相当于:

if (x==1)
{
  //code fires if x == 1
}
else if (x==2)
{
  //code fires if x == 2
}

...

else
{
  //code fires if x does not match any of the if's above
}

但是,我认为没有必要避免if-else-chains。 switch语句的一个限制是它们只测试完全相等;那就是你无法测试“case x&lt; 3”---在C ++中抛出一个错误&amp;在C中它可能有效,但表现出意想不到的方式,这比抛出错误更糟糕,因为你的程序会以意想不到的方式发生故障。

答案 35 :(得分:1)

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

如果将所有方法(executeStepX)打包到Object类中,它可以有一个属性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,则执行特殊步骤)......

我对其他方法的问题,比如ok = execute(); if(execute())是你的代码应该清晰可读,就像正在发生的事情的流程图一样。在流程图中,您将有两个步骤:1。执行2.基于结果的决策

所以你不应该在if语句或类似内容中隐藏重要的重型方法,它们应该独立存在!

答案 36 :(得分:1)

[&]{
  bool conditionA = executeStepA();
  if (!conditionA) return; // break
  bool conditionB = executeStepB();
  if (!conditionB) return; // break
  bool conditionC = executeStepC();
  if (!conditionC) return; // break
}();
executeThisFunctionInAnyCase();

我们使用隐式引用捕获创建一个匿名lambda函数,然后运行它。其中的代码立即运行。

当它想要停止时,它只是return s。

然后,在它运行之后,我们运行executeThisFunctionInAnyCase

lambda中的

returnbreak到块的结尾。任何其他类型的流量控制都可以。

例外情况是孤立的 - 如果你想抓住它们,请明确地做。如果抛出异常,请小心运行executeThisFunctionInAnyCase - 如果它可以在异常处理程序中抛出异常,通常不希望运行executeThisFunctionInAnyCase,因为这会导致混乱(这将导致混乱)语言)。

这种基于内联函数的捕获的一个很好的属性是你可以重构现有的代码。如果你的功能很长,将其分解为组件部分是一个好主意。

适用于更多语言的变体是:

bool working = executeStepA();
working = working && executeStepB();
working = working && executeStepC();
executeThisFunctionInAnyCase();

你写每条短路的单独线路。可以在这些行之间注入代码,在任何情况下为您提供多个&#34;或者您可以在执行步骤之间执行if(working) { /* code */ }以包含应该运行的代码,当且仅当您没有已经救了出来。

面对添加新的流量控制,这个问题的一个好方法应该是强大的。

在C ++中,更好的解决方案是将一个快速的scope_guard类放在一起:

#ifndef SCOPE_GUARD_H_INCLUDED_
#define SCOPE_GUARD_H_INCLUDED_
template<typename F>
struct scope_guard_t {
  F f;
  ~scope_guard_t() { f(); }
};
template<typename F>
scope_guard_t<F> scope_guard( F&& f ) { return {std::forward<F>(f)}; }
#endif

然后在相关代码中:

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

并且scope的析构函数自动运行executeThisFunctionInAnyCase。您可以在范围结束时注入越来越多的此类&#34;&#34; (每当您创建需要清理的非RAII资源时)(给每个人提供不同的名称)。它也可以使用lambdas,因此您可以操作局部变量。

Fancier范围保护可以支持在析构函数中中止调用(使用bool保护),阻止/允许复制和移动,并支持类型删除&#34;便携式&#34;可以从内部上下文返回的范围保护。

答案 37 :(得分:1)

正如@Jefffrey所说,你可以在几乎所有语言中使用条件短路功能,我个人不喜欢条件超过2个条件的语句(超过一个&&||),只是风格问题。这段代码做了同样的事情(可能会编译相同的),它对我来说看起来更清晰。只要executeStepX()中的每个函数都返回一个可以强制转换为{{1}的值,就不需要花括号,中断,返回,函数,lambdas(只有c ++ 11),对象等。如果要执行下一个语句,否则为true

false

任何时候任何函数返回if (executeStepA()) if (executeStepB()) if (executeStepC()) //... if (executeStepN()); // <-- note the ';' executeThisFunctionInAnyCase(); ,都不会调用下一个函数。

我喜欢@Mayerz的答案,因为你可以在运行时改变要调用的函数(及其顺序)。这种感觉就像observer pattern,你有一组订阅者(函数,对象,等等),只要满足给定的任意条件,就会调用和执行这些订阅者。在许多情况下,这可能是过度杀戮,所以明智地使用false

答案 38 :(得分:1)

很简单。

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

这将保留布尔变量conditionAL,conditionS和conditionS。

答案 39 :(得分:1)

在某些特殊情况下,虚拟继承树和虚方法调用可以处理您的决策树逻辑。

objectp -> DoTheRightStep();

我遇到过这种情况,当它像魔杖一样工作时。 当然,如果您的ConditionX可以一致地转换为“对象是A”条件,这是有道理的。

答案 40 :(得分:0)

为什么要使用OOP?在伪代码中:

abstract class Abstraction():
   function executeStepA(){...};
   function executeStepB(){...};   
   function executeStepC(){...};
   function executeThisFunctionInAnyCase(){....}
   abstract function execute():

class A(Abstraction){
   function execute(){
      executeStepA();
      executeStepB();
      executeStepC();
   }
}
 class B(Abstraction){
   function execute(){
      executeStepA();
      executeStepB();
   }
}
class C(Abstraction){
     function execute(){
       executeStepA();
     }
}

这样你的if's消失

item.execute();
item.executeThisFunctionInAnyCase();

通常,使用OOP可以避免使用。

答案 41 :(得分:0)

while(executeStepA() && executeStepB() && executeStepC() && 0);
executeThisFunctionInAnyCase();
即使其他函数没有完成,也必须在任何情况下执行executeThisFunctionInAnyCase()。

while语句:

while(executeStepA() && executeStepB() && executeStepC() && 0)

将执行所有功能,而将不会循环作为其明确的错误陈述。 这也可以在退出前重试一段时间。

答案 42 :(得分:0)

如何将条件内容移动到其他内容,如:

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

这确实解决了缩进问题。

答案 43 :(得分:0)

已经提到了假循环,但到目前为止我没有在给出的答案中看到以下技巧:你可以使用do { /* ... */ } while( evaulates_to_zero() );来实现双向早期断路器。使用break终止循环而不通过评估条件语句,而continue将评估条件语句。

如果你有两种终结方法,你可以使用它,其中一条路径必须比另一条路径做更多的工作:

#include <stdio.h>
#include <ctype.h>

int finalize(char ch)
{
    fprintf(stdout, "read a character: %c\n", (char)toupper(ch));

    return 0;
}

int main(int argc, char *argv[])
{
    int ch;
    do {
        ch = fgetc(stdin);
        if( isdigit(ch) ) {
            fprintf(stderr, "read a digit (%c): aborting!\n", (char)ch);
            break;
        }
        if( isalpha(ch) ) {
            continue;
        }
        fprintf(stdout, "thank you\n");
    } while( finalize(ch) );

    return 0;
}

执行此操作会提供以下会话协议:

dw@narfi ~/misc/test/fakeloopbreak $ ./fakeloopbreak 
-
thank you
read a character: -

dw@narfi ~/misc/test/fakeloopbreak $ ./fakeloopbreak 
a
read a character: A

dw@narfi ~/misc/test/fakeloopbreak $ ./fakeloopbreak 
1
read a digit (1): aborting!

答案 44 :(得分:0)

到目前为止,已有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();

优点:

  • 不会以巨大的缩进级别结束
  • 错误处理代码(可选)位于调用失败函数
  • 之后的行上

缺点:

  • 如果你的步骤不仅仅包含在单个函数调用中,那么会变得丑陋
  • 如果需要任何流程而不是“按顺序执行步骤,如果一个失败则中止”

答案 45 :(得分:-1)

别。有时你需要复杂性。诀窍在于你是如何做到的。拥有“当条件存在时你做什么”可能占用一些空间,使得if语句树看起来比实际大。因此,如果设置了条件,而不是做事情,只需将变量设置为该情况的特定值(枚举或数字,如10,014。在if树之后,然后有一个case语句,对于该特定值,做任何你会在if树上完成。它会照亮树。 如果是x1    如果是x2       如果是x3           VAR1:= 100016;       万一    万一 万一 案件 VAR = 100016     做案例100016事......

答案 46 :(得分:-1)

如果在各个步骤下移动条件,条件可以简化,这里是c#伪代码,

这个想法是使用编排而不是中央编排。

void Main()
{
    Request request = new Request();
    Response response = null;

    // enlist all the processors
    var processors = new List<IProcessor>() {new StepA() };

    var factory = new ProcessorFactory(processors);

    // execute as a choreography rather as a central orchestration.
    var processor = factory.Get(request, response);
    while (processor != null)
    {
        processor.Handle(request, out response);
        processor = factory.Get(request, response); 
    }

    // final result...
    //response
}

public class Request
{
}

public class Response
{
}

public interface IProcessor
{
    bool CanProcess(Request request, Response response);
    bool Handle(Request request, out Response response);
}

public interface IProcessorFactory
{
    IProcessor Get(Request request, Response response);
}   

public class ProcessorFactory : IProcessorFactory
{
    private readonly IEnumerable<IProcessor> processors;

    public ProcessorFactory(IEnumerable<IProcessor> processors)
    {
        this.processors = processors;
    }

    public IProcessor Get(Request request, Response response)
    {
        // this is an iterator
        var matchingProcessors = processors.Where(x => x.CanProcess(request, response)).ToArray();

        if (!matchingProcessors.Any())
        {
            return null;
        }

        return matchingProcessors[0];
    }
}

// Individual request processors, you will have many of these...
public class StepA: IProcessor
{
    public bool CanProcess(Request request, Response response)
    {
        // Validate wether this can be processed -- if condition here
        return false;
    }

    public bool Handle(Request request, out Response response)
    {
        response = null;
        return false;
    }
}

答案 47 :(得分:-1)

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

之前提到过这种方法,但我想更深入地了解使用这种方法对付箭头类型代码的优点。

根据我的经验,这种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();

因此,如果只有一个函数调用的列表,你可以做所有那些讨厌的事情,并确保当函数退出时你不会有任何初始化的残留。

你们的队友们非常感谢能够在1场比赛中完成100行代码。

但等等! 还有更多! 箭头类型代码的一个主要特征是您需要具有特定的订单! 并且整个应用程序中的特定顺序需要相同(多线程死锁避免规则1:在整个应用程序中始终以相同的顺序使用互斥锁) 如果其中一个新手,只是以随机顺序生成函数会怎么样?更糟糕的是,如果要求您将其暴露给java或C#,该怎么办? (是的,跨平台是痛苦的)

幸运的是,这是一种方法。 在要点中,我建议这样做:

  • 创建一个枚举,从第一个资源开始到最后一个

  • 定义一个从枚举中获取值并将其与函数指针配对的对

  • 将这些对放在一个向量中(我知道,我只是定义了一个地图的使用:),但我总是为小数字做矢量

  • 将可变参数宏从将函数指针转换为整数(在java或C#中很容易公开;)))

  • 在可变参数函数中
  • ,对那些整数进行排序

  • 运行时,运行分配给该整数的函数。

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

  • 用于初始化的一行代码,无论有多少东西都可以使用

  • 强制执行调用顺序:除非你(架构师)决定允许这样做,否则你不能在loadResources之前调用startInfernalMachine

  • 如果一路上发生故障,请完成清理工作(考虑到您已正确取消初始化)

  • 更改整个应用程序中初始化的顺序意味着只更改枚举中的顺序

答案 48 :(得分:-1)

  

如何避免“if”链?

我还没有找到使用递归的答案。 (如果你听过这个,请立即停止我;)

我也没有找到支持来确定哪个if-clause失败了(想想多线程)......(我曾经在嵌入式系统中遇到过40个深链。)作为调试支持的一部分,我用了一个位集。

  // here I use 26 bits in a result bitset - you can use as many bits as you need
  typedef  std::bitset<26>  BitSet26;  

我的递归方法需要一些支持......来执行步骤选择:

  int executeStep(int i, bool& retVal)
  {
     int nxt = 0;
     switch (i)
     {
     case 'A': { retVal = executeStepA(); nxt = 'B'; } break;
     case 'B': { retVal = executeStepB(); nxt = 'C'; } break;
     case 'C': { retVal = executeStepC(); nxt = 'D'; } break;
     case 'D': { retVal = executeStepD(); nxt = 'I'; } break;
        // ...
     case 'I': { retVal = executeStepI(); nxt = 'J'; } break;
     case 'J': { retVal = executeStepJ(); nxt = 'R'; } break;
        // ...
     case 'R': { retVal = executeStepR(); nxt = 'S'; } break;
     case 'S': { retVal = executeStepS(); nxt = 'Z'; } break;
        // ...
     case 'Z': { retVal = executeStepZ(); nxt = 0;   } break;

     default:  { retVal = false;          nxt = 0;   } break;
     }
     return(nxt);
  }

我认为上面的内容非常简单,但实际上我没有所有的“executeStepN()”方法,并且经常在相应的switch子句中安装它们包含的代码。

启动序列可能如下所示:

     BitSet26  b (0); // clear all bits

     int retVal = tRecurse(b, 'A'); // launch the sequence, 
     //                                starting with step 'A'

     executeThisFunctionInAnyCase();

这将通过序列运行递归,而没有if链。当步骤返回false时,递归终止。在tRecurse()结束时可以很容易地传回一个唯一的retVal(错误代码),但是我想展示使用位模式来传递通过/失败信息。

递归看起来像这样:

  int tRecurse(BitSet26& b, int i)
  {
     int  retVal = 0;
     bool result = false;
     int     nxt = executeStep(i, result);

     if (result) // if (previous step return true)
     {
        b[i-'A'] = 1;  // map 'A' to 0 index

        retVal = tRecurse(b, nxt); // recurse

     } // else done, b initialized to 0

     return(retVal);
  }

没有涉及链条。好吧,我承认,我喜欢递归。

以下是启动递归并报告结果的示例:

  int foo(void)
  {
     BitSet26  b;

     int retVal = tRecurse(b, 'A'); // launch the sequence, starting with step A

     executeThisFunctionInAnyCase();

#ifndef  ndbg          
     { 
        // development diagnostics
        std::cout << "BitSet26: " <<  b.to_string() << std::endl;
        std::string c = "ZYXWVUTSRQPONMLKJIHGFEDCBA";
        std::cout << "          ";
        for (int i=0; i < 26; i++)
        {
           char kar = c[i];
           if(!b[kar-'A']) kar = ' ';
           std::cout << kar;
        }

        std::cout << "   : PASS" << std::endl << "          ";
        for (int i=0; i < 26; i++)
        {
           char kar = static_cast<char>(tolower(c[i]));
           if(b[kar-'A']) kar = ' ';
           std::cout << kar;
        }
        std::cout << "   : Failed (or incomplete)" << std::endl;
     }
#endif
     return(retVal);
  }

用户输出是开发诊断,所以不要介意凌乱。

相反,请考虑您的错误处理如何根据找到的位模式决定恢复。

一个简单的例子,只有8个执行步骤成功,第9个'Z'失败。我想我应该只在bitset中使用9位......呃。

./dumy104 
A
B
C
D
...
I
J
...
R
S
...
Z
executeThisFunctionInAnyCase()

BitSet26: 00000001100000001100001111
                 SR       JI    DCBA   : PASS
          zyxwvut  qponmlk  hgfe       : Failed (Z) (or incomplete)

FINI

更新2014 07 30

忘记提出关键想法:

您是否听说过将循环展开为优化技术?

在这里,我使用递归将if-then链转换为循环。当然,递归并不是编写循环的唯一方法。

答案 49 :(得分:-1)

鉴于功能:

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

答案 50 :(得分:-2)

关于当前代码示例,基本上是问题#2,

[...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...

但是,细节可能会因实际问题而有很大差异。

当我需要这样的一般最终动作时,我不会在现场定义自定义struct,而是经常使用一般的范围保护类。对于C ++ 98,范围保护是invented by Petru Marginean,然后使用临时生命周期扩展技巧。在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。这更加清晰,更通用,因为它支持嵌套循环或开关。但是,您询问了break

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