Chapter 8. Managed Code Analysis and Code Metrics

WHAT'S IN THIS CHAPTER?

  • Understanding why code analysis is important

  • Enabling and running code analysis in Visual Studio 2010

  • Correcting code analysis rule violations

  • Creating a custom code analysis rule

  • Understanding code metrics and how to use them

This chapter describes the code analysis and code metric features included with Visual Studio 2010 Premium and Visual Studio 2010 Ultimate. These tools can quickly and easily inspect your code to find common mistakes, make suggestions for improvements, and even indicate violations of standards.

The discussion begins by examining the origins of the Static Code Analysis tool. You'll learn about Microsoft's .NET "Design Guidelines for Class Library Developers" and how it is related to the tools.

Then you will learn a bit about the tool itself and how to take advantage of its full integration with Visual Studio 2010. This includes enabling static code analysis review for your projects, selecting rules to apply, and working with the results of the analysis. Static code analysis is also available for database projects, but that particular use of static code analysis will not be covered in this chapter. For more information on using static code analysis with database projects, see Chapter 10.

However, using the IDE is not always an option, and sometimes you need additional flexibility. The Static Code Analysis tool is available to you from the command line. You will learn how to use the command line for code analysis and how to include code analysis with your automated builds.

The Static Code Analysis rules that ship with Visual Studio will probably not be sufficient for the specific standards and practices of your own projects. To address this, you will learn how you can create and integrate new custom rules. You will learn about the mechanics of rules and the new Introspection engine. You will then create an example rule using introspection and call it from the Visual Studio IDE.

This chapter wraps up with a look at Code Metrics, a tool in Visual Studio that can provide insight into how well-written your code is. Each code metric is examined in detail, and you learn how to understand what code metrics are trying to tell you.

THE NEED FOR ANALYSIS TOOLS

Ensuring that developers follow best practices and write consistent code is a major challenge in today's software development projects. The act of documenting standards and practices is often skipped or overlooked. However, even in projects for which standards have been established, getting developers to read and follow those practices is another major challenge.

One of the best resources available for .NET developers is Microsoft's .NET Framework "Design Guidelines for Class Library Developers" (Design Guidelines). These guidelines document Microsoft's (formerly) internal practices for developing class libraries, and are freely available at http://msdn.microsoft.com/library/en-us/cpgenref/html/cpconnetframeworkdesignguidelines.asp.

The guidelines cover a wide range of subjects, including naming conventions, usage guidelines, and performance and security considerations. When put into practice, they help ensure that your approach will be consistent with that of other developers. In addition, they have evolved over a number of years to reflect a considerable amount of knowledge, best practices, and lessons learned.

As useful as the design guidelines are, the reality of software creation is that many developers will not be familiar with their contents. Most times, this is not a fault of the developer, but rather the process that the developer must follow. For some companies, design guidelines are not as important as simply getting the project finished, regardless of the long-term benefit that following those guidelines will have. The desire to automate the process of evaluating code for compliance with these guidelines led to the creation of FxCop.

USING MANAGED CODE ANALYSIS

FxCop is a tool used to analyze managed code against a library of rules. You can create rules for almost any purpose — naming conventions, security, attribute usage, and so on. FxCop contains nearly 200 rules, based on the .NET Framework Design Guidelines described earlier.

FxCop has been available from Microsoft on the GotDotNet.com site for several years. Before that, it had been used internally at Microsoft for analysis of its own frameworks to help ensure predictable and consistent interfaces. Previous versions of FxCop have been standalone applications, separated from the Visual Studio IDE.

With Visual Studio Team System 2005, FxCop was rebranded as the Managed Code Analysis tool, and fully integrated with the IDE, enabling analysis to be performed with a simple build of your application. The FxCop heritage of Managed Code Analysis is generally hidden when you're using the IDE. But, as you'll see, the FxCop name still appears when creating new rules and using command-line options.

An example project will be presented throughout this chapter. To begin the project, create a new C# Class Library project and name it SampleLibrary. Rename the Class1.cs file to PayCalculator.cs, and insert the following code, which, as you'll soon see, fails to meet several code analysis guidelines:

using System;

namespace SampleLibrary
{
    public class PayCalculator
    {
        public enum Pay_Level
        {
            EntryLevel = 20,
            Normal = 35,
            Senior = 50
        }

        public static int MaximumHours;
        public const double BONUS = 0.10;

        static PayCalculator()
        {
            MaximumHours = 100;
        }

        public static double ComputePayment(int hours, Pay_Level level)
        {
            if (hours > MaximumHours)
            {
                throw new ArgumentOutOfRangeException("Employee works too much");
            }

            return ((int)level * hours);
        }
    }
}
                                       
USING MANAGED CODE ANALYSIS

While this code will compile and run as expected, you can make several improvements to it, and the Code Analysis tool will help you find them. These improvements will help make your code easier to understand, and possibly catch potential run-time errors (such as buffer overflows).

Built-in Managed Code Analysis Rules

As mentioned earlier, Visual Studio ships with nearly 200 rules for Managed Code Analysis, each of which helps to enforce the practices documented in the .NET Framework Design Guidelines, as well as other practices recommended by Microsoft. This section briefly describes each of the 11 rule groups to help you understand when you might apply them to your projects.

Table 8-1 describes the groups of rules included with Visual Studio 2010.

Table 8.1. Groups of Rules

RULE GROUP (NUMBER OF RULES)

DESCRIPTION

Design (62)

