C H A P T E R  6

.NET Rules and Regulations

For some developers, the statement that there are .NET rules and regulations goes too far. But this chapter does not lay down the law in a series of commandments. Development is about creativity, imagination, passion, and inventiveness; it is not about rejecting all convention in favor of individual expression. There are accepted standards and guidelines that are appropriate for you, your team, and your organization. There are generalized statements, principles, and procedures that describe what is true and good in nearly all cases. It is important to sort out those practices that best serve as rules and regulations, as a way to establish norms and to govern behavior, which supports innovative development.

As a developer, following the .NET rules and regulations helps you to remain aligned with good practices and avoid bad practices. As a team leader, the rules and regulations help the members of the team to be consistent and to align with one another. As an organization, the alignment across teams helps achieve compliance with accepted standards, effective development principles, and good practices.

A foolish consistency is not the goal. The goal is a prudent and intentional consistency that helps build upon the experiences and expertise of many .NET developers. The goal is to avoid bad practices, known failure paths, and systemic problems. The goal is to achieve positive outcomes by avoiding the painful and costly mistakes of other developers. By following the advice that underlies the rules and regulations, you benefit from the collective wisdom of expert developers. These are ruthlessly helpful .NET practices for you, your team, and your organization (see Table 6-1).

images

images

Commentary

Coding Standards and Guidelines

The Microsoft All-In-One Code Framework (AIOCF) team has established and documented the .NET coding standards and guidelines that that team follows. That document is broad; it covers C++, C#, and VB.NET. The IDesign C# Coding Standards document is specific to C#, but covers .NET Framework–specific guidelines that include topics like ASP.NET and web services, data access, and security. The IDesign and AIOCF documents are different in many ways, yet similar in many others. Your team or organization could benefit from adopting one or the other or synergizing them together, or perhaps neither document suits the people and circumstances.

This section has two purposes. First, to provide you with sources and material to develop .NET rules and regulations. Second, to provide specific examples of .NET rules and regulations that—in this author's opinion—are widely-accepted and especially helpful practices. With this information you can assemble and establish the .NET rules and regulations appropriate to your circumstances.

Sources

There are many sources of .NET rules and regulations (see Table 6-2 for a list). Other teams have written coding standards documents and made them widely available. Other organizations have written and published coding standards documents, as well. Some of these sources are short and to the point. Others are very comprehensive with detailed justification and code examples. The most important thing to appreciate is that other experienced developers have put a great deal of thought and discussion into defining these coding standards and guidelines.

images Practice 6-1 Examine the .NET Coding Standards and Guidelines Others Have Written

The primary virtue of building upon the .NET coding standards and guidelines that others have written is that your rules and regulations can focus on making a statement. Your rules and regulations use emphatic phrases (such as, “Do,” “Do Not,” “Never,” “Avoid,” and “Always”) and let the source material support the assertions. Remember, the rules and regulations are a lot more likely to be accepted if they are succinct, plain-spoken, and backed up by credible coding standards and guidelines.

It is important to highlight that Microsoft has established the “minimum recommended” rule set that focuses on what they deem the most critical coding problems. These include potential security holes, application crashes, and other important logic and design errors. You should review the Microsoft Minimum Recommended Rules rule set1 and strive to include these rules in the .NET rules and regulation you create for your projects.

images

images

__________

2 The All-In-One Code Framework is also known as CodeFx.

images Note Much of the .NET rule and regulation verification can be automated using tools associated with some of these sources. Chapter 11 describes practices to automate code analysis using FxCop.

Exceptions

Throwing exceptions is the primary .NET mechanism for reporting errors. Every .NET method, property, event, or other member ought to throw an exception if it cannot successfully fulfill its purpose. In other words, an exception should be thrown if the code execution failed or could not complete through a normal exit path. Also, when there is an error, throw exceptions and do not return error codes.

It is important to know when and how to appropriately set up a try-catch block. In general, only catch the specific exceptions that the code needs and knows how to handle. For example, catch a specific exception if necessity dictates that the code must check for a condition that is likely to occur and handling the exception is appropriate. This can be enforced by Code Analysis rule CA1031, which is part of the Microsoft Basic Design Guideline Rules rule set.

images Practice 6-2 Do Not Catch General Exception Types

Do not swallow errors by writing a catch block that catches all exceptions or catches exceptions as a non-specific exception type, such as System.Exception. Catch only specific exceptions that the code knows how to handle. Do not catch all exceptions and rethrow the exception. Examples of code that violates this practice are illustrated in Listing 6-1.

Listing 6-1. Swallowing Exceptions: Bad Practice

// Do not swallow all exceptions
try
{
    return Calculator.ThrowsExceptions(
        principal,
        ratePerPeriod,
        termInPeriods);
}
catch
{
    // Unhelpful message
    Log.WriteError(
        "Generic message: try block did not work");
}

// Do not catch exceptions that the code does not know how to handle
try
{
    return Calculator.ThrowsExceptions(
        principal,
        ratePerPeriod,
        termInPeriods);
}
catch (SystemException exception)
{
    // Misleading message
    Log.WriteError(
        "The principal amount is invalid: '{0}'",
        exception.Message);
}

// Do not catch all exceptions and rethrow them
try
{
    return Calculator.ThrowsExceptions(
        principal,
        ratePerPeriod,
        termInPeriods);
}
catch
{
    // try-catch block serves no useful purpose
    throw;
}

For all exception types that the code expects and knows how to handle write a catch block for those specific exception types. If a general exception type is required to be caught, rethrow the exception as the last statement in the catch block. An example of properly catching general exceptions is illustrated in Listing 6-2.

Listing 6-2. Catching Exceptions: Good Practice

// Assumption: the code is required to catch and log all exceptions.
try
{
    return Calculator.ThisMethodThrowsExceptions(
        principal,
        ratePerPeriod,
        termInPeriods);
}
catch (ArgumentException exception)
{
    Log.WriteError(
        "The parameter '{0}' is invalid: {1}",
        exception.ParamName,
        exception.Message);
    throw;
}
catch (InvalidCastException exception)
{
    Log.WriteError(
        "Could not cast properly: {0}",
        exception.Message);
    throw;
}
catch (Exception exception)
{
    Log.WriteError(
        "Unexpected exception: {0}", exception.Message);
    throw;
}

images Note Although there are rare cases when swallowing exceptions is acceptable, such cases ought to be cautiously scrutinized and approved by the project's technical leaders.

Depending on the way .NET code rethrows exceptions, important stack information can be preserved or lost. It is important to understand the difference between the throw and throw exception statements.

images Practice 6-3 Rethrow to Preserve Stack Details

An example of code that catches an exception and incorrectly specifies it when rethrowing the exception is shown in Listing 6-3. This improper rethrow causes the stack trace to point to the throw exception statement, which is not where the error occurred. This can be enforced by Code Analysis rule CA2200, which is part of the Microsoft Minimum Recommended Rules rule set.

Listing 6-3. Rethrowing Exceptions: Bad Practice

try
{
    return Calculator.ThrowsExceptions(
        principal,
        ratePerPeriod,
        termInPeriods);
}
catch (ArgumentException exception)
{
    Log.WriteError(
        "The parameter '{0}' is invalid: {1}",
        exception.ParamName,
        exception.Message);

    // Original stack trace is lost
    throw exception;
}

Write an empty throw statement when catching and rethrowing an exception. This is the established way to preserve the call stack. The empty throw statement makes sure the stack trace points to the method where the exception originated to help with debugging an error. An example of properly rethrowing an exception is illustrated in Listing 6-4.

Listing 6-4. Rethrowing an Exception: Good Practice

try
{
    return Calculator.ThrowsExceptions(
        principal,
        ratePerPeriod,
        termInPeriods);
}
catch (ArgumentException exception)
{
    Log.WriteError(
        "The parameter '{0}' is invalid: {1}",
        exception.ParamName,
        exception.Message);

    throw;
}

It is important to guard a method against invalid arguments passed in from code that calls the method. When writing if-then-throw blocks that validate arguments, the code should throw the most specific exception that is sensible and appropriate.

