Defensive Programming in C# – Part 3

Tiis chapter discusses the techniques for writing code in a manner that reduces the chances of bugs and delivers high-performance, reliable assemblies suitable for production use. These techniques reflect our cumulative experience (as an original C# team member in Eric Gunnerson’s case and as a professional C# developer, consultant, and MVP in Nick Wienholt’s case).

Some of the material in this chapter reinforces the advice presented in earlier chapters, and other sections cover new topics.

1. Naming Conventions

Chapter 38 listed the naming conventions you should use in C# applications. These recom­mendations are a subset of the full set of guidelines at http://msdn.microsoft.com/library/ default.asp?url=/library/en-us/cpgenref/html/cpconnamespacenamingguidelines.asp, which in turn are a subset of Microsoft’s own internal coding guidelines, which are currently (in early 2005) available at http://blogs.msdn.com/brada/articles/361363.aspx.

Naming conventions are primarily designed to speed up coding and to reduce bugs by allowing a developer to differentiate items such as types, variables, enumerations, and other code constructs based on the way they’re named. Naming conventions work in conjunction with the language, the compiler, and the development environment, and advances in these areas can lessen the need for complex naming conventions. .NET and the C# language, which are strongly typed and have excellent facilities such as reflection for dealing with type information, make Hungarian notation redundant. (Hungarian notation uses type-identifying prefixes in variable names.)

Naming conventions are a means to an end (in this case, the end is faster, better code); they aren’t an end goal in themselves. Because they’re easy to learn and enforce, intermediate-level developers often go overboard in developing and enforcing naming standards and code layout rules that take a long time to implement and maintain. Avoid this tendency to overconventionalize the way code is written, and realize that a few, clear rules are much more likely to be respected than a large, multivolume developer handbook.

The following tips will make living with naming conventions and coding guidelines a little easier:

Use the minimal set of naming conventions and coding guidelines necessary to arrive at good code. Chapter 38 will serve as a good basis.

Use coding guidelines (such as the position of braces and tabs/spaces) that are supported by the code editor. Visual Studio 2005 offers a great range of flexibility regarding these issues. Ensure that each developer is using the same settings. With the development envi­ronment set up correctly, you’ll automatically take care of code layout issues. Although it’s possible for each developer to maintain separate settings and use Visual Studio’s reformatting power to modify source files for their edits, it will cause unneeded formatting discrepancies in the code base and confusing churn in the source control system.

For developers moving to .NET, use an auditing tool to pick up lapses in naming conventions. FxCop from Microsoft is built specifically for this purpose and is integrated into Visual Studio 2005.

Don’t use Hungarian notation in non-UI code, even though Hungarian notation may still be useful in UI code to differentiate between the name of member variables and the UI element that displays them, as well as making controls of the same class easier to find using IntelliSense.

2. Embrace the IDE

A lot of real-world code does mundane and tedious work—managing object population, getting the information from objects into UI controls and back again, laying out screens, and dealing with the flow of data into and out of a database. Microsoft has expended an enormous sum of money in research and development to make Visual Studio .NET an indispensable aid for creating this tedious code in a fast and robust manner. If you’re still an old-style developer cutting code in Notepad, try Visual Studio .NET 2005 for a month or so, making a genuine attempt to use its features to simplify your coding efforts. It’s unlikely you’ll choose to go back to Notepad at the end of the evaluation.

3. Exceptions

Chapter 4 introduced the topic of exception handling. The following sections summarize a number of the points made in Chapter 4 and also cover a few items not covered earlier.

3.1. Throw Early, Throw Often

To borrow from the famous electoral fraud campaign associated with Richard Daley (Chicago’s Democratic mayor during the 1960 presidential election), it’s good practice to throw excep­tions early and often. Not only does this help with code correctness, it helps reduce the likelihood of security vulnerabilities. Noted Microsoft security expert Michael Howard has stated that if security problems caused by uncaught malicious input could be solved, the vast majority of computer security issues would disappear (Microsoft Professional Developer Confer­ence 2003, Security Symposium, Session 1).

Thoroughly checking all parameters that come into a method and rejecting the invalid parameters by throwing an ArgumentException-derived object is the best way to implement malicious input checks. The .NET Framework libraries are written using this technique (it’s possible to verify this claim by using Reflector (http://www.aisto.com/roeder/dotnet/) or the Shared Source CLI (which is based on the commercial CLR and is available from http:// msdn.microsoft.com/net/sscli/).

The actual pattern used is as follows:

using System;

class SomeRandomClass {

private static readonly int MaxValueP1 = 10;

public void SomeRandomMethod(int p1, string p2)

{

if (p1 < 0 || p1 > SomeRandomClass.MaxValueP1)

{

throw new ArgumentOutOfRangeException(“p1”, p1,

“p1 must be a positive number less than ” + MaxValueP1.ToString());

}

if (p2 == null)

{

throw new ArgumentNullException(“p2”);

}

if (p2.Length > p1)

{

throw new ArgumentOutOfRangeException(

“The length of p2 cannot be greater than the value of p1”);

}

//real method body

}

//other methods

}

Although one of the guidelines related to exception management is to minimize the number of exceptions thrown, it’s more important to avoid security bugs and any other type of bug rather than to incur the performance hit of exceptions. If performance is favored too highly over security, the code produced may end up as part of a fast-spreading Internet worm, and performance of this kind is in nobody’s interests.

3.2. Catching, Rethrowing, and Ignoring Exceptions

This section serves only to reiterate the advice presented in Chapter 4. Many developers get these items wrong when developing their code.

The two simple rules are as follows:

If an exception can’t be handled, don’t catch it. Many developers, particularly those from a language background that doesn’t include exceptions, feel obliged to catch every exception. Letting an exception that can’t be handled pass through isn’t a case of bad coding manners and doesn’t indicate a lazy program. The stack trace will let the ultimate catcher know that the exception came through a particular method, so catching just to log is largely redundant. The only time when a global catch block is appropriate is at application or service boundaries. If resources need to be released, use a finally block rather than a catch block. The C# using statement (covered in a moment) simplifies this pattern.

Use throw instead of throw ex (where ex is the exception variable from the catch statement). Using throw ex will reset the stack trace, which is rarely the intended result. If code is rethrowing lots of exceptions, consider whether it’s a violation of the first guideline.

3.3. Use using

When instantiating any scope-limited IDisposable-implementing object, use a using block to ensure the resource is properly released. A using block guarantees that a resource will be cleaned up properly—provided that the machine, process, or application domain hosting the code isn’t forcibly shut down. If an exception is thrown, or a return statement is executed, the finally block generated by the C# compiler will execute, and the IDisposable-implementing object will have its Dispose method called if the object has been created.

You can include two objects in the same using statement if they’re of the same type. If the objects are of different types, you can nest the using blocks:

using (SqlConnection conn1 = new SqlConnection(), conn2 = new SqlConnection())

{

//some more code here

using (SqlCommand cmd = new SqlCommand())

{

;

}

}

4. Collections

When upgrading from .NET 1.x to 2.0, the key challenge is to “reset your defaults” and move rapidly to the generic-based collections. Generics are CLS-compliant, which means all other .NET 2.0-compliant languages should at least be able to consume generic code, if not extend it. Generics have many advantages and essentially no disadvantages. The only two problems are developer familiarity and the requirement for end users to upgrade their .NET Framework version to 2.0. For the sake of a day of training and 20MB of hard disk space, the benefits of generics far outweigh this potential inconvenience.

5. Thread-Safety

Dealing with threads is one of the most challenging aspects typically facing an intermediate- level developer. At some stage, most developers “discover threads” and go wild with all sorts of weird and wonderful threading patterns. This usually lasts until they spend all night or all weekend in front of the computer attempting to diagnose some intermittent threading bug. The pain of this episode is useful in teaching a developer to use threads sparingly and only when appropriate. If you have yet to experience this event, conduct the mental exercise of placing yourself in the position of all the developers who have experienced it and attempt to learn from their mistakes.

As a general rule, apply threads only in the following situations:

  • When a lengthy operation needs to occur and the UI thread must remain responsive to user input. Remember that Windows Forms Control methods and properties (with the exception of IsInvokeRequired and Invoke()) need to be invoked back onto the UI thread.
  • When two or more operations need to be conducted and minimal dependencies exist between the two operations. Examples include servicing multiple clients simultaneously and writing some data to a database and flat file at the same time.

5.1. Understand Processor Memory Models

Chapter 31 touched on processor memory models, and, because this an introductory book, this section won’t drill into the topic in much more detail. The message worth emphasizing from Chapter 31 is that it isn’t wise to get too clever with threading optimizations until you understand the whole range of operating system, .NET Framework, and processor optimizations.

With the x86 instruction set and current 32-bit processors, the physical and logical execu­tion of code paths is fairly similar. With the new range of 64-bit processors, logical and physical code paths begin to diverge, and a processor may do something such as speculatively execute a code branch or load a bit of data based on a guess that the code branch result or data will probably be needed. If the processor guesses wrong, the speculative results are discarded silently, but if an activity is occurring on another thread without the normal locking statements, you can get strange results. Normal locking statements are often not used with operations that are atomic at an instruction-set level, such as variable assignment. With more permissive memory models such as those on the 64-bit chips, this can cause problems. A specific processor instruction and a .NET Framework method (Thread .MemoryBarrier()) can deal with this problem without resorting to locking, but unless code is being written (and will be maintained) by developers with a mastery of all these issues, it’s much wiser to take the small performance hit of locking.

5.2. Locking on this and Type

In .NET 1.x, the pattern of locking on an object’s this reference for instance data and a type’s Type instance for static data was the dominant pattern. This pattern has been deprecated in favor of holding private object references that are used with Monitor locks and the lock statement.

Using private variables prevents two separate pieces of code from locking on the same object for different purposes, which may potentially cause deadlock, and it prevents malicious code that’s loaded through plug-ins or similar means from conducting Denial of Service attacks through thread locking.

The issues surrounding the use of locks on the this reference are a microcosm of the larger problem of ensuring that multiple threads don’t interfere with each other in unintended and undesirable ways. Although there’s a wealth of methods, ranging from the lightweight Interlock functions all the way up to heavyweight semaphores and mutexes, it’s critical that the activity of various threads is partitioned enough so they aren’t constantly trying to acquire locks on shared resources. If multiple threads are spending the majority of their time attempting to use shared resources to complete a unit of work, it could indicate certain design problems.

6. Code-Quality Tools

Developers have relied on code-quality tools such as PC-Lint to supplement the checks made by the compiler for many years. Although the strong typing and verification features of .NET and C# mean that many of the tasks that have traditionally been implemented by code-quality tools are now part of the compile process, the need for code-quality tools hasn’t disappeared. The following sections cover a few entry-level tools that will be useful to developers getting started with .NET.

6.1. NUnit

Although Extreme Programming (XP) has failed to take over the world as the dominant software development methodology, agile programming’s promotion of continuous integration and unit testing has had a marked impact on a huge range of developers and development shops that haven’t adopted the other elements of XP. While unit testing and continuous integration weren’t invented by XP, it has promoted them from a “should do” to a “must do” in the mind of most developers. This section focuses on unit testing; to learn more about continuous integration, consult one of the multitude of XP and development methodology books that have been released in the past five years.

Unlike in other areas of open-source tools, NUnit is the dominant force in .NET unit testing and is freely available from http://www.nunit.org. NUnit ships with an easy-to-read primer to get started with unit testing, so it’d be redundant to repeat that material here.

Writing unit tests is easy from a syntax perspective. The hardest part of unit testing is constructing unit tests that are meaningful enough so they’re likely to trap bugs without being so tightly coupled to the implementation of a method that any change causes the test to fail. Although this is trivial to implement and demonstrate in a unit test that examines code that performs some basic arithmetic operation, it’s much harder to achieve with real-world methods that have database and UI interactions. Even if you achieve loose coupling between components, testing application-level components such as Employee or PayrollRun can be a difficult task.

One of the best sources for looking at the way real-world unit tests are written is the Shared Source CLI (SSCLI) from Microsoft. While not written using NUnit (which obviously doesn’t predate the .NET Framework), the tests compromise hundreds of real unit tests that helped ensure the high quality of the .NET Framework. The range and breadth of tests disprove the notion that “real” developers on “real” projects don’t unit test and are a fantastic source of unit testing inspiration.

6.2. FxCop

FxCop statically analyzes a compiled assembly for compliance with .NET Framework guide­lines. Microsoft originally created the tool to ensure the .NET Framework complied with these guidelines and provided a consistent programming experience. Microsoft released the tool as a free download as part of .NET 1.x (see the FxCop home page at http://www.gotdotnet.com/ team/fxcop/ for details) and has now integrated it into Visual Studio .NET 2005. Although FxCop can’t enforce coding layout standards (it inspects only the compiled assembly), it can ensure compliance with naming guidelines and has many other security and efficiency checks.

As well as providing a good out-of-the-box experience for checking compiled assemblies, FxCop offers a great extension mechanism that allows assemblies to be checked without dealing with all the pain associated with loading and parsing MSIL. Although not as powerful and mature as third-party offerings from companies such as Compuware, the fact that FxCop is freely available makes it a good entry point into the code/assembly analysis world.

Source: Gunnerson Eric, Wienholt Nick (2005), A Programmer’s Introduction to C# 2.0, Apress; 3rd edition.

Leave a Reply

Your email address will not be published. Required fields are marked *