Better method than GoTo for Guard Clause at the start of a loop?

Thanks all, I'm pretty happy using GoTo how I plan to as basically "Continue" or "Skip to the loop"

I appreciate the separate sub/function option and I do if things repeat or are used elsewhere (as Pat says in his reply as I type this), that Criteria example looks perfectly reasonable size wise, they've done a good job of making it very dense in what it does, many tests & checks etc!

I do have one (old - so less well written) module that is basically one function split into about 8 functions called by a Case because it just got out of hand, the module is 520 lines of code, 1022 lines including blanks and comments, it was just one function, to handle a fairly complex (7 different types) barcode system, i.e. "if it's a persons barcode do A, if it's a job do B, if it's stock do C, if it's equipment do D, if it's a step in a process do E, etc etc etc. The poor module starts with 31 global variables defined, lol

This is one thing very cool about GoSub, it keeps you in the same sub/function for local variables, or recordsets (I use these a fair bit lately) which make me more hesitant to split things out and are what lead me to ask this question in the first place, I am going to be 2 or 3 loops and multiple recordsets deep and I'd like to avoid nesting if's as much as possible
 
Do not discount using GoSubs without knowing what they are used for and how to use them.
No way, as soon as it makes the code more readable and maintainable, I would use it.
However, when I compare the code shown from #6 with #7, I don't see the advantage. ;)
 
UP side of GoSubs:
1. They exemplify my preferred methodology, "Divide and Conquer" as applied to program design. I'm not being arrogant by pushing this method because it was taught to me by my college professors and instructors, and it appeared in college programming texts as far back as the 1960s. This is one of Pat's "likes" - the ability to break up the process into smaller, more easily managed chunks.
2. The GoSub's code can see the variables and objects defined in the code from which they are called. No need to worry about passing variables.
3. They do not change contexts, which means their stack footprint is small. Specifically, they do not involve a "call frame" like a Sub or Function would involve, so they are memory friendly. Call frames for Sub or Function code that involves an array (for example) or an application object (for another, bigger example) can get quite demanding.

