while(真的)与糟糕的编程实践有关吗?

Is while (true) with break bad programming practice?

提问人:Edward Tanguay 提问时间:12/24/2008 最后编辑:SimsonEdward Tanguay 更新时间:2/23/2023 访问量:111278

问:

我经常使用这个 Code Pattern:

while(true) {

    //do something

    if(<some condition>) {
        break;
    }

}   

另一位程序员告诉我,这是不好的做法,我应该用更标准的程序员来代替它:

while(!<some condition>) {

    //do something

}   

他的理由是,你太容易“忘记休息”,并有一个无休止的循环。我告诉他,在第二个例子中,你可以很容易地放入一个永远不会返回 true 的条件,因此也很容易有一个无限循环,所以两者都是同样有效的做法。

此外,我通常更喜欢前者,因为当你有多个断点时,它使代码更容易阅读,即多个条件脱离了循环。

任何人都可以通过为一方或另一方添加证据来丰富这一论点吗?

while循环 可读性

评论

2赞 Jonathan Leffler 12/24/2008
如果我指出 stackoverflow.com/questions/212531/ 的问题,我是否会被塑造成诅咒的角色......这基本上是在问同样的事情?
0赞 Jim Nelson 12/24/2008
另一个类似的问题有有趣的答案:stackoverflow.com/questions/244445/......

答:

4赞 yfeldblum 12/24/2008 #1

如果有很多方法可以中断循环,或者如果中断条件不能在循环的顶部轻松表达(例如,循环的内容需要运行一半,但另一半不能运行,在最后一次迭代中),则第一个是可以的。

但是,如果你能避免它,你就应该避免它,因为编程应该是以最明显的方式编写非常复杂的东西,同时还要正确和高性能地实现功能。这就是为什么你的朋友,在一般情况下,是正确的。你朋友编写循环结构的方式要明显得多(假设没有获得上一段中描述的条件)。

1赞 ieure 12/24/2008 #2

这取决于你想做什么,但总的来说,我更喜欢把条件放在 while。

  • 它更简单,因为您不需要在代码中进行其他测试。
  • 它更容易阅读,因为您不必在循环中寻找休息时间。
  • 你正在重新发明轮子。只要测试是真的,就要做一些事情。为什么要通过将中断条件放在其他地方来颠覆它?

如果我正在编写一个守护进程或其他进程,我会使用 while(true) 循环,直到它被杀死。

0赞 Doug T. 12/24/2008 #3

他可能是对的。

从功能上讲,两者可以是相同的。

但是,为了可读性和理解程序流,while(条件)更好。休息更像是某种程度上。while(条件)对继续循环的条件等非常清楚。这并不意味着 break 是错误的,只是可读性可能较低。

63赞 Andrew G. Johnson 12/24/2008 #4

这两个示例之间存在差异。第一个将每次至少执行一次“做某事”,即使该语句永远不会为真。第二个只会在语句计算结果为 true 时“做某事”。

我认为你要找的是一个 do-while 循环。我 100% 同意这不是一个好主意,因为它使维护此代码变得困难,并且您逃避循环的方式非常时尚,这被认为是不好的做法。while (true)goto

尝试:

do {
  //do something
} while (!something);

请查看您的个人语言文档,了解确切的语法。但是看看这段代码,它基本上做了 do 中的事情,然后检查 while 部分,看看它是否应该再做一次。

评论

7赞 Tracker1 12/24/2008
我经常将 while(true) 用于服务中的工作线程......仅在线程中止异常的情况下返回...
15赞 jmucchiello 12/24/2008
这个答案没有考虑到更常见的成语: while (true) { /* do something / if (condition) break; / do more something / } 这样可以避免重复“做某事”部分: / do something / while (condition) { / do more something / / do something */ }
0赞 alper 11/28/2017
在 while 语句里面,而不是如果我写它仍然没问题吗?@Andrew G.约翰逊!somethingTrue
0赞 codelogic 12/24/2008 #5

