“while(true)”循环有那么糟糕吗?[已结束]

Are "while(true)" loops so bad? [closed]

提问人:JHarnach 提问时间:7/28/2011 最后编辑:Peter MortensenJHarnach 更新时间:12/3/2019 访问量:220410

问:

我用 Java 编程已经有好几年了,但我最近才回到学校获得正式学位。我很惊讶地发现,在我的上一次作业中,我因为使用如下循环而失分。

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

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

我完全理解 Java 的缺点,并且我有很好的意识不使用它们。我还意识到,一个更完整的程序会提供一些其他的逃避方式,例如结束程序,但这不是我的教授引用的原因,所以......gotobreak:label

怎么了?do-while(true)

Java while-loop do-while

评论

26赞 Chris Eberle 7/28/2011
问问你的老师,这种东西是很主观的。
21赞 Magnus Hoff 7/28/2011
我发现这篇文章有助于了解 goto 的危害。与的比较可能是善意的,但实际上被误解了。也许你可以用这个;)教育你的教授根据我的经验,教授们对编程的技巧知之甚少。break
107赞 Voo 7/28/2011
在我看来,唯一真正无可争辩的坏事是,与后者等同的事实是迄今为止更传统的形式,也更清晰。do {} while (true)while(true) {}
13赞 Steven 7/28/2011
如果有人不欣赏 的简单表达能力,他们应该尝试用没有它的语言进行编程。在你想要它之前,它不需要太多的循环!break
17赞 aaaa bbbb 7/28/2011
我不同意家庭作业标签。

答:

230赞 Jon Skeet 7/28/2011 #1

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

在这是我写的第一件事的情况下,我几乎总是至少尝试将其重构为更清晰的东西。有时它无济于事(或者另一种选择是有一个变量,除了指示循环的结束之外,它没有任何有意义的作用,不如语句清晰),但至少值得尝试。boolbreak

例如,在哪些地方使用起来比标志更清晰,请考虑: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);
    }
}

我认为后者读起来更复杂:它有一个额外的块,缩进更多,如果你想弄清楚返回时会发生什么,你需要仔细查看块的其余部分,以检查块之后是否有任何东西会发生,无论是否已设置为。elseactOnInputtestConditiontrueelserunningfalse

该语句更清楚地传达了意图,并让块的其余部分继续执行它需要做的事情,而不必担心早期的条件。break

请注意,这与人们对方法中的多个 return 语句的论证完全相同。例如,如果我可以在前几行中计算出方法的结果(例如,因为某些输入为 null、空或零),我发现直接返回该答案比使用一个变量来存储结果、然后是整个其他代码块,最后是一个语句更清晰。return

评论

3赞 JHarnach 7/28/2011
我同意这不是我接触的第一个工具,但它似乎可以干净利落地解决问题,而且我确实喜欢干净的代码。
3赞 Jon Skeet 7/28/2011
@X-Zero:是的,有时。如果你能把“休息”变成“回报”,那往往是善良的......虽然你最终可能仍然会得到一个while (true)
24赞 Jon Skeet 7/28/2011
@trutheality:不过,我更喜欢我的大部分代码尽可能不缩进。对我来说,具有复合分配的条件感觉更复杂。
4赞 Jon Skeet 8/3/2011
@Vince:是的,如果你能在循环开始时轻松表达条件,那就太好了。但有时你不能——这就是我们正在谈论的情况。请注意我回答的前几句话。
7赞 Jon Skeet 8/4/2011
@Ismail:我希望不是。帖子应该只根据其内容来判断,而不是根据作者来判断。哎呀,如果我觉得 Eric Lippert 的帖子不准确/无用,我什至会投反对票。不过,这还没有发生:)
2赞 Daniel Leschkowski 7/28/2011 #2

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

你是否务实是一个问题。务实的程序员可能只是在这种简单的情况下使用 break。

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

评论

