当代码流是这样的:
if(check())
{
...
...
if(check())
{
...
...
if(check())
{
...
...
}
}
}
我通常看到这种方法可以避免上述混乱的代码流:
do {
if(!check()) break;
...
...
if(!check()) break;
...
...
if(!check()) break;
...
...
} while(0);
有什么更好的方法可以避免这种工作/黑客,从而使其成为更高级别(行业级别)的代码?
任何跳出盒子的建议都是欢迎的!
当然不是答案,而是一个答案(为了完整起见)
而不是:
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标签。到目前为止,我还没有看到允许这样做的意义,但在这种特殊情况下,将开关隐藏在一个漂亮的宏后面是很方便的。
我不是特别喜欢在这种情况下使用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为真的原因。
我对这里提出的不同答案的数量感到惊讶。但是,最后在我必须更改的代码中(即删除这个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;
}
...
...
所以,这里所做的是,整理的东西被隔离在一个函数中,事情突然变得如此简单和干净!
我认为这个解决方案值得一提,所以在这里提供了它。
Try to extract the code into a separate function (or perhaps more than one). Then return from the function if the check fails.
If it's too tightly coupled with the surrounding code to do that, and you can't find a way to reduce the coupling, look at the code after this block. Presumably, it cleans up some resources used by the function. Try to manage these resources using an RAII object; then replace each dodgy break with return (or throw, if that's more appropriate) and let the object's destructor clean up for you.
If the program flow is (necessarily) so squiggly that you really need a goto, then use that rather than giving it a weird disguise.
If you have coding rules that blindly forbid goto, and you really can't simplify the program flow, then you'll probably have to disguise it with your do hack.