“ while(true)”循环是如此糟糕吗?[关闭]


218

我已经用Java编程了几年了,但是最近我刚回到学校获得了正式学位。得知我在上一次作业中因使用如下所示的循环而丢了积分,我感到非常惊讶。

do{
     //get some input.
     //if the input meets my conditions, break;
     //Otherwise ask again.
} while(true)

现在,对于我的测试,我只是在扫描一些控制台输入,但是我被告知不鼓励这种循环,因为使用break类似于goto,我们只是不这样做。

我完全理解gotoJava及其表亲的陷阱break:label,并且我有很好的理由不使用它们。我还意识到,一个更完整的程序可以提供其他一些逃生手段,例如只是结束程序,但这不是我的教授所引用的原因,所以...

这有什么错do-while(true)


23
问你的老师,这种东西很主观。
克里斯·埃伯勒

18
我发现本文有助于理解goto的危害。与之break比较可能是很好的意思,但实际上被误解了。也许您可以在这方面教育您的教授;)以我的经验,教授们对编程技巧并不了解很多。
Magnus Hoff

100
在我看来,唯一真正的,无可争辩的坏事是在我的脑海中,事实上,后者do {} while (true)等效于while(true) {}后者,这是更传统的形式,而且更加清楚。
Voo

11
如果任何人都不喜欢的简单表达能力break,那么他们应该尝试使用没有它的语言进行编程。不需要太多循环就可以了!
史蒂文

16
我不同意作业标签。
aaaa bbbb

Answers:


220

我不会说这很糟糕 -但同样地,我通常至少会寻找替代方案。

在我写第一件事的情况下,我几乎总是至少尝试将其重构为更清晰的内容。有时它是bool无济于事的(或者替代方法是拥有一个变量,该变量除了指示循环的结束之外,没有其他意义,而不是一条break语句),但这至少值得一试。

作为使用break标志比使用标志更清晰的示例,请考虑:

while (true)
{
    doStuffNeededAtStartOfLoop();
    int input = getSomeInput();
    if (testCondition(input))
    {
        break;
    }
    actOnInput(input);
}

现在,让我们强制它使用标志:

boolean running = true;
while (running)
{
    doStuffNeededAtStartOfLoop();
    int input = getSomeInput();
    if (testCondition(input))
    {
        running = false;
    }
    else
    {
        actOnInput(input);
    }
}

我认为后者阅读起来更复杂:它有一个额外的else块,actOnInput它更加缩进,并且,如果您想弄清楚testCondition返回时发生的情况true,则需要仔细查看块的其余部分以检查是否存在无论是否设置为else块之后都不会出现任何东西。runningfalse

break语句可以更清楚地传达意图,并让块的其余部分继续进行所需的操作,而不必担心较早的条件。

请注意,这与人们在一个方法中拥有多个return语句的说法完全一样。例如,如果我可以在前几行中得出方法的结果(例如,因为某些输入为空,为空或为零),那么发现直接返回该答案要比使用变量存储结果更清楚,然后是一整段其他代码,最后是一条return语句。


3
我同意这不是我所追求的第一个工具,但是它似乎可以这么干净地解决问题,并且我喜欢干净的代码。
JHarnach 2011年

3
@ X-Zero:是的,有时候。如果您可以将“休息”转变为通常的“回报”,尽管您可能最终仍然会得到while (true)
Jon Skeet

21
@trutheality:我希望我的大多数代码尽可能不缩进。对于我来说,具有复合任务的条件会更复杂。
乔恩·斯基特

3
@Vince:是的,如果您可以在循环开始时轻松表达条件,那就太好。但是有时候你做不到,那就是我们正在谈论的情况。请注意我的回答的前几句话。
乔恩·斯基特

6
@Ismail:我希望不会。帖子应仅根据其内容而不是作者进行判断。哎呀,如果我觉得Eric Lippert的帖子不准确/无用,我什至会不满意。不过,这还没有发生:)
乔恩·斯基特

100

AFAIK没什么,真的。老师只是对过敏goto,因为他们听到某处确实很糟糕。否则,您将只写:

bool guard = true;
do
{
   getInput();
   if (something)
     guard = false;
} while (guard)

