Операторы GoTo и альтернативы в VB.NET

Я разместил на другом форуме фрагмент кода с просьбой о помощи, и люди указали мне, что использование операторов GoTo - очень плохая практика программирования. Мне интересно: а почему это плохо?

Какие альтернативы GoTo можно использовать в VB.NET, которые в целом будут считаться более эффективными?

Рассмотрим этот фрагмент ниже, где пользователь должен ввести дату своего рождения. Если месяц / дата / год недействительны или нереалистичны, я хотел бы вернуться и снова спросить пользователя. (Я использую операторы if для проверки целочисленного размера ... если есть лучший способ сделать это, я был бы признателен, если бы вы также сказали мне это: D)

retryday:
    Console.WriteLine("Please enter the day you were born : ")
    day = Console.ReadLine
    If day > 31 Or day < 1 Then
        Console.WriteLine("Please enter a valid day")
        GoTo retryday
    End If

person qais    schedule 23.04.2010    source источник


Ответы (14)


Я собираюсь отличаться от всех остальных и сказать, что GOTO сами по себе не все зло. Зло исходит от неправильного использования GOTO.

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

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

Я бы написал ваш код так (мой VB немного ржавый ...):

Dim valid As Boolean = False

While Not valid
    Console.WriteLine("Please enter the day you were born: ")

    Dim day As String

    day = Console.ReadLine

    If day > 31 Or day < 1 Then
        Console.WriteLine("Please enter a valid day.")
    Else
        valid = True
    End If
End While

Если вы возьмете свой код GOTO и посмотрите на него, как кто-то сначала подойдет к вашему коду? «Хм ... retryday? Что это значит? Когда это произойдет? О, значит, мы переходим к этой метке, если день выходит за пределы допустимого диапазона. Хорошо, поэтому мы хотим выполнить цикл до тех пор, пока дата не будет считаться действительной и находящейся в диапазоне» .

А если вы посмотрите на мой:

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

