当代码流是这样的:
if(check())
{
...
...
if(check())
{
...
...
if(check())
{
...
...
}
}
}
Run Code Online (Sandbox Code Playgroud)
我一般都看到这个工作,以避免上面的凌乱的代码流:
do {
if(!check()) break;
...
...
if(!check()) break;
...
...
if(!check()) break;
...
...
} while(0);
Run Code Online (Sandbox Code Playgroud)
有哪些更好的方法可以避免这种解决方案/黑客攻击,从而使其成为更高级别(行业级别)的代码?
任何开箱即用的建议都是受欢迎的!
Mik*_*ail 308
在函数中隔离这些决策并使用returns代替breaks,这被认为是可接受的做法.虽然所有这些检查都对应于与函数相同的抽象级别,但这是非常合乎逻辑的方法.
例如:
void foo(...)
{
if (!condition)
{
return;
}
...
if (!other condition)
{
return;
}
...
if (!another condition)
{
return;
}
...
if (!yet another condition)
{
return;
}
...
// Some unconditional stuff
}
Run Code Online (Sandbox Code Playgroud)
Mat*_*son 254
有时候使用goto实际上是正确的答案 - 至少对于那些没有在宗教信仰中长大的人来说," goto无论问题是什么都不能成为答案" - 这就是其中一个案例.
此代码使用黑客攻击do { ... } while(0);的唯一目的是打扮goto一个break.如果你打算使用goto,那么就开放吧.让代码HARDER阅读是没有意义的.
特殊情况就是当你有很多具有相当复杂条件的代码时:
void func()
{
setup of lots of stuff
...
if (condition)
{
...
...
if (!other condition)
{
...
if (another condition)
{
...
if (yet another condition)
{
...
if (...)
...
}
}
}
....
}
finish up.
}
Run Code Online (Sandbox Code Playgroud)
它实际上可以通过没有这么复杂的逻辑使代码正确的清除.
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.
}
Run Code Online (Sandbox Code Playgroud)
编辑:为了澄清,我决不建议使用goto作为一般解决方案.但有些情况下,goto解决方案比其他解决方案更好.
想象一下,例如我们正在收集一些数据,并且正在测试的不同条件是某种"这是收集的数据的结束" - 这取决于某种"继续/结束"标记,这取决于哪里你在数据流中.
现在,当我们完成后,我们需要将数据保存到文件中.
是的,通常有其他解决方案可以提供合理的解决方案,但并非总是如此.
das*_*ght 82
您可以使用带bool变量的简单延续模式:
bool goOn;
if ((goOn = check0())) {
...
}
if (goOn && (goOn = check1())) {
...
}
if (goOn && (goOn = check2())) {
...
}
if (goOn && (goOn = check3())) {
...
}
Run Code Online (Sandbox Code Playgroud)
一旦checkN返回a,该执行链将立即停止false.check...()由于&&操作员的短路,不会再执行任何呼叫.此外,优化编译器足够聪明,可以识别设置goOn为false单行道,并goto end为您插入缺失.结果,上面的代码的性能将与do/的相同while(0),只是没有对其可读性的痛苦打击.
Mik*_*our 38
尝试将代码提取到单独的函数中(或者可能不止一个).如果检查失败,则从函数返回.
如果它与周围的代码紧密耦合来做到这一点,并且你找不到减少耦合的方法,请查看此块之后的代码.据推测,它清理了函数使用的一些资源.尝试使用RAII对象管理这些资源; 然后break用return(或者throw,如果那更合适的话)替换每个狡猾的东西,让对象的析构函数为你清理.
如果程序流程(必然)如此波动以至于你真的需要一个goto,那么就使用它而不是给它一个奇怪的伪装.
如果你有盲目禁止的编码规则goto,而你实际上无法简化程序流程,那么你可能不得不用你的do黑客来伪装它.
utn*_*tim 36
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;
}
Run Code Online (Sandbox Code Playgroud)
在C中,在大多数代码库中,if(error_ok != ...和goto代码通常隐藏在一些很方便的宏(RET(computation_result),ENSURE_SUCCESS(computation_result, return_code)等).
C++提供了超过C的额外工具:
清理块功能可以实现为RAII,这意味着您不再需要整个cleanup块并允许客户端代码添加早期返回语句.
无论何时你无法继续,你都会抛出,将所有if(error_ok != ...变成直接的呼叫.
等价的C++代码:
result_type cpp_code()
{
raii_resource1 r1 = computation1();
raii_resource2 r2 = computation2();
// ...
return computed_value_n;
}
Run Code Online (Sandbox Code Playgroud)
这是最佳做法,因为:
它是显式的(即,当错误处理不明确时,算法的主要流程是)
编写客户端代码非常简单
它很小
很简单
它没有重复的代码构造
它不使用宏
它不使用奇怪的do { ... } while(0)结构
它可以通过最小的努力重复使用(也就是说,如果我想将调用复制computation2();到另一个函数,我不必确保do { ... } while(0)在新代码中添加一个,也不必添加#definegoto包装器宏和清理标签,也不还要别的吗).
Pet*_*r R 21
为了完整起见,我正在添加一个答案.许多其他答案指出,大条件块可以分成单独的函数.但正如同时也指出的那样,这种方法将条件代码与原始上下文分开.这是在C++ 11中将lambdas添加到语言中的一个原因.其他人建议使用lambdas,但没有提供明确的样本.我在这个答案中加了一个.让我感到震惊的是,它do { } while(0)在许多方面与这种方法非常相似- 也许这意味着它仍然是goto伪装的....
earlier operations
...
[&]()->void {
if (!check()) return;
...
...
if (!check()) return;
...
...
if (!check()) return;
...
...
}();
later operations
Run Code Online (Sandbox Code Playgroud)
kri*_*iss 18
当然不是的答案,但答案(为完整起见)
代替 :
do {
if(!check()) break;
...
...
if(!check()) break;
...
...
if(!check()) break;
...
...
} while(0);
Run Code Online (Sandbox Code Playgroud)
你可以写:
switch (0) {
case 0:
if(!check()) break;
...
...
if(!check()) break;
...
...
if(!check()) break;
...
...
}
Run Code Online (Sandbox Code Playgroud)
这仍然是一个跳转进行了伪装,但至少它不是一个循环了.这意味着你不必非常仔细地检查是否有一些继续隐藏在块中的某个地方.
该构造也很简单,您可以希望编译器将其优化.
正如@jamesdlin所建议的那样,你甚至可以将它隐藏在宏之后
#define BLOC switch(0) case 0:
Run Code Online (Sandbox Code Playgroud)
并使用它
BLOC {
if(!check()) break;
...
...
if(!check()) break;
...
...
if(!check()) break;
...
...
}
Run Code Online (Sandbox Code Playgroud)
这是可能的,因为C语言语法需要在切换后的语句,而不是括号内的块,并且您可以在该语句之前放置一个case标签.到目前为止,我没有看到允许这一点,但在这种特殊情况下,将开关隐藏在一个漂亮的宏后面是很方便的.
Dan*_*ard 15
我会推荐一种类似于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;
...
}
Run Code Online (Sandbox Code Playgroud)
ace*_*egs 12
对我do{...}while(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
Run Code Online (Sandbox Code Playgroud)
我认为编译器将删除二进制版本中的不必要while(0)条件do{...}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.
Run Code Online (Sandbox Code Playgroud)
请注意,标签是在结束后放置的}.这是避免一个可能的问题,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)
Run Code Online (Sandbox Code Playgroud)
这样,您可以执行嵌套块并指定要退出/跳出的位置.
//--------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);
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)
Run Code Online (Sandbox Code Playgroud)
意粉代码不是故障goto,这是程序员的错.您仍然可以不使用而生成意大利面条代码goto.
the*_*ill 11
如果您不需要在执行期间引入局部变量,那么您通常可以将其展平:
if (check()) {
doStuff();
}
if (stillOk()) {
doMoreStuff();
}
if (amIStillReallyOk()) {
doEvenMore();
}
// edit
doThingsAtEndAndReportErrorStatus()
Run Code Online (Sandbox Code Playgroud)
Den*_*ore 10
与dasblinkenlight的答案类似,但避免了内部的分配if可能会被代码审查者"修复":
bool goOn = check0();
if (goOn) {
...
goOn = check1();
}
if (goOn) {
...
goOn = check2();
}
if (goOn) {
...
}
Run Code Online (Sandbox Code Playgroud)
...
我需要在下一步之前检查步骤的结果时使用此模式,这与所有检查都可以使用大型if( check1() && check2()...模式预先完成的情况不同.
Car*_*cho 10
使用例外.您的代码看起来会更清晰(并且为了处理程序执行流程中的错误而创建了异常).要清理资源(文件描述符,数据库连接等),请阅读文章为什么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;
}
Run Code Online (Sandbox Code Playgroud)
从功能编程的角度来看,这是一个众所周知且解决得很好的问题 - 也许是monad.
为了回应我在下面收到的评论,我在这里编辑了我的介绍:你可以在各个地方找到有关实现C++ monad的全部细节,这将使你能够实现Rotsor的建议.grok monads需要一段时间,所以相反我会在这里建议一个快速的"穷人"monad-like机制,你只需要知道boost :: optional.
设置计算步骤如下:
boost::optional<EnabledContext> enabled(boost::optional<Context> context);
boost::optional<EnergisedContext> energised(boost::optional<EnabledContext> context);
Run Code Online (Sandbox Code Playgroud)
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.
}
Run Code Online (Sandbox Code Playgroud)
然后把它们连在一起:
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
}
Run Code Online (Sandbox Code Playgroud)
关于这一点的好处是你可以为每个计算步骤编写明确定义的单元测试.调用也像普通英语一样(通常是功能样式的情况).
如果你不关心不变性,每次使用shared_ptr等提出一些变化时返回相同的对象会更方便.
将if语句移动到一个产生数字或枚举结果的额外函数中怎么样?
int ConditionCode (void) {
if (condition1)
return 1;
if (condition2)
return 2;
...
return 0;
}
void MyFunc (void) {
switch (ConditionCode ()) {
case 1:
...
break;
case 2:
...
break;
...
default:
...
break;
}
}
Run Code Online (Sandbox Code Playgroud)
我并不特别喜欢使用break或return在这种情况下.鉴于通常在我们遇到这种情况时,通常是一种相对较长的方法.
如果我们有多个出口点,当我们想要知道什么会导致某些逻辑被执行时,它可能会造成困难:通常我们只是继续上升封闭那条逻辑的块,并且那些封闭块的标准告诉我们情况:
例如,
if (conditionA) {
....
if (conditionB) {
....
if (conditionC) {
myLogic();
}
}
}
Run Code Online (Sandbox Code Playgroud)
通过查看封闭的块,很容易发现myLogic()只有在conditionA and conditionB and conditionC真实时才会发生.
当有早期回报时,它变得不那么明显:
if (conditionA) {
....
if (!conditionB) {
return;
}
if (!conditionD) {
return;
}
if (conditionC) {
myLogic();
}
}
Run Code Online (Sandbox Code Playgroud)
我们再也无法向myLogic()上看,看着封闭的块来弄清楚条件.
我使用了不同的解决方法.这是其中之一:
if (conditionA) {
isA = true;
....
}
if (isA && conditionB) {
isB = true;
...
}
if (isB && conditionC) {
isC = true;
myLogic();
}
Run Code Online (Sandbox Code Playgroud)
(当然欢迎使用相同的变量来替换所有变量isA isB isC.)
这种方法至少会给读者提供代码,这myLogic()是在执行时执行的isB && conditionC.读者会得到一个提示,他需要进一步查找导致isB为真的内容.
或许这样的事情
#define EVER ;;
for(EVER)
{
if(!check()) break;
}
Run Code Online (Sandbox Code Playgroud)
或使用例外
try
{
for(;;)
if(!check()) throw 1;
}
catch()
{
}
Run Code Online (Sandbox Code Playgroud)
使用例外,您也可以传递数据.
| 归档时间: |
|
| 查看次数: |
18085 次 |
| 最近记录: |