RVBA Coding Conventions |
Search [fdjbciek] |
||
Previous Next Xoc Software Training RVBA Conventions Maya Calendar Program Company Information Tools ASP.NET and Other Tips Articles Miscellaneous Downloads Links Search Other Xoc managed sites: http://grr.xoc.net http://www.986faq.com http://www.mayainfo.org https://mayacalendar.xoc.net http://www.yachtslog.com |
Version 1.01Copyright © 1999-2000 by Gregory ReddickIndex: IntroductionWhat follows are the Reddick VBA (RVBA) Coding Conventions. The objectives of the conventions are to make code:
These conventions are separate from the RVBA Naming Conventions and may be adopted without adopting the naming conventions. The current version of these conventions can always be found on the Xoc Software web site: http://www.xoc.net. More rational is provided for these recommendations than is given in the RVBA Naming Conventions. In most cases, there are good rationales for the given conventions. However, in some cases an arbitrary decision was made to select one convention from a set of reasonable alternatives. The other reasonable alternatives to the conventions placed in {braces} at the end of a section. In some cases, a topic only relates to the Visual Basic 6.0 development environment, as opposed to VBA in general. In those cases, the topic is marked with [VB6] after the topic heading. Some of the comments within a section may also be Visual Basic 6.0, specific. Many of these rules can also apply to VBScript code, but which rules apply are not explicitly marked. No set of conventions can cover every case or every consideration. The general rule is that exceptions to the conventions can be made with the approval of the programming team after careful consideration. The sections are listed in alphabetical order to facilitate their use as a reference work. However, this makes the flow of the document unusual for casual reading as some topics are much more technical than others. ArraysAlways specify the both the lower and upper bound of an array. This makes explicit whether element zero of the array is a valid element or not. For example: Dim astrValue(1 To 10) As String By convention the index variable used to walk an array should always be a Long data type. This assures that if the array size grows past 32767 elements when maintaining the program that the index variable can still address all elements in the array. When walking an array, always use the VBA LBound and UBound functions to visit each item. This makes sure that every item in the array is visited. For example: Dim iastrValue As Long For iastrValue = LBound(astrValue) To UBound(astrValue) MsgBox astrValue(iastrValue) Next iastrValue AssertionsVBA provides a built-in assertion mechanism through Debug.Assert. If the expression following the Debug.Assert evaluates to True, the code continues. If the expression evaluates to False, VBA enters Break mode as if a breakpoint had been set on that line. The line shown here acts as a hard coded breakpoint: Debug.Assert False Assertions that do not include a function call in the expression are removed by the compiler when an executable is made, so they only apply to debugging inside the VBA environment. Assertions with a function call in the expression will remain in the executable, but the resulting value of the expression is discarded. VBA doesn't remove function calls because they may have side effects, but discards the return value from the function. Any time that there is an assumption in the code about the state of the program, there should be an assertion that states the assumption. For example, suppose that a procedure includes this code: Select Case intValue Case 1 MsgBox "Aircraft" Case 2 MsgBox "AutoMobile" Case 3 MsgBox "SnowMobile" End Select This code assumes that the value of intValue is between one and three. However, if through some bug, intValue had the value of zero or four, this code doesn't work right. The result is that no MsgBox appears at all. Tracking down why the MsgBox doesn't appear is time consuming. Instead, the code could be written one of two other ways. Either: Debug.Assert intValue >= 1 And intValue <= 3 Select Case intValue Case 1 MsgBox "Aircraft" Case 2 MsgBox "AutoMobile" Case 3 MsgBox "SnowMobile" End SelectOr Select Case intValue Case 1 MsgBox "Aircraft" Case 2 MsgBox "AutoMobile" Case 3 MsgBox "SnowMobile" Case Else Debug.Assert False End Select In general, every Select/End Select block should have a Case Else to trap unexpected values. If the Case Else should never occur, then a Debug.Assert False should be inserted into the block. If the code is correctly written to handle 1 To 3, but zero and four are allowed values, the code should be written with a comment in the Case Else block to indicate that this is expected, like this: Select Case intValue Case 1 MsgBox "Aircraft" Case 2 MsgBox "AutoMobile" Case 3 MsgBox "SnowMobile" Case Else 'Do nothing End Select Assertions trap logic errors early. Rather than waiting to see the results of a bug in the use interface, there is immediate feedback that the bug has occurred. Assertions are only effective if they are present, which means that they have to be added when writing the code. Any logic error that is fixed in the code is a good indication that some additional assertions need to be added. CommentsA comment in VBA starts with an apostrophe and ends at the end of the line. Comments may be placed on a line by themselves or at the end of a line. A comment starts with the apostrophe followed immediately by the text with no space between the two. The comment at the end of a line should be used in only a few places:
Examples: Dim dateUTC As String 'time in Univeral Coordinated Time Case 11 'Division by Zero If the end of a line comment line exceeds the 80 characters line limit, continue the comment on the next line indented by one tab stop. For example: Case 35602 'This key is already associated with an element of this 'collection Set nodChild = tvw.Nodes.Item(cci.Guid) All other comments should be placed on a separate line above the line they are documenting and indented to the same level. A comment of this sort is generally preceded by a blank line unless it is the first line of an indented block. For example: vt = vti.VarType 'Special hack for analyzing my code If LCase$(Left$(strParamName, Len(strcDecPrefix))) = strcDecPrefix Then strDataType = strDecimal End If If it is the first line of an indented block, it is not preceded by a blank line. For example: If mboolShowProperties Then 'Show properties for each member For Each mi In ci.Members Comments should state the intention of the code not how it performs the task. This is an example of a worthless comment: 'Place the VarType into the vt variable vt = vti.VarType It is worse than no comment at all. The comment is wrong if the code changes to use the variable name vtCur instead of vt without changing the comment. When reading a comment that doesn't match the code, the question becomes whether the comment is correct or the code is correct. Usually it is the comment that is wrong, but it may take some time to prove that. In general, do not write comments that have to be maintained, because in the real-world people frequently do not maintain comments. A comment that states the intention of the code, though, may be useful. For example: 'Store VarType for recovery in error condition. vt = vti.VarType However, use these comments only when the intention is not immediately clear when reading the code. Instead strive to make the code self explanatory, through good naming and coding conventions. ConstantsAlways give constants an explicit data type. For example: Private Const dblcPi As Double = 3.14159265358979 If a literal value other than zero or one appears in the code, consideration should be given to whether it makes things more readable and maintainable to replace it with a constant. Replace a magic number used more than once in the code with a constant. Global constants are allowed and encouraged. Replace sets of constants of the data type long with enumerated types using Enum. Date Functions and Date VariablesBe careful when using the VBA date functions: Date, DateAdd, DateSerial, DateValue, and Now. These functions return a variant containing a date. If implicit type conversion turns the return value into a string, that string representation of the date may contain a two-digit year number, depending on the operating system the program runs under and the localization settings of the user. A two-digit year number is, of course, not Y2K compliant. Also do not allow a variable of type Date to be implicitly converted into a string. In both cases use the Format$ function to explicitly convert the date into a string. For example: strValue = Format$(Date, "mm/dd/yyyy") Because you will always want to use the Format$ function to create a string containing a date, you should never use the Date$ function. Default PropertiesUsing default properties makes code difficult to read. As an example, VBA allows you to just use the name of a textbox and looks up the default property, Text, like this: MsgBox txtValue This displays the value in the Text property of the txtValue textbox. On the other hand, it is much clearer to say: MsgBox txtValue.Text To even be more explicit, it could even be expressed as: MsgBox Me.txtValue.Text This, however, does not add any additional worth because all references to a control in a module for a form are implicitly on Me. The reason to be explicit about default properties is to keep the programmer from having to figure out what property is being referenced. This is especially true when referencing ActiveX controls and ActiveX DLLs where the default properties are obscure. For example, when an ADO field is referenced, you are allowed to say: varValue = rst!strFirstName This references the Value property of the strFirstName field. However, it is much clearer to say: varValue = rst.Fields.Item("strFirstName").Value This code doesn't use any default properties and retrieves the same value. Deprecated FeaturesAvoid using features Visual Basic supports only for backwards compatibility. Avoid using undocumented features. Also, avoid using functionality that VBA has replaced with functionality that is more modern. Some examples of these kinds of features:
DisambiguationWhen referencing classes from an ActiveX library, always use the library name to explicitly tell VBA from what library to get the class. If you don't, then VBA will use the order of the libraries in the References dialog to determine from which library it gets the class. The library name always appears in the upper left-hand listbox of the VBA object browser. For example, if there are references to both the Access and Excel object libraries, then this is ambiguous: Dim appObj As Application Because both the Access and Excel libraries include an Application class, which Application class is referenced depends on which one appears first in the References dialog. Instead, it should be declared like this: Dim appObj As Excel.Application Microsoft refers to this as "disambiguation". With this declaration, it does not matter what the order of the libraries is inside the References dialog, as appObj will always refer to the Excel Application object. All references to class names in libraries should include the disambiguating library name. DLL Base Address [VB6]The base address is the location that the DLL is loaded into memory. If two DLLs are loaded into the same base address, then VBA moves the second DLL to a new address. VBA then has to modify the binary code within the DLL's address space to reflect the new address. This slows down loading the second DLL. Libraries used together should start at different base addresses. In Visual Basic, enter the Base Address for a library in the Project Properties dialog Compile tab. Enter a random number base address different than any other used at the same time. Dollar Sign ($) FunctionsIf the result of a function is used as a string or assigned it to a string variable, use the $ form of the function. This results in faster executing code, because a conversion from a variant to a string is unnecessary. For example, this is proper usage of dollar sign functions: If LCase$(Left$(strParamName, Len(strcDecPrefix))) = strcDecPrefix Then This example calls the LCase$ and Left$ functions instead of the LCase and Left functions because the result is used as a String. If the result is used as a Variant, then call the LCase and Left functions instead. The $ version of the function returns the same value as the Variant version. The one exception to the rule is the VBA Date function. The Date function should always be used because the Date$ function doesn't behave correctly. The Date$ always returns information in mm-dd-yyyy format regardless of the Windows localization settings, whereas the Date function uses the localization settings. Furthermore, you will always want to use the Format$ function to explicitly format a date. See also the section on Date Functions. Error HandlingA procedure should always include runtime error handling. In general, Error handling should be blocked out the same way in every procedure, as shown in this example: Private Sub Test() On Error GoTo ErrorHandler 'Code for the procedure goes here ExitProcedure: On Error Resume Next 'Cleanup code for the procedure goes here Exit Sub ErrorHandler: Select Case Err.Number 'Case statements for expected errors goes here Case Else Call UnexpectedError(Err.Number, Err.Description, Err.Source, _ Err.HelpFile, Err.HelpContext) End Select Resume ExitProcedure End Sub Use the label names shown in the example, although the label names have been arbitrarily chosen. Notice that the Exit Sub and ErrorHandler label are left justified making them easily findable. Case statements for expected errors should be given with the error number and a comment with the error message. For example: Select Case Err.Number 'Case statements for expected errors go here Case 11 'Division by zero MsgBox "Zero isn't a valid divisor", vbExclamation, Me.Caption Case Else The UnexpectedError routine is a global routine that is only called in a condition where a runtime error that isn't expected is received, meaning that there is a bug in the problem. This procedure should log the error message. At the absolute minimum it should just look like this, but ideally it should do a lot more to log the error: Public Sub UnexpectedError(ByVal lngNumber As Long, _ ByVal strDescription As String, ByVal strSource As String, _ ByVal strHelpfile As String, ByVal lngHelpContext As Long) On Error Resume Next MsgBox "[" & strSource & "]" & vbCrLf & "Run-time error '" _ & CStr(lngNumber) & "':" _ & vbCrLf & vbCrLf & strDescription, vbExclamation, App.Title, _ strHelpfile, lngHelpContext Debug.Print "Case " & CStr(lngNumber) & " '" & strDescription Debug.Assert False End Sub The first executable line of every procedure should be the On Error GoTo ErrorHandler line. The only exception to the rule is when a procedure checks the values of its arguments and generates a runtime error when they are invalid. In this case, the checking code comes before the On Error GoTo line. For example Public Sub Test(ByVal intValue As Integer) If intValue < 1 Or intValue > 10 Then Call Err.Raise(Number:=lngcInvalidValue, _ Description:=strcInvalidValue) End If On Error Goto ErrorHandler Exiting a ProcedureIn general, a procedure should only have one exit point. Having one exit point makes it easier to read the code and understand when and where it exits. If you use the code mentioned in the Error Handling code section, that exit point is the Exit Sub, Exit Function, or Exit Property line at the top of the error handling. The only other way to exit the procedure should be through using Err.Raise. These Err.Raise lines should occur either before the On Error GoTo line when validating the parameters (see Error Handling) or inside the error handler. In a few cases, there may be a need to raise an error inside the body of the procedure. In such cases, you should explicitly set any object variables to Nothing (see Nothing), and then exit the procedure. In such cases, the exiting the procedure should be explicitly detailed by a comment that shows the exit, consisting of an arrow stretching to 80 character right margin. The On Error GoTo 0 statement has to be used to turn off error handling for this procedure before executing the Err.Raise. For example, if this code appears somewhere after the On Error GoTo line, it should be written like this to make it explicit that there is an exit point in the middle of the procedure: If intValue > 1000 Then 'Raise an Error-------------------------------------------------------> Set rst = Nothing On Error Goto 0 Call Err.Raise(Number:=lngcInvalidValue, _ Description:=strcInvalidValue) End if For/Next and For Each/Next LoopsThe index variable used in the For/Next loop should be specified on the Next line. This makes it explicit which For loop is being completed. For example: Dim iastrValue As Long For iastrValue = LBound(astrValue) To UBound(astrValue) MsgBox astrValue(iastrValue) Next iastrValue The object variable used to walk the collection should be placed on the Next line in a For Each/Next loop. For example: Dim frm As Form For Each frm in Forms If Not (frm Is Me) Then Unload Me End If Next frm GoTo StatementsYou can usually avoid using GoTo statements in VBA code. However, GoTo statements are not explicitly prohibited. Use GoTo statements only when the alternative code is not as clear as the GoTo statement. A common reason to use a GoTo statement is to jump out of nested loops. For example: For iastrOuterLoop = 1 To 10 For iastrInnerLoop = 1 To 100 'some other code If astr(iastrOuterLoop, iastrInnerLoop) = "Done" Then GoTo ExitNestedLoops End If 'some other code Next iastrInnerLoop Next iastrOuterLoop ExitNestedLoops: 'More code here HeadersEach module should start with a header code that looks something like this: '$Header: $ '****************************************************************************** Option Explicit 'This module includes definitions of Windows API calls The line of asterisks is an apostrophe followed by 79 asterisks. See the section on Long Lines. Each Public procedure should begin with a header block that looks something like this: Public Sub Almanac(ByVal lngTrecena As Long, ByVal vein As veinc, _ ByVal lngRows As Long, ByRef alngBlack() As Long, _ ByRef alngRed() As Long, ByRef aveinRowStart() As veinc, _ ByRef aveinAlmanac() As veinc, ByRef lngComplete As Long) 'Generates a Maya almanac 'lngTrecena [in] Upper left corner trecena 'vein [in] Upper left corner veintena 'lngRows [in] Number of rows in the almanac 'alngBlack() [in] Black distance numbers across almanac 'alngRed() [out] Calculated Red trecena numbers across almanac 'aveinRowStart() [out] Calculated Leftmost shown veintenas in almanac 'aveinAlmanac() [out] Actual veintenas implied by almanac 'lngComplete [out] Number almanac misses completing by. 'Return value: None 'If lngComplete returns zero then it is an almanac, if it is non-zero, 'then it misses completing and you'll need to report that. You will still 'need to handle the error encNotAnAlmanac because the black numbers 'in alngBlack must wrap back to the starting lngTrecena. On Error GoTo ErrorHandler Read/write values allowed are [in], [out], and [inout]. Event procedures do not need a header unless the scope is changed to Public. Private procedures may need the header depending on the context. Note that the name of the routine is not referenced in the comments, making it possible to change the name of the procedure without changing the comments. No change history or coding history is included. Histories should be maintained by source code control systems, not by programmers since they are rarely properly kept up to date. The comments are addressed to the person calling the procedure, and should include just enough information to tell the person how to call the procedure and use the returned values. After the On Error GoTo, other comments can be placed describing algorithms and other implementation details, if needed (although see the section on Comments). IndentingTab stops should be set at four spaces. No member of a programming team should vary this number, as it makes editing other members of the team's code difficult. All code inside a block should be indented one tab stop from the surrounding code, with exceptions noted elsewhere in this document. Indenting blocks makes finding the start and end of the block easy. A block is defined as the code that falls between the following keywords:
For example: For Each ci In tlio.Constants Set nodChild = tvw.Nodes.Add(Relative:=nod.Key, _ Relationship:=tvwChild, _ Key:=ci.Guid & ci.Name, Text:=ci.Name, Image:=strcEnum) Call nodChild.EnsureVisible DoEvents If mboolShowProperties Then For Each mi In ci.Members Set nodEnumChild = tvw.Nodes.Add(Relative:=nodChild.Key, _ Relationship:=tvwChild, Text:=mi.Name & strcEquals _ & mi.Value, Image:=strcConstant) Call nodEnumChild.EnsureVisible DoEvents Next mi End If Next ci See also the section on Select/End Select Blocks. {Alternative: The entire programming team may standardize on another number of spaces for a tab stop.} {Alternative: Exit Function, Exit Property, and Exit Sub statements may be indented to the level of the surrounding code.} InstantiationAn object variable should not be declared with New on the line it is declared on, unless there is a good reason to do so. The declaration should instead be broken into two lines. For example: Dim rst As ADODB.Recordset Set rst = New ADODB.Recordset Not this: Dim rst As New ADODB.Recordset Breaking it into two lines causes each reference to the rst variable to execute slightly faster. In addition, the object variable can be tested to see if it contains the value Nothing. For example: If rst Is Nothing Then MsgBox "rst not initialized" End If If a one-line declaration is used, the above code would never execute the MsgBox because the reference to the rst variable in the If statement causes the object to be instantiated before the Is operator is evaluated. For Private and Public object variables, occasionally the convenience of using the New keyword outweighs the performance benefit, so the one-line declaration may still be used. LabelsLabels in the code should be left justified, regardless of the indenting level of the surrounding code. They should appear on a line by themselves. For example: ExitProcedure: On Error Resume Next Long Lines of CodeVBA code editors will scroll a line of code to make the end of the line visible. However, this makes it difficult to read the code quickly. It also means the code is not understandable if placed into a media that doesn't scroll, such as a paper print out or a book. For these reasons, the length of lines should be restricted. A physical line of code should not exceed 80 characters. If a logical line of code exceeds 80 characters, then the line should be broken into two or more physical lines using the underscore line continuation character. All physical lines in the logical line following the first physical line should be indented one tab stop (four spaces) from the first physical line. It may help to place a line at the top of the module with an apostrophe followed by 79 asterisks. Then the code window of the VBA editor can be sized to barely make the last asterisk visible. A fixed width font, such as Courier New, should be used to display the code in the VBA code window. You should choose an appropriate place to break the line to enhance the maximum readability of the remaining code. When breaking lines that have a list separated by commas, you should break the line after a comma and before the next non-space character. For example: Private Sub GetFiles(ByRef fso As Scripting.FileSystemObject, _ ByRef fld As Scripting.Folder) When breaking a line that is an expression built by operators, break the line before an operator of the expression. For example when the expression is built of string concatenation operators, break it like this: strParameters = strParameters & strAdd _ & strPassingConvention & pmi.Name & strArray _ & strcAs & strDataType & strDefault The next line becomes more readable this way. If you have a long literal string, you may have to break the line like this: strValue = "This is a very, very long string that will cause the code " _ & "to wrap. Because of this, you will need to break it." In such cases, break it before the start of a word. Note that VBA performs the string concatenation at runtime, so this has performance considerations. In many cases, the string should be placed into a constant, an entry in resource file, or a database field and retrieved from there. Comments should never be continued. When a comment exceeds 80 characters, continue the comment on the next line preceded by another apostrophe. See the section on Comments. Don't overly indent lines. Move overly indented code to a new procedure and call it from the original. In general, code should not need to be indented more than eight tab stops. {Alternative: Place operators at the end of the line before the line continuation character instead of on the next line.} NothingExplicitly set Object variables to Nothing before allowing the variable to be destroyed. This is especially true of object variables declared with the Dim keyword. For example: Public Sub Test(ByVal intValue As Integer) 'error handling omitted for clarity Dim rst As ADODB.RecordSet Set rst = New ADODB.RecordSet 'More code here Set rst = Nothing End Sub Setting the object variable to Nothing is not just good programming practice. If the rst object has code in its Class_Terminate event handler, that code can mess with global variables and objects. In addition, set the object variable to Nothing is before exiting the procedure with Err.Raise. For example: Private Sub Test(ByVal intValue As Integer) 'error handling omitted for clarity Dim rst As ADODB.RecordSet Set rst = New ADODB.RecordSet 'More code here If intValue > 1000 Then 'Raise an Error------------------------------------------------------> Set rst = Nothing On Error Goto 0 Call Err.Raise(Number:=lngcInvalidValue, _ Description:=strcInvalidValue) End if Set rst = Nothing End Sub In the example just shown, if the rst object variable is not set to Nothing before performing the Err.Raise, the Class_Terminate of the rst object likely will change the properties of the Err object so that it no longer reflects the number given in lngcInvalidValue. This Class_Terminate code executes before the calling routine's error handler is invoked. This weird flow of execution has caused a number of very difficult to track down bugs. Parameters to a ProcedureEvery parameter to a procedure should be given an explicit data type, including variants. Every parameter should be passed by value using the ByVal keyword, with a few exceptions. These are:
Even in the cases where the argument should be passed by reference, you should explicitly prefix the parameter with ByRef, even though this is the default in VBA. This makes it explicit that you meant to pass that parameter by reference. After VBA inserts an event procedure, the parameters to the event procedure should be changed to include ByVal and ByRef keywords, and change the parameter names to use the appropriate naming conventions. For example, VBA inserts the event procedure like this: Private Sub Form_MouseMove(Button As Integer, Shift As Integer, X As Single,Y As Single) End Sub This should be changed to read like this: Private Sub Form_MouseMove(ByRef intButton As Integer, _ ByRef intShift As Integer, ByRef sngX As Single, ByRef sngY As Single) End Sub By changing it to read like this, the naming conventions indicate the data type and the ByRef keywords indicate that VBA may see the changes to the parameters. ParenthesesAlways use parentheses where the reading of the line may be unclear. For example, suppose that a line is written: If Not frmTest Is Nothing Then It may not be clear that the Is operator has higher precedence than the Not operator in this line. Recode it to read: If Not (frmTest Is Nothing) Then This makes clear the evaluation order of the operators. The general rule is that if there is any question what the operator precedence is, use parentheses to make it clear. Procedure ScopeAlways use the Private scope on a procedure unless you need to expose the procedure outside the current module. In a library, use the Friend scope when you need a larger scope. Use Public only when access to the procedure is required outside the library. For example: Private Sub Test() Project Properties [VB6]In Visual Basic, the Project Properties dialog should always be filled in. These values may not apply to VBA hosts other than Visual Basic. Most of these fields can be retrieved in the operating system from the EXE, DLL, or OCX file by right clicking on it in the Windows Explorer, then selecting Properties, then clicking on the Version tab in the dialog that appears. The values can be retrieved from within the program by getting properties of the App global system object. The following fields should be always be filled in:
Raising ErrorsWhen you raise a runtime error from a component, to be trapped in the calling code, the error number that you raise should have a unique error number. For this purpose, VBA defines a constant vbObjectError that guarantees that errors that you generate will not conflict with ones that VBA defines. However, all libraries use errors in the range larger than vbObjectError, so you should strive to be different from the other libraries with your numbers. There is no way to guarantee this; the chances can be reduced by starting your errors at a random number in the range 512 to 32767 larger than vbObjectError. No library that an organization produces should ever have conflicting error numbers with another library from the same organization. For example: XYZ Software might start numbering its errors at vbObjectError + 4096. The first library produced from XYZ software might generate errors in the range from vbObjectError + 4096 to vbObjectError + 4146, the second library from vbObjectError + 4147 to vbObjectError + 4196, etc. Select/End Select BlocksThe Select/End Select block is indented differently from other blocks (see Indenting). The Case blocks within the Select/End Select are lined up with the Select/End Select keywords. Code within a Case block is indented one tab stop from the Case statement. For example: Select Case Err.Number Case tliErrCantLoadLibrary Err.Raise Number:=Err.Number, Description:=Err.Description, _ Source:=Err.Source Case 35602 'This key is already associated with an element of this 'collection Set nodChild = tvw.Nodes.Item(cci.Guid) nodChild.Image = "InstClass" Resume NextItem Case Else Call UnexpectedError(Err.Number, Err.Description, Err.Source, _ Err.HelpFile, Err.HelpContext) End Select In non-RVBA coding standards, it is more common to indent Case blocks one tab stop from the surrounding Select/End Select. However, this causes the actual executing code to be indented two tab stops from the surrounding Select/End Select. The readability of the code is just as good, if not better with this scheme, although it takes some getting use to the first Case block being indented to the same level as the Select line. See also the note about Case Else blocks in the section on Assertions. {Alternative: Indent the Case blocks one tab stop from the surrounding Select/End Select. Then indent the code in the case blocks one more tab stop.} Source Code Control [VB6]Code should be maintained using some sort of Source Code Control. Microsoft Visual SourceSafe is the most common product used for this. When using Visual SourceSafe, the Administrator should configure it to expand keywords in files in the SourceSafe Administrator Options dialog. The following files should be expanded: *.bas,*.cls,*.ctl,*.frm,*.pag,*.vbp. Entries such as $Header: $ can then be placed into the code and are expanded automatically. See the SourceSafe documentation on keyword expansion. Also, see the use of the Comments entry in the section on Project Properties in this document. Type ConversionVBA is considered a weakly typed language. You can construct expressions such as this one: strValue = "Your order came to " & intQuantity * curPrice VBA will automatically convert the result of the expression into a string to make the expression work. However, it is better programming practice to make explicit what VBA is doing using the type conversion functions: CBool, CInt, CLng, CStr, etc., plus the Format$ function. For example: strValue = "Your order came to " _ & Format$(CCur(intQuantity) * curPrice, "$#,###.00") The RVBA Naming Conventions will help point out possible bugs. If you see a line that looks like this, you may have a potential bug: intValue = lngInput If the value in the variable lngInput is 90,000, this line will cause an Overflow runtime error. The fact that the types of the variables are different is a clear warning sign. If, however, you knew the value in lngInput could only be in the range 1 to 1000, it might be acceptable to do the assignment like this: Debug.Assert lngValue >= 1 And lngValue <= 1000 intValue = CInt(lngInput) See also the section on Assertions. Variable DeclarationEvery variable should be explicitly declared. Using the Option Explicit keyword at the top of the module will have VBA enforce explicit declaration. The VBA editor's Tools Options dialog has a Require Variable Declarations setting that will make Option Explicit automatically inserted in all new modules. Every variable should be given an explicit data type. This includes variants, which are the default. For example, a variant should be declared as: Dim varValue As Variant Rather than letting it be implicitly defined or declaring it as: Dim varValue Every variable should be declared on a line by itself. This precludes running into this bug: Dim intValue, intTest As Integer That declaration makes it obvious from the naming conventions that the two variables were meant to be declared as integers, but the first variable is defined as a variant. If instead the declarations were made on a line by themselves, the problem goes away. For example: Dim intValue As Integer Dim intTest As Integer In addition, by declaring each variable on a line by itself, you can use the Ctrl+Y keyboard shortcut to cut the declaration to the clipboard regardless of where the caret is on the line, then paste it somewhere else. If there are multiple declarations on a line then editing is not as easy. Variable InitializationSome languages allow declaring a variable and giving it an initial value at the same time. VBA does not have a syntax for that. A variable has a default value at the time that it is declared based on its data type. However, a syntax variation can be used inside of a procedure to give the feel of initializing it and assigning the default value. Visual Basic allows you to place multiple logical lines of code on the same physical line if you separate them with colons. It also allows you to mix the declaration of variables with executable lines of code. Therefore, inside a procedure you can initialize a variable and give it a default value in one physical line like this: Public Sub Test() 'Error handling omitted for clarity Dim intValue As Integer: intValue = 7 Dim strTest As String: strTest = "Default Value" 'other code End Sub Variables Scope and LifetimeVariables should always be declared with the smallest level of scope and the shortest lifetime possible. Thus, you should declare variables with Dim inside of procedures by preference. If you need a longer lifetime, then use Static. If you need a wider scope, use Private. Only use Public as a last resort. Public variables declared in standard modules are global and can be changed by any piece of code throughout the entire project. This makes debugging changes to their value very difficult. Global variables of this sort should only be used in the context where they are set during initialization of the program, then remain static for the rest of the time the program executing. Global variables should never be changed in one part of the program to be retrieved in another part of the program. In such cases, you should use parameters of procedures or properties of forms or objects to pass the information. If there are more than 20 global variables in the program, it is a warning sign that the program design is wrong. Having Public constants are allowed and encouraged. See the section on Constants. Version Compatibility [VB6]After building an ActiveX control or ActiveX DLL for the first time in Visual Basic, the project compatibility should be set to Binary Compatibility in the Project Properties dialog. Each time the component is "released," a copy of the component should be made to the same directory, but with the filename extension set to CMP. The binary compatible file entry should point to this file. That means that you can modify the interface to the file within a release as long as you are still backwards compatible with the last release. The CMP file should be checked into the Source Code Control project, whereas the current copy of the component itself probably should not be checked in (see the section on Source Code Control). SummaryThe RVBA Coding Conventions make your code easier to read, easier to maintain, more reliable, and faster. |
||
Top |
[www.xoc.net] Copyright © 1997-2023 by Gregory Reddick . All Rights Reserved. 02/20/09 01:28 |