GoTo statements and alternatives in VB.NET
I've posted a code snippet on another forum asking for help and people pointed out to me that using GoTo
statements is very bad programming practice. I'm wondering: why is it bad?
What alternatives to GoTo
are there to use in VB.NET that would be considered generally more of开发者_JAVA技巧 a better practice?
Consider this snippet below where the user has to input their date of birth. If the month/date/year are invalid or unrealistic, I'd like to loop back and ask the user again. (I'm using if statements to check the integer's size... if there's a better way to do this, I'd appreciate if you could tell me that also :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
I'm going to differ from everyone else and say that GOTOs themselves are not all the evil. The evil comes from the misuse of GOTO.
In general, there is almost always better solutions than using a GOTO, but there really are times when GOTO is the proper way to do it.
That being said, you are a beginner, so you shouldn't be allowed to judge if GOTO is proper or not (because it hardly ever is) for a few more years.
I would write your code like this (my VB is a bit rusty...):
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
If you take your GOTO code and look at it, how would someone first approach your code? "Hmm.. retryday? What does this do? When does this happen? Oh, so we goto that label if the day is out of range. Ok, so we want to loop until the date is considered to be valid and in range".
Whereas if you look at mine:
"Oh, we want to keep doing this until it's Valid. It is valid when the date is within range."
http://xkcd.com/292/ I think this is the standard opinion of GoTo
.
Instead, try and use a Do Until
loop. Do Until
loops will always execute once and are great when you need to prompt the user and want to make sure that you do not proceed until they enter the correct information.
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
The GOTO
construct produces sphagetti code. This makes tracing through code almost impossible.
Procedural / Functional programming is a much better approach.
Questions about the merits of the GoTo
statement (or rather the lack thereof) are perennial on this site. Click here for an example: Is GoTo still considered harmful?
With regards to an alternative to GoTo
, in the provided snippet, a while
loop would nicely do the trick, maybe something like:
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
GOTOs are a pretty political issue. The 'solution' to GOTOs is to use other built-in navigation constructs like functions, methods, loops, etc. For VB, you could make a sub-procedure that runs that code, or put it in a While loop. You can google both of those subjects fairly easily.
A little clunky but:
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
Also consider some basic loops in "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
Here's the nice equivalent:
For x As Integer = 0 To 10
Debug.WriteLine(i)
Next
For x As Integer = 0 To 10
Debug.WriteLine(i * 2)
Next
Which is more readable and less error prone?
Using goto has been considered a bad practice for decades now. Perhaps it was a backlash against the original BASIC (before Visual Basic). In the original BASIC there were no while loops, no local variables (only globals), and (in most BASIC versions) functions could not take parameters or return values. Moreover, functions were not explicitly separated; control can implicitly fell from one function to another if you forgot a RETURN statement. Finally, code indentation was a foreign concept in these early BASICs.
If you used the original BASIC for a some time (like I did), you would come to appreciate how the use of global variables and gotos everywhere makes a large program hard to understand, and without great care, turned it into a tangled mess of "spaghetti". When I learned QBASIC, with its WHILE..WEND loops and SUBs, I never looked back.
I don't think gotos hurt in small quantities, but in the coder culture a strong sense lingers that they are somehow evil. Therefore, I would avoid gotos for no other reason than to avoid offending sensibilities. Occasionally I find that a goto solves a problem cleanly (like breaking out of an outer loop from within an inner loop), but you should consider whether another solution makes the code more readable (e.g. put the outer loop in a separate function and use "exit function", instead of goto, in the inner loop).
I wrote a C++ program with perhaps 100,000 lines of code and I've used goto 30 times. Meanwhile, there are more than 1,000 "normal" loops and around 10,000 "if" statements.
Functions FTW!
Okay, I'm not sure if your code is really VB.Net here, since you've got some wonky type stuff going on (i.e. Console.Readline
returns a String
, not a number you can do comparisons on)... so we'll just forget about type for the moment.
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
And separately
Function ValidDate(day) As Boolean
Return day > 31 Or day < 1
End Function
Or you could have fun with recursion and early-return syntax! ;)
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
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
You can do almost anything that you can do with GOTO
s with simple built-in language constructs like decision structures and loops, and GOTO
statements often make for messy, impossible-to-understand spaghetti code. Loops and ifs and such have a clear, accepted, understandable usage.
See, as is usually suggested, Dijkstra's Go-To Statement Considered Harmful
It is often recommended that we follow Dijkstra's advice in Go-To Statement Considered Harmful.
Donald Knuth answered Dijkstra pretty soundly. This example here is a modern version of one of his counterexamples. Personally I write infinite loops with internal breaks when I encounter this one but there's a few other rare cases where I will write GOTO statements.
The most common ones for me are breaking out of deeply nested loops and this pattern:
ContinueTry:
Try
'Worker code
Catch ex as IO.IOException
If MessageBox.Show(...) = DialogResult.Retry Then Goto ContinueTry
Throw
End Try
I also have two cases of large finite state machines with goto statements providing the transitions.
I'll throw mine even though the 'by the book' wolves outhere will downvote. Have a look at : Is it ever advantageous to use 'goto' in a language that supports loops and functions? If so, why?
I've got to agree with everyone else here: GOTO itself is not evil, but misusing it will certainly make your life miserable. There are so many other control structures to choose from, and a well-written program can usually handle most every situation without goto. That being said, I am at the near-completion point of a program that's racking up about 15,000 lines, and I used one, and only one, GOTO statement (which I may be replacing we will see). It's the first time I've used GOTO in the last dozen or so programs I've dealt with. But in this instance it got rid of a compiler error (using Me.Close() twice within the same Sub but within different If structures; I could have suppressed it but I simply threw in a label and replaced one Me.Close() with a GoTo CloseLabel). If I start running into more instances that require Me.Close() within this Sub, I'm likely to put Me.Close() in its own sub and simply call that sub from the If structures or other loops that result in a closing of the program... As I said, there's alternatives, but sometimes, and when used very rarely, sparingly, and strategically, GoTo can still be helpful. Just beware of spaghetti code, that's a blinking mess lol
Your code is fine. It is concise and clear. It is better than inflating the job 50% to 200% with extra variables and different verbs that do the same thing.
If you are just skipping backwards or forwards to the beginning or end of a logical block, then go(to) for it. A "Loop" or an "End while" is still a goto, but the destination is implied. The only advantage is that the compiler will stop you from making two loops cross paths, but it can't with a pair of gotos. When using goto's, don't cross the streams. That would be bad. -- Dr. Spengler
My other pet peeve is the "one entrance, one exit" rule. Of course you can only have one entrance, unless you are writing in assembler. But the "one exit" rule is stupid. It just leads to a bunch of nested bounds checks that walks your code off the right margin. It is much more clear to test all your parameters at the top of the routine and "exit sub" if they are illegal. What makes more sense?
if badparam then
log error
exit sub
endif
if badparam2 then
log error2
exit sub
endif
do stuff
or this?
if goodparam then
if goodparam2 then
do stuff
else
log error2
endif
else
log error
endif
When you have six bounds checks and "stuff" is 60 lines that you can't break up into smaller bits, then the second way turns into a nightmare for anyone who has to maintain it. It's better to finish what you were doing -- checking the exceptions -- than to defer all the exception handling to the end.
My $0.02
精彩评论