【问题标题】:Are "while(true)" loops so bad? [closed]“while(true)”循环这么糟糕吗? [关闭]
【发布时间】:2011-10-14 14:09:06
【问题描述】:

我已经使用 Java 编程好几年了,但我最近才回到学校获得正式学位。我很惊讶地发现,在我的上一个作业中,我因为使用​​下面这样的循环而丢了分。

do{
     //get some input.
     //if the input meets my conditions, break;
     //Otherwise ask again.
} while(true)

现在我的测试只是扫描一些控制台输入,但有人告诉我不鼓励这种循环,因为使用break 类似于goto,我们只是不这样做。

我完全理解 goto 和它的 Java 表亲 break:label 的缺陷,并且我有理由不使用它们。我也意识到一个更完整的程序会提供一些其他的逃避方式,例如结束程序,但这不是我的教授引用的原因,所以......

do-while(true) 有什么问题?

【问题讨论】:

  • 问问你的老师,那种东西很主观。
  • 我发现这篇文章有助于理解what's harmful about goto。与break 的比较可能是好意,但实际上被误解了。也许您可以就此对您的教授进行教育;)根据我的经验,教授对编程技巧知之甚少。
  • 我认为唯一真正无可争辩的坏事是,do {} while (true) 等同于 while(true) {},而后者是迄今为止更传统的形式,更清晰。跨度>
  • 如果有人不欣赏break 的简单表达能力,他们应该尝试用没有它的语言进行编程。在你想要它之前不需要太多循环!
  • 我不同意作业标签。

标签: java while-loop do-while


【解决方案1】:

我不会说它不好 - 但同样,我通常至少会寻找替代方案。

在我写的第一件事的情况下,我几乎总是至少尝试将它重构为更清晰的东西。有时它无济于事(或者替代方法是有一个 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 语句的争论完全相同。例如,如果我可以在前几行中计算出方法的结果(例如,因为某些输入为空、空或零),我发现直接返回该答案比使用变量来存储结果更清晰,然后是一整块其他代码,最后return 语句。

【讨论】:

  • 我同意这不是我使用的第一个工具,但它似乎可以如此干净地解决问题,而且我确实喜欢干净的代码。
  • @X-Zero:是的,有时。如果你能把“休息”变成“回归”,那通常是好事......虽然你最终可能还是会得到while (true)
  • @trutheality:我更喜欢我的大部分代码尽可能不缩进。复合赋值的条件对我来说感觉更复杂。
  • @Vince:是的,这很好如果您可以在循环开始时轻松表达条件。但有时你不能——这就是我们正在谈论的情况。请注意我回答的前几句话。
  • @Ismail:我希望不会。帖子应仅根据其内容来判断,而不是根据作者。哎呀,如果我觉得 Eric Lippert 的帖子不准确/没有帮助,我什至会投反对票。但这还没有发生:)
【解决方案2】:

您可以只使用一个布尔标志来指示何时结束 while 循环。 Breakgo to 是软件难以维护的原因 - 软件危机 (tm) - 应该避免,也可以轻松避免。

这是一个你是否务实的问题。务实的程序员可能只是在这种简单的情况下使用 break。

但最好养成不使用它们的习惯,否则您可能会在不合适的情况下使用它们,例如在复杂的嵌套循环中,使用break 会使代码的可读性和可维护性变得更加困难。

【讨论】:

  • 因为根据我的经验,保留布尔标志可能并不清晰,而且可能不太清晰?
  • @Jon Skeet 嗯,这是一个养成好习惯或通过使用 break 来训练自己养成坏习惯的问题。一个叫做“运行”的布尔你不清楚吗?它很明显,易于调试,并且正如我之前提到的,保持良好习惯是一件好事。问题出在哪里?
  • 一个名为 running 的布尔值,然后要求我在循环内有一个 if (running),当我想要退出循环时缩进所有其余代码绝对是 less i> 对我来说比简单的 break 语句清楚地说明 确切 我想要做什么。您似乎在考虑公理地将 break 用作一个坏习惯 - 我不这么认为。
  • 为什么循环中有一个 if(运行)?与循环结束时的 break 一样,您检查是否要中断并翻转标志而不是使用 break,并在 while(running) 中使用标志而不是 while(true)。我不明白你的意思,说真的。我同意务实的编码员可能会在某些情况下使用 break,但我真的不明白你在我的建议下所做的 cmets
  • 您假设在确定是否退出后,您不需要在循环中执行任何操作。例如,在 OP 的情况下,如果他不退出,他需要征求更多意见。
