The macro recorder is a wonderful thing. It’s one of the tools at your disposal to assist you in your journey, a good way to observe code that does exactly what you just did, and learn what parts of the object model to use for doing what. The problems begin when you see macro recorder code constantly invoking
Worksheet.Activate, working against the
Selection object, generating dozens of repetitive statements and redundant code that can easily trick a neophyte into thinking “so that’s how it’s done!” – the macro recorder is a great way to familiarize with a number of APIs, …and that’s all it needs to be.
There are very few ways to write more inefficient and bug-prone code, than to use the macro recorder’s output as a model of how VBA code should be written. How to avoid Select and Activate has to be the single most linked-to post in the VBA tag on Stack Overflow, yet an untold number of young souls remain to be saved from the curse of the macro recorder.
Of course, we have all the tools we need to defeat that curse. I’m not going to repeat everything in that very good SO thread, but the crux of it boils down to, in my opinion, a few simple things.
Early Binding and Guard Clauses
From an automation standpoint,
Selection is an interesting object. In the Excel object model,
Selection is a
Shape that’s selected, the
Chart you just clicked on, the
Range of cells you navigate to. If the current selection is relevant to your code, consider making it an input with an explicit object type: the selection you’re expecting very likely has a very specific type, like a
Range. Simple example:
Public Sub MyMacro() Selection.Value = 42 'multiple possible errors End Sub
Selection is pulled from that code and taken in as a
Range parameter instead, we eliminate all ambiguities and restore the natural balance of the universe by coding against the
Range interface rather than against
Object – which means compile-time validation and IntelliSense:
Public Sub MyMacro() If Not TypeOf Selection Is Excel.Range Then Exit Sub '<~ that's a *guard clause* DoSomething Selection End Sub Private Sub DoSomething(ByVal target As Range) target.Value = 42 End Sub
Note the similarities between
MyMacro in the first snippet, and
DoSomething in the second one – it’s what they do differently that makes… all the difference. Now the procedure can work with any
Range object, whether it’s actually selected or not.
Selection is never really needed: what you can do against
Selection you can do with any
Range if what you mean to work with is a
Range, or any
Chart if what you mean to work with is a
It might look like it’s more code, more complicated to write – it might even be. But binding these types at compile-time makes things much simpler, really. When we make a member call against
Object, the compiler doesn’t even care that the member exists. This involves overhead at run-time, and a non-zero chance of error 438 being raised when the member does not exist. Like
Object is very flexible… too much for its own good.
A member call against
Selection is inherently late-bound. Exactly like
dynamic in C#, you want to break out of it, as soon as possible: if you’re expecting a
Range, declare a
Range and run with it, be explicit. In turn, you’ll be rewarded with VBA blowing up with a type mismatch error (13) early on, rather than with some object doesn’t support property or method error (438), possibly far removed from where the problem really stems from – the instruction that wrongly assumed a
Selection could be treated like any old
Public Sub MyMacro() Dim cell As Range Set cell = Selection '<~ type mismatch if Selection isn't a Range End Sub
The macro recorder will never generate a single control flow statement. No loops, no conditionals, no variables, no structure. If you take something that’s tedious, and make a computer do it tediously for you, you may be “getting the job done”, but you’re 1) being very mean to your computer, and 2) you could easily be misusing the object model in ways that make it inefficient. The best way to tell a computer to do something 20 times isn’t to tell it 20 times to do one thing!
By simply introducing/declaring variables to hold the
Range objects we’re working with, we eliminate the need to
Activate everything we touch, and
Selection becomes useless, for the most part – if your macro is kicked off with a keyboard shortcut and works with whatever is selected at that time, more power to you – but that doesn’t mean your entire code needs to work with
Selection, only that you need to validate what’s selected and switch to early binding as early as possible, using properly typed local variables.
Range.Select when you need to programmatically visually select cells in the worksheet, for the user to see that selection being made and later perhaps interacted with.