it-swarm.cn

“ break”和“ continue”是否是不好的编程习惯?

我的老板总是不停地提到糟糕的程序员在循环中使用breakcontinue

我一直在使用它们,因为它们很有意义。让我向您展示灵感:

function verify(object) {
    if (object->value < 0) return false;
    if (object->value > object->max_value) return false;
    if (object->name == "") return false;
    ...
}

这里的重点是,首先该功能检查条件是否正确,然后执行实际功能。 IMO同样适用于循环:

while (primary_condition) {
    if (loop_count > 1000) break;
    if (time_exect > 3600) break;
    if (this->data == "undefined") continue;
    if (this->skip == true) continue;
    ...
}

我认为这使阅读和调试更加容易。但我也看不出任何缺点。

206
Mikhail

当在块的开始使用时,作为第一次检查,它们就像先决条件一样工作,因此很好。

在块的中间使用时,周围有一些代码,它们的作用就像隐藏的陷阱,因此很不好。

253
Klaim

您可以阅读Donald Knuth在1974年发表的论文 带有转到语句的结构化编程 ,其中他讨论了go to在结构上是合乎需要的。它们包括breakcontinue语句的等效项(其中go to的许多用法已开发成更有限的构造)。您的老板是否会骂Knuth为不良程序员?

(这些示例引起了我的兴趣。通常,喜欢任何代码的一个入口和一个出口的人都不喜欢breakcontinue,并且这种人也对多个return语句。)

90
David Thornley

我不认为他们是坏人。它们不好的想法来自结构化编程的时代。与以下概念有关:函数必须具有单个入口点和单个出口点,即。 e。每个功能只有一个return

如果您的函数很长,并且有多个嵌套循环,那么这是有道理的。但是,您的函数应该短一些,并且应该将循环及其主体包装成它们自己的短函数。通常,强制一个函数具有单个出口点可能会导致逻辑混乱。

如果函数很短,如果有一个循环,或者最糟糕的是有两个嵌套循环,并且如果循环体很短,那么很清楚breakcontinue的作用。还很清楚多个return语句做什么。

这些问题在Robert C. Martin的“清洁代码”和Martin Fowler的“重构”中得到了解决。

46
Dima

糟糕的程序员讲的是绝对的(就像Sith一样)。好的程序员应该使用最清晰的解决方案(在所有其他条件相同的情况下)。

频繁使用Break and Continue会使代码难以遵循。但是,如果替换它们使代码更难遵循,那么那将是一个糟糕的更改。

您所举的示例肯定是一种情况,其中断断续续应替换为更优雅的内容。

44
Matthew Read

大多数人认为这是个坏主意,因为这种行为不容易预测。如果您正在阅读代码,并且看到while(x < 1000){},则假定它将一直运行到x> = 1000 ...但是,如果中间有中断,则表示此情况不成立,因此你真的不能相信你的循环...

这也是人们不喜欢GOTO的原因:当然,它can可以很好地使用,但是它也可能导致令人讨厌的意大利面条代码,该代码在各个部分之间随机跳转。

就我自己而言,如果我要执行一个在多个条件下中断的循环,我会执行while(x){}然后在需要中断时将X切换为false。最终结果将是相同的,并且阅读该代码的任何人都将更仔细地研究切换X值的事物。

25
Satanicpuppy

是的,您可以[重新]编写没有break语句的程序(或从循环中间执行相同操作的返回值)。但是您可能必须引入其他变量和/或代码重复,这通常会使程序更难以理解。因此,Pascal(编程语言)非常糟糕,特别是对于初学者来说。您的老板基本上希望您使用Pascal的控制结构进行编程。如果Linus Torvalds穿着您的鞋子,他可能会向您的老板展示中指!

有一个计算机科学结果叫做Kosaraju的控制结构层次结构,该结果可以追溯到1973年,并在1974年Knuth撰写的有关goto的著名论文中得到提及。 。)S. Rao Kosaraju在1973年证明的是,不可能将具有多级深度中断n的所有程序重写为具有break的程序深度小于n而不引入额外的变量。但是,可以说这仅仅是一个理论上的结果。 (只需添加一些额外的变量?!当然,您可以这样做来取悦您的老板...)

从软件工程的角度来看,更重要的是埃里克·罗伯茨(Eric S.Roberts)在1995年发表的一篇题​​为的论文《循环退出与结构化编程:重新开放辩论》http://cs.stanford.edu/people/eroberts/papers/SIGCSE-1995/LoopExits.pdf )。罗伯茨总结了他之前其他人进行的一些实证研究。例如,当一组CS101型学生被要求为实现在数组中顺序搜索的函数编写代码时,研究的作者对使用中断/返回/跳转来退出找到元素时的顺序搜索循环:

