Directives

[Previous] [Next]

3.1 Give procedures and modules descriptive names .

One of the easiest ways to make your code more understandable is to give your procedures descriptive names. Function names such as DoIt , GetIt , and PrintIt aren't nearly as understandable as CalculateSalesTax , RetrieveUserID , and PrintSpreadsheet .

The first thing to remember when naming a procedure is that DOS is gone (not dead and gone, but for the most part gone). Many programmers from the DOS days are still trying to break old habits that hinder rather than benefit development. For instance, some languages commonly used in the past limited the number of characters you could use to define a procedure name . We still see programmers choosing procedure names such as CALCTAX , GETIT , GETREC , and DELREC . If you're doing this, it's time to make a change. Code consisting of abbreviated procedure names is difficult (at best) to understand and maintain, and there's simply no valid reason to do this anymore. Certainly, procedure names such as CalculateSalesTax , RetrieveEmployee , and DeleteEmployee are much more self-explanatory than the older names.

Correctly naming procedures can make all the difference in the world when it comes to debugging and maintaining a project. Take naming procedures seriously; don't reduce understandability for the sake of reduced typing.

Incorrect:

  Private Function  InvoicePost()  As Boolean Private Function  PrintIt()  As Boolean Public Sub  SaveItem()  Public Sub  DeleteRecord() 

Correct:

  Private Function  PostInvoice()  As Boolean Private Function  PrintSpreadsheet()  As Boolean Public Sub  SavePicture()  Public Sub  DeleteContact() 

Practical Applications

3.1.1 Use mixed-case letters when defining procedure names. In the past, some languages limited you to uppercase procedure names. This limitation does not exist in Visual Basic, and you should define all procedure names in mixed-case letters. IT'S HARDER TO READ SENTENCES IN ALL UPPERCASE THAN IT IS TO READ THEM IN MIXED CASE, and the same holds true for procedure names. By the way, all-lowercase names are almost (but not quite) as difficult to read as all-uppercase names. For instance, compare EmployeeAge to EMPLOYEEAGE and employeeage . Also, it's OK to use an underscore in a variable name to simulate a space (for example, Employee_Age ), but if you do use underscores, use them consistently. Note that you cannot use spaces in variable names.

Incorrect:

  Private Function  POSTINVOICE()  As Boolean Public Sub  savepicture() 

Correct:

  Private Function  POSTINVOICE()  As Boolean Public Sub  savepicture() 

3.1.2 Don't abbreviate when defining procedure names. If you ask two people how to abbreviate a given word, you'll probably end up with two different answers. If you believe that specific items within your application should be abbreviated, document those situations and make sure everyone uses those abbreviations all the time. Definitely don't abbreviate certain words in some procedures but not in others.

Incorrect:

  Private Function  SaveEmp()  As Boolean Public Sub  DelContact()  Public Sub  CalcTax() 

Correct:

  Private Function  SaveEmployee()  As Boolean Public Sub  DeleteContact()  Public Sub  CalculateSalesTax() 

3.2 Give every procedure a single exit point.

Debugging code in an event-driven application can be quite challenging ”procedures are called from within procedures (fan-out), and code execution often bounces around like a billiard ball on a bumper pool table. The simple statements Exit Function and Exit Sub can compound this complexity. Every procedure has a single entry point, and this makes sense. You wouldn't want different calling procedures to be able to enter a given procedure in different code locations ”that would be a nightmare. Although it's not as obvious, every procedure should also have a single exit point.

When you can be assured that control always exits a procedure in the same way, at the same line of code, debugging becomes considerably easier. If you allow a procedure to be exited from anywhere within the code, how can you be sure that cleanup code is always executed before the procedure is exited? Sure, you might be able to remember exactly what is occurring within a procedure, but what about when another developer edits the procedure to add just one more Select Case clause and uses an Exit Sub to exit the procedure? Any code that should be executed to clean up the procedure sits unused, and problems can easily result.

The solution is to create a label called PROC_EXIT in every procedure. Underneath this label, place all necessary cleanup code and an appropriate Exit statement ( Exit Sub , Exit Function , or Exit Property ). Whenever you need to exit the procedure, include a GoTo PROC_EXIT statement rather than directly invoking the Exit command.