person Earlz    schedule 23.04.2010
comment
Хотел бы добавить, используйте goto, чтобы избежать запутанных логических конструкций. Обратите внимание, что использования goto в VB.NET обычно можно избежать. В C # вы либо используете операторы goto, либо разбрасываете return по всему коду. - person AMissico; 25.04.2010
comment
@AMissico: ради бога, не используйте goto, чтобы избежать использования оператора return. Это было бы квалифицировано как злое использование goto. Принцип здесь прост: когда встречается goto, читатель должен следовать goto, чтобы узнать, что происходит. Когда видна более явная конструкция, такая как return, читатель сразу же точно знает, что происходит. И знает, что кодер не сделал ничего потенциально глупого. Кроме того, то, что начиналось как тривиальное использование goto, может стать менее очевидным, поскольку метод добавляется со временем. - person ToolmakerSteve; 21.11.2013
comment
@AMissico: re C # vs VB.NET: C #, начиная с первого дня, имеет эквивалент каждой конструкции управления потоком, которая есть в VB.NET. Кроме того, goto НИКОГДА не требуется ни на одном из языков. Вынесите метод за скобки (даже если он вызывается только из одного места) и используйте возвраты. Используйте цикл Пока / Пока с логическим флагом. - person ToolmakerSteve; 21.11.2013
comment
@ToolmakerSteve. О чем ты говоришь? Вы уверены, что отвечаете на мой небольшой комментарий? - person AMissico; 21.11.2013
comment
@ToolmakerSteve. Кажется, вы подразумеваете, что использование операторов return в качестве операторов управления потоком - это нормально. Тем не менее, на мой взгляд, это можно квалифицировать как злое использование return, затрудняет чтение кода и увеличивает сложность метода. - person AMissico; 21.11.2013
comment
@AMissico: Да, использование операторов return в качестве управления потоком предпочтительнее альтернатив. При правильном использовании он УМЕНЬШАЕТ сложность кода и сохраняет удобочитаемость. Это становится проблемой, когда кто-то гнездится глубоко, так что возвращение зарыто глубоко. В этом случае проблема заключается в глубокой вложенности: любое другое решение будет так же трудно читать, если только глубокая часть не будет выделена как отдельный метод. Другая проблема возникает, если возврат застревает в конце строки после другого оператора (в языках, которые это допускают). возврат на отдельной строке - очень четкий и легкий для чтения. - person ToolmakerSteve; 27.11.2013
comment
@AMissico: re C # vs VB.Net, я отвечаю на ваше предложение. Обратите внимание, что использования goto в VB.NET, как правило, можно избежать. В C # вы либо используете операторы goto, либо разбрасываете return по всему коду. Подразумевается, что VB.Net может избегать goto в ситуациях, которые C # не может. Это неправда; C # имеет эквиваленты для всех управляющих конструкций VB. - person ToolmakerSteve; 27.11.2013
comment
@ToolmakerSteve, по опыту, использование нескольких операторов return - беспорядок, и его следует избегать. Каждое заявление о возврате увеличивает сложность обслуживания. Это факт. - person AMissico; 28.11.2013
comment
@ToolmakerSteve, да у C # есть такие же эквиваленты и еще несколько. Программисты VB всегда строили свою логику и процедуры так, чтобы они возвращали один раз из-за природы языка. Эта практика естественным образом перешла на VB.NET. Поэтому часто приходится избегать определенных операторов потока управления, сохраняя небольшие процедуры с единственными точками выхода. Это не так в C-подобных языках. - person AMissico; 28.11.2013
comment
@AMissico. Мы не можем решить эту проблему здесь. Придется показывать примеры и встречные примеры. Дело в каждой ситуации - это то, что вы делаете ВМЕСТО возврата. Возврат, конечно же, далеко в списке возможных решений. И я никогда не использую возврат, кроме как в коротком методе, глубиной не более двух отступов. Однако бывают ситуации, когда альтернативы хуже. goto еще ниже в списке качества по той причине, которую я указал в своем первоначальном ответе. Большую часть своей карьеры я занимался поддержкой и расширением дрянного кода других людей; Я хорошо знаком со злоупотреблением возвратом. - person ToolmakerSteve; 28.11.2013
comment
@AMissico: вот что меня озадачивает: вы говорите, что переход к метке в конце функции менее сложен в обслуживании, чем возврат, который является эквивалентом? Если да, то почему? Если нет, то что вы говорите об использовании goto vs. return? - person ToolmakerSteve; 28.11.2013
comment
@ToolmakerSteve; ›› Мы не можем решить этот вопрос здесь. ‹< Я думаю, что мы скорее согласны, чем не согласны. Похоже, мы просто наткнулись на некоторые вопросы из-за отсутствия контекста только в комментариях. ›› Я хорошо знаком со злоупотреблением возвратом ‹< Несколько лет назад я унаследовал большой кусок дерьма. Я точно знаю, о чем вы говорите. - person AMissico; 28.11.2013
comment
@ToolmakerSteve; ›› меня озадачивает ‹< Я не уверен. Интересно, каков был бы индекс ремонтопригодности трех идентичных методов с разными операторами потока управления? Один метод использует switch и единственную возвращаемую переменную, другой похожий на переключатель goto и единственную возвращаемую переменную, а третий - switch и множественную возвращаемую переменную. Я предполагаю, что все три будут скомпилированы в почти идентичный IL. Если бы методы просто вызывали return в разных точках, не возвращая переменную, я подозреваю, что IL будет сильно отличаться между этими тремя. - person AMissico; 28.11.2013

http://xkcd.com/292/ Я думаю, что это стандартное мнение GoTo.

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

Sub Main()
    'Every time the loop runs, this variable will tell whether
    'the user has finally entered a proper value.
    Dim Valid As Boolean = False

    'This is the variable which stores the final number which user enters.
    Dim Day As Integer = 0
    Do Until Valid
        Console.WriteLine("Enter the day:")
        Dim DayStr As String = Console.ReadLine()

        If Not Integer.TryParse(DayStr, Day) Then
            Console.WriteLine("Invalid value! It must be a valid number.")
            Valid = False
        ElseIf (Day < 1) Or (Day > 31) Then
            onsole.WriteLine("Invalid day! It must be from 1 to 31.")
           Valid = False
        Else
           Valid = True
        End If
    Loop

    'blablabla
    'Do whatever you want, with the Day variable
End Sub
person Andy    schedule 23.04.2010
comment
Зачем в этом случае использовать целое число вместо логического? На мой взгляд, будет проще написать «Действовать до тех пор, пока не будет действительным». Кроме того, нет необходимости устанавливать значение 0 для действительного, поскольку оно уже равно 0. - person Chris Dunaway; 26.04.2010
comment
Я использовал int, потому что не был уверен, есть ли у VB логический класс. Я объявил valid = 0, потому что вы всегда должны убедиться, что переменные объявлены явно, прежде чем ссылаться на них. - person Andy; 28.04.2010
comment
@ChrisDunaway Полагаю, он не программист на VB? Я считаю, что в некоторых языках программирования нет логических значений, следовательно, 0 и 1. Кроме того, вы можете видеть точку с запятой в valid = 0; - person Sreenikethan I; 08.04.2020

Конструкция GOTO производит код сфагетти. Это делает практически невозможным отслеживание кода.

Процедурное / функциональное программирование - гораздо лучший подход.

person Raj More    schedule 23.04.2010
comment
Вы должны сказать, что конструкция GOTO МОЖЕТ производить спагетти-код. - person erikkallen; 23.04.2010

Вопросы о достоинствах заявления GoTo (или, скорее, об отсутствии такового) на этом сайте постоянно возникают. Щелкните здесь, чтобы увидеть пример: Является ли GoTo по-прежнему опасным?

Что касается альтернативы GoTo, в предоставленном фрагменте кода неплохо подойдет цикл while, может быть, что-то вроде:

day = -1
While (day < 0)
   Console.WriteLine("Please enter the day you were born : ")
   day = Console.ReadLine
   If day > 31 Or day < 1 Then
     Console.WriteLine("Please enter a valid day")
      day = -1
   End If
End While
person mjv    schedule 23.04.2010

GOTO - это довольно политический вопрос. «Решение» для GOTO - использовать другие встроенные конструкции навигации, такие как функции, методы, циклы и т. Д. Для VB вы можете создать подпроцедуру, которая запускает этот код, или поместить ее в цикл While. Вы можете довольно легко погуглить обе эти темы.

person brydgesk    schedule 23.04.2010

Немного неуклюже, но:

    Dim bContinue As Boolean

    Console.WriteLine("Enter a number between 1 and 31")

    Do
        Dim number As Integer = Console.ReadLine()
        If number >= 1 AndAlso number <= 31 Then
            bContinue = True
        Else
            Console.WriteLine("Please enter a VALID number between 1 and 31")
        End If
    Loop Until bContinue

Также рассмотрим некоторые основные петли в "goto land"

        Dim i As Integer
startofloop1:

        Debug.WriteLine(i)
        i += 1
        If i <= 10 Then
            GoTo startofloop1
        End If

        i = 0

startofloop2:

        Debug.WriteLine(i * 2)
        i += 1
        If i <= 10 Then
            GoTo startofloop2
        End If

Вот хороший эквивалент:

   For x As Integer = 0 To 10
        Debug.WriteLine(i)
    Next
    For x As Integer = 0 To 10
        Debug.WriteLine(i * 2)
    Next

Что более читабельно и менее подвержено ошибкам?

person Jeremy    schedule 23.04.2010

Использование goto уже несколько десятилетий считается плохой практикой. Возможно, это была негативная реакция на исходный BASIC (до Visual Basic). В исходном BASIC не было циклов while, локальных переменных (только глобальные) и (в большинстве версий BASIC) функции не могли принимать параметры или возвращать значения. Более того, функции не были явно разделены; управление может неявно переходить от одной функции к другой, если вы забыли оператор RETURN. Наконец, отступы в коде были чужеродным понятием для этих ранних BASIC.

Если вы какое-то время использовали исходный BASIC (как я), вы бы оценили, как повсеместное использование глобальных переменных и gotos делает большую программу трудной для понимания и без особой осторожности превращает ее в запутанный беспорядок. "спагетти". Когда я изучил QBASIC с его циклами WHILE..WEND и SUB, я никогда не оглядывался назад.

Я не думаю, что gotos вредят в небольших количествах, но в культуре кодеров сохраняется сильное чувство, что они в чем-то злы. Поэтому я бы избегал gotos только по одной причине, кроме как во избежание оскорбления чувств. Иногда я обнаруживаю, что goto решает проблему чисто (например, выход из внешнего цикла из внутреннего цикла), но вы должны подумать, делает ли код более читаемым другое решение (например, поместите внешний цикл в отдельную функцию и используйте " функция выхода "вместо goto во внутреннем цикле).

Я написал программу на C ++ примерно с 100 000 строк кода и 30 раз использовал goto. Между тем существует более 1000 «нормальных» циклов и около 10 000 «если» операторов.

person Qwertie    schedule 23.04.2010
comment
Мой старый компьютерный учитель писал на базовом. Мы, конечно, не делали отступов. Мы сохранили всю память, которую могли получить! Мы даже сделали программу, которая максимально сжимала наши БЕЙСИК-программы, удаляя все новые строки и сокращая имена переменных! - person Earlz; 24.04.2010

