[The static location of this piece can be found at this address]
For the past while I've been using Visual Studio Team Edition for Software Developers, one of its benefits over the Professional Edition being the inclusion of static code analysis functionality right in the IDE.
This functionality comes via the FxCop codeset, which is an excellent -- albeit unpolished -- freely available tool for analyzing the probable code quality of Intermediate Language assemblies, testing code to ensure compliance with naming standards, best practices, and highlighting areas of code that are suspect. While it's less than pleasant starting FxCop analysis from scratch on long existing project -- to be met with hundreds upon hundreds of error messages -- it's a painless process if you add it to your quality checks early on.
The standalone FxCop is largely the same as the VSTE version, and in some ways is superior. For instance that it retains the ability to actually pass configuration settings to rules, rather than accepting whatever the defaults for the rule are.
One of the few differences between the standalone application and the VSTE-included version are the addition of several new maintenance checks in the Team Edition code, one of the most useful being the cyclomatic complexity checks. Cyclomatic complexity, for those who haven't come across it before, is often used to roughly gauge the complexity of a piece of code, to determine likely candidates for refactoring, and to identify what will likely become a maintenance problem in the future. Finding the most complex pieces of code often brings you to the buggiest code as well.
Given that I still use FxCop, both the .NET 1.1 and .NET 2.0 versions (not least because the integrated version offers no ability to configure settings for rules, instead only allowing you to wholesale enable or disable. This eliminates the ability to set thresholds for tests such as the cyclomatic complexity rules), the lack of consistency between the two versions was an annoying gap.
So I implemented a simple cyclomatic counting rule for the standalone FxCop. While in there, I added checks for statement count (the number of intermediate language "statements", which can be indicative of overly complex methods), and callout count (e.g. callouts to other methods, again which can be an indicator of overly complex/convoluted methods).
As one added benefit, I added the ability to log all of these metrics to an SQL-capable OleDB destination (e.g. SQL Server, Access, etc). If you configured an OLEDB connection string, as detailed below, you can do data analysis after a run to create pretty reports of the complexity distributions of your projects, and so on.
yafla FxCop
Rules for .NET 1.1 (e.g. FxCop 1.32)
yafla FxCop
Rules for .NET 2.0 (e.g. FxCop 1.35)
Like any tool of this type, there is only a moderate correlation between the metrics measured and actual code quality or maintainability: It is entirely possible that the optimal implementation is a highly-complex, lengthy method. This tool only provides guidance, helping to determine which code should get a complexity analysis, however from there experience and good judgement have to be applied to determine if it's really a fault. If you're using the .NET 2.0 version of FxCop, make use of the SuppressMessage attribute on methods that are necessarily highly complex.
Drop yaflaRules.dll in your FxCop Rules subdirectory (e.g. C:\\program files\\Microsoft FxCop 1.32\\Rules).
If you want more advanced settings, configure FxCop with your targets and selected rules and then save the project file. Open the newly created .FxCop file in an editor (for instance notepad) and find the <Settings /> element. Expand it to an opening and closing tag (e.g. <Settings></Settings>), and between it add
<Rule TypeName="MethodComplexity"></Rule>
Between the Rule element add any of the following entries as Name attributes of an Entry element (as exampled following) -
Connection String - an OleDb connection string
determining where it will log metrics. e.g.
Provider=SQLNCLI;Server=(local);Database=Analysis;Trusted_Connection=yes;
Target Table - The target table for metric
logging. Default -
MethodComplexity
Cyclomatic Critical
Error - Level at which a critical error is
triggered. Default - 60
Cyclomatic Error - Level at which an error is
triggered. Default - 50
Cyclomatic Critical Warning - Level at which a
critical warning is triggered. Default - 45
Cyclomatic Warning - Level at which a warning is
triggered. Default - 40
Cyclomatic Information - Level at which an
infromation event is triggered. Default - 20
Cyclomatic Recommended - Recommended level.
Default - 20
Statements Critical Error - Statement count at
which a critical error is triggered. Default - 500
Statements Error - Statement count at which an
error is triggered. Default - 350
Statements Critical Warning - Statement count at
which a critical warning is triggered. Default - 250
Statements Warning - Statement count at which a
warning is triggered. Default - 200
Statements Information - Statement count at which
an information event is triggered. Default - 150
Statements Recommended - Recommended maximum
statement count per method. Default - 100
Callouts Critical Error - Callout count at which a
critical error is triggered. Default - 100
Callouts Error - Callout count at which an error
is triggered. Default - 75
Callouts Critical Warning - Callout count at which
a critical warning is triggered. Default - 50
Callouts Warning - Callout count at which a
warning is triggered. Default - 40
Callouts Information - Callout count at which an
information event is triggered. Default - 30
Callouts Recommended - Recommended maximum callout
count per method. Default - 30
For instance, you might end up with a <Settings> element that looks like the following:
<Settings><Rule TypeName="MethodComplexity"><Entry Name="Connection String">Provider=SQLNCLI;Server=(local);Database=Analysis;Trusted_Connection=yes;</Entry><Entry Name="Callouts Warning">100</Entry><Entry Name="Cyclomatic Critical Warning">500</Entry></Rule></Settings>
If you opt to take advantage of metrics logging, the destination table (which will be default will be MethodComplexity, unless overridden with the Target Table name entry) requires the following columns:
ContainingType - text (e.g.
nvarchar(255))
MethodName - text (e.g. nvarchar(255))
Cyclomatic - int
Statements - int
Callouts - int
e.g.
CREATE TABLE [dbo].[MethodComplexity](
[ContainingType] [nvarchar](255) COLLATE
SQL_Latin1_General_CP1_CI_AS NOT NULL,
[MethodName] [nvarchar](255) COLLATE
SQL_Latin1_General_CP1_CI_AS NOT NULL,
[Cyclomatic] [int] NOT NULL,
[Statements] [int] NOT NULL,
[Callouts] [int] NOT NULL
) ON [PRIMARY]
Hopefully someone finds this interesting. It scratched my itch.