几乎是同一回事。

也许这更干净(因为所有循环信息都包含在块的顶部):

for (bool endLoop = false; !endLoop;)
{

}

4
我喜欢您的建议,因为终止条件更加明显。这意味着在阅读代码时,您将更快地了解其尝试执行的操作。我永远不会使用无限循环,而会经常使用您的两个版本。
雪橇

4
大家一致认为,旗帜总比表示坏要好,因为它表达了意图吗?我可以看到这是有益的。所有可靠的答案,但我将其标记为已接受。
JHarnach 2011年

20
这两种方法都仍然遭受循环的其余部分(至少在某些情况下)被污染的困扰,即使您知道自己正在中断,也必须继续前进到循环主体的末端。我在回答中举一个例子……
乔恩·斯基特

8
我更喜欢乔恩·斯基特的回答。此外,虽然{true){}比{} while(true)更好
aaaa bbbb

1
我讨厌Dijkstra曾经写过GoTo文章。虽然GoTo当然可以在过去经常被滥用,但这并不意味着您永远不要使用它。此外,“退出”,“中断”,“退出时”,“尝试/捕获”都是Goto的专门形式。Gotos通常可以使代码更具可读性。是的,我知道没有Goto可以做任何事情。这并不意味着应该。
基比

39

Douglas Crockford对他希望JavaScript如何包含loop结构发表了评论:

loop
{
  ...code...
}

而且我也不认为Java具有loop结构会更糟。

没有什么内在的错误的while(true)循环,但有教师要阻止他们的倾向。从教学的角度来看,让学生创建无休止的循环并且不理解为什么循环从未消失是很容易的。

但是他们很少提到的是 所有循环机制都可以通过while(true)循环来复制。

while( a() )
{
  fn();
}

是相同的

loop
{
  if ( !a() ) break;
  fn();
}

do
{
  fn();
} while( a() );

是相同的:

loop
{
  fn();
  if ( !a() ) break;
}

for ( a(); b(); c() )
{
  fn();
}

是相同的:

a();
loop
{
  if ( !b() ) break;
  fn();
  c();
}

只要您能够以一种可以使您选择使用的构造起作用的方式来设置循环就无关紧要。如果恰好适合for循环,请使用for循环。

最后一部分:保持循环简单。如果每次迭代都需要执行许多功能,请将其放入函数中。您可以在工作后随时对其进行优化。


2
+1:涉及语句for时,循环还有其他复杂性continue,但它们并不是主要的扩展。
多纳研究员

我通常会在需要序列运行时使用for循环,而在要满足条件时使用while循环。这样可以使代码更清晰易懂。
dascandy 2011年

