当代码流是这样的:

if(check())
{
  ...
  ...
  if(check())
  {
    ...
    ...
    if(check())
    {
      ...
      ...
    }
  }
}

我通常看到这种方法可以避免上述混乱的代码流:

do {
    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
} while(0);

有什么更好的方法可以避免这种工作/黑客,从而使其成为更高级别(行业级别)的代码?

任何跳出盒子的建议都是欢迎的!


当前回答

我不是特别喜欢在这种情况下使用break或return的方法。考虑到通常当我们面对这样的情况时,这通常是一个比较长的方法。

如果我们有多个出口点,当我们想要知道什么会导致某些逻辑被执行时,这可能会造成困难:通常情况下,我们只是继续向上包含该逻辑的块,而这些块的标准告诉我们情况:

例如,

if (conditionA) {
    ....
    if (conditionB) {
        ....
        if (conditionC) {
            myLogic();
        }
    }
}

通过查看封闭块,很容易发现myLogic()只在条件a和条件b和条件c为真时发生。

当有早期的回报时,它变得不那么明显:

if (conditionA) {
    ....
    if (!conditionB) {
        return;
    }
    if (!conditionD) {
        return;
    }
    if (conditionC) {
        myLogic();
    }
}

我们不能再从myLogic()向上导航,查看封闭块来找出条件。

我使用了不同的变通方法。这是其中之一:

if (conditionA) {
    isA = true;
    ....
}

if (isA && conditionB) {
    isB = true;
    ...
}

if (isB && conditionC) {
    isC = true;
    myLogic();
}

(当然欢迎用同一个变量替换所有isA isB isC)

这样的方法至少会让代码的读者知道,当isB && conditionC时执行myLogic()。给读者一个提示,他需要进一步查找使isB为真的原因。

其他回答

在函数中隔离这些决策并使用返回而不是中断被认为是可以接受的实践。虽然所有这些检查都对应于与函数相同的抽象级别,但这是一种非常符合逻辑的方法。

例如:

void foo(...)
{
   if (!condition)
   {
      return;
   }
   ...
   if (!other condition)
   {
      return;
   }
   ...
   if (!another condition)
   {
      return;
   }
   ... 
   if (!yet another condition)
   {
      return;
   }
   ...
   // Some unconditional stuff       
}

有些时候,使用goto实际上是正确的答案——至少对那些没有在宗教信仰中长大的人来说,“不管问题是什么,goto永远都不是答案”——这是其中的一个例子。

此代码使用了do{…}, (0);唯一的目的就是把一个傻瓜打扮成休息的样子。如果你打算使用goto,那就公开使用它。让代码更难阅读是没有意义的。

一种特殊的情况是当你有很多代码和相当复杂的条件时:

void func()
{
   setup of lots of stuff
   ...
   if (condition)
   {
      ... 
      ...
      if (!other condition)
      {
          ...
          if (another condition)
          {
              ... 
              if (yet another condition)
              {
                  ...
                  if (...)
                     ... 
              }
          }
      }
  .... 

  }
  finish up. 
}

没有如此复杂的逻辑,实际上可以更清楚地表明代码是正确的。

void func()
{
   setup of lots of stuff
   ...
   if (!condition)
   {
      goto finish;
   }
   ... 
   ...
   if (other condition)
   {
      goto finish;
   }
   ...
   if (!another condition)
   {
      goto finish;
   }
   ... 
   if (!yet another condition)
   {
      goto finish;
   }
   ... 
   .... 
   if (...)
         ...    // No need to use goto here. 
 finish:
   finish up. 
}

编辑:澄清一下,我绝不是建议使用goto作为通用解决方案。但在某些情况下,goto是比其他解决方案更好的解决方案。

例如,想象一下,我们正在收集一些数据,测试的不同条件是某种“这是正在收集的数据的结束”——这取决于某种“继续/结束”标记,这些标记根据您在数据流中的位置而变化。

现在,当我们完成之后,我们需要将数据保存到一个文件中。

是的,通常有其他解决方案可以提供合理的解决方案,但并不总是如此。

代码流本身已经是一个代码气味,在函数中发生了太多的事情。如果没有直接的解决方案(函数是一个通用的检查函数),那么使用RAII,这样您就可以返回,而不是跳转到函数的结束部分,可能会更好。

如果根据故障发生的位置需要不同的清理步骤,另一个模式很有用:

    private ResultCode DoEverything()
    {
        ResultCode processResult = ResultCode.FAILURE;
        if (DoStep1() != ResultCode.SUCCESSFUL)
        {
            Step1FailureCleanup();
        }
        else if (DoStep2() != ResultCode.SUCCESSFUL)
        {
            Step2FailureCleanup();
            processResult = ResultCode.SPECIFIC_FAILURE;
        }
        else if (DoStep3() != ResultCode.SUCCESSFUL)
        {
            Step3FailureCleanup();
        }
        ...
        else
        {
            processResult = ResultCode.SUCCESSFUL;
        }
        return processResult;
    }

我会推荐一种类似于Mats答案的方法,减去不必要的goto。只在函数中放入条件逻辑。任何始终运行的代码都应该在调用方调用函数之前或之后执行:

void main()
{
    //do stuff always
    func();
    //do other stuff always
}

void func()
{
    if (!condition)
        return;
    ...
    if (!other condition)
        return;
    ...
    if (!another condition)
        return;
    ... 
    if (!yet another condition)
        return;
    ...
}