哪个是更好的做法 - 对于带有中断或条件循环的循环?

时间:2009-02-27 17:03:59

标签: loops for-loop while-loop language-agnostic

我只是好奇人们对这个话题的看法。假设我有一个对象数组,我想循环遍历它们以查看对象是否包含某些值,如果是,我想停止循环。哪种更好的做法 - 带有中断的for循环或条件循环?

我提供的示例中的伪代码仅用于参数(它也在ActionScript中,因为这是我最近的主要语言)。另外,我不是在寻找有关语法的最佳实践想法。

for break with break:

var i:int;

var isBaxterInMilwaukee:Boolean;    

for (i = 0; i < arrayLen; i++)
{
    if (myArray[i]["name"] == "baxter"
         && myArray[i]["location"] == "milwaukee")
    {
        isBaxterInMilwaukee = true;

        barkTwice();

        break;
    }
}

条件循环:

var i:int;

var isBaxterInMilwaukee:Boolean;    

while (!isBaxterInMilwaukee && i < arrayLen)
{
    if (myArray[i]["name"] == "baxter"
         && myArray[i]["location"] == "milwaukee")
    {
        isBaxterInMilwaukee = true;

        barkTwice();
    }

    i++;
}

20 个答案:

答案 0 :(得分:28)

简而言之,您应该选择最容易阅读和维护的版本。

在稍微老一点的时候,我知道打破循环被认为是禁忌(与goto声明相同)。循环应该在循环条件下破坏而在其他任何地方都没有。因此,while循环将成为可行的方法。

(这可能是程序集的延续,其中循环基本上是一个代码块,在结尾处有一个go-to-the-begin-if-true跳转语句。块中的多个条件跳转语句使得它非常强大难以调试;因此他们应该避免并在最后合并为一个。)

我觉得这个想法今天似乎有所改变,特别是对于foreach循环和托管世界;现在真的是风格问题。当然,除了一些纯粹主义者之外,许多人都可以接受突然发现的循环。请注意,我仍然会避免在while循环中使用break,因为这会混淆循环条件并使其混乱。

如果你允许我使用foreach循环,我认为下面的代码是 lot 比它的while循环兄弟更容易阅读:

bool isBaxterInMilwaukee;    

foreach (var item in myArray)
{
    if (item.name == "baxter" && item.location == "milwaukee")
    {
        isBaxterInMilwaukee = true;    
        barkTwice();
        break;
    }
}

然而,随着逻辑的复杂性增加,您可能需要在break语句附近考虑一个突出的注释,以免它被埋没并且很难找到。


可以说,整个事情应该重构为自己的函数,它不会找到break,但实际上是return的结果(随意使用而不是for循环版本):

bool isBaxterInMilwaukee(Array myArray)
{      
    foreach (var item in myArray)
    {
        if (item.name == "baxter" && item.location == "milwaukee")
        {
            barkTwice();
            return true;
        }
    }
    return false;
}

正如Esko Luontola指出的那样,最好将调用移到此函数之外的barkTwice(),因为副作用在函数名称中并不明显,也与在每种情况下都找不到Baxter有关。 (或者添加一个布尔参数BarkTwiceIfFound并将该行更改为读取if(BarkTwiceIfFound) barkTwice();以使副作用清晰。)


对于记录,您也可以在for-loop中进行标记检查而不会中断,但我觉得这实际上会损害可读性,因为您不希望在for循环定义中出现额外条件:

var i:int;

var isBaxterInMilwaukee:Boolean;    

for (i = 0; !isBaxterInMilwaukee && i < arrayLen; i++)
{
    if (myArray[i]["name"] == "baxter"
         && myArray[i]["location"] == "milwaukee")
    {
        isBaxterInMilwaukee = true;    
        barkTwice();
    }
}