4
没人再写for (;;) {了吗?(发音为“永远”)。这曾经很受欢迎。
达伍德·伊本·卡里姆

16

早在1967年,埃德加·迪克斯特拉(Edgar Dijkstra)在一家商业杂志上写了一篇文章,介绍了为何应从高级语言中删除goto来提高代码质量。由此产生了一个称为“结构化编程”的整体编程范例,尽管并非所有人都同意goto自动意味着错误的代码。

结构化编程的症结本质在于,代码的结构应确定其流程,而不是在可能的情况下弄乱或中断或继续确定流程。同样,在该范式中也不鼓励具有多个进入或退出点的循环或函数。

显然,这并不是唯一的编程范例,但通常可以轻松地将其应用于其他范例,例如面向对象的编程(ala Java)。

您的老师可能已经被教过,并且正在尝试向您的班级讲课,以确保我们的代码是结构化的,并遵循结构化编程的隐含规则,我们最好避免使用“意大利面条式代码”。

尽管使用break的实现没有内在的“错误”,但有些人认为,在while()条件内显式指定循环条件的代码更容易读取,并且消除了过于棘手的可能性。使用while(true)条件似乎很容易引起新手程序员在代码中突然出现的陷阱,例如偶然创建无限循环或使代码难以阅读或造成不必要的混乱的风险。

具有讽刺意味的是,异常处理是一个领域,随着您进一步学习Java编程,肯定会出现与结构化编程的偏差。

您的讲师也可能希望您证明自己有能力使用该章或本课中所教的特定循环结构或语法,并且尽管您编写的代码在功能上等效,但您可能并未在其中展示您应该在该课程中学习的特定技能。


2
这是Edsger Dijkstra的论文。这是一本好书。
罗伊·普林斯

14

读取输入的常用Java约定是:

import java.io.*;
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String strLine;

while ((strLine = br.readLine()) != null) {
  // do something with the line
}

读取输入的常用C ++约定是:

#include <iostream>
#include <string>
std::string data;
while(std::readline(std::cin, data)) {
  // do something with the line
}

在C语言中

#include <stdio.h>
char* buffer = NULL;
size_t buffer_size;
size_t size_read;
while( (size_read = getline(&buffer, &buffer_size, stdin)) != -1 ){
  // do something with the line
}
free(buffer);

或者,如果您确信自己知道文件中最长的文本行有多长时间,则可以

#include <stdio.h>
char buffer[BUF_SIZE];
while (fgets(buffer, BUF_SIZE, stdin)) {
  //do something with the line
}

如果要测试用户是否输入了quit命令,可以很容易地扩展这3个循环结构中的任何一个。我会用Java为您完成:

import java.io.*;
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line;

while ((line = br.readLine()) != null  && !line.equals("quit") ) {
  // do something with the line
}

所以,虽然有一定身在何处的情况下break或者goto是有道理的,如果你正在做的是从一个文件或行控制台线读取,那么你不应该需要一个while (true)循环来实现它-你的编程语言已经提供你使用适当的习惯用法将输入命令用作循环条件。


实际上,如果您使用while (true)循环而不是这些常规输入循环之一,则可能会忘记检查文件的末尾。
肯·布鲁姆

3
您可以通过将循环的第一部分填充到while条件语句中来有效地完成此操作。在Java的最终条件下,它已经变得相当繁重,如果您必须进行大量操作来决定是否继续,则可能会花费很长时间。您可以将其拆分为一个单独的函数,这可能是最好的。但是,如果您确实希望在一个功能中使用它,并且在决定是否继续之前要做一些琐碎的工作,那while(true)可能是最好的。
poolie

@poolie:最好将read命令用作循环条件(检查EOF),并在循环中检查其他条件作为break语句。
肯·布鲁姆

1
就其价值而言,gets()这不是常见的约定。这非常有助于缓冲溢出。fgets(buffer, BUFFER_SIZE, file)更像是标准做法。
戴夫

@Dave:我现在已经编辑了要使用的答案fgets
肯·布鲁姆

12

这并不是一件可怕的事情,但是在编码时您需要考虑其他开发人员。即使在学校。

您的开发人员应该能够在循环声明中看到循环的exit子句。你没那么做 您在循环的中间隐藏了exit子句,从而为其他尝试理解您的代码的人做更多的工作。这与避免“中断”之类的原因相同。

话虽如此,您仍然会在现实世界中的许多代码中看到类似的事情。


4
如果循环从此开始,while (true)那么很明显里面将会有一个breakreturn,或者它将永远运行。坦率地说很重要,但while(true)并不是特别糟糕。在循环迭代中具有复杂不变性的变量将是引起更多焦虑的一个例子。
poolie

4
尝试在搜索中深入三个级别,然后以某种方式突破。如果您不只是从那时返回,代码将变得更糟。
dascandy 2011年

11

是你的枪,子弹和脚...

不好,是因为您要自找麻烦。不会是您或本页上的其他任何张贴者都拥有简短/简单的while循环示例。

麻烦将在将来的某个非常随机的时间开始。这可能是由另一个程序员引起的。可能是安装软件的人。可能是最终用户。

为什么?我必须弄清楚为什么700K LOC应用程序会逐渐开始消耗100%的CPU时间,直到每个CPU饱和。这是一个了不起的while(true)循环。它又大又讨厌,但归结为:

x = read_value_from_database()
while (true) 
 if (x == 1)
  ...
  break;
 else if (x ==2)
  ...
  break;
and lots more else if conditions
}

没有最后的else分支。如果该值与if条件不匹配,则循环将一直运行直到时间结束。

当然,程序员指责最终用户没有选择程序员期望的价值。(然后,我在代码中消除了while(true)的所有实例。)

