Practical Standards for Microsoft Visual Basic .NET (Pro-Developer)

[Previous] [ Next ]

8.1 Do not place multiple statements on a single line.

Microsoft Visual Basic is a robust language whose roots run deep. Many of its current features originate from early forms of Basic such as BASICA and GW-BASIC. While many of these "features" have survived a long time, it's time for some of them to die. One of these features is the ability to use the colon (:) to place multiple statements on a single line.

In the old days of Basic, code was often written on a screen that supported only about 80 characters (across) by 24 lines (down). Frequently, this meant that programmers had to pack as much information as possible into the tiny screen space. Some languages even restricted the number of characters or lines in a program. Whatever the reasons, many programmers adopted the technique of placing multiple statements on a single line. The truly cruel developers didn't just stop at placing two statements on a line, but would often include three or four! What's the harm, you say? Take a look at this procedure:

Dim intXLeg AsInteger Dim intYLeg AsInteger Dim udtCircle As typeCircle '*RetrievethelengthoftheXlegoftherectangle. intXLeg=Abs(m_rectBound.Right-m_rectBound.Left):intYLeg=_ Abs(m_rectBound.Bottom-m_rectBound.Top) If (intXLeg=0) Or (intYLeg=0) ThenGoTo PROC_EXIT With udtCircle .Aspect=Abs(intYLeg/intXLeg):.xCenter=m_rectBound.Left+_ (m_rectBound.Right-m_rectBound.Left)/2:.yCenter=_ m_rectBound.Top+(m_rectBound.Bottom-m_rectBound.Top)/2 '*Calculatetheradiusbasedonthelongerlegoftherectangle. If intXLeg>intYLeg Then .Radius=intXLeg/2 Else .Radius=intYLeg/2: EndIf EndWith

This procedure is inherently complicated. It takes a rectangle and determines, based on the rectangle's coordinates, whether it bounds a valid circle. If so, the specific circle information is stored in the variable udtCircle. When multiple statements appear on the same line separated by a colon, it becomes difficult to determine where one statement ends and another begins; complexity is added for no valid reason. Notice the final End If in the procedure. It's tacked onto the same line as the Else clause's code statement. Why? Because it saves vertical space. Not a very good reason in this example, and not a good reason in any other situation. Look at the same code sample with each statement having its own line:

Dim intXLeg AsInteger Dim intYLeg AsInteger Dim udtCircle As typeCircle '*RetrievethelengthoftheXlegoftherectangle. intXLeg=Abs(m_rectBound.Right-m_rectBound.Left) intYLeg=Abs(m_rectBound.Bottom-m_rectBound.Top) If (intXLeg=0) Or (intYLeg=0) ThenGoTo PROC_EXIT With udtCircle .Aspect=Abs(intYLeg/intXLeg) .xCenter=Bound.Left+(m_rectBound.Right-m_rectBound.Left)/2 .yCenter=Bound.Top+(m_rectBound.Bottom-m_rectBound.Top)/2 '*Calculatetheradiusbasedonthelongerlegoftherectangle. If intXLeg>intYLeg Then .Radius=intXLeg/2 Else .Radius=intYLeg/2 EndIf EndWith

The code is still complex, but it is not nearly as difficult to read. When you let each statement assert itself and give each all the space it requires, you write what I call territorial statements. Territorial statements stake their ground and don't give an inch. They don't share a line with any other statement, and they consume as many lines as they need.

8.2 Use the line continuation character.

While many programmers create territorially weak statements by making statements share lines, even more developers create territorially weak statements by not giving them enough lines. One reason for this is that Visual Basic didn't always support the use of the line continuation character (_) to break long statements into smaller fragments on multiple lines. Many programmers did not adopt the line continuation character when it showed up, and they're still writing mile-long statements that wear out your mouse as you scroll through the statement. If you're still writing these supertanker lines of code, here's a chance to introduce a small change into your programming style that will make your code much easier to read and maintain.

Using the line continuation character is simple. You simply select an appropriate spot in a statement and place the character in that location, preceded by a space. The line continuation character denotes that the next line of code is part of the current code statement. The benefits of using this character are evident when you use it to break very long SQL statements into multiple lines, as shown here:

