当代码流是这样的:

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

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

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

其他回答

TLDR: RAII、事务性代码(仅设置结果或在已经计算时返回内容)和异常。

长一点的回答:

在C语言中,这类代码的最佳实践是在代码中添加一个EXIT/CLEANUP/other标签,在该标签中进行本地资源的清理,并返回错误代码(如果有的话)。这是最佳实践,因为它将代码自然地划分为初始化、计算、提交和返回:

error_code_type c_to_refactor(result_type *r)
{
    error_code_type result = error_ok; //error_code_type/error_ok defd. elsewhere
    some_resource r1, r2; // , ...;
    if(error_ok != (result = computation1(&r1))) // Allocates local resources
        goto cleanup;
    if(error_ok != (result = computation2(&r2))) // Allocates local resources
        goto cleanup;
    // ...

    // Commit code: all operations succeeded
    *r = computed_value_n;
cleanup:
    free_resource1(r1);
    free_resource2(r2);
    return result;
}

在C语言中,在大多数代码库中,if(error_ok !=…goto代码通常隐藏在一些方便的宏(RET(computation_result), ENSURE_SUCCESS(computation_result, return_code)等)后面。

c++比C提供了额外的工具:

清理块功能可以作为RAII实现,这意味着您不再需要整个清理块,并允许客户端代码添加早期返回语句。 当无法继续时抛出抛出,转换所有if(error_ok !=…变成直接的通话。

等价的c++代码:

result_type cpp_code()
{
    raii_resource1 r1 = computation1();
    raii_resource2 r2 = computation2();
    // ...
    return computed_value_n;
}

这是最佳实践,因为:

It is explicit (that is, while error handling is not explicit, the main flow of the algorithm is) It is straightforward to write client code It is minimal It is simple It has no repetitive code constructs It uses no macros It doesn't use weird do { ... } while(0) constructs It is reusable with minimal effort (that is, if I want to copy the call to computation2(); to a different function, I don't have to make sure I add a do { ... } while(0) in the new code, nor #define a goto wrapper macro and a cleanup label, nor anything else).

也许就像这样

#define EVER ;;

for(EVER)
{
    if(!check()) break;
}

或者使用异常

try
{
    for(;;)
        if(!check()) throw 1;
}
catch()
{
}

使用异常还可以传递数据。

从函数式编程的角度来看,这是一个众所周知的、很好解决的问题——也许是单子。

为了回应下面我收到的评论,我在这里编辑了我的介绍:你可以在不同的地方找到实现c++单子的完整细节,这将让你实现Rotsor建议的目标。这需要一段时间来理解单子,所以我要在这里建议一个快速的“穷人”单子式机制,你只需要知道boost::optional。

设置你的计算步骤如下:

boost::optional<EnabledContext> enabled(boost::optional<Context> context);
boost::optional<EnergisedContext> energised(boost::optional<EnabledContext> context);

显然,每个计算步骤都可以执行类似于返回boost::none这样的操作,如果给出的可选参数为空的话。例如:

struct Context { std::string coordinates_filename; /* ... */ };

struct EnabledContext { int x; int y; int z; /* ... */ };

boost::optional<EnabledContext> enabled(boost::optional<Context> c) {
   if (!c) return boost::none; // this line becomes implicit if going the whole hog with monads
   if (!exists((*c).coordinates_filename)) return boost::none; // return none when any error is encountered.
   EnabledContext ec;
   std::ifstream file_in((*c).coordinates_filename.c_str());
   file_in >> ec.x >> ec.y >> ec.z;
   return boost::optional<EnabledContext>(ec); // All ok. Return non-empty value.
}

然后把它们串在一起:

Context context("planet_surface.txt", ...); // Close over all needed bits and pieces

boost::optional<EnergisedContext> result(energised(enabled(context)));
if (result) { // A single level "if" statement
    // do work on *result
} else {
    // error
}

这样做的好处是,您可以为每个计算步骤编写定义清晰的单元测试。此外,调用读起来像简单的英语(通常是函数式风格的情况)。

如果你不关心不可变性,并且每次返回相同的对象更方便,你可以使用shared_ptr或类似的方法来提出一些变化。

类似于dasblinkenlight的答案,但避免了if语句中的赋值,这可能会被代码评审员“修复”:

bool goOn = check0();
if (goOn) {
    ...
    goOn = check1();
}
if (goOn) {
    ...
    goOn = check2();
}
if (goOn) {
    ...
}

...

当一个步骤的结果需要在下一步之前检查时,我使用这种模式,这与所有检查都可以在前面用一个大if(check1() && check2()…类型的模式。

为什么标记法没有被回答,这是自古以来使用的方法。

//you can use something like this (pseudocode)
long var = 0;
if(condition)  flag a bit in var
if(condition)  flag another bit in var
if(condition)  flag another bit in var
............
if(var == certain number) {
Do the required task
}