Creating single exit points makes procedures more like black boxes. Execution comes in one door and leaves through another door, resulting in fewer errors and a less difficult debugging process. The act of creating single exit points is amazingly trivial, but the rewards are tremendous. It's not often that the rewards will be so much greater than the effort required, so make sure to take advantage of it!

Incorrect:

  Public Function  IsFormLoaded(strFormName  As String  )  As Boolean   '* Purpose  :  Determine whether a specified form is loaded.    '* Accepts  :  strFormName - the name of a form.    '* Returns  :  True if the form is loaded, False if not.   On Error GoTo  PROC_ERR  Dim  intCounter  As Integer   '* Since referring to a form loads the form, the proper    '* way to determine whether the form is loaded is to loop    '* through the Forms collection, which contains only    '* loaded forms.   For  intCounter = 0  To  Forms.Count - 1  '* If the current form is the specified form,       '* return True and get out.   If  Forms(intCounter).Name = strFormName  Then  IsFormLoaded =  True   Exit Function       End If   Next  intCounter  '* Form was not found; return False.  IsFormLoaded =  False  PROC_ERR:    MsgBox "basMain  IsFormLoaded" & vbCrLf & "Error: " & _           Err.Number & vbCrLf & Err.Description  Resume Next End Function  

Correct:

  Public Function  IsFormLoaded(strFormName  As String  )  As Boolean   '* Purpose  :  Determine whether a specified form is loaded.    '* Accepts  :  strFormName - the name of a form.    '* Returns  :  True if the form is loaded, False if not.   On Error GoTo  PROC_ERR  Dim  intCounter  As Integer   '* Since referring to a form loads the form, the proper    '* way to determine whether the form is loaded is to loop    '* through the Forms collection, which contains only    '* loaded forms.   For  intCounter = 0  To  Forms.Count - 1  '* If the current form is the specified form,       '* return True and get out.   If  Forms(intCounter).Name = strFormName  Then  IsFormLoaded =  True   GoTo  PROC_EXIT  End If   Next  intCounter  '* Form was not found; return False.  IsFormLoaded =  False  PROC_EXIT:  Exit Function  PROC_ERR:    MsgBox "basMain  IsFormLoaded" & vbCrLf & "Error: " & _           Err.Number & vbCrLf & Err.Description  Resume Next End Function  

Practical Application

3.2.1 Create robust exit code. Don't get complacent when adding cleanup code to a procedure. If the proper exit process depends on the states of certain variables, check the values of those variables and respond accordingly in your cleanup code.

Incorrect:

  Private Sub  cboTerms_Requery()  '* Purpose  :  Fill the Terms custom combo box.   On Error GoTo  PROC_ERR  Dim  strSQL  As String   Dim  rstTerms  As  Recordset        cboTerms.Clear        strSQL = "SELECT [InvoiceTerm] FROM tblInvoiceTerms " & _             "ORDER BY [InvoiceTerm];"  Set  rstTerms = dbSales.OpenRecordset(strSQL, dbOpenForwardOnly)  '*AddalloftheinvoicetermstotheTermscombobox.   Do While Not  rstTerms.EOF       cboTerms.AddItem rstTerms![InvoiceTerm]       rstTerms.MoveNext  Loop  PROC_EXIT:    rstTerms.Close  Set  rstTerms =  Nothing   Exit Sub  PROC_ERR:  Call  ShowError(Me.Name, "cboTerms_Requery", Err.Number, _                   Err.Description)  GoTo  PROC_EXIT  End Sub  

Correct:

  Private Sub  cboTerms_Requery()  '* Purpose  :  Fill the Terms custom combo box.   On Error GoTo  PROC_ERR  Dim  strSQL  As String   Dim  rstTerms  As  Recordset        cboTerms.Clear        strSQL = "SELECT [InvoiceTerm] FROM tblInvoiceTerms " & _             "ORDER BY [InvoiceTerm];"  Set  rstTerms = dbSales.OpenRecordset(strSQL, dbOpenForwardOnly)  '*AddalloftheinvoicetermstotheTermscombobox.   Do While Not  rstTerms.EOF       cboTerms.AddItem rstTerms![InvoiceTerm]       rstTerms.MoveNext  Loop  PROC_EXIT:  '* If a database error occurred, rstTerms won't be set to    '* a valid recordset.   If Not  (rstTerms Is  Nothing  )  Then  rstTerms.Close  Set  rstTerms = Nothing  End If   Exit Sub  PROC_ERR:  Call  ShowError(Me.Name, "cboTerms_Requery", Err.Number, _                   Err.Description)  GoTo  PROC_EXIT  End Sub  

