在for循环内,如果可能,是否应该将中断条件移到条件字段中?[关闭]


48

有时我需要循环,需要这样的中断:

for(int i=0;i<array.length;i++){
    //some other code
    if(condition){
        break;
    }
}

我对写作感到不舒服

if(condition){
    break;
}

因为它消耗3行代码。我发现循环可以重写为:

                                   ↓
for(int i=0;i<array.length && !condition;i++){
    //some other code
}

所以我的问题是,如果可能的话,将条件移至条件字段以减少代码行是否是一种好习惯?


7
具体取决于什么condition
Bergi

4
misra为何在循环中禁止使用“ break”和“ continue”是有原因的。另见本:stackoverflow.com/q/3922599/476681
BЈовић

35
我认为行数本身不是真正的问题。它可以写在一行上。如果(条件)中断;我认为问题是可读性。我个人不喜欢破坏循环而不是使用循环条件。

97
因为它消耗3行代码, 这是非常非常非常讨厌不喜欢任何样式的原因。IMO的“消费代码行”介于无关紧要的事物与美好的事物之间。试图将太多的逻辑填充到太少的行中会导致无法读取的代码和难以发现的错误。
安德鲁·亨利

3
可能不受欢迎的意见:for(int i=0;i<array.length && !condition;i++)相对不常见,只是略读代码的人可能会忽略它;对于whileor do while循环,这可能是一个很好的用例,在or 循环中,循环经常有多个中断条件。
Jules

Answers:


154

您提供的这两个示例在功能上并不等效。在原始版本中,条件测试是在“其他代码”部分之后进行的,而在修改版本中,条件测试是在循环体的开头进行的。

绝不能仅出于减少行数的目的而重写代码。显然,以这种方式工作时,这是一个不错的好处,但是绝对不要以可读性或正确性为代价。


5
我理解您的观点,但我维护了足够的旧代码,其中循环长达数百行,并且有多个条件中断。当您期望代码达到您期望的条件中断时,调试变得非常困难,但是先前的代码首先执行了条件中断。您如何处理该代码?在上述类似的简短示例中,很容易忘记这种太常见的情况。
Berin Loritsch '18

84
@BerinLoritsch:处理该问题的正确方法是不要有几百行长的循环!
克里斯(Chris)

27
@Walfrat:如果不能重写它,那么实际上就不再有问题了。话虽这么说,您通常可以很安全地使用“提取方法”类型的重构来移动代码块,从而使您的主方法循环更短,并希望使逻辑流程更明显。确实是个案。我会坚持我的一般原则,即保持方法简短,绝对要使下一个循环逻辑保持简短,但我不想在一般意义上说些什么……
克里斯

4
@AndrewHenle简洁是可读性,至少在某种意义上说,增加冗长性总是会降低可读性和可维护性。降低LOC是通过增加密度和提高抽象级别来实现的,并且与该站点上其他Q / A对中证明的可维护性紧密相关。
Leushenko '18

5
@Joe Do-While构造非常罕见,以至于容易让需要稍后维护该代码的开发人员绊倒。一些开发人员实际上从未见过他们!我不确定一次执行是否可以提高可读性-如果有的话,会增加理解代码的难度。举个例子-我当前的代码库已有15年的历史,并且拥有大约200万个LOC。已经有大约五十名开发人员接触过它。它恰好有零个do-while循环!
T. Sar-恢复莫妮卡

51

我不赞成“它消耗3行代码”这样的说法是不好的。毕竟,您可以将其编写为:

if (condition) break;

只消耗一行

如果if出现在循环的一半,那么它当然必须作为单独的测试存在。但是,如果此测试出现在块的末尾,则说明您创建了一个循环,该循环在循环的两端均具有继续测试,这可能会增加代码的复杂性。但是请注意,有时if (condition)只有在执行该块之后,才可以进行测试。

但是假设情况并非如此,可以采用其他方法:

for(int i=0;i<array.length && !condition;i++){
    //some other code
}

退出条件保持在一起,可以简化操作(特别是如果“ 其他一些代码 ”很长的话)。

当然,这里没有正确的答案。因此,请注意该语言的习惯用法,您团队的编码约定等。但是总的来说,当在功能上可行时,我会采用单一测试方法。


14
@ jpmc26,更好吗?否。有效的替代样式?当然。
David Arno

6
更好,因为以后编辑代码时更难弄乱,而且没有任何明显的缺点。
jpmc26

4
由于“循环内容可能很长”而将它们保持在一起似乎是一个较弱的论点。如果您的内容难以阅读,那么除了保存两行代码外,您还有其他问题。
BlueWizard

12
@ jpmc26不同意。根据此答案,在最短的单线条件下,括号是多余的杂物。没有,则更优雅,更轻松。他们就像一个三元条件运算符。我从未忘记在将大括号分成更大的语句块时添加大括号。毕竟我已经添加了换行符。
benxyzzy

3
所有这些都是样式偏好,但是如果有人认为它更安全,那么我将不同意这种推理。我本人更喜欢不使用大括号,而在下一行,但这并不意味着它比其他样式或多或少有效
phflack

49

这种问题几乎一直在引发编程之初就引发了争论。把我的帽子成环,我会去用的版本break条件和这里的原因(这种特定的情况下):

for环就是那里的循环来指定迭代值。在其中编码中断条件只会使IMO的逻辑混乱。

有了单独的分隔break符,可以很清楚地知道,在正常处理期间,这些值与for循环中指定的一样,并且应该继续进行处理,除非出现条件。

作为背景知识,我开始编写a break,然后将条件放入for循环中多年(在一位杰出的程序员的指导下),然后又返回到该break样式,因为它感觉更简洁了(这是该样式中的主流样式)。我正在使用的各种代码主题)。

这是一天结束时的另一场圣战-有人说你永远不要人为地打破循环,否则这不是真正的循环。做任何您认为容易理解的事情(包括您自己和他人)。如果真的想减少击键,那么在周末去打高尔夫球吧-可选啤酒。


3
如果条件有一个好名字,例如“ found”(例如,您正在数组中查找某物),则将其放在for循环的开头是个好主意。
user949300 '18

1
有人告诉我,for当知道迭代次数时(例如,当数组/列表的末尾没有中断条件时),而在迭代次数未知时,则使用循环whilewhile循环似乎是这种情况。如果关注可读性,则idx+=1循环内的内容比if/else块更容易阅读
Laiv

您不能只是将中断条件放在循环中(如果后面有代码),因此至少您必须编写“ if(condition){found = true;
Continue

我们对for(int i=0; i<something;i++)循环是如此的习惯,以至于我猜有一半的开发人员并不真正记得for循环可以以不同的方式使用,因此很难理解这一&&condition部分。
拉尔夫·克莱伯霍夫

@RalfKleberhoff确实。我看到许多新开发人员对三方语句表达式都是可选的表示惊讶。我什至不记得我上次见到的for(;;)东西了
Robbie Dee

43

for循环是迭代某件事1 -他们不只是笑,let's-拉一些,随机的东西,来自该体和放了他们,在半实物头-这三个词语具有非常具体含义:

  • 第一个是初始化迭代器。它可以是索引,指针,迭代对象或其他任何对象-只要用于迭代
  • 第二个是检查我们是否到达终点。
  • 第三是用于推进迭代器。

这个想法是将迭代处理(如何迭代)与循环内的逻辑(每次迭代做什么)分开。许多语言通常都有一个for-each循环,可以使您免于迭代的细节,但是即使您的语言没有该构造,或者即使您的语言无法使用该构造,也应限制循环头迭代处理。

因此,您需要问自己-有关迭代处理或循环内逻辑的条件是?很有可能,这与循环内的逻辑有关-因此应在循环内而不是在标头中对其进行检查。

1与其他循环相反,这些循环“等待”某些事情发生。一for/ foreach循环应该有一个项目的“清单”,反覆的概念-即使该列表是懒惰的或无限的或抽象的。


5
这是我阅读问题时的第一个想法。失望的是,我不得不滚动浏览六个答案才能看到有人说出来
Nemo

4
嗯... 所有循环都是迭代-这就是“迭代”一词的意思:-)。我假设您的意思是“迭代集合”或“使用迭代器迭代”?
psmears

