Чи є використання позначеної перерви гарною практикою в Java?


81

Я дивлюсь на якийсь старий код 2001 року і натрапив на таке твердження:

   else {
     do {
       int c = XMLDocumentFragmentScannerImpl.this.scanContent();
       if (c == 60) {
         XMLDocumentFragmentScannerImpl.this.fEntityScanner.scanChar();
         XMLDocumentFragmentScannerImpl.this.setScannerState(1);
         break label913;
       }

Я ніколи раніше цього не бачив і виявив тут позначені перерви:

http://docs.oracle.com/javase/tutorial/java/nutsandbolts/branch.html

Чи не по суті це функціонує так goto? Це навіть хороша практика використовувати його? Мені стає неприємно.


2
де label913визначено?
Микола Кузнєцов

1
У цьому випадку, приблизно 200 рядків пізніше, в кінці досить великого оператора перемикання.
avgvstvs

continue і ім'я мітки зробить код для обробки тієї частини коду, де блок був позначений міткою
user_CC

5
Тоді в цьому випадку проблема полягає в розмірі оператора switch!
Cyrille Ka

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

Відповіді:


119

Ні, це не як перехід, оскільки ви не можете "перейти" до іншої частини потоку управління.

Зі сторінки, на яку ви зробили посилання:

Оператор break закінчує мічений оператор; це не передає потік контролю на етикетку. Потік керування передається у виписку відразу після позначеного (припиненого) виписки.

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

Розглянемо цей приклад:

first:
for( int i = 0; i < 10; i++) {
  second:
  for(int j = 0; j < 5; j ++ )
  {
    break xxx;
  }
}

third:
for( int a = 0; a < 10; a++) {

}

Ви можете замінити xxxна firstабо second(щоб розірвати зовнішній або внутрішній цикл), оскільки обидва цикли виконуються, коли ви натискаєте breakоператор, але заміна xxxна thirdне компілюється.


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

3
так, якби я використовував break first;, чи компілятор одразу ж повернувся б до рядка first:і знову виконав ці цикли?
Ungeheuer

15
@JohnnyCoder немає, break first;НЕ буде порушувати (кінець) петля марковані first, тобто він буде продовжувати third. З іншого боку continue first;, закінчуватиметься поточна ітерація firstта розриватиметься secondразом із нею і продовжуватиметься із наступним значенням iin first.
Томас

1
@TheTechExpertGuy ну, не дуже. Це лише подібне до gotoбільш-менш того самого сенсу if-elseтвердження. Goto набагато більш "гнучкі", як у "йти куди завгодно", наприклад, до позиції перед циклом (насправді саме так використовувались старі мови для реалізації циклів: "якщо умова не виконана, перейдіть до початку тіла циклу (знову)") . Нещодавно мені доводилося з ними мати справу, і повірте мені: gotoчи справжній біль у попі, тоді як позначені перерви - ні.
Томас

1
@TheTechExpertGuy, ласкаво просимо :) - Оскільки ви новачок, я схильний ще раз дати пораду: взагалі не використовувати goto, навіть якщо C ++ це підтримує. Майже завжди є кращий спосіб вирішити речі, які позбавлять вас від головного болю згодом. Спокуса може прийти ... чиніть опір!
Томас

16

Це не так страшно, як gotoтому, що воно надсилає керування лише до кінця позначеного оператора (як правило, конструкція циклу). Те, що робить gotoнеможливим, це те, що це довільна гілка в будь-якому місці, включаючи мітки, знайдені вище в джерелі методу, так що ви можете мати домашню циклічну поведінку. Функціональність розбиття ярликів у Java не дозволяє такого роду шаленостей, оскільки контроль йде лише вперед.

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


6
@Boann: я не маю на увазі припускати, що goto - це взагалі зло у всіх контекстах. це лише одне з таких речей, як арифметика покажчиків, перевантаження оператора та явне управління пам’яттю, за якими творці Java вирішили, що не вартує клопоту. І прочитавши програми COBOL, які, здавалося, перевищували 90% gotos, я не сумую, що вони залишили це поза Java.
Nathan Hughes

1
О, ви мали рацію вперше @NathanHughes, goto - це прокляте зло. Звичайно, не у всіх ситуаціях, але так, я, наприклад, не пропускаю цього.
Сприйняття

2
goto може бути більш "корисним", ніж ярлики, @Boann, але це повністю переважає їх вартість обслуговування. етикетки - це не чарівна паличка, якою є гото, і це добре.
DavidS

9

Завжди можна замінити breakновим методом.

Розглянемо перевірку коду на наявність загальних елементів у двох списках:

List list1, list2;

boolean foundCommonElement = false;
for (Object el : list1) {
    if (list2.contains(el)) {
        foundCommonElement = true;
        break;
    }
}

Ви можете переписати його так:

boolean haveCommonElement(List list1, List list2) {
    for (Object el : list1) {
        if (list2.contains(el)) {
            return true;
        }
    }
    return false;
}

Звичайно, щоб перевірити наявність загальних елементів між двома списками це краще використовувати list1.retainAll(new HashSet<>(list2))метод , щоб зробити це в O(n)з O(n)додатковою пам'яті, або сортування обох списків в , O(n * log n)а потім знайти на загальні елементи O(n).


На мій погляд, це майже завжди правильний підхід. gotoє (потенційно) шкідливим, оскільки може надіслати вам стільки місць - зовсім не очевидно, що робиться; створення допоміжної функції дає чіткі межі наступному хлопцеві щодо того, що ви намагаєтеся зробити і де ви збираєтеся це зробити, і це дає вам можливість назвати цю функцію. Набагато зрозуміліший шлях.
ethanbustad

@ethanbustad Але це приблизно позначено break, ні goto. Звичайно, показаний тут рефакторинг - хороша ідея в обох випадках. Але маркований breakне є настільки немодерованим і схильним до спагетіфікації коду, як gotoє.
underscore_d

Цей приклад жахливий, перерва не працює, якщо мітка тут просто марна.
Діракнот,

8

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

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

Або, перефразовуючи, проблема gotoполягає в тому, що програма може прибути в середині блоку коду, без того, щоб програміст зрозумів стан програми на той момент. Стандартні конструкції, орієнтовані на блок, призначені для чіткого розмежування переходів стану, мітка breakпризначена для переведення програми в певний відомий стан (за межами міченого блоку, що містить).

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

Отже, загалом, я б вважав позначених breakяк небезпечні. На мій погляд, це ознака того, що блок повинен бути перетворений у функцію, з обмеженим доступом до охоплюючої області.

Однак цей прикладний код явно був продуктом генератора синтаксичного аналізатора (OP зауважив, що це вихідний код Xerces). А генератори синтаксичного аналізатора (або генератори коду загалом) часто беруть на себе свободу з кодом, який вони генерують, оскільки вони прекрасно знають стан, і людям не потрібно його розуміти.


Я не згоден. breakє досить корисним кілька разів. Наприклад, коли ви шукаєте якесь значення, яке може надходити з кількох упорядкованих джерел. Ви намагаєтеся по одному, а коли перше знайдете, ви break. Це більш елегантний код, ніж if / else if / else if / else if. (Принаймні це менше рядків.) Переміщення всього в контекст методу не завжди може бути практичним. Інший випадок - якщо ви умовно перериваєтеся з кількох вкладених рівнів. Коротше кажучи, якщо вам не подобається break, то вам не подобається returnде-небудь ще, крім кінця методу.
Ondra Žižka

Для контексту; «Go To Заява вважається шкідливим» було написано , коли такі речі , як doі whileне існує , і все використовували if(.. ) gotoскрізь замість цього. Він мав на меті заохотити мовних дизайнерів додати підтримку таким речам, як doі while. На жаль, як тільки бандаж почав котитися, він перетворився на ажіотажний поїзд, що підживлюється невігласами, вкладаючи речі на зразок breakциклів "лише один раз" і кидаючи винятки, хто знає що, для всіх (рідкісних) випадків, що gotoє чистішим і менш шкідливим.
Brendan

1

Це не схоже на gotoтвердження, коли ви стрибаєте регулятор потоку назад. Етикетка просто показує вам (програмісту), де стався обрив. Крім того, керування потоком передається до наступного оператора після break.

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


for(int i = 0; i < 1; i++) { do_something(); if(I_want_to_go_backwards) { i = 0; continue; } }.
Brendan

1

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

Наприклад, змініть це:

someLabel:
for (int j = 0; j < 5; j++)
{
    // do something
    if ...
        break someLabel;
}

в це:

private void Foo() {
    for (int j = 0; j < 5; j++)
    {
        // do something
        if ...
            return;
    }
}

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.