3.3 Give every procedure a clearly defined scope.

Scope refers to the visibility of a variable or procedure within a project. Procedures can be defined as having either module-level, global, or friend scope. When a procedure is declared with the Private keyword, it has module-level scope and can be called only from procedures within the same module. A procedure declared with the Public keyword has global scope and can be called from any module within the project. In addition, public procedures of public class modules are available to external programs. Declaring a procedure (in a public class module) with the Friend keyword makes the procedure public to all modules within the project, but it does not make the procedure public to external programs.

When creating a procedure, always explicitly define its scope. Although it's possible to define a procedure without using Public , Private , or Friend , you should avoid doing this. Take a look at the following procedure definition in a standard module. Is this procedure private to the module, or is it public to all modules within the project?

  Sub  DisplayConfirmationMessage()   End Sub  

The previous procedure is actually a public procedure because Visual Basic uses Public scope as the default. If your intent really is to create a public procedure, make that clear to the reader by explicitly declaring the procedure with the Public keyword like this:

  Public Sub  DisplayConfirmationMessage()   End Sub  

Often, procedures declared without Public , Private , or Friend are really intended to be module-level ( Private ) procedures. However, without being specifically declared as Private , they are inadvertently created as Public procedures. Reduce the amount of effort required of the reader by giving each and every procedure a clearly defined scope. Also make sure that you're giving a procedure the scope that makes the most sense. If a procedure is called by only one other procedure within the same module, make it Private . If the procedure is called from procedures in multiple modules, explicitly declare the procedure as Public .

Practical Application

3.3.1 Every procedure definition should begin with Public , Private , or Friend . If you have existing procedures without one of these keywords, go through your project to determine the proper scope for each procedure and modify their declarations accordingly.

Incorrect:

  Sub  CalculatePOTotals()   End Sub  

Correct:

  Public Sub  CalculatePOTotals()   End Sub  

3.4 Use parameters to pass data between procedures.

Although they're not quite as problematic as global variables, module-level variables should be avoided as much as possible as well. In general, the smaller the scope of a variable the better. One way to reduce module-level and global variables is to pass data between procedures as parameters, rather than having the procedures share global or module-level variables.

Practical Applications

3.4.1 Specify a data type for each and every parameter. This can't be stressed enough. When creating procedures with parameters, always explicitly declare each parameter as a specific data type. For more information on data types, see Chapter 6, "Variables." When you omit the As < type > portion of a parameter declaration, the parameter is created as a Variant. I'll discuss the problems inherent with the Variant data type in Chapter 6. If you want to create a Variant parameter, do so explicitly using As Variant.

Incorrect:

  Private Sub  CreateStockRecord(ItemID, Repair, Quantity) 

Correct:

  Private Sub  CreateStockRecord(strItemID  As String  , blnRepair _  As Boolean  , sngQuantity  As Single  ) 

3.4.2 Pass data ByVal or ByRef , as appropriate. Visual Basic passes data to procedure parameters by reference unless told to do otherwise . When a variable is passed by reference ( ByRef ) to a parameter of a procedure, the procedure receives a pointer to the original variable. Any subsequent changes made to the parameter are made to the original variable. On the other hand, when a variable is passed by value ( ByVal ) to a parameter, the procedure receives a copy of the variable. (String variables are an exception to this rule.) Any changes made to the parameter are made only to the copy, and the original variable remains unchanged. One possible standard would be to preface each parameter with ByRef or ByVal . That way you're explicitly declaring which process to use, and the code is more self-documenting . However, the discipline required to preface every parameter with ByRef or ByVal is great.