3
@psmears好吧,也许我选错了单词-英语不是我的母语,当我想到迭代时,我想到的是“对某事的迭代”。我会解决问题的答案。
伊丹·阿里

一个中间情况是条件类似someFunction(array[i])。现在,条件的两个部分都与您要遍历的事情有关,将它们与&&循环头合并在一起将很有意义。
Barmar

我尊重您的立场,但不同意。我认为for本质上的循环是计数器,而不是列表的循环,这for在早期语言的语法中得到了支持(这些语言通常具有for i = 1 to 10 step 2语法和step命令,甚至允许初始值不是第一个的索引)元素-暗示数字上下文,而不是列表上下文)。我认为支持循环的唯一用例是for仅对列表进行迭代,这是并行化,并且无论如何现在都需要显式语法以免破坏代码,例如排序。
Logan Pickup

8

我建议将版本与一起使用if (condition)。它更具可读性,更易于调试。编写3行额外的代码不会打断您的手指,但会让下一个人更容易理解您的代码。


1
仅当(如已注释)循环块没有数百行代码并且其中的逻辑不是火箭科学。我认为您的论点还可以,但是我想念类似的东西是naming things为了了解发生了什么以及何时满足中断条件。
Laiv

2
@Laiv如果循环块中包含数百行代码,则问题要比在哪里写中断条件的问题更大。
Aleksander

这就是为什么我建议将答案放在上下文中,因为并非总是如此。
Laiv

8

如果要遍历应跳过某些元素的集合,则建议使用新选项:

  • 迭代之前过滤您的收藏集

Java和C#都使这项工作相对琐碎。如果C ++有一种方法可以使花哨的迭代器跳过某些不适用的元素,我不会感到惊讶。但这只是在您选择的语言支持的情况下才是真正的选择,而使用的原因break是因为是否处理某个元素存在一定的条件。

否则,有充分的理由使您的条件成为for循环的一部分-假设它的初始评估是正确的。

  • 更容易看到循环何时提前结束

我已经编写了代码,其中您的for循环需要几百行,其中包含多个条件和一些复杂的数学运算。您遇到的问题是:

  • break 逻辑中隐藏的命令很难找到,有时令人惊讶
  • 调试需要逐步遍历循环的每一步,以真正了解正在发生的事情
  • 许多代码没有可轻松实现的可重构,也没有单元测试来帮助重写后的功能等同

在这种情况下,我会按照优先顺序推荐以下准则:

  • 过滤集合以消除break可能的需要
  • 使条件部分成为for循环条件
  • break尽可能将条件句放在循环的顶部
  • 添加多行注释以引起对中断及其原因的关注

这些准则始终受您代码正确性的约束。选择最能代表其意图并提高代码清晰度的选项。


1
这个答案大部分都很好。但是...我可以看到过滤集合可以消除对continue语句的需求,但是我看不出它们对的帮助如何break
user949300 '18

1
@ user949300 takeWhile过滤器可以替换break语句。如果你有一个-的Java例如只让他们在java的9(不是它的很难实现它自己)
伊詹阿里耶

1
但是在Java中,您仍然可以在迭代之前进行过滤。使用流(1.8或更高版本)或使用Apache Collections(1.7或更早版本)。
Laiv

