do-while-false循环是常见的吗?

前一阵子我改变了我处理c风格错误的方式。

我发现很多代码看起来像这样:

int errorCode = 0; errorCode = doSomething(); if (errorCode == 0) { errorCode = doSomethingElse(); } ... if (errorCode == 0) { errorCode = doSomethingElseNew(); } 

但最近我一直这样写:

 int errorCode = 0; do { if (doSomething() != 0) break; if (doSomethingElse() != 0) break; ... if (doSomethingElseNew() != 0) break; } while(false); 

我已经看到很多代码在出现错误后没有执行任何内容,但它始终以第一种方式编写。 有没有其他人使用这种风格,如果你不这样做,为什么?

编辑:只是为了澄清,通常这个构造使用errno否则我将在断开之前将值赋给int 。 此外,通常还有更多的代码,而不仅仅是if (error == 0 )子句中的单个函数调用。 但是,要考虑很多好点。

第二个片段看起来不对劲。 你有效地重新发明了。

任何阅读第一种代码风格的人都会立即知道发生了什么,第二种风格需要更多检查,从而使维护更加困难,从长远来看,没有真正的好处。

编辑,在第二种风格,你已丢弃错误代码,所以你不能采取任何纠正措施或显示信息性的消息,记录有用的东西等….

如果您使用的是C ++,只需使用exception即可。 如果您使用的是C,那么第一种风格效果很好。 但是如果你真的想要第二种风格,只需使用gotos – 这正是gotos真正是最清晰的结构的情况。

  int errorCode = 0; if ((errorCode = doSomething()) != 0) goto errorHandler; if ((errorCode = doSomethingElse()) != 0) goto errorHandler; ... if ((errorCode = doSomethingElseNew()) != 0) goto errorHandler; return; errorHandler: // handle error 

是的getos可能很糟糕,并且每次调用后的exception或显式error handling可能会更好,但是getos比选择另一个构造来尝试和模拟它们要好得多。 使用gotos还可以为特定错误添加另一个error handling程序:

  int errorCode = 0; if ((errorCode = doSomething()) != 0) goto errorHandler; if ((errorCode = doSomethingElse()) != 0) goto errorHandler; ... if ((errorCode = doSomethingElseNew()) != 0) goto errorHandlerSomethingElseNew; return; errorHandler: // handle error return; errorHandlerSomethingElseNew: // handle error return; 

或者如果error handling更多的是“展开/清理你所做的”变种,你可以像这样构造它:

  int errorCode = 0; if ((errorCode = doSomething()) != 0) goto errorHandler; if ((errorCode = doSomethingElse()) != 0) goto errorHandler1; ... if ((errorCode = doSomethingElseNew()) != 0) goto errorHandler2; errorHandler2: // clean up after doSomethingElseNew errorHandler1: // clean up after doSomethingElse errorHandler: // clean up after doSomething return errorCode; 

