当代码流是这样的:

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

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

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

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

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


当前回答

我对这里提出的不同答案的数量感到惊讶。但是,最后在我必须更改的代码中(即删除这个do-while(0)黑客或任何东西),我做了一些与这里提到的任何答案不同的事情,我很困惑为什么没有人想到这一点。以下是我所做的:

初始代码:

do {

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

finishingUpStuff.

Now:

finish(params)
{
  ...
  ...
}

if(!check()){
    finish(params);    
    return;
}
...
...
if(!check()){
    finish(params);    
    return;
}
...
...
if(!check()){
    finish(params);    
    return;
}
...
...

所以,这里所做的是,整理的东西被隔离在一个函数中,事情突然变得如此简单和干净!

我认为这个解决方案值得一提,所以在这里提供了它。

其他回答

当然不是答案,而是一个答案(为了完整起见)

而不是:

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

你可以这样写:

switch (0) {
case 0:
    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
}

这仍然是一个伪装的goto,但至少它不再是一个循环。这意味着你不需要非常仔细地检查是否有一些继续隐藏在块的某处。

构造也非常简单,您可以希望编译器会优化它。

正如@jamesdlin所建议的,你甚至可以把它隐藏在一个宏后面

#define BLOC switch(0) case 0:

然后像这样使用它

BLOC {
    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
}

这是可能的,因为C语言语法期望在开关之后有一个语句,而不是一个括号中的块,并且您可以在该语句之前放置一个case标签。到目前为止,我还没有看到允许这样做的意义,但在这种特殊情况下,将开关隐藏在一个漂亮的宏后面是很方便的。

使用异常。您的代码将看起来更加清晰(并且创建异常正是为了处理程序执行流中的错误)。有关清理资源(文件描述符,数据库连接等),请阅读文章“为什么c++不提供一个“finally”结构?”

#include <iostream>
#include <stdexcept>   // For exception, runtime_error, out_of_range

int main () {
    try {
        if (!condition)
            throw std::runtime_error("nope.");
        ...
        if (!other condition)
            throw std::runtime_error("nope again.");
        ...
        if (!another condition)
            throw std::runtime_error("told you.");
        ...
        if (!yet another condition)
            throw std::runtime_error("OK, just forget it...");
    }
    catch (std::runtime_error &e) {
        std::cout << e.what() << std::endl;
    }
    catch (...) {
        std::cout << "Caught an unknown exception\n";
    }
    return 0;
}

对我来说,当(0)是好的时候做{…}。如果您不想看到do{…}while(0),您可以为它们定义替代关键字。

例子:

SomeUtilities.hpp:

#define BEGIN_TEST do{
#define END_TEST }while(0);

SomeSourceFile.cpp:

BEGIN_TEST
   if(!condition1) break;
   if(!condition2) break;
   if(!condition3) break;
   if(!condition4) break;
   if(!condition5) break;
   
   //processing code here

END_TEST

我认为编译器将在二进制版本的do{…}while(0)中删除不必要的while(0)条件,并将断点转换为无条件跳转。您可以检查它的汇编语言版本来确定。

使用goto还可以生成更简洁的代码,并且使用条件-然后跳转的逻辑非常简单。您可以执行以下操作:

{
   if(!condition1) goto end_blahblah;
   if(!condition2) goto end_blahblah;
   if(!condition3) goto end_blahblah;
   if(!condition4) goto end_blahblah;
   if(!condition5) goto end_blahblah;
   
   //processing code here

 }end_blah_blah:;  //use appropriate label here to describe...
                   //  ...the whole code inside the block.
 

注意标签放在结束}之后。这是避免一个可能的问题,在goto中,不小心将代码放在中间,因为你没有看到标签。现在就像do{…}while(0)没有条件代码。

为了让代码更清晰,更容易理解,你可以这样做:

SomeUtilities.hpp:

#define BEGIN_TEST {
#define END_TEST(_test_label_) }_test_label_:;
#define FAILED(_test_label_) goto _test_label_

SomeSourceFile.cpp:

BEGIN_TEST
   if(!condition1) FAILED(NormalizeData);
   if(!condition2) FAILED(NormalizeData);
   if(!condition3) FAILED(NormalizeData);
   if(!condition4) FAILED(NormalizeData);
   if(!condition5) FAILED(NormalizeData);

END_TEST(NormalizeData)

有了这个,你可以做嵌套块,并指定你想要退出/跳出的位置。

BEGIN_TEST
   if(!condition1) FAILED(NormalizeData);
   if(!condition2) FAILED(NormalizeData);

   BEGIN_TEST
      if(!conditionAA) FAILED(DecryptBlah);
      if(!conditionBB) FAILED(NormalizeData);   //Jump out to the outmost block
      if(!conditionCC) FAILED(DecryptBlah);
  
      // --We can now decrypt and do other stuffs.

   END_TEST(DecryptBlah)

   if(!condition3) FAILED(NormalizeData);
   if(!condition4) FAILED(NormalizeData);

   // --other code here

   BEGIN_TEST
      if(!conditionA) FAILED(TrimSpaces);
      if(!conditionB) FAILED(TrimSpaces);
      if(!conditionC) FAILED(NormalizeData);   //Jump out to the outmost block
      if(!conditionD) FAILED(TrimSpaces);

      // --We can now trim completely or do other stuffs.

   END_TEST(TrimSpaces)

   // --Other code here...

   if(!condition5) FAILED(NormalizeData);

   //Ok, we got here. We can now process what we need to process.

END_TEST(NormalizeData)

杂乱的代码不是goto的错,而是程序员的错。不使用goto仍然可以生成意大利面条代码。

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

例如:

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

我就是这么做的。

void func() {
  if (!check()) return;
  ...
  ...

  if (!check()) return;
  ...
  ...

  if (!check()) return;
  ...
  ...
}