Chapter 2. Designing Modules and Procedures

Directives

2.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 closer to dead every day). Many programmers from the early 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 Delete Employee 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
2.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() 
2.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() 

2.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.

Note

Much to my surprise, I've found that using single exit points is one of those standards that seems to highly polarize developers. It appears that a number of developers are strongly against single exit points. The primary reason seems to be the reliance on GoTo, which I can understand. The good news now is that if you use structured exception handling as discussed in Chapter 10, "Exception Handling," the Finally block of an exception handler acts as a single exit point and there's no longer a need to use GoTo for this effect. Most of the sample code in this book doesn't include structured error handling only because I don't want it to get in the way of the topic at hand. In production code, every procedure should use structured exception handling as detailed in Chapter 10.


 

Practical Application
2.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 fclsMain_Closed(ByVal sender As Object, _                             ByVal As System.EventArgs) _                             Handles MyBase.Closed    Try           Catch ex As Exception           Finally        ' Free resources used by the drawing surface.       m_objDrawingSurface.Dispose()    End Try End Sub 
Correct:
Private Sub fclsMain_Closed(ByVal sender As Object, _                             ByVal As System.EventArgs) _                             Handles MyBase.Closed    Try           Catch ex As Exception           Finally        ' If an error occurred earlier, it's possible that the variable       ' doesn't hold a reference to an object, in which case calling       ' Dispose() would cause an error.        If Not (m_objDrawingSurface Is NothingThen          m_objDrawingSurface.Dispose()       End If    End Try End Sub 

2.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 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 makes the procedure private 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, declare it by using Private. If the procedure is called from procedures in multiple modules, explicitly declare the procedure as Public.

Practical Application
2.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 

2.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 the number of module and global variables is to pass data between procedures as parameters, rather than having the procedures share global or module-level variables.

Practical Applications
2.4.1 Specify a data type for each and every parameter.

Visual Basic .NET now includes the project option Option Strict, which forces you to declare all variables as specific object types. When possible, it's recommended that you use Option Strict in your projects. (See Chapter 5 for more information.) When you don't use Option Strict in a project, you can declare variables (parameters are really just a special kind of variable) without a specific type, but you shouldn't. 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 5.) When you omit the As < type > portion of a parameter declaration, the parameter is created as an Object. I'll discuss the problems inherent with the Object data type in Chapter 5. If you want to create a Object parameter, do so explicitly using As Object.

Incorrect:
Private Sub CreateStockRecord(ItemID, Repair, Quantity) 
Correct:
Private Sub CreateStockRecord(strItemID As String, blnRepair _                               As Boolean, sngQuantity As Single) 
2.4.2 Pass data ByVal or ByRef, as appropriate.

Visual Basic passes data to procedure parameters by value unless told to do otherwise. Note that this is the opposite behavior of previous versions of Visual Basic, where parameters where passed by reference by default. When a variable is passed by value (ByVal) to a parameter, the procedure receives a copy of the variable. Any changes made to the parameter are made to the copy only; the original variable remains unchanged. 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 as well.

In general, passing parameters by value creates code that tends to be more solid the reason being that you can't change the value of a parameter and inadvertently change the value of the variable in the calling procedure. These types of errors can be difficult to track down, so passing parameters by value is usually your best choice. If you don't specify ByVal or ByRef, Visual Basic will add ByVal to your parameter declarations automatically. If you need a variable passed by reference, be sure to explicitly declare the parameter to be received ByRef.

Note

This practical application doesn't include correct/incorrect examples.


 

