Является ли использование маркированного прерывания хорошей практикой в ​​Java?

82

Я смотрю на какой-то старый код 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? Это вообще хорошая практика? Меня это беспокоит.

avgvstvs
источник
2
где label913определяется?
Николай Кузнецов
1
В этом случае примерно через 200 строк в конце довольно большого оператора switch.
avgvstvs
continue и label name заставят код обработать ту часть кода, где блок был помечен меткой
user_CC
5
В этом случае проблема заключается в размере оператора switch!
Cyrille Ka
8
Это похоже на сгенерированный код (на самом деле парсер). С сгенерированным кодом используется множество приемов, которые нельзя использовать в написанном от руки коде (прежде всего потому, что они упрощают генерацию).
парсифаль

Ответы:

120

Нет, это не похоже на goto, поскольку вы не можете «перейти» к другой части потока управления.

Со страницы, на которую вы указали:

Оператор 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 Не совсем, вы все еще не можете выпрыгнуть из потока управления. Обозначенный continue просто запустит следующую итерацию помеченного цикла и будет работать, только пока этот цикл уже выполняется.
Thomas
3
так что, если бы я использовал break first;, компилятор вернулся бы к строке сразу после first:и снова выполнил бы эти циклы?
Ungeheuer
15
@JohnnyCoder нет, break first;не будет нарушать (конец) петля маркированы first, то есть он будет продолжать third. С другой стороны , continue first;положит конец текущей итерации firstи разорвать secondвместе с этим и будет продолжать следующее значение iв first.
Thomas
1
@TheTechExpertGuy ну не совсем. Это только gotoв более или менее похожем смысле на if-elseутверждение. Goto гораздо более «гибкие», как в «идти куда угодно», например, в позицию перед циклом (на самом деле, как старые языки использовались для реализации циклов: «если условие не выполнено, перейти к началу тела цикла (снова)») . Недавно мне приходилось иметь с ними дело, и поверьте мне: gotoэто настоящая заноза в заднице, а маркированные перерывы - нет.
Томас
1
@TheTechExpertGuy, пожалуйста :) - Поскольку вы новичок, я склонен дать совет еще раз: не используйте его gotoвообще, даже если C ++ это поддерживает. Почти всегда есть лучший способ решить проблемы, который впоследствии избавит вас от головной боли. Может возникнуть искушение ... сопротивляйтесь!
Томас
16

Это не так ужасно, gotoпотому что он отправляет управление только в конец помеченного оператора (обычно это конструкция цикла). Дело в том, что делает gotoнесимпатичным, что это произвольная ветвь в любое место, в том числе этикетки нашла выше в источнике метода, так что вы можете иметь доморощенное зацикливание поведения. Функциональность разрыва ярлыков в Java не допускает такого безумия, потому что контроль только продвигается вперед.

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

Натан Хьюз
источник
6
@Boann: я не хочу сказать, что goto - это зло во всех контекстах. это просто одна из тех вещей, как арифметика указателей, перегрузка операторов и явное управление памятью, которые создатели Java решили, что они не стоят того. И, прочитав программы COBOL, которые, казалось, на 90% состояли из gotos, мне не жаль, что они оставили его вне Java.
Натан Хьюз
1
О, ты был прав в первый раз, @NathanHughes, goto чертовски злой. Не во всех ситуациях, конечно, но да, я этого не скучаю.
Perception
2
goto может быть более "полезным", чем ярлыки @Boann, но это полностью перевешивает стоимость их обслуживания. метки - это не волшебная палочка, как gotos, и это хорошо.
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).

Logicray
источник
На мой взгляд, это почти всегда правильный подход. goto(потенциально) вредно, потому что может отправить вас в очень много мест - совсем не очевидно, что делается; создание вспомогательной функции дает четкие границы следующему человеку в отношении того, что вы пытаетесь сделать и где вы собираетесь это делать, и дает вам возможность назвать эту часть функциональности. Этот способ более понятен.
Итанбустад
@ethanbustad Но речь идет о маркировке break, а не о ней goto. Конечно, показанный здесь рефакторинг - хорошая идея в обоих случаях. Но breakLabeled не так уж немодерирован и склонен к спагеттификации кода, как gotoесть.
underscore_d
Этот пример ужасен, break не работает, если, метка здесь просто бесполезна.
Диракнот
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являются более чистыми и менее вредными.
Брендан
1

Это не похоже на gotoутверждение, в котором вы перемещаете управление потоком назад. Ярлык просто показывает вам (программисту), где произошел разрыв. Кроме того, управление потоком передается следующему оператору после break.

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

noMAD
источник
for(int i = 0; i < 1; i++) { do_something(); if(I_want_to_go_backwards) { i = 0; continue; } }.
Брендан
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;
    }
}

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

Марек
источник