在VB.NET中的GoTo语句及其替代方案

10

我在另一个论坛上发布了一段代码片段,并请求帮助,有人指出使用 GoTo 语句是非常糟糕的编程实践。 我想知道:为什么这样做是不好的?

在 VB.NET 中,除了 GoTo,还有哪些替代方法可以被认为是更好的实践呢?

考虑下面的代码片段,用户必须输入他们的出生日期。 如果月份/日期/年份无效或不切实际,我想循环回去再次询问用户。 (我正在使用 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
14个回答

21

我与其他人不同,认为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?这个是干什么的?它在什么时候发生?哦,如果日期超出范围,我们会跳到那个标签。好了,所以我们想要循环直到日期被认为是有效和在范围内。”
相比之下,如果你看我的代码:
“哦,我们希望一直执行直到它是有效的。当日期在范围内时它就是有效的。”

1
希望添加,使用goto以避免复杂的逻辑结构。请注意,在VB.NET中通常可以避免使用goto。在C#中,您可以使用goto或在代码中分散return语句。 - AMissico
@AMissico:求你了,别使用goto来避免使用return语句。那样会被视为滥用goto。原则很简单:当遇到“goto”时,读者必须跟随该语句才能知道正在发生什么。而当看到更明确的构造,比如return语句,读者立即知道正在发生什么,并且知道编码人员没有做出任何潜在的愚蠢之举。此外,最初作为goto的琐碎用法可能会变得不太明显,随着时间的推移会添加方法。 - ToolmakerSteve
@ToolmakerSteve。你在说什么?你确定你是在回复我的小评论吗? - AMissico
@ToolmakerSteve。你似乎在暗示使用return语句作为流程控制语句是可以的。然而,在我看来,这将被视为滥用return,使代码难以阅读,并增加了方法的复杂性。 - AMissico
@AMissico:关于C#和VB.Net的问题,我回应你的一句话:“请注意,在VB.NET中使用goto通常是可以避免的。在C#中,您要么使用goto,要么在代码中散布return语句。”这意味着VB.Net可以避免C#无法避免的情况下使用goto。这是不正确的;C#有与VB的所有控制结构等效的语句。 - ToolmakerSteve
显示剩余8条评论

6

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

在这种情况下,为什么要使用整数而不是布尔值?在我看来,写“Do Until valid”更加简洁。此外,由于valid已经是0,因此无需将其设置为0。 - Chris Dunaway
我使用了int,因为我不确定VB是否有一个布尔类。我声明valid = 0,因为在引用变量之前,你应该始终确保变量被明确声明。 - Andy
@ChrisDunaway 我猜他不是VB程序员?我相信有些编程语言没有布尔值,因此使用0和1。而且,你可以看到valid = 0;中的分号。 - Sreenikethan I

2
< p > GOTO 构造会产生意大利面式代码。这使得跟踪代码几乎不可能。

过程/函数式编程是一个更好的方法。


5
你应该这样表达:“GOTO结构可能产生混乱的代码”。 - erikkallen

2

这个网站经常会有关于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

1

函数万岁!

好的,我不确定你的代码是否真的是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

1
不要使用递归,除非没有简单的循环解决方法。一个问题是:随着其他条件的考虑,许多方法随着时间的推移变得更加复杂。多年后的编辑可能会意外地将简单的递归用法转换为潜在的无限递归。递归的另一个问题是:调用堆栈会变得更深。这里不太可能有影响,但这是一个坏习惯。哎呀,我之所以使用递归是因为我懒或聪明,没有意识到这是一个会变成数千个调用的情况...而后来的编辑使它对编译器进行尾递归变得太复杂了。 - ToolmakerSteve

1

GOTO语句是一个相当政治化的问题。解决GOTO语句的方法是使用其他内置的导航结构,如函数、方法、循环等。对于VB,您可以创建一个运行该代码的子过程,或将其放入While循环中。您可以轻松地在谷歌上搜索这两个主题。


1
有点笨重,但是:
    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

哪个更易读且更少出错?


1

使用goto已经被认为是一个不好的实践几十年了。也许这是对原始BASIC(在Visual Basic之前)的反弹。在原始BASIC中,没有while循环,没有局部变量(只有全局变量),而且(在大多数BASIC版本中)函数不能带参数或返回值。此外,如果您忘记了RETURN语句,控制可以从一个函数隐式地转移到另一个函数; 最后,在这些早期的BASIC中,代码缩进是一个陌生的概念。

如果您使用原始BASIC一段时间(就像我一样),您会欣赏到如何在全局变量和无处不在的goto的情况下使大型程序难以理解,并且如果没有极大的注意,将其变成“意大利面条”般的混乱。当我学习QBASIC时,它具有WHILE..WEND循环和SUBs,我再也没有回头。

我不认为少量的goto会有害,但在编码文化中,仍然存在着一种强烈的感觉,认为它们某种程度上是邪恶的。因此,我会避免使用goto,没有其他原因,只是为了避免冒犯感情。偶尔我发现goto可以干净地解决问题(例如从内部循环中退出外部循环),但您应该考虑另一种解决方案是否使代码更易读(例如将外部循环放在单独的函数中,并在内部循环中使用“exit function”而不是goto)。

我写了一个 C++ 程序,大概有 100,000 行代码,使用了 30 次 goto 语句。同时,还有超过 1,000 个“普通”的循环和约 10,000 个“if”语句。


我的老电脑老师以前用Basic编程,“当然,我们那时候不缩进。我们尽可能节省所有内存!我们甚至编写了一个程序,可以将我们的BASIC程序压缩到最小,删除所有换行符并缩短变量名称!” - Earlz

0

通常建议我们遵循Dijkstra在“Go-To语句被认为是有害的”中的建议。

Donald Knuth对Dijkstra的回应非常有力。这个例子是他反例的现代版本之一。个人遇到这种情况时,我会使用内部跳出的无限循环,但还有其他一些罕见的情况,我会编写GOTO语句。

对我来说,最常见的情况是从深度嵌套的循环中跳出和这种模式:

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

我还有两个大型有限状态机的案例,其中包含使用goto语句提供转换。


1
一个微不足道的转换可以从这个例子中移除goto。一个更好的例子是:如何在内部循环中跳出/继续外部循环? - Qwertie
"这里"在“这个例子中”指的是问题中的例子。 - Joshua

0
While True
    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")
        Continue
    Else
        Break
    End If
End While

1
那你觉得改成 do 循环怎么样?可以这样写:DO ... Loop while day > 31 Or day < 1。这样就不需要 continue 和 else 语句了。 - tloach
2
breakcontinue只是更具体的GOTO版本 :) - Earlz
1
@Earlz。如果那是一个幽默尝试,我很抱歉;我会字面理解你的评论:像“break”和“continue”这样的结构的整个重点在于它们执行明确定义、已知安全且易于理解的跳转。而如果遇到goto,则读者必须手动验证编码人员没有做出愚蠢的事情。此外,如果您看到“goto”,您不知道分支的原因:它完成了什么?而如果您看到“break”或“continue”,您立即知道它的作用和原因。 - ToolmakerSteve

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接