我还没有找到一个使用[此样式]尝试编写程序的人,但该人提供了错误的解决方案。

罗伯茨还说:

尝试不使用for循环的显式返回来解决问题的学生的情况要差得多:在尝试该策略的42名学生中,只有7名能够设法产生正确的解决方案。该数字表示成功率不到20%。

是的,您可能比CS101的学生更有经验,但是如果没有使用break语句(或等效地从循环中间返回/转到),最终您将编写代码,尽管结构合理,但在额外逻辑方面足够多变量和代码重复,可能有人会在尝试遵循老板的编码风格时在其中放入逻辑错误。

我还要在这里说罗伯茨的论文对普通程序员而言更加容易理解,因此比Knuth的论文更好地初读。它也更短,涵盖了一个狭窄的主题。您甚至可以向老板推荐它,即使他是管理层而不是CS类型。

16
Fizz

我不考虑使用这两种不良做法中的任何一种,但是在同一个循环中使用过多它们应该重新考虑在循环中使用的逻辑。谨慎使用它们。

9
Bernard

您给出的示例不需要中断也可以继续:

while (primary-condition AND
       loop-count <= 1000 AND
       time-exec <= 3600) {
   when (data != "undefined" AND
           NOT skip)
      do-something-useful;
   }

我对您的示例中的4行有一个“问题”,即它们全都处于同一水平,但是它们却做不同的事情:有些中断,有些继续...您必须阅读每一行。

在我的嵌套方法中,您越深入,代码就越“有用”。

但是,如果深入您发现停止循环的原因(除了主要条件),则可以使用中断或返回。与使用在顶级条件下进行测试的额外标志相比,我更愿意这样做。中断/返回更直接;它比设置另一个变量更好地说明了意图。

7
Peter Frings

“坏处”取决于您如何使用它们。我通常仅在循环构造中使用中断,这会节省我无法通过算法重构保存的周期。例如,循环遍历一个集合以查找具有特定属性值设置为true的项目。如果您只需要知道其中一个项目将此属性设置为true,那么一旦达到该结果,就可以适当地中断以终止循环。

如果使用中断不会使代码特别易于阅读,缩短运行时间或以显着方式节省处理周期,那么最好不要使用它们。我倾向于尽可能地使用“最低公分母”进行编码,以确保跟随我的任何人都可以轻松查看我的代码并弄清楚发生了什么(我并不总是成功的)。休息减少了,因为它们确实引入了奇数的进/出点。被误认为他们的行为很像一个过时的“ goto”声明。

6
Joel Etherton

绝对不是...是的,使用goto是不好的,因为它会破坏程序的结构,并且也很难理解控制流程。

但是,如今绝对需要使用诸如breakcontinue之类的语句,并且根本不认为这是不好的编程习惯。

同样不难理解使用breakcontinue的控制流程。在switch这样的结构中,绝对需要break语句。

4
Radheshyam Nayak

我将用第二个代码段替换

while (primary_condition && (loop_count <= 1000 && time_exect <= 3600)) {
    if (this->data != "undefined" && this->skip != true) {
        ..
    }
}

并非出于任何简洁的原因-实际上,我认为这更容易阅读,也可以让别人了解正在发生的事情。一般而言,循环的条件应完全包含在循环的条件下,而不是随处可见。但是,在某些情况下breakcontinue可以提高可读性。 breakcontinue还要多:D

3
El Ronnoco

基本概念来自能够对程序进行语义分析。如果您只有一个入口和一个出口,则表示可能的状态所需的数学运算比必须管理分支路径的运算要容易得多。

在某种程度上,这种困难反映出了能够在概念上对您的代码进行推理。

坦白说,您的第二个代码并不明显。到底在做什么是继续“继续”,还是“下一步”循环?我不知道。至少您的第一个例子很清楚。

3
Paul Nathan

我同意你的老板。它们之所以不好是因为它们会产生高度复杂的方法。这种方法难以阅读且难以测试。幸运的是,有一个简单的解决方案。将循环体提取到单独的方法中,其中“继续”变为“返回”。 “返回”更好,因为“返回”结束之后-不必担心本地状态。

对于“ break”,将循环本身提取到单独的方法中,将“ break”替换为“ return”。

如果提取的方法需要大量参数,则表明要提取一个类-将它们收集到上下文对象中。

2
kevin cline

我不同意你的老板。有适当的位置可以使用breakcontinue。实际上,将理解和异常处理引入现代编程语言的原因是,您不能仅使用structured techniques解决所有问题。