我想到使用后一种结构的一些优点:

  • 更容易理解循环在做什么,而无需在循环的代码中寻找中断。

  • 如果你在循环代码中不使用其他中断,那么你的循环中只有一个出口点,那就是while()条件。

  • 通常最终会减少代码,这增加了可读性。

0赞 toad 12/24/2008 #6

我更喜欢 while(!) 方法,因为它更清晰、更直接地传达了循环的意图。

13赞 Bill the Lizard 12/24/2008 #7

我更喜欢

while(!<some condition>) {
    //do something
}

但我认为这更多的是可读性问题,而不是“忘记休息”的可能性。我认为忘记是一个相当薄弱的论点,因为这将是一个错误,你会立即找到并修复它。break

我反对使用 a 来摆脱无限循环的论点是,你本质上是将该语句用作 .我并不反对使用(如果语言支持它,这是公平的游戏),但如果有更易读的替代方案,我确实会尝试替换它。breakbreakgotogoto

在许多点的情况下,我会用break

while( !<some condition> ||
       !<some other condition> ||
       !<something completely different> ) {
    //do something
}

以这种方式合并所有停止条件可以更容易地看到什么将结束这个循环。 语句可以四处散布,这绝不是可读的。break

评论

0赞 wilhelmtell 12/24/2008
或者,如果你发现许多OR很笨拙,你可以定义一个函数we_are_good() { return cond1 || cond2 || cond3; } local到编译单元,并使用该谓词函数作为循环条件。
0赞 Bill the Lizard 12/24/2008
我通常只在我要多次使用函数时才将它们拉到函数中,但这是我个人喜好的问题。可读性也是一个很好的理由。
1赞 Larry OBrien 12/24/2008 #8

如果存在一个(且只有一个)非异常中断条件,则最好将该条件直接放入控制流结构(while)中。看到 while(true) { ... } 使我作为代码阅读者认为没有简单的方法来枚举中断条件,并让我想“仔细查看这一点并仔细考虑中断条件(在当前循环中设置了哪些条件以及在上一个循环中可能设置了哪些条件)”

简而言之,在最简单的情况下,我和你的同事在一起,但 while(true){ ... } 并不少见。

11赞 Martin Beckett 12/24/2008 #9

而 (true) 可能是有道理的,如果你有很多语句,并且你想在任何失败时停止

 while (true) {
     if (!function1() ) return;   
     if (!function2() ) return;   
     if (!function3() ) return;   
     if (!function4() ) return;  
   }

优于

 while (!fail) {
     if (!fail) {
       fail = function1()
     }           
     if (!fail) {
       fail = function2()
     }  
     ........

   }

评论

1赞 Edward Tanguay 12/24/2008
是的,这正是我的经验。我认为前者更容易阅读。
1赞 Roalt 12/24/2008
在我的大学里,有一位老师告诉我,在一个函数中做多个返回语句绝对是邪恶的。你应该把你的结果放在一个变量中(例如,称为result),并在最后返回它。我当然没有听...
1赞 Daniel Earwicker 12/24/2008
@Roalt - 见 stackoverflow.com/questions/36707/...
2赞 Thomas Eyde 12/26/2008
话又说回来,四个if可以重写并移动到while条件:while (function1() & function2() ...)
0赞 Martin Beckett 12/28/2008
while(true) 多个出口确实允许您在函数调用之间放置其他代码。
26赞 Norman Ramsey 12/24/2008 #10

何时可以在表单中编写代码

while (condition) { ... }

while (!condition) { ... }

如果正文中没有出口(breakcontinuegoto),则首选该形式,因为有人只需查看标头即可阅读代码并理解终止条件。很好。

