Recently,
both Glenn Block and Ayende wrote about how to define
some sort of active conventions about
the code base. The idea is great! The proposed implementation is based on the
framework System.Reflection used from
some unit tests to assert some properties on some elements of the code base. Not
taking account the problems encountered with Reflection, this approach suffers the same
problem than writing custom rules for FxCop: there is a lot of noise/friction and it
takes dozens of minutes just to write a single convention and make sure it is
working correctly.
The
language Code Query Language
supported by NDepend has
been especially designed to write active conventions
/ rules/ constraints in a frictionless way. The purpose of this post is to enumerate
popular active code conventions written with CQL. These conventions are related
to a rich set of area including: Dependencies, Structure, Quality,
Metrics, Evolution, Diff / Changes, Coverage, Purity / Side-Effects /
Immutability, Encapsulation, Call Graph, Separation of Concerns,
Componentization, Layering, Coupling / Dead / Unused Code, Naming Conventions…
Dependencies / Structure
As the name
NDepend suggests, dependencies is a
major concern of the tool. CQL lets write all sorts of conventions about
which part of the code is allowed to use or not which other part. Suppose you
are in charge of writing conventions on mscorlib
and you don’t want that the namespace Microsoft.Win32
uses the namespace System.Collections.
You just have to write the CQL rule:
WARN IF Count > 0 IN SELECT NAMESPACES WHERE
IsDirectlyUsing "System.Collections.Generic" AND
NameIs "Microsoft.Win32"
I hope that
the syntax is slick and concise enough to not require any additional comments.
The VisualNDepend UI lets generate such a rule directly from the Dependencies
Structure Matrix:
From there,
you can write any constraints you can imagine in order to restrict (and thus
control) the evolution of the structure of your code base. For example the
following constraint warns if the assembly Asm1
uses something else from the assembly Asm2
than the class MyNamespace.Foo:
WARN IF Count > 0 IN SELECT TYPES FROM ASSEMBLIES "Asm2"
WHERE IsDirectlyUsedBy "Asm1" AND
!NameIs "MyNamespace.Foo"
…and the
following rules warns if the class MyNamespace.Foo
is used by a namespace which name doesn’t satisfy the regex ‘begin with MyNamespace.Internal’:
WARN IF Count > 0 IN SELECT NAMESPACES
WHERE IsDirectlyUsing "MyNamespace.Foo" AND
!NameLike "^MyNamespace.Internal"
Related CQL
conditions: IsDirectlyUsing, IsDirectlyUsedBy, NameIs, FullNameIs, NameLike, FullNameLike
More on
this here: Keep your code structure clean
Quality / Metrics
CQL comes
with support for 80 code metrics + numerous facilities in VisualNDepend to dig into abnormal values. Suppose that you don’t
want that a method have more than 25 lines of code except if it is a Windows Form InitializeComponent() generated method:
WARN IF Count > 0 IN SELECT METHODS
WHERE NbLinesOfCode > 25 AND !NameIs "InitializeComponent()"
Amongst
popular quality metrics supported by NDepend let's quote: PercentageComment, CyclomaticComplexity (computed from code source or IL),
IL Nesting Depth, Size of Instance, Efferent Coupling …
The
exhaustive description of all metrics is available here.
Evolution / Changes
CQL can be
used to rule the diff between any 2 snapshots of a code base taken at
different points in time. In other words, one can continuously control the evolution of the
code. Suppose you want to avoid breaking changes such as a public
method in the older snapshot that doesn’t exist anymore in the newer snapshot:
WARN IF Count > 0 IN SELECT METHODS
WHERE IsPublic AND (VisibilityWasChanged OR WasRemoved)
More on API
breaking changes rules here: Avoid API breaking changes
Mixing CQL code
changes conditions and other CQL conditions is a smart way to write rules that
should be applied only from a particular milestone. Suppose that from today,
all method refactored or added should have a cyclomatic complexity lower than
8:
WARN IF Count > 0 IN SELECT METHODS
WHERE CyclomaticComplexity > 8 AND (CodeWasChanged OR WasAdded)
More on
this possibility here: Ensure the quality of the code that will be developed
this year
Related CQL
conditions: WasAdded, WasRemoved, CodeWasChanged, CommentsWereChanged,
VisibilityWasChanged, WasChanged, BecameObsolete, IsUsedRecently,
IsNotUsedAnymore, IsUsedDifferently, IsInNewerBuild, IsInOlderBuild
Code Coverage by Tests
NDepend can import code coverage metrics computed from
NCover and Microsoft Visual Studio Team System. For example you can define an
attribute on types YourNamespace.FullCoveredAttribute
and make sure that all types tagged with this attribute are and will remain
100% covered:
WARN IF Count > 0 IN SELECT TYPES
WHERE PercentageCoverage < 100 AND HasAttribute "YourNamespace.FullCoveredAttribute"
You might
also want to make sure that code that was added or refactored since the last
released is 100% covered (a popular agile practice that so far, cannot be strictly
applied without CQL):
WARN IF Count > 0 IN SELECT METHODS
WHERE PercentageCoverage < 100 AND (CodeWasChanged
OR WasAdded)
More on
this here: Make the most of your test coverage data
Related CQL
conditions: PercentageCoverage, PercentageBranchCoverage, NbLinesOfCodeCovered, NbLinesOfCodeNotCovered, IsExcludedFromCoverage
Purity / Side-Effects /
Immutability
One hot .NET
language topics actually is purity, meaning how / why / when states are changing at run-time. When states remain constants, there are no more side-effects and
as a result you can assert numerous cool things on your program, such as no
corrupted state in a multi-threaded environment. CQL lets write some conventions about the mutability. For example you can define an attribute on types YourNamespace.ImmutableAttribute and
make sure that all types tagged with this attribute are immutable, meaning the
state of their instance objects won’t changed once created:
WARN IF Count > 0 IN SELECT TYPES
WHERE !IsImmutable AND HasAttribute "YourNamespace.ImmutableAttribute"
In the same
spirit, you might want to enforce that some methods are pure:
WARN IF Count > 0 IN SELECT METHODS
WHERE (ChangesObjectState OR ChangesTypeState) AND
HasAttribute "YourNamespace.PureAttribute"
…or that all structure are immutable:
WARN IF Count > 0 IN SELECT TYPES
WHERE !IsImmutable AND IsStructure
You can
also restrict write access to a particular field to certain methods:
WARN IF Count > 0 IN SELECT METHODS
WHERE IsDirectlyWritingField "YourNamespace.YourClass.m_Field" AND
!FullNameIs "YourNamespace.YourClass.Method1()" AND
!FullNameIs "YourNamespace.YourClass.set_Field(Int32)"
More on
this here: Immutable Types: understand
their benefits and use them
Related CQL
conditions: ChangesObjectState, ChangesTypeState, IsImmutable, IsWritingField,
DepthOfIsWritingField, IsDirectlyWritingField
Optimal Encapsulation
CQL comes
with several conditions especially designed to pinpoint code elements not optimally
encapsulated, such as an internal methods that could be declared private without any compilation break:
WARN IF Count > 0 IN SELECT METHODS
WHERE IsInternal AND CouldBePrivate
More on
this here: Optimal Encapsulation
Related CQL
conditions: CouldBeInternal, CoulBeInternalProtected, CouldBeProtected,
CouldBePrivate, ShouldBePublic, IsPublic,
IsInternal, IsProtected, IsPrivate, IsInternalAndProtected, IsInternalOrProtected
Coupling / Dead / Unused Code
With a bit
of astute, it is easy to write rules that detects potentially dead code, i.e code that is not used anymore and that can
be safely removed. The word potentially is used
here because a static analysis tool cannot mathematically detect the exact set of dead code
elements. The idea is to detect code elements with no afferent coupling,
meaning, not used anywhere in the code. We rely on the fact that the value of the metric Ca (Afferent Coupling) is equal to 0 in
such case. For example, to warn if some methods are potentially not used:
WARN IF Count > 0 IN SELECT TOP 10 METHODS WHERE
MethodCa == 0 AND // Ca=0 -> No Afferent Coupling -> The
method is
// not used in
the context of this application.
!IsPublic AND // Public methods might be used by client
// applications
of your assemblies.
!IsEntryPoint AND // Main() method is not used by-design.
!IsExplicitInterfaceImpl AND // The IL code never explicitely
calls
// explicit
interface methods implementation.
!IsClassConstructor AND // The IL code never explicitely calls class ctors.
!IsFinalizer
//
The IL code never explicitely calls finalizers.
More on
this here: Code metrics on Coupling, Dead Code, Design Flaws and Re-engineering
Related CQL
conditions: NamespaceCa, MethodCa, TypeCa, FieldCa…
Naming Conventions
Because CQL
supports regex validation, it is easy to write any naming convention such as:
all static fields names should begin with s_:
WARN IF Count > 0 IN SELECT WHERE !NameLike "^s_" AND IsStatic
Or all
exception classes should end up with Exception:
WARN IF Count > 0 IN SELECT TYPES WHERE
DeriveFrom "System.Exception" AND
!NameLike