9赞 Jon Skeet 7/28/2011
因为根据我的经验,保留布尔标志可能并不清晰,而且可能不太清晰?
3赞 Daniel Leschkowski 7/28/2011
@Jon Skeet 嗯,这是一个问题,是用一个好的哈比特,还是用休息时间训练自己一个坏的哈比特。你不清楚一个叫做“跑步”的布尔值?它很明显,易于调试,正如我之前提到的,一个好的 habbit 是一件好事。问题出在哪里?
5赞 Jon Skeet 7/28/2011
一个叫做 running 的布尔值,然后要求我在循环中有一个,当我只想退出循环时缩进所有其他代码,这对我来说肯定不如一个简单的 break 语句清楚,它准确地说明了我想要做什么。你似乎正在考虑将休息作为一种公理化的坏习惯——我不这么认为。if (running)
2赞 Daniel Leschkowski 7/28/2011
为什么你会在循环中有一个 if(运行)?与循环末尾的 break 一样,您可以检查是否要 break out 并翻转标志而不是使用 break,并在 while(running) 而不是 while(true) 中使用该标志。我不明白你的意思,说真的。我同意务实的编码人员在某些情况下可能会使用 break,但我真的没有得到您在我的建议下发表的评论
4赞 Jon Skeet 7/28/2011
你假设在你决定是否退出后,你不需要在循环中做任何事情。例如,在 OP 的情况下,如果他不退出,他需要要求更多的输入。
1赞 Petey B 7/28/2011 #3

这更像是一个美学问题,更容易阅读代码,因为你明确知道为什么循环会在循环声明中停止。

102赞 El Marcel 7/28/2011 #4

AFAIK什么都没有,真的。老师只是过敏,因为他们在某个地方听到了它真的很糟糕。否则你只会写:goto

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

这几乎是一回事。

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

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

}

评论

4赞 Sled 7/28/2011
我喜欢你的建议,因为终止条件更加明显。这意味着在阅读代码时,您将更快地了解它试图做什么。我永远不会使用无限循环,而是经常使用您的两个版本。
4赞 JHarnach 7/28/2011
人们的共识是,一面旗帜比打破要好,主要是因为它表达了意图?我认为这是有益的。所有可靠的答案,但我会将其标记为已接受。
21赞 Jon Skeet 7/28/2011
这两者仍然受到循环的其余部分被污染(至少在某些情况下)的影响,即使你知道你正在破坏,也必须继续走到循环主体的末端。我会在我的答案中举个例子......
9赞 aaaa bbbb 7/28/2011
我更喜欢 Jon Skeet 的回答。此外,while (true) {} 比 do {} while(true) 要好得多
2赞 Kibbee 7/28/2011
我讨厌 Dijkstra 曾经写过那篇 GoTo 文章。虽然GoTo过去经常被滥用,但这并不意味着你不应该使用它。此外,Exit For、Break、Exit While、Try/Catch 都只是 Goto 的特殊形式。Gotos 通常可以使代码更具可读性。是的,我知道没有Goto可以做任何事情。这并不意味着它应该这样做。
11赞 rfeak 7/28/2011 #5

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

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

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

评论

5赞 poolie 7/28/2011
如果循环开始时,很明显它会有一个 OR,或者它会永远运行。直截了当很重要,但就其本身而言并不是特别糟糕。在循环迭代中具有复杂不变量的变量将是引起更多焦虑的一个例子。while (true)breakreturnwhile(true)
5赞 dascandy 7/28/2011
尝试在搜索中深入三个级别,然后以某种方式爆发。如果你不从那里返回,代码会更糟。
4赞 AlexR 7/28/2011 #6

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

while(true)经常令人困惑,可读性较差。

我认为这些规则并不能涵盖 100% 的情况,但可能只有 98% 的情况。

评论

0赞 LegendLength 1/7/2016
说得好。这就像使用一个 for 循环:while(true) {i++;...}。您将循环条件隐藏在循环中,而不是隐藏在“签名”中。
5赞 Patrick87 7/28/2011 #7

从某种意义上说,结构化编程构造比(有点非结构化的)中断和继续语句更受欢迎,这是不好的。相比之下,根据这一原则,它们比“goto”更受欢迎。

我总是建议让你的代码尽可能结构化......虽然,正如 Jon Skeet 指出的那样,不要让它比这更有条理!

14赞 Ken Bloom 7/28/2011 #8

用于读取输入的常用 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
}

