- Determining Coding Standards for .NET
- Sample .NET Coding Standards
Sample .NET Coding Standards
The remainder of this article provides a detailed sample of coding standards for a fictional company called Netz Inc. They are largely made up of Microsoft various recommendations and best practices, brought together into one place to save you from having to search around the various Microsoft web sites.
Overview of Technical and Applications Architecture
The Netz architecture consists of many different types of clientsPDAs, cell phones, PCs, etc. XML web services provide these clients with access to server-side functionality. The .NET framework takes care of the View part of an application to a great extent by mapping the view appropriately to the device on which the application is to be displayed. The ASP.NET and XML web services provide most of the Controller functionality, and the server-side code provides the Modeli.e., a mapping to business/data entities (see Figure 1).
Figure 1 From Application Architecture for .NET. (Courtesy Microsoft Corporation.)
General Programming Practices
Experienced developers follow numerous programming practices or rules, which typically are derived from hard-learned lessons. The practices listed in this section are not all-inclusive, and should not be used without due consideration. Veteran programmers deviate from these practices on occasion, but not without careful consideration of the potential repercussions. Using the best programming practice in the wrong context can cause more harm than good.
NOTE
For a detailed discussion, see Coding Techniques and Programming Practices.
To conserve resources, be selective in the choice of datatype to ensure that the size of a variable is not excessively large.
Keep the lifetime of variables as short as possible when the variables represent a finite resource for which there may be contention, such as a database connection.
Keep the scope of variables as small as possible to avoid confusion and to ensure maintainability. When maintaining legacy source code, the potential for inadvertently breaking other parts of the code can be minimized if variable scope is limited.
Use variables and routines for one and only one purpose. Avoid creating multipurpose routines that perform a variety of unrelated functions.
When writing classes, avoid the use of public variables. Instead, use procedures to provide a layer of encapsulation and to allow an opportunity to validate value changes.
When using objects pooled by MTS, acquire resources as late as possible and release them as soon as possible. Create objects as late as possible, and destroy them as early as possible to free resources.
When using objects that are not being pooled by MTS, examine the expense of the object creation and the level of contention for resources to determine when resources should be acquired and released.
Use only one transaction scheme, such as MTS or SQL Server, and minimize the scope and duration of transactions.
Be wary of using ASP Session variables in a web farm environment. At a minimum, don't place objects in ASP Session variables because session state is stored on a single machine. Consider storing session state in a database instead.
Stateless components are preferred when scalability or performance is important. Design the components to accept all the needed values as input parameters instead of relying upon object properties when calling methods. This technique eliminates the need to preserve object state between method calls. When it's necessary to maintain state, consider alternative methods such as maintaining state in a database.
Don't open data connections using a specific user's credentials. Connections that have been opened using such credentials cannot be pooled and reused, thus losing the benefits of connection pooling.
Avoid the use of forced data conversion, sometimes referred to as variable coercion or casting, which may yield unanticipated results. This occurs when two or more variables of different datatypes are involved in the same expression. When it's necessary to perform a cast for other than a trivial reason, that reason should be provided in an accompanying comment.
Develop and use error-handling routines:
For more information on error handling in Visual Basic, see Error Handling and Debugging in the Microsoft Office 2000/Visual Basic Programmer's Guide.
For more information on error handling and COM, see Error Handling in the Platform SDK.
For more information on error handling for web pages, see Handling and Avoiding Web Page Errors.
Be specific when declaring objects (ADODB.Recordset instead of just Recordset), to avoid the risk of name collisions.
Require the use of Option Explicit in Visual Basic and VBScript to encourage forethought in the use of variables and to minimize errors resulting from typographical errors.
Avoid the use of variables with application scope.
Use RETURN statements in stored procedures to help the calling program know whether the procedure worked properly.
Use early-bound objects to reduce upgrade problems. Both Visual Basic 6.0 and Visual Basic .NET support late-bound objects, which is the practice of declaring a variable as the Object datatype and assigning it to an instance of a class at runtime. However, during the upgrade process, late-bound objects can introduce problems when resolving default properties, or in cases where the underlying object model has changed and properties, methods, and events need to be converted. For more details on this issue, see Microsoft's upgrade recommendations.
Use Select Case or Switch statements in lieu of repetitive checking of a common variable using If...Then statements.
Explicitly release object references.
Which Language Is Used for Which Purpose?
The aim of .NET is to provide the developer with a choice of development language; code in all languages is compiled through the same Common Language Runtime (CLR). The choice on Netz projects is limited to the following:
Web forms for the front-end functionality (ASP.NET should be used for the front end, and Visual Basic .NET code for the "code behind")
ASP.NET and XML for web services
Visual Basic .NET on the server side
SQL Server transact SQL for stored procedures
Which Object Type Is Used for Which Purpose?
Web forms should be used for the front-end functionality.
ASP.NET web pages should be used for the control functionality.
COM+ objects (developed in Visual Basic .NET) should be used in the Model, server-side code.
Stored procedures should be used within the database in preference to SQL (wherever possible).
Formatting Source Code
The general principles for formatting source code are designed to make the source code as easily readable as possible. Remember that 80% of the effort expended on any development project is in maintenance, and in an average of 60% of maintenance work, the original developer is not the person who maintains the code.
General Layout
Formatting makes the logical organization of the code obvious. Taking the time to ensure that the source code is formatted in a consistent, logical manner is helpful to you and to other developers who must decipher the source code.
The following points are recommended formatting techniques.
Establish a standard size for an indentfour spacesand use it consistently. Align sections of code using the prescribed indention.
Use a monotype font when publishing hardcopy versions of the source code.
Indent code along lines of logical construction. Without indenting, code becomes difficult to follow:
If ... Then If ... Then ... Else End If Else ... End If
Indenting the code yields easier-to-read code:
If ... Then If ... Then ... Else ... End If Else ... End If
Establish a maximum line length for comments (70 characters) and code to avoid having to scroll the source code editor. This strategy also provides cleaner hardcopy presentation.
Use spaces before and after most operators when doing so does not alter the intent of the code.
Use whitespace to provide organizational clues to source code. Doing so creates "paragraphs" of code, which aid the reader in comprehending the logical segmenting of the software.
When a line is broken across several lines, make it obvious that it is incomplete without the following line by placing the concatenation operator at the end of each line instead of at the beginning.
Where appropriate, avoid using more than one statement per line.
When writing HTML, establish a standard format for tags and attributes, such as all uppercase for tags and all lowercase for attributes. As an alternative, adhere to the XHTML specification to ensure all HTML documents are valid. Although there are file size tradeoffs to consider when creating web pages, use quoted attribute values and closing tags to ease maintainability.
When writing SQL statements, use all uppercase for keywords and mixed case for database elements, such as tables, columns, and views.
Divide source code logically between physical files.
Put each major SQL clause on a separate line so that statements are easier to read and edit:
SELECT FirstName, LastName FROM Customers WHERE State = 'WA'
Break large, complex sections of code into smaller, more comprehensible modules.
Commenting
Software documentation exists in two forms: external and internal. External documentation, such as specifications, help files, and design documents, is maintained outside the source code. Internal documentation comprises comments that developers write within the source code at development time.
Despite the availability of external documentation, source code listings should be able to stand on their own because hardcopy documentation can be misplaced. External documentation should consist of specifications, design documents, change requests, bug history, and the coding standards used.
One challenge of internal software documentation is ensuring that the comments are maintained and updated in parallel with the source code. Although properly commenting source code serves no purpose at runtime, it's invaluable to a developer who must maintain a particularly intricate or cumbersome piece of software.
The following points are recommended commenting techniques. (See Microsoft's Visual Studio Coding Techniques for details on commenting code.)
NOTE
If you're developing in C#, use the XML Documentation feature, which is very much like the Javadoc facility for Java code in some ways. For more information, see XML Documentation.
When modifying code, always keep the commenting around it up to date.
At the beginning of every routine, provide standard, boilerplate comments, indicating the routine's purpose, assumptions, and limitations. A boilerplate comment should be a brief introduction that explains why that section of code exists and what it can do.
Avoid adding comments at the end of a line of code; end-line comments make code more difficult to read. However, end-line comments are appropriate when annotating variable declarations, in which case you should align all end-line comments at a common tab stop.
Avoid clutter comments, such as an entire line of asterisks. Instead, use whitespace to separate comments from code.
Avoid surrounding a block comment with a typographical frame. It may look attractive, but it's difficult to maintain.
Prior to deployment, remove all temporary or extraneous comments to avoid confusion during future maintenance work.
If you need comments to explain a complex section of code, examine the code to determine whether you should rewrite it. If at all possible, don't document bad coderewrite it. Although performance should not typically be sacrificed to make the code simpler for human consumption, a balance must be maintained between performance and maintainability.
Use complete sentences when writing comments. Comments should clarify the code, not add ambiguity.
Comment as you code because you are unlikely to have time to do it later. Also, should you get a chance to revisit code you've written, that which is obvious today probably will not be obvious six weeks from now.
Avoid superfluous or inappropriate comments, such as humorous sidebar remarks.
Use comments to explain the intent of the code. They should not serve as inline translations of the code.
Comment anything that's not readily obvious in the code.
To prevent recurring problems, always use comments on bugfixes and workaround code, especially in a team environment.
Use comments on code that consists of loops and logic branches. These are key areas that will assist source code readers.
Throughout the application, construct comments using a uniform style with consistent punctuation and structure.
Separate comments from comment delimiters with whitespace. Doing so will make comments obvious and easy to locate when viewed without color clues.
Naming Guidelines
Most naming issues are dealt with in Microsoft's Namespace Naming Guidelines. See these guidelines for more details on components such as Interfaces, Attributes, Enum types, and Property and Event naming. Some general principles of naming can also be found in Microsoft's Visual Studio Coding Techniques.
The following sections provide recommended naming techniques for Netz Inc.
Naming of Namespaces
The general rule for naming namespaces is to use the company name followed by the technology name and optionally the feature and design:
CompanyName.TechnologyName[.Feature][.Design]
For example:
Microsoft.Media Microsoft.Media.Design
Prefixing namespace names with a company name or other well-established brand avoids the possibility of two published namespaces having the same name. For example, Microsoft.Office is an appropriate prefix for the Office Automation Classes provided by Microsoft.
Naming of Components/Variables/Properties
The naming scheme is one of the most influential aids to understanding the logical flow of an application. A name should tell "what" rather than "how." By avoiding names that expose the underlying implementationwhich can changeyou preserve a layer of abstraction that simplifies the complexity. For example, you could use GetNextStudent() instead of GetNextArrayElement().
A tenet of naming is that difficulty in selecting a proper name may indicate that you need to further analyze or define the purpose of an item. Make names long enough to be meaningful but short enough to avoid verbosity. Programmatically, a unique name serves only to differentiate one item from another. Expressive names function as an aid to a human reader; therefore, it makes sense to provide a name that a human reader can comprehend. However, be certain that the chosen names are in compliance with the applicable language's rules and standards.
Naming of Routines/Methods
Avoid elusive names that are open to subjective interpretation, such as AnalyzeThis() for a routine, or xxK8 for a variable. Such names contribute to ambiguity.
In object-oriented languages, it's redundant to include class names in the name of class properties, such as Book.BookTitle. Instead, use Book.Title.
Use the verb-noun method for naming routines that perform some operation on a given object, such as CalculateInvoiceTotal().
In languages that permit function overloading, all overloads should perform a similar function. For languages that don't permit function overloading, establish a naming standard that relates similar functions.
Naming of Variables
Append computation qualifiers (Avg, Sum, Min, Max, Index) to the end of a variable name where appropriate.
Use complementary pairs in variable names, such as min/max, begin/end, and open/close.
Because most names are constructed by concatenating several words, use mixed-case formatting to simplify reading them. In addition, to help distinguish between variables and routines, use Pascal casing (CalculateInvoiceTotal) for routine names, where the first letter of each word is capitalized. For variable names, use camel casing (documentFormatType), where the first letter of each word except the first is capitalized.
Boolean variable names should contain Is to imply yes/no or true/false values, such as fileIsFound.
Avoid using terms such as Flag when naming status variables, which differ from Boolean variables in that they may have more than two possible values. Instead of documentFlag, use a more descriptive name such as documentFormatType.
Even for a short-lived variable that may appear in only a few lines of code, use a meaningful name. Use single-letter variable names, such as i, or j, for short-loop indexes only.
Don't use literal numbers or literal strings, such as For i = 1 To 7. Instead, use named constants, such as For i = 1 To NUM_DAYS_IN_WEEK for ease of maintenance and understanding.
Naming of Tables
When naming tables, express the name in the singular form. For example, use Employee instead of Employees.
When naming columns of tables, don't repeat the table name; for example, avoid a field called EmployeeLastName in a table called Employee.
Don't incorporate the datatype in the name of a column. This will reduce the amount of work should it become necessary to change the datatype later.
Naming of Microsoft SQL Server Items
Don't prefix stored procedures with sp, which is a prefix reserved for identifying system stored procedures.
Don't prefix user-defined functions with fn_, which is a prefix reserved for identifying built-in functions.
Don't prefix extended stored procedures with xp_, which is a prefix reserved for identifying system extended stored procedures.
Naming Miscellaneous Items
Minimize the use of abbreviations. If you must abbreviate, be consistent. An abbreviation should have only one meaning; likewise, each abbreviated word should have only one abbreviation. For example, if you use min to abbreviate minimum, do so everywhere; don't also use min to abbreviate minute.
When naming functions, include a description of the value being returned, such as GetCurrentWindowName().
Like a procedure name, a file or folder name should accurately describe its purpose.
Avoid reusing names for different elements, such as a routine called ProcessSales() and a variable called iProcessSales.
Avoid homonyms, such as write and right, to prevent confusion during code reviews.
Avoid reusing names.
When naming elements, avoid commonly misspelled words. Also, be aware of differences that exist between regional spellings, such as color/colour and check/cheque.
Avoid typographical marks to identify datatypes, such as $ for strings or % for integers.
Capitalization Styles
See Microsoft's .NET Framework General Reference for detailed information on this topic. The following table provides a general list of identifier types, showing how they should be capitalized.
Identifier |
Case |
Example |
Class |
Pascal |
AppDomain |
Enum type |
Pascal |
ErrorLevel |
Enum values |
Pascal |
FatalError |
Event |
Pascal |
ValueChange |
Exception class |
Pascal |
WebException (always ends with the suffix Exception) |
Read-only Static field |
Pascal |
RedValue |
Interface |
Pascal |
IDisposable (always begins with the prefix I) |
Method |
Pascal |
ToString |
Namespace |
Pascal |
System.Drawing |
Parameter |
Camel |
typeName |
Property |
Pascal |
BackColor |
Protected instance field |
camel |
redValue (rarely used; a property is preferable to using a protected instance field) |
Public instance field |
Pascal |
RedValue (rarely used; a property is preferable to using a public instance field) |
Case Sensitivity
To ensure cross-language, cross-platform operation, don't use names that require case sensitivity; that is, never create two components, files, directories, properties, methods, or variables that differ only by case within the same context.
Types
Don't use names that are dependent on the type of a variable/parameter in a particular language. Instead, use names based on the purpose of the variable/parameter. This principle is intended to keep Netz policies in line with Microsoft's stated policy on Hungarian notation/prefixes.
NOTE
Admittedly, this advice is highly contentious. Many well-respected authors on Visual Basic programming have spoken out in preference of Hungarian notation or other kind of prefixesfor example, see Practical Standards for Microsoft Visual Basic .NET (Microsoft Press, 2002), by James D. Foxall.
Visual Basic Old Style |
C# Old Style |
Visual Basic New Style |
C# New Style |
Sub Write(doubleValue As Double); |
void Write(double doubleValue); |
Sub Write(value As Double); |
void Write(double value); |
Sub Write(singleValue As Single); |
void Write(float floatValue); |
Sub Write(value As Single); |
void Write(float value); |
Sub Write(longValue As Long); |
void Write(long longValue); |
Sub Write(value As Long); |
void Write(long value); |
Naming of (Security) Roles
Windows 2000 uses DNS naming standards for hierarchical naming of Active Directory domains and computers. For this reason, domain and computer objects are part of both the DNS domain hierarchy and the Active Directory domain hierarchy. Although these domain hierarchies have identical names, they represent separate namespaces.
NOTE
For details, see Microsoft's Active Directory Logical Structure.
The domain hierarchy defines a namespace. A namespace is any bounded area in which standardized names can be used to symbolically represent some type of information, such as an object in a directory or an Internet Protocol (IP) address, and that can be resolved to the object itself. In each namespace, specific rules determine how names can be created and used. Some namespaces, such as the DNS namespace and the Active Directory namespace, are hierarchically structured and provide rules that allow the namespace to be partitioned. Other namespaces, such as the Network Basic Input/Output System (NetBIOS) namespace, are flat (unstructured) and cannot be partitioned.
The main function of DNS is to map user-readable computer names to computer-readable IP addresses. Thus, DNS defines a namespace for computer names that can be resolved to IP addresses, or vice versa. In Windows NT 4.0 and earlier, DNS names were not required; domains and computers used NetBIOS names, which were mapped to IP addresses by using the Windows Internet Name Service (WINS). Although DNS names are required for Windows 2000 domains and Windows 2000based computers, NetBIOS names also are supported in Windows 2000 for interoperability with Windows NT 4.0 domains and with clients that are running Windows NT 4.0 or earlier, Windows for Workgroups, Windows 98, or Windows 95.
Source Code Control
Visual SourceSafe, integrated into the IDE, is used in Netz Inc. for source code control. The Source Code Manager has ownership of the source code control system and any questions can be directed to him/her.
A different branch will exist at the project level within SourceSafe for each "release" of a project's code. For the release that is currently under development, this will be subdivided into development, test, uat (user acceptance test), and live branches. The development tree will correspond to the version of code currently residing on the development web server, the test tree to the version on the test server, and so on.
Bugzilla will be used for bug/defect tracking.
Portability Issues
All code should be tested to ensure that it runs on any device that can view the applicationPDAs, mobiles, PCs, etc.
Localization/Internationalization Issues
Microsoft provides a localization/globalization step-by-step guide.
Documenting Changes/Change Logs
A change control process is detailed in the internal standards documentation.
Bugzilla will be used for bug/defect tracking.
Code reviews by the Source Code Manager for the project will check the following:
Filename naming conventions
Package/directory structure
Configuration files/registry keys needed
Any database changes required by the code
Before any code is checked in for the first time and before it is checked back in after changes are made, it must be
Commented
Logged in the Change Management log/Defect log
Labeled/versioned as appropriate
Unit tested
Standard Error Handling: ASP.NET
To display error messages in ASP.NET, use custom error-handling ASP.NET pages named error404.aspx for 404 (not found) errors and error.aspx for all other errors. The error.aspx file is specified in the <customErrors> section of the Web.config file:
<customErrors defaultredirect="error.aspx" mode="on"> <error statuscode="404" redirect="error404.aspx"/> </customErrors>
Standard Error Handling: Components
When an application calls a method of your component, the method can provide error information by throwing an exception. The client application can implement an exception handler to trap errors that may be thrown by the method.
There are a number of programming tradeoffs to consider when you select an error-generation strategy for your component, but the most important consideration should be the convenience of the developer who will use your component. If it's possible for your component to handle an exception internally, it should do so. The client application should receive exceptions from your component only when additional intervention is required.
When you raise exceptions, the developer who calls your method has the choice of handling the exception or allowing it to be thrown further up the call stack.
Whatever exceptions you throw from your component should be logically consistent. If you decide to create custom exceptions to report errors unique to your component, they should be limited to reporting that particular error. Don't use a general exception when a more specific one can be used. The purpose of raising an exception is to provide the client application with as much information as possible about how to correct the error and keep the program running smoothly.
If you create and throw custom exceptions in your component, they should be well documented so that developers using your component will be able to handle them. Your documentation should include a description of the exception, the conditions under which it's likely to occur, suggested ways for resolving the error condition, and any error codes or additional information that the exception returns.
Exceptions Are for Exceptional Circumstances
Don't throw exceptions as a means of communicating between componentsuse events for that. Exceptions should not be thrown in cases where completely expected results occur. For example, a component that reads text files should not throw an exception when it reaches the end of the file. This is a case where an event would be appropriate. By limiting throwing exceptions to exceptional cases, you'll help developers to debug their applications rapidly by not having to wade through numerous "expected" exceptions.
COM-Visible Components and Exceptions
If your component is to be called by a COM client, take into account these additional considerations. All exceptions thrown from a component to a COM client must provide an error code. With standard exceptions, this code is provided "behind the scenes" via the HResult property. If you're creating and throwing custom exceptions, you must set an appropriate HResult value. If your custom exception returns an error condition that's similar to a standard error condition, the HResult value for that error condition can be used. Otherwise, you can set the value however you choose. In this case, it's important to document all error codes that might be passed via HResult so that the developer using your component can implement appropriate responses. For details, see HRESULTs and Exceptions.
Unstructured Error Handling (Visual Basic Only)
Visual Basic .NET supports unstructured error handling using the On Error Goto syntax. While this kind of error handling will continue to function in your components, its implementation is not recommended. Code written using this method of error handling is difficult to debug and maintain, and it can degrade application performance. Structured error handling using the Try...Catch...Finally block results in code that's easier to maintain, more robust, and more flexible in the ways in which errors can be handled.
Standard Session Timeout Handling
Detailed guidelines on how to handle session timeouts using common code can be found in the technical architecture documentation for Netz Inc. In the common directory for each application is code that handles session timeouts in a standard manner.
Standard Help Facilities
Each application/web page must have a link in the upper-right corner of the screen/page linking the user to the standard help page/screen for the current application. This standard help page/screen should provide some context-sensitive help on the right side of the screen and provide access to the index/glossary on the left side, with search facilities across the top of the page/screen. Standard code is available in the common directories to provide consistent help across all applications. See the technical architecture documentation for more information.
Configuration Information
The error.aspx file is specified in the <customErrors> section of the Web.config file:
<customErrors defaultredirect="error.aspx" mode="on"> <error statuscode="404" redirect="error404.aspx"/> </customErrors>
Each registered user of an application has a profile record in the profile table. This table includes configuration information for that user, including localization information, personalization information, etc.
Users who don't log in to use a web site/application are considered to be "guest users" and don't have access to personalization/localization features until they register.
Configuration information about hardware, web servers, application servers, etc. can be found in the technical architecture documentation.