Files
claudeskill/skills/vba-best-practices/SKILL.md
Misaka_Company 3c073b73e2 Add guidance on duplicate Dim declarations in VBA
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>
2026-02-27 13:37:20 +08:00

171 lines
6.4 KiB
Markdown

---
name: vba-best-practices
description: 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 with `g_`
- Constants → `ALL_CAPITALS`; helper functions prefix with `fn`/`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 `vbNullString` instead of `""`** for empty string assignment; use `Len(s) = 0` to check emptiness — both are faster than comparing against `""`.
- **Use `vb` string constants** instead of `Chr()` 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.**
```vb
' ❌ 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:
```vb
' ❌ 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 `ByVal` or `ByRef` explicitly** — VBA defaults to `ByRef`, which can cause silent side effects. Use `ByVal` unless you intentionally need to modify and return the argument.
- **Minimize global variables.** Use `Public`/`Private` at module level (not `Global`/bare `Dim`). If a global is unavoidable, prefix with `g_`.
- **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:
```vb
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 `GoTo`** for program logic — use `If/ElseIf`, `Select Case`, and loops. (`GoTo ErrorHandler` is the one accepted exception.)
- **Use `With … End With`** whenever you reference the same object more than once — fewer dot-lookups means faster execution:
```vb
With ws.Range("A1").Font
.Bold = True
.Size = 12
End With
```
- **Never reference `ActiveCell`, `ActiveSheet`, or `ActiveWorkbook`** — always bind to explicit object variables:
```vb
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 into `Enum` blocks.
---
## 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:
```vb
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 Preserve` inside a loop is forbidden.** Oversize the array upfront; `ReDim` (without `Preserve`) 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 |