如果您正在测试用户是否输入了命令,则可以轻松扩展这 3 个循环结构中的任何一个。我将用 Java 为您完成:quit

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
}

因此,虽然确实存在 or 是合理的情况,但如果您所做的只是逐行读取文件或控制台,那么您就不需要循环来完成它——您的编程语言已经为您提供了使用输入命令作为循环条件的适当习惯用法。breakgotowhile (true)

评论

0赞 Ken Bloom 7/28/2011
事实上,如果您使用的是循环而不是这些传统的输入循环之一,您可能忘记检查文件的末尾。while (true)
3赞 poolie 7/28/2011
通过将循环的第一部分塞入条件中,可以有效地实现此目的。在最终的 Java 条件下,它已经变得相当沉重,如果你必须进行大量的操作来决定是否继续,它可能会变得相当长。您可以将其拆分为一个单独的函数,这可能是最好的。但是,如果您确实希望它存在于一个函数中,并且在决定是否继续之前还有许多工作要做,那么这可能是最好的。whilewhile(true)
0赞 Ken Bloom 7/28/2011
@poolie:那么最好使用 read 命令作为循环条件(检查 EOF),并将循环中的其他条件作为中断语句进行检查。
1赞 Dave 8/3/2011
就其价值而言,这不是常见的惯例。它非常有利于缓冲区溢出。 更像是标准做法。gets()fgets(buffer, BUFFER_SIZE, file)
0赞 Ken Bloom 8/3/2011
@Dave:我现在已经编辑了要使用的答案。fgets
40赞 zzzzBov 7/28/2011 #9

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();
}

只要你能以一种有效的方式设置你的循环,你选择使用的构造就不重要了。如果它碰巧适合一个循环,请使用一个循环。forfor

最后一部分:保持循环简单。如果每次迭代都需要发生很多功能,请将其放在函数中。你总是可以在它工作后优化它。

评论

2赞 Donal Fellows 7/28/2011
+1:当涉及语句时,循环会更加复杂,但它们不是主要的扩展。forcontinue
0赞 dascandy 7/28/2011
当有我想要运行的序列时,我通常使用 for 循环,当有我想要满足的条件时,我通常使用 while 循环。这使代码更清晰易读。
4赞 Dawood ibn Kareem 12/31/2014
没有人再写了吗?(发音为“永远”)。这曾经很受欢迎。for (;;) {
0赞 Pharap 9/12/2022
@DawoodibnKareem 不多,这是一件好事! 是丑陋的,并且只是因为一些非常糟糕的早期 C 编译器太愚蠢了,无法正确地优化 while(true) 的条件。值得庆幸的是,我们现在生活在一个可读性胜过黑客传播的精英主义深奥主义的时代,尽管我们还没有生活在一个简单优雅的时代,这种简单优雅已经真正被欣赏并铭记于心。for(;;)loop
2赞 dr jimbob 7/28/2011 #10

语句没有大问题,但有些人可能认为它略微降低了代码的可读性。尝试给变量起有意义的名称,在适当的位置计算表达式。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);

同样,举个微不足道的例子,两者都非常可读;但是,如果循环变得非常大或嵌套得很深(这意味着您可能已经重构了),则第一种样式可能会更清晰一些。

3赞 Shadur 7/28/2011 #11

虽然不一定是为什么不使用的答案,但我一直认为这部漫画和随附的作者声明简洁地解释了为什么做而而不是做。while (true)

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

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

...如果你知道你在做什么,并确保在某个时候评估为 .exit_timetrue

老师不鼓励你使用,因为除非你确切地知道自己在做什么,否则这是犯严重错误的简单方法。while(true)

评论

1赞 dr jimbob 7/29/2011
我认为设置和都比在循环末尾有一个更清晰。中断是循环的短路 -- 在循环中间用作短路时完全没问题,但你应该只评估 while 语句中的条件,而不是在循环结束时有中断。exit_time = false; while(!exit_time) { execute_stuff(); }do { execute_stuff(); } while(! exit_time );if( condition ) { break; }while(true)
0赞 Theraot 2/8/2014
关于那部漫画:虽然 do-while 不能做所有 while 可以做的事情,但 do-while 有时做 do-while 可以做的事情更好,而 while 也可以做。
2赞 gnat 7/28/2011 #12

