The rule consists in minimizing what is called the Nesting Depth. The Nesting
Depth is a metric defined on methods that is relative to the maximum depth
of the more nested scope in a method body. For example the following method has
a Nesting Depth of 3.
public static
void Method(List<string> list) {
for (int i = 0; i < 10; i++) {
if (i
> 5) {
foreach
(string s in
list) {
Console.WriteLine(s);
}
}
}
}
Nesting Depth is not so obvious when it comes to complex if statement. For example the following
method has a Nesting Depth of 3 even though
it has only one if statement.
public static
void Method(string
s) {
if (s != null && s.Length > 0 &&
s.Contains("a")) {
Console.WriteLine(s);
}
}
Obviously you want to keep the Nesting
Depth of your methods low. But it is not only about readability but it is also
for testability. Here is a common situation where choosing for lower Nesting Depth enhance the testability of
the code.
public static
void Method(List<string> list) {
foreach (string s in list) {
if (s != null &&
s.Length > 0 &&
s.Contains("a") /*...*/ ) {
Console.WriteLine(s);
}
}
}
public static
void Method(List<string> list) {
foreach (string s in list) {
if (s == null) { continue; }
if
(s.Length == 0) { continue; }
if
(!s.Contains("a")) { continue; }
// ...
Console.WriteLine(s);
}
}
The second code is more readable because what does matter, the WriteLine() call, has a Nesting Depth of 1 instead of 4. The
code is more testable also because now, if we don’t write some unit tests to
cover the 3 cases where the Writeline()
is not invoked, we won’t have 100% coverage.
The same trick applies to methods that return something, for example:
public static
int Method(string
s) {
if (s == null) { return 0; }
if (s.Length
== 0) { return 0; }
if
(!s.Contains("a")) { return 0; }
// ...
return
s.GetHashCode();
}
…is better than…
public static
int Method(string
s) {
if ( s == null ||
s.Length == 0 ||
!s.Contains("a")
/*...*/ ) {
return 0;
}
return
s.GetHashCode();
}
…because it is both more readable and more testable.
Hopefully the new NDepend version 2.7 has support for Nesting Depth. The following CQL rule will ensure that you don't get too high in Nesting Depth:
WARN IF Count > 0 IN SELECT METHODS WHERE ILNestingDepth > 4
The
implementation infers Nesting Depth
from the IL code. It means that it works no matter the language you choose and that
Nesting Depth values are comparable
amongst languages. It means also that you have another way to measure the
quality of third parties code you rely on, because you don’t need the source
files to measure it, only the assemblies are needed (and it doesn’t matter if they
are obfuscated or not). Who cares that in mscorlib
the method System.Text.UnicodeEncoding.GetBytes(Char*,Int32,Byte*,Int32,EncoderNLS)
has a Nesting Depth value of 16!
The only drawback of inferring the Nesting
Depth from IL is that sometime values are a bit higher than expected
because of C# and VB.NET compiler optimizations, especially on switch with many case conditions. For example, when a switch on a string has
more than 6 cases, the C# compiler estimates that it is worth creating a generic
Dictionary<string,int> and to
do the switch on the hash value of the string. Have a look at the IL of the
following method:
public static
int Method(string
s) {
switch (s) {
case "1":
return
1;
case "2":
return
2;
case "3":
return
3;
case "4":
return
4;
case "5":
return
5;
case "6":
return
6;
case "7":
return
7;
}
return 0;
}