恕我直言,使用while(true)之类的结构并不是好的防御性编程。它会回来困扰你。

(但我确实记得,如果我们不对每一行都发表评论,甚至对于i ++,教授们的评价就会降低;)


3
+1有关防御性编程的评论。
JHarnach

3
在您的示例中,代码之所以愚蠢,并不是因为while(true)的选择,而是因为在代码周围放了一个循环。
Florian F

确实,该循环的目的是什么?:)
juzzlin 2014年

6

从某种意义上说,结构化编程构造优于(有些非结构化)break和continue语句是不好的。相比之下,根据此原理,它们比“转到”优先。

我总是建议您使代码尽可能地结构化……尽管,正如乔恩·斯凯特(Jon Skeet)所指出的那样,请不要使代码结构化!


5

根据我的经验,在大多数情况下,循环具有继续运行的“主要”条件。这是应该写入while()运算符本身的条件。所有其他可能破坏循环的条件都是次要的,不是那么重要,等等。可以将它们写为其他if() {break}语句。

while(true) 常常令人困惑并且可读性较差。

我认为这些规则并未涵盖100%的案件,但可能只涵盖其中的98%。


说得好。这就像在for循环中使用while(true){i ++; ...}。您将循环条件埋在循环中而不是“签名”中。
LegendLength

3

虽然不一定是为什么不使用的答案while (true),但我总是能找到此漫画和作者的陈述是关于为什么做而不是做的简要说明。

关于您的问题:没有固有的问题

while(true) {
   do_stuff();
   if(exit_time) {
      break;
   }
}

... 如果您知道自己在做什么,请确保exit_time在某个时候评估结果为true

老师会劝阻您不要使用它,while(true)因为直到您真正知道自己在做什么之前,除非您确实知道,否则这是犯下严重错误的简便方法。


我认为设置exit_time = false; while(!exit_time) { execute_stuff(); }do { execute_stuff(); } while(! exit_time );都比if( condition ) { break; }在循环末尾带有时要清晰得多while(true)。中断是对循环的短路-用作循环中间的短路时很好,但是您应该只在while语句中评估条件,而不要在循环结束时中断。
jimbob博士2011年

关于那本漫画:虽然“做某事”不能做所有“可以做的事”,但“做某事”有时更好地做“做某事”可以做的事,即使“做某事也可以做”。
Theraot 2014年

3

您可能只使用布尔标志来指示何时结束while循环。Breakgo to是软件难以维护的原因-软件危机(tm)-应该避免,并且很容易做到。

你是否务实是一个问题。在这种简单的情况下,务实的编码人员可能只会使用break。

但是最好不要使用它们,否则可能会在不合适的情况下使用它们,例如在复杂的嵌套循环中,使用会使代码的可读性和可维护性变得更难break


8
因为根据我的经验,保持布尔值标记可能更不清楚,而且可能不太清楚?
乔恩·斯基特

3
@Jon Skeet好吧,这是一个养成良好习惯或通过休息训练自己成为不良习惯的问题。一个叫做“ running”的布尔值对您不清楚吗?它显而易见,易于调试,而且正如我之前提到的那样,保持良好的习惯是一件好事。问题出在哪里?
Daniel Leschkowski 2011年

4
一个叫做running的布尔值然后要求我if (running)在循环内进行操作,当我只想退出循环时,缩进所有其余代码对我来说绝对不像简单的break语句(该语句明确说明了我想做什么)那样清晰。你似乎用break作为一个坏习惯,可以考虑公理 -我不认为它是这样的。
乔恩·斯基特

2
为什么在循环中会有一个if(运行)?与在循环结束时使用break一样,您检查是否要中断并翻转标志而不是使用break并在while(running)而不是while(true)中使用该标志。我不明白你的意思。我同意一个务实的编码人员在某些情况下可能会使用break,但是我真的没有收到您根据我的建议所作的评论
Daniel Leschkowski 2011年

3
您假设在确定是否退出后无需在循环内执行任何操作。例如,在OP的情况下,如果他不退出,则需要请求更多输入。
乔恩·斯基特

3

