是(true)与破坏编程实践?
我经常使用这种代码模式:
while(true) { //do something if(<some condition>) { break; } }
另一位程序员告诉我,这是不好的做法,我应该用更标准的代码replace它:
while(!<some condition>) { //do something }
他的推理是,你可以“轻松地忘记”rest,并有一个无止境的循环。 我告诉他,在第二个例子中,你可以轻而易举地处于一个永不回头的状态,所以就像循环一样容易,所以两者都是同样有效的做法。
而且,我经常更喜欢前者,因为当有多个中断点时,代码更容易阅读,即多个条件脱离循环。
任何人都可以通过为一方或另一方添加证据来丰富这一论点吗?
这两个例子之间有差异。 第一个会每次至less执行一次“做某事”,即使这个陈述从来都不是真的。 当语句评估为真时,第二个只会“做某事”。
我认为你正在寻找的是一个do-while循环。 我100%的同意, while (true)
不是一个好主意,因为这使得难以维护这个代码,你逃避循环的方式是非常goto
esque这被认为是不好的做法。
尝试:
do { //do something } while (!something);
检查您的个人语言文档的确切语法。 但看看这个代码,它基本上是做什么的,然后检查while部分,看它是否应该再次执行。
引用那位着名的开发人员华兹华斯:
…
事实上,我们所失败的监狱
我们自己没有监狱。 因此对于我来说,
在各种各样的情绪中,“双重消遣是被束缚的
在十四行诗的地面上,
如果有些人(因为他们的需要必须)
谁感到自由太重了,
应该在那里find简短的安慰,就像我find的那样。
华兹华斯接受了十四行诗的严格要求作为解放框架,而不是一件紧身衣。 我build议“结构化编程”的核心是放弃构build任意复杂stream程图的自由,以便于理解。
我自由地同意, 有时早期退出是expression行动的最简单的方式。 然而,我的经验是,当我强迫自己使用最简单的控制结构(并真正考虑在这些限制内进行devise)时,我经常发现结果是更简单,更清晰的代码。 与的缺点
while (true) { action0; if (test0) break; action1; }
很容易让action0
和action1
变成越来越大的代码块,或者增加“再多一个”testing中断动作序列,直到难以指向特定的行并回答“什么条件我知道在这一点上持有?“ 所以,在没有为其他程序员制定规则的情况下,我尽可能避免在自己的代码中使用while (true) {...}
成语。
当你可以在窗体中编写你的代码
while (condition) { ... }
要么
while (!condition) { ... }
没有退出( break
, continue
,或goto
)在身体 ,这种forms是首选,因为有人可以阅读代码,并了解终止条件只是看标题 。 那很好。
但是很多循环都不适合这个模型,而在中间显式退出的无限循环是一个光荣的模型 。 ( continue
循环通常比有break
循环更难理解)如果你想要一些证据或权威来引用,可以看看Don Knuth关于结构化程序devise与转向语句的着名论文。 你会发现你可能想要的所有例子,论点和解释。
成语的一个小点: while (true) { ... }
书写while (true) { ... }
你是一位古老的Pascal程序员,或者现在也许是Java程序员。 如果你用C或C ++编写,首选的习惯用法是
for (;;) { ... }
这没有什么好的理由,但你应该这样写,因为这是C程序员期望看到的。
我更喜欢
while(!<some condition>) { //do something }
但我认为这更多的是可读性,而不是“忘记rest”的可能性。 我认为忘记break
是一个相当微弱的理由,因为这将是一个错误,你会马上find并修复它。
我反对使用break
摆脱无休止的循环的论点是,你基本上使用break
语句作为goto
。 我不虔诚地反对使用goto
(如果语言支持它,这是公平的游戏),但我确实试图取代它,如果有一个更可读的select。
在许多break
的情况下,我会replace它们
while( !<some condition> || !<some other condition> || !<something completely different> ) { //do something }
以这种方式巩固所有的停止条件使得更容易看到结束这个循环的过程。 break
陈述可能会散布在四周,而这只是可读性。
而(真)如果你有许多陈述,而你想停下来(如果有的话)失败的话,这可能是有意义的
while (true) { if (!function1() ) return; if (!function2() ) return; if (!function3() ) return; if (!function4() ) return; }
比…更好
while (!fail) { if (!fail) { fail = function1() } if (!fail) { fail = function2() } ........ }
如果有许多方法可以从循环中断开,或者如果在循环的顶部不能容易地expression中断条件(例如,循环的内容需要中途运行,而另一半则不能运行,在最后一次迭代)。
但是,如果你能避免它,那么你应该这样做,因为编程应该尽可能以最明显的方式编写非常复杂的东西,同时也要正确,高效地实现function。 这就是为什么你的朋友在一般情况下是正确的。 你的朋友编写循环结构的方法要明显得多(假设前一段所述的条件没有获得)。
哈维尔对我早些时候的回答(引用华兹华斯)做了一个有趣的评论:
我认为while(true){}是比while(condition){}更“纯”的结构。
而我无法在300个字符中作出充分的回应(对不起!)
在我的教学和辅导中,我已经非正式地将“复杂性”定义为“我脑海中需要有多less代码能够理解这个单一的行或expression? 我必须记住的东西越多,代码越复杂。 代码越明确地告诉我,越不复杂。
所以,为了减less复杂性,让我在完整性和实力而不是纯粹的方面回答哈维尔。
我想到了这个代码片段:
while (c1) { // p1 a1; // p2 ... // pz az; }
同时expression两件事情:
- 只要
c1
保持真实,(整个)身体就会重复 - 在点1,其中
a1
被执行,c1
被保证保持。
差异是一个angular度; 第一个问题与整个循环的外部dynamic行为有关,而第二个问题则有助于理解内部的静态保证,特别是在考虑a1
时,我可以依靠这个保证。 当然, a1
的净效应可能会使c1
失效,要求我在第二点等我所能想到的更加困难。
让我们举一个特定的(微小)例子来思考条件和第一个行动:
while (index < length(someString)) { // p1 char c = someString.charAt(index++); // p2 ... }
“外层”的问题是,循环显然是在someString
中做某些事情,只要index
位于someString
中就可以做到。 这build立了一个期望,我们将在主体内修改index
或someString
(在我检查主体之前不知道的位置和方式),以便最终发生终止。 这给了我思考身体的背景和期望。
“内在”问题是我们保证,遵循第一点的行为是合法的,所以在第二点阅读代码的时候,我可以考虑用我知道已经合法获得的字符值来做什么。 (如果someString
是一个空引用,我们甚至不能评估这个条件,但是我也假设我们已经在这个例子的上下文中保护了它)!
相反,一个forms的循环:
while (true) { // p1 a1; // p2 ... }
让我在这两个问题上失望。 在外层,我想知道这是否意味着我真的应该期望这个循环永远循环(例如操作系统的主事件调度循环),或者是否还有其他事情正在进行。 这给我既没有一个明确的背景来阅读身体,也没有期望什么构成(不确定)终止的进展。
在内部层面上,我绝对没有明确保证在第一点可能会有什么情况。当然,在任何情况下都是这样的情况是我们在程序中任何时候都可以知道的最可能的陈述。 了解行动的前提条件,当试图思考行动完成时,是非常有价值的信息!
所以,我认为,根据我上面所描述的逻辑while (true) ...
成语比while (c1) ...
更加不完整和脆弱,因此更复杂。
这取决于你想要做什么,但总的来说,我宁愿把条件放在一边。
- 这很简单,因为你不需要在代码中进行另一个testing。
- 阅读起来比较容易,因为你不必在圈内寻找rest时间。
- 你正在重新发明轮子。 只要testing是真实的,一切的一点就是做一些事情。 为什么要把破坏条件放在别的地方呢?
如果我正在编写一个守护进程或其他进程,直到它被杀死,我会使用一个while(true)循环。
如果有一个(也是唯一一个)非常规的突破条件,那么把这个条件直接放在控制stream构造(while)中是可取的。 看到while(true){…}让我作为一个代码阅读者认为没有简单的方法来列举break条件,并让我认为“仔细观察,仔细考虑break条件(以前设置的是什么他们在当前循环中,可能已经在前一个循环中设置)“
总之,我与你的同事在最简单的情况下,但(真){…}并不罕见。
完美的顾问回答:这取决于。 大多数情况下,正确的做法是使用while循环
while (condition is true ) { // do something }
或者是一个“重复直到”,用C语言来完成
do { // do something } while ( condition is true);
如果其中任何一种情况有效,请使用它们。
有时候,就像在服务器的内部循环一样,你的意思是一个程序应该继续下去,直到外部中断它为止。 (考虑一下,例如一个httpd守护进程 – 它不会停止,除非它崩溃或停止closures。)
然后只用一段时间(1):
while(1) { accept connection fork child process }
最后一种情况是在终止之前想要完成某个function部分的罕见情况。 在这种情况下,请使用:
while(1) { // or for(;;) // do some stuff if (condition met) break; // otherwise do more stuff. }
在SO中已经有一个基本相同的问题了吗?尽pipe这是一个好的devise吗? 。 @Glomek回答(在一个被低估的职位):
有时候这是非常好的devise。 有关示例,请参阅Donald Knuth的“ 使用Goto语句进行结构化编程” 。 我经常使用这个基本概念来运行“n次半”的循环,尤其是读取/处理循环。 不过,我通常只会尝试只有一个break语句。 这使循环终止后,程序状态的推理更容易。
稍后,我回答了有关的,也是悲观的低估的评论(部分原因是我第一次没有注意到Glomek,我想):
一篇引人入胜的文章是Knuth 1974年出版的“结构化程序devise语言”(可在他的“Literate Programming”一书中find,也可能在其他地方)。 它讨论了除其他事项外,控制循环的方法,和(不使用术语)循环半语句。
Ada还提供循环结构,包括
loopname: loop ... exit loopname when ...condition...; ... end loop loopname;
原始问题的代码与此意图类似。
被引用的SO项目和这个项目之间的一个区别是“最后的rest”。 这是一个使用break来提早退出循环的单次循环。 对于这是否是一种好的风格也有疑问 – 我手边没有交叉引用。
不,这并不坏,因为当您设置循环时,您可能并不总是知道退出条件,或者可能有多个退出条件。 但是,它确实需要更多的注意力来防止无限循环。
有时你需要无限循环,例如在端口上监听或等待连接。
所以,虽然(真)不应该归类为好或坏,让情况决定使用什么
不是那么多的(真的)部分是坏的,但是你必须打破或者脱离这个问题。 rest和转到并不是真正可以接受的stream量控制方法。
我也没有真正看到这一点。 即使在循环遍历程序的整个过程中,至less可以有一个叫做Quit的布尔值或者设置为true的布尔值,以便像while(!Quit)一样在循环中正确地跳出循环…不是只是在某个任意点打个招呼跳出来,
问题在于并不是每个algorithm都贴着“while(cond){action}”模型。
一般的循环模型是这样的:
loop_prepare loop: action_A if(cond) exit_loop action_B goto loop after_loop_code
当没有action_A时,可以用下面的代替它:
loop_prepare while(cond) action_B after_loop_code
当没有action_B时,可以用下面的代替它:
loop_prepare do action_A while(cond) after_loop_code
在一般情况下,action_A将执行n次,action_B将执行(n-1)次。
一个真实的例子是:打印由逗号分隔的所有表格元素。 我们需要(n-1)个逗号的所有n个元素。
你总是可以做一些技巧来坚持while循环模型,但是这将总是重复代码或检查两次相同的条件(每个循环)或添加一个新的variables。 所以你将永远比while-true-break循环模型效率低,可读性差。
(坏)“招”的例子:添加variables和条件
loop_prepare b=true // one more local variable : more complex code while(b): // one more condition on every loop : less efficient action_A if(cond) b=false // the real condition is here else action_B after_loop_code
(坏)“诡计”的例子:重复代码。 在修改两个部分中的一个时,重复的代码不能被遗忘。
loop_prepare action_A while(cond): action_B action_A after_loop_code
注意:在最后一个示例中,程序员可以通过将“loop_prepare”与第一个“action_A”混合,并将action_B与第二个action_A混合来混淆(自愿或不愿意)代码。 所以他可以有这样的感觉。
他可能是正确的。
在function上两者可以是相同的。
但是,为了可读性和理解程序stream程,while(条件)更好。 rest更多的是一种跳跃。 while(条件)在继续循环的条件上是非常明确的。这并不意味着break是错误的,只是可读性较差。
使用后面的结构的一些优点让我想起了:
-
理解循环在做什么而不在循环的代码中寻找中断是比较容易的。
-
如果循环代码中没有使用其他的中断,循环中只有一个退出点,那就是while()条件。
-
通常最终是更less的代码,这增加了可读性。
我更喜欢while(!)方法,因为它更清楚并且立即传达了循环的意图。
这里有很多关于可读性的讨论,而且它的结构非常好,但是所有循环的大小并不是固定的
His reasoning was that you could "forget the break" too easily and have an endless loop.
在一个while循环中,你实际上是要求一个无限期运行的进程,除非发生什么事情,如果某个参数没有发生什么事情,你将得到你想要的东西……一个无限循环。
我认为使用“while(true)”的好处是可能让多个退出条件更容易编写,特别是如果这些退出条件必须出现在代码块内的不同位置。 但是,对于我来说,当我不得不干预代码以查看代码如何交互时,可能会非常混乱。
我个人会尽量避免while(true)。 原因在于,无论何时回顾以前编写的代码,我通常都会发现,我需要弄清楚它运行/终止的时间多于实际执行的时间。 因此,首先find“rest”对我来说有点麻烦。
如果需要多个退出条件,我倾向于将条件确定逻辑重构为一个单独的函数,以便循环块看起来干净,更容易理解。
你的朋友推荐的与你所做的不同。 你自己的代码更类似于
do{ // do something }while(!<some condition>);
无论条件如何,它总是至less运行一次循环。
但有时间rest是完全可以的,正如其他人所说的那样。 为了回应你的朋友担心“忘记rest”,我经常写下如下的forms:
while(true){ // do something if(<some condition>) break; // continue do something }
通过良好的缩进,第一次读取代码的断点是清晰的,看起来像在循环开始或结束时破坏的代码的结构。
使用循环
while(1){do stuff}
在某些情况下是必要的。 如果你做任何embedded式系统编程(比如PIC,MSP430和DSP等微控制器),那么几乎所有的代码都将处于一个循环中。 当编码DSP时,有时你只需要一段时间(1){},其余的代码是一个中断服务程序(ISR)。
我更喜欢循环:
for (;;) { ... }
甚至
for (init();;) { ... }
但是,如果init()
函数看起来像循环的主体,那么最好使用它
do { ... } while(condition);