while(true)和循环中断-反模式?


32

考虑以下代码:

public void doSomething(int input)
{
   while(true)
   {
      TransformInSomeWay(input);

      if(ProcessingComplete(input))
         break;

      DoSomethingElseTo(input);
   }
}

假设此过程涉及有限但与输入有关的步骤数;循环的目的是由于算法而自行终止,而不是设计为无限期地运行(直到被外部事件取消为止)。因为检查循环是否应该结束的测试是在逻辑步骤的中间,所以while循环本身目前不检查任何有意义的事情;而是在概念算法的“适当”位置执行检查。

有人告诉我这是错误的代码,因为由于循环结构未检查结束条件,所以它更容易出错。弄清楚如何退出循环会更加困难,并且由于将来的更改可能会意外地忽略或破坏中断条件,因此可能会引发错误。

现在,代码的结构如下:

public void doSomething(int input)
{
   TransformInSomeWay(input);

   while(!ProcessingComplete(input))
   {
      DoSomethingElseTo(input);
      TransformInSomeWay(input);
   }
}

但是,这会重复调用代码中的方法,这违反了DRY。如果TransformInSomeWay后来被其他方法替换,则必须找到并更改这两个调用(在更复杂的代码中可能不那么明显地存在两个调用)。

您也可以这样写:

public void doSomething(int input)
{
   var complete = false;
   while(!complete)
   {
      TransformInSomeWay(input);

      complete = ProcessingComplete(input);

      if(!complete) 
      {
         DoSomethingElseTo(input);          
      }
   }
}

...但是您现在有了一个变量,其唯一目的是将条件检查移至循环结构,并且还必须多次检查以提供与原始逻辑相同的行为。

就我而言,我说的是,鉴于该代码在现实世界中实现的算法,原始代码最易读。如果您自己经历它,这就是您要考虑的方式,那么对熟悉算法的人来说将是直观的。

那么,哪个更好?通过围绕循环构造逻辑来将条件检查的责任赋予while循环是否更好?还是以需求或算法的概念描述所指示的“自然”方式构造逻辑更好,即使这可能意味着绕过循环的内置功能?


12
可能,但是尽管有代码示例,但提出的问题是IMO更具概念性,这与SE的这一领域更加一致。这里经常讨论什么是模式或反模式,这才是真正的问题。构建循环无限运行然后打破循环是一件坏事吗?
KeithS 2012年

3
do ... until构造对于此类循环也很有用,因为它们不必“启动”。
Blrfl 2012年

2
半循环的情况仍然没有任何真正好的答案。
罗伦·佩希特尔

1
“但是,这重复了对代码中方法的调用,违反了DRY。”我不同意该断言,这似乎是对该原理的误解。
安迪

1
除了我更喜欢(;;)的C风格(其明确表示“我有一个循环,而且我不检查循环语句”)之外,您的第一种方法绝对正确。你有一个循环。在决定是否退出之前,需要做一些工作。如果满足某些条件,则退出。然后执行实际工作,然后重新开始。那是绝对好的,易于理解,合乎逻辑,非冗余且易于正确设置。第二个变种很糟糕,而第三个变种却毫无意义。如果进入if的循环的其余部分很长,则变为可怕。
gnasher729

Answers:


14

坚持第一个解决方案。与精心设计的if语句和添加的变量相比,循环可能变得非常复杂,一个或多个干净的中断对您,其他任何查看您的代码,优化器和程序性能的人来说都容易得多。我通常的方法是使用“ while(true)”之类的东西建立一个循环,并获得最好的编码。通常,我可以并且以后会用标准的循环控件替换中断。毕竟,大多数循环都非常简单。出于您提到的原因,通过循环结构检查结束条件,但以添加全新变量,添加if语句和重复代码为代价,所有这些都容易出错。


“ if语句和重复代码,所有这些都更容易出错。” -尝试是的,当人们叫我如果的“未来的错误”
帕特

13

如果循环体不平凡,那只是一个有意义的问题。如果身体这么小,那么像这样分析它是微不足道的,根本不是问题。


7

我很难找到其他解决方案,而不是休息的解决方案。好吧,我更喜欢使用Ada方式

loop
  TransformInSomeWay(input);
exit when ProcessingComplete(input);
  DoSomethingElseTo(input);
end loop;

但中断解决方案是最干净的渲染。

我的观点是,当缺少控制结构时,最干净的解决方案是使用其他控制结构来模拟它,而不使用数据流。(同样,当我遇到数据流问题时,更喜欢纯数据流解决方案而不是涉及控制结构的解决方案*)。

弄清楚如何退出循环更加困难,

别误会,如果难度不大相同,就不会进行讨论:条件相同且存在于同一地点。

哪个

while (true) {
   XXXX
if (YYY) break;
   ZZZZ;
}

while (TTTT) {
    XXXX
    if (YYYY) {
        ZZZZ
    }
}

更好地呈现预期的结构?我支持第一项投票,您无需检查条件是否匹配。(但是请看我的缩进)。


1
哦,看,这是一种直接支持退出中循环的语言!:)
mjfgates 2012年

我喜欢您关于用控件结构模拟缺少的控件结构的观点。对于与GOTO相关的问题,这也可能是一个很好的答案。只要满足语义要求,就应该使用一种语言的控制结构,但是如果一种算法需要其他内容,则应该使用该算法所需的控制结构。
超级猫

3

while(true)和break是一个常见的成语。这是在类似C的语言中实现中间退出循环的规范方法。在循环的后半部分添加一个变量来存储退出条件和一个if()是一个合理的选择。有些商店可能会正式采用另一种形式,但没有一家是上等的。它们是等效的。