也许我很倒霉。也许我只是缺乏经验。但每次我记得处理时间while(true)break内,有可能提高应用代码提取方法,而块,里面存放的while(true)转化所有的,但(巧合?)breaks转换return秒。

以我的经验,while(true)不间断(即有回报或罚球)是相当舒适且易于理解的。


  void handleInput() {
      while (true) {
          final Input input = getSomeInput();
          if (input == null) {
              throw new BadInputException("can't handle null input");
          }
          if (input.isPoisonPill()) {
              return;
          }
          doSomething(input);
      }
  }

3

我认为是的,这非常糟糕……至少对于许多开发人员而言。这是开发人员没有考虑其循环条件的症状。因此,容易出错。


2

有没有大的问题,while(true)break声明,但是有些人可能认为它稍微降低了代码的可读性。尝试给变量赋予有意义的名称,并在适当的位置评估表达式。

对于您的示例,执行以下操作似乎更加清晰:

do {
   input = get_input();
   valid = check_input_validity(input);    
} while(! valid)

如果do while循环很长,则尤其如此-您确切地知道检查是否在进行额外的迭代。在抽象级别,所有变量/函数均具有适当的名称。的while(true)声明确实告诉您处理不在您认为的地方。

也许您希望第二次通过循环获得不同的输出。就像是

input = get_input();
while(input_is_not_valid(input)) {
    disp_msg_invalid_input();
    input = get_input();
}

那时对我来说更具可读性

do {
    input = get_input();
    if (input_is_valid(input)) {
        break;
    }
    disp_msg_invalid_input();
} while(true);

再次,用一个简单的例子,两者都很容易读懂。但是如果循环变得非常大或嵌套得很深(这意味着您可能应该已经重构),则第一种样式可能会更清晰一些。


1

在很多功能中,我使用相似但逻辑相反的东西。

DWORD dwError = ERROR_SUCCESS;

do
{
    if ( (dwError = SomeFunction()) != ERROR_SUCCESS )
    {
         /* handle error */
         continue;
    }

    if ( (dwError = SomeOtherFunction()) != ERROR_SUCCESS )
    {
         /* handle error */
         continue;
    }
}
while ( 0 );

if ( dwError != ERROR_SUCCESS )
{
    /* resource cleanup */
}

1

这更像是一种美学,更容易阅读代码,您可以在其中明确知道循环为什么会在循环的声明中立即停止。


1

我要说的是,通常认为它不是一个好主意的原因是您没有充分利用此结构的潜力。而且,我倾向于认为,当他们的学生带着“行李”进来时,许多编程指导员都不喜欢它。我的意思是说,我认为他们喜欢成为学生编程风格的主要影响力。因此,这也许只是讲师的生气。


1

对我来说,问题是可读性。

条件条件为while的语句不会告诉您有关循环的任何信息。这使得理解它的工作变得更加困难。

从这两个片段中,什么更容易理解?

do {
  // Imagine a nice chunk of code here
} while(true);

do {
  // Imagine a nice chunk of code here
} while(price < priceAllowedForDiscount);

0

我想对你的老师使用断裂就像打破一棵树的树枝来获取果实,使用其他技巧(使树枝成弓形),以便获得果实并且该分支仍然存在。:)


0

1)一切正常 do -while(true)

2)你的老师错了。

NSFS !!:

3)大多数老师是老师,而不是程序员。


@Connell等到您的老师听到这样的消息!
Pacerier,2011年

1
我在编程方面自学成才。我只需要判断是我的中学IT老师,他就教我们如何使用Dreamweaver制作网站,以使EVERYTHING成为绝对定位的部门...
Connell

@Connell上我的帖子以表明您的同意
Pacerier,2011年

2
“那些做不到的人,教书。” -这是一个很大的概括,您认为吗?医生,工程师,飞行员等是否应该自学成才,因为他们的老师根据您的能力不佳?
filip-fku 2011年

@ filip-fku哇哇〜好酷呀!
Pacerier,2011年

0

如果循环在后台线程上运行可能会很糟糕,因此当您通过终止UI线程关闭应用程序时,该代码段将继续执行。正如其他人已经说过的那样,您应该始终使用某种检查来提供取消的方法。

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.