Excel VBA - Using GoSub without Return - excel

I have a situation where I'd like to use GoSub within a Subroutine and in some situations Return, but in others I would not have it Return. This will be in a large be For Loop and GoSub without Return could happen potentially hundreds of times. If I do not Return will this build up in memory and cause any issues?
I suppose my question boils down to: does GoSub stack? And will a large enough stack of un-returned GoSub's cause problems?
If it does stack, I can change the code to use GoTo in the instances where I do not want to Return, but for simplicity's sake I'd rather not.
Also thank you in advance for not lecturing me on GoTo/GoSub not being best practice :)

As plenty of others said: Don't do it. I am now programming (for a living) since 30 years and never had the need to use GoSub/Return except as a substitute when a programming language didn't provide any subroutines.
That said - I was curious about how VBA handles this. First thing: I assume that there must be a kind of stack. You can have multiple GoSub/Return in one routine and it is handled correctly:
Sub testSub1()
Call StrangeRoutine1
End Sub
Sub StrangeRoutine1()
GoSub L1
Exit Sub
L1:
Debug.Print "Strange1 - L1a"
GoSub L2
Debug.Print "Strange1 - L1b"
Return
L2:
Debug.Print "Strange1 - L2a"
Return
End Sub
This Prints:
Strange1 - L1a
Strange1 - L2a
Strange1 - L1b
So the return statements jumps after the correct GoSub - this is for sure handled with a kind of stack.
However, it seems that this stack is cleared once a Subroutine is left. The following routine has a GoSub, but no Return. So it leaves an open GoSub on the "stack". But when called a 2nd time and issues a Return without an GoSub, it throws an runtime error 3 "Return without GoSub`.
Dim count As Long
Sub testSub2()
For count = 1 To 2
Call StrangeRoutine2
Next
End Sub
Sub StrangeRoutine2()
If count > 1 Then Return
GoSub L1
Exit Sub
L1:
Debug.Print "Strange2 - L1"
End Sub
P.S.: Did i mention it: Don't do it!

The shortcut to see the stack in VBE is Ctrl+E. As you see it does not stack:
Sub GosubDemo()
GoSub MyRoutine
Debug.Print "Line before Exiting"
Exit Sub
GoSomeWhereElso:
Debug.Print "SomewhereElso I am "
Return
MyRoutine:
Debug.Print "My routine"
GoSub GoSomeWhereElso
Return
End Sub
However, do not use GoSub or GoTo in VBA. It is considered a very bad practice. GoTo could be used for Error Handling like On Error GoTo ErrorHandler.
GoSub ... Return MSDN
Dijkstra - GoTo considered harmful

Related

Even without an Error, the code for "Line3:" still gets run on On Error GoTo Line3 [duplicate]

This question already has answers here:
Why VBA goes to error handling code when there is no error?
(5 answers)
Closed last year.
Example Code:
On Error GoTo Line3
More code
Line3:
Some code
Some code
More code
Even when there is no error, the "some code" underneath Line3 still gets run even though I don't want it to run. Otherwise when there is an error, Line3 gets run appropriately (skipping code that comes before it like it should).
Your code runs as it should. Code dealing with errors should be written outside of the main procedure, keeping in mind that your code should also try and spot potential errors and deal with them before they cause an error.
At the moment your error isn't being cleared when you jump to it, and because it's in the main body of code it's executed when your first set of More Code is finished and it reached the Line3 label.
Sub Test1()
'Ignore the error.
'MsgBox won't appear, but code won't know an error occured.
'MsgBox says all's good anyway, even though the error is still present.
Dim Rng As Range
On Error GoTo SkipLineWithError
MsgBox Rng.Address
SkipLineWithError:
MsgBox "All good, error number is " & Err.Number
End Sub
A better way is to try and catch the error before it happens:
Sub Test2()
'Checks that Rng won't throw an error if referenced.
'Code has dealt with the error and says all's good.
Dim Rng As Range
If Not Rng Is Nothing Then
MsgBox Rng.Address
Else
MsgBox "Range not set"
End If
MsgBox "All good, error number is " & Err.Number
End Sub
Sometimes, errors do occur though and they need to be dealt with properly. For this you jump out of the main procedure, deal with the error and jump back in again.
With this code notice the Exit Sub - the code between Exit Sub and End Sub is where your error handling goes. The main body of code ends when it reaches Exit Sub.
Resume tells your code where to jump back to - on its own it jumps back to the line that caused the error, Resume Next is the line after the error and Resume <label> jumps back to a label you've entered such as Resume Line3
Sub Test3()
Dim Rng As Range
On Error GoTo ErrorHandler
MsgBox Rng.Address
MsgBox "All good, error number is " & Err.Number
TidyExit:
'Close connections, general tidy up before ending procedure.
Exit Sub
ErrorHandler:
Select Case Err.Number
Case 91 'Deal with the error if it happens.
'For this we'll give Rng a default address.
Set Rng = Sheet1.Range("A1")
Resume
Case Else
MsgBox "Error couldn't be handled... display an error message."
Resume TidyExit 'Jump to end of main body of code.
End Select
End Sub
Edit: Have updated code based on comment by #VBasic2008. Was being lazy with my first code and missed a key point.
I've barely scraped the surface here, the links below should help.
on-error-statement
vba-error-handling
Using Gotos to manage execution paths is almost always a poor design choice. You could consider the following approach that does not rely so heavily on Goto labels. It is easier to see the intended logic flow (and error handling) without having to visually parse any Goto statements.
Sub Example()
If Not TryMorecode1() Then
Somecode1
Somecode2
End If
Morecode2
End Sub
Private Function TryMorecode1() As Boolean
'catch errors locally within this function
'return False if an error is generated
End Function
Private Sub Morecode2()
End Sub
Private Sub Somecode1()
End Sub
Private Sub Somecode2()
End Sub
The code will still run, it's just a label you can point the execution to if need be.
Try this to avoid your problem but without knowing your exact requirement, you’ll likely need to tweak the code structure to suit your needs.
My suggestion would be to put Line3 at the bottom, not in the middle of the routine. That way you just do a clean up and then get out. It tends to make more sense from a readability perspective. Of course, you should only do that if it makes sense to get out after the error occurs.
On Error GoTo Line3
More code
Goto Line4
Line3:
Some code
Some code
Line4:
On Error GoTo 0
More code
The trick with GoTo statements (if you intend to use them) is to use them sparingly.