一个优秀的使用这些语言工作的程序员,应该能够一眼就知道发生了什么。这可能是一个很好的面试问题。递给某人两个循环,每个循环使用一个习惯用语,然后问他们有什么区别(正确答案:“无”,也许加上“但我习惯使用那个”)。


2

您的替代方案并不像您想象的那样糟糕。好的代码应该:

  1. 在什么条件下应终止循环,并以良好的变量名/注释清楚地指出。(不应该隐藏破坏条件)

  2. 清楚指出执行操作的顺序。在此处将可选的第3个操作(DoSomethingElseTo(input))放在if里面是个好主意。

也就是说,让完美主义者进行黄铜抛光的本能很容易消耗大量时间。我只是调整上述if;注释代码并继续。


我认为,从长远来看,完美主义者的黄铜抛光本能可以节省时间。希望通过练习,养成这样的习惯,使您的代码完美无瑕,并且无需花费大量时间就可以对其进行完善。但是与物理结构不同,软件可以永久存在并可以在无数个地方使用。代码节省的速度甚至更快,更可靠,可用和可维护,可节省0.00000001%。(当然,这些天来我
精打细算的

好吧,我不能说你错了:-)这一个观点问题,如果铜管抛光过程中产生了好的技能,那就太好了。
2012年

无论如何,我认为这是一个可以考虑的可能性。我当然不想提供任何保证。认为我的黄铜抛光可以提高我的技能确实使我感觉更好,因此我可能对此有偏见。
RalphChapin 2012年

2

人们说使用不好while (true),而且很多时候他们都是对的。如果您的while语句包含许多复杂的代码,并且不清楚何时中断,则您将难以维护代码,而简单的错误可能会导致无限循环。

在您的示例中,您使用正确,while(true)因为您的代码清晰易懂。仅由于某些人认为使用它总是不好的做法,所以创建低效且难以阅读的代码是没有意义的while(true)

我遇到了类似的问题:

$i = 0
$key = $str.'0';
$key1 = $str1.'0';
while (isset($array][$key] || isset($array][$key1])
{
    if (isset($array][$key]))
    {
        // Code
    }
    else
    {
        // Code
    }
    $key = $str.++$i;
    $key1 = $str1.$i;
}

使用do ... while(true)避免isset($array][$key]不必要地被调用两次,从而使代码更短,更简洁并且更易于阅读。绝对没有引入无限循环错误的风险else break;

$i = -1;
do
{
    $key = $str.++$i;
    $key1 = $str1.$i;
    if (isset($array[$key]))
    {
        // Code
    }
    elseif (isset($array[$key1]))
    {
        // Code
    }
    else
        break;
} while (true);

遵循良好的做法并避免不良做法是很好的,但是总会有例外,保持开放的态度并意识到这些例外很重要。


0

如果特定的控制流自然地表示为break语句,则使用其他流控制机制来模拟break语句从根本上不是更好的选择。

(请注意,这包括部分展开循环并向下滑动循环主体,以使循环开始与条件测试一致)

如果您可以重新组织循环,以便通过在循环开始时进行条件检查自然地表达控制流,那就太好了。甚至值得花一些时间考虑是否可行。但这不是,不要尝试将方形钉插入圆孔中。


0

您也可以这样做:

public void doSomething(int input)
{
   while(TransformInSomeWay(input), !ProcessingComplete(input))
   {
      DoSomethingElseTo(input);
   }
}

或更少混淆:

bool TransformAndCheckCompletion(int &input)
{
   TransformInSomeWay(input);
   return ProcessingComplete(input))
}

public void doSomething(int input)
{
   while(TransformAndCheckCompletion(input))
   {
      DoSomethingElseTo(input);
   }
}

1
尽管有些人喜欢它,但我断言循环条件通常应保留给条件测试,而不是用于填充的语句。

5
循环条件中的逗号表达式...太糟糕了。你太聪明了 它仅在TransformInSomeWay可以表示为表达式的这种平凡情况下起作用。
gnasher729

0

当逻辑的复杂性变得笨拙时,使用带有中间循环中断的无界循环进行流量控制实际上是最有意义的。我有一个带有一些类似于以下代码的项目。(请注意循环末尾的异常。)

L: for (;;) {
    if (someBool) {
        someOtherBool = doSomeWork();
        if (someOtherBool) {
            doFinalWork();
            break L;
        }
        someOtherBool2 = doSomeWork2();
        if (someOtherBool2) {
            doFinalWork2();
            break L;
        }
        someOtherBool3 = doSomeWork3();
        if (someOtherBool3) {
            doFinalWork3();
            break L;
        }
    }
    bitOfData = getTheData();
    throw new SomeException("Unable to do something for some reason.", bitOfData);
}
progressOntoOtherThings();

为了做到这一点而又不破坏循环,我将得到如下结果:

void method() {
    if (!someBool) {
        throwingMethod();
    }
    someOtherBool = doSomeWork();
    if (someOtherBool) {
        doFinalWork();
    } else {
        someOtherBool2 = doSomeWork2();
        if (someOtherBool2) {
            doFinalWork2();
        } else {
            someOtherBool3 = doSomeWork3();
            if (someOtherBool3) {
                doFinalWork3();
            } else {
                throwingMethod();
            }
        }
    }
    progressOntoOtherThings();
}
void throwingMethod() throws SomeException {
        bitOfData = getTheData();
        throw new SomeException("Unable to do something for some reason.", bitOfData);
}

因此,现在在辅助方法中引发了异常。而且它有很多if ... else嵌套。我对这种方法不满意。因此,我认为第一种模式是最好的。


确实,我在SO上问了这个问题,并被大火击落... stackoverflow.com/questions/47253486/…–
intrepidis
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.