Чи погана практика використовувати перерву в циклі for? [зачинено]


123

Чи погана практика використовувати breakоператор у forциклі ?

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

Це погана практика? Я бачив використану альтернативу: визначте змінну vFoundта встановіть її як true, коли знайдено значення та перевірте vFoundв forумові заяви. Але чи потрібно створити нову змінну саме для цієї мети?

Я прошу в контексті нормального C або C ++ для циклу.

PS: Вказівки щодо кодування MISRA не рекомендують використовувати перерви.


28
Не розміщуйтеся breakв тій же лізі, що goto:)
BoltClock

2
Е, я не розмістив їх у тій же лізі, що йти до ... Правила MISRA вказують перерву, якщо я правильно пам'ятаю.
кікі

1
Єдина причина, про яку я можу подумати, щоб не використовувати перерву в циклі, це те, що вам все одно доведеться обробити більше елементів, які можуть змінити результат того, що ви робите ...
Younes

4
Правила MISRA були послаблені: misra.org.uk/forum/viewtopic.php?f=75&t=298
Йохан Котлінський

Відповіді:


122

Тут багато відповідей, але я цього ще не бачив:

Більшість «небезпеки» , пов'язаних з використанням breakабо continueв циклі зведені на немає , якщо ви напишете акуратні, легко читаються петлі. Якщо тіло вашого циклу охоплює кілька довжин екрана і має кілька вкладених підблоків, так, ви можете легко забути, що якийсь код не буде виконаний після перерви. Якщо, однак, цикл короткий і до точки, мета заяви перерви повинна бути очевидною.

Якщо цикл стає занадто великим, замість цього використовуйте один або кілька добре названих функціональних викликів у циклі. Єдина реальна причина уникати цього - це обробка вузьких місць.


11
Цілком правда. Звичайно, якщо петля настільки велика і складна, що важко побачити, що відбувається всередині неї, це проблема, перерви чи ні.
Джей

1
Інша проблема полягає в тому, що перерва та продовження викликають проблеми із рефакторингом. Це може бути ознакою того, що це погана практика. Намір коду також зрозуміліший при використанні операторів if.
Ед Грівз

Ці "добре названі виклики функцій" можуть бути локально визначеними лямбда-функціями замість визначених зовні приватних функцій, які не використовуватимуться в іншому місці.
DavidRR

135

Ні, перерва - це правильне рішення.

Додавання булевої змінної робить код важчим для читання та додає потенційне джерело помилок.


2
домовились. Особливо, якщо ви хочете вийти з циклу за більш ніж однієї умови. Булевий варіант, щоб залишити цикл, може стати справді заплутаним.
Ронтолог

2
Ось чому я використовую gotoдля виходу з рівня вкладених циклів рівня 2+ - тому що його немаєbreak(level)
Петър Петров

3
Я вважаю за краще поставити код циклу у функцію та просто повернутися. Це дозволяє уникнути goto та розбиває ваш код на менші шматки.
Дейв Брантон

53

Ви можете знайти всілякі професійні коди із заявами про "розрив". Це абсолютно доцільно використовувати це, коли це необхідно. У вашому випадку цей варіант кращий, ніж створення окремої змінної лише з метою виходу з циклу.


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. Nice синтаксис, який можна було б перетворити на майбутню С-подібну мову без двозначності.
supercat

"C-like language": P
nirvanaswap

15

Немає нічого поганого в використанні заяви про перерву, але вкладені петлі можуть заплутатися. Для поліпшення читабельності багато мов ( принаймні 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;
        }
    }
}

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

Якщо ви плануєте використовувати перерву під час ітерації над послідовністю для якогось конкретного елемента, ви можете переглянути структуру даних, яка використовується для зберігання ваших даних. Використання на зразок набору чи карти може дати кращі результати.


4
+1 для цикломатичної складності.
sp00m

.NET-програмісти, можливо, захочуть вивчити використання LINQ як потенційної альтернативи складним forциклам.
DavidRR