Функции FTW!

Хорошо, я не уверен, действительно ли ваш код здесь VB.Net, так как у вас есть кое-какие странные вещи (например, Console.Readline возвращает String, а не число, с которым вы можете сравнивать) ... так что мы ' Забуду на время о типе.

Console.Writeline("Please enter the day you were born : ")
day = Console.Readline()

While not ValidDate(day)
   Console.WriteLine("Please enter a valid day")
   day = Console.Readline()
End While

И отдельно

Function ValidDate(day) As Boolean
  Return day > 31 Or day < 1
End Function

Или вы могли бы повеселиться с синтаксисом рекурсии и раннего возврата! ;)

Function GetDate() As String
  Console.Writeline("Please enter the day you were born : ")
  day = Console.Readline()

  If ValidDate(day) Then Return day 'Early return

  Console.Writeline("Invalid date... try again")
  GetDate()
End Function
person Jeff B    schedule 25.09.2012
comment
Не используйте рекурсию, если нет простого способа решить с помощью цикла. Одна проблема: многие методы со временем усложняются, так как придумываются другие условия. Годы спустя редактирование может случайно превратить простое использование рекурсии в потенциальную бесконечную рекурсию. Еще одна проблема с рекурсией: стек вызовов становится глубже. Вряд ли здесь имеет значение, но это плохая привычка. К сожалению, я использовал рекурсию, потому что был ленив или умен, не понимая, что это была ситуация, которая могла бы превратиться в тысячи вызовов ... и более позднее редактирование сделало компилятор слишком сложным для хвостовой рекурсии. - person ToolmakerSteve; 21.11.2013

Вы можете делать почти все, что можете делать с GOTOs, с помощью простых встроенных языковых конструкций, таких как структуры принятия решений и циклы, а операторы GOTO часто создают беспорядочный, непонятный спагетти-код. Циклы, «если» и тому подобное имеют четкое, приемлемое и понятное использование.

См., Как обычно предлагают, Заявление Дейкстры о переходе, которое считается вредным

person froadie    schedule 23.04.2010

Часто рекомендуется следовать совету Дейкстры в «Заявлении о вреде».

Дональд Кнут довольно убедительно ответил Дейкстре. Этот пример является современной версией одного из его контрпримеров. Лично я пишу бесконечные циклы с внутренними перерывами, когда сталкиваюсь с этим, но есть несколько других редких случаев, когда я буду писать операторы GOTO.

Наиболее распространенными для меня являются выход из глубоко вложенных циклов и такой шаблон:

ContinueTry:
   Try
        'Worker code
   Catch ex as IO.IOException
        If MessageBox.Show(...) = DialogResult.Retry Then Goto ContinueTry
        Throw
   End Try

У меня также есть два случая больших конечных автоматов с операторами goto, обеспечивающими переходы.

person Joshua    schedule 23.04.2010
comment
Тривиальное преобразование удаляет goto из этого примера. Лучший пример: как прервать / продолжить внешний цикл из внутреннего цикла? - person Qwertie; 24.04.2010
comment
здесь в примере здесь относится к примеру в вопросе. - person Joshua; 25.04.2010

Я брошу свой, даже если волки «по книге» будут голосовать против. Взгляните на: Выгодно ли когда-нибудь использовать goto в языке, поддерживающем циклы и функции? Если да, то почему?

person lauCosma    schedule 16.10.2014

Я должен согласиться со всеми здесь: GOTO сам по себе не является злом, но неправильное его использование, безусловно, сделает вашу жизнь несчастной. Есть так много других управляющих структур на выбор, и хорошо написанная программа обычно может обрабатывать практически любую ситуацию без goto. При этом я нахожусь почти на этапе завершения программы, которая содержит около 15000 строк, и я использовал один и только один оператор GOTO (который я, возможно, заменю, мы увидим). Я впервые использовал GOTO из последней дюжины программ, с которыми мне приходилось иметь дело. Но в этом случае он избавился от ошибки компилятора (используя Me.Close () дважды в одном и том же Sub, но в разных структурах If; я мог бы подавить его, но я просто добавил метку и заменил один Me.Close () на a GoTo CloseLabel). Если я начну сталкиваться с большим количеством экземпляров, требующих Me.Close () в этой подпрограмме, я, вероятно, помещу Me.Close () в ее собственную подпрограмму и просто вызову эту подпрограмму из структур If или других циклов, которые приведут к закрытию программы ... Как я уже сказал, есть альтернативы, но иногда и при очень редком, экономном и стратегическом использовании GoTo все же может быть полезен. Просто остерегайтесь спагетти-кода, это мерцающий беспорядок, лол