strSQL="SELECTtblAssemblyDetail.*,tblInventory.Description,"&_ "tblInventory.SerialsFROMtblInventoryRIGHTJOIN"&_ "tblAssemblyDetailONtblInventory.ItemID="&_ "tblAssemblyDetail.ItemIDWHERE((tblAssemblyDetail."&_ "Assembly)="""&Me.ItemID&""")ORDERBY"&_ "tblAssemblyDetail.LineNumber;"

Without the use of the line continuation character, all you'd see in a typical code window is the text that appears on the first line. Thanks to the use of the line continuation character, all six lines displayed here actually compose a single code statement; it's a very territorial statement indeed.

When you deal with statements that contain large strings, if you don't enclose the string on each line with quotes, Visual Basic interprets the line continuation character as part of the string and you get a compile error. For instance, the following statement incorrectly uses the line continuation character:

strMessage="Thecloudsmovedswiftlyoverthevalley,churning_ andrumblingastheythreatenedthevillagers."

This statement simply won't compile. To properly place the statement on multiple lines, you have to concatenate (by using the ampersand) pieces of the string, like this:

strMessage="Thecloudsmovedswiftlyoverthevalley,churning"&_ "andrumblingastheythreatenedthevillagers."

NOTE


Since the line continuation character denotes a continuation, you can't place end-of-line comments after it.

Practical Applications

8.2.1 Do not exceed 90 characters on a line. In MS-DOS, you typically had columns of about 80 characters to work with ”that was it. In Visual Basic, the number of visible characters on a line is determined by the font size and monitor resolution of the developer's machine. Strictly limiting the number of characters on a line to 80 is not necessarily appropriate, nor is writing statements that can be viewed in their entirety only on monitors set to a resolution of 1280 x 1024.

NOTE


If you're developing on a machine at a resolution of 800 x 600 or less, consider buying a larger monitor and increasing your resolution. Your productivity will increase at a resolution of 1024 x 768 if you have a large enough monitor. A 17-inch monitor is perfect for 1024 x 768 for many developers, although you might consider a 19-inch monitor if you have vision problems.

At 800 x 600, with a code window sized to pretty much fill the screen horizontally, you can fit about 90 characters on a line. At 1024 x 768 under the same circumstances, you can fit quite a bit more, but you shouldn't. Generally , it's best to work with a code window sized smaller than the screen so that you still have access to windows such as the Properties window and, more important, the Project Explorer. Add the fact that many people are still using 800 x 600, and you've got a strong case for using about 90 characters as a line length. Depending on the optimum locations to split a statement, you might go slightly over 90 characters or way under 90 characters. It's OK to fall short of 90 characters by 10 or even 20 characters if it makes sense, but you should try to not exceed 90 characters. It would be nice if Visual Basic had an option to place a light gray vertical line in the code window at a specified column so that you could easily see when you approach the maximum length for a statement. Perhaps in a future release

8.2.2 Do not right-align multiple-line statements; always split a statement after a space. It isn't necessary and it's generally not a good idea to break statements so that each successive code line aligns with the right edge of the first line. When you break statements in this fashion, the breaks usually come in inferior locations. In addition, it becomes almost impossible to keep such rigid formatting when you modify a statement. When you break a statement into multiple lines, first find the general location where it would be best to break the statement. Then try to break the statement between reserved words or keywords. If you must break the statement in the middle of a string, try to place the break between words and after a space. By consistently breaking strings after spaces, you reduce the possibility of introducing problems when you modify the string.

NOTE


There are some places that you cannot break a statement, such as the middle of a reserved word. If you attempt to split the statement in such a place, you receive a syntax error.

Take a look at the statements below. Notice that an effort was made to line up the right sides of the lines. Although it looks pretty, this style is not practical and is prone to error. Notice that the words serial and designated are split. If you make any modifications to the text of this statement, you have to be careful to not leave orphaned letters or sentence fragments. Also, if you make changes and want to preserve the uniformity of the right side, you probably have to reformat some or all of the lines.

Incorrect:

strMessage="Serialnumber"&strSerialNumber&"foritem"""&_ strItemID&"""isinlocation"&rstSerials![Location]&_ "."&vbCrLf&"Ifyoucontinue,thelocationofthiss"&_ "erialnumberwillbechangedtothelocationdesignate"&_ "dinthetransfer."

Correct:

strMessage="Serialnumber"&strSerialNumber&"foritem"""&_ strItemID&"""isinlocation"&_ rstSerials![Location]&"."&vbCrLf&"Ifyou"&_ "continue,thelocationofthisserialnumberwill"&_ "bechangedtothelocationdesignatedinthetransfer."

If you make a spelling or syntax error in the text of a string, the worst that will probably happen is that you'll be embarrassed when the user sees the text displayed. If you accidentally corrupt the text of a SQL statement, your code might actually fail. Therefore, when you break up long SQL statements (a common task in database applications), you should maintain clear break points for ease of reading and maintenance. This is where the rule of always splitting after spaces gives you the most benefit. Consider the following code statement:

Statement with an error:

strSQL="SELECTtblAccounts.*,tblCustomAccount.*FROM"&_ "(tblAccountsINNERJOINtblAssignedAccGroupsONtblAccounts."&_ "AccountNumber=tblAssignedAccGroups.AccountNumber)"&_ "LEFTJOINtblCustomAccountONtblAccounts.AccountNumber="&_ "tblCustomAccount.AccountNumberWHERE((("&_ "tblAssignedAccGroups.Group)="""&_ frmGroups.SelectedGroup&"""));"

Notice that there is no space at the end of the string at the end of the first line. When concatenated with the second line, the text " FROM(tblAccounts " is created ”a definite syntax error, but not necessarily an obvious one to spot. Also note how the text on each line starts with a character and not a space. This consistency helps you spot errors and makes it easier to insert new code.

8.2.3 Break between expressions a statement that performs complicated expression evaluations. Breaking such statements between expressions doesn't just make your code easier to digest (because the entire statement is visible), it can actually make the expression easier for the reader to understand. Consider the following If statement:

If blnMoving And recStart.X=Int(X/intMag) And recStart.Y=_ Int(Y/intMag) Then EndIf

This statement is split at an acceptable location, which allows the reader to view the entire statement without scrolling, but the statement doesn't make clear exactly what it is evaluating. A more logical place to break the line is between two conditions, as shown below. Note the variation as well. You can place the Boolean operators at the end of each line, but I believe that placing them at the beginning of each line improves clarity. Choose the style that you are most comfortable with and use it consistently.

Correct:

If blnMoving And recStart.X=Int(X/intMag)_ And recStart.Y=Int(Y/intMag) Then EndIf

Perhaps even better:

If blnMoving_ And recStart.X=Int(X/intMag)_ And recStart.Y=Int(Y/intMag) Then EndIf

Variation:

If blnMoving And _ recStart.X=Int(X/intMag) And _ recStart.Y=Int(Y/intMag) Then EndIf

8.3 Indent continuation lines.

There is no hard-and-fast rule for the number of characters to indent continuation lines; you must make a judgment based on the first line of the statement. Some general guidelines are:

Formatting is visual, so think of these indentation guidelines visually. By comparing good and bad indentation in the following Practical Applications, you'll gain a better understanding of these indentation guidelines.

Practical Applications

8.3.1 When you set a variable to a value, make all continuation lines start at the same indentation as the value portion of the first line. If you are setting the variable to the result of a complicated expression, it might make more sense to break a line between subexpressions .

Incorrect:

strSQL="SELECTtblAssemblyDetail.*,tblInventory.Description,"&_ "tblInventory.SerialsFROMtblInventoryRIGHTJOIN"&_ "tblAssemblyDetailONtblInventory.ItemID="&_ "tblAssemblyDetail.ItemIDWHERE((tblAssemblyDetail.Assembly)="&_ "'PackageA')ORDERBYtblAssemblyDetail.LineNumber;"

Also incorrect:

strSQL="SELECTtblAssemblyDetail.*,tblInventory.Description,"&_ "tblInventory.SerialsFROMtblInventoryRIGHTJOIN"&_ "tblAssemblyDetailONtblInventory.ItemID=tblAssemblyDetail."&_ "ItemIDWHERE((tblAssemblyDetail.Assembly)='PackageA')"&_ "ORDERBYtblAssemblyDetail.LineNumber;"

Correct:

strSQL="SELECTtblAssemblyDetail.*,tblInventory.Description,"&_ "tblInventory.SerialsFROMtblInventoryRIGHTJOIN"&_ "tblAssemblyDetailONtblInventory.ItemID="&_ "tblAssemblyDetail.ItemIDWHERE((tblAssemblyDetail."&_ "Assembly)='PackageA')ORDERBY"&_ "tblAssemblyDetail.LineNumber;"

8.3.2 When you split a long procedure heading, indent all continuation lines two tab stops (generally six characters). Alternatively, you can indent all lines after the first line to the start of the first parameter. Either style is acceptable. Choose the one you're most comfortable with and use it consistently.

Incorrect:

PublicSub CreateNewNote(lngAccountNumber AsLong ,_ strDate AsString ,strStartTime AsString ,strNote AsString ,_ lngContactNumber AsLong ,lngRepNumber AsLong ) OnErrorGoTo PROC_ERR

Also incorrect:

PublicSub CreateNewNote(lngAccountNumber AsLong ,_ strDate AsString ,strStartTime AsString ,strNote AsString ,_ lngContactNumber AsLong ,lngRepNumber AsLong ) OnErrorGoTo PROC_ERR

Correct:

PublicSub CreateNewNote(lngAccountNumber AsLong ,_ strDate AsString ,strStartTime AsString ,strNote AsString, _ lngContactNumber AsLong ,lngRepNumber AsLong ) OnErrorGoTo PROC_ERR

Alternate:

PublicSub CreateNewNote(lngAccountNumber AsLong ,_ strDate AsString ,strStartTime AsString ,_ strNote AsString ,lngContactNumber_ AsLong ,lngRepNumber AsLong ) OnErrorGoTo PROC_ERR

8.3.3 When you call a procedure, indent all continuation lines to the start of the first argument. The name of the procedure being called will clearly stand out from the arguments being passed to it. When you split a statement that calls a very long procedure name that accepts many arguments, it might be impractical to indent to the first argument. In this case, indent two tab stops.

Incorrect:

Call CreateNewNote(rstAccount![AccountNumber],VBA.Date,VBA.Time,_ strNote,lngContactNumber,rstAccount![RepNumber])

Also incorrect:

Call CreateNewNote(rstAccount![AccountNumber],VBA.Date,VBA.Time,_ strNote,lngContactNumber,rstAccount![RepNumber])

Correct:

Call CreateNewNote(rstAccount![AccountNumber],VBA.Date,VBA.Time,_ strNote,lngContactNumber,rstAccount![RepNumber])

8.3.4 When you set a variable or property equal to the result of an expression, break the statement just after the equal sign to ensure that as much of the expression as possible remains on one line. If the statement consists of many expressions, it might be better to break the statement between expressions, as discussed earlier.

Incorrect:

grdDetail.Columns(c_grdCanBuild).Text=lngQuantityLocation/_ Val(grdDetail.Columns(c_grdNeeded).Text)

Correct:

grdDetail.Columns(c_grdCanBuild).Text=_ lngQuantityLocation/Val(grdDetail.Columns(c_grdNeeded).Text)

8.3.5 When you split a long If statement, indent continuation lines two tab stops (six characters). Statements within the If construct are indented a single tab stop, so using two tab stops for the continuation lines offers visual clarity. Notice the use of a blank line after the If statement to separate the subordinate lines from the actual block of statements within the If End If construct.

Incorrect:

If (lngCanBuild<lngMaxCanBuild) Or _ (lngMaxCanBuild<0) Then lngMaxCanBuild=lngCanBuild EndIf

Also incorrect:

If (lngCanBuild<lngMaxCanBuild) Or _ (lngMaxCanBuild<0) Then lngMaxCanBuild=lngCanBuild EndIf

Correct:

If (lngCanBuild<lngMaxCanBuild) Or _ (lngMaxCanBuild<0) Then lngMaxCanBuild=lngCanBuild EndIf

Splitting long statements to fit on multiple lines is an easy way to make your code easier to read and maintain. When a long statement appears on multiple lines, you can see the entire statement without any scrolling. When a statement is left on a single line, like a long freight train whose locomotive and caboose you can't see at the same time, making changes to the line is overly difficult; you can't see the full statement, nor can you see the previous or the next statement.

8.4 Use indentation to show organizational structure.

If you want to make a complicated procedure more difficult to comprehend, don't indent anything. Conversely, to make a complex procedure easier to understand, indent appropriately. Indentation gives the reader a visual representation of the organization of statements that perform unified tasks , much like a flowchart is a visual representation of a series of events. Indentation of code closely mimics the behavior of indentation in an outline or a table of contents because elements are placed into an ordered hierarchy.

While you might reject certain programming standards for reasons such as the number of developers working on a project and the size of the program, correct indentation is always required. Visual Basic makes it easy to indent code using predefined tab stops. These tab stops show the organizational structure of a procedure by visually outlining subordinate statements and the nesting of loops and decision blocks. Although Visual Basic lets you set your tab stops to any number of characters, the accepted standard is three characters. If you use fewer than three characters, visual definition is reduced. Using more than three wastes too much space, especially in complicated code with many nested constructs.

You define the number of characters in a tab stop by using Visual Basic's Options dialog box. Choose Options from the Tools menu, and then specify the number of characters in the Tab Width field, as shown in Figure 8-1.

Figure 8-1. Use the Options dialog box to set the number of characters in a tab stop.

Generally, the outer level of code (that is, the leftmost code, which is the highest in the hierarchy) should be indented one tab stop from the left side of the code window. This allows the heading of the procedure and its termination statement (such as End Sub or End Function ) to clearly define the skeleton of the procedure, as shown here:

Function NoZeroLengthString( ByVal strText AsVariant ) AsVariant '*Purpose:Convertazero-lengthstringtoaNull. '*Accepts:strText-thetextthatmayormaynotbeazero- '*lengthstring. '*Returns:IfstrTextisazero-lengthstring,returnsNull. '*Otherwise,returnsthestartingstring. OnErrorGoTo PROC_ERR '*Checktoseewhetherthereceivedstringisa '*zero-lengthstring. If strText="" Then NoZeroLengthString= Null Else NoZeroLengthString=strText EndIf PROC_EXIT: ExitFunction PROC_ERR: MsgBox"mdlUtilitiesNoZeroLengthString"&vbCrLf&_ Err.Number&vbCrLf&Err.Description GoTo PROC_EXIT EndFunction

Each successive nested construct should be indented a single tab stop beyond the preceding level. An end-of-construct statement such as End If, Loop, or Next should be at the same indentation level as its beginning-of-construct statement, as shown here:

For intCounter=0 To Forms.Count-1 '*CheckthenameofeachformintheFormscollection. If Forms(intCounter).Name=strFormName Then m_blnIsFormLoaded= True GoTo PROC_EXIT EndIf Next intCounter

Below is a list of situations in which you should indent. This list is not all-inclusive, but it catalogs most of the situations in which indentation is necessary. As important as it is to know when and how to indent, it's equally important to understand why you should indent at these locations so that you can make judgment calls in other circumstances. I'll illustrate these reasons in the following Practical Applications. You should indent

Practical Applications

8.4.1 Indent after an If statement when an End If is used. The body of an If End If block is perhaps the most common place for indentation. Since the body statements are subordinate to the If statement itself, they are indented. All end-of-construct statements such as End If appear at the same level of indentation as the statement that begins the construct.

Incorrect:

If Left$(txtLocation.Text,4)="Fax:" Then MAPIMess.RecipAddress="FAX:"&txtRecipient.Text&"@"&_ strFaxNumber EndIf

Correct:

If Left$(txtLocation.Text,4)="Fax:" Then MAPIMess.RecipAddress="FAX:"&txtRecipient.Text&"@"&_ strFaxNumber EndIf

8.4.2 Indent after an Else statement. Since the Else statement has the same level of importance as the If and End If statements, it has the same level in the hierarchy and therefore the same level of indentation. The following code shows the statements that are subordinate to the Else statement, just as the statements following the If statement are subordinate to the If statement itself.

Incorrect:

If InStr(txtLocation.Text,"@") Then MAPIMess.RecipAddress="SMTP:"&txtLocation.Text Else MAPIMess.RecipAddress="MS:"&strServer&"/"&txtLocation.Text EndIf

Correct:

If InStr(txtLocation.Text,"@") Then MAPIMess.RecipAddress="SMTP:"&txtLocation.Text Else MAPIMess.RecipAddress="MS:"&strServer&"/"&txtLocation.Text EndIf

8.4.3 Indent after a Select Case statement. A Select Case construct has a beginning-of-construct statement and an end-of-construct statement. When two statements are used to begin and end a construct, the code block between the two statements should be indented.

Incorrect:

SelectCase objTool.Name CaseIs ="Save" CaseIs ="Exit" EndSelect

Correct:

SelectCase objTool.Name CaseIs ="Save" CaseIs ="Exit" EndSelect

8.4.4 Indent after a Case statement. It's not enough to indent the Case statements themselves in a Select Case construct. While the Case statements are subordinate to the Select Case statement, the block of statements that form the body of a Case statement are subordinate to the Case statement itself, and therefore should be indented.

Incorrect:

SelectCase objTool.Name CaseIs ="Save" Call SaveDocument CaseIs ="Exit" UnloadMe EndSelect

Correct:

SelectCase objTool.Name CaseIs ="Save" Call SaveDocument CaseIs ="Exit" UnloadMe EndSelect

8.4.5 Indent after a Do statement. The Do Loop structure has both a beginning-of-construct and an end-of-construct statement, so its body of statements must be indented. Correctly indenting the body of statements within a loop construct clearly shows the reader the flow of execution produced by the loop.

Incorrect:

Do WhileNot rstSales.EOF lstCategories.AddItemrstSales![Category] rstSales.MoveNext Loop

Correct:

DoWhileNot rstSales.EOF lstCategories.AddItemrstSales![Category] rstSales.MoveNext Loop

8.4.6 Indent successive lines of a statement that has been split with the line continuation character. No single rule applies to the indentation of continuation lines. The amount of indentation is determined by the statement itself, as I described earlier in this chapter.

Incorrect:

strSQL="SELECTtblAssemblyDetail.*,tblInventory.Description,"&_ "tblInventory.SerialsFROMtblInventoryRIGHTJOIN"&_ "tblAssemblyDetailONtblInventory.ItemID="&_ "tblAssemblyDetail.ItemIDWHERE((tblAssemblyDetail.Assembly)="&_ "'PackageA')ORDERBYtblAssemblyDetail.LineNumber;"

Also incorrect:

strSQL="SELECTtblAssemblyDetail.*,tblInventory.Description,"&_ "tblInventory.SerialsFROMtblInventoryRIGHTJOIN"&_ "tblAssemblyDetailONtblInventory.ItemID=tblAssemblyDetail."&_ "ItemIDWHERE((tblAssemblyDetail.Assembly)='PackageA')"&_ "ORDERBYtblAssemblyDetail.LineNumber;"

Correct:

strSQL="SELECTtblAssemblyDetail.*,tblInventory.Description,"&_ "tblInventory.SerialsFROMtblInventoryRIGHTJOIN"&_ "tblAssemblyDetailONtblInventory.ItemID="&_ "tblAssemblyDetail.ItemIDWHERE((tblAssemblyDetail."&_ "Assembly)='PackageA')ORDERBY"&_ "tblAssemblyDetail.LineNumber;"

8.4.7 Indent after a With statement. With statements reduce the amount of code necessary to access multiple members of an object. Using With blocks to access many members of an object improves readability and can increase the speed of the application. All statements between With and End With are indented a single tab stop from the level of indentation of the beginning With statement.

Incorrect:

With lstSortOrder .Clear .AddItem"Ascending" .AddItem"Descending" EndWith

Correct:

With lstSortOrder .Clear .AddItem"Ascending" .AddItem"Descending" EndWith

8.4.8 Indent after calling an Edit or AddNew method of a Recordset. The Update or CancelUpdate method should appear at the same level of indentation as the Edit statement. It isn't always apparent, but calls to the AddNew and Edit methods of a Recordset act as beginning-of-construct statements because they must be followed with an Update or CancelUpdate method call. Calls to the Update and CancelUpdate methods act as end-of-construct statements and appear at the same level of indentation as the AddNew and Edit method calls. All code in between is indented a single tab stop.

Incorrect:

rstAccounts.Edit rstAccounts![Name]=strName rstAccounts![Address]=strAddress rstAccounts.Update

Correct:

rstAccounts.Edit rstAccounts![Name]=strName rstAccounts![Address]=strAddress rstAccounts.Update

8.4.9 Indent after a BeginTrans method call. A call to the BeginTrans method (beginning-of-construct) is always followed by a call to the CommitTrans method or the RollBack method (end-of-construct). The call to BeginTrans should have the same level of indentation as the call to the CommitTrans or RollBack method, with everything in between indented one tab stop.

Incorrect:

Workspaces(0).BeginTrans DoWhileNot rstAccounts.EOF intCounter=intCounter+1 rstAccounts.Edit rstAccounts![RepNumber]=lngToRepNumber rstAccounts.Update rstAccounts.MoveNext Loop Workspaces(0).CommitTrans

Correct:

Workspaces(0).BeginTrans '*UpdateeachrecordintheRecordset. DoWhileNot rstAccounts.EOF intCounter=intCounter+1 rstAccounts.Edit rstAccounts![RepNumber]=lngToRepNumber rstAccounts.Update rstAccounts.MoveNext Loop Workspaces(0).CommitTrans

8.4.10 Indent code that is subordinate to a line label. Chapter 11, "Controlling Code Flow," discusses keeping GoTo statements to a minimum, but if you add error trapping and single exit points to all of your procedures, as you should, you'll always have at least two code labels in every procedure. Visual Basic doesn't allow you to indent labels, and this works to your advantage. If you indent all of your highest-level code one tab stop from the left side of the code window, the labels within the procedure will stand out because they will be flush left with the code window, as shown in the code below. Treat code under each label as top-level code by indenting a single tab stop, and indent subordinate statements according to the other Practical Applications in this section.

FunctionNoZeroLengthString( ByVal strText AsVariant ) AsVariant '*Purpose:Convertazero-lengthstringtoaNull. '*Accepts:strText-thetextthatmayormaynotbeazero- '*lengthstring. '*Returns:IfstrTextisazero-lengthstring,returnsNull. '*Otherwise,returnsthestartingstring. OnErrorGoTo PROC_ERR '*Checktoseewhetherthereceivedstringisa '*zero-lengthstring. If strText="" Then NoZeroLengthString= Null Else NoZeroLengthString=strText EndIf PROC_EXIT: ExitFunction PROC_ERR: MsgBox"mdlUtilitiesNoZeroLengthString"&vbCrLf&_ Err.Number&vbCrLf&Err.Description GoTo PROC_EXIT EndFunction

NOTE


The indentation of code comments has its own set of directives and is discussed in Chapter 9.

8.5 Indent code within the Declarations section of a module to show subordination.

Code written in the Declarations section of a module is treated differently from procedure code. Code within a Declarations section is considered to be at the same level in the hierarchy as procedure definitions. Consequently, such code is not indented one tab stop from the left side of the code window; it is flush left. However, the indenting of code to show subordination is still applicable . The most notable circumstances are the declarations of enumerations and user-defined data types. The statements that make up the body of these declarations should be indented a single tab stop from the beginning and ending statements, as shown in the following Practical Applications.

Practical Applications

8.5.1 Indent the bodies of all user-defined data type declarations. User-defined data type declarations are similar in structure to procedures. They appear in the Declarations section of a module, so they do not have any initial indentation. However, just as the body of a procedure is indented a single tab stop, so is the body of a user-defined data type declaration.

Incorrect:

PrivateType RECT Left AsLong Top AsLong Right AsLong Bottom AsLong EndType

Correct:

PrivateType RECT Left AsLong Top AsLong Right AsLong Bottom AsLong EndType

8.5.2 Indent the bodies of all enumeration declarations. Enumeration declarations are similar to user-defined data type declarations and are formatted in the same way. Since enumeration declarations appear in the Declarations section of a module, they do not have initial indentation. However, the body of an enumeration declaration is indented a single tab stop.

Incorrect:

OptionExplicit PublicEnum tpsAVI tpsFileCopy=0 tpsFileDelete=1 tpsFileDeleteToRecycle=2 tpsFileMove=3 EndEnum

Correct:

OptionExplicit PublicEnum tpsAVI tpsFileCopy=0 tpsFileDelete=1 tpsFileDeleteToRecycle=2 tpsFileMove=3 EndEnum

8.6 Use white space to group related statements.

White space is not evil. In earlier days of programming, you had to make your code physically as small as possible. This generally meant eliminating all but the absolutely necessary white space. Those days are gone, and strategically placed blank lines make code much easier to follow and should be used appropriately.

Constructing procedures is not unlike writing a document. When you write a document, you group related sentences into paragraphs. If a sentence doesn't make sense within a paragraph, it's moved to a different paragraph or placed in a bulleted or numbered list. Code within a procedure is similar in that related statements generally appear in groups. Frequently, a code statement stands somewhat autonomously between two groups or other autonomous statements. Using blank lines to separate groups of related statements and autonomous statements makes the code easier to read and to analyze.

Many programmers use blank lines sporadically, placing them in somewhat random locations. Blank lines should clearly exhibit their purpose by appearing in logical locations.

In general, you should insert a blank line

You should insert two blank lines between procedures.

I'll discuss each of these situations in detail in the following Practical Applications.

Practical Applications

8.6.1 Insert a blank line before and after each If Then construct (specifically, before the comment in front of the If statement). The If Then construct signifies that a decision is being made that affects the flow of execution. This decision is very much an independent thought even though it might be related to the processes surrounding it. When you debug code, you often scan the code for If statements to determine where the flow of execution changed unexpectedly. If you don't use blank lines before an If statement, it's not as apparent to the reader that a decision is being made.

Incorrect:

PrivateSub cmdReplaceDoubleQuotes_Click() Dim intLocation AsInteger '*Iftheuserhasn'tenteredanytext,getout. If Len(Text1.Text)=0 ThenGoTo PROC_EXIT '*Determinewhetheradouble-quote(")appearsinthestring. intLocation=InStr(1,Text1.Text,Chr$(34)) '*IfintLocation>0thereisadouble-quote.Replacewith '*twosinglequotes. If intLocation>0 Then Text1.Text=Left$(Text1.Text,intLocation-1)&"''"&_ Mid$(Text1.Text,intLocation+1) EndIf PROC_EXIT: ExitSub EndSub

Correct:

PrivateSub cmdReplaceDoubleQuotes_Click() Dim intLocation AsInteger Const c_DoubleQuote="""" '*Iftheuserhasn'tenteredanytext,getout. If Len(Text1.Text)=0 ThenGoTo PROC_EXIT '*Determinewhetheradouble-quote(")appearsinthestring. intLocation=InStr(1,Text1.Text,c_DoubleQuote) '*IfintLocation>0thereisadouble-quote.Replacewith '*twosinglequotes. If intLocation>0 Then Text1.Text=Left$(Text1.Text,intLocation-1)&"''"&_ Mid$(Text1.Text,intLocation+1) EndIf PROC_EXIT: ExitSub EndSub