Another, and definitely easier, solution is to preface a parameter with ByRef only when that parameter will be used to change the value of the original variable (also called the argument). In that case, this practical application would read, "When a parameter is used to change the value of its argument, explicitly declare the parameter as ByRef ." Then you can add a comment to the Accepts section of the procedure comment header stating that the parameter is being used as an output parameter ”that is, to change the value of its argument. This approach gives you the best of both worlds .

Incorrect:

  Public Sub  ParseName(strFullName  As String  , strFirstName  As String  , _       strMiddleInitial  As String  , strLastName  As String  ) 

Correct:

  Public Sub  ParseName(strFullName  As String  , _  ByRef  strFirstName  As String  , _  ByRef  strMiddleInitial  As String  , _  ByRef  strLastName  As String  )  '* Purpose  :  Parse a full name such as "John Smith" or "John D.    '*             Smith" into the proper separate components.    '* Accepts  :  strFullName - the name to parse.    '*             strFirstName, strMiddleInitial, and strLastName are    '*             used as output parameters. This procedure populates    '*             the corresponding arguments with the proper values.  

3.4.3 Always validate parameters ”never assume that you have good data. A common mistake among programmers is to write procedures that assume they have good data. This assumption isn't such a problem during initial development when you're writing the calling procedures. Chances are good then that you know what the allowable values for a parameter are and will provide them accordingly. However, the following situations can be troublesome if you don't validate parameter data:

  • Someone else creates a calling procedure, and that person isn't aware of the allowable values.
  • Bad data entered by a user or taken from a database is passed to the parameter. When you hard-code values, you have more control. When the data can come from "outside the box," you can't be sure what data is coming into a procedure.
  • You add new calling procedures at a later time and mistakenly pass bad data.
  • A procedure is made a public member of a public object. When that happens, you can't be sure of the data passed to the parameter from an external program.

Given these possibilities, and given the fact that it doesn't take much effort to validate incoming data, it's simply not worth the risks not to validate incoming data. Validating parameters is one of the first tasks you should perform in a procedure.

Incorrect:

  Public Function  DuplicateContact(lngContactNumber  As Long  )  As Boolean   '* Purpose  :  Create a new contact that is a duplicate of    '*             the specified contact.    '* Accepts  :  lngContactNumber - the unique ID of the contact to    '*             duplicate. '*Returns:Trueifsuccessful,Falseotherwise.   On Error GoTo  PROC_ERR   End Function  

Correct:

  Public Function  DuplicateContact(lngContactNumber  As Long  )  As Boolean   '* Purpose  :  Create a new contact that is a duplicate of    '*             the specified contact.    '* Accepts  :  lngContactNumber - the unique ID of the contact to    '*             duplicate. '*Returns:Trueifsuccessful,Falseotherwise.   On Error GoTo  PROC_ERR  '* Make sure a valid contact number has been specified.   If  lngContactNumber <= 0  Then GoTo  PROC_EXIT  PROC_EXIT:  Exit Function End Function  

3.4.4 Use enumerations when a parameter accepts only a small set of values. Chapter 5, "Using Constants and Enumerations," describes enumerations in great detail, but they bear mentioning here also. If a parameter accepts only a small set of values, create an enumeration for the parameter. Using enumerations reduces the potential of data entry errors in coding. Remember, however, that declaring a parameter as an enumerated type does not ensure that a value passed to the parameter is a member of the enumeration; you must still validate the data. Enumerations are powerful and have many advantages ”whenever possible, consider using an enumeration.

Incorrect:

  Public Sub  PrintReport(strRptFileName  As String  , _ lngDestination  As Long  )  If  lngDestination < 0  Or  lngDestination > 1  Then GoTo  PROC_EXIT  PROC_EXIT:  Exit Sub End Sub  

Correct:

  Public Sub  PrintReport(strRptFileName  As String  , _ lngDestination  As  tpsPrintDestination)  '*Makesureavaliddestinationhasbeenspecified.   If  lngDestination <> tpsScreen  And _  lngDestination <> tpsPrinter  Then        GoTo  PROC_EXIT  End If   PROC_EXIT:  Exit Sub End Sub  

3.5 Call procedures in a consistent and self-documenting manner.

Visual Basic offers numerous shortcuts you can take when writing code. Generally, these shortcuts don't affect performance, but they often do sacrifice the readability of your code in favor of saving a few keystrokes at development time. You should attempt to make your code as self-documenting as possible. One area in which you can take shortcuts, but shouldn't, is when calling procedures.