14

break - це цілком прийнятне для використання твердження (так продовжується , btw). Вся справа в читабельності коду - доки у вас немає занадто складних циклів і подібного, це добре.

Це не так, як вони були тією ж лігою, що й Гото . :)


Я бачив, як він стверджував, що заява про перерву - це фактично гото .
гленатрон

3
Проблема goto полягає в тому, що ви можете вказати його куди завгодно. Обидва розбиваються і продовжують зберігати модульність коду. Цей аргумент знаходиться на тому ж рівні, що і для однієї функції є одна точка виходу.
Захарій Йейтс

1
Я чув, як він стверджував, що наявність декількох точок виходу для функції - це ефективно Goto . ; D
гленатрон

2
@glenatron Проблема з goto - це не оператор goto, це введення мітки, що goto переходить до цієї проблеми. Коли ви читаєте goto-заяву, немає сумнівів щодо потоку управління. Коли ви читаєте мітку, виникає велика невизначеність щодо потоку управління (де всі gotos, які передають керування на цю мітку?). Заява про перерву не вводить мітку, тому не вносить нових ускладнень.
Стівен К. Сталь

1
"Перерва" - це спеціалізований гото, який може залишати лише блоки. Історичні проблеми з "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;
}

Це також допомагає розмикати код. У головному рядку "знайти" стає єдиним твердженням, а коли умови складні, вони записуються лише один раз.


1
Саме те, що я хотів сказати. Часто, коли я опиняюсь як я використовую break, я намагаюся знайти якийсь спосіб перефабрикувати код у функції, щоб я міг використовувати його returnзамість цього.
Рене Саарсоо

Я погоджуюся на 100% з вами, немає нічого суттєво поганого в використанні перерви. Однак, як правило, вказується, що код, в якому він знаходиться, може знадобитися витягнути у функцію, коли перерва буде замінена поверненням. Це слід вважати «кодовим запахом», а не відвертою помилкою.
vascowhite

Ваша відповідь може підказати деяким вивчити доцільність використання кількох операторів повернення .
DavidRR

13

Це залежить від мови. Хоча ви можете перевірити булеву змінну тут:

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

це неможливо зробити при ітерації над масивом:

for element in bigList: ...

У будь-якому разі, breakзробить обидва коди читабельнішими.


7

У вашому прикладі ви не знаєте кількості ітерацій циклу for . Чому б не використовувати натомість цикл while, що дозволяє визначити кількість ітерацій на початку?

Тому загалом не потрібно використовувати переривання , оскільки цикл може бути викладений як цикл " час" .


1
Цикл "для" часто хороший, якщо відома максимальна кількість ітерацій. Як було сказано, цикл (1) іноді кращий в сценарії, коли людина шукає предмет і планує створити його, якщо його не існує. У цьому випадку, якщо код знайде елемент, він робить прямий перерву, і якщо він потрапляє в кінець масиву, він створює новий елемент, а потім робить перерву.
supercat

2
У наведеному прикладі - пошук за масивом ключа, цикл for - це правильна ідіоматична конструкція. Ви не використовуєте цикл часу, щоб пройти по масиву. Ви використовуєте цикл. (або цикл для кожного циклу, якщо він є). Ви робите це з люб'язності до інших програмістів, оскільки вони розпізнають конструкцію "for (int i = 0; i <ra.length; i ++) {}" як "Прогулянка по масиву" Додавання перерви, до цієї конструкції є просто заява "Вийдіть рано, якщо можете".
Кріс Кадмор

7

Я згоден з іншими, хто рекомендує використовувати 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 оператори переключення!).
  • 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;

Це дуже загальний приклад, за завісою відбувається багато речей, зокрема переривається. Не використовуйте це як код котла, я просто намагаюся проілюструвати приклад.

Моя особиста думка полягає в тому, що немає нічого поганого в тому, щоб написати цикл таким чином, якщо дотримуватися належних зусиль, щоб не залишатися в циклі на невизначений термін.