8.6.2 Insert a blank line before and after each Select Case construct. Like the If statement discussed above, Select Case statements adjust the flow of execution. Blank lines help isolate the decision and make the code more readable. To further increase clarity, place a blank line before every Case statement except the first one.

Incorrect:

PrivateSub cmd_Click(Index AsInteger ) Const c_cmdOK=0 Const c_cmdCancel=1 '*Determinewhichbuttonwasclicked. SelectCase Index CaseIs =c_cmdOK Call SaveDocument UnloadMe CaseIs =c_cmdCancel UnloadMe EndSelect PROC_EXIT: ExitSub EndSub

Correct:

PrivateSub cmd_Click(Index AsInteger ) Const c_cmdOK=0 Const c_cmdCancel=1 '*Determinewhichbuttonwasclicked. SelectCase Index CaseIs =c_cmdOK Call SaveDocument UnloadMe CaseIs =c_cmdCancel UnloadMe EndSelect PROC_EXIT: ExitSub EndSub

8.6.3 Insert a blank line before and after each loop. Loops can be difficult to understand. When you debug a procedure that has one or more loops, it's imperative that the loops be easy to spot in code. Placing a blank line in front of and after each looping construct clearly distinguishes the loop from surrounding code.

Incorrect:

PrivateSub FillSecurityGroupList() Dim rstSecurity As Recordset Dim strSQL As String lstSecurityGroup.Clear Set rstSecurity=dbContacts.OpenRecordset("tblSecurityGroups",_ dbOpenForwardOnly) '*AddallthesecuritygroupsintheRecordsettothelist. DoWhileNot rstSecurity.EOF lstSecurityGroup.AddItemrstSecurity![SecurityGroup] rstSecurity.MoveNext Loop PROC_EXIT: ExitSub EndSub