您还可以使用while循环模拟自动递增机制。我不喜欢这个有几个原因 - 你必须将i初始化为比实际起始值小1,并且根据编译器如何使循环条件逻辑短路,你的值{{{ 1}}退出循环可能会有所不同。尽管如此,对某些人来说,这可能会提高可读性:

i

答案 1 :(得分:7)

我一直不喜欢在代码中使用breaks ...在这种情况下它似乎并不重要,但在更多涉及的循环中,它可能会让另一个读取它的编码器感到非常困惑。通常,它通常导致不理解循环如何终止,直到编码器发现循环中的嵌套break深。通过指定一个标记条件来检查循环的每次迭代,它使这更加清晰。

这个问题类似于在方法体内深处的return语句,它们不容易被发现(而不是设置retVal变量并在结束时返回方法)。用一个小方法,这似乎很好,但它越大,它就会越混乱。

这不是一种操作效率,它是一种可维护性的东西。

向你的同事询问特定情况下的可读性和可理解性......这才是真正重要的。

答案 2 :(得分:5)

我想说这取决于。在这种情况下,带有中断的循环对我来说更清晰。

答案 3 :(得分:4)

在for循环中,您还可以通过将早期退出条件放在for循环声明中来提前退出。所以对于你的例子,你可以这样做:

var i:int;

var isBaxterInMilwaukee:Boolean;    

isBaxterInMilwaukee = false;

for (i = 0; i < arrayLen && !isBaxterInMilwaukee; i++)
{
    if (myArray[i]["name"] == "baxter"
        && myArray[i]["location"] == "milwaukee")
    {
        isBaxterInMilwaukee = true;

        barkTwice();
    }
}

这样你就不需要休息,而且它比while循环更具可读性。

答案 4 :(得分:4)

两者之间存在概念上的差异。 for循环用于迭代离散集,while循环用于基于条件重复语句。其他语言添加finally子句和循环结构,如foreachuntil。它们往往具有相当少的传统for循环。

在任何情况下,我使用的规则是for循环迭代和while循环重复。如果您看到类似的内容:

while (counter <= end) {
   // do really cool stuff
   ++counter;
}

然后你可能会因为迭代而更好地使用for循环。但是,循环如:

for (int tryCount=0; tryCount<2; ++tryCount) {
    if (someOperation() == SUCCESS) {
       break;
    }
}

应该写成while循环,因为它们确实在重复某些事情,直到条件为真。

不使用break的想法,因为它goto 一样邪恶是非常荒谬的。你怎么能证明抛出异常呢?这只是一个非本地和非确定性的转到!顺便说一句,这不是对异常处理的咆哮,只是一种观察。

答案 5 :(得分:3)

最有意义的一个就是将这个想法传达给人类阅读最佳代码的人。记住代码可读性首先,您通常会做出正确的选择。通常,除非你真的需要,否则你不想使用像break这样的东西,因为如果经常这样做,或者甚至只是在一组深层嵌套的表达式中,它会使事情难以理解。 continue有时可以起到与休息相同的作用,然后循环将正常退出,而不是因为它被破坏了。在这种情况下,我可以通过几种不同的方式来编写它。

你想要的最好的事情可能就是修改你的while循环:

while(!isBaxterInMilwaukee || i < arrayLen) {
  if(myArray[i]["name"] == "baxter" && myArray[i]["location"] == "milwaukee") {
    isBaxterInMilwaukee == true;
    barkTwice()
  } else {
    i++;
  }
}

这很清楚,不使用breakcontinue,因此您可以一眼就看出,由于{{1}中指定的条件之一,您将始终终止表达。

ETA while循环中可能应为i < arrayLen,否则第一次失败,除非输入值与目标值相同... < / p>

答案 6 :(得分:3)

中断被视为“结构化goto”,应谨慎使用。如果它是邪恶的较小者,请使用它。

避免继续

编辑:由于我得到了一些评论和一些downvotes,我将添加一些支持性参考

A C/C++ coding standard

And a weaker statement for Java (see .52)

答案 7 :(得分:3)

我看到两个循环中断了,这是正确的吗?

反正:

  • 当循环开始之前有已知的迭代次数(最大数量)时,我会选择FOR循环。
  • 我会选择WHILE。
  • 在FOR循环中,我可以自由使用BREAK。
  • 在WHILE循环中,我更喜欢使用复杂条件而不是BREAK(如果可能的话)。

答案 8 :(得分:2)

我会说休息,更清楚(即使你发表评论,为什么你突破了循环) Imho while while循环不清楚,我会去休息

答案 9 :(得分:2)

我肯定会选择for + break。 'for'是一种立即可识别的“迭代序列”的习语,它更容易理解“迭代序列;如果找到的值“比合并的循环和停止条件更早结束。

可能有证据表明您在条件循环代码中出现了两个错误!

  • while条件(!isBaxterInMilwaukee || i == arrayLen) - 你的意思是“(!(isBaxterInMilwaukee || i == arrayLen))”?

  • 如果您使用的是终止循环变量,则
  • 不需要break语句。

就我个人而言,我发现一个简单的“中断”比试图跟踪终止循环变量更容易阅读。

答案 10 :(得分:2)

问题有两个方面:

  • 要做什么(例如:查找其中一个项目是否包含该位置的指定人员)
  • 如何这样做(例如:使用索引,迭代等)

这两个例子混合了两个,很难理解 中的 。如果我们只能在代码中表达 what 部分,那将是最好的。以下是使用specification模式

执行此操作的示例(c#3.5)
// what we are looking for?
IsPersonInLocation condition = new IsPersonInLocation("baxter", "milwaukee");

// does the array contain what we are looking for?
bool found = myArray.Find(item => condition.IsSatifiedBy(item));

// do something if the condition is satisfied
if (found) {
    barkTwice();
}

为了完整性,这里是条件的类定义:

class IsPersonInLocation {
    public string Person { get; set; }
    public string Location { get; set; }
    public IsPersonInLocation(string person, string location) {
        this.Person = person;
        this.Location = location;
    }
    bool IsSatifiedBy(item) {
        return item["name"] == this.Person
            && item["location"] == this.Location;
    }
}

答案 11 :(得分:1)

我的理论是,有一个类似于“信噪比”的有用的编程抽象,即“问题 - 工具比” - 良性可以在一个维度上衡量我花了多少时间思考与我考虑如何使用该工具(在本例中为语言语法)的时间相比,问题及其解决方案。

通过这种方法,我尝试更频繁地使用更少的构造,因为我(并希望那些跟随的人)能够更快更准确地理解我的代码结构的本质。而且,由于“for循环”的变化可以很好地覆盖其他可能被使用的情况(没有失真),所以当它们可以互换时,我将它们作为第一选择使用。

很高兴在“for”循环顶部的一行中提供有关循环规则的所有事情(grokwise)。出于同样的原因,我也倾向于将“默认”开关放在测试中。

但一致性和清晰度是最重要的考虑因素。 YMMV,当然。

答案 12 :(得分:1)

将循环封装在自己的方法中,并在匹配条件成功时使用return to end处理。

一些示例C#代码:

class Program
{
   static bool IsBaxterInMilwaukee(IList<WhoAndWhere> peopleAndPlaces)
   {
      foreach (WhoAndWhere personAndPlace in peopleAndPlaces)
      {
         if (personAndPlace.Name == "Baxter" 
            && personAndPlace.Location == "Milwaukee")
         {
            return true;
         }
      }
      return false;
   }

   static void Main(string[] args)
   {
      List<WhoAndWhere> somePeopleAndPlaces = new List<WhoAndWhere>();
      somePeopleAndPlaces.Add(new WhoAndWhere("Fred", "Vancouver"));
      somePeopleAndPlaces.Add(new WhoAndWhere("Baxter", "Milwaukee"));
      somePeopleAndPlaces.Add(new WhoAndWhere("George", "London"));

      if (IsBaxterInMilwaukee(somePeopleAndPlaces))
      {
         // BarkTwice()
         Console.WriteLine("Bark twice");
      }
   }

   public class WhoAndWhere
   {
      public WhoAndWhere(string name, string location)
      {
         this.Name = name;
         this.Location = location;
      }

      public string Name { get; private set; }
      public string Location { get; private set; }
   }

}

答案 13 :(得分:1)

我想两者实际上都不一样有趣。如果你想要的可读性,你应该寻找更高级别的结构。

在JS中:

if(myArray.some(function(o) { o.name == "baxter" && o.location == "milwaukee" }))
  barkTwice();

或使用您自己的一些工具

if(myArray.containsMatch({name:"baxter",location:"milwaukee"})
  barkTwice();

答案 14 :(得分:1)

我有一个C ++背景,所以我仍然有机会尝试“像编译器一样思考”。虽然循环往往会导致更严格的代码,因此只有在您知道每次都要遍历数组中的每个元素时才会考虑for循环。

编辑:我现在相信这是过度的,如果你使用.Net或任何你不会用几个紧密循环来弥补虚拟机开销。我确实认为记住某些做法的“原因”是件好事。

答案 15 :(得分:1)

这在很大程度上取决于具体情况。但是在你的例子中,你想要走一个有限长度的数组,并且使用for循环可以很容易地做到这一点,并防止运行结束。在你的while循环示例中,你必须自己进行递增 - 如果你想使用continue语句跳到下一个循环,这可能会有问题 - 并且制作一个更复杂的条件表达式(由方式,有一个错误;我认为你的意思是&& i != arrayLen)。你只需要做额外的代码来完成for循环帮助提供的效果。

当然,一些纯粹主义者会争辩说breakcontinue不应该被使用,如果需要你应该使用if-else和boolean变量而不是继续或突破循环。但我认为这可以使循环看起来更难看,特别是如果它相对较短且易于掌握,就像这个例子一样。对于具有更长代码的循环,其中中断或继续可以容易地忽略通知,纯粹方法可能更清楚,因为在这种情况下循环已经很难掌握。但是你总是可以将它作为for循环的一部分来做,只需将它作为条件的一部分添加。

更好的做法是测试绑定i < arrayLen的数组而不是完全相等,以防某些事情导致i跳过确切的值(我实际上看到这发生在Y2K错误中,这可以通过更好的做法避免。)

答案 16 :(得分:0)

我的一般立场是:

如果它有一个循环计数器用于()(就像while循环一样)。

答案 17 :(得分:0)

我投票while,因为休息会降低可归咎性。

如果循环增长太长并且你插入了你希望运行的代码,那么你可能没有意识到循环包含一个中断。它不会。

但我赞成不要让我思考编码模型。

答案 18 :(得分:0)

在ES6中,它变得非常容易,不需要使用break关键字,我们可以使用find函数,一旦条件满足,我们就可以返回true。

date     |id |children
-----------------------
1/1/2019 |A  |[D#P, Z#Z]
1/1/2019 |C  |[K#H]

在这里我们匹配条件,一旦条件匹配,我们根据问题调用函数,然后返回true。这样就不会超出范围。

SELECT
    first_value(case
            when parent_id
            then date
            end)
        over (
            partition by parent_id
            order by date
            rows between unbounded preceding and unbounded following)
        as first_date)
        id,
        list_agg(parent_id)
    FROM foo

答案 19 :(得分:0)

当循环开始前已知迭代次数(最大次数)时,我会选择FOR循环。 否则我会选择WHILE。 在FOR循环中,我可以自由使用BREAK。 在WHILE循环中,我更喜欢使用复杂条件而不是BREAK(如果可能)。