VBA: The use of GoTo for non errors

Just a quick question today, I am new to VBA and I am currently writing some code. In the code I use GoTo not for errors but just to write two different parts of functionality. For example the below:
Sub run_FetchTradeFilter()
Dim EntrySelection As Range
Set EntrySelection = Selection
[EntireTradeQuery].Calculate
If [CustomQuery] = True Then GoTo CaseCustomQuery Else GoTo CaseCustomList
CustomQuery:
'fill
'with
'ccde A
CustomList:
'fill
'with
'ccde B
EntrySelection.Select
End Sub
My question is, should I be using GoTo if its not for an error. Or should I be using an if statement or should I be using a SwitchCase?
Great, thanks to both for the clarification! Bets to avoid GoTo. Therefore I will write 2 separate functions and call them in the If statement

Understand the pro and cons of the GoTo statement

I'm working with excel vba since 3 month now and this is (after one course of programming in university) my first real contact to programming. Please take that to account.
I built up a userform with many textboxes. Therefore I wrote a makro which first checks if the user put in a value in every textbox so that afterwards the procedure begins. If there is not a value in every textbox I want the exit sub after msgbox the user to fill again every textbox. Quiet simple, right?
I thought the best way to manage this is using the Go to-statement. After showing my boss the code he told me I should never use this statement to avoid some sort of spaghetti code. He told me a real programmer would never use this statement and would try to work his way around. This is what my code looks like:
Private Sub SaveButton_Click()
Dim i As Integer
'mandatory textboxes:
For i = 1 To 13
If UserForm1.Controls("Textbox" & i) = "" Then: GoTo again
Next
'procedure...
Exit Sub
again:
MsgBox "Please fill in every mandatory textbox"
End Sub
My question: is it right to avoid this statement in every situation? Is it really some sort of unspoken rule to never use that statement? What are the Pros and Cons of this, and which are my alternatives(especially in this case)?
I appreciate every helpful answer. Thank you!
Your code can be easily re-written as below:
Private Sub SaveButton_Click()
Dim i As Integer
'mandatory textboxes:
For i = 1 To 13
If UserForm1.Controls("Textbox" & i) = "" Then
MsgBox "Please fill in every mandatory textbox"
Exit Sub
End If
Next
End Sub
Don't ever use GoTo unless it is behind On Error … or not avoidable. If there is any chance to avoid GoTo, then avoid it. It makes your code hard to maintain and is considered to be a bad practice.
As GSerg pointed out there might be rare cases where GoTo cannot be avoided. Eg. using GoTo for emulating missing language constructs (e.g. VBA lacks the Continue keyword) and exiting deeply nested loops prematurely.
Could be rewritten thus. So below the goto is replace by an Exit For and then a subsequent test. Avoid goto unless in an On Error Goto <lable> statement.
Private Sub SaveButton_Click()
Dim i As Integer
Dim bGut As Boolean: bGut = True
'mandatory textboxes:
For i = 1 To 13
If UserForm1.Controls("Textbox" & i) = "" Then
bGut = False
Exit For '* skip out
End If
Next
If Not bGut Then
MsgBox "Please fill in every mandatory textbox"
Else
'* start processing
End If
End Sub

How do I break on errors?