1
Не могли б ви детальніше зупинитися на цьому? Мені це здається, ніби цикл не робить абсолютно нічого ... якщо тільки continueв логіці програми немає тверджень. Є там?
Рене Саарсоо

1
продовжувати заяви зайві, оскільки ви вже пов'язані нескінченним циклом. Ви в основному виконуєте необхідну логіку, необхідну для досягнення мети, і якщо ця мета не досягнута за n кількість спроб або закінчилася умова очікування, ви виходите з циклу через перерву і вживаєте коригувальних дій або повідомляєте викликовий код про помилку. Я часто бачу цей тип коду в ЦК, які спілкуються через загальну шину (RS-485 тощо). Ви в основному намагаєтеся захопити лінію і спілкуватися без зіткнень, якщо зіткнення трапляються, ви чекаєте тривалості, визначеної випадковим числом, і намагаєтеся знову.
Нейт

1
І навпаки, якщо справи йдуть добре, ви також використовуєте заяву перерви для виходу після виконання умови. Враховуючи цей випадок, код після циклу виконується, і всі раді.
Нейт

"якщо (call_some_bool_returning_function ()) повернути TRUE; інакше повернути FALSE;" міг просто бути "return call_some_bool_returning_function ()"
frankster

3

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

У цих випадках навіть буде сенс встановити прапор і перевірити лише значення прапора ПІСЛЯ всіх forітерацій циклу, які фактично запущені. Звичайно, для всіх ітерацій циклу потрібно запустити код, який все ще займає приблизно стільки ж часу.

Якщо вам не байдуже час виконання програми, використовуйте break;та continue;полегшуйте код для читання.


1
Якщо терміни важливі, вам може бути краще спати і економити системні ресурси, ніж дозволяти програмі робити відомі непридатні операції.
Bgs

2

У правилах MISRA 98, які використовуються для моєї компанії в C dev, заява про перерву не повинна використовуватися ...

Редагувати: перерва дозволена в MISRA '04


2
Саме тому я задав це питання! Я не знаходжу вагомих причин, чому таке правило існує як настанова кодування. Також, як усі тут сказали, перерва; виглядає краще.
кікі

1
Я точно не знаю, чому це заборонено (розумніша людина, ніж я думав про це ...), але перерва (і багаторазове повернення) краще для читабельності (на мою думку, але моя думка не є правилами корпорацій ... )
Беноот

1
Так, правила MISRA дозволяють використовувати заяви про перерви: misra.org.uk/forum/viewtopic.php?f=75&t=298
luis.espinal

Ми використовували правила MISRA 98 ... Точно, що MISRA 2004 змінює правила
Benoît

0

Звичайно, break;це рішення зупинити цикл for або петлі foreach. Я використовував його в php в foreach і for loop і виявив, що працює.


0

Я не погоджуюсь!

Чому б ви ігнорували вбудовану функціональність циклу 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
ліниве кодування IS професійний код :)
Джейсон

Тільки якщо це не біль у
задці,

2
Я схильний практикувати "GTFO ASAP". Тобто, якщо я можу вийти з структури чисто, тоді я повинен це зробити якнайшвидше, і якнайближче до умови, що визначає вихід.
Кріс Кадмор

Добре, що це теж працює. Я думаю, що аргумент, який я намагаюся натрапити, полягає в тому, що якщо ви використовуєте цикл, це не завадить використовувати вбудовану функціональність, щоб зробити ваш код читабельним і модульним. Якщо вам абсолютно потрібно мати заяви про розрив всередині вашого циклу, я б сидів і замислювався над тим, для чого потрібен такий рівень складності.
Біф Магріф

1
Ха-ха. Розглянемо той факт, що більшість людей, які відлякують, breakстверджують, що це для читабельності коду. А тепер погляньте ще раз на шалений стан довгих 5 миль у петлях вище ...
Томаш Зато - Відновіть Моніку
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.