When Not to Use Hungarian Notation

Directives

4.1 Prefix all constants with c_ and a scope designator.

In the past, one convention for denoting a constant was to use all uppercase letters for the constant's name. For instance, when you created a constant to store a column index in a grid, you would use a statement like this:

Const COLUMN_INDEX = 7 

Typing anything in code in all uppercase letters is now considered antiquated and undesirable. Mixed-case text (also referred to as camel casing) is much easier to read. However, because variable and procedure names are also entered in mixed case, it's still important to denote when an item is a constant. A better convention is to prefix the constant name with c_. For example, the constant shown above would be declared like this:

Const c_ColumnIndex = 7 

This constant name is a bit easier to read, and you can still immediately tell that you're looking at a constant as opposed to a variable. Notice that I didn't use an underscore in place of a space this is the preferred convention.

Note

Labels for use with GoTo are one of the few exceptions to using mixed-case letters. Such labels, which should be used sparingly, appear in all uppercase letters. Refer to Chapter 9, "Controlling Code Flow," for more information on using these labels.


 

Another identifying characteristic of a constant as opposed to a variable is the lack of a data type prefix. For instance, if you were storing the column indicator in a variable, you would probably declare the variable by using a statement like this:

Dim intColumnIndex As Integer 

Many developers don't realize that you can actually create a constant of a specific data type. For instance, the following statement is completely legal:

Const c_InterestRate As Single = 7.5 

If you turn on Option Strict, you must declare each constant as a specific data type. In general, I think adding a type prefix to a constant adds complexity. Unlike the case of variables, you'll often commit a constant to memory and the prefix can just get in the way. If you decide to use type prefixes on constants, use the variable-naming prefixes discussed in Chapter 3, "Naming Conventions," and be sure to use this technique consistently. For example:

Const c_sngInterestRate As Single = 7.5 

Regardless of whether you choose to use a type prefix on a constant, you should still use the same prefix scheme for indicating the scope of constants that you use for variables. For constants declared locally (within a procedure), no scope indicator is necessary. For constants declared as Private in the Declarations section of a module, you should use the prefix m. For global constants (constants declared as Public within a standard module), you should use the prefix g. The following are declarations of the same constant at different levels of scope:

Procedure:

Const c_InterestRate = 7.5

 

Module (private):

 Private Const mc_InterestRate = 7.5

 

Global:

Public Const gc_InterestRate = 7.5

 

Note

Constants are declared Private by default if you don't explicitly declare them with the Public keyword. As with procedures and variables, constants should always have a clearly defined scope. If you want to create a private constant, explicitly declare the constant using the Private keyword.


 

By consistently specifying the scope of a constant in addition to denoting the constant with c_, you'll make your code easier to read and to debug. If you're ever unsure where a constant is declared, right-click the constant and choose Go To Definition from the context menu that appears. Visual Basic .NET will take you directly to the constant's declaration.

Practical Applications

When you uniquely identify constants and denote their scope, you create more readable code.

4.1.1 Declare constants using mixed-case characters, prefixing each constant with c_.

Remember that identifying constants by using all uppercase letters is out.

Incorrect:
Const USDATE = "mm/dd/yyyy" Const INTERESTRATE = 6.75 
Correct:
Const c_USDate = "mm/dd/yyyy" Const c_InterestRate = 6.75 
4.1.2 Denote a constant's scope using a scope designator prefix.

Knowing a constant's scope is extremely important for debugging. All constants declared in the Declarations section of any type of module need a g or an m designator.

Incorrect (module level or global level):
Private Const c_USDate = "mm/dd/yyyy" Public Const c_InterestRate = 6.75 
Correct:
Private Const mc_USDate = "mm/dd/yyyy" Public Const gc_InterestRate = 6.75 

4.2 Use constants in place of magic numbers, regardless of scope.