Typically focused on the interfaces and structure of code, this group enforces proper implementation of common concepts such as classes, events, collections, namespaces, and parameters. These rules revolve around the Microsoft .NET Framework Design Guidelines (http://msdn.microsoft.com/en-us/library/czefa0ke(VS.71).aspx).

Globalization (11)

This group includes practices to support the internationalization of code. This can include avoiding strings of literal text, correct use of CultureInfo, and formatting.

Interoperability (17)

This group is focused on the correct use of COM Interop. Included are rules for proper use of PInvoke, the ComVisible attribute, and marshalling.

Maintainability (6)

These are rules to help make your code easier to maintain. This group identifies potential problems such as complexity and overuse of inheritance.

Mobility (2)

These are rules to help detect code that will not run effectively in mobile or disconnected environments.

Naming (24)

This group enforces naming standards as described in the Design Guidelines. Using these rules verifies that names of items such as assemblies, classes, members, and variables conform to standards. Some rules will even help to detect misspellings in your assigned names.

Performance (17)

These rules help to detect places in your code that may be optimized for performance. They detect a wide variety of wasteful or extraneous code.

Portability (3)

These are rules to find code that might not be easily portable between operating environments.

Reliability (6)

The rules in this group will help to detect problems with your code that may lead to intermittent failures, including failure to dispose of objects, improper use of the garbage collector, bad threading use, and more. These rules can be extremely useful, because intermittent errors are frequently the most difficult to identify and correct.

Security (26)

These rules help to identify insufficient or incorrect security practices. Rules exist to find missing attributes, improper use of permissions, and opportunities for SQL injection attacks.

Usage (42)

These rules cover a broad spectrum of recommended practices. Whereas the design group rules typically involve API structure, these rules govern the methodologies of code. Practices include proper exception management, handling of arithmetic overflow, serialization, and inheritance.

Of course, the rules that ship with Visual Studio are only a starting point. Microsoft and others will certainly make additional rules available, and you can add your own custom rules and rule groups as well. You'll learn how to create custom Managed Code Analysis rules later in this chapter.

Code Analysis Rule Sets

Visual Studio 2010 has introduced a new concept revolving around code analysis called code analysis rule sets. In previous editions, you had to spend time looking through all the code analysis rules and deciding which rules to use and which ones not to use. With Visual Studio 2010, code analysis rules can now be grouped into rule sets, making it easy for everyone to get started using code analysis. The code analysis rules that ship by default are already grouped into specified rule sets, but you have the capability to create your own custom rule sets as needed.

Table 8-2 shows the rule sets included with Visual Studio 2010.

Table 8.2. Rule Sets

RULE SET

DESCRIPTION

Microsoft All Rules

This rule set contains all code analysis rules.

Microsoft Basic Correctness Rules

This rule set focuses on logic errors and common mistakes made when using the .NET framework APIs.

Microsoft Basic Design Guideline Rules

This rule set focuses on enforcing best practices to make code easy to understand and use.

Microsoft Extended Correctness Rules

This rule set expands on the basic correctness rules to maximize the reported logic and framework usage errors.

Microsoft Extended Design Guideline Rules

This rule set expands on the basic design guideline rules to maximize the number of reported usability and maintainability issues.

Microsoft Globalization Rules

This rule set focuses on problems that may occur if your application has not been properly localized.

Microsoft Minimum Recommended Rules

This rule set focuses on the most critical problems in your code, including security holes and application crashes. This is the default rule set applied to newly created projects.

Microsoft Security Rules

This rule set contains all Microsoft security rules.

To create a new rule set, in Visual Studio, select File

Rule Sets

Enabling Managed Code Analysis

By default, code analysis is disabled for projects in Visual Studio. To enable analysis, open your project's Properties window and select Code Analysis from the left-hand side tabs. You will see a drop-down of the different rule sets available for use with code analysis. You will no longer see a collapsed list of rules as with previous versions, but instead you will see the new menu showing the selected rule set, as shown in Figure 8-1.

FIGURE 8-1

Figure 8.1. FIGURE 8-1

Warning

To enable and configure Code Analysis for ASP.NET applications, from the main menu in Visual Studio, select Website

FIGURE 8-1

To enable code analysis upon build, check the box labeled "Enable Code Analysis on Build." Select the desired rule set in the drop-down list box, or choose multiple rule sets. Save your settings via Save Selected Items on the File menu, or by pressing Ctrl+S.

To view the rules contained in an individual rule set, select the rule set in the drop-down list box, then click the Open button. This opens the individual rules that comprise that rule set. Rules or entire groups of rules can be disabled by unchecking their boxes.

In addition, each rule in a rule set can be set to one of the following:

  • Warning (the default) — Warnings serve as an advisory that something may need to be corrected, but they will not prevent the project's build from succeeding.

  • Error — You may want to set certain rules or groups of rules to Error if they are critically important, thus preventing a build when those rules are violated.

  • Inherit — Inherit means this rule will use the same indicator that the group it is contained in uses.

  • None — This means no setting.

Use the drop-down in the Action column to choose among Warning, Error, None, or Inherit. As with enabling rules, this can be done for specific rules or for entire groups of rules.

Figure 8-2 illustrates how to enable and disable specific rules and how each can be set to Warning or Error as necessary.

FIGURE 8-2

Figure 8.2. FIGURE 8-2

Finally, you can specify different sets of code analysis properties for each configuration. By default, settings apply to the Active build configuration, but you can be more specific. For example, you may wish to treat certain critical rules as Errors in your Release builds, but as Warnings in Debug. You might instead decide to disable code analysis entirely for your Release builds. Simply choose a build type from the Configuration drop-down menu, and then review your settings. To make changes affecting all build configurations, select the All Configurations option, and then modify and save your settings.

Executing Static Code Analysis

Once you have enabled code analysis and configured the rules to reflect your development standards, code analysis will be performed each time you build your project. Go ahead and build your sample project now.

Note

You can also execute code analysis on your project by choosing Build

Executing Static Code Analysis

The output window will include details about your build, including results from calling code analysis. After the build, the Error List window may appear, displaying a number of warnings and possibly some errors. The Error List does not automatically open if there are only warnings. If you do not see the Error List, choose View

Executing Static Code Analysis

By default, the Microsoft Minimum Recommended Rules rule set is selected, and, thus, no warnings are generated. For the purpose of this example, return to the rule set selection and choose the Microsoft All Rules rule set.

Figure 8-3 shows the Error List displaying code analysis results for the SampleLibrary assembly.

FIGURE 8-3

Figure 8.3. FIGURE 8-3

Analysis of the SampleLibrary code indicates ten potential rule violations. Each item in the list has a full description indicating how your code is in violation of a rule. The Error List has File and Line columns that indicate (when appropriate) specific source files and code related to each warning. Some warnings do not relate to specific code, but perhaps to a lack of an attribute or security setting. In such cases, there will be no value in the File column. Others may refer directly to problem code, perhaps naming violations or performance issues. You can double-click on the warning, and the code editor will switch to the related code.

Each time you run code analysis, the results are stored in an XML file. This file is named <Project Name>.CodeAnalysisLog.xml, and is located in your project's build output directory (that is, inDebug or inRelease). For the SampleLibrary project, the file will be SampleLibrary.dll.CodeAnalysisLog.xml.

If you open the file from within the IDE, you will see the raw, unformatted XML. However, the XML has an associated XSL template that formats the data into HTML, similar to what is shown in Figure 8-4.

FIGURE 8-4

Figure 8.4. FIGURE 8-4

To see this view, open the XML file with Internet Explorer. To customize rendering, you can supply your own XSL templates. If you choose to do this, you should make a copy of the included template and modify the copy to suit your needs. The base template is found in your Visual Studio installation directory as Team ToolsStatic Analysis ToolsFxCopXmlCodeAnalysisReport.xsl.

Working with Rule Violations

Several issues should be addressed in the sample PayCalculator class. For each warning or error, you must determine whether the rule actually applies to your project or a specific section of code. If it does, you must modify the project to address the issue; otherwise, you may choose to ignore the rule. This section describes how to act on identified issues, and how to ignore, or suppress, a given rule.

Although, as part of this discussion, you will immediately go into the code and make corrections as necessary, your organization or project may require the use of work items to track any changes. Or perhaps you don't have time to immediately address an identified problem but would like to use a work item as a reminder.

Fortunately, you can easily create work items directly from Code Analysis rule violations. Simply right-click on the warning or error and choose Create Work Item from the menu. Choose the correct team project, and you will be shown the New Work Item dialog. Make any necessary changes and save your new work item.

Correcting Problems

Looking through the Error List shown in Figure 8-3, you should see item CA1810, with a description of "Initialize all static fields in 'PayCalculator' when those fields are declared and remove the explicit static constructor."

Right-click on this warning and choose Show Error Help. This will display the documentation for the rule that triggered this warning, including suggestions for resolving the issue. You are currently assigning the value of 100 to MaximumHours inside the static constructor of PayCalculator. The rule's Help text states that your code may perform more efficiently if you make that assignment when the variable is defined.

To address this issue, double-click on this warning and you'll be brought to the static constructor of the PayCalculator class. Change the code to assign the value in the declaration as follows:

public static int MaximumHours = 100;

Next, delete the static PayCalculator constructor entirely. Build the project and look at the Error List window. The specific warning should no longer be in the list.

There is another easy problem to correct. Many of the code analysis rules relate to standard naming conventions. Find the warning "Remove the underscores from type name 'PayCalculator.Pay_Level'" and double-click. The rule helps to enforce the naming convention that underscores should not be used in type names. Use the built-in refactoring support to rename it. Right-click on the Pay_Level enumeration and choose Refactor

Correcting Problems

Mark the PayCaclulator class definition static as follows:

public static class PayCalculator

Rules can also help ensure that you're using the Framework correctly. You can see from the following warning that the rule has detected that you might not be creating the ArgumentOutOfRangeException correctly; "Method 'PayCalculator.ComputePayment(int, PayCalculator.Pay_Level)' passes 'Employee works too much' as the 'paramName' argument to a 'ArgumentOutOfRangeException' constructor. Replace this argument with one of the method's parameter names. Note that the provided parameter name should have the exact casing as declared on the method." To fix this, change the line that throws the exception to the following:

if (hours > MaximumHours)
    {

     throw new ArgumentOutOfRangeException("hours", "Employee works too much");
    }

One of the remaining warnings, "Mark 'SampleLibrary.dll' with CLSCompliantAttribute(true) because it exposes externally visible types," is a fairly common suggestion. Consider addressing this when creating a reusable library assembly that might be consumed by code of more than one .NET language. Common Language Specification (CLS) compliance specifies that your assembly must meet the common structure and syntax supported by all .NET languages as defined in the CLS. Keep in mind that there may be times when CLS compliance is not possible, such as when exposing unsigned types.

To address this warning, open AssemblyInfo.cs and add the following line:

[assembly: System.CLSCompliant(true)]

The assembly: notation is used because the attribute applies to the entire assembly, and not to a specific class or member. Other assembly-level attributes can be found in the AssemblyInfo.cs file.

Now, build the project. The violations you corrected should no longer generate messages in the Error List. The remaining five warnings will be addressed shortly.

Suppressing Messages

Visual Studio 2010 ships with many rules, and not all of them are appropriate for every project. There is a chance that some rules will trigger warnings that simply don't apply to certain parts of your project. To prevent these irrelevant messages from recurring, right-click on the rule violation and choose Suppress Message(s).

When you suppress a message, Visual Studio automatically adds an attribute to your code to indicate that a rule should not apply. The SuppressMessage attribute can be applied to a code construct, such as a field, method, or class, and to an entire assembly.

Warning

Suppressing a message is not the same as disabling a rule. Suppression prevents the specific violation of a rule from recurring, but other violations of the same rule will still be identified. You should disable a rule only if you're certain it could never be meaningfully applied to any part of your project.

Let's continue with the SampleLibrary example and use message suppression to clean up more of the code analysis violation messages.

The warnings for CA1709 states, "Correct the casing of 'BONUS' in member name PayCalculator.BONUS." Assume that your organization has different naming conventions for constants, and you know that this rule will not apply to this BONUS constant. Right-click on the message and choose Suppress Message

Suppressing Messages
[System.Diagnostics.CodeAnalysis.SuppressMessage(
    "Microsoft.Naming",
    "CA1709: IdentifiersShouldBeCasedCorrectly",    MessageId = "BONUS")]

The next time Code Analysis is run, the engine will recognize this attribute. Moreover, even when the CA1709 rule is violated at this point, no message will be created. Messages for any other violations of this rule elsewhere in the code will still be reported as normal.

Note

In many cases (especially in ASP.NET applications), Visual Studio will automatically generate helper or wrapper code. Previous versions of FxCop had difficulty working with such generated code and often flagged many warnings — for example, naming convention violations, which would have to be investigated and generally excluded. Fortunately, the .NET Framework 2.0 provided a new attribute, GeneratedCodeAttribute, that the Managed Code Analysis tool uses to identify code that it does not need to analyze.

Two more messages don't apply to the project. "Consider making 'PaymentCalculator.MaximumHours' non-public or a constant" reminds you that external users of the class could change its value. This is the behavior you want, so right-click on the message and choose Suppress Message

Suppressing Messages

As you can see, suppressing messages can quickly add a number of attributes to your code. If you find that you always suppress a given message, it is probably better to exclude the rule altogether; then your code will not require the additional SuppressMessage attributes. However, as noted previously, use caution when doing this, because you could unintentionally be missing valid violations that should be addressed.

The warning "Sign 'SampleLibrary.dll' with a strong name key" applies to the overall assembly. If you know that you'll never use this assembly in the Global Assembly Cache (GAC), and will have no other need for strong names, you can suppress this message. Right-click the warning and select Suppress Message

Suppressing Messages
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage(
    "Microsoft.Design",
    "CA2210:AssembliesShouldHaveValidStrongNames")]

