Плохо ли использовать break в цикле for?

Является ли плохой практикой использование оператораbreak внутри цикла for?

Скажем, я ищу значение в массиве. Сравните внутри цикла for и когда значение найдено, break; для выхода из цикла for.

Это плохая практика? Я видел альтернативу: определить переменную vFound и установить для нее значение true, когда значение будет найдено, и проверить vFound в условии оператора for. Но обязательно ли для этого создавать новую переменную?

Я спрашиваю в контексте обычного цикла C или C++ for.

P.S. В руководстве по кодированию MISRA не рекомендуется использовать break.


person kiki    schedule 13.10.2010    source источник
comment
Не помещайте break в ту же лигу, что и goto :)   -  person BoltClock    schedule 13.10.2010
comment
Э-э, я не помещал их в ту же лигу, что и в... правила MISRA указывают перерыв, если я правильно помню.   -  person kiki    schedule 13.10.2010
comment
Единственная причина, по которой я могу придумать, чтобы не использовать разрыв внутри цикла, - это когда вам все еще нужно обрабатывать больше элементов, которые могут изменить результат того, что вы делаете...   -  person Younes    schedule 13.10.2010
comment
Правила MISRA были смягчены: misra.org.uk/forum/ viewtopic.php?f=75&t=298   -  person Johan Kotlinski    schedule 13.10.2010
comment
Кому нужен goto, когда у тебя есть брейк-лейбл? ‹3 Ява   -  person Raven Dreamer    schedule 13.10.2010
comment
@Raven: кому нужен брейк-лейбл, когда у тебя есть продолжения и динамический ветер? ‹3 Схема   -  person Claudiu    schedule 13.10.2010
comment
Я бы не рассматривал возможность разрыва только в том случае, если цикл предназначен для каждого (объект в списке) {}, поскольку это означает, что код внутри цикла должен применяться ко всем объектам одинаково.   -  person Jono    schedule 14.10.2010
comment
Дублировано: programmers.stackexchange.com/a/58253/72317   -  person Felipe    schedule 31.10.2013
comment
@user724198. Точно. Закрыто как неконструктивное, хотя у него масса голосов и ответов. Я вижу это все равно. Крайне раздражающий и жалкий.   -  person Hermes Trismegistus    schedule 07.02.2017
comment
tbh, в большинстве случаев, которые я видел, я не могу понять, почему он использовался внутри for вместо использования while, которое проверяет это условие И условие for. Я согласен, что это зависит от случая, но в большинстве случаев это не похоже на замену другой функции? Как сказал @e.James, это проблема только для длинных циклов, в то время как его можно использовать в более коротких. Это нормально, но это не масштабируется в общем случае. Я не мог увидеть это в теореме, так как ее результат, похоже, зависит от эвристики. Вы можете использовать его; но зачем, если его можно заменить чем-то более конструктивным?   -  person Maurizio Carcassona    schedule 28.10.2019


Ответы (19)


Здесь много ответов, но я еще не видел этого:

Большинство «опасностей», связанных с использованием break или continue в цикле for, сводятся на нет, если вы пишете аккуратные, легко читаемые циклы. Если тело вашего цикла занимает несколько длин экрана и имеет несколько вложенных подблоков, да, вы можете легко забыть, что какой-то код не будет выполняться после разрыва. Однако, если цикл короткий и точный, назначение оператора break должно быть очевидным.

Если цикл становится слишком большим, вместо этого используйте один или несколько хорошо названных вызовов функций внутри цикла. Единственная реальная причина, по которой этого следует избегать, — обработка узких мест.

person e.James    schedule 13.10.2010
comment
Совершенно верно. Конечно, если петля такая большая и сложная, что трудно увидеть, что в ней происходит, это проблема, есть ли у вас перерыв или нет. - person Jay; 30.12.2013
comment
Другая проблема заключается в том, что прерывание и продолжение вызывают проблемы с рефакторингом. Это может быть признаком того, что это плохая практика. Назначение кода также становится более ясным при использовании операторов if. - person Ed Greaves; 19.01.2015
comment
Эти хорошо названные вызовы функций могут быть локально определенными лямбда-функциями вместо внешних частных функций, которые не будут использоваться где-либо еще. - person DavidRR; 16.09.2015

Нет, перерыв - правильное решение.

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