You can call a procedure in a number of ways. When calling a Sub procedure, you can use the word Call or leave it out. For instance, the following statements will both call the same Sub procedure:

  Call  ShowError("clsApplication", "ShowRep", _                Err.Number, Err.Description) ShowError "clsApplication", "ShowRep", Err.Number, Err.Description 

Although you can omit the word Call and save yourself having to type two parentheses to boot, you should avoid this technique. The Call keyword specifically indicates that the statement is calling a Sub procedure as opposed to a Function procedure, and it makes the code more easily readable.

Visual Basic lets you call a Sub procedure and a Function procedure in exactly the same way. Consider this function:

  Public Function  DisplayContact(lngContactNumber  As  Long)  As Boolean    End Function  

You can call this function by using any of these statements:

  Call  ShowRep(lngRepNumber) ShowRep lngRepNumber blnResult = ShowRep(lngRepNumber) 

To make code as self-documenting as possible, differentiate between calling Sub procedures and calling Function procedures. Always use the Call keyword when calling Sub procedures, and always retrieve the value of a Function call ”even if you aren't going to use the value. The first statement in the previous example is misleading; it looks like a Sub procedure call rather than a Function procedure call. Although the second statement is better than the first, it does not retrieve the result of the function. This is not nearly as legible as the third statement, which does retrieve the result. If you aren't going to use the result of a Function call, consider placing a comment in front of the Function call stating what is returned from the function and why you don't need it.

Practical Applications

3.5.1 Always use the Call keyword when calling Sub procedures. By using Call when calling Sub procedures, it's easier to distinguish the call from a Function call.

Incorrect:

 PerformWordMerge8 strMergeFile, strTemplateName 

Correct:

  Call  PerformWordMerge8(strMergeFile, strTemplateName) 

3.5.2 When setting a variable to the result of a function, include the parentheses in the Function call even if there are no arguments. When calling a Function procedure, it isn't necessary to use the parentheses if the procedure doesn't accept any arguments. However, the code becomes a little more self-documenting when you include the parentheses anyway. The parentheses make it easy to tell that the symbol is a function name as opposed to a variable name.

Incorrect:

 strDefaultWareHouse = GetDefaultWarehouse 

Correct:

 strDefaultWareHouse = GetDefaultWarehouse() 

3.5.3 Always retrieve the return value of a function, even if you aren't going to use it. Although Visual Basic lets you call a Function procedure without retrieving the function's return value, you should always retrieve the return value even if you aren't going to use it. This improves readability and can greatly aid the debugging process. Many functions return a result code, indicating whether the function was successful. Although you might not need to use the result code right away, it might become important later.

Incorrect:

  Public Sub  OpenDocument(strFileName  As String  )  '* Purpose  :  Launch the associated program of the specified    '*             document.    '* Accepts  :  A full path and file name of a document.   On Error GoTo  PROC_ERR      ShellExecutefrmMain.hwnd, "Open", strFileName, 0, 0, 1 PROC_EXIT:  Exit Sub  PROC_ERR:    MsgBox Me.Name & "  OpenDocument" & vbCrLf & "Error: " & _           Err.Number & vbCrLf & Err.Description  Resume Next End Sub  

Correct:

  Public Sub  OpenDocument(strFileName  As String  )  '* Purpose  :  Launch the associated program of the specified    '*             document.    '* Accepts  :  A full path and file name of a document.   On Error GoTo  PROC_ERR  Dim  lngResult  As Long   '* The ShellExecute function returns a value indicating the result    '* of trying to open the document. We might need this value later.  lngResult = ShellExecute(frmMain.hwnd, "Open", _                             strFileName, 0, 0, 1) PROC_EXIT:  Exit Sub  PROC_ERR:    MsgBox Me.Name & "  OpenDocument" & vbCrLf & "Error: " & _           Err.Number & vbCrLf & Err.Description  Resume Next End Sub  


Practical Standards for Microsoft Visual Basic
Practical Standards for Microsoft Visual Basic .NET (Pro-Developer)
ISBN: 0735613567
EAN: 2147483647
Year: 2000
Pages: 57

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net