images Practice 6-4 Instantiate Argument Exceptions Correctly

For example, do not throw the base type ArgumentException if a null argument is passed. Also, it is a bad practice not to provide the name of the parameter to the exception constructor. Both of these bad practices are shown in Listing 6-5. This can be enforced by Code Analysis rule CA2208, which is part of the Microsoft Basic Design Guideline Rules rule set.

Listing 6-5. Argument Exceptions: Bad Practice

if (principal == null)
{
    throw new ArgumentException();
}

decimal principalAmount;
if (!decimal.TryParse(principal, out principalAmount))
{
    throw new ArgumentException();
}

If null is passed for the principal argument then throw ArgumentNullException. Instead of calling the default argument exception constructor, call one of the overloads with the parameter name. Whenever it would be helpful, provide a message to the argument exception constructor that means something to the developer that calls the method. Examples of properly instantiating argument exceptions are shown in Listing 6-6.

Listing 6-6. Argument Exceptions: Good Practice

if (principal == null)
{
    throw new ArgumentNullException("principal");
}

decimal principalAmount;
if (!decimal.TryParse(principal, out principalAmount))
{
    throw new ArgumentOutOfRangeException(
        "principal",
        string.Format("Cannot convert to decimal: '{0}'", principal));
}

Disposable Pattern

Using the disposable pattern is the primary .NET mechanism for releasing native and unmanaged resources. The disposable pattern is implemented by implementing the System.IDisposable interface. It is common to declare a separate Dispose(bool) method that encapsulates all the resource cleanup logic so that the logic can be reused for both the System.IDisposable.Dispose method and a finalizer method, if applicable.

images Practice 6-5 Types That Own Native Resources or Disposable Fields Should Be Disposable

If a class owns native resources or fields that are IDisposable types then the class should implement the IDisposable interface. If the class is not IDisposable then the native resource and fields are not disposed of efficiently. To free up resources, disposable types ought to be disposed of as soon as the class no longer uses them. Listing 6-7 shows the bad practice of declaring a class that owns fields that are IDisposable types, yet the class does not implement IDisposable. This can be enforced by Code Analysis rule CA1049, which is part of the Microsoft Minimum Recommended Rules rule set.

Listing 6-7. Type That Owns Disposable Fields: Bad Practice

public class OwnsDisposableFields
{
    private DisposableResource disposableResourceOne;
    private DisposableResource disposableResourceTwo;
    // Other fields continue to be declared here.

    public void OperationThatAllocatesResources()
    {
        disposableResourceOne = new DisposableResource();
        disposableResourceTwo = new DisposableResource();
    }

    ...

}

When a .NET type owns native resources or disposable fields, that type should implement IDisposable. To release the unmanaged resources properly, call each of the field's Dispose method from within the implementation of the Dispose method. This is illustrated in Listing 6-8.

Listing 6-8. Type That Owns Disposable Fields Implements IDisposable: Good Practice

public class OwnsDisposableFields : IDisposable
{
    private DisposableResource disposableResourceOne;
    private DisposableResource disposableResourceTwo;
    // Other fields continue to be declared here.

    private bool disposed;

    ~OwnsDisposableFields()
    {
        Dispose(false);
    }

    public void OperationThatAllocatesResources()
    {
        disposableResourceOne = new DisposableResource();
        disposableResourceTwo = new DisposableResource();
    }

    // The System.IDisposable.Dispose method.
    public void Dispose()
    {
        Dispose(true);

        // Use SupressFinalize in case a subclass of this one
        // implements a finalizer.
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        // If you need thread safety, use a lock around these
        // operations, as well as in your methods that use the resource.

        // Protect from being called multiple times.
        if (disposed)
        {
            return;
        }

        if (disposing)
        {
            // Clean up all managed resources
            if (this.disposableResourceOne != null)
            {
                // Release managed resources
                this.disposableResourceOne.Dispose();

                // Free the unmanaged resource
                this.disposableResourceOne = null;
            }

            if (this.disposableResourceTwo != null)
            {
                // Release managed resources
                this.disposableResourceTwo.Dispose();

                // Free the unmanaged resource
                this.disposableResourceTwo = null;
            }
        }

        disposed = true;
    }
}