I hope that the first part of this chapter has convinced you of the importance of replacing hard-coded numbers (magic numbers) with constants, regardless of scope. It might be tempting to use a hard-coded number within a procedure because it seems silly to create a constant for a single use in a single place. Certainly, maintaining the value is easy enough; you don't need to perform a search and replace when the number exists only once. However, readability is still a problem. Magic numbers are called magic for a reason. When someone else looks at the code, how can you be sure that the number will make complete sense to him or her? Regardless of scope, you should always replace magic numbers with constants. All together now: "Use constants in place of magic numbers, regardless of scope."

Incorrect:
' Fill a most-recently-used list. For intCount = 1 To 4    strFileName = RetrieveRecentFileName(intCount)        ' If an entry was found, add it to the list.     If strFileName <> "" Then       objItem = lvwRecent.Items.Add(strFileName)       objItem.ImageIndex = 1    End If   Next intCount 
Correct:
' Fill a most-recently-used list. Const c_MaxRecentlyUsed = 4 For intCount = 1 To c_MaxRecentlyUsed    strFileName = RetrieveRecentFileName(intCount)        ' If an entry was found, add it to the list.     If strFileName <> "" Then       objItem = lvwRecent.Items.Add(strFileName)       objItem.ImageIndex = 1    End If Next intCount 

4.3 Use enumerations whenever they are available.

When a procedure or a parameter is declared as an enumerated type, it's common courtesy nay, it's your duty to reference the enumeration member names rather than their values. When you use an enumeration, your code is much easier to read and less likely to contain errors. Your code is also less likely to fail if someone later breaks backward compatibility by changing the values that correspond to the member names.

Note

Using enumerated members when available is so important that when you turn on Option Strict, you must use enumeration members; attempting to pass a value other than an enumerated member will throw a compile error.


 

For instance, say you're using an ActiveX component and one of its properties is BorderStyle. The developer has designated Flat and Sunken as the possible values and has exposed them as members of an enumeration, as shown here:

Public Enum BorderStyle    Flat = 0    Sunken = 1 End Enum 

Say that you use the literal values rather than the enumeration's member names and you're updating to a new component. The developer has added a new BorderStyle value called Chiseled to the component. The developer wasn't really thinking of backward compatibility, and he or she changed the enumeration structure to the following:

 Public Enum BorderStyle    Flat = 0    Chiseled = 1    Sunken = 2 End Enum 

You can see that if you hard-code 1 to designate a sunken appearance, you'll get unexpected results after you upgrade. Obviously, component developers shouldn't break backward compatibility in this way, but it does happen. If you use the member's name rather than its value, your code won't be affected by such an oversight. Whether to use enumerations shouldn't even be a question. If a function supports them, use them. Again, turning on Option Strict, which is highly advisable, will force you to use enumerated members where applicable.

Incorrect:
MessageBox.Show("Print all documents?", "Print", 4, 4) 
Correct:
MessageBox.Show("Print all documents?", "Print", _                 MessageBoxButtons.YesNo, MessageBoxIcon.Question) 

4.4 Use an enumeration whenever a parameter accepts a limited number of values.

Even developers who truly believe in enumerations sometimes miss the opportunity to use them. As you develop code, you might not always think about creating an enumeration because an enumeration might seem like overkill in certain situations. In general, whenever a procedure accepts a limited set of values, use an enumeration. For instance, if you have a procedure in which a parameter can use only one of two values, that parameter is a prime candidate for an enumeration.

Creating an enumeration for two values might seem excessive, but it's not. You still get the benefits of avoiding magic numbers, including reduced data entry and greater legibility. Also, if you decide to add members in the future, an enumeration will make it easier to do so. If you turn Option Strict on, you get the added advantage of having the compiler validate passed data. Whether the values are strings or numbers is irrelevant; you can benefit from using an enumeration in both situations.