也许我运气不好。或者也许我只是缺乏经验。但是每次我记得处理 having inside 时,都可以改进将 Extract Method 应用于 while-block 的代码,这保持了 but(巧合?)将所有 s 转换为 s。while(true)breakwhile(true)breakreturn

根据我的经验,没有休息(即回球或投掷)非常舒适且易于理解。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);
      }
  }
0赞 Jim Fell 7/29/2011 #13

我在很多函数中使用了类似的东西,但逻辑相反。

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 */
}
20赞 Jessica Brown 7/29/2011 #14

早在 1967 年,Edgar Dijkstra 在一本贸易杂志上写了一篇文章,内容是关于为什么应该从高级语言中删除 goto 以提高代码质量。一个由此诞生的称为“结构化编程”的编程范式,尽管肯定不是每个人都同意 goto 自动意味着糟糕的代码。

结构化编程的关键在于,代码的结构应该决定其流程,而不是尽可能地让 gotos 或中断或继续决定流程。同样,在这种范式中,也不鼓励对一个循环或函数有多个入口点和出口点。

显然,这不是唯一的编程范式,但通常可以很容易地应用于其他范式,如面向对象编程(ala Java)。

你的老师可能已经被教导过,并且正试图教你的班级,我们最好避免“意大利面条代码”,确保我们的代码是结构化的,并遵循结构化编程的隐含规则。

虽然使用 break 的实现本身并没有什么“错误”,但有些人认为,如果循环条件是在 while() 条件中显式指定的,则读取代码要容易得多,并且消除了一些过于棘手的可能性。使用新手程序员在代码中经常出现的 while(true) 条件肯定存在陷阱,例如意外创建无限循环的风险,或者编写难以阅读或不必要的混淆代码。

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

你的导师也可能希望你展示你使用该章或课文中教授的特定循环结构或语法的能力,虽然你编写的代码在功能上是等效的,但你可能没有展示你应该在那节课中学习的特定技能。

评论

2赞 Roy Prins 4/28/2018
这是 Edsger Dijkstra 的论文。这是一本好书。
11赞 jqa 7/29/2011 #15

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

这很糟糕,因为你在自找麻烦。不会是你或此页面上的任何其他海报有短/简单 while 循环的例子。

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

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

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++,教授们也会打分;)

评论

4赞 Florian F 9/1/2014
在您的示例中,代码是愚蠢的,不是因为选择了 while(true),而是因为在代码周围放置一个循环的想法。
0赞 juzzlin 11/21/2014
事实上,这个循环的意义何在?:)
0赞 Bimal Sharma 7/29/2011 #16

我想对你的老师使用休息就像折断一根树枝来得到果实,使用一些其他技巧(弓形树枝)这样你得到果实,树枝还活着:)

0赞 Pacerier 7/30/2011 #17

1) 没什么问题do -while(true)

2)你的老师错了。

NSFS!!:

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

评论

1赞 Connell 8/4/2011
我在编程方面是自学成才的。我所要判断的就是我在中学的 IT 老师,他教我们使用 Dreamweaver 制作网站,让一切都是绝对定位的 div......
2赞 filip-fku 8/5/2011
“那些做不到的人,教”——这是一个相当大的概括,你不觉得吗?医生、工程师、飞行员等都应该自学成才,因为他们的教官在你看来是无能的吗?
1赞 Paul 7/30/2011 #18

我想说的是,一般来说,它不被认为是一个好主意的原因是你没有充分利用这个结构。此外,我倾向于认为,当他们的学生带着“包袱”进来时,很多编程教师不喜欢它。我的意思是,我认为他们喜欢成为对学生编程风格的主要影响。所以也许这只是教练的一个小烦恼。

0赞 Dmitry 8/2/2011 #19

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

1赞 Razor 8/3/2011 #20

对我来说,问题在于可读性。

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

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

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

do {
  // Imagine a nice chunk of code here
} while(price < priceAllowedForDiscount);
2赞 Griff Sob Becker 8/4/2011 #21

我认为是的,这很糟糕......或者至少,对于许多开发人员来说。这是开发人员不考虑其循环条件的症状。因此容易出错。