images Practice 6-6 Dispose an Object Before the Object Loses Scope

It is a bad practice to instantiate a local object of an IDisposable type but not write code to ensure that it is disposed before the object is out of scope. Code that does not dispose an IDisposable object before the object loses scope is shown in Listing 6-9. Whenever possible the object should be explicitly disposed before the object loses scope. This issue can be identified by Code Analysis rule CA2000, which is part of the Microsoft Minimum Recommended Rules rule set.

Listing 6-9. Code That Does Not Dispose an Object Before the Object Loses Scope: Bad Practice

var tempFilename = Path.GetTempFileName();

var filestream = File.OpenWrite(tempFilename);

var letters = Encoding.ASCII.GetBytes("Text to write.");
foreach (var letter in letters)
{
    filestream.WriteByte(letter);
}

return tempFilename;

The using statement allows the code to declare when objects that implement IDisposable should be properly disposed. The using block wraps the object's scope so as to automatically call the Dispose method after the close of the block to ensure that the object is disposed once it loses scope. This is illustrated in Listing 6-10.

Listing 6-10. The Using Block Ensures That the Object Is Disposed Once It Loses Scope: Good Practice

var tempFilename = Path.GetTempFileName();

using (var filestream = File.OpenWrite(tempFilename))
{
    var letters = Encoding.ASCII.GetBytes("Text to write.");
    foreach (var letter in letters)
    {
        filestream.WriteByte(letter);
    }
}

return tempFilename;

images Practice 6-7 Do Not Dispose Objects Multiple Times

Nested using statements can cause the Dispose method of an object to be called multiple times. An improperly implemented Dispose method may not expect to be called multiple times, and so, the Dispose method responds by throwing an exception or by behaving badly.

In the example code shown in Listing 6-11, the inner variable declared in the nested using statement has a field that contains a reference to the specializedResource object from the outer using statement. When the nested block completes, the Dispose method of inner then calls the Dispose method of the specializedResource object. Next, the outer using block completes and attempts to dispose specializedResource; this calls the object's Dispose method for a second time. Multiple calls to the Dispose method are not always handled properly and should be avoided. This issue can be identified by Code Analysis rule CA2202, which is part of the Microsoft Minimum Recommended Rules rule set.

Listing 6-11. Code That Calls the Dispose Method Twice: Bad Practice

using (var specializedResource = new SpecializedResource())
{
    specializedResource.Initialize();

    using (var inner = new OperatesOnSpecializedResource(specializedResource))
    {
        inner.DoSomething();
    }
}

To prevent the Dispose method from being called twice, write code that uses a try-finally block instead of the outer using statement, as shown in Listing 6-12. Once the inner variable is declared, the specializedResource variable is set to null to ensure that the Dispose method is not called on that variable. The finally block contains a guard that makes sure the specializedResource variable is not null before the code calls the Dispose method. This ensures that the Dispose method is called if an exception occurs before inner can take responsibility for the specializedResource object's disposal.

Listing 6-12. Code That Prevents Dispose from Being Called Twice: Good Practice

SpecializedResource specializedResource = null;
try
{
    specializedResource = new SpecializedResource();

    specializedResource.Initialize();

    using (var inner = new OperatesOnSpecializedResource(specializedResource))
    {
        // OperatesOnSpecializedResource takes responsibility
        // for SpecializedResource disposal.
        specializedResource = null;

        inner.DoSomething();
    }
}
finally
{
    if (specializedResource != null)
    {
        specializedResource.Dispose();
    }
}

Miscellaneous

There are many more practices covering more topics than the ones already covered. This section brings together a miscellaneous set of practices covering a range of topics.

Calling Virtual Methods in Constructors

If an abstract or virtual method is called in the constructor of a base class then the overridden method, which is implemented in the derived class, is called before the derived class's constructor is run. This may be unexpected and improper behavior that can be very difficult to debug. An example of calling a virtual method in the constructor is shown in Listing 6-13. This issue can be identified by Code Analysis rule CA2214, which is part of the Microsoft Minimum Recommended Rules rule set.

