[Previous] [ Next ]
When you create and use variables, you should strive to make each variable serve a clearly defined purpose. It can be tempting to make variables serve double duty, but the small savings in memory is often negated by the resulting complexity of the code. A variable used for multiple purposes is said to be unfocused. If you need to perform unrelated actions or calculations on a variable, you'd be better served by having two focused variables rather than one unfocused variable. It's unnecessarily difficult to read and debug code that contains unfocused variables, and the code itself is more likely to contain errors.
Incorrect:
PublicSub AssignGroup(strGroup AsString ) '*Purpose:Addagrouptothecurrentaccount. '*Accepts:strGroup-Grouptoassigntoaccount. OnErrorGoTo PROC_ERR Dim strTemp AsString Const c_DuplicateRecord=3022 strTemp="INSERTINTOtblAssignedAccGroups(AccountNumber,"&_ "[Group])VALUES("&Me.AccountNumber&_ ","""&strGroup&""");" '*Captureanyerrors-specificallyduplicaterecorderrors. OnErrorResumeNext Err.Clear dbGroups.ExecutestrTemp,dbFailOnError '*Checktoseewhethertherewasaduplicaterecorderror. If Err.Number=c_DuplicateRecord Then '*Thisisaduplicateentry;ignoreandgetout. strTemp="Thegroupyouhavespecified("&strGroup&_ ")alreadyexistsinthedatabase." MsgBoxstrTemp,vbInformation EndIf OnErrorGoTo PROC_ERR PROC_EXIT: ExitSub PROC_ERR: Call ShowError(Me.Name,"AssignGroup",Err.Number,Err.Description) GoTo PROC_EXIT EndSub |
Correct:
PublicSub AssignGroup(strGroup AsString ) '*Purpose:Addagrouptothecurrentaccount. '*Accepts:strGroup-Grouptoassigntoaccount. OnErrorGoTo PROC_ERR Dim strSQL AsString Dim strMessage AsString Const c_DuplicateRecord=3022 strSQL="INSERTINTOtblAssignedAccGroups(AccountNumber,"&_ "[Group])VALUES("&Me.AccountNumber&_ ","""&strGroup&""");" '*Captureanyerrors-specificallyduplicaterecorderrors. OnErrorResumeNext Err.Clear dbGroups.ExecutestrSQL,dbFailOnError '*Checktoseewhethertherewasaduplicaterecorderror. If Err.Number=c_DuplicateRecord Then '*Thisisaduplicateentry;ignoreandgetout. strMessage="Thegroupyouhavespecified("&strGroup&_ ")alreadyexistsinthedatabase." MsgBoxstrMessage,vbInformation EndIf OnErrorGoTo PROC_ERR PROC_EXIT: ExitSub PROC_ERR: Call ShowError(Me.Name,"AssignGroup",Err.Number,Err.Description) GoTo PROC_EXIT EndSub |
The names you choose for variables are very important. Even if your code will never be seen by another person, you should write it as if it will be reviewed by dozens of other developers. Don't use the names of friends or pets, and don't use offensive words. When you're coming down from a caffeine high at three o'clock in the morning, you might be tempted to use all sorts of strange variable names. Using a bizarre reference to the Vincent Price movie you've been watching might help you release tension, but it will make your code considerably less readable.
NOTE
It's easier to name a variable that has been created for a well-defined purpose.
Giving your variables logical names is one of the easiest things you can do to make your code more understandable. Variables such as x , y , and i have almost no place in modern software development. They might be fine for loop counters (although better names are almost always available), but they should never hold important data. Not convinced? Have you ever seen code like this?
Dim rstCustomers As Recordset Dim i AsInteger '*PrintalistofthefieldsintheRecordset. For i=0 To rstCustomers.Fields.Count-1 Debug.Print rstCustomers.Fields(i).Name Next i |
Sure, i works in this example. However, what does i represent? In this instance, it represents an index of a field. With that in mind, consider how much more readable this code is:
Dim rstCustomers As Recordset Dim intIndex AsInteger '*PrintalistofthefieldsintheRecordset. For intIndex=0 To rstCustomers.Fields.Count-1 Debug.Print rstCustomers.Fields(intIndex).Name Next intIndex |
NOTE
You should reserve x, y, and z for variables that hold coordinates. For instance, it's perfectly valid to have variables named x and y in a charting program.
For a more dramatic example, look at the next code snippet, which was taken directly from a well-known Visual Basic publication. I've left out the declarations of the variables on purpose.
av=Split(sCmdLine,".,"&sCRLF&sTab) ForEach v In av s=s&v Next |
Can you tell exactly what this code does? I can't. It's pretty obvious that this code would be much more understandable if the variables had descriptive names. Also, note the use of sCRLF and sTab (constants defined by the author). Using constants in place of magic numbers is good practice, but Microsoft Visual Basic includes built-in constants for these values ( vbCrLf and vbTab , respectively). You should always use a system constant if one is available (as discussed in Chapter 5, "Using Constants and Enumerations").
An important thing to remember when you name variables is that the limitations that existed in older languages are gone. For instance, some languages limited the number of characters you could use to define a variable, so some developers still use variable names such as EMPADD1 , EMPADD2 , and STXTOTAL. Code consisting of deliberately shortened variable names is difficult to understand and maintain. There is no longer a valid reason for this practice. This is not to say that all variables should have long names; you should choose names that make the most sense ”whether they're short or long. The advantage of naming variables EmployeeAddress1 , EmployeeAddress2 , and SalesTaxTotal is clear: these names are more self-explanatory than their shorter counterparts.
6.2.1 Avoid naming variables Temp . One variable name you should avoid is Temp . It's easy to create variables such as rstTemp , intTemp , and strTemp , but all local variables are temporary by nature, so naming a local variable Temp is redundant. Generally , developers mean "scratch variable" when they say "temp variable," and you can almost always find an alternative for Temp . For instance, when you use the InStr() function to determine the location of a string within a string, it might be tempting to use a statement such as this:
intTemp=InStr(strBeginTime,"A") |
A much better alternative is to give the variable a descriptive name like this one:
intCharacterLocation=InStr(strBeginTime,"A") |
Or you can simply use this:
intLocation=InStr(strBeginTime,"A") |
Incorrect:
PublicFunction StripDoubleQuotes(ByValstrString AsString ) AsString '*Purpose:DoublequotescauseerrorsinSQLstatements. '*Thisprocedurestripsthemfromtheenteredtext '*andreplacesthemwithpairsofsinglequotes. '*Accepts:strStringbyvaluesothatchangesmadetoitaren't '*alsomadedirectlytothevariablebeingpassedin. '*Returns:Theoriginalstring,withalldoublequotesreplaced '*bypairsofsinglequotes. OnErrorGoTo PROC_ERR Dim intTemp AsInteger Dim blnQuotesFound AsBoolean Const c_DoubleQuote="""" blnQuotesFound= False '*Repeataslongasdoublequotesarefound. Do '*Lookforadoublequote. intTemp=InStr(1,strString,c_DoubleQuote) '*Ifadoublequoteisfound,replaceitwithapair '*ofsinglequotes. If intTemp>0 Then strString=Left$(strString,intTemp-1)&"''"&_ Mid$(strString,intTemp+1) blnQuotesFound= True Else blnQuotesFound= False EndIf LoopWhile blnQuotesFound StripDoubleQuotes=strString PROC_EXIT: ExitFunction PROC_ERR: Call ShowError(Me.Name,"StripDoubleQuotes",Err.Number,_ Err.Description) ResumeNext EndFunction |
Correct:
PublicFunction StripDoubleQuotes(ByValstrString AsString ) AsString '*Purpose:DoublequotescauseerrorsinSQLstatements. '*Thisprocedurestripsthemfromtheenteredtext '*andreplacesthemwithpairsofsinglequotes. '*Accepts:strStringbyvaluesothatchangesmadetoitaren't '*alsomadedirectlytothevariablebeingpassedin. '*Returns:Theoriginalstring,withalldoublequotesreplaced '*bypairsofsinglequotes. OnErrorGoTo PROC_ERR Dim intLocation AsInteger Dim blnQuotesFound AsBoolean Const c_DoubleQuote="""" blnQuotesFound= False '*Repeataslongasdoublequotesarefound. Do '*Lookfordoublequotes. intLocation=InStr(1,strString,c_DoubleQuote) '*Ifadoublequoteisfound,replaceitwithapair '*ofsinglequotes. If intLocation>0 Then strString=Left$(strString,intLocation-1)&"''"&_ Mid$(strString,intLocation+1) blnQuotesFound= True Else blnQuotesFound= False EndIf LoopWhile blnQuotesFound StripDoubleQuotes=strString PROC_EXIT: ExitFunction PROC_ERR: Call ShowError(Me.Name,"StripDoubleQuotes",Err.Number,_ Err.Description) ResumeNext EndFunction |
IT'S HARDER TO READ SENTENCES IN ALL UPPERCASE THAN SENTENCES IN MIXED CASE. In the past, some languages required all uppercase letters for variable names. This limitation does not exist in Visual Basic, so use mixed case for all variable names. All lowercase is almost as bad as all uppercase. For instance, EmployeeAge is far better than EMPLOYEEAGE or employeeage . If you prefer, you can use underscore characters to simulate spaces (for example, Employee_Age ). However, if you do this, do it consistently.
Incorrect:
Dim strFIRSTNAME AsString Dim strlastname AsString |
Correct:
Dim strFIRSTNAME AsString Dim strlastname AsString |
Also correct:
Dim strFirst_Name AsString Dim strLast_Name AsString |
Although you should generally avoid abbreviating variable names, sometimes it makes sense to shorten a word or a term used for a variable name. Thirty-two characters is about the longest variable name you should create. Longer names can be difficult to read on some displays.
When you need to abbreviate, be consistent throughout your project. For instance, if you use Cnt in some areas of your project and Count in other areas, you add unnecessary complexity to the code.
One situation in which you might want to abbreviate variable names is when you're working with a set of related variables, such as these employee- related variables:
Dim strEmployeeName AsString Dim strEmployeeAddress1 AsString Dim strEmployeeAddress2 AsString Dim strEmployeeCity AsString Dim strEmployeeState AsString Dim strEmployeeZipcode AsString |
Here, abbreviation becomes more attractive. If you abbreviate, be consistent. For instance, don't use variations such as EmpAddress1 , EmplAddress1 , and EmpAdd1 . Pick the abbreviation that makes sense and stick to it. Also, try to abbreviate only the part of the variable name that is the common component, not the part that is the unique specifier . In this case, abbreviating Employee is acceptable, but abbreviating Address is probably not a good idea.
Incorrect:
Dim strEmployeeName AsString Dim strEmplAdd AsString Dim strEmployeeCity AsString |
Correct:
Dim strEmpName AsString Dim strEmpAddress AsString Dim strEmpCity AsString |
Most projects contain variables that are related to one another. For instance, when you work with device contexts to perform drawing functions, you often have a source context and a destination context. You might also have variables that keep track of the first and last items in a set. In such situations, you should use a standard qualifier at the end of the variable name. By placing the qualifier at the end, you create more uniform variables that are easier to understand and easier to search for. For example, use strCustomerFirst and strCustomerLast instead of strFirstCustomer and strLastCustomer .
Common Variable Qualifiers
Qualifier | Description |
---|---|
First | First element in a set. |
Last | Last element in a set. |
Next | Next element in a set. |
Prev | Previous element in a set. |
Cur | Current element in a set. |
Min | Minimum value in a set. |
Max | Maximum value in a set. |
Save | Preserves another variable that must be reset later. |
Tmp | A "scratch" variable whose scope is highly localized within the code. The value of a Tmp variable is usually valid only across a set of contiguous statements within a single procedure. |
Src | Source. Frequently used in comparison and transfer routines. |
Dst | Destination. Often used in conjunction with Src . |
Incorrect:
Dim strFirstCustomer AsString Dim strLastCustomer AsString Dim strPreviousCustomer AsString |
Correct:
Dim strCustomerFirst AsString Dim strCustomerLast AsString Dim strCustomerPrevious AsString |
Boolean variable names deserve special mention. These variables have the value True or False ”nothing else. When you name Boolean variables, always use the positive form ”for instance, blnFound , blnLoaded , and blnDone rather than blnNotFound , blnNotLoaded , and blnNotDone. Using the negative form increases the chances that the variable will be handled incorrectly, particularly in complex expressions, where programming errors are more likely to occur. Positive names make Boolean variables more understandable and reduce the risk of programming errors. Notice in the incorrect example below that the negative nature of the blnNotInHere variable (used to keep track of whether or not the procedure is currently executing) means that the procedure never fully executes because a Boolean variable is initialized to False.
Incorrect:
PrivateSub cmdPrint_Click(Index AsInteger ) '*Purpose:Printorcancel,dependingonthebuttonclicked. OnErrorGoTo PROC_ERR Static blnNotInHere AsBoolean Const c_Print=0 Const c_Cancel=1 '*Preventthiseventfromexecutingtwicebecauseofauser '*double-clickingthebutton. IfNot (blnNotInHere) ThenGoTo PROC_EXIT blnNotInHere= False '*Determinewhichbuttonwasclicked. SelectCase Index CaseIs =c_Print '*Printthequote. Call PrintQuote CaseIs =c_Cancel '*Unloadtheform. UnloadMe CaseElse '*Nootherindexisexpected. EndSelect '*Resetthevariablethatkeepstrackofwhetherwe'reinhere. blnNotInHere= True PROC_EXIT: ExitSub PROC_ERR: Call ShowError(Me.Name,"cmdPrint_Click",Err.Number,_ Err.Description) ResumeNext EndSub |
Correct:
PrivateSub cmdPrint_Click(Index AsInteger ) '*Purpose:Printorcancel,dependingonthebuttonclicked. OnErrorGoTo PROC_ERR Static blnInHere AsBoolean Const c_Print=0 Const c_Cancel=1 '*Preventthiseventfromexecutingtwicebecauseofauser '*double-clickingthebutton. If blnInHere ThenGoTo PROC_EXIT blnInHere= True '*Determinewhichbuttonwasclicked. SelectCase Index CaseIs =c_Print '*Printthequote. Call PrintQuote CaseIs =c_Cancel '*Unloadtheform. UnloadMe CaseElse '*Nootherindexisexpected. EndSelect '*Resetthevariablethatkeepstrackofwhetherwe'reinhere. blnInHere= False PROC_EXIT: ExitSub PROC_ERR: Call ShowError(Me.Name,"cmdPrint_Click",Err.Number,_ Err.Description) ResumeNext EndSub |
When a variable is declared with Dim , Private , Public , or Static , it's explicitly declared. Visual Basic does not require that you explicitly declare your variables, but it should. Code that includes variables that aren't explicitly declared is far more prone to errors than code with explicitly declared variables and has no place in modern software development. This is one reason why explicit declaration of variables is one of the first things a code reviewer looks for. When code simply references variables as needed (implicit declaration), it can seem as though the developer didn't give much thought to the logic of the code or the data used. Variables are important, and they deserve a formal declaration. A formal declaration at least shows that the developer gave due consideration to the use of the variable and didn't just throw it in at the last minute.
Here is a simple illustration of the problems of not explicitly declaring variables:
PrivateSub cmdTest_Click() strMessage="Thisisatest." Debug.Print strMesage EndSub |
If you weren't paying close attention, you might have expected Visual Basic to print "This is a test." when the procedure was executed. However, Visual Basic prints an empty string. Here's what happens.
The first statement is fine. It implicitly creates a new variable called strMessage and places the string "This is a test." into the new variable.
Line 1: strMessage="Thisisatest." |
But here's the problem. Notice the misspelling of the variable name:
Line 2: Debug.Print strMesage |
Since strMesage has not been declared as a variable and it's not a reserved word, Visual Basic automatically creates and initializes the variable "on the fly," just as it did for the variable strMessage . When variables are not explicitly declared as a specific data type, they are created as variants (which is a problem unto itself, as noted in Directive 6.9). A variant is initially empty, and therefore nothing prints. (Actually, an empty string is printed.) Note that all of this occurs without any compile-time or run-time errors.
You can easily prevent Visual Basic from allowing you to reference variables that aren't explicitly declared by adding Option Explicit to the Declarations section of a module. If Visual Basic encounters a variable that hasn't been explicitly declared in a module that contains Option Explicit in its Declarations section, an error is generated at compile time. Since you can't compile code with compile errors, you physically can't distribute these types of errors in compiled programs. Note, however, that Option Explicit applies only to the module in which it appears. Therefore, you must place it in the Declarations section of every module in your project (including Form and Class modules) for it to be fully effective.
Visual Basic can automatically add the Option Explicit statement to new modules. To enable this feature, choose Options from the Tools menu and then select Require Variable Declaration, as shown in Figure 6-1.
Note that when you enable this feature, Visual Basic does not place Option Explicit into any existing modules; you must manually add it to existing modules. The good news is that Require Variable Declaration is a systemwide setting, so all new modules created in any projects will have Option Explicit placed in their Declarations sections.
It's hard to fathom why, after all this time, Option Explicit is still an option. What's worse , it's not even the default option ”when Visual Basic is first installed, it does not enforce variable declaration; you have to turn on this feature. One of the first things you should do after installing Visual Basic is enable this feature. If you have a large project developed without Option Explicit in the Declarations sections of your modules, add Option Explicit to the modules and attempt to compile your code. You might be surprised at the results. If you find references to variables that haven't been declared, you'll probably want to put the code through some rigorous examination and testing.
Figure 6-1. Select the Require Variable Declaration check box on the Editor tab of the Options dialog box to enforce explicit variable declaration.
Incorrect:
PrivateSub cmdTest_Click() intValue=6 Debug.Print intValue/2 EndSub |
Correct:
PrivateSub cmdTest_Click() Dim intValue AsInteger intValue=6 Debug.Print intValue/2 EndSub |
Visual Basic provides many different data types for variables, and often, more than one data type will work in a given situation. Choosing the best data type for each variable can reduce memory requirements, speed up execution, and reduce the chance of errors. Also, different data types have different resource requirements, and the data type you use for a variable can affect the results of computations based on that variable. For instance, consider the following code:
PrivateSub cmdCalculate_Click() Dim sngFirst AsSingle Dim sngSecond AsSingle Dim intResult AsInteger sngFirst=3 sngSecond=0.5 intResult=sngFirst+sngSecond Debug.Print intResult EndSub |
When this code is executed, the number 4 is printed in the Immediate window. The correct result should, of course, be 3.5. However, Visual Basic does its best to follow your instructions and rounds the result of the calculation to fit in the Integer variable. Visual Basic doesn't raise a run-time error in situations like this ”it simply forces the value to fit the data type. Such problems can be extremely tricky to debug. In this example, simply changing the type of the intResult variable to Single solves the problem.
Coercing (rounding) is not the only problem created by incorrect numeric data types. When you perform numeric calculations, you must be mindful of possible overflow errors. If you attempt to put a value into a variable and the value is greater than the maximum value for the data type, the run-time error "6 ” Overflow" occurs. (See Figure 6-2.)
Figure 6-2. When you attempt to put too large a value in a variable, this overflow error occurs.
This type of error is easy to demonstrate . Consider the following code:
PrivateSub cmdCalculate_Click() Dim intFirst AsInteger Dim intSecond AsInteger Dim intResult AsInteger intFirst=32000 intSecond=32000 intResult=intFirst+intSecond Debug.Print intResult EndSub |
The maximum value that can be stored in an Integer variable is 32,767. Since intFirst and intSecond are both assigned the value 32,000, no error is generated. However, when you attempt to place the result of adding these two numbers (64,000) in intResult (another Integer variable), an overflow error occurs. In this case, changing intResult to a Long solves the problem. Fortunately, overflow errors are easier to debug than rounding problems, but they often aren't apparent until the application is used live.
When you create a variable, consider whether the variable will hold the results of calculations based on other variables. In some cases, going to the next larger data type (Double from Single, Long from Integer) is the safest bet. In others, you might find that there is an even more specific data type available, such as the Currency data type for holding monetary values.
Incorrect:
PrivateSub PrintTax(sngTaxableAmount AsSingle ,lngTaxRate AsLong ) '*Purpose:Printthetotaltax(taxableamount*taxrate). '*Accepts:sngTaxableAmount-thedollarvaluetowhichtax '*applies. '*lngTaxRate-thelocaltaxrate. OnErrorGoTo PROC_ERR Dim sngTax AsSingle sngTax=sngTaxableAmount*lngTaxRate Debug.Print sngTax PROC_EXIT: ExitSub PROC_ERR: Call ShowError(Me.Name,"PrintTax",Err.Number,Err.Description) GoTo Proc_Exit EndSub |
Correct:
PrivateSub PrintTax(curTaxableAmount AsCurrency ,_ sngTaxRate AsSingle ) '*Purpose:Printthetotaltax(taxableamount*taxrate). '*Accepts:curTaxableAmount-thedollarvaluetowhichtax '*applies. '*sngTaxRate-thelocaltaxrate. OnErrorGoTo Proc_Err Dim curTax AsCurrency curTax=curTaxableAmount*sngTaxRate Debug.Print curTax PROC_EXIT: ExitSub PROC_ERR: Call ShowError(Me.Name,"PrintTax",Err.Number,Err.Description) GoTo Proc_Exit EndSub |
The following table lists the Visual Basic data types.
Visual Basic Data Types
Data Type | Value Range |
---|---|
Byte | 0 to 255 |
Boolean | True or False |
Integer | -32,768 to 32,767 |
Long | -2,147,483,648 to 2,147,483,647 |
Single | -3.402823E38 to -1.401298E-45 for negative values 1.401298E-45 to 3.402823E38 for positive values |
Double | -1.79769313486232E308 to -4.94065645841247E-324 for negative values 4.94065645841247E-324 to 1.79769313486232E308 for positive values |
Currency | -922,337,203,685,477.5808 to 922,337,203,685,477.5807 |
Date | January 1, 100 to December 31, 9999 |
Object | Any object reference |
Variable-length String | 0 to approximately 2 billion characters |
Variant | Numbers: any numeric value up to the range of a Double String: same range as for variable-length String |
Here are some helpful guidelines for using data types:
Visual Basic is extremely lenient on the typing of variables. It's quite content to "protect" you by coercing data when it feels it's appropriate. Nowhere is this more evident than in the use of the Variant data type. This data type is often abused, and there is almost always a better alternative to the Variant data type. When used correctly, a Variant is a great tool for certain tasks . You can create Variant variables that can hold any type of data, including objects. Visual Basic is even fairly smart at determining the type of data a Variant holds and applying the appropriate behavior to the determined type. For instance, take a look at the following code:
Dim vntA AsVariant Dim vntB AsVariant vntA="1" vntB=2 Debug.Print vntA+vntB |
When executed, the code prints 3 , even though the variable vntA contains a string. When Visual Basic encounters the + operator, it determines that, if at all possible, it must perform arithmetic using numbers. Visual Basic evaluates the two Variant variables in the preceding code and determines that they do indeed each contain a number. It then adds the two numbers and gets a result of 3. However, by changing a single character in the procedure, as shown below, the outcome is completely different:
Dim vntA AsVariant Dim vntB AsVariant vntA="1" vntB=2 Debug.Print vntA&vntB |
When this code is executed, 12 is printed. The ampersand (&) character is used for string concatenation; when Visual Basic encounters the ampersand, it evaluates the two variables to determine whether their values can be interpreted as Strings. Although only one of the variables ( vntA ) contains a String, Visual Basic decides that it can still represent the other variable's value as a String. This is called coercion or (somewhat less affectionately) evil type coercion (ETC). The result of such coercion is often unpredictable. Visual Basic can sometimes coerce strictly typed variables (variables declared as specific data types), but it is more likely to do this with Variants. The previous two code samples produce the same results even if the variables are declared as specific types such as String and Long. However, compare the following two code examples:
Dim strA AsString Dim strB AsString strA=1 strB=2 Debug.Print strA+strB |
and
Dim vntA AsVariant Dim vntB AsVariant vntA=1 vntB=2 Debug.Print vntA+vntB |
The first code example prints 12. When Visual Basic works with String variables, it treats the + symbol as a concatenation operator. However, recall that the default behavior of the + symbol is for arithmetic. In the second example, Visual Basic looks at the Variant variables to see if their values can be treated as numbers, which in this case they can. Therefore, it adds them and prints 3. Other than the data types (and the names of the variables to enforce the naming prefixes), these two procedures are identical ”but they produce different values.
NOTE
Never use the + symbol for string concatenation ”use an ampersand.
The simple fact that Visual Basic treats data in a Variant variable differently at different times should be enough to dissuade you from using Variants when they aren't necessary. Variants have other pitfalls as well. For instance, they consume more memory than any other Visual Basic data type ”16 bytes for any numeric value up to the range of a Double and a whopping 22 bytes plus the string length when they hold string data! Arrays of any data type require 20 bytes of memory plus 4 bytes for each dimension ”for example, Dim MyArray(9, 5) declares an array with 2 dimensions ”plus the bytes for each data element itself. Consequently, if you create an array of Variants, you should have a darned good reason for doing so. Not only do Variants consume more memory than other data types, but manipulating the data within them is almost always slower than performing the same manipulation with strict data types.
Declaring a variable with As Variant isn't the only way to create a Variant variable. If you neglect to include an As <type> clause in a variable declaration, the variable is created as a Variant. For instance, the following two declarations yield the same result:
Dim MyVariable Dim MyVariable AsVariant |
The same holds true for Function procedures. If you neglect to include an As <type> clause when you define a function, the return value of the function is always a Variant. The following definitions yield the same result:
PrivateFunction MyFunction() PrivateFunction MyFunction() AsVariant |
For the reasons stated above, you should always declare variables and Function procedures as specific types. If you want to declare either one as type Variant, explicitly declare the variable or Function procedure by using As Variant .
In addition to omitting an As <type> clause when declaring a variable or Function procedure, there's another all-too-common way to inadvertently create Variant variables. Consider this line of code:
Dim VariableA,VariableB,VariableC AsString |
What are the data types of these three variables? If you said that they're all Strings, you're probably not alone, but you're definitely not correct. Visual Basic allows you to declare multiple variables on a single line to save space ”not to make it easy to declare multiple variables of the same data type (which would be the obvious reason). When you declare multiple variables on the same line with a single As <type> clause, only the last variable in the Dim statement is given the specified type. All the other variables are allocated as Variants. For this reason, you should always declare each individual variable on a separate line. If you must declare multiple variables on a single line ”and you should avoid this ”declare each variable with an As <type> clause, like this:
Dim VariableA AsString ,VariableB AsString |
By paying close attention to your declaration of variables, you can keep the number of Variant variables to a minimum, resulting in leaner, quicker, and often more dependable code.
Incorrect:
Dim vntControl AsVariant '*Loopthroughallcontrolsontheformandprinttheirheights. ForEach vntControl In Me.Controls Debug.Print vntControl.Height Next vntControl |
Correct:
Dim cntControl AsControl '*Loopthroughallcontrolsontheformandprinttheirheights. ForEach cntControl In Me.Controls Debug.Print cntControl.Height Next cntControl |
6.9.1 Declare only one variable per line if possible. To reduce the possibility of inadvertently creating Variant variables and to create more readable code, refrain from declaring multiple variables on the same line.
Incorrect:
Dim strName,strAddress AsString |
Also incorrect:
Dim intAge AsInteger ,strName AsString ,strAddress AsString |
Correct:
Dim intAge AsInteger Dim strName AsString Dim strAddress AsString |
The scope of a variable is its visibility to other procedures and modules. Visual Basic essentially offers three levels of scope: procedural, module, and global. These levels are explained in the following table.
Levels of Scope in Visual Basic
Scope | Description |
---|---|
Procedural | The variable is declared within the procedure and therefore can be seen and used only within the procedure. Variables with procedural scope are often called local variables. |
Module | The variable is declared (with Dim or Private ) in the Declarations section of a module and is visible to all procedures within the module. |
Global | The variable is declared as Public within a standard module. The variable is visible to all procedures within all modules of the project. |
You should try to minimize scope as much as possible. The smaller the scope of a variable, the lower the odds of errors related to the variable. Also, when variables go out of scope, their resources are reclaimed by the operating system. If the scope of a variable is wider than it needs to be, the variable might remain in existence and consume resources long after it's no longer needed. If data needs to be shared between procedures, pass the data as parameters between the procedures if at all possible.
Not all global variables are evil, but if you think of them as being generally demonic, you'll write better code. Unfortunately, some programmers treat the global variable as a panacea; they declare it once and use it for ever. Some even (gasp!) use a single global variable for multiple purposes!
The problems inherent with global variables are numerous . Their major problem is that they can be changed by any procedure within any module, and it's difficult to track where these changes take place. For instance, suppose that Procedure A makes use of a global variable. Procedure A reads the value of the variable and then calls Procedure B. Procedure B in turn calls Procedure C, which changes the value of the global variable. Procedure C ends and returns execution to Procedure B, which then ends and returns execution to Procedure A. Procedure A, meanwhile, is totally unaware of the change made to the global variable by Procedure C and proceeds to perform actions based on the original value of the global variable. Debugging in such scenarios is a nightmare.
As I mentioned earlier, consumption of resources is an important aspect of scope. With a single, ordinary variable, resource consumption is minimal. Imagine, however, the problems associated with creating global-level Recordset variables! Record locking and resource concerns are just a few of the issues associated with global-level Recordsets. Consider a procedure operating on the assumption that the Recordset is pointing to one record when it's actually pointing to another. With Recordset variables, minimizing scope can have a huge impact on the reliability of the application.
When you find yourself declaring a global variable, stop and look for a way to obtain the same functionality without using a global variable. You can usually find a better option, but it's not always obvious. For instance, if a module-level variable is used in only one procedure, you should make it a static variable local to that procedure. By doing so, you eliminate the possibility of another procedure inadvertently modifying the contents of the variable.
An excellent example of the use of a static variable can be found in the prevention of reentrancy. When we released our first Microsoft Windows program to our users, they experienced all sorts of errors. As developers, we took for granted that people knew to click buttons and toolbars only once. We came to find out that many users believed that double-clicking was the proper way to trigger all actions. Consequently, the Click event of a given button would execute on the second click of a double-click, but the event would still be in the process of executing from the first click. This is known as reentrancy, and it causes very strange and undesirable behavior. Users were also causing reentrancy when they double-clicked an item in a list or grid to perform an action. If the computer took slightly longer to perform the function than the user expected, they'd double-click the item again, causing the double-click to fire a second time while still executing code from the first time. Using a static variable, as shown in the following procedure, you can prevent reentrancy from causing problems in a procedure. Note, if you use a global variable instead of a static one, you run the risk of inadvertently changing the variable's value in another procedure that's performing the same technique.
PrivateSub grdListFind_DblClick() '*Purpose:Displaytheselectedrecordwhentheuser '*double-clicksarowonthegrid. OnErrorGoTo PROC_ERR Static blnInHere AsBoolean '*Protectthiseventfromreentrancycausedbyanoverzealoususer. If blnInHere ThenGoTo PROC_EXIT '*Flagthatwe'reinhere. blnInHere= True Call ShowSelectedRecord blnInHere= False PROC_EXIT: ExitSub PROC_ERR: Call ShowError(Me.Name,"grdListFind_dblClick",Err.Number,_ Err.Description) ResumeNext EndSub |
When you define variables, remember to use a scope prefix to clearly indicate the scope of the variable. The table on the following page lists the possible prefixes. (For more information on naming conventions and Hungarian prefixes, see Chapter 4.)
Scope Prefixes for Variables
Prefix | Description | Example |
---|---|---|
g | Global | g_strSavePath |
m | Local to module or form | m_blnDataChanged |
st | Static | st_blnInHere |
(no prefix) | Nonstatic variable; variable is local to the procedure |
A common mistake that new Visual Basic programmers make is to use the plus sign (+) to concatenate Strings. The plus sign is used in arithmetic to add two numbers, so to many people it feels natural to use the plus sign to concatenate two Strings. Visual Basic even lets you use the plus sign for concatenation, but you should never do this; the ampersand is the official symbol for concatenating Strings. The main problem with using the plus sign arises when one of the variables that you're concatenating is not a String variable. If a variable has a numeric data type, Visual Basic performs arithmetic rather than concatenation. Consider the following code:
Dim strFirst AsString Dim strSecond AsString strFirst="1" strSecond="2" Debug.Print strFirst+strSecond |
When this code is executed, 12 is printed in the Immediate window. Compare the previous code to the following:
Dim strFirst AsString Dim sngSecond AsSingle strFirst="1" sngSecond="2" Debug.Print strFirst+sngSecond |
The only difference is that the data type of sngSecond is now a Single. When this code is executed, the number 3 is printed. Because of this possible coercion, the only acceptable method of concatenation is to use the ampersand. The ampersand clearly conveys the intent of the function, removing any doubt a reader might have, and eliminates the possibility of incorrect results because of type coercion.
Incorrect:
Dim strFirstName AsString Dim strLastName AsString Dim strFullName AsString strFirstName="Ethan" strLastName="Foxall" '*Concatenatethefirstandlastnamesintoacompletename. strFullName=strFirstName+strLastName |
Correct:
Dim strFirstName AsString Dim strLastName AsString Dim strFullName AsString strFirstName="Ethan" strLastName="Foxall" '*Concatenatethefirstandlastnamesintoacompletename. strFullName=strFirstName&strLastName |