I have a Function that has some bug in it somewhere causing it to return #VALUE when I try to execute it in excel.
I have no idea where the error is, and stepping through the code is just tedious. So I'd like the debugger to break as soon as an error occurs.
I tried going to Tools->options->General->"Break on All Errors" but noticed no change.
How do I get the VBA IDE to break on an error?
Just add an error handler in your function like the one below.
If an error occurs, the IDE will print the error description in the immediate window and stop on Debug.Assert 0.
Then press F8 two times to go to the line where the error occured.
Function Test() As Variant
On Error GoTo ErrHandler
Dim v(), n&, r&, c&
For r = 1 To 3
For c = 1 To 4
n = n + 1
ReDim Preserve v(1 To r, 1 To c)
v(r, c) = n
Next c
Next r
Test = v
Exit Function
ErrHandler:
Debug.Print Err.Number & ": " & Err.Description
Debug.Assert 0
Resume
End Function
Something like:
Public Function dividddeee(a As Variant, b As Variant) As Double
On Error GoTo wtf
dividddeee = a / b
Exit Function
wtf:
On Error GoTo 0
MsgBox "Houston, we've had a problem here"
MsgBox a & vbCrLf & b
End Function
If you add error handlers, you can take advantage of Debug.Assert to force a break if you don't want the standard handler to execute. You can define a compiler constant to just let your error handlers deal with it when you release it to the wild. If you want to see what specific line caused the error, you can put Resume Next after the Debug.Assert. When you step through it, it will take you to the line immediately after the one that caused the error.
Drop this small demo into Module and run the Sub with both Release = True and Release = False for an example:
Option Explicit
#Const Release = False
Private Sub Demo()
Debug.Print DivByZero(5)
End Sub
Public Function DivByZero(inValue As Integer) As Double
On Error GoTo Handler
DivByZero = inValue / 0
Exit Function
Handler:
#If Release Then
MsgBox Err.Description & " in DivByZero"
#Else
Debug.Assert False
Resume Next
#End If
End Function
If you call a VBA function as UDF the settings of the VBA IDE are not involved. So no chance for error debugging this way.
Try calling the function from a test Sub. Then the error debugging will work.
But there are some things a function cannot do as a UDF but can do called from a Sub. If one of those things is the reason for the #VALUE error, then no way around setting a breakpoint in the function and stepping forward until the next step is not possible. The last line in stepping is the error line.
You should really mention if the function is
Called from an Excel cell.
Has an event handler.
Show how your variables are declared.
If called from a cell, the inputs to the function can cause you problems with different inputs. Sometimes preventing the call of the function if the types significantly change to something unexpected. For example a parameter declared as variant in its signature will pass in an error but fail in the function. You may be trapping this error and returning #VALUE in the code. No way for us to know that.
If you have an event handler, for a quick test, you could put a 'Stop' in the event handler to stop like you are asking. If not you can put one in as already stated. Assertions are nice, I like them and use a lot of them - but here since you know the function and are working on this explicit problem a Stop should be good enough for your testing. Just don't save it for production code. Make a Test copy of the book.

vba error code in form

hey, first thanks to all for answering my other questions. I am extremely new to Excel VBA and some things I just get hung up on. I have a userform (not embedded in a worksheet) and I have a few fields that are for currency (amounts, etc) and if someone inputs a letter it errors after they hit the command button and they lose all info. I need error code to where I can tell them in a msgbox that they should not put characters in a currency field. I don't need it specific to those fields but I don't want them to lose there data when they hit the command button to dump the data into a spreadsheet.
How can I have them see the error msg, hit the ok button and have it take me right back to the screen without losing the data they have alread entered? Basically give them the opporunity to correct their error but not have to reinput 50 fields?
Thanks
Can't be specific without the actual code, but add error handlers to your code:
Sub SomeRoutine()
Dim stuff
On Error GoTo EH
' Code
Exit Sub
EH:
' Any errors with come here
If Err.Number = <specific errors to trap> Then
MsgBox "Oops..."
'As a debug tools, put a Resume here,
' but be sure to put a break on it,
' and don't leav it in the finished code
Resume
End If
End Sub
As I understand it you want the user to enter numeric numbers only into the text box - right? This is what I normally do.
In a global module add the following function:
Function IFF(c, t, f)
Dim v
If c Then v = t Else v = f
IFF = v
End Function
Then in your textbox_change event add the below:
Private Sub txtAmount_Change()
txtAmount.Text = IFF(IsNumeric(txtAmounto.Text), Val(txtAmount.Text), 0)
End Sub
This will basically put 0 in the box as soon as the user enters an invalid number.
Hope this helps
A slightly different take on error handlers than that given by chris neilsen
Sub SomeRoutine
On Error GoTo ErrHandler 'doesn't matter where you put it
'as long as it's before the code you want to protect
'Dim Stuff
'Do Stuff
ExitRoutine: 'Note the colon(:), which makes this a label
'Any cleanup that you _always_ want
Exit Sub
ErrHandler:
Select Case Err.Number
Case <some error you want to handle specially>
'special handling
Case Else
'default handling, which may include:
Resume ExitRoutine
End Select
Resume
End Sub
Note that that last Resume will never get hit in normal processing (if you've written your error-handling Cases correctly), but will let you set it as "Next Statement" when you're debugging in Break mode. This is an easy way to see exactly which statement threw the error.

Resources