C# Code Guidelines

  • Notation and naming

In general, the standard Microsoft guidelines are used: https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/naming-guidelines

  • Variable Initialization

All variables are to be initialized to their default values by the compiler. Avoid unnecessary initialization as it increases overhead (string name = string.Empty or int sessionId = 0 are examples of improper declaration).

Define global page variables as protected or private and never public.

  • Comments

All properties, constructors and methods should contain XML comments. More info can be found at: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/xmldoc/xml-documentation-comments

Recommendations for Commenting (based on Code Complete by Steve McConnell)

Summary of the code

A comment that summarizes code does just that: It distills a few lines of code into one or two sentences. Such comments are more valuable than comments that merely repeat the code because a reader can scan them more quickly than the code. Summary comments are particularly useful when someone other than the code’s original author tries to modify the code.

Description of the code’s intent

A comment at the level of intent explains the purpose of a section of code. Intent comments operate more at the level of the problem than at the level of the solution. For example,
// gets current employee information
Is an intent comment, whereas
// updates EmpRec structure
Is a summary comment in terms of the solution. A six-month study conducted by IBM found that maintenance programmers “most often said that understanding the original programmer’s intent was the most difficult problem”. The distinction between intent and summary comments isn’t always clear, and it’s usually not important.

The only two kinds of comments that are acceptable for completed code are intent and summary comments.

Commenting Paragraphs of Code

Use comments to prepare the reader for what is to follow. Good comments tell the person reading the code what to expect. A reader should be able to scan only the comments and get a good idea of what the code does and where to look for a specific activity. A corollary to this rule is that a comment should always precede the code it describes.

There’s no virtue in excessive commenting. Too many comments obscure the code they’re meant to clarify. Rather than writing more comments, put the extra effort into making the code itself more readable.

If you find anything that isn’t obvious from the code itself, put it into a comment.

Follow the Principle of Proximity and put comments as close as possible to the code they describe. They’re more likely to be maintained, and they’ll continue to be worthwhile.

  • Formatting

Tab Size: 2

Indent Size: 2

Check Insert Spaces

  • Code structure

Do not create arrow formations, maximum number of nested conditions is 3.

http://martinfowler.com/refactoring/catalog/replaceNestedConditionalWithGuardClauses.html

Do not use magic strings http://stackoverflow.com/questions/3205549/net-strategies-to-avoid-magic-string

Always use the SOLID principle to structure your code: http://en.wikipedia.org/wiki/SOLID_(object-oriented_design)

  • Exception handling

To develop an effective policy on handling exceptions, it’s good to think of the entire application architecture—i.e., where is the exception being raised and where should it be resolved? Considered that way, the right response is usually obvious. (But the problem is that we don’t usually think this way until we’re deep into coding the application, all the while writing some stock exception-handling block that commits us to a safe but less effective policy).

In that light, this article will build on the thoughts in my earlier blog to look at exception-handling from the perspective of the typical n-tiered application, which, for brevity, I’ll condense into a data access layer, a business logic layer, and a presentation layer. Within each tier it may also be helpful to distinguish where the exception is raised in the call stack. The top of the stack, of course, is the most recent call—the one that resulted in the exception.

In the data access layer we have the greatest chance to fix the problem, particularly at the top of the stack. For example, a connection string may time out, but an alternate or failover connection is available. Or we try to open a file that doesn’t exist, but we’re okay creating a new one if that happens. Actually fixing problems helps to distinguish a superior application, one likely to be useful for years to come. But we must be prepared: we put a Try block around just the line or lines of code that may occasion a specific error condition, then we add a Catch block for just that condition—no catching generic Exception objects for us!

What happens after we catch it? We fix it if possible, yes, but then we need to move on, usually by calling the code that immediately follows the Try block. Here we are rewarded for our fidelity to the principle in the previous paragraph. Otherwise, our Catch block will have to repeat the same lines of unexecuted code from the Try. Either way, the problem is fixed, and no further exception handling is required.

If we cannot fix the problem, what then? We recall that we’re in the data access layer, way deep in the application. First, we want to restore the state of any data structures that were altered by the problematic code. Second, if we need to bail out, we want to close connections and release resources. These two requirements may lead—generally do—to a Finally block, one of the world’s most useful concepts. Recall that a Finally block may be used with a Try block even without a Catch.

