Clarify that VBA uses procedure-level scope and declaring the same variable more than once causes compile errors. Include examples of correct and incorrect patterns. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
6.4 KiB
name, description
| name | description |
|---|---|
| vba-best-practices | VBA (Visual Basic for Applications) coding standards for Excel, Word, and Office automation. Trigger this skill when the user asks for help writing or reviewing VBA macros, wants to improve code quality, or shares VBA code asking "is this good?" or "how can I improve this?" |
VBA Best Practices
Code Structure
Always start every module with Option Explicit — forces explicit variable declaration, catches typos at compile time. Enable globally via Tools > Options > Editor > "Require Variable Declaration".
Declare variables at the top of each procedure, one per line, with descriptive names. Avoid single letters (i, j) and reserved words.
Naming conventions:
- Subs/Functions →
PascalCase - Local variables → Hungarian prefix (
iCount,sName,bFlag) - Module-level →
PascalCase; globals prefix withg_ - Constants →
ALL_CAPITALS; helper functions prefix withfn/fx
Indent with tabs. Everything inside loops, conditionals, and With blocks gets one level deeper.
Variables & Data Types
- Avoid
Variant— it's 16 bytes and slow. Use it only when the type is genuinely unknown or when passing arrays of mixed types. - Never use
Option Base 1— keep the default array base of 0. - Never use
Option Compare Text— keep string comparisons case-sensitive by default. - Use
vbNullStringinstead of""for empty string assignment; useLen(s) = 0to check emptiness — both are faster than comparing against"". - Use
vbstring constants instead ofChr()calls:vbTab,vbLf,vbCr,vbNewLine(fastest),vbNullChar.
No Duplicate Dim Declarations Within a Procedure
VBA uses procedure-level scope — a variable declared anywhere inside a Sub/Function is scoped to the entire procedure. Declaring the same variable name more than once (even in separate If/Else branches or nested loops) causes a compile error.
Rule: declare every variable exactly once, at the top of the procedure.
' ❌ Compile error — same name declared twice in different branches
Sub BadExample()
If condition Then
Dim x As Integer ' first declaration
x = 1
Else
Dim x As Integer ' DUPLICATE — compiler rejects this
x = 2
End If
End Sub
' ✅ Correct — declared once at the top, assigned freely in any branch
Sub GoodExample()
Dim x As Integer ' single declaration
If condition Then
x = 1
Else
x = 2
End If
End Sub
The same rule applies to loop variables reused across multiple For Each loops:
' ❌ Compile error — rawMat declared inside two separate loops
For Each rawMat In rawMaterials ' implicit Dim inside loop
allRawMaterials.Add rawMat
Next rawMat
For Each rawMat In allRawMaterials ' duplicate Dim — compile error
' ...
Next rawMat
' ✅ Correct — declare once at the top, reuse freely
Dim rawMat As Variant
For Each rawMat In rawMaterials
allRawMaterials.Add rawMat
Next rawMat
For Each rawMat In allRawMaterials
' ...
Next rawMat
Tip: A quick way to catch duplicates is to search for Dim <varname> within the same procedure. With Option Explicit enabled, the compiler will also surface this error on the first compile attempt.
Parameters & Scope
- Always declare
ByValorByRefexplicitly — VBA defaults toByRef, which can cause silent side effects. UseByValunless you intentionally need to modify and return the argument. - Minimize global variables. Use
Public/Privateat module level (notGlobal/bareDim). If a global is unavoidable, prefix withg_. - Validate arguments at the top of every Sub/Function before using them.
Error Handling
Never use On Error Resume Next — it silently swallows bugs. Instead, add a custom error handler to every procedure:
Sub DoSomething()
On Error GoTo ErrorHandler
' ... code ...
Exit Sub
ErrorHandler:
Debug.Print "Error in DoSomething: " & Err.Description
' Log, re-raise, or recover as appropriate
End Sub
Handle the error in the same procedure where it occurs.
Control Flow & Objects
- No
GoTofor program logic — useIf/ElseIf,Select Case, and loops. (GoTo ErrorHandleris the one accepted exception.) - Use
With … End Withwhenever you reference the same object more than once — fewer dot-lookups means faster execution:
With ws.Range("A1").Font
.Bold = True
.Size = 12
End With
- Never reference
ActiveCell,ActiveSheet, orActiveWorkbook— always bind to explicit object variables:
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets("Data")
ws.Range("A1").Value = "Hello"
- Use built-in constants and enumerations with full prefix (
VBA.vbMsgBoxResult.vbYes,msoFileType.msoFileTypeExcelWorkbooks) for readability and IntelliSense support. Group related custom constants intoEnumblocks.
Performance
- Bulk data via arrays — read a range into an array, process in memory, write back in one shot. Never loop over individual cells for large datasets:
Dim arData As Variant
arData = Sheet1.Range("A1:D500").Value ' one read
' ... process arData() in memory ...
Sheet1.Range("A1:D500").Value = arData ' one write
ReDim Preserveinside a loop is forbidden. Oversize the array upfront;ReDim(withoutPreserve) once at the end if needed.- Minimize
Application.ScreenUpdating = False— use only when necessary, and always restore it (including inside error handlers).
Quick Checklist
| Rule | Why it matters |
|---|---|
Option Explicit in every module |
Prevents undeclared-variable bugs |
| Descriptive names + Hungarian prefixes | Readability & self-documentation |
| Each variable declared once, at procedure top | Duplicate Dim = compile error |
ByVal/ByRef always explicit |
No silent side effects |
Custom GoTo ErrorHandler, not Resume Next |
Errors surface instead of hiding |
With…End With for repeated object refs |
Speed + clarity |
No ActiveCell/ActiveSheet/ActiveWorkbook |
Robust against user selections |
vbNullString / Len() for strings |
Faster than "" comparison |
| Arrays for bulk range I/O | Orders-of-magnitude speed gain |
No Option Base 1 or Option Compare Text |
Predictable defaults |
No GoTo except for error handler label |
Structured, readable flow |