Correct:

PrivateSub FillSecurityGroupList() Dim rstSecurity As Recordset Dim strSQL As String lstSecurityGroup.Clear Set rstSecurity=dbContacts.OpenRecordset("tblSecurityGroups",_ dbOpenForwardOnly) '*AddallthesecuritygroupsintheRecordsettothelist. DoWhileNot rstSecurity.EOF lstSecurityGroup.AddItemrstSecurity![SecurityGroup] rstSecurity.MoveNext Loop PROC_EXIT: ExitSub EndSub

8.6.4 Insert a blank line after declaring a block of variables. If many variables are declared in a procedure or a module, consider using a blank line to separate each group of variables that are declared as the same data type.

Incorrect:

PrivateSub SampleProcedure() Dim strLabelFile AsString Dim strSQL AsString Dim strOutput AsString Dim strHeader AsString Dim intFieldIndex AsInteger Dim intFileNumber AsInteger Dim lngAccountsDumped AsLong Dim rstRepInfo As Recordset Dim rstReps As Recordset Dim rstContacts As Recordset Dim rstAccounts As Recordset PROC_EXIT: ExitSub EndSub

Correct:

PrivateSub SampleProcedure() Dim strLabelFile AsString Dim strSQL AsString Dim strOutput AsString Dim strHeader AsString Dim intFieldIndex AsInteger Dim intFileNumber AsInteger Dim lngAccountsDumped AsLong Dim rstRepInfo As Recordset Dim rstReps As Recordset Dim rstContacts As Recordset Dim rstAccounts As Recordset PROC_EXIT: ExitSub EndSub