That done, we address the exception itself. We want the exception to percolate up the stack to a location where, although it probably cannot be fixed there either, it can be more effectively acted on. So we’re going to let the runtime’s exception handler work. Effectively that presents us with two options: 1) do nothing, which will cause the runtime to check the next method on the stack for a Catch block; or 2) embellish the exception and re-throw it. Number two is usually the better option because the method we’re in right now knows the most about the context of the exception and is in the best position to provide helpful information. Often this is meta-data, but it could be anything: the more specific, detailed, and technical the information the better. Why? Because this error, if it makes it all the way to a log file (the worst case, but the one we have to assume), is going to be read by a developer, who will want as much information about it as possible. The message, the call stack, the client application—these values are all given to us by the exception object itself. The context—the meta-data—requires a new field. And we don’t need to stop there. We could also include the history of this kind of exception in this application and what was done to fix it; another new field.

The built-in Exception types, of course, are inadequate for conveying this information. Therefore, we need to build our own. Every application—possibly every layer—should declare at least one really robust and capable class derived from Exception, whether it defines its own or uses one from a common library. It should have as many additional fields and methods as it needs to perform its specialized role. This is the object that we’re going to re-throw if we need more information.

From the data access layer the exception percolates eventually to the business logic layer, which presents us with a dilemma. To act or not to act? Looking at the seven possible options, not to act, which just feels wrong, is probably right. Obviously we don’t want to swallow the exception—for all practical purposes that is never a good choice. Catching the exception and re-throwing it, without adding any value to it, is no better than doing nothing; in fact, it uses more system resources. The business logic layer is in a poor position to the embellish the exception unless it happens to know something about the context that the data access layer did not, like a senior manager in the organization with access to the big picture. And it is probably not in a position to fix the problem. Doing nothing, then, is generally the right choice.

What about logging the exception there? No, it’s too soon, because the choice of logging or presenting should be left to the presentation layer, even if the presentation layer winds up calling a component in the business logic layer to do the actual work. Why? Because the presentation layer is—or should be—in the unique position of knowing what corrective actions the end user is capable of performing. It is very likely the place where the cause of the exception was introduced (i.e., the end user’s input). It has—or should have—the most immediate access to the validation layer. And because it’s so closely conversant with patterns of human error, it may even be in the position to fix the error without further prompting.

If not, it display an error message. And that message should be something special. Remember all that technical stuff we embellished the exception with? We may decide to put it in an internal message link, accessed by a More Info button, but that’s not what we’re going to display. We’re going to rephrase the exception message to make it easily understood by non-technical people and not at all frightening to them: what happened, why it happened, what, if anything, can be done about it, and what the repercussions are if it can’t. All in clear, simple language (e.g., don’t say “repercussions;” and for that matter don’t say “e.g.” either); easily understood language; language free of jargon. As in all communications, when we write these messages we should ask ourselves what action we expect the user to take as a result of reading them.

If it cannot fix the exception itself, if it cannot present the exception to the user to fix (or shrug off), then the presentation layer logs the exception. This is always a sad outcome. An error log is the last resort, the morgue, of exception-handling; it doesn’t do anything except facilitate the inquest. An end user’s experience with the application has been diminished, an opportunity has been lost. But the exception, chock-full of technical detail and context, can help lead to the cause of the problem and in this way help lead to a solution that prevents or accommodates future errors of this type.

Lead, follow, or get out of the way: it’s not quite that simple, but good exception handling knows when to act, when to watch, when to communicate, and when to gather information. Once we get our general bearings in the application, and once we ask ourselves the vital question, “What could go wrong here and what could I do about it?” exception handling becomes more than an exercise in snippet pasting.
When re-throwing an exception, the proper way to preserve the stack trace is to simply call throw, not specifying the exception

This:

catch (AccountNotFoundException ex)

{

throw;

}

Not This:

catch (AccountNotFoundException ex)

{

throw ex;

}

Sign Up To Our Newsletter

Stay up-to-date with our technical tips, click on a story to learn more.

Join Now