There is one warning left: "CA 1034: Microsoft.Design: Do not nest type 'PayCalculator.Pay_level'. Alternatively, change its accessibility so that it is not externally visible." Suppress this warning message, build the project, and you should now see an empty Error List. This indicates all enabled Code Analysis rules have either been passed or suppressed.

Note

The effect of assembly-level suppression is basically the same as if you had excluded the rule altogether. The advantage of the attribute-based approach is that it is easy to see which rules have been suppressed project-wide by viewing the GlobalSuppressions.cs file. In addition, you could add comments to that file to indicate the reason for suppressing the rule to other developers. Excluding a rule by not selecting it in the Code Analysis section of the project's properties has the same effect but does not offer a way to document why certain exclusions were made.

USING THE COMMAND-LINE ANALYSIS TOOL

Like the versions of FxCop that preceded Visual Studio 2010, a command-line interface is available for static code analysis. This tool, called FxCopCmd.exe, can be found in your Visual Studio 2010 installation directory under Team ToolsStatic Analysis ToolsFxCop.

FxCopCmd can perform any of the code analysis functions that are available to you in the Visual Studio IDE. In fact, the IDE uses FxCopCmd under the covers to execute analysis and generate reports.

FxCopCmd Options

Table 8-3 shows some of the options that FxCopCmd.exe supports.

