Что лучше - для цикла с прерыванием или условного цикла?

Мне просто любопытно, что думают люди по этой теме. Скажем, у меня есть массив объектов, и я хочу просмотреть их в цикле, чтобы увидеть, содержат ли объекты определенные значения, и если да, то я хочу остановить цикл. Что лучше - цикл for с перерывом или условный цикл?

Псевдокод в приведенном мной примере предназначен только для аргументации (он также находится в ActionScript, поскольку в последнее время это мой основной язык). Кроме того, я не ищу лучших практических идей по синтаксису.

для цикла с разрывом:

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

Ответов (18)

Решение

Короче говоря, вы должны выбрать ту версию, которую легче всего читать и поддерживать.

Я знаю, что раньше выход из цикла считался запретом (наравне с оператором goto). Циклы должны были прерываться по условию цикла и больше нигде. Таким образом, цикл while был бы подходящим вариантом.

(Вероятно, это пережиток сборки, где циклы в основном представляют собой блок кода с оператором перехода go-to-the-start-if-true в конце. Множественные операторы условного перехода в блоке чрезвычайно затрудняют отладку ; поэтому их следовало избегать и в конце объединить в одно целое.)

Мне кажется, эта идея сегодня немного меняется, особенно с циклами foreach и управляемым миром; теперь это действительно вопрос стиля. Обнаруженные циклы for, возможно, стали приемлемыми для многих, за исключением, конечно, некоторых пуристов. Обратите внимание, что я бы по-прежнему избегал использования break в цикле while, поскольку это может запутать условие цикла и сбить его с толку.

Если вы позволите мне использовать цикл foreach, я считаю, что приведенный ниже код намного легче читать, чем его собрат по циклу while:

bool isBaxterInMilwaukee;    

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

Однако по мере того, как логика становится все более сложной, вы можете захотеть рассмотреть заметный комментарий рядом с break утверждением, чтобы его не было похоронить и его было трудно найти.


Возможно, все это должно быть реорганизовано в отдельную функцию, которая не break найдена, но фактически return является результатом (вместо этого не стесняйтесь использовать версию for-loop):

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

Как указал Эско Луонтола, вероятно, было бы лучше переместить вызов за barkTwice() пределы этой функции, поскольку побочный эффект не очевиден из имени функции и не связан с поиском Бакстера в каждом случае. (Или добавьте логический параметр 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();
    }
}

Я бы сказал, это зависит от обстоятельств. В этом случае мне кажется более понятным цикл с разрывом.

Наиболее разумным будет тот, который лучше всего передает идею человеку, читающему код. Помните, прежде всего , о читабельности кода , и вы обычно сделаете правильный выбор. Обычно вы не хотите использовать что-то вроде, break если вам это действительно не нужно, потому что это может затруднить отслеживание, если вы будете делать это часто или даже просто в глубоко вложенном наборе выражений. continue иногда может служить той же цели, что и прерывание, и тогда цикл завершится нормально, а не потому, что он был прерван. В этом случае есть несколько разных способов написать это.

Вероятно, лучшее, что вам здесь нужно, - это модификация вашего while цикла:

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

Это ясно и не использует break или continue, поэтому вы можете сразу сказать, что вы всегда завершаете работу в результате одного из условий, указанных в while выражении.

ETA : вероятно, должен быть i < arrayLen в while цикле, иначе он не сработает с первого раза, если входное значение не совпадает с целевым значением ...

Моя общая позиция:

Если у него есть счетчик цикла, используйте for () (например, while цикл).

В ES6 это было сделано очень просто, нет необходимости использовать ключевое слово break, мы можем использовать функцию поиска, когда условие удовлетворяется, мы можем вернуть true.

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

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

Здесь мы сопоставляем условие, и как только условие совпадает, мы вызываем функцию в соответствии с вопросом, а затем возвращаем истину. Так что дальше не идет.

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

Мне всегда не нравилось использование breaks в коде ... В этом случае это, кажется, не имеет значения, но в более сложных циклах это может ужасно сбить с толку другого кодировщика, читающего его. В общем, это часто приводит к непониманию того, как цикл может завершиться, пока кодер не обнаружит вложенность break глубоко в цикле. Указав условие флага, которое проверяется на каждой итерации цикла, можно сделать это намного яснее.

Эта проблема была бы похожа на наличие return операторов, которые находятся глубоко в теле метода, где их нелегко обнаружить (вместо того, чтобы устанавливать retVal переменную и возвращаться в конце метода). При использовании небольшого метода это кажется прекрасным, но чем больше он становится, тем больше запутывается.

Дело не в эффективности эксплуатации, а в ремонтопригодности.

Спросите своих коллег, что можно прочитать и понять в конкретной ситуации ... Вот что действительно важно.

Я бы сказал, перерыв, яснее (даже если вы добавите комментарий, почему вы выходите из цикла) Имхо, цикл while не ясен, я бы пошел на перерыв

Это во многом зависит от конкретных обстоятельств. Но в вашем примере вы хотите пройти по массиву ограниченной длины, и использование цикла for упрощает это и защищает от сбега с конца. В вашем примере цикла while вы должны выполнить собственное приращение - что может быть проблематично, если вы хотите использовать continue оператор для перехода к следующему циклу - и сделать более сложное условное выражение (которое, кстати, имеет ошибку , Я думаю ты имел ввиду && i != arrayLen ). Вам просто нужно написать дополнительный код, чтобы добиться эффекта, обеспечиваемого циклами for.