person smirkingman    schedule 13.10.2010
comment
согласованный. Особенно, если вы хотите выйти из цикла при более чем одном условии. Тогда логическое значение выхода из цикла может стать действительно запутанным. - person Rontologist; 13.10.2010
comment
Вот почему я использую goto для выхода из вложенных циклов уровня 2+, потому что break(level) нет. - person Петър Петров; 25.01.2017
comment
Я бы предпочел поместить код цикла в функцию и просто вернуться. Это позволяет избежать перехода и разбивает ваш код на более мелкие фрагменты. - person Dave Branton; 08.03.2017

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

person Amit S    schedule 13.10.2010

Использование break, а также continue в цикле for совершенно нормально.

Это упрощает код и улучшает его читаемость.

person Community    schedule 13.10.2010
comment
Да... Какое-то время мне не нравилась эта идея, и я кодировал ее, но вскоре я понял... обходной путь часто был кошмаром для техобслуживания. Ну, не кошмар, но более подвержен ошибкам. - person MetalMikester; 13.10.2010

Далеко не плохая практика, Python (и другие языки?) расширил for цикл, поэтому его часть будет только выполняться, если цикл не выполняет break.

for n in range(5):
    for m in range(3):
        if m >= n:
            print('stop!')
            break
        print(m, end=' ')
    else:
        print('finished.')

Выход:

stop!
0 stop!
0 1 stop!
0 1 2 finished.
0 1 2 finished.

Эквивалентный код без break и удобного else:

for n in range(5):
    aborted = False
    for m in range(3):
        if not aborted:
            if m >= n:
                print('stop!')
                aborted = True
            else:            
                print(m, end=' ')
    if not aborted:
        print('finished.')
person Nick T    schedule 13.10.2010
comment
Ооо, мне это нравится, и я иногда хотел, чтобы это было в C. Также хороший синтаксис, который можно было бы без двусмысленности адаптировать к будущему C-подобному языку. - person supercat; 13.10.2010
comment
C-подобный язык :P - person nirvanaswap; 18.03.2016

Общее правило: если следование правилу требует от вас сделать что-то более неудобное и трудночитаемое, чем нарушение правила, то нарушайте правило.

В случае зацикливания до тех пор, пока вы что-то не найдете, вы столкнетесь с проблемой различения найденного и не найденного, когда выйдете. То есть:

for (int x=0;x<fooCount;++x)
{
  Foo foo=getFooSomehow(x);
  if (foo.bar==42)
    break;
}
// So when we get here, did we find one, or did we fall out the bottom?

Так что ладно, вы можете установить флаг или инициализировать «найденное» значение равным нулю. Но

Вот почему в целом я предпочитаю вводить свои поиски в функции:

Foo findFoo(int wantBar)
{
  for (int x=0;x<fooCount;++x)
  {
    Foo foo=getFooSomehow(x);
    if (foo.bar==wantBar)
      return foo;
  }
  // Not found
  return null;
}

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

person Jay    schedule 13.10.2010
comment
Именно то, что я хотел сказать. Часто, когда я использую break, я пытаюсь найти способ преобразовать код в функцию, чтобы вместо этого использовать return. - person Rene Saarsoo; 13.10.2010
comment
Я согласен с вами на 100%, нет ничего плохого в использовании break. Однако обычно это указывает на то, что код, в котором он находится, может потребоваться извлечь в функцию, где break будет заменен на return. Это следует рассматривать как «запах кода», а не как прямую ошибку. - person vascowhite; 29.12.2013
comment
Ваш ответ может побудить некоторых изучить целесообразность использования множественных операторов return. - person DavidRR; 16.09.2015

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

int[] iArray = new int[]{0,1,2,3,4,5,6,7,8,9};
int[] jArray = new int[]{0,1,2,3,4,5,6,7,8,9};

// label for i loop
iLoop: for (int i = 0; i < iArray.length; i++) {

    // label for j loop
    jLoop: for (int j = 0; j < jArray.length; j++) {

        if(iArray[i] < jArray[j]){
            // break i and j loops
            break iLoop;
        } else if (iArray[i] > jArray[j]){  
            // breaks only j loop
            break jLoop;
        } else {
            // unclear which loop is ending
            // (breaks only the j loop)
            break;
        }
    }
}

Я скажу, что операторы break (и return) часто увеличивают цикломатическую сложность, что затрудняет доказательство кода. делает правильную вещь во всех случаях.

Если вы рассматриваете возможность использования разрыва при повторении последовательности для какого-либо конкретного элемента, вам может потребоваться пересмотреть структуру данных, используемую для хранения ваших данных. Использование чего-то вроде набора или карты может дать лучшие результаты.

person cyber-monk    schedule 13.10.2010
comment
+1 за цикломатическую сложность. - person sp00m; 30.05.2013
comment
Программисты .NET могут захотеть изучить использование LINQ в качестве потенциальной альтернативы сложным for циклам. . - person DavidRR; 16.09.2015

break — вполне приемлемое выражение для использования (как и continue, кстати). Все дело в удобочитаемости кода — пока у вас нет слишком сложных циклов и тому подобного, все в порядке.

Не то чтобы они были той же лигой, что и goto. :)

person riviera    schedule 13.10.2010
comment
Я видел аргументы в пользу того, что оператор break является фактически goto. - person glenatron; 13.10.2010
comment
Проблема с goto в том, что вы можете указать его куда угодно. Как break, так и continue сохраняют модульность кода. Этот аргумент находится на том же уровне, что и наличие одной точки выхода для каждой функции. - person Zachary Yates; 13.10.2010
comment
Я слышал, что наличие нескольких точек выхода для функции является эффективным переходом. ;Д - person glenatron; 13.10.2010
comment
@glenatron Проблема с goto не в операторе goto, проблема заключается во введении метки, на которую переходит goto. Когда вы читаете оператор goto, нет никакой неопределенности в отношении потока управления. Когда вы читаете метку, возникает много неопределенности в отношении потока управления (где все переходы, которые передают управление этой метке?). Оператор break не вводит метку, поэтому не вносит никаких новых сложностей. - person Stephen C. Steel; 13.10.2010
comment
Я вполне согласен. Первый комментарий был в основном потому, что я видел этот аргумент, и он заставил меня улыбнуться, а затем второй комментарий, потому что я тоже видел этот аргумент, и это была отличная установка. Как всем известно, правильный способ справиться с такого рода вопросами потока управления — это генерировать исключение и позволять ему быть перехваченным какой-нибудь византийской системой обработки исключений. Гораздо меньше путаницы. ;п - person glenatron; 13.10.2010
comment
Разрыв — это специальный переход, который может оставлять только блоки. Исторические проблемы с goto заключались в следующем: (1) он мог использоваться и часто использовался для создания перекрывающихся блоков циклов таким образом, что это невозможно с надлежащими управляющими структурами; (2) общий способ сделать конструкцию if-then, которая обычно была ложной, заключался в том, чтобы истинный случай разветвлялся в несвязанное место в коде, а затем разветвлялся обратно. В редком случае это стоило бы двух ветвей, а в общем случае — нуля, но код выглядел бы отвратительно. - person supercat; 13.10.2010
comment
Оператор IF фактически является GOTO. :-) - person Jay; 18.05.2021

Это зависит от языка. Хотя вы можете проверить логическую переменную здесь:

for (int i = 0; i < 100 && stayInLoop; i++) { ... }

это невозможно сделать при переборе массива:

for element in bigList: ...

В любом случае, break сделает оба кода более читабельными.

person eumiro    schedule 13.10.2010

