"while(true)"循环是如此糟糕吗?

JHa*_*ach 215 java while-loop do-while

我已经用Java编程了好几年了,但我刚刚回到学校获得正式学位.我很惊讶地发现,在我上一次任务中,我使用了如下所示的循环而丢失了分数.

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

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

我完全理解goto和它的Java堂兄的陷阱break:label,我很有意识不使用它们.我也意识到一个更完整的程序会提供一些其他的逃避手段,例如刚刚结束程序,但这不是我教授引用的原因,所以......

怎么了do-while(true)

Jon*_*eet 217

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

在我写的第一件事情的情况下,我几乎总是至少尝试将它重构为更清晰的东西.有时它无法帮助(或者替代方法是使用一个bool没有任何意义的变量,除了指示循环的结束,不如break声明更清楚),但它至少值得尝试.

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

while (true)
{
    doStuffNeededAtStartOfLoop();
    int input = getSomeInput();
    if (testCondition(input))
    {
        break;
    }
    actOnInput(input);
}
Run Code Online (Sandbox Code Playgroud)

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

boolean running = true;
while (running)
{
    doStuffNeededAtStartOfLoop();
    int input = getSomeInput();
    if (testCondition(input))
    {
        running = false;
    }
    else
    {
        actOnInput(input);
    }
}
Run Code Online (Sandbox Code Playgroud)

我认为后者更复杂:它有一个额外的else块,actOnInput更多的缩进,如果你想弄清楚testCondition返回时会发生什么true,你需要仔细查看块的其余部分来检查那里在块之后else不会发生任何事情,无论是否running设置false.

break声明更清楚地传达了意图,并让其余部分继续了解它需要做的事情,而不必担心早期的情况.

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

  • @trutheality:我更喜欢尽可能缩小我的大部分代码.复合分配的条件对我来说更复杂. (21认同)
  • @Ismail:我希望不是.帖子应仅根据其内容而不是作者来判断.哎呀,如果我认为这是不准确/无益的话,我甚至会向Eric Lippert发帖.虽然这还没有发生:) (6认同)
  • 我同意这不是我达到的第一个工具,但它似乎只是如此干净地处理问题,我确实喜欢干净的代码. (3认同)
  • @ X-Zero:是的,有时候.如果你可以把"休息"变成一个通常是善良的"回归"......虽然你可能仍然会以"while(true)"结束 (3认同)
  • @Vince:是的,如果你能在循环开始时轻松表达这个条件,那就太好了*.但有时你不能 - 这就是我们所谈论的情况.请注意我的答案中的前几句话. (3认同)
  • 在非破坏版本中"清理杂乱"实际上并不难:只需要做`if(!(stop = condition)){dostuff; 实际上,如果条件足够复杂,将`stop = condition;`(或`running = condition;`)单独放在一行上,之后重用该值可能实际上提高了可读性. (2认同)
  • 一个人一生中必须阅读的其他人的代码越多,就越会对这个答案+1。(反之亦然。) (2认同)

El *_*cel 100

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

bool guard = true;
do
{
   getInput();
   if (something)
     guard = false;
} while (guard)
Run Code Online (Sandbox Code Playgroud)

这几乎是一回事.

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

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

}
Run Code Online (Sandbox Code Playgroud)

  • 即使你知道你正在破坏*,这两个仍然会受到污染的其余部分(至少在某些情况下)的影响,因为必须保持循环体的末端*.我会在我的回答中举一个例子...... (20认同)
  • 我更喜欢Jon Skeet的回答.而且,while(true){}比do {}好多了,而(true) (8认同)
  • 我喜欢你的建议,因为终止条件更加明显.这意味着在阅读代码时,您将很快了解它正在尝试做什么.我永远不会使用无限循环但经常使用你的两个版本. (4认同)
  • 一致认为,一面旗帜胜过休息,主要是因为它表达了意图?我可以看到这是有益的.所有可靠的答案,但我会将此标记为已接受. (4认同)
  • 我讨厌 Dijkstra 曾经写过那篇 GoTo 文章。虽然 GoTo 在过去经常被滥用,但这并不意味着您永远不应该使用它。此外,Exit For、Break、Exit While、Try/Catch 都只是 Goto 的特殊形式。Goto 通常可以使代码更具可读性。是的,我知道任何事情都可以在没有 Goto 的情况下完成。这并不意味着它应该。 (2认同)

zzz*_*Bov 38

道格拉斯·克罗克福德谈到他希望JavaScript包含一个loop结构:

loop
{
  ...code...
}
Run Code Online (Sandbox Code Playgroud)

而且我认为Java对于拥有一个loop结构也不会更糟.

没有什么内在的错误的while(true)循环,但就是教师要阻止他们的倾向.从教学的角度来看,让学生创建无限循环并且不理解为什么循环不会被转义是非常容易的.

但他们很少提到的是所有循环机制都可以用while(true)循环复制.

while( a() )
{
  fn();
}
Run Code Online (Sandbox Code Playgroud)

是相同的

loop
{
  if ( !a() ) break;
  fn();
}
Run Code Online (Sandbox Code Playgroud)

do
{
  fn();
} while( a() );
Run Code Online (Sandbox Code Playgroud)

是相同的:

loop
{
  fn();
  if ( !a() ) break;
}
Run Code Online (Sandbox Code Playgroud)

for ( a(); b(); c() )
{
  fn();
}
Run Code Online (Sandbox Code Playgroud)

是相同的:

a();
loop
{
  if ( !b() ) break;
  fn();
  c();
}
Run Code Online (Sandbox Code Playgroud)

只要你可以设置你的循环中,一个方式工作,你选择使用结构是不重要的.如果恰好适合for循环,请使用for循环.

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

  • 难道没有人写'for(;;){`了吗?(发音为"forever").这曾经很受欢迎. (3认同)
  • +1:当涉及`continue`语句时,`for`循环还有其他复杂性,但它们不是主要的扩展. (2认同)