person Michael Tant    schedule 09.05.2016
comment
Я не думаю, что исходный вопрос подходит для Stack Overflow, но ваш ответ подходит для данного вопроса. Возможно, вы захотите разбить этот ответ на отдельные абзацы, а не на гигантский кусок текста, чтобы сделать его более читабельным. - person Kmeixner; 09.05.2016

Ваш код в порядке. Это лаконично и понятно. Это лучше, чем раздувать работу на 50–200% дополнительными переменными и разными глаголами, которые делают то же самое.

Если вы просто переходите назад или вперед к началу или концу логического блока, тогда перейдите (к) для этого. «Цикл» или «Конец в то время» все еще остается переходом, но адрес назначения подразумевается. Единственное преимущество заключается в том, что компилятор не позволит вам создать перекрестные пути двух циклов, но не с парой goto. При использовании goto не пересекайте потоки. Это было бы плохо. - Доктор Шпенглер

Другая моя любимая мозоль - это правило «один вход - один выход». Конечно, у вас может быть только один вход, если вы не пишете на ассемблере. Но правило «одного выхода» глупо. Это просто приводит к набору вложенных проверок границ, которые уводят ваш код за пределы правого поля. Гораздо понятнее проверить все ваши параметры в верхней части подпрограммы и «выход из подпрограммы», если они недопустимы. Что имеет больше смысла?

if badparam then
  log error
  exit sub
  endif

if badparam2 then
  log error2
  exit sub
  endif

do stuff

или это?

if goodparam then
  if goodparam2 then
    do stuff
  else
    log error2
    endif
else
  log error 
  endif 

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

My $0.02

person Ron    schedule 23.04.2010
comment
-5 за использование Goto в этом контексте определенно лучше, чем явный цикл +1 для правила anti 1-exit. (Понятия не имею, почему 1 выход считается более структурированным, чем пара выходов. Конечно, им всегда можно злоупотреблять) - person Earlz; 24.04.2010
comment
Правило, которому я следую для выходов: избегайте использования return между первым и последним операторами в функции, которая имеет побочные эффекты. - person supercat; 01.10.2012
comment
Я также минус 1 ответ. Я впервые почувствовал, что ответ заслуживает резкости -1. Это определенно НЕ одна из немногих ситуаций, когда GOTO оправдан. Большинству начинающих программистов было бы лучше, если бы они научились НИКОГДА не использовать goto, чтобы они были вынуждены понимать, как рефакторинг кода, чтобы его было легко понять. Только тогда, после двух или трех лет программирования, они могут подумать об использовании goto. ПРИМЕЧАНИЕ. С другой стороны, я на 100% согласен с тем, что один выход - безумное правило, которое приводит к ненужным осложнениям в упрощенном коде. - person ToolmakerSteve; 21.11.2013
comment
@ToolmakerSteve: То, что вы слышали о статье Эдсгера Дейкстры о GoTo, признанной вредной, не означает, что любое использование goto - зло. Взгляните на stackoverflow.com/questions/24451/, и мы поговорим об этом. Я достаточно слышал о «goto - плохая практика». - person lauCosma; 16.10.2014
comment
+1 за выражение своего мнения, а не за чтение как «машину» - person lauCosma; 16.10.2014

person    schedule
comment
Фуууу, а как насчет того, чтобы вместо этого сделать цикл do? Больше похоже на DO ... Loop while day ›31 Or day‹ 1. Удаляет регистр continue и else. - person tloach; 23.04.2010
comment
break и continue - это просто более конкретные версии GOTO :) - person Earlz; 23.04.2010
comment
@Earlz. Прошу прощения, если это была попытка юмора; Я воспринимаю ваш комментарий буквально: весь смысл таких конструкций, как break и continue, заключается в том, что они делают четко определенные, заведомо безопасные и понятные прыжки. В то время как, если встречается goto, читающий должен вручную убедиться, что кодировщик не сделал чего-то глупого. Кроме того, если вы видите goto, вы не знаете ПРИЧИНУ ветки: что она выполняет? Если вы видите break или continue, вы сразу понимаете, что он делает и почему. - person ToolmakerSteve; 21.11.2013