"Гото" это плохо?
После некоторого исследования о том, как прорваться через вторичный цикл
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" - плохая практика. Картинка ниже прекрасно иллюстрирует мою точку зрения:
Так
- Насколько действительно плохое утверждение goto и почему?
- Есть ли более эффективный способ разорвать основной цикл, чем использование оператора 'goto'?
10 ответов
РЕДАКТИРОВАТЬ:
Насколько действительно плохое утверждение goto и почему?
Это зависит от конкретной ситуации. Я не могу вспомнить, когда я обнаружил, что код стал более читабельным, чем рефакторинг. Это также зависит от вашего личного восприятия читабельности - некоторым людям это нравится больше, чем другим, что видно из других ответов. (Интересно, что он широко используется в сгенерированном коде - весь асинхронный / ожидающий код в C# 5 эффективно основан на большом количестве goto).
Проблема в том, что ситуации, когда 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;
}
Я обычно предпочитаю делать это, а не вводить дополнительную локальную переменную, чтобы отслеживать "закончил ли я", хотя, конечно, это сработает.
Я собираюсь сильно не согласиться со всеми другими ответами здесь. Код, который вы используете goto
в этом нет ничего плохого. Есть причина, по которой C# имеет goto
утверждение, и именно для этих типов сценариев, которые вы описываете.
goto
просто имеет негативную стигму, потому что в 1970-х годах и прежние люди писали ужасный, совершенно не поддерживаемый код, где поток управления перепрыгивал повсюду из-за goto
, C#'s goto
даже не позволяет переходить между методами! И все же все еще существует иррациональное клеймо против него.
На мой взгляд, нет ничего плохого в использовании "модерна" goto
вырваться из внутренней петли. "Альтернативы", предлагаемые людьми, всегда оказываются более сложными и трудными для чтения.
Методы, как правило, должны быть многоразовыми. Создание целого отдельного метода для внутренней части цикла, который будет вызываться только из этого одного места, и где реализация метода может оказаться в некотором отдаленном месте в исходном коде, не является улучшением.
Этот вид глупых упражнений, чтобы избежать goto
в основном эквивалент политкорректности, но в мире программирования.
Насколько действительно плохое утверждение goto и почему?
Это действительно плохо по всем приведенным нормальным причинам. Это прекрасно, когда эмулирует помеченные циклы в языках, которые их не поддерживают.
Замена его функциями во многих случаях приведет к разбросу логики, которая действительно должна читаться как одна и та же единица. Это затрудняет чтение. Никто не любит следовать по пути функций, которые на самом деле ничего не делают, пока в конце пути, когда вы несколько забыли, с чего начали.
Замена его логическими значениями и кучей дополнительных операций и разрывов просто очень неуклюжа и затрудняет выполнение реальных намерений, как любой шум.
В java (и javascript) это вполне приемлемо (помечены как петли):
outer: while( true ) {
for( int i = 0; i < 15; ++i ) {
break outer;
}
}
В C# похоже, что очень близким эквивалентом не является:
while( true ) {
for (int I = 0; I < 15; I++) {
goto outer;
}
}
outer:;
Из-за слова goto
Это имеет психологический эффект, заставляя людей отбросить весь здравый смысл и заставить их связывать xkcd независимо от контекста.
Есть ли более эффективный способ разорвать основной цикл, чем использование оператора 'goto'?
В некоторых случаях нет, поэтому другие языки предоставляют помеченные циклы, а C# - 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;
}
Я иногда использую "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...
}
}
Я знаю, что вы можете делать то же самое рекурсивно, но кого это волнует, просто работает, и я использую.
Мой коллега (у которого 15 лет + в программировании прошивки), и я все время использую goto. Однако мы используем его только для обработки исключений! Например:
if (setsockopt(sd,...)<0){
goto setsockopt_failed;
}
...
setsockopt_failed:
close(sd);
Насколько мне известно, это лучший способ обработки исключений в C. Я верю, я тоже работаю на C#. Кроме того, я не единственный, кто так думает: примеры хороших gotos в C или C++
Я согласен с большинством ответов о том, как плохо идет.
Мое предложение избежать 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
}
}
}
Чтобы добавить к другим ответам здесь, помимо выхода из вложенных циклов, одним из аккуратных способов использования goto является простой и чистый конечный автомат:
goto EntryState;
EntryState:
//do stuff
if (condition) goto State1;
if (otherCondition) goto State2;
goto EntryState;
State1:
//do stuff
if (condition) goto State2;
goto State1;
State2:
//do stuff
if (condition) goto State1;
goto State2;
Это довольно чистый и читаемый способ создания конечных автоматов, и, кроме того, он бесплатен для производительности! Хотя вы могли бы сделать это без goto, на самом деле это только сделает ваш код менее читабельным. Не избегайте goto, просто подумайте, прежде чем использовать его.
Перейти к этому опасны, кроме других вопросов упоминания. Для правильной компиляции Go to в некоторых языках, например, используется C#, несмотря на то, что в этой функции нет операторов возврата, это скомпилирует.
public int Test()
{
goto one;
one:
{
goto one;
}
}
Вот пример того, где, по моему мнению, вполне нормально использовать goto:
switch (law.Value)
{
case "J":
remark += "Routinely oppressive and restrictive\r\n";
goto case "H";
case "H":
remark += "Legalized oppressive practices\r\n";
goto case "G";
case "G":
remark += "Severe punishment for petty infractions\r\n";
goto case "F";
case "F":
remark += "All facets of daily life rigidly controlled\r\n";
goto case "E";
case "E":
remark += "Full-fledged police state\r\n";
goto case "D";
case "D":
remark += "Paramilitary law enforcement\r\n";
goto case "C";
case "C":
remark += "Unrestricted invasion of privacy\r\n";
goto case "B";
case "B":
remark += "Rigid control of civilian movement\r\n";
goto case "A";
case "A":
remark += "Weapon possession\r\n";
goto case "9";
case "9":
remark += "Weapons outside home\r\n";
goto case "8";
Лично мне нравится думать о goto как о "goto ведет в ад"... и во многих отношениях это так, поскольку это может привести к крайне не поддерживаемому коду и действительно плохим практикам.
При этом, он все еще реализован по причине, и когда используется, его следует использовать экономно, если нет другого решения. ОО языки пригодны для того, чтобы не нуждаться в этом (сильно).
Единственный раз, когда я когда-либо видел, чтобы он использовался в эти дни, это запутывание... пример goot использования goto для создания кода из ада, так что люди будут удерживаться от попыток понять это!
Некоторые языки зависят от эквивалентных ключевых слов. Например, в сборке x86 у вас есть ключевые слова, такие как JMP (переход), JE (переход, если равен) и JZ (переход, если ноль). Они часто требуются на ассемблере, так как на этом уровне нет ОО, и есть несколько других способов перемещения в приложении.
AFAIK... держись подальше от этого, если не нужно абсолютно.