Что лучше - для цикла с прерыванием или условной петлей?
Мне просто интересно, что думают люди по этой теме. Скажем, у меня есть массив объектов, и я хочу прокрутить их, чтобы увидеть, содержат ли объекты определенные значения, и если да, я хочу остановить цикл. Что лучше - цикл for с перерывом или условный цикл?
Псевдокод в приведенном выше примере предназначен только для аргумента (он также находится в ActionScript, так как это мой основной язык в последнее время). Кроме того, я не ищу идеи лучшей практики по синтаксису.
для цикла с 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++;
}
Ответы
Ответ 1
Короче говоря, вы должны пойти с той версией, которая проще всего читать и поддерживать.
В несколько более старые времена я знаю, что выход из цикла считался no-no (наравне с оператором goto). Предполагалось, что петли будут разбиты на состояние цикла и больше нигде. Таким образом, петля while была бы способ пойти.
(Вероятно, это сокращение от сборки, где циклы в основном представляют собой блок кода с инструкцией перехода от начала к началу-if-true в конце. Несколько операторов условного перехода в блоке делают это чрезвычайно трудно отлаживать, поэтому их следует избегать и объединить в один в конце.)
Я чувствую, что сегодня эта идея немного меняется, особенно с петлями foreach и управляемым миром; это действительно вопрос стиля сейчас. Прерывание находки для циклов, возможно, стало приемлемым для многих, но, конечно же, сохранить пуристов. Обратите внимание, что я все же избежал бы использовать break in while-loop, поскольку это может запутать условие цикла и сделать его запутанным.
Если вы разрешите мне использовать цикл foreach, я считаю, что код ниже намного легче читать, чем его брат в петле:
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()
вне этой функции, поскольку побочный эффект не является очевидным из имени функции и не связан с поиском Baxter в каждом случае. (Или добавьте логический параметр BarkTwiceIfFound
и измените строку на чтение if(BarkTwiceIfFound) barkTwice();
, чтобы очистить побочный эффект.)
Для записи вы также можете выполнить проверку флага в for-loop без перерыва, но я чувствую, что это действительно ухудшает читаемость, потому что вы не ожидаете дополнительного условия в определении for-loop:
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
on выход из цикла может меняться. Тем не менее, это возможно, и для некоторых людей это может улучшить читаемость:
var i:int = -1;
var isBaxterInMilwaukee:Boolean;
while (!isBaxterInMilwaukee && ++i < arrayLen)
{
if (myArray[i]["name"] == "baxter"
&& myArray[i]["location"] == "milwaukee")
{
isBaxterInMilwaukee = true;
barkTwice();
}
}
Ответ 2
Мне всегда не нравилось использование breaks
в коде... В этом случае это, похоже, не имеет значения, но в более сложных циклах, это может быть ужасно запутанным для другого кодера, читающего его. В общем, это часто приводит к тому, что не удается понять, как цикл может завершиться до тех пор, пока кодер не зафиксирует вложенную break
в глубину цикла. Указав условие флага, которое проверяет каждую итерацию цикла, оно делает это намного яснее.
Эта проблема будет похожа на наличие операторов return
, которые находятся глубоко в теле метода, где их не так легко заметить (вместо установки переменной retVal
и возврата в конце метода). С небольшим методом это кажется прекрасным, но чем больше он будет, тем более запутанным будет это.
Это не эффективность работы вещь, это вещь поддерживающая.
Спросите своих сотрудников, что читаемо и понятно для конкретной ситуации... Это действительно важно.
Ответ 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.
Ответ 5
Существует концептуальная разница между ними. Циклы 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
, довольно бессмысленна. Как вы можете оправдать бросание исключения? Это просто нелокальное и недетерминированное переключение! Кстати, это не напыщенная речь об обработке исключений, просто наблюдение.
Ответ 6
Тот, который имеет наибольший смысл, будет тем, который передает идею человеку, читающему код лучше всего. Прежде всего помните читаемость кода, и вы, как правило, сделаете правильный выбор. Как правило, вы не хотите использовать что-то вроде 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
, иначе он не сработает с первого раза, если входное значение не будет таким же, как целевое значение...
Ответ 7
break считается "структурированным переходом" и его следует использовать экономно. Используйте его, если это меньше зла.
Избегайте продолжить еще больше.
Изменить: Поскольку у меня появилось довольно много комментариев и некоторые downvotes, я добавлю вспомогательные ссылки
Стандарт кодирования C/С++
И более слабый оператор для Java (см..52)
Ответ 8
Я вижу разрыв в обеих циклах, это правильно?
В любом случае:
- Я бы выбрал цикл FOR, когда есть известное число (максимальное число) итераций до начала цикла.
- Я бы выбрал WHILE иначе.
- В цикле FOR я свободно использую BREAK.
- В цикле WHILE я предпочитаю использовать сложное условие вместо BREAK (если это возможно).
Ответ 9
Я бы сказал, что разрыв, более ясный (даже если вы добавите комментарий, почему вы выходите из цикла)
Imho цикл while не ясен, я бы пошел на перерыв
Ответ 10
Я бы обязательно пошел с перерывом+. "For" - это мгновенно узнаваемая идиома для "итерации по последовательности", и ее легче понять "итерация по последовательности, заканчивается раньше, если значение найдено", чем комбинированное условие цикла и остановки.
Там могут быть доказательства этого в том, как вы, кажется, допустили две ошибки в вашем условном коде цикла!
-
условие while (! isBaxterInMilwaukee || я == arrayLen) - вы имели в виду "(! (isBaxterInMilwaukee || я == arrayLen))??
-
оператор break не нужен, если вы используете переменную terminate-loop.
Лично я нахожу простой "перерыв" намного легче читать, чем пытаться отслеживать переменную terminate-loop.
Ответ 11
Есть два аспекта проблемы:
- Что делать (например: найти, если один из элементов содержит указанного человека в местоположении)
- Как сделать это (например: использовать индекс, итерацию и т.д.)
Оба примера смешивают два и трудно понять, что из этого. Лучше всего было бы, если бы мы могли выразить в коде только ту часть. Вот пример (С# 3.5), который делает это с помощью specification pattern
// 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;
}
}
Ответ 12
Это зависит от конкретных обстоятельств. Но в вашем примере вы хотите пройти массив ограниченной длины, и использование цикла for упрощает его выполнение и защищает от истечения конца. В примере с вашим циклом вы должны сделать свой собственный прирост - что может быть проблематичным, если вы хотите использовать оператор continue
для перехода к следующему циклу - и сделать более сложное условное выражение (которое, кстати, имеет ошибку, я думаю, вы имели в виду && i != arrayLen
). Вам просто нужно сделать дополнительный код, чтобы добиться эффекта, который предоставляет помощь в цикле.
Конечно, некоторые пуристы утверждают, что break
и continue
не должны использоваться и что вам нужно использовать if-else и логические переменные, если необходимо, а не продолжать или выходить из цикла. Но я думаю, что это может сделать цикл более уродливым, особенно если его относительно короткое и легко понять в целом, как этот пример. Для цикла с гораздо более длинным кодом, где разрыв или продолжение может легко скрыться от уведомления, пуристский подход может быть более понятным, поскольку в этом случае цикл уже усложняется. Но вы всегда можете сделать это как часть цикла for, просто добавьте его как часть условного.
Также лучше проверить массив, связанный с i < arrayLen
, а не на точное равенство, если что-то заставило i
пропустить точное значение (я действительно видел, что это произошло в ошибке Y2K, которая могла иметь избегали лучшей практики).
Ответ 13
У меня есть фон С++, и поэтому у меня все еще есть моменты, когда я пытаюсь "думать как компилятор". В то время как циклы, как правило, приводят к более жесткому коду, и поэтому для циклов только когда-либо учитывались, если вы знали, что каждый раз перебираете каждый элемент в массиве.
EDIT: теперь я считаю, что это слишком много, если вы используете .Net или что бы вы не намеревались нанести накладные расходы с помощью нескольких жестких циклов. Я действительно думаю, что помнить, почему "почему" некоторых практик - это хорошо.
Ответ 14
Моя теория заключается в том, что есть полезная абстракция программирования, подобная "отношение сигнал/шум", что является "соотношением" проблема-инструмент "- доброта может быть измерена в одном измерении тем, сколько времени я трачу на размышления о проблема и ее решение, по сравнению с тем временем, которое я трачу, думая о том, как использовать инструмент (в данном случае синтаксис языка).
В этой мере я стараюсь использовать меньшее количество конструкций чаще, потому что я (и, надеюсь, те, кто следуют) может быстрее и точно оценить суть моей структуры кода. И так как вариации "для циклов" делают довольно хорошую работу, охватывая случаи, когда другие могут использоваться (без искажений), я использую их как первое предпочтение, когда они взаимозаменяемы.
И хорошо иметь все, что вам нужно знать (grokwise) о правилах циклов в одной строке в верхней части цикла "за". Я также склонен сначала поставить переключатель "по умолчанию" в тестах по той же причине.
Но согласованность и ясность - это размышление о верховности. YMMV, конечно.
Ответ 15
Я думаю, что на самом деле это не так интересно. Вы должны искать конструкторы более высокого уровня, если вы читаете их.
В JS:
if(myArray.some(function(o) { o.name == "baxter" && o.location == "milwaukee" }))
barkTwice();
или с некоторыми утилитами вашего собственного
if(myArray.containsMatch({name:"baxter",location:"milwaukee"})
barkTwice();
Ответ 16
Инкапсулируйте цикл в свой собственный метод и используйте обработку возврата к завершению, когда условие соответствия выполнено.
Пример кода С#:
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; }
}
}
Ответ 17
Моя общая позиция:
Если он использует счетчик циклов для() (например, while while while).
Ответ 18
Я проголосую while
, потому что breaks уменьшают grokability.
Вы не можете понять, что цикл содержит разрыв, если цикл слишком длинный, и вы вставляете код, который вы ожидаете запустить, и это не так.
Но я подписываюсь на то, что не заставляю меня думать о модели кодирования.