Listing 6-13. Constructor Calling a Virtual Method: Bad Practice

public class BaseClass
{
    public DateTime CurrentTime { get; protected set; }

    protected BaseClass()
    {
        this.InitializationSteps();
    }

    protected virtual void InitializationSteps()
    {
        // Base initialization code
    }
}

public class DerivedClass : BaseClass
{
    public DerivedClass()
    {
        this.CurrentTime = DateTime.Now;
    }

    protected override void InitializationSteps()
    {
        // Relies on CurrentTime having been set,
        // but the DerivedClass constructor is not called
        // until after the BaseClass constructor is done.
    }
}

images Practice 6-8 A Constructor Should Not Call Virtual Methods

A better implementation moves the call to the virtual method out of the base class constructor and into a separate Initialize method in the base class. This is shown in Listing 6-14.

Listing 6-14. Move Virtual Method Calls out of the Base Class Constructor: Good Practice

public class BaseClass
{
    public DateTime CurrentTime { get; protected set; }

    public bool Initialized { get; private set; }

    protected BaseClass()
    {
    }

    public void Initialize()
    {
        this.InitializationSteps();
        this.Initialized = true;
    }

    protected virtual void InitializationSteps()
    {
        // Base initialization code
    }
}

public class DerivedClass : BaseClass
{
    public DerivedClass()
    {
        this.CurrentTime = DateTime.Now;
    }

    protected override void InitializationSteps()
    {
        // The CurrentTime has been set
        // before the Initialize method is called.
    }
}
Defining a Zero-Value enum Element

The default value of an uninitialized enumeration is zero. Often the code logic is assuming that the enumeration value was explicitly set when it is actually an uninitialized value. For example, the caller of a class did not set the enumeration value of a property, but the method's code cannot make that determination because the zero-value in the enumeration is valid. In the enumeration declaration shown in Listing 6-15 the Beginning enumeration element has an implicit value of zero, which is the same as an uninitialized enumeration, and this coincidence may cause unexpected or erroneous behavior.

Listing 6-15. First Enumeration Element Is Inadvertently Set to Zero: Bad Practice

public enum WorkflowStep
{
    Beginning,
    Middle,
    End,
}

images Practice 6-9 Consider Explicitly Defining the Zero-Value Element of an Enumeration

A better practice explicitly defines the zero-value enumeration element, whenever appropriate. This allows methods to write code to guard against uninitialized enumeration values. It also makes it easier to understand the value when debugging the code. This approach is shown in Listing 6-16.

Listing 6-16. Enumeration Zero-Value Is Explicitly Set to ‘Unknown’: Good Practice

public enum WorkflowStep
{
    Unknown = 0,
    Beginning,
    Middle,
    End,
}
Returning an Empty Instance

Often the caller of a method or property does not perform a null check on the object that is returned. If your code returns a string or enumeration then it is likely the caller will not check for null before using the object.

images Practice 6-10 Prefer Returning an Empty Instance over Null

The better practice is to return an empty instance, such as string.Empty, whenever the empty instance is semantically equivalent to returning null. This is a good defensive-programming measure when there is a high likelihood that the caller will not perform a check for null before using the return value. This practice is also appropriate for methods and properties that return arrays, collections and many other enumerable types, where an empty instance is appropriate.

Publicly Visible static readonly Fields

In .NET the constant field is designed to be used for values that are never intended to change. Public constant field values ought to be invariable numbers, like the number of days in a week. The const keyword is for permanent, unchanging, and fixed values. Because of their permanence and for the sake of efficiency, when a client assembly uses these const fields the value is copied into the other assembly. As a result, a change to the const field's value requires all assemblies that use the field to be recompiled to receive the updated value. Take a look at the code in Listing 6-17. The const field MaximumLoanAmount is the maximum loan amount for the Calculator class. If another assembly references the value of 17,500 then that value, not a reference to the field, is provided to the client assembly. And so, if the MaximumLoanAmount is changed to 18,000 then the client assembly still has the value of 17,500 until the client assembly's source code is recompiled.

Listing 6-17. Publicly Visible Constant Fields That Are Changeable: Bad Practice