【解决方案3】:

这更像是一种美学,更容易阅读你明确知道为什么循环将在循环声明中停止的代码。

【讨论】:

    【解决方案4】:

    AFAIK 没什么,真的。老师们只是对goto 过敏,因为他们在某处听说这真的很糟糕。否则你只会写:

    bool guard = true;
    do
    {
       getInput();
       if (something)
         guard = false;
    } while (guard)
    

    这几乎是一样的。

    也许这样更干净(因为所有循环信息都包含在块的顶部):

    for (bool endLoop = false; !endLoop;)
    {
    
    }
    

    【讨论】:

    • 我喜欢你的建议,因为终止条件更加明显。这意味着在阅读代码时,您将很快了解它正在尝试做什么。我永远不会使用无限循环,而是经常使用你的两个版本。
    • 大家的共识是,flag 比 break 更好,主要是因为它表达了意图?我可以看到这是有益的。所有可靠的答案,但我会将其标记为已接受。
    • 这两者仍然受到循环的其余部分被污染(至少在某些情况下)的影响,因为必须一直走到循环体的末尾即使你认识你'正在打破。我会在我的回答中举个例子......
    • 我更喜欢 Jon Skeet 的回答。此外,while (true) {} 比 do {} while(true) 好得多
    • 我讨厌 Dijkstra 曾经写过那篇 GoTo 文章。虽然 GoTo 当然可以在过去经常被滥用,但这并不意味着你永远不应该使用它。此外,Exit For、Break、Exit While、Try/Catch 都是 Goto 的特殊形式。 Gotos 通常可以使代码更具可读性。是的,我知道没有 Goto,任何事情都可以完成。这并不意味着它应该这样做。
    【解决方案5】:

    这并不是一件可怕的事情,但是您在编码时需要考虑其他开发人员。即使在学校。

    您的开发人员应该能够在循环声明处看到您的循环的退出子句。你没有那样做。您将 exit 子句隐藏在循环的中间,为其他出现并试图理解您的代码的人做更多的工作。这与避免“中断”之类的事情的原因相同。

    话虽如此,您仍然会在现实世界的大量代码中看到类似的内容。

    【讨论】:

    • 如果循环以while (true) 开始,很明显它里面会有breakreturn,或者它会永远运行。直截了当很重要,但while(true) 本身并不是特别糟糕。在循环迭代中具有复杂不变量的变量将是导致更多焦虑的一个例子。
    • 尝试在搜索中深入三个级别,然后以某种方式爆发。如果你不只是从那个点返回,代码会变得更糟。
    【解决方案6】:

    根据我的经验,在大多数情况下循环都有“主要”条件才能继续。这是应该写入 while() 运算符本身的条件。所有其他可能打破循环的条件都是次要的,不是那么重要等。它们可以写成附加的if() {break} 语句。

    while(true) 经常令人困惑且可读性较差。

    我认为这些规则并没有涵盖 100% 的情况,但可能只涵盖了 98%。

    【讨论】:

    • 说得好。这就像使用 for 循环:while(true) {i++;...}。您将循环条件埋在循环内部而不是“签名”中。
    【解决方案7】:

    从某种意义上说,结构化编程结构优于(有些非结构化的)break 和 continue 语句,这很糟糕。相比之下,根据这一原则,它们更倾向于“转到”。

    我总是建议让您的代码尽可能结构化...不过,正如 Jon Skeet 所指出的,不要让它比这更结构化!

    【讨论】:

      【解决方案8】:

      读取输入的常用 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
      }
      

      如果您正在测试您的用户是否输入了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
      }
      

      因此,虽然在某些情况下breakgoto 是合理的,但如果您所做的只是逐行读取文件或控制台,那么您不需要while (true) 循环为了完成它——你的编程语言已经为你提供了一个适当的习惯用法,可以将输入命令用作循环条件。

      【讨论】:

      • 事实上,如果您使用while (true) 循环而不是这些常规输入循环之一,您可能会忘记检查文件的结尾。
      • 通过将循环的第一部分填充到while 条件中,您可以有效地完成此操作。在最终的 Java 条件下,它已经变得相当庞大,如果您必须进行大量操作来决定是否继续,它可能会变得很长。您可以将其拆分为一个单独的函数,这可能是最好的。但是,如果您确实希望在一个函数中使用它,并且在决定是否继续之前还有很多工作要做,那么while(true) 可能是最好的。
      • @poolie:那么最好使用 read 命令作为循环条件(检查 EOF),并检查循环内的其他条件作为 break 语句。
      • 对于它的价值,gets() 不是常见的约定。它非常有利于缓冲区溢出。 fgets(buffer, BUFFER_SIZE, file) 更像是标准做法。
      • @Dave:我现在编辑了答案以使用fgets
      【解决方案9】:

      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 循环。

      最后一部分:保持循环简单。如果每次迭代都需要执行很多功能,请将其放入函数中。您可以在它运行后随时对其进行优化。

      【讨论】:

      • +1:当涉及continue 语句时,for 循环还有其他复杂性,但它们不是主要扩展。
      • 我通常在有一个我想要运行的序列时使用 for 循环,当我想要满足一个条件时使用 while 循环。这使代码更清晰易读。
      • 没人再写for (;;) {了吗? (发音为“永远”)。这曾经很受欢迎。
      【解决方案10】:

      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);
      

      再次,通过一个简单的例子,两者都非常易读;但是如果循环变得非常大或嵌套很深(这意味着您可能应该已经重构),第一种样式可能会更清晰一些。

      【讨论】:

        【解决方案11】:

        虽然不一定是为什么不使用 while (true) 的答案,但我总是发现 this comic and accompanying author's statement 是一个简洁的解释,说明为什么要使用 while 而不是 do-while。

        关于你的问题:

        没有固有问题
        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 ); 都比在带有while(true) 的循环末尾设置if( condition ) { break; } 更清晰。 Breaks 是循环的短路——在循环中间用作短路时非常好,但你应该只评估 while 语句中的条件而不是在循环结束时有一个 break。
        • 关于那个漫画:while do-while 不能做 while 可以做的所有事情,do-while 有时会更好地做 do-while 可以做的事情,即使 while 也可以做。跨度>
        【解决方案12】:

        也许我不走运。或者我只是缺乏经验。但是每次我回想起处理while(true) 内部有break 时,都可以改进将Extract Method 应用于while-block 的代码,它保留了while(true) 但是(巧合? ) 将所有breaks 转换为returns。

        根据我的经验,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);
              }
          }
        

        【讨论】:

          【解决方案13】:

          我在很多函数中都使用了类似的东西,但逻辑相反。

          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 */
          }
          

          【讨论】:

            【解决方案14】:

            早在 1967 年,Edgar Dijkstra 在一本贸易杂志上写了一篇关于为什么应该从高级语言中删除 goto 以提高代码质量的文章。一个称为“结构化编程”的完整编程范式由此产生,但当然不是每个人都同意 goto 自动意味着糟糕的代码。

            结构化编程的关键本质上是代码的结构应该决定其流程,而不是在可能的情况下通过 goto 或中断或继续来决定流程。类似地,在该范例中也不鼓励对循环或函数具有多个入口和出口点。

            显然这不是唯一的编程范式,但它通常可以很容易地应用于其他范式,例如面向对象编程(ala Java)。

            您的老师可能已经被教导过,并且正在尝试通过确保我们的代码是结构化的并遵循结构化编程的隐含规则来告诉您的班级我们最好避免“意大利面条式代码”。

            虽然使用 break 的实现本身并没有什么“错误”,但有些人认为在 while() 条件中明确指定循环条件的代码更容易阅读,并消除了一些过于棘手的可能性.使用 while(true) 条件肯定有一些陷阱,这些条件似乎在新手程序员的代码中经常出现,例如意外创建无限循环的风险,或者使代码难以阅读或不必要地混淆。

            具有讽刺意味的是,异常处理是一个偏离结构化编程的领域,当您进一步使用 Java 编程时,这种情况肯定会出现。

            您的讲师也可能希望您展示您使用特定循环结构或语法的能力,该循环结构或语法在您的课文的该章或课程中教授,虽然您编写的代码在功能上是等效的,但您可能没有一直在展示你应该在那节课中学习的特定技能。

            【讨论】:

            【解决方案15】:

            这是你的枪,你的子弹和你的脚......

            这很糟糕,因为你在自找麻烦。不会是您或此页面上的任何其他发帖人提供短/简单 while 循环的示例。

            麻烦将在未来某个非常随机的时间开始。它可能是由另一个程序员引起的。可能是安装软件的人。可能是最终用户。

            为什么?我必须找出为什么一个 700K LOC 的应用程序会逐渐开始消耗 100% 的 CPU 时间,直到每个 CPU 都饱和。这是一个惊人的 while (true) 循环。它又大又讨厌,但归结为:

            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 获得关于防御性编程的评论。
            • 在您的示例中,代码愚蠢不是因为选择了 while(true),而是因为在代码周围放置循环的想法。
            • 确实,那个循环的意义何在? :)
            【解决方案16】:

            我猜对你的老师使用break就像折断树枝来得到果实,使用一些其他技巧(弯曲树枝)这样你得到果实并且树枝还活着。:)

            【讨论】:

              【解决方案17】:

              1) do -while(true) 没有任何问题

              2) 你的老师错了。

              NSFS!!:

              3) 大多数教师是教师而不是程序员。

              【讨论】:

              • @Connell 等你的老师听到!
              • 我是在编程方面自学的。我要评判的是我中学的 IT 老师,他教我们使用 Dreamweaver 制作网站,让一切都是绝对定位的 div...
              • @Connell 在我的帖子上显示你的同意
              • “那些不能做的,教。” - 这是一个相当大的概括,你不觉得吗?医生、工程师、飞行员等都应该自学吗,因为你认为他们的教练不称职?
              • @filip-fku wow wow~cool it cool it!
              【解决方案18】:

              我想说,一般来说,它不是一个好主意的原因是你没有充分利用这个构造。另外,我倾向于认为很多编程老师不喜欢他们的学生带着“行李”进来。我的意思是我认为他们喜欢成为影响学生编程风格的主要因素。所以也许这只是教练的小毛病。

              【讨论】:

                【解决方案19】:

                如果您的循环在后台线程上运行可能会很糟糕,因此当您通过终止 UI 线程来关闭应用程序时,该段代码将继续执行。正如其他人已经说过的,您应该始终使用某种支票来提供取消的方式。

                【讨论】:

                  【解决方案20】:

                  对我来说,问题在于可读性。

                  条件为真的 while 语句不会告诉您有关循环的任何信息。它使理解它的工作变得更加困难。

                  从这两个 sn-ps 中什么更容易理解?

                  do {
                    // Imagine a nice chunk of code here
                  } while(true);
                  
                  do {
                    // Imagine a nice chunk of code here
                  } while(price < priceAllowedForDiscount);
                  

                  【讨论】:

                    【解决方案21】:

                    我认为是的,这很糟糕......或者至少对许多开发人员来说。这是不考虑循环条件的开发人员的症状。因此容易出错。

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 2018-08-06
                      • 2015-07-23
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2016-07-27
                      • 1970-01-01
                      • 2016-12-17
                      相关资源
                      最近更新 更多