Я согласен с теми, кто рекомендует использовать break. Очевидный последовательный вопрос: почему кто-то рекомендует иначе? Ну... когда вы используете break, вы пропускаете остальную часть кода в блоке и оставшиеся итерации. Иногда это вызывает ошибки, например:

  • ресурс, полученный в верхней части блока, может быть высвобожден в нижней части (это верно даже для блоков внутри циклов for), но этот шаг высвобождения может быть случайно пропущен, когда «преждевременный» выход вызван оператором break (в « современный" C++, "RAII" используется для обработки этого надежным и безопасным способом: в основном, деструкторы объектов надежно освобождают ресурсы независимо от того, как происходит выход из области)

  • кто-то может изменить условный тест в операторе for, не заметив, что есть другие делокализованные условия выхода

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

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

  • у них есть среда разработки, которая поощряет определенный стиль программирования / кода, и у них есть статистические доказательства того, что это дает чистую выгоду в этой ограниченной среде, или
  • на них повлияли рекомендации по программированию или опыт работы в такой среде, или
  • они просто диктаторские идиоты, или
  • любой из вышеперечисленных + историческая инерция (актуальна тем, что обоснования более применимы к C, чем к современному C++).
person Tony Delroy    schedule 13.10.2010
comment
Ну да, но опять же, я видел ошибки в коде людей, которые искренне старались избегать break. Они избегали его, но не продумали условий, которые заменяли бы использование break. - person Tomáš Zato - Reinstate Monica; 16.10.2016

В вашем примере вы не знаете количество итераций для цикла for. Почему бы вместо этого не использовать цикл while, который допускает неопределенное количество итераций в начале?

Следовательно, нет необходимости использовать оператор break в целом, так как цикл лучше описать как цикл while.

person Schedler    schedule 13.10.2010
comment
Цикл for часто хорош, если известно максимальное количество итераций. При этом цикл while(1) иногда лучше в сценарии, когда кто-то ищет элемент и планирует создать его, если он не существует. В этом случае, если код находит элемент, он выполняет прямой разрыв, а если он достигает конца массива, он создает новый элемент, а затем выполняет разрыв. - person supercat; 13.10.2010
comment
В приведенном примере — поиск ключа в массиве — цикл for является правильной идиоматической конструкцией. Вы не используете цикл while для обхода массива. Вы используете цикл for. (или цикл for-each, если он доступен). Вы делаете это из любезности по отношению к другим программистам, так как они сразу распознают конструкцию for (int i=0; i ‹ ra.length; i++){} как обход массива. если можно заявление. - person Chris Cudmore; 13.10.2010

Совершенно правильно использовать break - как указывали другие, он нигде не находится в той же лиге, что и goto.

Хотя вы можете захотеть использовать переменную vFound, когда хотите вне цикла проверить, было ли найдено значение в массиве. Также с точки зрения удобства сопровождения может быть полезным наличие общего флага, сигнализирующего о критериях выхода.

person Ace    schedule 13.10.2010

Я не вижу причин, по которым это было бы плохой практикой, ПРИ УСЛОВИИ, что вы хотите завершить обработку STOP в этот момент.

person Cornelius J. van Dyk    schedule 13.10.2010

Я проанализировал кодовую базу, над которой сейчас работаю (40 000 строк JavaScript).

Я нашел только 22 утверждения break из них:

  • 19 были использованы внутри операторов switch (всего у нас есть только 3 оператора switch!).
  • 2 использовались внутри циклов for — код, который я сразу классифицировал как подлежащий рефакторингу в отдельные функции и замене оператором return.
  • Что касается последнего цикла break внутри while... Я запустил git blame, чтобы посмотреть, кто написал эту хрень!

Итак, согласно моей статистике: Если break используется вне switch, это запах кода.

Я также искал continue утверждений. Не нашел.

person Rene Saarsoo    schedule 13.10.2010

В мире встраиваемых систем существует множество кода, в котором используется следующая конструкция:

    while(1)
    { 
         if (RCIF)
           gx();
         if (command_received == command_we_are_waiting_on)
           break;
         else if ((num_attempts > MAX_ATTEMPTS) || (TickGet() - BaseTick > MAX_TIMEOUT))
           return ERROR;
         num_attempts++;
    }
    if (call_some_bool_returning_function())
      return TRUE;
    else
      return FALSE;

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

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

person Nate    schedule 13.10.2010
comment
Не могли бы вы уточнить это? Мне кажется, что цикл абсолютно ничего не делает... если только в логике приложения нет операторов continue. Здесь? - person Rene Saarsoo; 13.10.2010
comment
Операторы continue не нужны, так как вы уже связаны бесконечным циклом. В основном вы выполняете необходимую логику, необходимую для достижения цели, и если эта цель не достигается за n попыток или истекает время ожидания, вы выходите из цикла с помощью конструкции break и предпринимаете корректирующие действия или сообщаете вызывающему коду об ошибке. Я часто вижу этот тип кода в UC, которые обмениваются данными через общую шину (RS-485 и т. д.). Вы в основном пытаетесь занять линию и общаться без коллизий, если случаются коллизии, вы ждете продолжительность, определяемую случайным числом, и пытаетесь очередной раз. - person Nate; 16.10.2010
comment
И наоборот, если все идет хорошо, вы также используете оператор break для выхода после выполнения условия. В этом случае код, следующий за циклом, выполняется, и все довольны. - person Nate; 16.10.2010
comment
если (call_some_bool_returning_function()) вернуть TRUE; иначе вернуть ЛОЖЬ; можно просто вернуть call_some_bool_returning_function() - person frankster; 06.03.2014

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

В этих случаях имеет смысл даже установить флаг и проверять значение флага только ПОСЛЕ того, как все итерации цикла for фактически выполнены. Конечно, все итерации цикла for должны запускать код, который по-прежнему занимает примерно одинаковое время.

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

person ndim    schedule 13.10.2010
comment
Если время важно, вам может быть лучше сделать несколько снов и сэкономить системные ресурсы, чем позволить программе выполнять заведомо бесполезные операции. - person Bgs; 07.11.2013

В правилах MISRA 98, которые используются в моей компании в C dev, оператор break не должен использоваться. ...

Изменить: в MISRA '04 разрешен перерыв.

person Benoît    schedule 13.10.2010
comment
Именно поэтому я задал этот вопрос! Я не вижу веских причин, почему такое правило существует в качестве руководства по кодированию. Кроме того, как все остальные здесь сказали, перерыв; выглядит лучше. - person kiki; 13.10.2010
comment
Я не знаю точно, почему это запрещено (более умный человек, чем я, думал об этом...), но разрыв (и множественный возврат) лучше для удобочитаемости (на мой взгляд, но мое мнение не является корпоративными правилами... ) - person Benoît; 13.10.2010
comment
Эм, правила MISRA разрешают использование операторов break: misra .org.uk/forum/viewtopic.php?f=75&t=298 - person luis.espinal; 13.10.2010
comment
Мы использовали правила MISRA 98... Точно MISRA 2004 меняет правила - person Benoît; 14.10.2010

Конечно, break; — это решение для остановки цикла for или цикла foreach. Я использовал его в php в циклах foreach и for и нашел работу.

person gautamlakum    schedule 13.10.2010

Я не согласен!

Почему вы игнорируете встроенную функциональность цикла for, чтобы создать свою собственную? Вам не нужно изобретать велосипед.

Я думаю, что имеет смысл размещать ваши проверки в верхней части цикла for, например

for(int i = 0; i < myCollection.Length && myCollection[i].SomeValue != "Break Condition"; i++)
{
//loop body
}

или если вам нужно сначала обработать строку

for(int i = 0; i < myCollection.Length && (i == 0 ? true : myCollection[i-1].SomeValue != "Break Condition"); i++)
{
//loop body
}

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

for(int i = 0; i < myCollection.Length && (i == 0 ? true : myCollection[i-1].SomeValue != "Break Condition"); i++)
{
    DoAllThatCrazyStuff(myCollection[i]);
}

Или, если ваше состояние сложное, вы также можете удалить этот код!

for(int i = 0; i < myCollection.Length && BreakFunctionCheck(i, myCollection); i++)
{
    DoAllThatCrazyStuff(myCollection[i]);
}

«Профессиональный код», изобилующий пробелами, мне не кажется профессиональным кодом. Звучит как ленивое кодирование ;)

person Biff MaGriff    schedule 13.10.2010
comment
ленивое кодирование - это профессиональный код :) - person Jason; 13.10.2010
comment
Только если это не боль в прикладе поддерживать :) - person Biff MaGriff; 13.10.2010
comment
Я склонен практиковать GTFO как можно скорее. То есть, если я могу чисто выйти из структуры, я должен сделать это как можно скорее и как можно ближе к условию, определяющему выход. - person Chris Cudmore; 13.10.2010
comment
Ну это тоже работает. Я думаю, что аргумент, который я пытаюсь донести, заключается в том, что если вы используете цикл for, не повредит использовать его встроенную функциональность, чтобы сделать ваш код читабельным и модульным. Если вам абсолютно необходимо иметь операторы break внутри вашего цикла, я бы посидел и подумал о том, зачем нужен этот уровень сложности. - person Biff MaGriff; 14.10.2010
comment
Ха-ха. Учтите тот факт, что большинство людей, которые не одобряют break, утверждают, что это делается для удобочитаемости кода. Теперь взгляните еще раз на сумасшедшее состояние длиной 5 миль в петлях выше... - person Tomáš Zato - Reinstate Monica; 16.10.2016