public static class Calculator
{
    public const int MaximumLoanAmount = 17500;

...

}

images Practice 6-11 Avoid a Publicly Visible Constant Field

A better practice is to define a publicly visible field as static readonly. Declaring the field in this way allows the value to change without requiring client assemblies to recompile to receive the updated value. This makes sense for the MaximumLoanAmount value because that amount is changeable. This better approach is shown in Listing 6-18.

Listing 6-18. Changeable Constant Values Declared As Static Readonly Fields: Good Practice

public static class Calculator
{
    public static readonly int MaximumLoanAmount = 17500;

...

}

Code Smells

While reviewing source code, there are times when you sense something is not quite right. Perhaps the code is not clear. Perhaps the design seems overly complicated. You are not certain, but you have a distinct feeling that the code is suspect. Kent Beck coined the term code smell to describe when source code is gives you the inkling that there is a problem that can and should be improved.3 The idea is to use that awareness to investigate further, understand the design, and suggest improvements, if appropriate.

images

images

__________

Comments

Comments should only be written to explain or clarify the source code. There are good reasons to write comments. The comments might provide background information. The comments state important underpinnings, rationales and limitations. However, too often comments are written to counteract the odor of poorly written code. When the source code is well written, redundant comments are unnecessary because the source code is clear and easy to understand. In extreme cases, comments complicate the code because the comments are no longer true or are misleading. Comments that do not add value, misrepresent the code, or are superfluous need to be removed from the source code.

images Practice 6-12 Sort Out Comments That Do Not Add Value or Complicate the Code

An all too common code smell occurs when comments are written as a substitute for helpful variable names. These comments are usually seen near the variable declaration. The variable is declared with a vague or cryptic name and there's a comment that describes the variable, as shown in Listing 6-19.

Listing 6-19. Comments Substituting for Helpful Variable Names: Bad Practice

    string p; // principal as string
    decimal r; // rate per month
    int t; // term in months

A variable's name should speak for itself. The variable name must indicate what the variable stands for. The variable name must be clear, correct, and consistent. For example, if the variable is a string representation of a principal amount then the fact that it is not a decimal ought to be clear from the variable name. Better variable names are shown in Listing 6-20.

Listing 6-20. Clear Variable Names Instead of Comments: Good Practice

    string principalAsString;
    decimal ratePerMonth;
    int termInMonths;

A dilemma occurs when comments are wrong or misleading. Believe the code or believe the comment. An experienced developer who maintains source code with untrustworthy comments learns to ignore the misleading or wrong comments. Other developers perform double duty by maintaining both the code and the comments. The source code becomes littered with comments that are unhelpful or only serve to complicate things. In Listing 6-21 the comments directly contradict the code, which leaves a bad smell for future developers.

Listing 6-21. The Comments Are Wrong or Misleading: Bad Practice

    // Round the rate per month to 4 significant digits
    // by rounding toward the nearest even number
    return Math.Round(ratePerMonth, 6, MidpointRounding.AwayFromZero);

Comments can be important reminders or messages to other developers. They ought to speak to intention and justification. Let the code speak for itself. Better comments clarify an important principle or rationale that endures over time, as shown in Listing 6-22.

Listing 6-22. Comment to Clarify Rationale or Justification: Good Practice

    // The rate needs to be properly rounded
    return Math.Round(ratePerMonth, 6, MidpointRounding.AwayFromZero);

Sometimes comments are added to describe an entire block of code within a method. The developer feels that it is important to explain the significance of this section of code. In fact, the comments are describing the need to extract the code into a new method or use an existing method. In Listing 6-23 the comment clearly describes where an algorithm begins and ends: however, this code really belongs in a separate method.

Listing 6-23. Comments That Describe a Missing Method: Bad Practice

...
    // Begin: compute the rate per month from the APR
    decimal ratePerMonth;

    if (annualPercentageRate < 0.01m ||
        annualPercentageRate >= 20.0m)
    {
        throw new InvalidOperationException(string.Format(
            "AnnualPercentageRate {0}% is not valid",
            annualPercentageRate));
    }

    ratePerMonth = (annualPercentageRate / 100m) / MonthsPerYear;

    ratePerMonth = Math.Round(ratePerMonth, 6, MidpointRounding.AwayFromZero);
    // End
