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

问题描述 投票:23回答:19

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

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

for 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++;
}
loops for-loop while-loop language-agnostic
19个回答
28
投票

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

在较旧的时代,我知道突然出现循环被认为是禁忌(与goto声明同等)。循环应该在循环条件下破坏而在其他任何地方都没有。因此,while循环将成为可行的方法。

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

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

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

bool isBaxterInMilwaukee;    

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

然而,随着逻辑的复杂性增加,你可能想在break声明附近考虑一个突出的评论,以免它被埋没而且很难找到。


可以说,整个事情应该被重构为它自己的函数,它没有找到break,但实际上returns结果(可以随意使用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循环中执行标记检查而不会中断,但我觉得这实际上会损害可读性,因为您不期望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初始化为比实际起始值小一个,并且根据你的编译器如何使循环条件逻辑短路,你的i在退出循环时的值可能变化。然而,对于某些人来说,这是可能的,这可以提高可读性:

var i:int = -1;

var isBaxterInMilwaukee:Boolean;    

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

2
投票

我绝对会选择for + break。 'for'是一种立即可识别的“迭代序列”的习语,它更容易理解“迭代序列;如果发现值超过组合的循环和停止条件,则提前结束。

你似乎在条件循环代码中犯了两个错误的方式可能有证据证明这一点!

  • while条件(!isBaxterInMilwaukee || i == arrayLen) - 你的意思是“(!(isBaxterInMilwaukee || i == arrayLen))”?
  • 如果您使用的是terminate-loop变量,则不需要break语句。

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


2
投票

问题有两个方面:

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

这两个例子混合了两者,很难理解如何做。如果我们能够在代码中仅表达哪个部分,那将是最好的。这是一个使用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;
    }
}

1
投票

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

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

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


1
投票

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

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


1
投票

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

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

在“for”循环顶部的单行中,您需要知道(grokwise)有关循环规则的所有内容,这很好。出于同样的原因,我也倾向于将“默认”开关放在测试中。

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


1
投票

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

在JS中:

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

或者你自己的一些工具

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

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

}

0
投票

我的一般立场是:

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


0
投票

我投票while,因为休息减少了grokability。

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

但我订阅了不要让我思考编码的模型。


0
投票

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

let barkTwice = () => {
    console.log('bark twice');
}

let arr = [{
        location: "waukee",
        name: "ter"
    }, {
        location: "milwaukee",
        name: "baxter"
    },
    {
        location: "sample",
        name: "sampename"
    }
];

在这里我们匹配条件,一旦条件匹配,我们调用函数,根据问题然后我们返回true。所以它不会超越。

arr.find(item => {
    if (item.location == "milwaukee" && item.name == "baxter") {
        barkTwice();
        return true
    }
});

8
投票

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

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

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

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


5
投票

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


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
投票

两者之间存在概念上的差异。 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一样邪恶是非常荒谬的。你怎么能证明抛出异常呢?这只是一个非本地和非确定性的转到!顺便说一句,这不是对异常处理的咆哮,只是观察。


3
投票

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

你想要的最好的东西可能是修改你的while循环:

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

这很清楚,并且不使用breakcontinue,因此您可以一眼就看出,您将始终因while表达式中指定的条件之一而终止。

ETA:可能应该是i < arrayLen循环中的while,否则它第一次失败,除非输入值与目标值相同...


3
投票

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

无论如何:

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

3
投票

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

避免继续下去。

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

A C/C++ coding standard

And a weaker statement for Java (see .52)


2
投票

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

© www.soinside.com 2019 - 2024. All rights reserved.