是(真的)与破坏不良的编程实践?

时间:2008-12-24 00:28:02

标签: while-loop readability

我经常使用这种代码模式:

while(true) {

    //do something

    if(<some condition>) {
        break;
    }

}   

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

while(!<some condition>) {

    //do something

}   

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

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

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

23 个答案:

答案 0 :(得分:43)

两个例子之间存在差异。第一个将每次至少执行一次“做某事”,即使该陈述永远不是真的。当语句评估为真时,第二个只会“做某事”。

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

尝试:

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

检查您的个人语言文档以获取确切的语法。但是看看这段代码,它基本上做了什么,然后检查while部分,看它是否应该再做一次。

答案 1 :(得分:29)

引用那些着名的开发者的消息,华兹华斯:

  

...
  事实上,监狱,我们毁灭了   我们自己,没有监狱;因此,对我而言,   在各种各样的情绪中,'twas pastime受到束缚   在十四行诗中,地面上很少;   如果有些灵魂(为了他们的需要必须),那就很高兴了   谁感受到了太多自由的重担,
  正如我所发现的,应该在那里找到简短的慰借。

华兹华斯接受了十四行诗作为一个解放框架的严格要求,而不是作为一件紧身衣。我建议“结构化编程”的核心是放弃构建任意复杂的流程图的自由,以支持易于理解的解放。

我自由地同意有时提前退出是表达行动的最简单方式。然而,我的经验是,当我强迫自己使用最简单的控制结构(并且真的考虑在这些约束内进行设计)时,我经常发现结果更简单,代码更清晰。

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

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

答案 2 :(得分:19)

何时可以用

形式编写代码
while (condition) { ... }

while (!condition) { ... }

没有退出(breakcontinuegoto)正文,首选该表单,因为有人可以阅读代码并且< strong>仅通过查看标题来了解终止条件。那很好。

但是很多循环都不适合这个模型,而中间有明确退出的无限循环是一个光荣的模型。 (带有continue的循环通常比使用break的循环更难理解。)如果你想要一些证据或权威来引用,那么请看Don Knuth关于Structured Programming with Goto Statements的着名论文;你会找到你想要的所有例子,论据和解释。

成语的一个小问题:将while (true) { ... }作为旧Pascal程序员的品牌,或者现在可能是Java程序员。如果您在C或C ++中编写,首选的习语是

for (;;) { ... }

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

答案 3 :(得分:11)

我更喜欢

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

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

我反对使用break摆脱无限循环的论点是你基本上使用break语句作为goto。我并不虔诚地反对使用goto(如果语言支持它,这是公平的游戏),但如果有更可读的选择,我会尝试替换它。

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

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

通过这种方式整合所有停止条件可以更容易地看到将要结束此循环的内容。可以散布break个陈述,这些都是可读的。

答案 4 :(得分:6)

Javier对我之前的回答(引用华兹华斯的一篇文章)做了一个有趣的评论:

  

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

我无法用300个字符作出充分回应(对不起!)

在我的教学和指导中,我非正式地将“复杂性”定义为“为了能够理解这一行或表达,我需要在其余的代码中使用多少代码?”我必须记住的东西越多,代码就越复杂。代码告诉我的越多,就越不复杂。

因此,为了降低复杂性,让我回答Javier的完整性和力量,而不是纯洁。

我想到了这段代码片段:

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

同时表达两件事:

  1. 只要c1保持为真,就会重复(整个)身体,
  2. 在第1点,执行a1c1 保证保留。
  3. 区别在于视角;其中第一个与整个循环的外部动态行为有关,而第二个则有助于理解我在特别考虑a1时可以依赖的内在静态保证。当然,a1的净效应可能使c1无效,要求我更难以理解我在第2点可以依赖的内容等等。

    让我们用一个特定的(微小的)例子来考虑条件和第一个动作:

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

    “外部”问题是,循环显然在someString内执行某些操作,只有index位于someString时才能执行此操作。这预示着我们将在身体内修改indexsomeString(在我检查身体之前不知道的位置和方式),以便最终终止。这给了我思考身体的背景和期望。

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

    相反,形式的循环:

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

    让我了解这两个问题。在外层,我想知道这是否意味着我真的应该期望这个循环永远循环(例如操作系统的主事件调度循环),或者是否还有其他事情发生。这既没有给出阅读身体的明确背景,也没有给出对(不确定)终止进展的预期。

    在内在层面,我对于在第1点可能存在的任何情况都有绝对没有明确的保证。条件true,当然在任何地方都是真的,是最弱的关于我们在程序中的任何一点可以知道的内容的陈述。在试图考虑行动的实现时,理解行动的先决条件是非常有价值的信息!

    所以,根据我上面描述的逻辑,我建议while (true) ...成语比while (c1) ...更加不完整和弱,因此更复杂。

答案 5 :(得分:6)

虽然(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()
     }  
     ........

   }

答案 6 :(得分:3)

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

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

答案 7 :(得分:2)

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

因此,虽然(真实)......不应该归类为好或坏,但让情况决定使用什么

答案 8 :(得分:1)

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

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

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