@IdanArye-有一些将这些功能添加到Java流API的附加库(例如JOO-lambda
Jules,

@IdanArye在您发表评论之前从未听说过takeWhile。明确地将过滤器链接到命令令我感到异常和棘手,因为在“正常”过滤器中,每个元素都会返回true或false,而与之前或之后的情况无关。这样,您可以并行运行事物。
user949300 '18

8

我强烈建议您采取最少惊讶的方法,除非您在其他方面获得了重大收益。

人们在阅读单词时不会阅读每个字母,在阅读书时不会阅读每个单词-如果他们擅长阅读,他们会查看单词和句子的轮廓,并让大脑充满其余的内容。

因此,偶然的开发人员可能会认为这只是循环的标准,甚至不会去看:

for(int i=0;i<array.length&&!condition;i++)

如果无论如何都要使用该样式,我建议您更改零件,for(int i=0;i<;i++)告诉读者这是循环的标准。


if- 配合使用的另一个原因break是,您不能始终将for-condition 与您的方法配合使用。您必须使用if- break当中断条件太复杂而无法隐藏在一条for语句中时,或者依赖于只能在for循环内部访问的变量。

for(int i=0;i<array.length&&!((someWidth.X==17 && y < someWidth.x/2) || (y == someWidth.x/2 && someWidth.x == 18);i++)

因此,如果您决定使用if- break,则可以满足要求。但是,如果您决定使用for-condition,则必须同时使用if- breakfor-condition。为保持一致,只要条件发生变化,就必须for-condition和if- 之间来回移动break条件。


4

您的转换假设进入循环时condition对结果的求值true。在这种情况下,我们无法对此进行评论,因为它尚未定义。

在这里成功完成“减少代码行”操作后,您可以继续研究

for(int i=0;i<array.length;i++){
    // some other code
    if(condition){
        break;
    }
    // further code
}

并应用相同的变换,得出

for(int i=0;i<array.length && !condition;i++){
    // some other code
    // further code
}

这是无效的转换,因为您现在无条件执行further code以前没有的操作。

一种更安全的转换方案是提取some other code并评估condition一个单独的函数

bool evaluate_condition(int i) // This is an horrible name, but I have no context to improve it
{
     // some other code
     return condition;
}

for(int i=0;i<array.length && !evaluate_condition(i);i++){
    // further code
}

4

TL; DR在您的特定情况下尽一切可能做得最好

让我们来看这个例子:

for ( int i = 1; i < array.length; i++ ) 
{
    if(something) break;
    // perform operations
}

在这种情况下,如果something真为真,我们不希望for循环中的任何代码执行,因此将test something的条件移入条件字段是合理的。

for ( int i = 1; i < array.length && !something; i++ )

再根据是否something可以将其设置为true循环之前(而不是循环内部),这可能会更加清楚:

if(!something)
{
    for ( int i = 1; i < array.length; i++ )
    {...}
}

现在想象一下:

for ( int i = 1; i < array.length; i++ ) 
{
    // perform operations
    if(something) break;
    // perform more operations
}

我们在那里发送了一个非常明确的消息。如果something在处理数组时变为true,则此时放弃整个操作。为了将检查移至条件字段,您需要执行以下操作:

for ( int i = 1; i < array.length && !something; i++ ) 
{
    // perform operations
    if(!something)
    {
        // perform more operations
    }
}

可以说,“消息”变得混乱了,我们正在两个地方检查故障情况-如果故障情况发生变化而我们忘记了那些地方之一怎么办?

当然,for循环和条件还有更多不同的形状,它们都有自己的细微差别。

编写代码,使其读起来很简洁(这显然是主观的)。在一套严苛的编码准则之外,所有 “规则”都有例外。写出您认为最能代表决策的内容,并最大程度地减少将来犯错误的机会。


2

虽然它很吸引人,因为您在循环头中看到了所有条件,并且break可能在大型块中造成混淆,但for循环是大多数程序员期望在一定范围内循环的一种模式。

尤其是由于这样做,添加额外的中断条件可能会引起混乱,并且很难看出它在功能上是否等效。

通常,一个好选择是使用whileor do {} while循环检查两个条件,假设您的数组至少有一个元素。

int i=0
do {
    // some other code
    i++; 
} while(i < array.length && condition);

使用仅检查条件而不从循环头运行代码的循环,您可以很清楚地知道何时检查条件,什么是实际条件以及有副作用的代码。


尽管这个问题已经有了一些好的答案,但我还是要特别赞扬它,因为它提出了一个重要的观点:对我来说,在for循环(for(...; i <= n; ...))中进行简单的绑定检查之外的任何事情实际上会使代码更难阅读,因为我拥有停下来想一想这里发生了什么,并且可能在第一次通过时就完全错过了这个条件,因为我不希望它在那里。对我而言,for循环意味着您正在某个范围内循环,如果存在特殊的退出条件,则应if使用或明确将其退出,也可以使用while
CompuChip

1

这很糟糕,因为代码是供人类阅读的-非惯用语for循环常常使人难以理解,这意味着这些错误更有可能隐藏在此处,但是原始代码可能会得到改进,同时保留两者简短易读。

如果要在数组中查找值(提供的代码示例有些通用,但也可能是这种模式),则可以明确地尝试使用编程语言为此目的提供的功能。例如(伪代码,因为未指定编程语言,所以解决方案取决于特定语言)。

array.find(item -> item.valid)

这将明显缩短解决方案,同时简化您的代码,因为代码明确说明了您的需求。


1

我认为没有理由说您不应该这样做。

  1. 这降低了可读性。
  2. 输出可能不相同。但是,这可以通过do...while循环(而不是while)来完成,因为在执行某些代码后会检查条件。

但最重要的是,请考虑一下,

for(int i=0;i<array.length;i++){

    // Some other code
    if(condition1){
        break;
    }

    // Some more code
    if(condition1){
        break;
    }

    // Some even more code
}

现在,您可以通过将这些条件添加到该for条件中来真正实现它吗?


什么是“ 开放 ”?你是说“ 意见 ”吗?
彼得·莫滕森

什么是你的意思“在openion,说,你不应该几个原因”?你能详细说明吗?
彼得·莫滕森

0

我认为删除break可以是一个好主意,但不能节省代码行。

不编写它的好处break是循环的后置条件是显而易见的。当您完成循环并执行程序的下一行时,循环条件i < array.length && !condition必须为false。因此,要么i大于或等于您的数组长度,要么condition为true。

在的版本中break,有一个隐藏的陷阱。循环条件表明,当您为每个有效的数组索引运行了一些其他代码时,循环终止,但是实际上有一条break语句可以在此之前终止循环,除非您查看循环代码,否则您将看不到该语句。非常小心。自动化的静态分析器(包括优化编译器)也可能难以推断出循环的后置条件。

这是Edgar Dijkstra撰写“ Goto被认为有害”的最初原因。这不是风格问题:中断循环使得很难正式地就程序状态进行推理。break;我一生中写了很多声明,成年后甚至写了一篇goto(打破对性能至关重要的内部循环的多个层次,然后继续搜索树的另一个分支)。但是,我比a更有可能return从循环中编写条件语句(该条件语句从不在循环后运行,因此无法将其后置条件弄糟)break

后记

实际上,重构代码以提供相同的行为实际上可能需要更多的代码行。您的重构可能对某些其他特定代码有效,或者如果我们可以证明condition从错误开始。但是您的示例在一般情况下等效于:

if ( array.length > 0 ) {
  // Some other code.
}
for ( int i = 1; i < array.length && !condition; i++ ) {
  // Some other code.
}

如果其他代码不是单行代码,则可以将其移至某个函数以免重复自己,然后您几乎将循环重构为尾递归函数。

不过,我知道这不是您提出问题的重点,并且您一直在保持简单。


问题不是中断使代码争论不休,而是中断在随机位置使代码变得复杂。如果确实需要这样做,那么就不必因为中断而争论不休了,而是因为代码需要做一些复杂的事情。
gnasher729

这就是我不同意的原因:如果您知道没有break语句,那么您知道循环条件是什么,并且当条件为假时循环就会停止。如果有break?然后,有多种方法可以终止循环,在通常情况下,弄清其中一种何时可以终止循环实际上是在解决“停机问题”。实际上,我更大的担心是循环看起来像是在做一件事,但是实际上,在循环之后编写代码的任何人都忽略了掩埋在复杂逻辑中的边缘情况。循环条件中的东西很难错过。
戴维斯洛

0

是的,但是您的语法是非常规的,因此很差(tm)

希望您的语言支持类似

while(condition) //condition being a condition which if true you want to continue looping
{
    do thing; //your conditionally executed code goes here
    i++; //you can use a counter if you want to reference array items in order of index
}

2
也许我应该为“条件”使用一个不同的词,但我并不是说这个例子中的条件完全相同
Ewan

1
for循环不仅在远程上不是非常规的,而且绝对没有理由这种形式的while循环比等效的for循环要好。实际上,大多数人会说情况更糟,例如CPP核心指南
Sean Burton,

2
有些人讨厌创新的选择。或者以一种不同于他们头脑中的方式看问题。或更笼统地说,是聪明的驴子。我感觉你很痛苦!
马丁·马特

1
@sean对不起老兄,我知道人们讨厌被告知他们错了,但我还是将您引荐给es.77
Ewan,

2
while强调了代码中的怪物-否则隐藏在例程/普通代码中的异常。业务逻辑位于前台和中心,包括循环机制。它避免了对for循环替代方案的“等待片刻...”反应。此答案解决了该问题。绝对投票。
Radarbob

0

如果您担心过于复杂的for语句难以阅读,那绝对是一个有效的问题。浪费垂直空间也是一个问题(尽管不太重要)。

(个人而言,我不喜欢if语句必须始终带有花括号的规则,这在很大程度上浪费了垂直空间。请注意,问题是在浪费垂直空间。使用具有有意义的行的垂直空间应该不是问题。)

一种选择是将其拆分为多行。如果您使用的是for带有多个变量和条件的非常复杂的语句,这绝对是您应该做的。(对我来说,这是对垂直空间的更好利用,它会给尽可能多的行一些有意义的东西。)

for (
   int i = 0, j = 0;
   i < array.length && !condition;
   i++, j++
) {
   // stuff
}

更好的方法可能是尝试使用更具说明性和较少要求的形式。这取决于语言,或者您可能需要编写自己的函数来启用这种功能。这也取决于condition实际情况。大概它必须能够以某种方式进行更改,具体取决于数组中的内容。

array
.takeWhile(condition)  // condition can be item => bool or (item, index) => bool
.forEach(doSomething); // doSomething can be item => void or (item, index) => void

for在整个讨论中,将复杂或不寻常的陈述分成多行是最好的主意
user949300

0

被遗忘的一件事:当我从循环中中断时(无论如何实现,都不做所有可能的迭代),通常不仅要退出循环,而且还要知道为什么会发生这种情况。例如,如果我寻找X,不仅会中断循环,而且还想知道X的位置和位置。

在那种情况下,让条件处于循环之外是很自然的。所以我开始

bool found = false;
size_t foundIndex;

在循环中,我将写

if (condition) {
    found = true;
    foundIndex = i;
    break;
}

与for循环

for (i = 0; i < something && ! found; ++i)

现在检查循环中的条件是很自然的。是否存在“ break”语句取决于要避免的循环中是否有进一步的处理。如果需要某些处理而不需要其他处理,则可能会有类似

if (! found) { ... }

您循环中的某处。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.