“while(true)”循环如此糟糕?
我已经使用Java进行了几年的编程,但是最近我刚回到学校学习正式课程。 我很惊讶地发现,在我的最后一项任务中,我失去了使用如下所示循环的要点。
do{ //get some input. //if the input meets my conditions, break; //Otherwise ask again. } while(true)
现在对于我的testing,我只是扫描一些控制台input,但我被告知,这种循环是不鼓励的,因为使用break
类似于goto
,我们只是不这样做。
我完全理解goto
及其Java表兄弟break:label
的缺陷break:label
,我有良好的意识不要使用它们。 我也意识到,一个更完整的程序将提供一些其他的逃生方法,比如说,只是结束程序,但这不是我的教授引用的原因,所以…
什么是错误的, do-while(true)
?
我不会说这是坏的 – 但同样我通常至less会寻找替代品。
在我写第一件事情的时候,我几乎总是试图把它重构成更清晰的东西。 有时它是无法帮助的(或者是另一种方法是有一个bool
variables除了指示循环的结束,没有任何意义,除了break
语句之外没有什么意义),但至less值得尝试。
作为比标志更清楚地使用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); } }
我认为后者更复杂:它有一个额外的块, actOnInput
更加缩进,如果你正在试图解决testCondition
返回true
时会发生什么,你需要仔细看看块的其余部分检查是否在else
块之后没有什么东西会发生,无论running
是否设置为false
。
break
语句更清楚地expression意图,并让其余的部分继续进行,而不用担心之前的情况。
请注意,这与人们对于方法中多重返回语句的说法完全一样。 例如,如果我可以在前几行中计算一个方法的结果(例如,因为某些input为空,空或零),我发现直接返回该答案比使用variables来存储结果更清晰,然后是一整块其他的代码, 最后是一个return
语句。
AFAIK没什么,真的。 老师对goto
过敏,因为他们听到的地方真的很糟糕。 否则,你只会写:
bool guard = true; do { getInput(); if (something) guard = false; } while (guard)
这几乎是一回事。
也许这是更干净的(因为所有的循环信息都包含在块的顶部):
for (bool endLoop = false; !endLoop;) { }
道格拉斯·克罗克福德(Douglas Crockford)对他希望JavaScript如何包含loop
结构有一个评论:
loop { ...code... }
而且我不认为Java对于loop
结构来说会更糟糕。
while(true)
循环没有任何内在的错误,但教师有一种倾向,劝阻他们。 从教学的angular度来看,让学生创造无限循环,不明白为什么循环不能逃脱,这是很容易的。
但是他们很less提到的是, 所有的循环机制都可以用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(); }
只要你可以设置循环的方式,你select使用的构造是不重要的。 如果它恰好适合for
循环,则使用for
循环。
最后一部分:保持简单的循环。 如果每次迭代都需要发生很多function,请将其放入函数中。 你可以随时对它进行优化。
早在1967年,Dijkstra在一篇贸易杂志上写了一篇关于为什么要从高级语言中删除goto以提高代码质量的文章。 一个被称为“结构化编程”的整个编程范例出来了,尽pipe当然不是每个人都同意goto自动意味着不好的代码。 结构化程序devise的关键在于,代码的结构应该决定它的stream程,而不是打个岔子,或者继续确定stream程。 类似地,在这个范例中,也有一个循环或者函数有多个入口和出口点。 显然,这不是唯一的编程范例,但通常它可以很容易地应用于其他范例,如面向对象编程(ala Java)。 你的老师可能已经被教过了,正试图教你的class级,通过确保我们的代码是结构化的,遵循隐含的结构化编程规则,最好避免“意大利面代码”。 尽pipe使用break的实现没有任何内在的“错误”,但是有些人认为在while()条件中明确指定循环的条件的情况下阅读代码要容易得多,并且消除了一些过于棘手的可能性。 使用新手程序员在代码中频繁出现的while(true)条件,例如意外创build无限循环的风险,或者使代码难以阅读或不必要地混淆,这些肯定有缺陷。
具有讽刺意味的是,exception处理是一个偏离结构化编程的领域,当你进一步用Java进行编程的时候,肯定会出现和预料到这一点。
也可能你的老师可能期望你能展示你使用特定的循环结构或语法的能力,在你的文章的章节或教训中,而你所写的代码在function上是等同的,你可能没有演示过你在本课中应该学习的特殊技能。
Java阅读input的惯例是:
import java.io.*; BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String strLine; while ((strLine = br.readLine()) != null) { // do something with the line }
阅读input的通常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 }
如果您正在testing以查看用户是否input了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)
循环来完成它 – 你的编程语言已经为您提供了使用input命令作为循环条件的适当的习惯用法。
这并不是什么糟糕的事情,但编码时需要考虑其他开发者。 即使在学校。
您的开发人员应该能够在循环声明中看到循环的exit子句。 你没有那样做。 你在循环中间隐藏了退出子句,为其他人来做更多的工作,试图理解你的代码。 这就是避免像“rest”这样的事情的原因。
这就是说,在现实世界中,你仍然可以看到很多这样的代码。
这是你的枪,你的子弹和你的脚…
这是不好的,因为你在惹麻烦。 这不会是你或任何其他海报在这个网页上有短/简单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 }
没有最后的分支。 如果值不匹配,则循环一直保持运行直到时间结束。
当然,程序员指责最终用户没有select程序员期望的价值。 (然后我删除了代码中while(true)的所有实例。)
恕我直言,这是不好的防守编程使用像while(true)的构造。 它会回来困扰你。
(但是,如果我们没有评论每一行,即使对于i ++,我也会记得教授们的评分;
从结构化程序devise的结构优先于(有些非结构化)的中断和继续语句的angular度来看,这是不好的。 相比之下,他们更喜欢根据这个原则“转到”。
我总是build议尽可能使代码尽可能结构化……尽pipe如Jon Skeet所指出的那样,不要使它更加结构化!
根据我的经验,在大多数情况下循环有“主要”条件继续。 这是应该写入while()运算符本身的条件。 所有其他可能打破循环的条件是次要的,不是那么重要等等。它们可以被写作额外的if() {break}
语句。
while(true)
往往令人困惑,而且可读性较差。
我认为这些规定并不包括100%的案件,但可能只有98%。
虽然不一定是为什么不使用while (true)
的答案,但是我总是发现这个漫画和伴随的作者的陈述简单地解释了为什么要做,而不是do-while。
关于你的问题:没有固有的问题
while(true) { do_stuff(); if(exit_time) { break; } }
… 如果你知道你在做什么,并确保exit_time
将在某些时候评估为true
。
老师不鼓励你使用while(true)
因为除非你知道自己在做什么,否则这是一个容易犯的错误。
您可能只需使用布尔标志来指示何时结束while循环。 Break
和go to
软件是难以维护的原因 – 软件危机(TM) – 应该避免,也容易也可以。
这是一个问题,如果你是务实或不。 实用的程序员可能会在这种简单的情况下使用break。
但是最好不要使用它们,否则你可能会在不适合的情况下使用它们,比如在复杂的嵌套循环中,你的代码的可读性和可维护性通过使用break
变得更加困难。
我认为对于很多开发者来说,这是非常糟糕的,或者至less是这样。 这是开发商的症状,不考虑他们的循环条件。 因此容易出错。
while(true)
与break
语句没有大的问题,但有些人可能会认为它略微降低了代码的可读性。 尝试赋予variables有意义的名字,在合适的位置评估expression式。
就你的例子而言,做一些事情似乎更加清晰:
do { input = get_input(); valid = check_input_validity(input); } while(! valid)
如果do while循环变长,尤其如此 – 您确切知道检查的位置是否正在发生额外的迭代。 所有variables/函数都在抽象层次上有适当的名字。 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);
同样,一个微不足道的例子都是非常可读的。 但是如果循环变得非常大或深度嵌套(这意味着你可能应该已经重构了),第一种风格可能会更清晰一些。
我使用了类似的东西,但逻辑相反,在我的许多function。
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 */ }
这更多的是美学的东西,更容易阅读代码,你明确知道为什么循环将停止在循环的声明。
也许我很不走运。 或者,也许我只是缺乏经验。 但是每当我记得while(true)
里面进行处理的while(true)
,有可能改进使用Extract Method的代码, while-block保持while(true)
但是(通过巧合?)把所有的break
秒。
根据我的经验while(true)
没有rest的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); } }
1) do -while(true)
没有错,
2)你的老师是错的。
的NSF !!:
3)大多数教师是教师而不是程序员。
我会说,通常它不被认为是一个好主意的原因是你没有使用这个构造来发挥它的全部潜力。 另外,我倾向于认为当学生们带着“包袱”进来的时候,很多程序员都不喜欢它。 我的意思是我认为他们喜欢成为他们的学生编程风格的主要影响力。 所以也许这只是教练的宠儿。
对我来说,问题是可读性。
一个具有真实条件的陈述告诉你没有任何关于循环。 这让理解它的工作变得更加困难。
从这两个片段中将会更容易理解?
do { // Imagine a nice chunk of code here } while(true); do { // Imagine a nice chunk of code here } while(price < priceAllowedForDiscount);
我想对你的老师使用rest就像打破树的一个分支得到水果,使用一些其他的技巧(低头分支),让你得到的果实,分支还活着。
如果你的循环运行在后台线程上,可能会很糟糕,所以当你通过终止一个UI线程来closures你的应用程序时,这段代码将继续执行。 正如其他人已经说过的,你应该总是使用某种检查来提供取消的方法。