...

Comments that are really clues to missing methods are examples of where the code is not speaking for itself. By extracting the code into a separate method the complexity of the code is reduced. In Listing 6-24 the commented section of code is extracted to the new ComputeRatePerMonth method. With a properly named method the comments are unnecessary.

Listing 6-24. Extract a Method to Uncomplicate Code: Good Practice

...
    ratePerMonth = ComputeRatePerMonth(annualPercentageRate);
...

private static decimal ComputeRatePerMonth(
    decimal annualPercentageRate)
{
    if (annualPercentageRate < 0.01m ||
        annualPercentageRate >= 20.0m)
    {
        throw new InvalidOperationException(string.Format(
            "AnnualPercentageRate {0}% is not valid",
            annualPercentageRate));
    }

    var ratePerMonth = (annualPercentageRate / 100m) / MonthsPerYear;

    // The rate needs to be properly rounded
    return Math.Round(ratePerMonth, 6, MidpointRounding.AwayFromZero);
}

There is one last point to make about comments. A lot of source code files end up with lines and sections of commented-out code. In extreme cases, entire classes or methods that are no longer needed are commented out. It seems that many of us have a hard time letting go of the code we write. However, leaving code commented out does a real disservice to the developers that follow. The best practice is to delete the unused code and let the version control system track the changes. Some developers like a two-phase approach. First, comment out the code and check in the changes. Second, delete the commented-out code and check in the removal as a separate change set. Whatever the approach, the end result ought to be that source code files do not have a lot of commented-out code.

Way Too Complicated

It is all too common for source code to become overly complex and downright complicated. Rarely is it helpful to go back and figure out why the code turned out the way it did. A much more useful endeavor is to regularly and consistently reduce the complexity and improve the situation. The discipline of refactoring is a method for improving the design of existing source code. A comprehensive discussion of refactoring includes both the proper diagnoses and an effective prescription. This section focuses on symptoms of code that is way too complicated and would probably benefit from refactoring.

Some common symptoms of overly complicated source code include the following:

  • Long Method: A method that is hard to read and understand because it is too long.
  • Long Parameter List: A method that has so many parameters that it is too complicated.
  • Conditional Complexity: A method that has so much conditional branching and logic that it is difficult to follow.
  • Large Class: A class that is hard to read and understand because it is so large.
  • Speculative Generality: Code that is hard to read and understand because it has functionality based on imagined requirements or a perceived need for future extensibility.

From these and many other symptoms comes the motivation to improve the code's readability and maintainability. The art and practice of refactoring encompasses an entire book unto itself. Under Appendix A you will find resources for the effective application of refactoring. The important point here is that source code must not become overly complex. Establish rules and regulations appropriate to you, your team, and your organization to prevent code from becoming way too complicated.

Unused, Unreachable, and Dead Code

Enhancing or extending software that contains many lines of unused, unreachable, or dead code can get tricky. Sometimes the code defines private methods that are never called. Code is written within conditional branches that can never be reached. Other times there are intermediate values that are calculated but are never used. How does this unnecessary code get in there? The developer who writes the code may not even realize that the code is not used. Also, during maintenance a change in requirements can cause the code to become superfluous.

The problem with unused, unreachable, and dead code is that the code is a diversion and a distraction. At some point, a developer reads the code to understand the purpose and rationale only to conclude that it is unimportant. That wastes time and mental energy. Developers might lose their train of thought or get confused by the unnecessary code. This reduces their productivity. Whether it is a 10% or 1% effect, the effect is still there. Establish rules and regulations to find and eliminate the unneeded code to keep the source code clean and clear.

Summary

In this chapter you learned about the sources of .NET standards and guidelines and how to integrate them to establish .NET rules and regulations that are right for your situation. You also learned about the importance of code smells and how better .NET practices are established to avoid bad code smells.

In the next chapter, you will learn practices that relate to the C# language and its power.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.218.93.169