Table 8.3. FxCopCmd Options

OPTION

DESCRIPTION

/f[ile]: <directory/file>

Assembly file(s) or directory(ies) to analyze. If a directory is used without a filename, Code Analysis will try to analyze all files in that directory with .dll or .exe extensions. You can specify this option more than once. It is required, unless you specify a project file with the /project option.

/r[ule]:<directory/file>

A rule assembly file or a directory to browse for rule assemblies. If a directory without a filename is supplied, Code Analysis will look for rules in any files with a .dll extension. You can specify this option more than once.

/r[ule]id:<[+|-]

Enables or disables a specific rule, supplying its Category and Category#CheckId> CheckId values — for example, /rid: +!Microsoft.Usage#CA2225.

/ruleset:<<+|-|=>file>

Specifies the rule set to be used for the analysis.

/rulesetdirectory:<directory>

Specifies a directory to search for rule set files specified by the /ruleset switch.

/o[ut]:<file>

Names a file in which the results of the analysis will be stored in XML form. Required, unless the /console option is used.

/p[roject]:<file>

Loads a project file that contains the settings for FxCopCmd to use (discussed shortly). Required if you do not use both the /file and /rules options.

/t[ypes]:<type list>

Used to constrain analysis to only the specified type(s). Supply a list of comma-delimited type names. Wildcards can be used to specify multiple types. (Optional)

/i[mport]:<directory/file>

Loads analysis reports or project files to exclude items from the current test that appear as excluded in the imported file. You may specify a file or a directory. If a directory is specified, Code Analysis will attempt to load all files with an .xml extension. (Optional)

/s[ummary]

Displays a summary after analysis. (Optional)

/v[erbose]

Gives more detailed status output. (Optional)

/q[uiet]

Suppresses output of status details. (Optional)

/u[pdate]

Saves the results of the current analysis to the specified project file. Ignored if you do not supply the /project option. (Optional)

/c[onsole]

Uses the console to display the analysis results. This is required unless you have specified the /out option.

/c[onsole]xsl:<file>

Applies an XSL file to transform XML output before displaying.

/plat[form]:<directory>

Location of platform assemblies. (Optional)

/d[irectory]: <directory>

Location to search for assembly dependencies. (Optional)

/help (or) /?

Help about command-line options.

/fo[rceoutput]

Write output XML and project files, even in the case where no violations occurred.

/dic[tionary]:<file>

Use a custom dictionary file.

/ignoreinvalidtargets [Short form: /iit]

Silently ignore invalid target files.

/asp[net]

Analyze only ASP.NET generated binaries, and honor global suppressions in App_Code.dll for all assemblies under analysis.

/searchgac [Short form: /gac]

Search Global Assembly Cache for missing references.

/successfile [Short form: /sf]

Create .lastcodeanalysissucceeded file in output report directory if no build-breaking messages occur during analysis.

/timeout:<seconds> [Short form: /to:<seconds>]

Overrride timeout for analysis deadlock detection. Analysis will be aborted when analysis of a single item by a single rule exceeds the specified amount of time. Specify a value of 0 to disable deadlock detection.

/savemessagestoreport:<Active|Excluded|Absent (default: Active)> [Short form: /smr:<Active|Excluded|Absent (default: Active)>]

Save messages of specified kind to output report.

/ignoregeneratedcode [Short form: /igc]

Suppress analysis results against generated code.

/overriderulevisibilities [Short form: /orv]

Run all overridable rules against all targets.

