2009-02-27 4 views
21

Мне просто интересно, какие мысли людей на эту тему. Предположим, у меня есть массив объектов, и я хочу пропустить их, чтобы увидеть, содержат ли объекты определенные значения, и если это так, я хочу остановить цикл. Что лучше - цикл 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++; 
} 
+0

У вас есть перерыв; в обоих – TheTXI

+0

Я не думаю, что вы имеете в виду, что вы должны быть в вашем цикле while. –

+0

Я никогда не использовал ActionScript, но часть «i == arrayLen» в состоянии while выглядит неправильно.Разве это не должно быть «i unwind

ответ

27

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

В несколько более старых случаях я знаю, что выход из цикла считался no-no (наравне с оператором goto). Предполагалось, что петли будут разбиты на состояние цикла и больше нигде. Таким образом, петля while была бы способ пойти.

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

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

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

bool isBaxterInMilwaukee;  

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

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


Можно утверждать, что вся эта вещь должна быть переработана в свою собственную функцию, которая не break на нашли, но на самом деле return s результат (не стесняйтесь использовать для цикла версии, а):

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

Как Esko Luontola отметил, что, вероятно, будет лучше, чтобы переместить вызов barkTwice() вне этой функции в качестве побочного эффекта не видно из имени функции, ни связано с нахождением Бакстер в каждом конкретном случае. (Или добавьте булево значение BarkTwiceIfFound и измените строку, чтобы прочитать if(BarkTwiceIfFound) barkTwice(); сделать побочный эффект ясного.)


Для записи, вы можете также сделать проверку флага в течение петли без перерыва, но я чувствую, что это на самом деле вредит читаемости, потому что вы не ожидаете дополнительное условие в определении для цикла:

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(); 
    } 
} 
+0

Рефакторинг кода для его собственной функции является самым ясным. Также было бы полезно переместить barkTwice() вне isBaxterInMilwaukee(), потому что имя функции никоим образом не указывает на побочный эффект лая два раза. –

+0

Например: if (isBaxterInMilwaukee (myArray)) {barkTwice(); } –

+0

Определенно согласовано. Я не хотел объяснять это больше, чем это уже есть, но это, безусловно, стоит упомянуть. Ред. –

5

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

7

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

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

Это не эффективность работы, это вещь для удобства обслуживания.

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

+0

Мне нравится эта идея: проверьте флаг независимо от типа цикла. –

+0

Использование флага хорошо, но полезно в больших циклах, в коротких циклах (несколько строк), которые вы можете легко просмотреть, я предпочитаю использовать перерывы. – Jiri

3

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

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

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

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

ЕТА: Вероятно, должно быть i < arrayLen в контуре while в противном случае он терпит неудачу в первый раз через, если входное значение не совпадает с целевым значением ...

+0

В основном, я не хочу, чтобы значение boolean возвращалось. это и есть цель вопроса. –

2

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

3

перерыва считается «структурированный переход», и его следует использовать экономно. Используйте его, если это меньше зла.

Избегайте Продолжить еще больше.

Edit: Так как я получил довольно много комментариев и некоторые downvotes я добавлю некоторые вспомогательные ссылки

A C/C++ coding standard

And a weaker statement for Java (see .52)

+0

Что случилось с продолжением? –

+0

неподдерживаемая гипотеза ... – dkretz

+0

Я также ненавижу перерыв. – teedyay

3

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

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

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

В цикле 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(); 
    } 
} 

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

1

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

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

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

1

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

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

1

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

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

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

Но согласованность и ясность - это размышление о верховье. YMMV, конечно.

+0

Это неправильный вариант: «Ваш пробег может отличаться». :) – dkretz

2

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

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

  • состояние в то время как (isBaxterInMilwaukee || я == arrayLen!) - Вы имели в виду «((isBaxterInMilwaukee || я == arrayLen)!)»?

  • инструкция break не нужна, если вы используете переменную terminate-loop.

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

+0

Разрыв не требуется, чтобы закончить конкретный пример цикла, но если он позволяет выполнить оператор i ++, то i не остается указывать на найденную позицию (что, возможно, было бы удобно сохранить). Поэтому избежать дополнительного приращения (перерывом или if-else и т. Д.), Вероятно, более полезно. –

4

Существует концептуальная разница между ними. 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 довольно бессмысленными. Как вы можете оправдать бросание исключения? Это просто нелокальный и недетерминированный переход! Кстати, это не спектакль по обработке исключений, просто наблюдение.

2

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

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

Оба примера смешивать два и трудно понять, что от как. Лучше всего было бы, если бы мы могли выразить в коде только что-то. Вот пример (C# 3.5), что делает это с помощью specification шаблона

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

Я согласен с принципом. Но этот ужасный беспорядок для такого простого поиска просто нежизнеспособен. –

+0

Для уменьшения сложности обычно требуется написать больше кода. Это может показаться противоречивым до тех пор, пока не будет признано, что проблема заключается не в суммарном объеме кода, а в количестве кода, который вы должны посмотреть на определенный момент, чтобы понять его. – Aleris

+0

Да. Но моя реакция была скорее реакцией на выбор языка. Я проиллюстрировал альтернативную версию вашего комментария в Java Script, чтобы проиллюстрировать эту разницу. –

0

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

Если он использует счетчик циклов для() (например, while while while).

0

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

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

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

1

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

В JS:

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

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

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

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

Некоторые примеры 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; } 
    } 

} 
Смежные вопросы