DOWN side of GoSubs:
1. In the Access VBA environment, they MUST be within the programming scope of the overall event code from which they are called. Unlike having a Public Sub in a general module, the target label of GoSub MUST be something that (if it weren't the entry point to a GoSub) could be the target of a GoTo. They are RIGOROUSLY concerned with transfer-of-control scope.
2. Because they can see things within the event-code scope, a lot of what they do is akin to side-effects - which are a temptation. Stated another way, all variables used in a GoSub routine are used as though they were passed by reference - which in fact, they are - even if you didn't want them to be.
3. Because they do not change contexts, error handling will be less specific since you won't know whether you were in the caller of a GoSub or in the GoSub itself. The Show Call Stack function used in code debugging won't make a distinction.

In my genealogy database, I had to use GoSub several times because of the potential for recursion in trying to establish generational differences between two people. Because it had a smaller stack footprint, it worked far better than the functions I originally used that, because of recursion, often "blew out" the program stack. ("Heap overflows stack" error. Or "Stack overflows heap", depending on which action revealed the dirty deed.)
 
In my genealogy database, I had to use GoSub several times because of the potential for recursion in trying to establish generational differences between two people. Because it had a smaller stack footprint, it worked far better than the functions I originally used that, because of recursion, often "blew out" the program stack. ("Heap overflows stack" error. Or "Stack overflows heap", depending on which action revealed the dirty deed.)
This is a good example in my opinion. GoSub has improved the process. That is a good reason to use it.

I return to the initial example, which I rearranged somewhat for comparison:

Variant with GoTo:
Code:
Public Function Func1_WithGoTo()

   Dim Var3 As Long
   Dim Var2 As Long
   Dim Var1 As Long

   With CurrentDb.OpenRecordset("select field, done from table1 where done = false", ...)
      Do While Not .EOF
         Var3 = .Fields(0).Value
         .edit
         .fields(1).Value = True
         .Update

         If DCount("field2", "table2", "field = " & Var3 & " and outstanding > 0") = 0 Then
             MsgBox "invalid Var"
             GoTo SkipLoop
         End If

         'do a whole bunch of stuff

         Do Until Var1 = Var2
         '  ...
         Loop

         .MoveNext
SkipLoop:
      Loop
      .Close
   End With

   MsgBox "Fin", vbSystemModal

End Function

Variant with GoSub:
Code:
Public Function Func1_WithGoSub()

   Dim Var3 As Long
   Dim Var2 As Long
   Dim Var1 As Long

   With CurrentDb.OpenRecordset("select field, done from table1 where done = false", ...)
      Do While Not .EOF
         Var3 = .Fields(0).Value
         .edit
         .fields(1).Value = True
         .Update
         GoSub DoSometingWithField
         .MoveNext
      Loop
      .Close
   End With

   MsgBox "Fin", vbSystemModal

Exit Function

DoSometingWithField:
   If DCount("field2", "table2", "field = " & Var3 & " and outstanding > 0") = 0 Then
       MsgBox "invalid Var"
       Return
   End If

   'do a whole bunch of stuff

   Do Until Var1 = Var2
   '  ...
   Loop

   Return

End Function

Variant with sub-procedure:
Code:
Public Function Func1_WithSubProc()

   With CurrentDb.OpenRecordset("select field, done from table1 where done = false", ...)
      Do While Not .EOF
         .edit
         .fields(1).Value = True
         .Update
         DoSometingWithField .Fields(0).Value
         .MoveNext
      Loop
      .Close
   End With

   MsgBox "Fin", vbSystemModal

End Function

Private Sub DoSometingWithField(ByVal Var3 As Long)

   Dim Var2 As Long
   Dim Var1 As Long

   If DCount("field2", "table2", "field = " & Var3 & " and outstanding > 0") = 0 Then
       MsgBox "invalid Var"
       Exit Sub
   End If

   'do a whole bunch of stuff

   Do Until Var1 = Var2
   '  ...
   Loop

End Sub
Unless I included a bug, all 3 variants should do the same thing.

Do we agree that the variant with GoTo is the worst to read?
How does it look comparing the variants with GoSub and the sub-procedure?

For this use case, I see more advantages for the last variant.
  1. easier testable (you can test the sub-procedure alone)
  2. no overlapping data operations in one procedure, which complicate error handling
  3. separate variable declaration ... no side effects possible
What do you think?
 
Last edited:
@Pat Hartman - OK, stated another way...

Con #1 means that you cannot effectively put the GoSub routine in another module even if you DID want to re-use it.
Con #2 is a holdover from someone who practically slapped my knuckles with a ruler when I was careless with side-effects, and that "in-scope" situation is a case where lines are blurred. Call me paranoid. Or if I was beside myself, call me paradox.

EDITED to clarify a fine point.
 
Last edited:
@The_Doc_Man I regularly use GoSubs (when I don't have any plan to re-use them in other functions) and I don't see any of your Cons as a real con. Maybe only the memory stack one. (I don't understand that)

I see only two cons for GoSubs:
1- You can not shift+F2 a GoSub to jump to that specific section of program (Subs and functions give you this shortcut) It means you have to scroll up and down regularly.
2- You can't jump over them while debugging. If you have several subs and functions that are called in a procedure, you can shift+F8 them to execute them without stepping in. But you can not do this on GoSubs. While debugging, you have to go line by line, or put a break somewhere ahead and do a F5 to jump over them. And after a while your function is full of break points and you're lost between them.

Yet, I like them and use them as much as I can. (again, if that section is not going to be used by other functions)
 
Last edited:
@The_Doc_Man I regularly use GoSubs (when I don't have any plan to re-use them in other functions) and I don't see any of your Cons as a real con. Maybe only the memory stack one. (I don't understand that)

I see only two cons for GoSubs:
1- You can not shift+F2 a GoSub to jump to that specific section of program (Subs and functions give you this shortcut) It means you have to scroll up and down regularly.
2- You can't jump over them while debugging. If you have several subs and functions that are called in a procedure, you can shift+F8 them to execute them without stepping in. But you can not do this on GoSubs. While debugging, you have to go line by line, or put a break somewhere ahead and do a F5 to jump over them. And after a while your function is full of break points and you're lost between them.

Yet, I like them and use them as much as I can. (again, if that section is not going to be used by other functions)

Your point #2 is one that I didn't think about, but you are quite right. A GoSub isn't a procedure call so step into/step over don't recognize it. I so rarely use your point #1 that it also eluded me.
 
My old functions were serious pyramid code, often multiple nested if's deep, since then I've been using guard clauses and setting values/checking values though a series of separate if's instead of nesting, it's definitely easier to keep track of the "end" compared to nesting!

However I'm stuck when it comes to loops as there's no "continue" function to "exit sub" from that loop based on the guard clause, I suppose I could call the function repeatedly instead of it being a loop, but that wouldn't work for a nested loop that's relying on values from the main function, I know they could be passed but that's back to getting harder to read and jumping around more than "goto SkipLoop" would be?

I'm not bothered either way about goto or not goto, but the convention is there for a reason and I'm curious to have other opinions on best practice
I would use a Boolean variable to skip but stay inside the loop and what I do inside the loop, or use Exit For to escape the Loop.

I would use GoTo's or GoSub ... Return if I need to handle many sub routines inside the same sub procedure or function. If those sub routines can be used elsewhere, I will just create function to handle it. I would prefer a separate function before a Gosub ... Return.

Tip: Before I ever write any VBA I try to determine how much of what I am about to write can be accomplished in a query or series of queries. Why? Because if the DB will ever be migrated to SQL Server, it is a lot easier to migrate queries to VIEWS than it is VBA to Stored Procedures.
 
It is exactly this problem that causes me to not be a fan of Niklaus Wirth, "father" of PASCAL. Wirth advocated NEVER using a GOTO but instead exclusively using IF-nesting. Talking about IF pyramids 8 to 10 layers deep, you reach the point where the levels of the IF become very hard to read - yet if you program in a "pure" PASCAL environment, that is exactly what you have to do.

When you have a decision to make and an action to take, there is nothing quite like an "IF decision THEN action" clause. Some languages support a verb to terminate an iteration of a loop by using, e.g. END DO to end the loop at point X rather than going down to the end. (VBA does NOT have this option. I checked the syntax reference.) But in the greater scheme of things, we should consider what is compiled.

Code:
DO UNTIL rs.EOF
    IF not rs![OK] THEN GOTO SKIPSTEP
        something else
SKIPSTEP:
    rs.MoveNext
LOOP
....
DO UNTIL rs.EOF
    IF rs![OK] THEN
        something else
    END IF
    rs.MoveNext
LOOP

If you consider these two options, they should produce near identical code. In one case you skip via GOTO. In the other case you skip via an IF nest. But internally the code is the same anyway. Now if you have MULTIPLE causes to skip, things get more complex but the concept is still the same. Whether you nest via an IF pyramid or use a single-line IF ... THEN GOTO ... to maneuver will be a matter of aesthetics and personal style.
 
.. and if "something else" requires more lines of code (possibly with further loops):

Code:
DO UNTIL rs.EOF
    IF rs![OK] THEN
        GoSub DoSomethingLineMarker
    END IF
    rs.MoveNext
LOOP
or
Code:
DO UNTIL rs.EOF
    IF rs![OK] THEN
        DoSomethingProcedure rs
    END IF
    rs.MoveNext
LOOP
 
Talking about IF pyramids 8 to 10 layers deep, you reach the point where the levels of the IF become very hard to read
Doc, most of the problems are because of poor Visual Basic editor that comes with Access, not the style of programming.
When I programmed in PHP, I used Zend (Eclipse) ide. It was a powerful editor with a lot of abilities. One of those was a small plus sign it gave you for most statements that spread through several lines. You can use it to Expand/Collapse them if necessary. Or even simply double click If/For Each/For……. To Expand/Collapse them.

It gives you the ability to go how many deep you want in your loops and IFs without being lost. Just hide others and take care of what you’re working on.
Something else that I loved about it was when the cursor was within a loop or IF, the background color of the group was changed. So you could easily concentrate on the most inner loop/IF.



A78D7AEB-5F4B-4E5B-8671-9FF273B55ED6.png


As far as I remember even Notepad++ (freeware) has this ability. But not Access. What a shame.
 
Last edited:
I really don't think there is anything inherently wrong with a goto, judiciously used. If you want to terminate an iteration and continue with the next iteration, goto the loop point. The goto can be be a simple way of cutting the Gordian knot without tying yourself in complex conventions. (and tortuous code simply to avoid using a goto statement). Note you might need to clear some variables/structures before the goto.

What is the assembler instruction? Unconditional jump. I expect some code might compile into an unconditional jump even if you don't explicitly use the goto command, but I may be wrong.

FWIW, I just got this from ChatGPT (Q: is it acceptable to use goto when programming).

The use of the `goto` statement in programming is a topic of debate among software developers. While `goto` can provide a way to transfer control to a specific location in code, it can also make code harder to understand, debug, and maintain. As a result, many programming languages, such as Python, Java, and C#, either discourage or completely prohibit the use of `goto`.

When code relies heavily on `goto` statements, it can create "spaghetti code," where the flow of control becomes difficult to follow. This can make it challenging to reason about the behavior of the program and introduce bugs that are hard to track down.

Modern programming languages offer alternative control structures, such as loops, conditionals, and functions, which provide more structured ways to control program flow. By using these constructs effectively, you can generally avoid the need for `goto` statements.

However, it's essential to note that there are a few situations where the use of `goto` may be acceptable or even necessary. Some legacy codebases or specific programming domains might still rely on `goto` for performance reasons or to work with existing code. Additionally, certain low-level programming tasks, like implementing state machines, may benefit from limited and careful use of `goto` statements.

In general, it is best to avoid using `goto` unless you have a compelling reason to do so and thoroughly understand the potential consequences. Striving for code clarity, maintainability, and readability should be the primary considerations when deciding whether to use `goto` or alternative control structures.
 

Users who are viewing this thread

Back
Top Bottom