提问人:JHarnach 提问时间:7/28/2011 最后编辑:Peter MortensenJHarnach 更新时间:12/3/2019 访问量:220410
“while(true)”循环有那么糟糕吗?[已结束]
Are "while(true)" loops so bad? [closed]
问:
我用 Java 编程已经有好几年了,但我最近才回到学校获得正式学位。我很惊讶地发现,在我的上一次作业中,我因为使用如下循环而失分。
do{
//get some input.
//if the input meets my conditions, break;
//Otherwise ask again.
} while(true)
现在对于我的测试,我只是扫描一些控制台输入,但我被告知不鼓励这种循环,因为使用类似于 ,我们只是不这样做。break
goto
我完全理解 Java 的缺点,并且我有很好的意识不使用它们。我还意识到,一个更完整的程序会提供一些其他的逃避方式,例如结束程序,但这不是我的教授引用的原因,所以......goto
break:label
怎么了?do-while(true)
答:
我不会说这很糟糕——但同样,我通常至少会寻找替代方案。
在这是我写的第一件事的情况下,我几乎总是至少尝试将其重构为更清晰的东西。有时它无济于事(或者另一种选择是有一个变量,除了指示循环的结束之外,它没有任何有意义的作用,不如语句清晰),但至少值得尝试。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
running
false
该语句更清楚地传达了意图,并让块的其余部分继续执行它需要做的事情,而不必担心早期的条件。break
请注意,这与人们对方法中的多个 return 语句的论证完全相同。例如,如果我可以在前几行中计算出方法的结果(例如,因为某些输入为 null、空或零),我发现直接返回该答案比使用一个变量来存储结果、然后是整个其他代码块,最后是一个语句更清晰。return
评论
while (true)
您可以只使用 Boolean 标志来指示何时结束 while 循环。 并且是软件难以维护的原因 - 软件危机(TM) - 应该避免,而且很容易也可以避免。Break
go to
你是否务实是一个问题。务实的程序员可能只是在这种简单的情况下使用 break。
但是最好不要使用它们,否则您可能会在不合适的情况下使用它们,例如在复杂的嵌套循环中,使用代码的可读性和可维护性变得更加困难。break
评论
if (running)
这更像是一个美学问题,更容易阅读代码,因为你明确知道为什么循环会在循环声明中停止。
AFAIK什么都没有,真的。老师只是过敏,因为他们在某个地方听到了它真的很糟糕。否则你只会写:goto
bool guard = true;
do
{
getInput();
if (something)
guard = false;
} while (guard)
这几乎是一回事。
也许这更干净(因为所有循环信息都包含在块的顶部):
for (bool endLoop = false; !endLoop;)
{
}
评论
这并不是一件可怕的事情,但是在编码时需要考虑其他开发人员。即使在学校。
您的其他开发人员应该能够在循环声明中看到循环的 exit 子句。你没有那样做。你把 exit 子句隐藏在循环的中间,让其他人来尝试理解你的代码,从而做更多的工作。这与避免“中断”之类的事情的原因相同。
话虽如此,您仍然会在现实世界中的许多代码中看到类似的东西。
评论
while (true)
break
return
while(true)
根据我的经验,在大多数情况下,循环具有继续的“主要”条件。这是应该写入 while() 运算符本身的条件。所有其他可能打破循环的条件都是次要的,不是那么重要等。它们可以写成附加语句。if() {break}
while(true)
经常令人困惑,可读性较差。
我认为这些规则并不能涵盖 100% 的情况,但可能只有 98% 的情况。
评论
从某种意义上说,结构化编程构造比(有点非结构化的)中断和继续语句更受欢迎,这是不好的。相比之下,根据这一原则,它们比“goto”更受欢迎。
我总是建议让你的代码尽可能结构化......虽然,正如 Jon Skeet 指出的那样,不要让它比这更有条理!
用于读取输入的常用 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 是合理的情况,但如果您所做的只是逐行读取文件或控制台,那么您就不需要循环来完成它——您的编程语言已经为您提供了使用输入命令作为循环条件的适当习惯用法。break
goto
while (true)
评论
while (true)
while
while(true)
gets()
fgets(buffer, BUFFER_SIZE, file)
fgets
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
最后一部分:保持循环简单。如果每次迭代都需要发生很多功能,请将其放在函数中。你总是可以在它工作后优化它。
评论
for
continue
for (;;) {
while(true)
的条件。值得庆幸的是,我们现在生活在一个可读性胜过黑客传播的精英主义深奥主义的时代,尽管我们还没有生活在一个简单优雅的时代,这种简单优雅已经真正被欣赏并铭记于心。for(;;)
loop
语句没有大问题,但有些人可能认为它略微降低了代码的可读性。尝试给变量起有意义的名称,在适当的位置计算表达式。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);
同样,举个微不足道的例子,两者都非常可读;但是,如果循环变得非常大或嵌套得很深(这意味着您可能已经重构了),则第一种样式可能会更清晰一些。
虽然不一定是为什么不使用的答案,但我一直认为这部漫画和随附的作者声明简洁地解释了为什么做而而不是做。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)
也许我运气不好。或者也许我只是缺乏经验。但是每次我记得处理 having inside 时,都可以改进将 Extract Method 应用于 while-block 的代码,这保持了 but(巧合?)将所有 s 转换为 s。while(true)
break
while(true)
break
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);
}
}
我在很多函数中使用了类似的东西,但逻辑相反。
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 */
}
早在 1967 年,Edgar Dijkstra 在一本贸易杂志上写了一篇文章,内容是关于为什么应该从高级语言中删除 goto 以提高代码质量。一个由此诞生的称为“结构化编程”的编程范式,尽管肯定不是每个人都同意 goto 自动意味着糟糕的代码。
结构化编程的关键在于,代码的结构应该决定其流程,而不是尽可能地让 gotos 或中断或继续决定流程。同样,在这种范式中,也不鼓励对一个循环或函数有多个入口点和出口点。
显然,这不是唯一的编程范式,但通常可以很容易地应用于其他范式,如面向对象编程(ala Java)。
你的老师可能已经被教导过,并且正试图教你的班级,我们最好避免“意大利面条代码”,确保我们的代码是结构化的,并遵循结构化编程的隐含规则。
虽然使用 break 的实现本身并没有什么“错误”,但有些人认为,如果循环条件是在 while() 条件中显式指定的,则读取代码要容易得多,并且消除了一些过于棘手的可能性。使用新手程序员在代码中经常出现的 while(true) 条件肯定存在陷阱,例如意外创建无限循环的风险,或者编写难以阅读或不必要的混淆代码。
具有讽刺意味的是,异常处理是一个与结构化编程的偏差肯定会出现的领域,并且随着您进一步使用 Java 编程而出现。
你的导师也可能希望你展示你使用该章或课文中教授的特定循环结构或语法的能力,虽然你编写的代码在功能上是等效的,但你可能没有展示你应该在那节课中学习的特定技能。
评论
这是你的枪,你的子弹和你的脚......
这很糟糕,因为你在自找麻烦。不会是你或此页面上的任何其他海报有短/简单 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++,教授们也会打分;)
评论
我想对你的老师使用休息就像折断一根树枝来得到果实,使用一些其他技巧(弓形树枝)这样你得到果实,树枝还活着:)
1) 没什么问题do -while(true)
2)你的老师错了。
NSFS!!:
3)大多数老师是教师,而不是程序员。
评论
我想说的是,一般来说,它不被认为是一个好主意的原因是你没有充分利用这个结构。此外,我倾向于认为,当他们的学生带着“包袱”进来时,很多编程教师不喜欢它。我的意思是,我认为他们喜欢成为对学生编程风格的主要影响。所以也许这只是教练的一个小烦恼。
如果循环在后台线程上运行,则可能会很糟糕,因此当您通过终止 UI 线程关闭应用程序时,该代码段将继续执行。正如其他人已经说过的那样,您应该始终使用某种支票来提供取消的方式。
对我来说,问题在于可读性。
具有 true 条件的 while 语句不会告诉您有关循环的任何信息。这使得理解它的工作变得更加困难。
从这两个片段中更容易理解什么?
do {
// Imagine a nice chunk of code here
} while(true);
do {
// Imagine a nice chunk of code here
} while(price < priceAllowedForDiscount);
我认为是的,这很糟糕......或者至少,对于许多开发人员来说。这是开发人员不考虑其循环条件的症状。因此容易出错。
评论
break
do {} while (true)
while(true) {}
break