Использование break в цикле for - плохая практика? [закрыто]

123

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

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

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

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

PS: Руководство по кодированию MISRA не рекомендует использовать break.

Кики
источник
28
Не breakgoto
попадать
2
Эээ, я не поместил их в ту же лигу, что и go ... Правила MISRA указывают перерыв, если я правильно помню.
kiki
1
Единственная причина, по которой я могу не использовать break внутри цикла, - это когда вам все еще нужно обрабатывать больше элементов, которые могут изменить результат того, что вы делаете ...
Юнес,
4
Правила MISRA были смягчены: misra.org.uk/forum/viewtopic.php?f=75&t=298
Йохан Котлински,
1
Дублировано: programmers.stackexchange.com/a/58253/72317
Фелипе

Ответы:

122

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

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

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

e.James
источник
11
Совершенно верно. Конечно, если цикл настолько большой и сложный, что трудно увидеть, что внутри него происходит, это проблема, есть ли у вас перерыв или нет.
Джей
1
Другая проблема заключается в том, что break и continue вызывают проблемы с рефакторингом. Это может быть признаком того, что это плохая практика. Намерение кода также становится яснее при использовании операторов if.
Эд Гривз
Эти «хорошо названные вызовы функций» могут быть локально определенными лямбда-функциями вместо определенных извне частных функций, которые не будут использоваться где-либо еще.
DavidRR
135

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

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

smirkingman
источник
2
согласовано. Особенно, если вы хотите выйти из цикла более чем при одном условии. Тогда логическое значение выхода из цикла может сильно запутать.
Ронтолог
2
Вот почему я использую gotoдля выхода из вложенных циклов Уровня 2+ - потому что их нетbreak(level)
Петър Петров
3
Я бы предпочел поместить код цикла в функцию и просто вернуться. Это позволяет избежать goto и разбить ваш код на более мелкие части.
Дэйв Брантон,
53

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

Амит С
источник
47

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

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


источник
Да ... Какое-то время мне не нравилась эта идея, и я закодировал ее, но вскоре я понял, что ... "обходной путь" часто был кошмаром обслуживания. Что ж, не кошмар, но более подверженный ошибкам.
MetalMikester
24

В отличие от плохой практики, 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.')
Ник Т
источник
2
Ооо, мне это нравится, и иногда мне хотелось, чтобы это было и в C. Хороший синтаксис, который можно без двусмысленности адаптировать в будущий C-подобный язык.
supercat
«C-подобный язык»: P
nirvanaswap
15

Нет ничего плохого в использовании оператора 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) часто увеличивают цикломатическую сложность, что затрудняет доказательство того, что код делает правильные вещи во всех случаях.

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

кибер-монах
источник
4
+1 за цикломатическую сложность.
sp00m
Программисты .NET могут захотеть изучить использование LINQ в качестве потенциальной альтернативы сложным forциклам.
DavidRR
14

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

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

Ривьера
источник
Я видел , как это утверждал , что заявление перерыва является эффективно Гото .
glenatron
3
Проблема с goto в том, что вы можете указать его куда угодно. И break, и continue, сохраняют модульность кода. Этот аргумент находится на том же уровне, что и одна точка выхода для каждой функции.
Zachary Yates,
1
Я слышал , что утверждал , что наличие нескольких точек выхода для функции является эффективно Гото . ; D
glenatron
2
@glenatron Проблема с goto заключается не в инструкции goto, а во введении метки, на которую переходит goto. Когда вы читаете инструкцию goto, нет никакой неопределенности относительно потока управления. Когда вы читаете метку, возникает большая неопределенность в отношении потока управления (где все goto, передающие управление этой метке?). Оператор break не вводит метку, поэтому не вносит никаких новых сложностей.
Стивен С. Стил
1
«Break» - это специальный переход, который может только оставлять блоки. Исторические проблемы с "goto" заключались в том, что (1) он мог быть и часто использовался для построения перекрывающихся блоков цикла таким образом, что невозможно при правильных структурах управления; (2) распространенный способ создания конструкции «если-то», которая обычно была «ложной», состояла в том, чтобы истинный регистр разветвлялся в несвязанное место в коде, а затем возвращался. Это стоило бы двух веток в редком случае и нуля в общем случае, но это делало код ужасным.
supercat
14

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

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

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

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

сойка
источник
1
Именно то, что я хотел сказать. Часто, когда я обнаруживаю, что использую break, я пытаюсь найти способ преобразовать код в функцию, чтобы я мог использовать ее returnвместо этого.
Рене Саарсу,
Я согласен с вами на 100%, в использовании break нет ничего плохого. Однако обычно это указывает на то, что код, в котором он находится, может потребоваться извлечь в функцию, в которой break будет заменен на return. Это следует рассматривать как «запах кода», а не как явную ошибку.
vascowhite
Ваш ответ может побудить некоторых исследовать целесообразность использования нескольких операторов возврата .
DavidRR
13

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

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

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

for element in bigList: ...

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

eumiro
источник
7

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

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