Also correct:

PrivateSub SampleProcedure() Dim strLabelFile AsString Dim strSQL AsString Dim strOutput AsString Dim strHeader AsString Dim intFieldIndex AsInteger Dim intFileNumber AsInteger Dim lngAccountsDumped AsLong Dim rstRepInfo As Recordset Dim rstReps As Recordset Dim rstContacts As Recordset Dim rstAccounts As Recordset PROC_EXIT: ExitSub EndSub

8.6.5 Insert a blank line between groups of statements that perform unified tasks. Good code should consist of logically sequenced processes or groups of related statements. These sections should be immediately visible to the reader.

Incorrect:

PublicFunction DeleteToRecycleBin( ByVal strFileName AsString )_ AsBoolean '*Purpose:Deleteafile,andsendittotheRecycleBin. OnErrorGoTo PROC_ERR Dim FileOperation As SHFILEOPSTRUCT Dim lngResult AsLong '*Setuptheparametersfordeletingthefile. With FileOperation .wFunc=FO_DELETE .pFrom=strFileName .fFlags=FOF_ALLOWUNDO+FOF_CREATEPROGRESSDLG EndWith '*Deletethefile. lngResult=SHFileOperation(FileOperation) '*ReturnTrueifsuccessful;otherwisereturnFalse. If lngResult<>0 Then DeleteToRecycleBin= False Else DeleteToRecycleBin= True EndIf PROC_EXIT: ExitFunction PROC_ERR: MsgBox"Error:"&Err.Number&vbCrLf&Err.Description GoTo PROC_EXIT EndFunction