/failonmissingrules [Short form: /fmr]

Treat missing rules or rule sets as an error, and halt execution.

/cul[ture]

Culture for spelling rules.

/outxsl:<file> [Short form: /oxsl:<file>]

Reference the specified XSL in the XML report file; use /outxsl:none to generate an XML report with no XSL style sheet.

/applyoutxsl [Short form: /axsl]

Apply the XSL style sheet to the output.

Notice that most of the commands have long and short forms available. For example, /summary and /s are equivalent. Arguments support the use of wildcards (*) to specify multiple items. Arguments with spaces in them must be surrounded with double quotes.

For example, to conduct analysis of a single assembly CustomLibrary.dll, use the following command:

FxCopCmd /f:SampleLibrary.dll /o:"FxCop Results.xml" /s

The /f (or /file) argument indicates which assembly to analyze, and the /o (or /output) option indicates that analysis output should be stored as XML in FxCop Results.xml. Finally, the /s (or /summary) option will display a short summary of the results of the analysis.

FxCopCmd Project Files

FxCopCmd's command-line options offer a good deal of flexibility, but to fine-tune your analysis, you should consider using a project file. A project file enables you to set options such as targets and rule assemblies, exclusions, and output preferences. You can then simply use the /project option to tell FxCopCmd to use those settings, instead of supplying a detailed list of arguments.

You should create a default FxCopCmd project file that you can copy and customize for each project. Create a new file named EmptyCodeAnalysisProject.fxcop and enter the following:

<?xml version="1.0" encoding="UTF-8"?>
<FxCopProject Version="1.36" Name="Temporary FxCop Project">
        <ProjectOptions>
        </ProjectOptions>
        <Targets>
        <Target Name="$(TargetFile)" Analyze="True" AnalyzeAllChildren="True" />
        </Targets>
        <RuleFiles>
        </RuleFiles>
        <FxCopReport Version="1.36" LastAnalysis="2004-04-20 22:08:53Z">
        </FxCopReport>
</FxCopProject>

Copy this to a new file and add your project's settings. The rules and files specified in your project file serve as the basis for FxCopCmd execution. Additional rules and target files can be specified on the command line with the /rules and /file options.

For example, here is a simple project file that specifies a target assembly, SampleLibrary.dll, and includes one rule assembly, the default Code Analysis naming conventions assembly:

<?xml version="1.0" encoding="UTF-8"?>
<FxCopProject Version="1.36" Name="Sample Library Code Analysis Project">
        <ProjectOptions>
        </ProjectOptions>
        <Targets>

             <Target Name="C:SampleLibraryinDebugSampleLibrary.dll"
                  Analyze="True"
AnalyzeAllChildren="True" />
        </Targets>
        <RuleFiles>
             <RuleFile Name="$(FxCopDir)RulesNamingRules.dll" Enabled="True"
             AllRulesEnabled="True" />
        </RuleFiles>
        <FxCopReport Version="1.36" LastAnalysis="2004-04-20 22:08:53Z">
        </FxCopReport>
</FxCopProject>

Save this to a file named SampleLibrary.fxcop. To execute Code Analysis for SampleLibrary using this project file, use the following command:

FxCopCmd /p:SampleLibrary.fxcop /o:"FxCop Results.xml" /s

Build Process Code Analysis Integration

You have now seen how to use FxCopCmd from the command line to analyze your code and report potential defects. However, with the full integration of code analysis with the Visual Studio IDE, why would you need to use FxCopCmd?

A common use of FxCopCmd is to enable automated code analysis from a build process. You can do this with Team Build, Visual Studio 2010's MSBuild, or one of many other build automation packages available (such as NAnt).

By integrating Code Analysis with your builds, you can ensure that your entire team's work is being evaluated against a consistent set of rules. You will quickly discover when a developer has added nonstandard code. Developers will quickly learn those rules and practices, because they don't want to be the person responsible for "breaking the build."

CREATING CODE ANALYSIS RULES

Visual Studio 2010 includes many code analysis rules, but no matter how comprehensive the rules from Microsoft are, they can never fully cover the specific requirements of your own projects. Perhaps you have specific naming conventions, or a standard way to load database connection strings. In many cases, you can create a custom code analysis rule to help diagnose the issue and help developers take corrective action.

Reflection and Introspection

Many static analysis tools use simple source-code inspection to identify issues. However, with FxCop, Microsoft decided to leverage the inherent functionality of .NET itself as the basis for creating rules. A very useful feature of .NET is called reflection. Using reflection, you can programmatically inspect other assemblies, classes, and members. You can even invoke methods or access fields, public or private, given appropriate security settings. Reflection is done without establishing a link to the target assembly at compilation time, a practice known as late binding.

Initial versions of FxCop relied on reflection as the basis for rules. However, a newer option is available, called introspection. Similar to reflection, introspection can inspect a target assembly to discover its types, and details about those types. It can also invoke members of those types. Introspection does this in a much faster manner than reflection, and supports multi-threaded operations. Furthermore, introspection does not lock the files under analysis, a problem suffered by previous versions of FxCop that needed to use reflection.

Given the clear advantages of introspection over reflection, Microsoft has leveraged introspection with the rules that are shipped with Visual Studio 2010. Let's use introspection for a custom rule.

Creating a New Rule

Creating a new rule can be challenging, so let's walk through the creation of one. Let's continue working with the SampleLibrary created earlier in this chapter. You'll recall that, when you ran code analysis on the SampleLibrary, a number of potential issues were flagged. There is actually another problem with the code that is not detected by the set of rules included with Visual Studio 2010.

In this section, you'll create a fairly simple rule to help correct a potentially serious issue. Exposing constant values from an assembly is a normal and expected practice, but with .NET, there is a surprising side effect.

When a second assembly references a source assembly that exposes a constant value, the value of that constant is actually stored directly in the intermediate language (IL) of the referencing assembly. This means that, even when you change and recompile the original assembly, the value is not changed in the referencing assembly. This can lead to extremely difficult-to-diagnose problems, and will require you to recompile all referencing assemblies, even though those assemblies have not changed.