附带说明我不想在这里进行宗教性的讨论,但是您可以像下面这样重新构建代码,使其更具可读性:

while (primary_condition) {
    if (loop_count > 1000) || (time_exect > 3600) {
        break;
    } else if ( ( this->data != "undefined") && ( !this->skip ) ) {
       ... // where the real work of the loop happens
    }
}

另一方面

我个人不喜欢在条件条件中使用( flag == true ),因为如果变量已经是布尔值,那么您将引入一个额外的比较,当布尔值具有所需的答案时就需要进行比较-除非您当然是确定您的编译器将优化额外的比较。

2
Zeke Hansell

我原则上不反对continuebreak,但我认为它们是非常底层的构造,通常可以用更好的东西代替。

我在这里以C#为例,考虑要遍历集合的情况,但是我们只希望满足某些谓词的元素,并且我们最多只希望进行100次迭代。

for (var i = 0; i < collection.Count; i++)
{
    if (!Predicate(item)) continue;
    if (i >= 100) break; // at first I used a > here which is a bug. another good thing about the more declarative style!

    DoStuff(item);
}

这看起来很干净。这不是很难理解。我认为从声明性的角度来看会有所收获。将其与以下内容进行比较:

foreach (var item in collection.Where(Predicate).Take(100))
    DoStuff(item);

也许Where和Take调用甚至都不应该使用此方法。也许应该在将集合传递给此方法之前完成此过滤。无论如何,通过远离低级内容,而将更多的精力放在实际的业务逻辑上,可以更清楚地了解我们真正感兴趣的内容。将我们的代码分离为更能遵循良好设计规范的内聚模块变得更加容易,因此上。

低级内容仍将存在于代码的某些部分中,但是我们希望尽可能地将其隐藏起来,因为这需要花大量的精力来代替我们思考业务问题。

0
sara

我认为这是一个嵌套在多个循环中的唯一问题。很难知道您要进入哪个循环。也可能很难遵循延续,但是我认为真正的痛苦来自于中断-逻辑可能难以遵循。

0
davidhaskins

我不喜欢这两种风格。这是我希望的:

function verify(object)
{
    if not (object->value < 0) 
       and not(object->value > object->max_value)
       and not(object->name == "") 
       {
         do somethign important
       }
    else return false; //probably not necessary since this function doesn't even seem to be defined to return anything...?
}

我真的不喜欢使用return中止一个函数。感觉像是滥用return

使用break也不总是很容易阅读。

更好的可能是:

notdone := primarycondition    
while (notDone)
{
    if (loop_count > 1000) or (time_exect > 3600)
    {
       notDone := false; 
    }
    else
    { 
        skipCurrentIteration := (this->data == "undefined") or (this->skip == true) 

        if not skipCurrentIteration
        {
           do something
        } 
        ...
    }
}

嵌套更少,复杂的条件重构为变量(在实际程序中,您必须具有更好的名称,显然...)

(以上所有代码均为伪代码)

0

这是解决问题的一种方法,还有其他解决方法。

当前许多主流语言(Java,.NET(C#+ VB),PHP,编写自己的语言)都使用“ break”和“ continue”跳过循环。他们都“结构化goto”句子。

没有他们:

String myKey = "mars";

int i = 0; bool found = false;
while ((i < MyList.Count) && (not found)) {
  found = (MyList[i].key == myKey);
  i++;   
}
if (found)
  ShowMessage("Key is " + i.toString());
else
  ShowMessage("Not found.");

跟他们:

String myKey = "mars";

for (i = 0; i < MyList.Count; i++) {
  if (MyList[i].key == myKey)
    break;
}
ShowMessage("Key is " + i.toString());

请注意,“ break”和“ continue”代码较短,通常将“ while”语句转换为“ for”或“ foreach”语句。

两种情况都与编码风格有关。 我不想使用它们,因为冗长的样式使我可以更好地控制代码。

实际上,我在某些项目中工作,在这些项目中必须使用这些句子。

一些开发人员可能认为它们不是必需的,但是假设,如果我们必须删除它们,我们也必须删除“ while”和“ do while”(“重复直到”,你是Pascal伙计们);-)

结论,即使我不想使用它们,我也认为它是一种选择,而不是不好的编程习惯。

0
umlcat

只要它们不像下面的示例那样用作伪装的goto:

do
{
      if (foo)
      {
             /*** code ***/
             break;
      }

      if (bar)
      {
             /*** code ***/
             break;
      }
} while (0);

我对他们很好。 (示例在生产代码中显示)

0
SuperBloup