Schedler
источник
1
Цикл for часто бывает хорош, если известно максимальное количество итераций. Как уже было сказано, цикл while (1) иногда лучше в сценарии, когда кто-то ищет элемент и планирует создать его, если он не существует. В этом случае, если код находит элемент, он выполняет прямой разрыв, а если он достигает конца массива, он создает новый элемент, а затем выполняет разрыв.
supercat
2
В приведенном примере - поиск ключа в массиве - цикл for является правильной идиоматической конструкцией. Вы не используете цикл while для обхода массива. Вы используете цикл for. (или цикл для каждого, если он доступен). Вы делаете это из уважения к другим программистам, поскольку они сразу узнают конструкцию «for (int i = 0; i <ra.length; i ++) {}» как «Прогулка по массиву». Добавление разрыва в эту конструкцию означает просто заявление «Уходите пораньше, если можете».
Крис Кадмор,
7

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

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

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

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

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

  • у них есть структура разработки, которая поощряет определенный стиль программирования / кода, и у них есть статистические доказательства того, что это дает чистую выгоду в этой ограниченной структуре, или
  • на них повлияли рекомендации по программированию или опыт в такой структуре, или
  • они просто диктаторские идиоты, или
  • любой из вышеперечисленных + историческая инерция (актуально в том смысле, что обоснования более применимы к C, чем к современному C ++).
Тони Делрой
источник
Ну, да, но опять же, я видел ошибки в коде людей, которые неукоснительно старались их избегать break. Они избежали этого, но не смогли продумать условия, которые заменили использование перерыва.
Томаш Зато - Восстановите Монику
5

Его вполне допустимо использовать break- как отмечали другие, он нигде не в той же лиге, что и goto.

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

туз
источник
5

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

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

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

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

Я тоже искал continueзаявления. Не нашел.

Рене Саарсу
источник
4

Я не вижу причин, по которым это было бы плохой практикой, ПРЕДОСТАВЛЯЕМЫЙ, когда вы хотите завершить обработку STOP на этом этапе.

Корнелиус Дж. Ван Дайк
источник
4

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

    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;

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

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

Nate
источник
1
Не могли бы вы подробнее рассказать об этом? Мне кажется, что цикл абсолютно ничего не делает ... если только continueвнутри логики приложения нет операторов. Здесь?
Рене Саарсу,
1
Операторы continue не нужны, поскольку вы уже связаны бесконечным циклом. Вы в основном выполняете необходимую логику, необходимую для достижения цели, и если эта цель не достигается за n попыток или истекает условие тайм-аута, вы выходите из цикла с помощью конструкции прерывания и предпринимаете корректирующие действия или сообщаете вызывающему коду об ошибке. Я часто вижу этот тип кода в uCs, которые обмениваются данными через общую шину (RS-485 и т. Д.). Вы в основном пытаетесь захватить линию и общаться без коллизий, если коллизии случаются, вы ждете время, определяемое случайным числом, и пытаетесь очередной раз.
Нейт
1
И наоборот, если все идет хорошо, вы также используете оператор break для выхода после выполнения условия. В этом случае выполняется код, следующий за циклом, и все довольны.
Нейт
"if (call_some_bool_returning_function ()) return TRUE; else return FALSE;" может быть просто «return call_some_bool_returning_function ()»
Франкстер
3

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

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

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

ndim
источник
1
Если время имеет важное значение, возможно, лучше будет немного поспать и сэкономить системные ресурсы, чем позволить программе выполнять заведомо бесполезные операции.
Bgs
2

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

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

Benoît
источник
2
Именно поэтому я задал этот вопрос! Я не нахожу веской причины, по которой такое правило используется в качестве руководства по кодированию. Также, как все здесь сказали, перерыв; выглядит лучше.
kiki
1
Я не знаю точно, почему это запрещено (более умный человек, чем я, думает об этом ...), но разрыв (и многократный возврат) лучше для читаемости (на мой взгляд, но мое мнение не является корпоративными правилами ... )
Бенуа
1
Правила MISRA разрешают использование операторов прерывания
luis.espinal
Мы использовали правила MISRA 98 ... Это точно, что MISRA 2004 меняет правила
Бенуа,
0

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

gautamlakum
источник
0

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

Почему вы игнорируете встроенные функции цикла 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]);
}

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

Бифф МаГриф
источник
4
ленивое кодирование - это профессиональный код :)
Джейсон
Только если это не головная боль в поддержании :)
Biff MaGriff
2
Я стараюсь практиковать GTFO как можно скорее. То есть, если я могу выйти из структуры чисто, то я должен сделать это как можно скорее и как можно ближе к условию, определяющему выход.
Крис Кадмор,
Это тоже работает. Я думаю, что аргумент, который я пытаюсь объяснить, заключается в том, что если вы используете цикл for, не повредит использовать его встроенные функции, чтобы сделать ваш код читаемым и модульным. Если вам абсолютно необходимо иметь операторы прерывания внутри вашего цикла, я бы сел и подумал, зачем нужен такой уровень сложности.
Biff MaGriff,
1
Ха - ха. Примите во внимание тот факт, что большинство людей, которые отговариваются, breakутверждают, что это сделано для удобства чтения кода. А теперь посмотрите еще раз на сумасшедшие условия длиной в 5 миль в петлях выше ...
Томаш Зато - Восстановите Монику