To address this, let's create a new rule, AvoidExposingPublicConstants, which searches a target assembly for publicly visible constant values. Begin by creating a new C# Class Library project named CustomCodeAnalysisRules.

Code Analysis loads designated assemblies and searches them for rule classes. Code Analysis rules implement a core interface called IRule. Rules that use introspection also implement IIntrospectionRule. However, these and other related interfaces and classes are wrapped by the BaseIntrospectionRule class.

Let's use this class as the basis for your own rules. To use this base class, let's add a reference in the CustomCodeAnalysisRules project to the FxCopSdk.dll and Microsoft.Cci.dll assemblies found in the Team ToolsStatic Analysis ToolsFxCop directory.

Creating a Base Rule

As mentioned earlier, most of the included code analysis rules inherit (typically indirectly) from a base class called BaseIntrospectionRule. While each custom rule could inherit directly from this class, it's easier to create a common base class that inherits from BaseIntrospectionRule. This is because the constructor to BaseIntrospectionRule requires three arguments. The first argument is the name of the rule, and the second is the name of the XML file containing rule data. The final argument is a reference to the rule assembly type.

If you created a rule assembly with multiple rules, each rule would have to supply those three arguments each time. However, with a new base class, you can abstract away the last two arguments and keep your rule code streamlined.

Create a new file called BaseStaticAnalysisRule.cs and add the following code:

using System;
using Microsoft.FxCop.Sdk;

namespace CustomCodeAnalysisRules
{
    public abstract class BaseStaticAnalysisRule : BaseIntrospectionRule
    {
        protected BaseStaticAnalysisRule(string name) :
            base(name,
                 "CustomCodeAnalysisRules.Rules",
                 typeof(BaseStaticAnalysisRule).Assembly) { }
    }
}

Because the values of the second and third parameter to the BaseIntrospectionRule constructor will be the same for all rules in your assembly, you use this simple class as a wrapper in which those values can be set. The second argument, CustomCodeAnalysisRules.Rules, needs further explanation and is described in detail later in this chapter in the section "Creating Rules.xml."

Implementing the Rule

Now that you have a base class to use for all of your custom rules, you can create a rule. A rule has two main components:

  • Rule implementation code — This is the code that analyzes the target assembly and determines whether the standard or guideline it is trying to enforce has been violated.

  • Rule descriptive XML — Having the implementation code is not enough. An embedded XML fragment is required in order to help Managed Code Analysis display the rule, and provide details such as descriptions and resolutions to the user.

Before you can create the rule's implementation, you must have an approach for evaluating and inspecting a target assembly. While there are many ways you could write code to do this, Microsoft has made the job much easier by including the Microsoft.Cci assembly with Visual Studio 2010. Let's take a look at what this assembly is and how to use it.

Using the Microsoft.Cci Assembly

The Microsoft.Cci assembly (or Common Compiler Infrastructure) originated from Microsoft Research and contains classes that provide features for language-based tools, such as compilers. This assembly is especially helpful for code analysis because it offers many classes that map directly to common programming constructs (such as classes, fields, members, and methods). You'll use these classes to inspect target assemblies to identify the places where your rule applies.

Note

You may be familiar with the System.CodeDom namespace. It is very useful for creating intermediate representations of programming constructs, and then using a language provider to generate code in a desired language, such as VB.NET. Conversely, Microsoft.Cci offers additional features for reading and inspecting existing code, exactly the task you face when creating a code analysis rule.

Table 8-4 lists the major classes offered by Microsoft.Cci, organized by programming concept.

Table 8.4. Microsoft.Cci Classes

Programming Concept

Related Microsoft.Cci Classes

Assembly

CompilationUnit

Namespace

Namespace

Types

Class, Struct, Interface

Type Member

Member

Member

Method, Field, Property, Event, EnumNode

Method

Method, InstanceInitializer, StaticInitializer

Statement

Block, AssignmentStatement, If, For, ForEach, DoWhile, While, Continue, ExpressionStatement, VariableDeclaration, Return, Switch, Lock

Expression

Variable, AssignmentExpression, UnaryExpression, BinaryExpression, NaryExpression, Literal, Parameter, Local

Exception-Related

ExceptionHandler, Throw, Try, Catch, Finally

Instructions and Operations

Instruction, OpCode

The members of the Microsoft.Cci namespace are organized in a hierarchical structure, with related classes organized under a parent type.

For example, the Member class is the parent for the types of things you'd expect to have as class members, Method, Property, Field, Event, and others. If you have a Method instance, you can use its members to obtain references to the items it contains. For example, the Instructions property returns an InstructionList that you can use to loop through the operations of the method. Similarly, the Method class also has a Parameters field, returning a ParameterList instance that can be used to inspect each parameter of the method.

The IIntrospectionRule Interface

As mentioned earlier, one of the abstractions the base class makes is the implementation of the IIntrospectionRule interface. This interface provides the opportunity to specify the conditions under which you want your rule to be invoked. IIntrospectionRule contains the following members:

ProblemCollection Check(Member member);
ProblemCollection Check(Module module);
ProblemCollection Check(Parameter parameter);
ProblemCollection Check(Resource resource);
ProblemCollection Check(TypeNode type);
ProblemCollection Check(string namespaceName, TypeNodeList types);

These overloads of the Check method provide the capability to indicate that your rule should be called when a specific kind of programming construct is currently the focus of the code analysis engine. You do not need to implement all of the Check methods in your custom rules, only the ones that expose the constructs you need.

In this chapter's example, you are looking for constants in an assembly, so you must observe the various members of each class, looking for those that are constants and exposed publicly. Therefore, you must use the Check(Member member) overload. This method will be called each time the analysis engine finds any type member, be it a constant, method, field, property, or other member type.

Writing the Rule Implementation Code

You now have a base class for your rule, and an understanding of the Microsoft.Cci namespace and IIntrospectionRule methods that will help you write the implementation. Create a new class file, AvoidExposingPublicConstants.cs.

First, add using statements for the namespaces you'll use:

using System;
using Microsoft.FxCop;
using Microsoft.FxCop.Sdk;