但是很多循环并不适合这个模型,中间有显式出口的无限循环是一个光荣的模型。(带有的循环通常比带有 的循环更难理解。如果你想要一些证据或权威来引用,看看 Don Knuth 的著名论文 Structured Programming with Goto Statements;您将找到您可能想要的所有示例、论据和解释。continuebreak

一个小小的成语:写作将你打上一个老 Pascal 程序员的烙印,或者现在可能是一个 Java 程序员。如果您用 C 或 C++ 编写,首选的成语是while (true) { ... }

for (;;) { ... }

这没有充分的理由,但你应该这样写,因为这是 C 程序员期望看到它的方式。

评论

9赞 Javier 12/24/2008
我从来没有遇到过任何喜欢(;;;)的 C 程序员{} over while(1){}
2赞 Windows programmer 12/24/2008
如果我没记错的话,K&R 首选 for(;;){} over while(1){}
2赞 Roalt 12/24/2008
我认为原因(;;){ } 代替 while is 是因为它更接近于说“永远”
2赞 Its me 12/29/2008
我认为它对 #define 更具可读性;;然后你说 for(EVER),这样可以更清楚地看到它是一个 for 语句,而 EVER 是一个定义。
12赞 Ben Burns 3/17/2014
以防万一,为了上帝的爱,请不要真正定义任何这些宏......
1赞 Charlie Martin 12/24/2008 #11

完美的顾问的答案是:视情况而定。大多数情况下,正确的做法是使用 while 循环

while (condition is true ) {
    // do something
}

或者用类似 C 的语言完成的“重复直到”

do {
    // do something
} while ( condition is true);

如果这两种情况中的任何一种都有效,请使用它们。

有时,就像在服务器的内部循环中一样,您实际上意味着程序应该继续运行,直到外部中断它。(例如,考虑一个 httpd 守护程序 -- 除非它崩溃或因关闭而停止,否则它不会停止。

THEN AND ONLY THEN 使用 while(1):

while(1) {
   accept connection
   fork child process
}

最后一种情况是极少数情况下,您希望在终止之前执行某些部分功能。在这种情况下,请使用:

while(1) { // or for(;;)
   // do some stuff
   if (condition met) break;
   // otherwise do more stuff.
}
0赞 Ólafur Waage 12/24/2008 #12

这里有很多关于可读性的讨论,它的构造非常好,但与所有大小不固定的循环一样(即 do while 和 while),你运行是有风险的。

His reasoning was that you could "forget the break" too easily and have an endless loop.

在一段时间内,你实际上是在要求一个无限期运行的进程,除非发生某些事情,如果某个参数内没有发生某些事情,你将得到你想要的......一个无休止的循环。

1赞 Conrad 12/24/2008 #13

我认为使用“while(true)”的好处可能是让多个退出条件更容易编写,特别是如果这些退出条件必须出现在代码块中的不同位置。但是,对我来说,当我必须试运行代码以查看代码如何交互时,它可能会很混乱。

就我个人而言,我会尽量避免 while(true)。原因是每当我回顾之前编写的代码时,我通常会发现我需要弄清楚它何时运行/终止,而不是它实际执行的操作。因此,必须首先找到“休息点”对我来说有点麻烦。

如果需要多个退出条件,我倾向于将条件确定逻辑重构为一个单独的函数,以便循环块看起来干净且易于理解。

35赞 joel.neely 12/24/2008 #14

引用过去著名的开发者华兹华斯的话:

...
事实上,我们注定要
进入的监狱,没有监狱是;因此,对我来说,
在杂乱无章的情绪中,“被束缚
在十四行诗的稀疏土地上是一种消遣;
如果一些灵魂(因为他们的需要一定是这样的)
感到太多自由的重量,应该在那里找到短暂的安慰,
正如我所发现的那样。

华兹华斯接受了十四行诗的严格要求,将其作为解放的框架,而不是作为直筒夹克。我认为,“结构化编程”的核心是放弃构建任意复杂的流程图的自由,转而支持一种自由的易理解性。

我完全同意,有时提前退出是表达行动的最简单方式。然而,我的经验是,当我强迫自己使用尽可能简单的控制结构(并真正考虑在这些约束下进行设计)时,我通常会发现结果是更简单、更清晰的代码。缺点

while (true) {
    action0;
    if (test0) break;
    action1;
}

是很容易让代码块越来越大,或者添加“再多一个”测试-中断-操作序列,直到很难指向特定行并回答这个问题,“我知道在这一点上有什么条件?因此,在不为其他程序员制定规则的情况下,我尽可能避免在自己的代码中使用这个习语。action0action1while (true) {...}

评论

1赞 Javier 12/24/2008
我不同意具体的观点(我认为 while(true){} 是比 while(condition){} 更“纯粹”的结构);但我喜欢“严格要求作为自由名声”的想法
2赞 Shog9 12/25/2008
@joel:多么好的答案!@Javier:Goto 尽可能纯粹,但一般来说,你最好选择一些更具限制性和描述性的东西。
3赞 Jonathan Leffler 12/24/2008 #15

在 SO 中已经有一个基本相同的问题,即 Is WHILE TRUE......破。。。结束,而一个好的设计?.@Glomek回答(在一个被低估的帖子中):

有时它是非常好的设计。有关一些示例,请参阅 Donald Knuth 的 Structured Programing with Goto Statements。我经常将这个基本思想用于运行“n 次半”的循环,尤其是读取/处理循环。但是,我通常尝试只有一个中断语句。这样可以更轻松地推断循环终止后的程序状态。

过了一会儿,我回复了相关的,也是被严重低估的评论(部分原因是我第一次没有注意到 Glomek 的,我想):

Knuth在1974年发表的《Structured Programming with go to Statements》一文很有意思(在他的著作《Literate Programming》中也有,可能在其他地方也有)。除其他事项外,它还讨论了突破循环的受控方式,以及(不使用该术语)循环半语句。

Ada 还提供循环结构,包括

loopname:
    loop
        ...
        exit loopname when ...condition...;
        ...
    end loop loopname;

原始问题的代码在意图上与此类似。

引用的 SO 项目与此项目之间的一个区别是“最终中断”;这是一个单次循环,它使用 Break 提前退出循环。也有人质疑这是否是一种好的风格——我手头没有交叉参考。

1赞 mcrute 12/24/2008 #16

不,这还不错,因为在设置循环时您可能并不总是知道退出条件,或者可能有多个退出条件。但是,它确实需要更多的小心来防止无限循环。

0赞 billyswong 12/24/2008 #17

你朋友推荐的和你做的不一样。你自己的代码更类似于

do{
    // do something
}while(!<some condition>);

无论条件如何,它始终至少运行一次循环。

但正如其他人所提到的,有时休息是完全可以的。为了回应你朋友对“忘记休息”的担忧,我经常以以下形式写:

while(true){
    // do something
if(<some condition>) break;
    // continue do something
}

通过良好的缩进,断点对于第一次读取代码的人来说是清楚的,看起来就像在循环的开头或底部中断的代码一样结构化。

3赞 JRomio 12/24/2008 #18

有时您需要无限循环,例如侦听端口或等待连接。

所以虽然(真的)...不应该被归类为好或坏,让情况决定使用什么

0赞 Gene Roberts 12/24/2008 #19

问题不在于while(true)部分不好,而在于你必须打破或摆脱它。break 和 goto 并不是真正可接受的流量控制方法。

我也不太明白这一点。即使在一个程序的整个持续时间中循环的东西,你至少可以有一个叫做 Quit 的布尔值,或者你设置为 true 的东西,以便在循环中正确地退出循环,比如 while(!退出)...不只是在某个任意点叫休息然后跳出来,

评论

0赞 reggaeguitar 7/24/2019
如果“真的不可接受”,为什么在这么多语言中“休息”?Break 肯定有它的用法
8赞 joel.neely 12/26/2008 #20

哈维尔对我之前的回答(引用华兹华斯的话)做了一个有趣的评论:

我认为 while(true){} 是比 while(condition){} 更“纯粹”的结构。

我无法用 300 个字符做出充分的回答(对不起!

在我的教学和指导中,我非正式地将“复杂性”定义为“我需要脑海中有多少代码才能理解这一行或表达式?我必须记住的东西越多,代码就越复杂。代码明确告诉我的越多,复杂性就越低。

因此,为了降低复杂性,让我从完整性和强度而不是纯粹性的角度来回答哈维尔。

我想到这个代码片段:

while (c1) {
    // p1
    a1;
    // p2
    ...
    // pz
    az;
}

同时表达两件事:

  1. 只要保持真实,(整个)正文就会重复,并且c1
  2. 在第 1 点,执行的地方保证成立。a1c1

区别在于视角;其中第一个与整个循环的外部动态行为有关,而第二个则有助于理解我在思考时可以指望的内在静态保证。当然,净效应可能会使 ,要求我更加努力地思考我在第 2 点可以指望什么,等等。a1a1c1

让我们举一个具体的(微小的)例子来考虑条件和第一个动作:

while (index < length(someString)) {
    // p1
    char c = someString.charAt(index++);
    // p2
    ...
}

“外部”问题是,循环显然在内部执行某些操作,而这些操作只能在 中完成。这设定了一种期望,即我们将修改身体中的任何一个或内部(在我检查身体之前不知道的位置和方式),以便最终终止。这给了我思考身体的背景和期望。someStringindexsomeStringindexsomeString

“内在”问题是,我们保证第 1 点之后的操作是合法的,因此在阅读第 2 点的代码时,我可以考虑使用我知道已合法获得的 char 值正在做什么。(如果条件是 null ref,我们甚至无法评估,但我也假设我们已经在这个示例的上下文中防止了这一点!someString

相比之下,表单的循环:

while (true) {
    // p1
    a1;
    // p2
    ...
}

让我在这两个问题上都失望。在外部层面,我想知道这是否意味着我真的应该期望这个循环永远循环(例如操作系统的主事件调度循环),或者是否有其他事情发生。这既没有给我一个明确的阅读正文的背景,也没有给我一个关于什么构成(不确定的)终止的进展的期望。

在内在层面上,我绝对没有明确保证任何可能发生在第 1 点的情况。条件 ,当然在任何地方都是正确的,是关于我们在程序中任何时候可以知道的东西的最弱的陈述。在尝试思考行动完成什么时,了解行动的先决条件是非常有价值的信息!true

因此,我认为这个成语比我上面描述的逻辑更不完整、更薄弱,因此更复杂。while (true) ...while (c1) ...

0赞 devin 12/26/2008 #21

使用循环,如

while(1) { 做事 }

在某些情况下是必要的。如果您进行任何嵌入式系统编程(例如 PIC、MSP430 和 DSP 编程等微控制器),那么几乎所有代码都将处于 while(1) 循环中。在为 DSP 编码时,有时您只需要一个 while(1){},其余的代码就是中断服务例程 (ISR)。

8赞 Vouze 5/5/2014 #22

问题在于,并非每个算法都坚持“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 循环模型,但这总是会重复代码或检查两次相同的条件(对于每个循环)或添加一个新变量。因此,与while-true-break循环模型相比,您的效率和可读性始终较低。

(坏的)“技巧”示例:添加变量和条件

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混合来混淆代码(自愿或不愿意)。所以他可以感觉到他没有这样做。

-1赞 yegor256 2/23/2023 #23

如果循环外部条件(未在循环内部更改),则使用 ,其中是条件。但是,如果循环在循环内部条件发生变化时循环停止,则将出口点显式标记为 会更方便,而不是等待它在循环的下一次迭代中发生:while(t)tbreak

while (true) {
  ...
  a := a + 1;
  if (a > 10) break; // right here!
  ...
}

正如在其他一些答案中已经提到的,在阅读特定行时,您必须在脑海中保留的代码越少越好。