2.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, as well as 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(ByVal lngContactNumber As LongAs 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   :  True if successful, False otherwise.       ' Procedure code goes here... PROC_EXIT:    End Function 
Correct:
   Public Function DuplicateContact(ByVal lngContactNumber As LongAs 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  :  True if successful, False otherwise.       ' Make sure a valid contact number has been specified.        If lngContactNumber <= 0 Then GoTo PROC_EXIT       ' Procedure code goes here... PROC_EXIT:    End Function 
2.4.4 Use enumerations when a parameter accepts only a small set of values.

Chapter 4, "Using Constants and Enumerations," describes enumerations in great detail, but they bear mentioning here as well. 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. When used in a program set to Option Strict, enumerations are type-safe, which means that you can pass only a defined enumerated value to an argument defined as an enumeration this promotes more reliable code. If you're not using Option Strict, 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(ByVal strRptFileName As String, _                           ByVal intDestination As Integer)       ' Make sure we have a valid print destination.        If intDestination < 0 Or intDestination > 1 Then GoTo PROC_EXIT PROC_EXIT:    End Sub 
Correct:
   Public Sub PrintReport(ByVal strRptFileName As String, _                           ByVal intDestination As enumPrintDestination)       ' Make sure we have a valid print destination.        If (intDestination < enumPrintDestination.Screen) Or _          (intDestination > enumPrintDestination.Printer) Then          GoTo PROC_EXIT       End If PROC_EXIT:    End Sub 
2.5 Call procedures in a consistent and self-documenting manner.

Visual Basic offers numerous shortcuts that 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. Visual Basic .NET has actually removed some of these shortcuts in order to promote more reliable code, but many still exist. 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 DisplayContact(intContactNumber) DisplayContact(intContactNumber) 

Although you can omit the word Call, 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.

Not only can you call a Sub procedure in multiple ways, you can also call Function procedures in more than one way. You can even call a Sub procedure and a Function procedure in exactly the same way. Consider this function:

Public Function DisplayContact(ByVal intContactNumber As Long) _                                As Boolean     End Function 

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

Call DisplayContact(intCurrentContact) DisplayContact(lngRepNumber) blnResult = DisplayContact(intCurrentContact) 

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 doesn't retrieve the result of the function. This is not nearly as legible as the third statement, which retrieves 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
2.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:
PerformWordMerge(strMergeFile, strTemplateName) 
Correct:
Call PerformWordMerge(strMergeFile, strTemplateName) 
2.5.2 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 during development or testing.

Incorrect:
   Public Sub OpenDocument(ByVal strFileName As String)       ' Purpose   :  Launch the associated program of the specified       '              document.       ' Accepts   :  A full path and file name of a document.       Shell(strFileName, AppWinStyle.MaximizedFocus, False, -1)  PROC_EXIT:    End Sub 
Correct:
   Public Sub OpenDocument(ByVal strFileName As String)       ' Purpose   :  Launch the associated program of the specified       '              document.       ' Accepts   :  A full path and file name of a document.        Dim intProcessID As Integer        ' Shell returns a value indicating the process ID. This might       ' be needed later.       intProcessID = Shell(strFileName, AppWinStyle.MaximizedFocus, _                            False, -1) PROC_EXIT:    End Sub 
2.6 Return values from a function by using the Return statement.

In the past, you returned a value for a function by using code such as this:

Private Function MyFunction() As Boolean    MyFunction = True End Function 

Visual Basic .NET introduces a new keyword that can be used to return a value for the function: Return. You should use Return whenever possible, like this:

Private Function MyFunction() As Boolean    Return True End Function 

One important thing that you need to know about Return is that it immediately initiates the termination of the procedure. If no structured exception handler is in place, the function simply exists and the value is passed back to the calling code. This means that no code executes after the Return statement. On the other hand, if you use a structured exception handler (as you should), execution jumps to the Finally block before exiting the procedure. This allows you to use Return and still execute clean-up code in a single exit point the best of all worlds.

2.7 Use a scratch variable within complex functions.

At times, you'll need to manipulate data that needs to be returned as the result of a function. In such cases, you should create a scratch variable to hold and manipulate the return value before actually returning it as the result of the function. This makes code more readable because the user can easily distinguish between manipulating the final result and making a new call to the procedure. Consider this procedure:

Public Function GetSomeValue() As Integer    GetSomeValue = 55    GetSomeValue = GetSomeValue + 6    GetSomeValue = GetSomeValue() + 6 End Function 

Did you say 67? If so, that's incorrect. In fact, the procedure returns no value, and instead throws a stack overflow exception because the third statement in the procedure calls the procedure again (notice the parentheses), which in turns calls the procedure again, and so forth. This happens until the stack space is exhausted and the exception is thrown infinite recursion. By using a scratch variable, you reduce the likelihood of such an oversight.

Incorrect:
   Public Function SumDigits(ByVal intInput As IntegerAs Integer        ' Purpose  :  Sum the digits in a number.        Dim strInput As String CStr(intInput)       Dim intCounter As Integer        ' Loop through the digits and return the sum.        For intCounter = 0 To strInput.Length - 1          SumDigits = SumDigits + CInt(strInput.Substring(intCounter, 1))       Next intCounter PROC_EXIT:    End Function 
Correct:
   Public Function SumDigits(ByVal intInput As IntegerAs Integer        ' Purpose  :  Sum the digits in a number.        Dim strInput As String CStr(intInput)       Dim intScratch As Integer = 0       Dim intCounter As Integer        ' Loop through the digits and return the sum.        For intCounter = 0 To strInput.Length - 1          intScratch = intScratch + CInt(strInput.Substring(intCounter, 1))       Next intCounter       ' Return the result.       SumDigits = intScratch PROC_EXIT:    End Function 


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

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