Jes*_*own 15

早在1967年,Dijkstra在一本贸易杂志上写了一篇关于为什么要从高级语言中删除goto以提高代码质量的文章.一个称为"结构化编程"的整个编程范例就是出于此,但当然并不是每个人都认为goto自动意味着代码不好.结构化编程的关键在于,代码的结构应该尽可能地确定其流程,而不是让步或者中断或继续确定流程.同样,在该范例中也不鼓励在循环或函数中具有多个进入和退出点.显然,这不是唯一的编程范例,但通常它可以很容易地应用于其他范例,如面向对象编程(ala Java).您的老师可能已经被教过,并且正在努力教您的课程,我们最好通过确保我们的代码结构化,并遵循结构化编程的隐含规则来避免"意大利面条代码".虽然使用break的实现没有任何固有的"错误",但是有些人认为在while()条件中明确指定循环条件的情况下读取代码要容易得多,并且消除了一些过于棘手的可能性.使用一段时间(真实)条件肯定存在陷阱,这些条件似乎是新手程序员经常在代码中弹出的,例如意外创建无限循环的风险,或者使代码难以阅读或不必要的混淆.

具有讽刺意味的是,异常处理是一个区域,当您进一步使用Java编程时,结构化编程的偏差肯定会出现并且可以预期.

您的教师也可能希望您能够证明您能够使用特定的循环结构或在该章或教科书中教授的语法,并且您编写的代码在功能上等同,您可能没有证明你应该在那一课中学到的特殊技能.

  • 这是 [Edsger Dijkstra 的论文](https://homepages.cwi.nl/~storm/teaching/reader/Dijkstra68.pdf)。这是一个很好的阅读。 (2认同)

Ken*_*oom 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
}
Run Code Online (Sandbox Code Playgroud)

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

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

在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);
Run Code Online (Sandbox Code Playgroud)

或者,如果您确信您知道文件中最长的文本行有多长,那么您可以这样做

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

如果您正在测试用户是否输入了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
}
Run Code Online (Sandbox Code Playgroud)

所以,虽然确实存在某种情况break或者说goto是合理的,但如果您所做的只是逐行读取文件或控制台,那么您不需要while (true)循环来完成它 - 您的编程语言已经为您提供了使用输入命令作为循环条件的适当习惯用法.

  • 你通过将循环的第一部分填充到`while`条件中来有效地实现这一点.在最终的Java条件下,它已经变得相当沉重,如果你不得不做大量的操作来决定是否继续,它可能会变得很长.你可以将它拆分成一个单独的函数,这可能是最好的.但是如果你确实想要它在一个函数中,并且在决定是否继续之前还有一些非常重要的工作要做,那么`while(true)`可能是最好的. (3认同)

rfe*_*eak 12

这不是一件很糟糕的事情,但在编码时你需要考虑其他开发人员.即使在学校.

你的开发人员应该能够在循环声明中看到你的循环的exit子句.你没有这样做.你在循环中间隐藏了退出子句,为其他人提供了更多的工作并试图理解你的代码.这与避免像"休息"这样的事情是一样的.

话虽这么说,你仍然可以在现实世界中看到很多代码.

  • 如果循环以"while(true)"开始,很明显它内部会有一个"break"或"return",或者它会永远运行.直截了当很重要,但是"虽然(真实)"本身并不是特别糟糕.在循环迭代中具有复杂不变量的变量将是导致更多焦虑的事物的示例. (4认同)
  • 尝试在搜索中深入三级,然后以某种方式突破.如果你不从那一点回来,代码将会变得更糟. (4认同)

jqa*_*jqa 11

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

这很糟糕,因为你在寻找麻烦.本页面上的您或任何其他海报都不会有短/简单的循环示例.

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

为什么?我必须找出为什么700K LOC应用程序会逐渐开始燃烧100%的CPU时间,直到每个CPU都饱和为止.这是一个惊人的while(真)循环.这是大而令人讨厌但它归结为:

x = read_value_from_database()
while (true) 
 if (x == 1)
  ...
  break;
 else if (x ==2)
  ...
  break;
and lots more else if conditions
}
Run Code Online (Sandbox Code Playgroud)

没有最终的其他分支.如果该值与if条件不匹配,则循环一直运行直到时间结束.

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

恕我直言,使用像while(true)这样的结构并不是一个好的防御性编程.它会回来困扰你.

(但我确实记得,如果我们不评论每一行,即使对于i ++,教授们也会评分;)

  • 关于防御性编程的评论+1. (3认同)
  • 在你的例子中,代码是愚蠢的,不是因为选择了while(true),而是因为想要在代码周围放置一个循环. (3认同)

Pat*_*k87 6

从某种意义上讲,结构化编程构造比(有点非结构化的)break和continue语句更受欢迎.相比之下,它们优选根据该原理"转到".

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


Ale*_*exR 5

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

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

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