Конечно, некоторые пуристы будут утверждать, что break и continue не следует использовать, и что вы должны использовать if-else и логические переменные, если это необходимо, а не продолжать или выходить из цикла. Но я думаю, что это может сделать цикл намного более уродливым, особенно если он относительно короткий и простой для понимания, как этот пример. Для цикла с гораздо более длинным кодом, где разрыв или продолжение можно легко скрыть от внимания, пуристский подход может быть более ясным, поскольку в этом случае цикл уже сложен для понимания. Но вы всегда можете сделать это как часть цикла for, просто добавьте его как часть условия.

Также лучше проверять связанный массив, i < arrayLen а не на точное равенство, на случай, если что-то заставит i пропустить точное значение (я действительно видел, как это произошло в ошибке 2000 года, чего можно было бы избежать с помощью лучшей практики).

В цикле 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.

Я вижу разрыв в обеих петлях, это правильно?

В любом случае:

  • Я бы выбрал цикл FOR, если известно количество (максимальное количество) итераций перед запуском цикла.
  • В противном случае я бы выбрал ПОКА.
  • В цикле FOR я свободно использую BREAK.
  • В цикле WHILE я предпочитаю использовать сложное условие вместо BREAK (если это возможно).

Моя теория состоит в том, что существует полезная абстракция программирования, подобная «соотношению сигнал / шум», которое является «отношением проблемы к инструменту» - доброту можно измерить в одном измерении тем, сколько времени я трачу на размышления о проблеме и его решение по сравнению со временем, которое я трачу на размышления о том, как использовать инструмент (в данном случае синтаксис языка).

Исходя из этого, я стараюсь чаще использовать меньше конструкций, потому что я (и, надеюсь, те, кто следит за ним) могу быстрее и точнее понять суть структуры моего кода. И поскольку варианты «for loops» довольно хорошо справляются со случаями, когда другие могут быть использованы (без искажений), я использую их в качестве первого предпочтения, когда они взаимозаменяемы.

И приятно иметь все, что вам нужно знать (по-разному) о правилах циклов, в одной строке в верхней части цикла for. Я также предпочитаю ставить переключатель «по умолчанию» первым в тестах по той же причине.

Но последовательность и ясность - вот главные соображения. YMMV, конечно.

У меня есть опыт работы с C++, и поэтому у меня все еще бывают моменты, когда я пытаюсь «думать как компилятор». Циклы while имеют тенденцию приводить к более жесткому коду, поэтому циклы for рассматривались только в том случае, если вы знали, что собираетесь каждый раз перебирать каждый элемент в массиве.

EDIT: теперь я считаю, что это перебор, если вы используете .Net или что-то еще, вы не собираетесь компенсировать накладные расходы виртуальной машины парой жестких циклов. Я действительно считаю, что помнить «почему» определенных практик - это хорошо.

Между ними существует концептуальная разница. for циклы предназначены для перебора дискретных наборов, а while циклы - для повторения операторов на основе условия. Другие языки добавляют finally предложения и конструкции цикла, такие как foreach или until . У них, как правило, значительно меньше традиционных 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 плохо, как и довольно бессмысленно. Как тогда вы можете оправдать выброс исключения? Это просто нелокальный и недетерминированный переход! Между прочим, это не тирада против обработки исключений, а просто наблюдение.

Я бы определенно выбрал for + break. «For» - это легко узнаваемая идиома для «итерации по последовательности», и ее легче понять «итерация по последовательности; закончить раньше, если значение найдено », чем комбинированное условие цикла и остановки.

Доказательством этого может быть то, что вы, кажется, сделали две ошибки в коде условного цикла!

  • условие while (! isBaxterInMilwaukee || i == arrayLen) - вы имели в виду «(! (isBaxterInMilwaukee || i == arrayLen))»?

  • Оператор break не требуется, если вы используете переменную завершения цикла.

Лично я считаю, что простой «разрыв» намного легче читать, чем пытаться отслеживать переменную завершения цикла.

У проблемы два аспекта:

  • Что делать (например: выяснить, есть ли в одном из предметов указанное лицо в локации)
  • Как это сделать (например: использовать индекс, повторять и т. Д.)

В обоих примерах смешано то и другое, и трудно понять, что и как . Лучше всего, если бы мы могли выразить в коде только то, что часть. Вот пример (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;
    }
}

Я голосую, while потому что перерывы уменьшают грокость.

Вы можете не осознавать, что цикл содержит разрыв, если цикл стал слишком длинным и вы вставляете код, который ожидаете запустить, а это не так.

Но я подписываюсь на модель кодирования "не заставляйте меня думать".

Думаю, ни то, ни другое на самом деле не очень интересно. Вам следует искать конструкции более высокого уровня, если вам нужна удобочитаемость.

В JS:

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

или с некоторыми собственными утилитами

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

Инкапсулируйте цикл в его собственном методе и используйте возврат для завершения обработки, когда ваше условие соответствия выполнено.

Пример кода 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; }
   }

}