Подтвердить что ты не робот

Плохо ли использовать выражение "goto"?

После некоторого изучения вопроса о том, как прорвать вторичный цикл

while (true) { // Main Loop
   for (int I = 0; I < 15; I++) { // Secondary loop
       // Do Something
       break; // Break main loop?
   }
}

большинству людей рекомендуется называть функцию 'goto'
Как выглядит следующий пример:

while (true) { // Main Loop
   for (int I = 0; I < 15; I++) { // Secondary Loop
       // Do Something
       goto ContinueOn; // Breaks the main loop
   }
}
ContinueOn:

Тем не менее; Я часто слышал, что утверждение "goto" - это плохая практика. Рисунок ниже прекрасно иллюстрирует мою мысль: Series found

Так

  • Как плохо работает инструкция goto и почему?
  • Есть ли более эффективный способ разбить основной цикл, чем использовать оператор 'goto'?
4b9b3361

Ответ 1

ИЗМЕНИТЬ:

Насколько плохое утверждение goto действительно и почему?

Это зависит от конкретной ситуации. Я не могу вспомнить, когда я нашел, что код стал более читаемым, чем рефакторинг. Это также зависит от вашего личного взгляда на читаемость - некоторые люди не любят его больше, чем другие, как видно из других ответов. (В качестве точки интереса он широко используется в сгенерированном коде - весь код async/await в С# 5 основан на эффективном множестве gotos).

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

Есть ли более эффективный способ разбить основной цикл, чем использовать оператор 'goto'?

Совершенно верно. Извлеките свой метод в отдельную функцию:

while (ProcessValues(...))
{
    // Body left deliberately empty
}

...

private bool ProcessValues()
{
   for (int i = 0; i < 15; i++)
   {
       // Do something
       return false;
   }
   return true;
}

Обычно я предпочитаю делать это, вводя дополнительную локальную переменную, чтобы отслеживать "я закончил", хотя это будет работать, конечно.

Ответ 2

Насколько плохое утверждение goto действительно и почему?

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

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

Замена его булеанами и связкой дополнительных ifs и breaks просто неудобно и затрудняет выполнение реальных намерений, таких как любой шум.

В java (и javascript) это вполне приемлемо (обозначенные петли):

outer: while( true ) {
    for( int i = 0; i < 15; ++i ) {
        break outer;
    }
}

В С# это выглядит как очень близкий эквивалент:

while( true ) {
   for (int I = 0; I < 15; I++) { 
       goto outer;
   }
}
outer:;

Из-за слова goto, который имеет психологический эффект от того, чтобы заставить людей отказаться от своего здравого смысла и заставить их связывать xkcd независимо от контекста.

Есть ли более эффективный способ разбить основной цикл, чем использовать оператор 'goto'?

В некоторых случаях этого не происходит, поэтому другие языки предоставляют помеченные циклы, а С# предоставляет goto. Обратите внимание, что ваш пример слишком прост, и он делает рабочие обходы не выглядят слишком плохими, потому что они адаптированы к этому примеру. Фактически, я мог бы просто предложить следующее:

   for (int I = 0; I < 15; I++) {
       break;
   }

Как насчет этого:

int len = 256;
int val = 65536;

for (int i = 0; i < len; i++)
{
    for (int j = 0; j < len; j++)
    {
        if (i + j >= 2 * val)
        {
            goto outer;
        }
        val = val / 2;
    }
}
outer:;

До сих пор это выглядит хорошо:

int len = 256;
int val = 65536;

for (int i = 0; i < len; i++)
{
    if (!Inner(i, ref val, len))
    {
        break;
    }
}

private bool Inner(int i, ref int val, int len)
{
    for (int j = 0; j < len; j++)
    {
        if (i + j >= 2 * val)
        {
            return false;
        }

        val = val / 2;
    }

    return true;
}

Ответ 3

Я решительно не соглашусь со всеми остальными ответами здесь. Код, который вы используете с помощью goto, не имеет ничего плохого. Существует причина, по которой С# имеет оператор goto, и именно для этих типов сценариев вы описываете.

goto просто имеет отрицательную стигму, потому что в 1970-х годах и раньше люди писали бы ужасный, совершенно неподъемный код, в котором поток управления прыгал по всему месту из-за goto. С# goto даже не позволяет переходить между методами! Но до сих пор существует иррациональная стигма.

По моему мнению, нет абсолютно ничего плохого в использовании "современного" goto для выхода из внутреннего цикла. Люди "альтернативы" всегда предлагают сложнее и труднее читать.

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

Такое глупое упражнение, чтобы избежать goto, в основном эквивалентно политической корректности, но в мире программирования.

Ответ 4

Я согласен с большинством ответов о том, как плохо получается.

Мое предположение, чтобы избежать goto, делает что-то вроде этого:

while (condition) { // Main Loop
   for (int i = 0; i < 15; i++) { // Secondary loop
       // Do Something
       if(someOtherCondition){
              condition = false // stops the main loop
              break; // breaks inner loop
       }
   }
}

Ответ 5

Я иногда использую "goto", и я нашел, что он выглядит хорошо, как пример выше;

bool AskRetry(Exception ex)
{
  return MessageBox.Show(you know here...) == DialogResult.Retry;
}

void SomeFuncOrEventInGui()
{
  re:try{ SomeThing(); }
  catch (Exception ex)
  {
    if (AskRetry(ex)) goto re;
    else Mange(ex); // or throw or log.., whatever...
  }
}

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

Ответ 6

Мой коллега (у которого есть 15 лет + в программировании прошивки), и я все время использую goto. Однако мы используем его только для обработки исключений!! Например:

if (setsockopt(sd,...)<0){
goto setsockopt_failed;
}
...

setsockopt_failed:
close(sd);

Насколько я знаю, это лучший способ справиться с исключениями в C. Я считаю, что я тоже работаю над С#. Кроме того, я не единственный, кто так думает: Примеры хороших gotos на C или С++

Ответ 7

Идти опасны помимо других проблем с упоминанием. Goto обходить правильную компиляцию в некоторых языках, например С#, несмотря на то, что в этой функции нет операторов возврата, это скомпилирует.

public int Test()
{
    goto one;
    one:
    {
        goto one;
    }
}

Ответ 8

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

Тем не менее, он по-прежнему применяется по какой-либо причине, и при его использовании его следует использовать экономно, если нет другого решения. Языки OO поддаются на это не очень важно (много).

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

Некоторые языки зависят от эквивалентных ключевых слов. Например, в x86 году у вас есть такие ключевые слова, как JMP (Jump), JE (Jump if Equal) и JZ (Jump if Zero). Они требуются часто на языке ассемблера, поскольку на этом уровне нет OO, и есть несколько других способов перемещения в приложении.

AFAIK... избегайте этого, если АБСОЛЮТНО НЕОБХОДИМО.