Now, create the class, inheriting from the BaseStaticAnalysisRule you created earlier:

namespace CustomCodeAnalysisRules
{
    public class AvoidExposingPublicConstants : BaseStaticAnalysisRule
    {
        public AvoidExposingPublicConstants() :
                base("AvoidExposingPublicConstants") {}

        public override ProblemCollection Check(Member member)
        {
            Field f = member as Field;
            if (f == null)
            {
                // Not a field
                return null;
            }
if (member.DeclaringType is Microsoft.FxCop.Sdk.EnumNode)
            {
                // Inside an enumeration
                return null;
            }

            if (member.IsVisibleOutsideAssembly && f.IsLiteral)
            {
                // Is publicly visible and is a constant
                Problems.Add(new Problem(GetResolution(member.Name.Name)));
            }

            return Problems;
        }

        public override TargetVisibilities TargetVisibility
        {
            get { return TargetVisibilities.ExternallyVisible; }
        }
    }
}

The constructor must only supply the name of the rule to the base class constructor, which will forward the name of your XML data store and the assembly type reference automatically to the BaseIntrospectionRule.

As determined earlier, you must implement the Check(Member member) overload from the IIntrospectionRule and search each member for constants. The first thing you do is attempt to convert the Member to a Field instance. If this fails, you know the member was not a field, and you can move on to the next member. If it is a Field, you check the Member to determine whether it was declared inside of an enumeration. You're not interested in enumerations, so you return null in this case.

Finally, you verify that the member is publicly visible with the IsVisibleOutsideAssembly property, and that it is a constant (or literal) value with the IsLiteral property. If these expressions are true, you have a publicly visible constant, and your rule has been violated.

When a rule has been violated, you must create a new Problem instance and add it to your rule's ProblemsCollection collection, provided by the BaseIntrospectionRule class. The argument to a Problem constructor is a Resolution instance. The BaseIntrospectionRule class offers a GetResolution helper method that loads the resource data from the embedded XML data for the current rule. Arguments to GetResolution are automatically inserted into any placeholders such as {0} and {1} in the rule's resolution text, in the same manner as String.Format.

The new Problem is added to the ProblemsCollection collection, and the ProblemsCollection collection is returned, indicating to the Code Analysis tool that a new violation has been added.

The final item in the rule implementation is the TargetVisibility property. This property is used by the Code Analysis tool to determine when items should be fed into the rule's Check method(s). The TargetVisibilities enumeration has values such as All, ExternallyVisible, NotExternallyVisible, AnonymousMethods, None, Obsolete, and Overridable that can be combined to indicate when the rule should be tested. In this case, you only care about publicly visible members, so you return TargetVisibilities.ExternallyVisible.

Creating Rules.xml

With the implementation written, you must now create an XML node that describes the rule and provides text to help the user understand and address rule violations. The outer Rules node specifies the name of the group of rules — for example, Performance Rules. It contains one or more Rule nodes, each describing a single rule.

Add a new XML file to the project. Name the file Rules.xml and enter the following:

<?xml version="1.0" encoding="utf-8" ?>
<Rules FriendlyName="Custom Code Analysis Rules">
  <Rule TypeName="AvoidExposingPublicConstants" Category="Wrox.Custom"
   CheckId="CS0001">
    <Name>Avoid exposing public constants</Name>
    <Description>The values of public constants are compiled into any referencing
assemblies.  Should that value change, it is not sufficient to recompile
    the source
assembly because that value will also be stored in those referencing assemblies.
Avoid public constants for this reason.</Description>
    <Resolution>Change public constant '{0}' to a readonly variable, or mark it as
     private or internal.</Resolution>
    <MessageLevel Certainty="99">Warning</MessageLevel>
    <FixCategories>NonBreaking</FixCategories>
    <url>/Custom/AvoidExposingPublicConstants.html</url>
    <Email>[email protected]</Email>
    <Owner>Contact Person's Name</Owner>
  </Rule>
</Rules>

Warning

You must embed the XML into the rule assembly, or the Code Analysis tool will not be able to load the XML, and your rule will fail. Set this by right-clicking on the XML field and choosing Properties. Under the Advanced section, find the Build Action property and select Embedded Resource. When you build the assembly, the XML will be included in the meta-data.

The Rule node has a TypeName attribute (which should match the name of the rule class), a Category (which is used when displaying violations), and a CheckId (which uniquely identifies that rule — for example, in SuppressMessage attributes). Name is a short, but friendly, version of the rule name. Description contains the full description of what the rule is detecting.

Resolution is the full text shown to users to help them correct the violation. It may contain placeholders, such as {0}, which will automatically be replaced with values from the implementation code, as discussed earlier in this chapter. This is extremely useful to help users quickly identify where problems exist.

Resolution also supports an optional Name attribute that enables you to specify multiple resolutions for the same rule, which can be selected at analysis time by your rule. To do so, instead of using the GetResolution method, use GetNamedResolution, supplying the name you wish to match.

MessageLevel provides a Certainty that the rule is applicable, with values from 0 to 99. A 99 indicates there is little doubt the rule has been violated and should be addressed. A lower value means violations of the rule are difficult to detect with great certainty. Use this value to indicate to the user how likely it is that a specific member or assembly has violated the rule.

The element value of the MessageLevel can be any of the Microsoft.VisualStudio.CodeAnalysis.Extensibility enumeration values, including Information, Warning, CriticalWarning, Error, or CriticalError. Use these to indicate the relative severity of violating a rule. You can see this and the MessageLevel value in practice when you open the XML report from a Code Analysis run, as shown in Figure 8-4.

FixCategories indicates whether the changes needed to correct a rule violation should generally be considered breaking or nonbreaking. Values come from the Microsoft.VisualStudio.CodeAnalysis.Extensibility enumeration and can be Breaking, NonBreaking, or DependsOnFix (which ties back to the concept of multiple named resolutions).

For example, let's say you have a custom rule that has two named resolutions, each used for different rule-violation scenarios. One resolution is easy to implement, and you consider it nonbreaking. But the other is complex to correct, requiring a breaking change.