Correct:

PublicFunction DeleteToRecycleBin( ByVal strFileName AsString )_ AsBoolean '*Purpose:Deleteafile,andsendittotheRecycleBin. OnErrorGoTo PROC_ERR Dim FileOperation As SHFILEOPSTRUCT Dim lngResult As Long '*Setuptheparametersfordeletingthefile. With FileOperation .wFunc=FO_DELETE .pFrom=strFileName .fFlags=FOF_ALLOWUNDO+FOF_CREATEPROGRESSDLG EndWith '*Deletethefile. lngResult=SHFileOperation(FileOperation) '*ReturnTrueifsuccessful;otherwisereturnFalse. If lngResult<>0 Then DeleteToRecycleBin= False Else DeleteToRecycleBin= True EndIf PROC_EXIT: ExitFunction PROC_ERR: MsgBox"Error:"&Err.Number&vbCrLf&Err.Description GoTo PROC_EXIT EndFunction

8.6.6 Insert two blank lines between procedures. If you still have Visual Basic configured to display only a single procedure at a time in the code window, you should consider changing your settings so that you can view multiple procedures in the code window. (See Figure 8-2.) This will increase your productivity. To enable this feature, choose Options from the Tools menu and select Default To Full Module View.

Figure 8-2. Viewing multiple procedures in the code window makes it easier to work with longer modules.

Blank lines are sometimes considered a minor detail because their appearance does not directly affect the behavior of compiled code. When Visual Basic compiles a project, all unnecessary white space is stripped by the compiler, so Visual Basic couldn't care less whether you use white space in your procedures. But white space isn't for Visual Basic's benefit ”it's for your benefit and for the benefit of those who have to review and maintain your code.

Категории