Renaming variables (objects, functions, procedures, etc.) - vba

as far as I know, I have to rename variables used in my VBA code "manually" using the search/replace function in the VBA editor (at least if I don't use an add-on like V-Tools, Speed Ferret, Rubberduck, etc.). "Replace all", which would be quick, might give you unwanted results in some cases. So you have to go through each instance manually always reorienting yourself where the search function jumped to etc.
So my question is: is there another, less time consuming, way? Maybe without the search&replace? Or maybe there an easy to remember pattern how to name a variable (object, function, procedure, etc.) so you are sure that the "replace all" option won't give you any unwanted results?
Thanks and Cheers!

First of all, make sure you use Option Explicit. When you do that, you can be slightly less careful while renaming - compiling your code will make sure that you didn't miss any variable names.
Second - use a good text editor to do your replace and create good regexes to identify your names. Expressions that make sure that you don't have the name as a part of another name.
Update after comment
Here's a little snippet to help you with export (and/or code analysis directly in VBE):
Sub AllCode()
Dim Component As Object
For Each Component In Application.VBE.ActiveVBProject.VBComponents
With Component.CodeModule
For i = 1 To .CountOfDeclarationLines
Debug.Print .Lines(i, 1)
Next i
For i = .CountOfDeclarationLines + 1 To .CountOfLines
Debug.Print .Lines(i, 1)
Next i
End With
Next
End Sub

Related

What are the risks of declaring a variable in the middle of the code?

I usually see in almost all of VBA codes all variables are declared after e.g. Sub/Function name line
I know and I used variable declaration in the middle of some of my codes (Not inside a loop) and saw no problems.
I usually avoided that because I see most of VBA example codes have them declared right after the first line. I just want to know what are the risks from an expert/experienced VB programmer point of view.
There are no risks of declaring it in the middle.
The effect of declaring a variable in the middle is that it can only be used after that point and not before (which is scope).
The lifetime of the variable is different: the variable is created (allocated and initialized to its respective flavour of zero) when you enter the procedure, but you may not actually use it until you reach its scope (the point in the procedure where it's declared).
Declaring inside or outside a loop does not make a difference in VB6/A as they do not have block scope, unlike VB.NET.
So there is no performance difference between the two approaches (because all variables are created when you enter the procedure), but there is a difference in usage (you may not use a created variable before its declaration line). If you think that distinction is helpful in making sure you are not using a variable wrongly, declare your variables only where needed. Otherwise you are free to pick any of the two approaches, just apply it consistently (it's probably not a good idea to declare most of the variables in the beginning and then some in the middle).
Declare your variables, when you actually need them. When you have all declarations lumped at the top of the procedure, refactoring becomes much harder. And when you want to double check your declaration as you read your code (or, perhaps, someone else), searching it at the top may be again quite inconvenient, unless you procedure is short.
I would try to declare variables in a location that conveys useful information to the next programmer, over and above being functionally correct. This normally means: follow the scoping rules of the language.
By declaring all variables at the top you are making them available (in scope) for the entire procedure. That increases the work for a reader in the future, trying to understand how they will be used. Better to have them as local as possible.
I would not declare them in a loop since that actually would not have significance in VB6/VBA - but someone else might find confusing or misleading, or worst case it may cause subtle bugs.
Of course remember that this is not the only coding practice that we should be mindful of - if the procedure is so long that the location of the variable declarations is a big problem, that's a really good sign that the procedure should be broken up into smaller discrete logical blocks. The variable declarations would just be a symptom, not the main cause.
IMO there were many bad programming practices back in the 90s and earlier when VBA/VB6 were invented, but the industry has significantly learned & improved since then. So code from that era (or inspired by it) is often not a good example.
Declaring your variables up front, at the top of your sub/function makes it easy for others (and perhaps for you if you come by the code after, say a month) to read and understand what your code needs to calculate, and what placeholders/variables are required for the code to function.
You can of course declare variables anywhere (as long as you remember not to use a variable unless you have actually declared it first). That can work, and it has no effect whatsoever on the performance of your code (unless your logic includes an early Exit Sub or Exit Function. In this case, there will be a difference in performance depending on if your code does actually allocate memory for the variables or not).
It just isn't good practice to declare some variables at the top then do some work, then declare another set of variables mid-code. There are exceptions of course. When the variable you declared mid-code is for a temporary use, or something like that.
Sub CalculateAge()
Dim BirthYear As Integer
Dim CurrentYear As Integer
'Code to fetch current year
'Code to get BirthYear from user/or document
'Code to report result
End Sub
Compare that with the following:
Sub CalculateAge2()
Dim BirthYear As Integer
'Code to ask the user or fetch the birth year from the document
Dim CurrentYear As Integer
'Code to populate currentYear
'Code to do the calculation and report result
End Sub
In the first example, there is a clear separation from variables and logic. In the second, everything is mixed.
The first example is a lot easier to read and understand, especially if you use a good naming convention.
If you look at how classes are written or defined, you will see properties usually are first declared, then methods/logic below. This is the common practice used to write code.
PS: In other languages, you can declare and assign variables in the same line. in C# or VB.Net you could say something like:
int Age = CurrentYear - BirthYear; //C#
Dim Age As Integer = CurrentYear - BirthYear 'VB.Net
This is great if you use a lot of temporary variables, that you don't intend to declare ahead of time or maybe it would be more clear if declared mid-logic. But that's not possible in VBA. You need a separate line to declare a variable, and another to assign a value. You end up with a lot of Dim ___ As ___ statements. You might as well move the declaration part somewhere else to reduce distraction while reading the logic. Again, this works best if you use a good and consistent naming convention. If not, you end up in a worse situation like:
Dim w As Integer
Dim a As Integer
a = 42 'we don't know what this variable is for
'but we know its type from the previous line
Some_Lines_Of_code_And_Logic
' more code
' more code
w = 2 'we don't know what (w) is for, and we have to
'look up its declaration to get a hint
'which might be tedious

Simplify VBA Code

I have a macro which reads and writes data from two sheets in the same workbook.
Is it possible to clean up and simplify the code/statements to improve readability and aid in debugging efforts?
The statements have become so long they are confusing to read even when using the space-underscore method to use more than a single line.
Example of a statement which has become unwieldy:
Range("mx_plan").Cells(WorksheetFunction.Match(sortedAircraft.Item(i).tailNumber, Range("aircraft")), WorksheetFunction.Match(currentWeekId, Range("week_id")) + weekly_hours_col_offset) = (acft_hoursDNE / acft_weeksRemaining)
I've intentionally tried to avoid making explicit references to individual cells or ranges.
Your statement is 225 characters!
Debugging it will be impossible, because it's one instruction doing way too many things, and you can only place a breakpoint on a line of code... so you can't break and inspect any of the intermediary values you're using.
Break it down:
tailNumber = sortedAircraft.Item(i).tailNumber
aircraft = someSheet.Range("aircraft").Value
planRow = WorksheetFunction.Match(tailNumber, aircraft)
weekId = someSheet.Range("week_id").Value
planColumn = WorksheetFunction.Match(currentWeekId, weekId)
Set target = someSheet.Range("mx_plan").Cells(planRow, planColumn + weekly_hours_col_offset)
target.Value = acft_hoursDNE / acft_weeksRemaining
Remember to declare (Dim) all variables you're using (use Option Explicit to make sure the code won't compile if you make a typo with a variable name), use meaningful names for all identifiers (names that tell the reader what they're for - use comments when the why isn't obvious from the code alone).
By breaking it down into multiple smaller steps, you're not only making it easier to read/maintain, you're also making it easier to debug, because a runtime error will be raised in a specific instruction on a specific line, and you'll be able to more easily pinpoint the faulty inputs.
Use With ... End With statements to localize any Range.Parent property.
Declare and Set a variable to the Excel Application object that can be used as a replacement for the WorksheetFunction object. This should make repeated calls to worksheet functions more readable.
Bring everything to the right of the equals sign down to the next line by supplying a _ (e.g. chr(95)). This acts like a concatenation character and allows single code lines to be spread over two or more lines. I've also use it to line up the two MATCH functions which return row and column to the Range.Cells property.
Dim app As Application
Set app = Application
With Worksheets("Sheet1").Range("mx_plan")
.Cells(app.Match(sortedAircraft.Item(i).tailNumber, Range("aircraft"), 0), _
app.Match(currentWeekId, Range("week_id"), 0) + weekly_hours_col_offset) = _
(acft_hoursDNE / acft_weeksRemaining)
End With
Set app = Nothing
That looks significantly more readable to my eye. Your use of named ranges may also be improved but it is hard to make suggestions without knowing the parent worksheets that each belongs to.
Note: I added a , 0 to each of the MATCH functions to force an exact match on unsorted data. I do not know if this was your intention but without them the data in the aircraft and week_id named ranges must be sorted (see MATCH function).

Why do we declare variables at the start of a module?

As my first language and as completely taught from other's example I never questioned the standard practice in VBA of grouping all variable declarations at the start of the module, routine or function they are scoped to as in this example.
Sub Traditional()
Dim bVariable as Boolean
Dim OtherVariable
' Some code using OtherVariable goes here
'
' Now we use bVariable
bVariable = True
Do While bVariable
bVariable = SomeFunction()
Loop
End Sub
Now I'm learning that standard practice in other languages is to declare variables as close to where they are used as possible, like this:
Sub Defensive()
Dim OtherVariable as String
' Some code using OtherVariable goes here
'
' Now we use bVariable
Dim bVariable as Boolean
bVariable = True
Do While bVariable
bVariable = SomeFunction()
Loop
End Sub
This seems completely sensible to me as a defensive programming practice - in that it limits both span and live time (as explained in Code Complete), so I'm wondering if there is any reason for not doing the same in VBA? Possible reasons I can think of are memory, running time (e.g. repeatedly declaring inside a loop), tradition - arguably a good reason as there must be hundreds of thousands of VBA programmers who expect to see all used variables at the start of the routine. Are there any I've missed that might explain the benefit of this practice or at least where it came from?
I declare all my variables at the top. I think declaring them closer to first use masks (at least) two other problems that should be fixed first.
Procedures too long: If you're procedure is more than fits on a screen, perhaps it's doing too much and should be broken into smaller chunks. You'll also find that unit tests are way easier to write when your procedures are small and only do one thing.
Too many variables: If you have a bunch of related variables, consider using a custom class module or user-defined type. It will make the code more readable and easier to maintain.
If your procedures are short and you're using classes and UDTs, the benefits of declaring the variables at the point of use are lessened or eliminated.
I think both way are just different coding style in VBA
In old C Standard, all Declaration must be on the top, I think many people just adopt this habit and bring it into other PL such as VBA.
Declaring variable on the top is clear for short list of variable names. It will be unreadable for a long list of variable name
Declaring variable close to where it's being used is introduced later. I think this practice has a clear advantage over "declare on the top" for PLs that has optimizer or more scope than VBA. (Like you can declare variables where the scope is visible in a FOR loop only) Then the optimizer will change the scope for you. (In VBA words, it may change a GLOBAL variable to a PROCEDURE variable)
For VBA, no perference

.Select, .Activesheet, .Activecell etc...

For this question, I refer to the post below to clarify myself:
Why is my conditional format offset when added by VBA?
In many, many posts I see these days, OP's are silently allowed to use .Activate, .Select, .Offset, etc... while they are an open door to potential bugs (most often caused by the end users).
The code is sometimes even supported.
My question: Is there one valid situation where you would use any of these statements without direct alternatives being available that catch typical bugs resulting from these stmts?
I'm referring to dynamic solutions that in my opinion are a must when developing for Excel.
Personally, in more than 6 years I can't remember a single case where I needed it; it seems always to be one of the the worst options available. In my previous company, it was a silent rule never to use it and it only made my VBA life (and that of the end user) better.
Why I create this question is because I think that it is worthful to make newcomers into VBA aware of the risks they take when using these statements (by experience proven risks when the End Users do something unexpected - in the end they don't have any affection with VBA) and to propose direct alternatives (I won't state I always did that before myself, but I feel in my gut that there is something wrong with just offering quick solutions on already bug monsters).
I believe that when silently allowed (which it automatically enhances in this case), starting VBA developers will create a growing amount of tools the wrong way (and thus also newcomers will inherit the behaviour - which they will also learn from Stack Overflow since Google returns the results they look for (!)).
If the developer is not aware why he "can" use a "select" and in which situations it is a potential bug, (s)he should never use it imho. Personally I might use the select stmt in the immediate window to do some quick checks on dynamic range definition (bug mode), but not in written code.
The result makes VBA in the end even more unpopular than it is already; the language will be made the victim in case trouble appear (yet it is imho still the "best" programming support available for the Excel and Access applications). I've seen this happen too many times in a large company where VBA is always "shit".
This is only my own honest experience.
It is not a question of being right or wrong; I am interested in hearing your point of view on the question.
I agree about Select and Activate, but not ActiveWorkbook, ActiveSheet, and ActiveCell (I agree that they are abused, but not that they should be avoided, per se). There are definitely legitimate uses for those. I have a program that automates a "fill series" that does so from the ActiveCell. My program can't predict what cells will be used; it's up the user to select it. That's part of the user interface.
However, there are three situations where I have had to use Select (now four that I read about zoom, but I don't ever use it).
Conditional Formatting. There is a work around using Application.ConvertFormula, but it's worse than just storing the selection, selecting the right cell, doing the deed, and reselecting the previous selection.
Data Validation. Same reason.
Shapes. I wish I could remember the details, but it's been too long since I've worked with Shapes. There was something I couldn't do without selecting the shape first.
Ridding code of Select and Activate is a noble fight.
There are a few methods in Excel that require Activate or ActiveSheet/ActiveWorkbook etc as I've been caught with a gotchas on occasion. The only one I can remember at the moment is the zoom property. Zoom affects only the sheet that's currently active in the window so to zoom all sheets you would need something like
Sub SetZoom()
Dim ws As Worksheet
Application.screenupdating = false
For Each ws In Worksheets
ws.Select
ActiveWindow.Zoom = 80
Next ws
Application.screenupdating = true
End Sub
You can use .Select to determine what a user's view is after running code - for example if you create a new workbook in your code, without using Activate or Select your user may not know this happens.
I frequently end a long operation creating a new workbook or other largescale data manipulations with
FinalViewWorkbook.FinalViewSheet.Range("A1").Select
Just to inform the end user about something - "oh, this created a new workbook of reports!" etc.
I think it is important in this matter to distinguish some:
Active-something: Only use this if it is absolutely necessary to know what the user is handling right now. In my experience, this is usually Data Validation or Active Sheet Detection (e.g. "Update the Sheet where the user just pressed a button").
Selection: Somewhat the same as Active, only use readingly. Userful either for Data Validation, or for gimmicks like "Interpret the cell value as path and open it in a new Explorer Window".
Select, Activate: Imho different from Selection, as it actually changes the selected Cell, Sheet etc. Never ever use this to read or write data, since it enables a user to mess up your program by just clicking. Users love to click. Only use this to Zoom (see answer by #user3357963) or clean up a view after your code has finished working (see answer by #enderland). (I'm not sure, but I think handling the PageView also requires ActiveSheet).
Select, Activate the 2nd: If you are new to VBA and are learning via Macro Recorder, you will find a lot of code generated like this:
First Range("A5").Select, then Selection.Value="NewValue". Join this to Range("A5").Value="NewValue".
Offset: Personally, I don't have a problem using .Offset() - I never encountered problems with this command. Instead, I think it's a handy way of saying "The cell next to this" without having to go through "This cell's sheet at this cell's row and column+1" every time.
In many, many posts I see these days, OP's are silently allowed to use .Activate, .Select, .Offset, etc...
I agree with this. Even though it's easier to just give the necessary answer to make a piece of code work, the use of ActiveCell.Value and the like should be discouraged. This will be much easier if there's a well explained Thread to link to, as this here is hopefully becoming :-)
From my perspective, with few exceptions, the only time you should use Select is as a user input, and only then after careful consideration of alternative design/UI requirements.
For example, I'd say it's generally not advisable to rely on Selection to let user define a Range object when this method keeps execution within the code:
Dim myRange as Range
Set myRange = Application.InputBox("Select your range", Type:=8)
However, if you need to prompt users to select a particular shape or object on the worksheet, then maybe it's better to let them make a Selection (however, this can open up a Pandora's Box of problems without good error-handling and logic to prevent undesired user actions...).
Here is an example of one such exception that I have in PowerPoint. I have some RibbonUI XML and VBA that adds buttons to the Shapes right-click context menu in PowerPoint, and adds similar buttons to the Ribbon itself. These are seamless UI that give the end-user a more "native" experience with the application -- users want to be able to right-click the chart and then run some macro procedures against that selected chart or table, etc. They don't want to press a button to open up a user form and scroll through a listbox of generic shape names or GUIDs.
The procedure code needs to examine the Selection in order to handle it properly so I can use something like below, where
Sub UpdateOrEditSelection(update As Boolean)
'This procedure invoked when user edits/updates a chart.
Dim uid As Variant
Dim sel As Selection
Dim s As Integer
Dim chartsToUpdate As Object
Dim multipleShapes As Boolean
Dim sld As Slide
Set sel = ppPres.Windows(1).Selection
If update Then
Set chartsToUpdate = CreateObject("Scripting.Dictionary")
Select Case sel.Type
Case ppSelectionShapes
For s = 1 To sel.ShapeRange.count
uid = sel.ShapeRange(s).Name
'....
'...
'..
'.
Next
Case ppSelectionSlides
For Each sld In sel.SlideRange
For s = 1 To sld.Shapes.count
uid = sld.Shapes(s).Name
'....
'...
'..
'.
Next
Next
Case ppSelectionText
s = 1
If sel.ShapeRange(s).HasTable Or sel.ShapeRange(s).HasChart Then
uid = sel.ShapeRange(s).Name
'....
'...
'..
'.
End If
End Select
'....
'...
'..
'.
Where does it come from?
The Macro Recorder. Essentially, this feature records every literal user input: scrolling, selecting, viewing, activating, default properties, etc., to the point of overkill. While this is sometimes helpful, it does encourage bad code written by people who don't know that it's bad, but I will not belabor that point which has been made here:
How to avoid using Select in Excel VBA macros
What is better, conceptually?
Program to the objects directly. If you're merely using VBA to mimic keystrokes and mouseclicks, you're doing it wrong.
Exceptions:
I've found when applying formatting to series data in charts, where Select is occasionally necessary. This seems IMO to be a bug with Excel and not a design feature.
Other applications (because VBA is not only Excel):
Word is a different animal, which relies a lot more on Selection object
In PowerPoint there are some sort of operations that can only be performed when the application and slide/shape are visible or otherwise in view. While you don't usually need to "select" anything, it does require more cumbersome code.
I found this snippet in my App:
Set tb = cht.Shapes.AddTextbox(msoTextOrientationHorizontal, ptLeft, tBoxTop, ptWidth, ptHeight)
tb.Select '<--- KEEP THIS LINE OTHERWISE TEXTBOX ALIGNMENT WILL NOT WORK ## ## ##
And this:
'PPT requires selecting the slide in order to export an image preview/jpg
sld.Select
ppPres.Windows(1).View.GotoSlide sld.SlideIndex
sld.Shapes(1).Chart.Export imgPath, ppShapeFormatJPG
And this, dealing with individual Point objects:
pt.Select
pt.format.Line.Visible = msoTrue
pt.format.Line.Visible = msoFalse
pt.MarkerSize = pt.MarkerSize + 2
This is not an exhaustive list, just some examples of exceptions that I found. While these were from PowerPoint, the charts in PowerPoint use the same object model as Excel so I would not be surprised if some of these also need to be hacked in Excel, and Word, too.
Outlook: I don't do much with Outlook, it is a lot like Word and actually uses the Word object model in the Inspector, but what little I do with Outlook does rely on things like ActiveInspector, etc.
Neither Word or PowerPoint have a "macro recorder" anymore (actually, I think Word might but it's so damn impotent as to be useless) and by the time most people do any development in other applications, they've figured most of this out already.

VB6 Editor changing case of variable names

I'm not much of a Visual Basic person, but I am tasked with maintaining an old VB6 app. Whenever I check out a file, the editor will replace a bunch of the uppercase variable names with lowercase automatically. How can I make this stop!? I don't want to have to change them all back, and it's a pain to have these changes show up in SourceSafe "Differences" when I'm trying to locate the REAL differences.
It is changing it automatically in the definition, too:
Dim C as Control becomes Dim c as Control. Dim X& becomes Dim x&. But it doesn't do it all the time; for example, three lines down from Dim x&, there's a Dim Y&, uppercase, which it did not change. Why's it do this to me?
Since I always find this thread first looking for a solution to messed-up casing, here is one Simon D proposed in a related question:
If you just need to fix one variable's casing (e.g. you accidentally made a cOrrectCAse variable and now they are all over the place), you can correct this for good by adding
#If False Then
Dim CorrectCase
#End If
to the beginning of your module. If you save the project and remove the statement later, the casing remains correct.
Using Excel VBA I often accidentally change all Range.Row to Range.row by carelessly dimming a row variable inside some function - with the help of Simon D's solution I can fix this now.
Continuing from DJ's answer...
And it won't only change the case of variables in the same scope either.
It will change the case of all variables with the same name in your entire project. So even if they're declared in uppercase in one place, another module might have different variables using the same variable names in lowercase, causing all variables in your project to change to lowercase, depending on which of the declarations was loaded (?) or edited last.
So the reason your C and X variables are changing case, while the Y isn't, is probably because C and X are declared somewhere else in your project too, but in lowercase, while Y isn't.
There's another mention of it here, where they mostly seem concerned with such variable names conflicting when case is being used to differentiate local from global variables. They end up going for prefixes instead.
The only alternative I can think of is to use some other editor with VB6-highlighting capabilities to do your editing...
Enums are even worse. Getting the case wrong anywhere the enum is used changes the case of the definition.
To get past the painful file diff experience, set the VSS option in the diff dialog to do case-insensitive comparisons. That way you'll only see the "real" changes.
It must be defined/declared in lower-case. Locate the declaration and fix it there. The IDE will always change the case to match the declaration.
Close all the VB projects, open the form file with a text editor, change the case of all the names then re-open the Project with VB IDE.
Prevent VB6 auto correct For enum values
As my general rule I declare constants in UPPERCASE. Since enums are essentially constants I like to declare values for enums in UPPERCASE as well. I noticed the VB6 IDE also auto corrects these.
I found the IDE does not correct these values when using numbers and underscores '_' in the value names.
Example:
Public Enum myEnum
VALUE 'Will be corrected to: Value
VALUE1 'Will not be corrected
VALUE_ 'Will not be corrected
End Enum
I do not know if this works in general and if this extends to naming/auto correction of variable names.
I have had a similar enumeration problem where for no apparent reason UPPERCASE was changed to MixedCase.
Enum eRowDepths
BD = 1
CF = 1
Separator = 1
Header = 3
subTotal = 2
End Enum
When I changed to the following (tailing the last character of the non-conforming variables), I had no problem
Enum eRowDepths
BD = 1
CF = 1
SEPARATO = 1
HEADE = 3
SUBTOTA = 2
End Enum
It turns out that this is a case of the tail wagging the dog. I have the following code, not the most elegant I admit but working nonetheless (please excuse formatting issues):-
'insert 3 rows
iSubTotalPlaceHolder = i
rows(ActiveSheet.Range(rangeDMirrorSubTotals).Cells.Count + _
Header _
& ":" _
& ActiveSheet.Range(rangeDMirrorSubTotals).Cells.Count + _
Header + _
subTotal + _
Separator).Insert
So it seems that the compiler won't accept explicit UpperCase constants as part of this statement.
This was acceptable
Dim fred as Integer
fred = SEPARATO + HEADE + SUBTOTA
So my work-around is to use a variable instead of the constants as part of the complex insert statement if I want to stick to the rule of keeping enumerated constants uppercase.
I hope this is of use
DJ is spot on... VB always changes the case of variables to match the original declaration. It's a 'feature'.
Continuing from Mercator's excellent answer...
I'd recommend:
Check out all files (I assume you're using VSS for a VB6 app)
Do a rebuild of the entire project group
Recheck back into VSS
Now base you're the real differences rather than the 'auto' changes that VB6 has tried to apply.