The Url is the path to an optional file that will show full details of the rule to the user, beyond what is given in the IDE. Email is the optional address of a contact person for help on the rule. Owner is the optionally provided name of a contact person for the rule.

Deploying a Rule

You now have a complete rule assembly with embedded XML containing the supporting data for the contained rule(s). The easiest way to get Visual Studio 2010 to use the contained rules is to move the assembly into the Team ToolsStatic Code AnalysisFxCopRules subdirectory of your Visual Studio installation directory. This will cause the IDE to recognize the rule assembly and read the contained rules so that you can select them for inclusion. The rule is added to the Microsoft.AllRules rule set by default.

A useful way to debug new rules is to create a single solution containing both the custom rule project and a sample target project with code that violates the rule. Open the Properties window for the rule assembly project and choose the Build Events tab. Add a post-build event command line to copy the rule assembly from the source project to the Team ToolsStatic Code AnalysisFxCopRules directory.

Note

Note that on Windows Vista and Windows 7 machines, you will need administrative access to copy the rule.

A problem with this approach is that if you open the Code Analysis properties window in either project, the custom rule assembly will be loaded and locked by the IDE. When this happens, you must close and reopen Visual Studio. However, this approach will generally make your rule debugging process much easier.

Learning from Existing Rules

You've now seen how to create your own rules and integrate them into the Code Analysis tool of Visual Studio 2010. You will certainly find many uses for additional rules, but before you begin creating them, you should invest some time learning from examples.

The recommended approach for those wishing to implement custom rules is to look at how the rules included with Visual Studio 2010 were written. While you don't have direct access to the source code for these rules, there is a tool that can help. Reflector, written by Lutz Roeder and now maintained by RedGate (www.red-gate.com/products/reflector), uses the power of .NET's reflection services to peer inside any assembly and generate an approximation of the source code. The target assembly can be any assembly, including those from Microsoft.

After obtaining Reflector, find the existing rules files in your Visual Studio 2010 installation directory under Team ToolsStatic Analysis ToolsFxCopRules. Using Reflector, open one of the rule assemblies, such as PerformanceRules.dll. You can then navigate to the Microsoft .Tools.FxCop.Rules.Performance namespace, where you will see all of the rules in the Performance category.

Opening each rule will show you the details you've learned about earlier in this chapter. The rules inherit from base helper classes, just as you saw with AvoidExposingPublicConstants and BaseStaticAnalysisRule. Opening the Resources node for any rule assembly enables you to view the XML data that was embedded in the assembly. Opening members such as the Check methods will show you code that, while not exactly original, will give you enough detail to determine how you might accomplish the same tasks in your own rules.

CODE METRICS

The Code Metrics tool is a set of software metrics that provide insight into the code that is being developed. Code Metrics provides a quick-and-easy way to determine the complexity of the code, and to isolate code areas that may be difficult to maintain in the future. Code metric information is calculated at the method level, and then rolled up all the way to the assembly level. Visual Studio 2010 calculates five different code metrics:

  • Cyclomatic Complexity — This measures the structural complexity of the code. It is created by calculating the number of different code paths through the code, including if statements, looping, and so on. A high number for Cyclomatic Complexity indicates that the code may be too complex, and should be refactored.

  • Depth of Inheritance — This indicates the number of class definitions that extend to the root of the class hierarchy. While inheritance in itself is not bad, having a lengthy inheritance level can make the code difficult to understand and troubleshoot. As with Cyclomatic Complexity, you want to have a low number for Depth of Inheritance.

  • Class Coupling — This indicates the total number of dependencies that a class has on other classes. This calculation does not include primitive or built-in types. A high level of Class Coupling indicates that changes in other classes could affect a specific class. You want a low number for Class Coupling.

  • Lines of Code — This indicates the number of executable lines of code in a method. This is an approximate count, based off the IL code, and only includes executable lines of code. Comments, braces, and white space are excluded. For Lines of Code, a low value is good, and a high value is bad.

  • The Maintainability Index — This is a combination of several metrics, including Cyclomatic Complexity, average Lines of Code, as well as computational complexity. This metric is calculated using the following formula:

    MAX(0,(171−5.2*ln(Halstead Volume)−0.23*(Cyclomatic Complexity)−16.2*ln(Lines of Code))*100/171)

The Maintainability Index is a value between 1 and 100. Unlike the previous four metrics, for Maintainability Index, the higher the value, the easier the code will be to maintain. Table 8-5 shows the Maintainability Index ranges and what they indicate.

Table 8.5. Maintainability Index Ranges

Color

Level

Range

Green

High Maintainability

Between 20 and 100

Yellow

Moderate Maintainability

Between 10 and 20

Red

Low Maintainability

Between 0 and 9

To run Code Metrics against your code, simply right-click the specific solution or project within Visual Studio, and select Calculate Code Metrics. The Code Metrics Results window will display with the results, as shown in Figure 8-5.

FIGURE 8-5

Figure 8.5. FIGURE 8-5

SUMMARY

This chapter demonstrated the need for static analysis tools and introduced you to the .NET Framework "Design Guidelines for Class Library Developers." These guidelines are a very important resource that Microsoft has made freely available, and the basis for Visual Studio 2010's included Code Analysis rules.

In this chapter, you learned about the Managed Code Analysis tool, including how it now integrates with Visual Studio 2010 and enables rule analysis to be performed with a simple build. You learned how to configure and execute analysis, and how to work with the resulting rule violation messages.

To support projects using a repeatable build process, or those that need additional flexibility, you learned how to use the command-line Managed Code Analysis tool, and how to create FxCopCmd project files to store settings.

You walked through the process of creating your own rule, and integrating it with your analysis process. You created the simple AvoidExposingPublicConstants rule as an example of how to use the new introspection engine to create new Managed Code Analysis rules in Visual Studio 2010.

Finally, you were introduced to Code Metrics. The five different code metric values were explained, and you saw how easy it was to run and view the results of the code metrics calculation.

Chapter 9 looks at the code profiling capabilities of Visual Studio 2010, and how they can be used to find and fix performance problems in your code.

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

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