Metamorphing Machine I rather be this walking metamorphosis
than having that old formed opinion about everything!

15 VB6 best practices and 3 controversial thought-provoking ones

1. Always use Option Explicit

With Option Explicit, the compiler will scream at your face if there's an undeclared variable being used anywhere.
This prevents the insidious misspelled variable error that goes undetected until is too late.

2. Declare your variables at the top of their procedures.

Visual Studio 6, VB6's IDE, does not display a variable's data type when you hover your cursor over it.
If you keep all your variable declarations at the top of their procedures, you're only a Ctrl + Up arrow away from it.

3. Declare all of your variables with their proper data type

Dim A, B, C As Date is not OK. It is ambiguous if A and B were intended to be variants or you've made a mistake.
Having it to be Dim A As Variant, B As Variant, C As Date leaves no space for doubts.

4. Use the ampersand operator ("&") to concatenate strings

Reserve the plus operator ("+") to addition only.
If you ever tried to concatenate a string to a number, you'll know what I'm talking about.
This causes an error:

Dim Score As Long
(...)
txtMessage.Text = "Your score is " + Score

5. Don't Set variables to Nothing mindlessly

There's no need to Set an object to Nothing expecting it will make the variable be garbage collected early - it won't.
Every Set to Nothing should have a logical reason, as triggering its Class_Terminate procedure, for instance.

6. Avoid using type characters in variables' names

They may be succinct but feels more like noise.
Dim Name As String is much more readable than Dim Name$.

7. Don't use the "If <variable> = True Then" nor "If <variable> = False Then" idioms

They are verbose for no benefit. Use If <variable> Then or If Not <variable> Then instead.

8. Aim to not use On Error Resume Next

Unless there are absolutely no alternatives.
The only times I can remember I had to use it was as follows:

9. Specify the passing protocol (ByVal or ByRef) explicitly

VB6 default passing mechanism is ByRef. In .NET, it is ByVal. If they are missing in a snippet, for instance, you would not have this context to mentally decode it properly.
Also, it will make it less painful to move code from VB6-land to .NET-land and vice-versa.

10. Prefer ByVal over ByRef

ByRef brings with it a promise that the variable will be modified inside the procedure. If it is not, then use ByVal.
There is a gotcha, though: When using ByVal, implicit conversion happens if needed.
When using ByRef, the argument's type should match exactly the parameter's one, or else you'll get a "ByRef argument type mismatch" error at compile time.

11. Lean towards Select Case instead of ElseIfs

Substitute a sequence of Ifs / ElseIfs for a Select Case if you're going to compare the same variable over and over again.
It is more readable and highlights that we are comparing the same variable against several other values.
If there's a slew of ElseIfs, we would need to check carefully each one of them to see if a different variable ever sneaked there.

12. Declare your pointers as LongPtr

Use the trick (poorly) explained in teaching a new trick to a corky dog and declare every pointer variable as LongPtr.
Not only your code will be better documented, but it will save you a few keystrokes if you ever need to copy and paste code to 64-bit Office's VBA environment.

13. Initialize class variables in its Class_Initialize Sub (duh!)

In a class' Initialize Sub, initialize all variables that will cause trouble if left with their default value.
I'm talking about collections that should be set, booleans that should start up being True, and things like that.
Believe it or not, I've seen a programmer not doing that and setting object's properties after Newing them, time after time. Some people...

14. Use the default property judiciously

Favor intent over conciseness. Using it as an "indexer" (in C# parlance) is OK and usual. We do it all the time when using a Collection.
Anything else will be confusing.

15. Keep your code clean of empty If branches

Learn how to invert a condition, instead. Example:

If A > 0 And A < 10 Then
'Blank
Else
(...)
End If

Is best served as:

If A <= 0 Or A >= 10 Then
(...)
End If

A. Use GoTo sparingly

If you follow my blog, you know I seldom use GoTo, but I'm not shy of using it, either.
Sometimes it allows me to not duplicate code. Sometimes it helps to highlight parts that need refactoring.
Using wisely, it should not (cannot?) "be considered harmful."

B. There's nothing wrong with using the Hungarian naming convention

I just don't like it to my regular variables, but use it all the time with UI controls.
My variables have carefully chosen names (except when they don't...), so their use/data type is clear from the name, but I've found it hard to apply the same reasoning to controls.
When having, say, txtUser.Text = User.Name, it's clear to me what is a textbox control and what is an object variable.

C. A procedure may have multiple points of exit

I'm not against early exits.
They make it clear that we don't have to bother with the early exit condition as we progress with the code, so, it is a welcome decrease of cognitive load in my book.

i.e.:

Public Function GetToken() As Token
(...)

If AtEnd Then
Set GetToken = NewToken(tkEndOfStream)
Exit Function
End If

(...)
End Function

We can be sure that if we've passed the If line, there's still work to do.
The problem is if you ever need to modify the code to manage a state or resource, then it becomes a burden to track all those Exit Subs, Exit Functions, or Exit Propertys.

I was unfortunate enough to go down this rabbit hole for reasons ranging from the banal as reverting a MousePointer from vbHourglass to vbDefault to as critical as rolling back a database transaction.

Taking inspiration from Go, what about having our own kind of defer?
Let me use the database transaction issue as an example. First, we'll create a new class:

Class DeferResolveTransaction
Public Done As Boolean
Public Conn As Connection

Public Sub Class_Terminate()
Debug.Assert Not Conn Is Nothing

If Done Then
Conn.CommitTrans
Else
Conn.RollbackTrans
End If
End Sub
End Class

Then, we could use it as shown below:

Sub HugeProcedureWithLotsOfEarlyExits()
Dim c As Connection
'Open the database connection and start a transaction

With New DeferResolveTransaction
Set .Conn = c

'Lots and lots of code with early exits

.Done = True
End With
End Sub

If the code hits an early exit, our anonymous variable goes out of scope, causing VB to call the object's Class_Terminate sub where the proper action will be taken.
I'm not saying it is the best way to handle this situation, but it is a viable alternative.

Next week we'll revisit our transpiler series for a brief moment.

Andrej Biasic
2021-12-15