Incorrect:
Public Sub ShowAVIFile(ByVal lngType As Long) 
Correct:
Public Enum AVIFileType As Integer    FileCopy = 0    FileDelete = 1    FileDeleteToRecycle = 2    FileNuke = 3    FindComputer = 4    FindFile = 5    FileMove = 6    Search = 7 End Enum Public Sub ShowAVIFile(ByVal intAVIFileType As AVIFileType) 

Note

Enumerations should be declared within their source class. For example, if you had a Print class that used a Destination enumeration (Printer, Screen, File, and so on) for a property value, you would place the enumeration in the Print class.


 

4.5 Validate values that are passed as enumerated types.

You must validate any value passed to a parameter declared as an enumerated type to ensure that it is acceptable. When a parameter is declared as an enumerated type, it's really a long integer with a fancy IntelliSense drop-down list. While other developers should use the named enumeration members, they are free to pass the parameter any valid long integer.

There are essentially two methods you can use to validate the data:

  • If the values fall within a range (such as 1 to 10), use an If End If construct to validate the data.

  • If the values form a discrete set that doesn't fit nicely in a range, use a Select Case construct.

In general, unless you are validating that the value falls within an acceptable range, use the Select Case construct rather than If End If. The Select Case construct gives you more flexibility if you need to add more values to the enumeration later.

Note

When you use Select Case, always include an Else clause to deal with an invalid value passed into the procedure.


 

Practical Applications

Making assumptions is one of the leading causes of errors in code. Visual Basic won't ensure that values passed to an enumerated type actually correspond to named members within the enumeration (unless Option Strict is turned on). However, turning Option Strict on ensures only that the enumerated members are specified instead of raw numbers; it doesn't ensure that you'll always have a valid value. For example, suppose that you've defined the following enumeration:

Private Enum ButtonStyle As Integer    Flat = 128    Popped = 64 End Enum 

And suppose that you have the following procedure definition:

Private Sub ShowButton(ByVal intButtonStyle As ButtonStyle) 

A developer could call the ShowButton() procedure using the following statement. Note that the actual value passed to the parameter would be 192 a value that doesn't correspond to an enumerated member.

Call ShowButton(ButtonStyle.Flat Or ButtonStyle.Popped) 

The bottom line is this: never assume you have good data.

4.5.1 Always validate data by using comparisons to the named members, not to magic numbers.

Refrain from using magic numbers for data validation, just as you refrain from using magic numbers elsewhere in your code.

Incorrect:
   Public Enum PrintDestination       Screen = 0       Printer = 1    End Enum    Public Sub PrintReport(ByVal strFileName As String, _          ByVal intDestination As PrintDestination)       ' Verify that a valid location has been specified.        If intDestination < 0 Or intDestination > 1 Then           GoTo PROC_EXIT       End If        ' Print the specified report.        PROC_EXIT:    End Sub 
Correct:
   Public Enum PrintDestination       Screen = 0       Printer = 1    End Enum    Public Sub PrintReport(ByVal strFileName As String, _          ByVal intDestination As PrintDestination)       ' Verify that a valid location has been specified.        If (intDestination < PrintDestination.Screen) Or _             (intDestination > PrintDestination.Printer) Then          GoTo PROC_EXIT       End If        ' Print the specified report.        PROC_EXIT:    End Sub 
4.5.2 Use Select Case to validate that a value is a valid member of a discrete set of values.

Don't forget to include the Case Else clause to handle invalid data.

Correct:
   Public Sub PrintDocument(ByVal intCallingForm As CallingForm)       ' Perform necessary actions based on the calling form.        Select Case intCallingForm          Case Is = CallingForm.ContactView                       Case Is = CallingForm.ServiceOrderView                       Case Is = CallingForm.CustomerInventoryView                       Case Else              ' The value passed as the calling form parameter             ' is invalid!             MessageBox.Show("An incorrect calling form " & _                             "has been specified!", "Error", _                             MessageBoxButtons.OK)             GoTo PROC_EXIT       End Select PROC_EXIT:    End Sub 


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