这个习惯用法为您提供了不重复清理代码的优势(当然,如果您使用的是C ++,RAII将更加干净地覆盖清理代码。

第一种风格是经验丰富的眼睛同时出现的模式。

第二个需要更多的思考 – 你看它并看到一个循环。 你期待几次迭代,但是当你读完它时,这个心理模型会被打破……

当然,它可能有用,但编程语言不仅仅是告诉计算机做什么的一种方式,它们也是将这些想法传达给其他人的一种方式。

我认为第一个可以让您更好地控制如何处理特定错误。 第二种方式只告诉你发生了错误,而不是它在哪里或是什么。

当然,例外优于两者……

简洁,紧凑,易于快速阅读?

怎么样:

 if ((errorcode = doSomething()) == 0 && (errorcode = doSomethingElse()) == 0 && (errorcode = doSomethingElseNew()) == 0) maybe_something_here; return errorcode; // or whatever is next 

为什么不替换do / while并使用函数断开而返回呢?

你重新改造了goto。

如何使用例外?

 try { DoSomeThing(); DoSomethingElse(); DoSomethingNew(); . . . } catch(DoSomethingException e) { . . } catch(DoSomethingElseException e) { . . } catch(DoSomethingNewException e) { . . } catch(...) { . . } 

你的方法并不是很糟糕,并不像这里的人声称的那样难以理解,但它是非常规的并且会使一些人烦恼(正如你在这里注意到的)。

在您的代码达到一定大小后,第一个可能会非常烦人,因为它有很多样板。

当我无法使用exception时,我倾向于使用的模式更像是:

 fn() { while(true) { if(doIt()) handleError();//error detected... } } bool doIt() { if(!doThing1Succeeds()) return true; if(!doThing2Succeeds()) return true; return false; } 

如果你在签名中放入正确的魔术咒语,你的第二个函数应该被内联到第一个函数中,并且每个函数应该更具可读性。

这在function上与没有非常规语法的while / bail循环相同(并且也更容易理解,因为您将循环/error handling的问题与“您的程序在给定循环中执行的操作”的问题分开)。

这应该通过exception来完成,至少在C ++标记是否正确的情况下。 如果你只使用C,没有错,虽然我建议使用布尔代替,因为你没有使用返回的错误代码。 您不必键入!= 0然后…

我在一些地方使用过这种技术(所以你不是唯一一个这样做的人)。 但是,作为一般规则,我不这样做,而且我对它的使用方式感到好奇。 在一些地方使用仔细的文档(评论),我很好。 在任何地方都使用 – 不,通常不是一个好主意。

相关展品:来自SQLCMD源代码的文件sqlstmt.ec,upload.ec,reload.ec(不是,不是微软的冒名顶替者;我的)。 ‘ .ec ‘扩展名意味着该文件包含ESQL / C – C中的嵌入式SQL,它被预处理为纯C; 您不需要知道ESQL / C来查看循环结构。 循环都标有:

  /* This is a one-cycle loop that simplifies error handling */ 

经典的C语言是:

 if( (error_val = doSomething()) == 0) { //Manage error condition } 

请注意,C从赋值中返回指定的值,从而可以执行测试。 通常人们会写:

 if( ! ( error_val = doSomething())) 

但为了清晰起见,我保留了== 0

关于你的习语……

你的第一个习语是好的。 你的第二个习语是滥用语言,你应该避免使用它。

那个版本怎么样呢

我通常只是做你的第一个例子,或者像这样的布尔值:

 bool statusOkay = true; if (statusOkay) statusOkay = (doSomething() == 0); if (statusOkay) statusOkay = (doSomethingElse() == 0); if (statusOkay) statusOkay = (doSomethingElseNew() == 0); 

但如果您真的热衷于第二种技术的简洁性,那么您可以考虑这种方法:

 bool statusOkay = true; statusOkay = statusOkay && (doSomething() == 0); statusOkay = statusOkay && (doSomethingElse() == 0); statusOkay = statusOkay && (doSomethingElseNew() == 0); 

只是不要指望维护程序员要感谢你!

我使用do { } while (false); 在适当的时候每隔一段时间。 我认为它类似于try / catch块,因为我将代码设置为具有一系列可能exception的决策的块,并且需要通过规则和逻辑的各种路径在最后合并块。

我很确定我只在C编程中使用这个结构,并且它不常用。

通过具体示例,您给出了一系列函数调用,这些调用将在完成系列完成后一个接一个地执行,或者如果检测到错误则系列停止,我可能只使用if语句检查错误变量。

 { int iCallStatus = 0; iCallStatus = doFunc1(); if (iCallStatus == 0) iCallStatus = doFunc2(); if (iCallStatus == 0) icallStatus = doFunc3(); } 

这很简短,即使没有评论,其含义也很简单明了。

我不时遇到的是,这个相当直接的顺序流程的程序步骤并不适用于特定的要求。 我需要的是创建一个包含各种决策的代码块,通常涉及循环或迭代某些系列的数据对象,我想将此系列视为一种事务,如果没有错误或中止,事务将被提交如果在处理交易期间发现某种错误情况。 作为此数据块的一部分,我可以为do { } while (false);的范围创建一组临时变量do { } while (false); 当我使用它时,我总是发表评论,指出这是一次迭代,类似于:

 do { // single loop code block begins // block of statements for business logic with single ending point } while (false); // single loop code block ends 

当我发现自己认为这个构造是必要的时候,我会看看代码是否需要重构,或者函数或函数集是否更合适。

我更喜欢这个结构而不是使用goto语句的原因是使用括号和缩进使源代码更容易阅读。 使用我的编辑器,我可以轻松找到块的顶部和底部,并且缩进可以更容易地将代码可视化为具有单个入口点和已知终点的块。 在块内可能有多个退出点但是我知道它们最终会在哪里结束。 使用这意味着我可以创建超出范围的本地化变量,但只使用括号而不使用do { } while (false); 这样做也是如此。 但是我使用do while因为我需要rest; 能力也是如此。

我会考虑在以下某些条件下使用此样式。 如果正在实现的业务逻辑需要一组由重新加入的不同可能执行路径共享和引用的变量。 如果业务逻辑复杂且具有多个状态并且检查具有多个if语句级别,并且如果在处理期间检测到错误,则设置错误指示并中止处理。

我使用它时唯一可以想到的是有点粗糙的东西,这有助于澄清并使处理中止更容易。 所以基本上我使用类似于使用try / catch抛出exception。

第一种风格是exception优越的一个很好的例子:你甚至看不到算法,因为它隐藏在显式error handling之下。

第二种方式滥用循环结构来模仿误导尝试以避免必须明确拼写goto 。 这显然是邪恶的,长时间使用将导致你走向黑暗的一面。

对我来说,我更喜欢:

 if(!doSomething()) { doSomethingElse(); } doSomethingNew(); 

所有其他的东西都是语法噪音,它掩盖了三个函数调用。 在Else和New中你可以抛出一个错误,或者如果年纪大了,可以使用longjmp返回到之前的一些处理。 不错,干净而且相当明显。

这里似乎存在比你的控制结构更深层次的问题。 为什么你有这么复杂的错误控制? 即你似乎有多种方法来处理不同的错误。

通常,当我收到错误时,我只是中断操作,向用户显示错误消息,并将控制权返回给事件循环(如果是交互式应用程序)。 对于批处理,请记录错误,并继续处理下一个数据项或中止处理。

这种控制流程很容易处理,但有例外。

如果必须处理错误编号,则可以通过在发生错误时继续正常error handling或返回第一个错误来有效地模拟exception。 发生错误后继续处理似乎非常脆弱,或者您的错误条件实际上是控制条件而非错误条件。

老实说,我所知道的更有效的C / C ++程序员只会在这种情况下使用gotos。 一般的方法是使用单个退出标签,并在其后进行所有清理。 该函数只有一个返回路径。 当清理逻辑开始变得复杂/有条件时,则将函数分解为子函数。 这对于使用C / C ++ imo进行编码的系统非常典型,其中您调用的API返回错误代码而不是抛出exception。

一般来说,搞砸了。 由于我所描述的用法非常普遍,所以我认为它很好。

第二种风格通常用于管理C中的资源分配和解除分配,其中RAII没有得到拯救。 通常,您可以在do之前声明一些资源,在伪循环中分配和使用它们,并在外部取消分配它们。

一般范例的一个例子如下:

 int status = 0; // declare and initialize resources BYTE *memory = NULL; HANDLE file = INVALID_HANDLE_VALUE; // etc... do { // do some work // allocate some resources file = CreateFile(...); if(file == INVALID_HANDLE_VALUE) { status = GetLastError(); break; } // do some work with new resources // allocate more resources memory = malloc(...); if(memory == NULL) { status = ERROR_OUTOFMEMORY; break; } // do more work with new resources } while(0); // clean up the declared resources if(file != INVALID_HANDLE_VALUE) CloseHandle(file); if(memory != NULL) free(memory); return status; 

话虽如此,RAII用更清晰的语法解决了同样的问题(基本上,你可以完全忘记清理代码)并处理这种方法没有的一些场景,例如exception。

我以前见过这种模式并且不喜欢它。 通常,可以通过将逻辑拉入单独的函数来清除它。

然后代码变成了

  ... int errorCode = doItAll(); ... int doItAll(void) { int errorCode; if(errorCode=doSomething()!=0) return errorCode; if(errorCode=doSomethingElse()!=0) return errorCode; if(errorCode=doSomethingElseNew()!=0) return errorCode; return 0; } 

将此与清理相结合也变得非常简单,您只需使用像日食回答中的goto和error handling程序。

  ... int errorCode = doItAll(); ... int doItAll(void) { int errorCode; void * aResource = NULL; // Somthing that needs cleanup after doSomethingElse has been called if(errorCode=doSomething()!=0) //Doesn't need cleanup return errorCode; if(errorCode=doSomethingElse(&aResource)!=0) goto cleanup; if(errorCode=doSomethingElseNew()!=0) goto cleanup; return 0; cleanup: releaseResource(aResource); return errorCode; } 

当我管理分配指针时,我使用第二种方法,当函数可以接受失败时,抛出exception并不是真正正确的答案。

我发现在一个地方而不是在几个地方管理指针清理更容易,而且你知道它只会在一个地方返回。

 pointerA * pa = NULL; pointerB * pb = NULL; pointerB * pc = NULL; BOOL bRet = FALSE; pa = new pointerA(); do { if (!dosomethingWithPA( pa )) break; pb = new poninterB(); if(!dosomethingWithPB( pb )) break; pc = new pointerC(); if(!dosemethingWithPC( pc )) break; bRet = TRUE; } while (FALSE); // // cleanup // if (NULL != pa) delete pa; if (NULL != pb) delete pb; if (NULL != pc) delete pc; return bRet; 

与…对比

 pointerA * pa = NULL; pointerB * pb = NULL; pointerB * pc = NULL; 

pointerA * pa = NULL; pointerB * pb = NULL; pointerB * pc = NULL;

pa = new pointerA();
if(!dosomethingWithPA(pa)){
删除pa;
返回FALSE;
}

pb = new poninterB();
if(!dosomethingWithPB(pb)){
删除pa;
删除pb;
返回FALSE;
}
pc = new pointerC();
if(!dosemethingWithPAPBPC(pa,pb,pc)){
删除pa;
删除pb;
删除电脑;
返回FALSE;
}

删除pa;
删除pb;
删除电脑;
返回TRUE;