Thursday, July 17, 2008

VB.NET Coding Guidelines

Table of Contents
1. Introduction
2. Style Guidelines
2.1 Tabs and Indenting
2.2 Option Explicit / Option Strict
2.3 Block Formatting
2.4 Single line statements
2.5 Commenting
2.5.1 Copyright notice
2.5.2 Documentation Comments
2.5.3 Comment Style
2.6 Spacing
2.7 Attributes
2.8 Naming
2.9 Naming Conventions
2.9.1 Interop Classes
2.10 File Organization
2.11 Doing things the Visual Basic .NET Way
2.12 Microsoft.VisualBasic.Compatibility
2.13 Other Considerations
1. Introduction
First, read the .NET Framework Design Guidelines. These guidelines exist to extend upon those guidelines for the VB.NET developer. Almost all naming conventions, casing rules, etc., are spelled out in this document. Unlike the Design Guidelines document, you should treat this document as a set of suggested guidelines. These guidelines are not to be taken a law and exist to offer guidance for people who do not have guidelines or are seeking improving upon their existing coding habits.
Note: Portions of this document are based on the C# Coding Guidelines written by Brad Abrams.
2. Style Guidelines
2.1 Tabs & Indenting
Tab characters (ASCII 9) should not be used in code. All indentation should be done with 2 space characters.
The reason I use 2 spaces is that, to me, it's just a readable as 3 or 4. I believe the VB runtime team uses 3 spaces and the default in the IDE is 4. However, if your code as a lot of indentation (which VB.NET does), you quickly end up with some pretty long lines and a lot of extraneous white space. The reason I settled on 2 spaces is because of these issues and it looks great when publishing code one the web and in print. In the end this is a really minor guideline. Use whatever your comfortable with since, with the "Pretty Print" feature in the IDE, you can quickly set the spacing to your preference.
2.2 Option Explicit / Option Strict
Although it’s possible to enable these options within the project properties, you should place the following at the top of every source file:
Option Explicit On Option Strict On
If you are unable to have both of these set to “On”, a comment should be given as to the reason for doing so.
Although there is an option in the IDE to enable this, the problem is that when you share code with others. The default in the IDE is these turned off. So when you provide code and they incorporate it within their project, the compiler features enabled when these are turn on are not gained. Also, if they are in every file, you know, without a doubt, what the options are set to. Finally, if you turn the options on in the IDE and you get code from another source, your going to see a potentially see a ton of errors. Have to fix them before (or adding Options Off) even being able to if the code does what you are looking for it to do. One final point. During code reviews, you can see this in the source... again, absolutely no questions being raised.
2.3 Block Formatting
All logical blocks should be formatted in such a way as to be clean and clear as to what’s occurring. Every If/Then should have an End If.
If someExpression Then DoSomething()Else DoSomethingElse()End If
“Select Case” statements should be formatted as follows:
Select Case someExpression Case 0 DoSomething() Case 1 DoSomethingElse() Case 2 Dim n As Integer = 1 DoAnotherThing(n) Case Else ' Normally this would be the default.End Select
Not wanting to get into a situation where these guidelines become too restrictive. Personally, in my code, I avoid ever having an empty Case Else. I think there should always be a default setting and I make this one inside of the Case Else. If there is a case when there is no default, then I raise an error. As for If/Then Else sections being empty, I just don't have them.
2.4 Single line statements
Single line statements should be avoided in every instance except possibly the Select Case blocks. Even then, strong consideration needs to be given to doing so and only done if the statement in question is a single line and very short. If any of the Case elements are not a single line, make all of the Case elements multiple lines for consistency.
Right:
Select Case value Case 0: x += 1 Case 1: x += 5 Case Else: x += 10End Select
OrSelect Case value Case 0 DoSomeProcessStep1() DoSomeProcessStep2() x += 1 Case 1 x += 5 Case Else x += 10End Select
Wrong:
Select Case value Case 0: DoSomeProcessStep1() : DoSomeProcessStep2() : x += 1 Case 1 x += 5 Case Else: x += 10End Select
2.5 Commenting
Comments should be used to describe intention, algorithmic overview, and/or logical flow. It would be ideal, if from reading the comments alone, someone other than the author could understand a function’s intended behavior and general operation. While there are no minimum comment requirements and certainly some very small routines need no commenting at all, it is hoped that most routines will have comments reflecting the programmer’s intent and approach.
2.5.1 Copyright notice
Each file should start with a copyright notice. To avoid errors in doc comment builds, you don’t want to use triple-apostrophe doc comments, but using XML makes the comments easy to replace in the future. Final text will vary by product (you should contact legal for the exact text), but should be similar to:
'-----------------------------------------------------------------------' ' Copyright (c) Microsoft Corporation. All rights reserved.' '-----------------------------------------------------------------------
Contact legal? As you can see with the mention of Microsoft Corporation within the example, this section is included mainly to be consistent with the Microsoft internal C# guidelines. Whatever the reason may be, it's pretty good advice.
2.5.2 Documentation Comments
All methods should use XML doc comments. For internal dev comments, the tag should be used.
Public Class World ''' Public stuff about the method ''' What a neat parameter! ''' Cool internal stuff! ''' Public Sub MyMethod(Byval value As Integer) ' ... End Sub
End Class
2.5.3 Comment Style
The ' (apostrophe) style of comment tag should be used in most situations. Where ever possible, place comments above the code instead of beside it. Separate the actually comment (text) from the comment tag with a minimum of a single space, begin the comment with an uppercase letter (proper sentence structure) and end each comment with a period. Here are some examples:
' This is required for WebClient to work through the proxy.GlobalProxySelection.Select = New WebProxy("http://itgproxy")
' Create object to access Internet resources.Dim myClient As New WebClient()
Comments can be placed at the end of a line when space allows:
Public Class SomethingUseful Private itemHash As Integer ' Some instance member. Private Static hasDoneSomething A Boolean ' Some static member.End Class
2.6 Spacing
In addition to using the “Pretty Listing” feature from within Visual Studio, follow these guidelines. Spaces improve readability by decreasing code density. Here are some guidelines for the use of space characters within code:
Do use a single space after a comma between function arguments.Right: Console.In.Read(myChar, 0, 1)Wrong: Console.In.Read(myChar,0,1)
Do not use a space after the parenthesis and function argumentsRight: CreateWorld(myChar, 0, 1)Wrong: CreateWorld( myChar, 0, 1 )
Do not use spaces between a function name and parenthesis.Right: CreateWorld()Wrong: CreateWorld ()
Do use a single space before and after comparison operatorsRight: If (x = y) ThenWrong: If (x=y) Then
Do use single line spacing between Class, Module, Method and Property definitionsRight: Sub Spin()End SubSub Bounce()End SubWrong: Sub Spin()End SubSub Bounce()End Sub
Someone asked about defining spacing within the methods. To me, this is something that is pretty dependent on the type and amount of code within the method. My general rule of thumb is methods that contain a section of variable declarations or contain a lot of If/End If testing, I add a single space on either side of the code. For code that is very small and/or doesn't contain any variable declarations separated on their own, I usually have no spacing within the method.
2.7 Attributes
Attributes should be on the line before the statement the attribute is pertaining to (followed by the space underscore)
_Public Enum ExitWindowFlags LogOff = &H0 Shutdown = &H1 Reboot = &H2 Force = &H4 PowerOff = &H8 ForceIfHung = &H10End Enum
2.8 Naming
Follow all .NET Framework Design Guidelines for both internal and external members. Highlights of these include:
Do not use Hungarian notation (except for private member variables).
Do use the m_ prefix for private Class level member variables. This is the only guideline in this document that goes against the .NET Framework Design Guidelines, but the guidelines in that document assume languages that are case-sensitive. It is necessary to distinguish the private member variables in some manner as to not collide with the public properties of the same name. To offer complete consistency, this guideline is extended to all private member variables.
Do use camelCasing for member variables.
Do use camelCasing for parameters.
Do use camelCasing for local variables.
Do use PascalCasing for function, property, event, and class names.
Do prefix interfaces names with “I”
Do not prefix enums, classes, or delegates with any letter
Do not use single-letter naming for variables.
Avoid using constants, use Enum instead. However, when you can’t avoid using constants, use PascalCasing.
Do use ex as the local exception variable in a Try…Catch statement.
The reason to extend the public rules (no Hungarian, etc.) is to produce a consistent source code appearance. In addition a goal is to have clean readable source. Code legibility should be a primary goal.
According to the guidelines, private variables are camelCased. I suggest following this guideline even for the backing variables using the m_, meaning that you would use m_hairColor and not m_HairColor. The variable name is actually (in concept) the hairColor portion and just adding the m_ to prevent a collision from occurring (to represent it as a backing variable).
If you choose not to use m_, your alternative should be to follow the guidelines which means you would use camelCasing and use a variable naming scheme that would allow you to differentiate the backing variable from the property name. This can be done by appending a word after the variable name such as Value, Member or Private giving you hairColorValue. I don't prefer to go this route myself, but the choice is ultimately yours.
Single-letter variable names should be avoided; however, if the single-letter naming is clear to anyone that will be viewing the code and doesn't need any explanation (x, y for coordinates for example), feel free to use them. The main thing is to avoid them unless it makes perfect sense to use them. Rule of Thumb: Think about an alternative to using the single-letter variable name and after additional thought the single-letter naming seems more appropriate, then it's probably OK. For example, instead of using i in an For/Next, use index, count, offset, etc.
2.9 Naming Conventions
2.9.1 Interop Classes
Classes that are there for interop wrappers (Declare statements) should follow the naming convention below:
NativeMethods – No suppress unmanaged code attribute, these are methods that can be used anywhere because a stack walk will be performed.
UnsafeNativeMethods – Has suppress unmanaged code attribute. These methods are potentially dangerous and any caller of these methods must do a full security review to ensure that the usage is safe and protected as no stack walk will be performed.
SafeNativeMethods – Has suppress unmanaged code attribute. These methods are safe and can be used fairly safely and the caller isn’t needed to do full security reviews even though no stack walk will be performed.Class NativeMethods Private Sub New() End Sub Friend Declare Sub FormatHardDrive Lib "user32" (ByVal driveName As String)End Class _Class UnsafeNativeMethods Private Sub New() End Sub Friend Declare Sub CreateFile Lib "user32" (ByVal fileName As String)End Class _Class SafeNativeMethods Private Sub New() End Sub Friend Declare Sub MessageBox Lib "user32" (ByVal [text] As String)End Class
All interop classes must be Private, and all methods must be Friend. In addition a private constructor should be provided to prevent instantiation.
Use the Declare statement instead of the Attribute/Method blocks for declaring Win32 interop unless there is a need to do so because the Declare statement doesn’t expose some needed functionality.
When handling errors involved with interop, use the follow when the API documentation states to use GetLastError (if you aren’t going to handle the error within the code):
Throw New Win32Exception(Marshal.GetLastWin32Error)
2.10 File Organization
Source files should contain only one public type, although multiple internal classes are allowed.
Source files should be given the name of the Public Class in the file.
Directory names should follow the Namespace for the class.
For example, I would expect to find the public class “System.Windows.Forms.Control” in “System\Windows\Forms\Control.vb”…
Class elements should be grouped into sections. Within these sections, elements should be alphabetized.
Private member variables
New / Finalize / Dispose
Public
Events
Properties
Methods
Enumerators
Protected
Events
Properties
Methods
Enumerators
Friend
Events
Properties
Methods
Enumerators
Private
Events
Properties
Methods
Enumerators
2.11 Doing things the Visual Basic .NET Way
Do use the Visual Basic runtime methods rather than .NET Framework where appropriate.
Do use the short method for instantiating classes on the same line.Right: Dim someStuff As New Collection()Wrong: Dim someStuff As Collection = New Collection()
Do use Try…Catch instead of On Error.
Do use Cnnn() methods instead of System.Convert.Tonnn() methods.
Do not use type characters ($, !, #, %, etc.)
Do not create a Class containing only Shared methods. Use Modules instead.
Using the Visual Basic runtime seems to cause people to enter into a holy war, so to explain this:
First, taking the example of Right(); yes, in the end, it will call upon String.Substring(). It's doing a bit more than that, which in many cases helps to make sure you code is more stable. Here is basically what the code looks like.
Public Shared Function Right(ByVal value As String, ByVal length As Integer) As String If (length < length =" 0" integer =" value.Length">= size) Then Return value End If Return value.Substring(size - length, length) End Function
By using Right, you don't have to worry about whether or not the value is nothing or even if the size you want is within the range. However, if this is important to you, then by all means, use Substring directly. I added the "where appropriate" because I think there are some areas where it makes more sense to use the objects (String for example) method over the runtime method. The main point here is to not be afraid to utilize these methods and if you don't use them, be sure you are aware of the reason as to why you aren't.
The second reason is that this suggestion is shared by members of the VB team; obviously they worked hard to make these available in order to make our development life more productive and I don't blame them for wanting to see the fruits of their labor leveraged by us. In addition, I'm constantly seeing false information being spread regarding the "non-use" of the runtime and how it's not the ".NET" way. These are every bit a part of the .NET Framework as say Windows Forms, ADO.NET, ASP.NET, etc. and should be given as much, if not more, consideration in being leveraged, especially by VB.NET developers.
Third, the compiler is able to optimize the code when you leverage several of these and my guess is this will improve over time. Not using these puts that responsibility on you, the developer and you don't gain any of these benefits in the current and future compiler. This is a minor point, but one I feel I should mention for completeness.
In the end, use whatever you are most comfortable with. Just be aware that it's not "wrong" to leverage the runtime, in fact, in many cases its significantly better than trying to do the same task on your own. To be fair, there are some areas in the VB runtime that don't perform that well; in those instances, you have to decide whether performance is your key concern and if it is, for those instances (and, this part is key, if it truly is a bottleneck), find an alternate solution. Performance is a completely different subject and there is no one right answer.
2.12 Microsoft.VisualBasic.Compatibility
Do not use any methods within the Microsoft.VisualBasic.Compatibility namespace. This namespace is not to be confused with Microsoft.VisualBasic; I encourage you to use these timesaving tidbits. If your project contains a reference to the Microsoft.VisualBasic. Compatibility.dll, remove it and replace any methods that have become invalid with methods either from the Microsoft.VisualBasic or System namespaces.
2.13 Other Considerations
The following are general considerations that you should keep in mind.
Use collections instead of arrays when returning a group of data from a method.
Use events instead of overriding methods. For example, use the Paint event instead of overriding OnPaint.
Avoid requiring the orchestration of multiple objects to do a single job.
Avoid performing operations within properties, use properties to set state. Configure an object through its properties and then use its methods to do the work.
Try to gather items in a namespace that directly relate to each other. Try to avoid putting too many classes under a general category as it creates intellisense noise for developers trying to find something.

No comments: