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 FIELDS 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 "Exception$"
Related CQL
conditions: NameLike, FullNameLike
Call Graph
NDepend is designed not only to handle direct
dependencies, but also indirect dependencies. Meaning, you can not only
constraint that A cannot use directly B, but also that A cannot used something
that is using something that is using B. For example, suppose that you want to
make sure that your business objects (contained in YourBusinessObjects namespace) won’t rely directly or indirectly on
your data access layer (contained in YourDataAccess
namespace).
WARN IF Count > 0 IN SELECT NAMESPACES WHERE
IsUsedBy "YourBusinessObjects" AND
NameIs "YourDataAccess"
The
conditions IsUsedBy and IsUsing could have been actually named IsIndirectlyUsedBy or IsIndirectlyUsing but the way it is now
sounds more concise. There are also facilities to get the depth of use. Once
you got the trick this opens a wide range of possibilities to re-engineer some
code, explained here: Deconstructing Software
Related CQL
conditions: IsUsing, IsUsedBy, DepthOfIsUsing, DepthOfIsUSedBy
Separation of Concern
CQL
conditions related to dependencies can be used to enforce some sort of
separation of concerns. For example, the following CQL rule makes sure that web methods are not using directly DB:
WARN IF Count > 0 IN SELECT METHODS WHERE
IsDirectlyUsing "System.Web" AND
IsDirectlyUsing "System.Data.SqlClient"
More on
this here: Dependencies and Concerns
Componentization, Layering
VisualStudio detects dependencies cycles between assemblies
but you are stuck if you need to forbid cycles between something more granular
than assemblies, aka namespaces or types or methods. Dependencies cycles are
strongly related to componentization because you define components especially
to layer them, meaning no dependency cycles should exist between components.
This limitation leads to a dramatic problem according to me: most .NET
developers consider that one component =
one assembly and this leads to aberration such as hundreds of VisualStudio projects. This is
aberration because assemblies are costly physical things while components should be lightweight logical artefacts.
If you
consider that namespace of the assembly Foo
are actually your components, you can constraint the fact that they should not
be entangled in cycles with the rule:
WARN IF Count > 0 IN SELECT ASSEMBLIES WHERE
ContainsNamespaceDependencyCycles AND NameIs "Foo"
This is
equivalent to:
WARN IF Count > 0 IN SELECT NAMESPACES FROM ASSEMBLIES "Foo" WHERE
!HasLevel
The metric Level is a cool metric that can only be
computed if no dependencies cycles exist, hence the equivalence. In the same
spirit you might want to constraint that methods of a type or types of a
namespaces are layered:
WARN IF Count > 0 IN SELECT TYPES WHERE
ContainsMethodDependencyCycles AND NameIs "YourNamespace.YourClass"
More on
this here: Control components dependencies to gain clean architecture, here Hints on
how to componentized code and here Layering, and here The Level
metric and the Discourse of Method
Related CQL
conditions: ContainsNamespaceDependencyCycles,
ContainsTypeDependencyCycles, ContainsMethodDependencyCycles, Level, HasLevel
Btw, now
that we know the meaning of the conditions IsUsing
and IsUsedBy if you don’t want that
the class MyNamespace.Foo be
involved in a cycle with some other classes you can write the rule:
WARN IF Count > 0 IN SELECT TYPES
WHERE IsUsing "MyNamespace.Foo" AND
IsUsedBy "MyNamespace.Foo"
Miscellaneous
Conventions
written with CQL cover a wide range of popular scenario and as shown, crossing
CQL features open the doors to even more scenario. Several dozens of CQL conventions
(applicable to any program) comes with any new NDepend project.
Of course, this set of rules can be extended and customized at whim. It is also possible to
insert active conventions directly in C# or VB.NET source code as explained here.
To clarify
things, NDepend is especially designed
for build process integration and warns when a convention is violated.
CQL is
currently evolving and more great features are currently under development. Also the post was far from covering all CQL conditions, enumerated in the CQL specification available
here. Here are some more rules:
//
<Name>Fields must be private</Name>
WARN IF Count > 0 IN SELECT FIELDS
WHERE !IsPrivate
//
<Name>Classes that should be sealed</Name>
WARN IF Count > 0 IN SELECT TYPES
WHERE IsClass AND NbChildren == 0 AND !IsSealed
// <Name>Not more than
5% of methods should use boxing or unboxing</Name>
WARN IF Percentage > 5 IN SELECT METHODS WHERE IsUsingBoxing OR IsUsingUnboxing
// <Name>Stateless
types only made of functions that might be static</Name>
WARN IF Count > 0 IN SELECT TOP 10 TYPES WHERE
SizeOfInst ==0 AND !IsStatic AND !IsGeneric AND !IsInterface
Posted
05-11-2008 10:10 PM
by
Patrick Smacchia