答案 9 :(得分:1)

问题在于并非所有算法都坚持&#34;而(cond){action}&#34;模型。

一般循环模型是这样的:

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个元素都带有(n-1)个逗号。

你总是可以做一些技巧来坚持while循环模型,但是这将总是重复代码或检查两次相同的条件(对于每个循环)或添加一个新变量。因此,与while-true-break循环模型相比,您总是效率低,可读性差。

(坏)&#34;技巧&#34;的例子:添加变量和条件

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

(坏)&#34;技巧&#34;的例子:重复代码。修改两个部分中的一部分时,不能忘记重复的代码。

loop_prepare
action_A
 while(cond):
  action_B
  action_A
after_loop_code

注意:在最后一个例子中,程序员可以通过混合&#34; loop_prepare&#34;来混淆(愿意或不愿意)代码。第一个&#34; action_A&#34;和action_B与第二个action_A。所以他可以感觉到他没有这样做。

答案 10 :(得分:1)

而(真实)部分并不是那么糟糕,但事实上你必须打破或转出它才是问题所在。 break和goto不是真正可接受的流量控制方法。

我也没有看到这一点。即使是在程序的整个持续时间内循环的东西,你至少可以像一个名为Quit的布尔值,或者你设置为true的东西,以便在循环中正确地离开循环(!退出)...不只是在某个任意点呼叫休息并跳出来,

答案 11 :(得分:1)

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

答案 12 :(得分:1)

Is WHILE TRUE…BREAK…END WHILE a good design?的SO中已经存在一个基本相同的问题。 @Glomek回答(在一个被低估的帖子中):

  

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

稍后,我回复了相关的,也是可悲的被低估的评论(部分是因为我没有注意到Glomek的第一次,我认为):

  

一篇引人入胜的文章是Knuth从1974年开始的“结构化编程与去声明”(可在他的书“Literate Programming”中找到,也可能在其他地方也有)。除其他事项外,它还讨论了断开循环的受控方式,以及(不使用该术语)循环半语句。

     

Ada还提供循环结构,包括

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

原始问题的代码在意图中类似于此。

引用的SO项与此之间的一个区别是“最终中断”;这是一个单次循环,它使用break来提前退出循环。关于这是否也是一种好的风格一直存在疑问 - 我手头没有交叉参考。

答案 13 :(得分:1)

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

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

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

答案 14 :(得分:1)

完美的顾问答案:这取决于。大多数情况下,正确的做法是使用while循环

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

或“重复直到”,使用

以类似C语言的方式完成
do {
    // do something
} while ( condition is true);

如果其中任何一种情况有效,请使用它们。

有时,就像在服务器的内部循环中一样,你的意思是程序应该继续运行,直到外部中断它为止。 (考虑一下,例如,一个httpd守护进程 - 它不会停止,除非它崩溃或被关闭停止。)

然后只有使用一段时间(1):

while(1) {
   accept connection
   fork child process
}

最终案例是您在终止之前想要执行某些功能的罕见场合。在这种情况下,请使用:

while(1) { // or for(;;)
   // do some stuff
   if (condition met) break;
   // otherwise do more stuff.
}

答案 15 :(得分:1)

如果存在一个(且只有一个)非异常中断条件,则将该条件直接置于控制流构造(while)中是可取的。看到(真){...}让我作为一个代码阅读者认为没有简单的方法来列举休息条件并让我思考“仔细看看这个并仔细考虑休息条件(之前设置的内容)它们在当前循环中以及可能在前一循环中设置的内容)“

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

答案 16 :(得分:0)

这里有很多关于可读性的讨论,它的构造非常好,但是对于所有不固定大小的循环(即,同时和同时),你都有风险。

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

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

答案 17 :(得分:0)

你朋友的建议与你所做的不同。您自己的代码更类似于

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

总是至少运行一次循环,无论条件如何。

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

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

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

答案 18 :(得分:0)

我更喜欢while(!)方法,因为它更清楚,并且立即传达了循环的意图。

答案 19 :(得分:0)

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

  • 在不查找循环代码中断的情况下,更容易理解循环正在做什么。

  • 如果您不在循环代码中使用其他中断,则循环中只有一个退出点,而这是while()条件。

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

答案 20 :(得分:0)

使用

之类的循环

while(1){do stuff}

在某些情况下,

是必要的。如果您进行任何嵌入式系统编程(想想像PIC,MSP430和DSP编程这样的微控制器),那么几乎所有代码都将处于while(1)循环中。有时候编写DSP时只需要一段时间(1){},其余代码就是中断服务程序(ISR)。

答案 21 :(得分:0)

我更喜欢for循环:

for (;;)
{
    ...
}

甚至

for (init();;)
{
    ...
}

但如果init()函数看起来像循环体,那么

就更好了
do
{
    ...
} while(condition);

答案 22 :(得分:0)

他可能是对的。

功能上两者可以完全相同。

但是,对于可读性并了解程序流程,while(条件)更好。休息时间更多的是各种各样的。 while(条件)对于继续循环的条件非常清楚,等等。这并不意味着中断是错误的,只是可读性较差。