I am
impressed by the buzz done around my last post on Number of Types in the .NET Framework. Actually it was just a quick
post that I wrote after having read the Brad
Abrams Number of Types in the .NET
Framework post to see what NDepend
would report on metrics on the .NET Fx v3.5. I didn’t expect that this would
turn in a debate around the completeness and the download size of the .NET
Fxv3.5 and even Java Vs. .NET. As a heavy user of .NET since the early beginning back in 2001 and as a big
fan of this platform, my opinion is biased even thought I think that some
parts are perfectible such as:
- Collections
are duplicated (for legacy reason)
- The design
of the IO API that after 7 years I still found not being intuitive
But so far,
these small issues don’t matter when I take account of the benefit of working
with .NET:
- A super
innovative language team able to bring a ground-breaking enhancement every 2
years.
- A powerful
compiler, able to compile my 60K lines of C# code in less than 5 seconds.
- Maybe the best debugger of all times.
- An optimized
CLR, close to the hardware, that let me for example use pointers from managed
code whenever I want
- An active
community able to make diamonds such as Mono.Cecil .
Instead of
rambling on this sensible debate, I prefer exposing more metrics relative to the
.NET Fx v.3.5.
Size of methods
The size of
methods is computed in terms of number of IL instructions.
As the .NET Fx v3.5 is compiled with optimization, you can guess the equivalent
number of lines of C# or VB.NET code by dividing values by 5.
SELECT METHODS WHERE NbILInstructions > 0 AND !IsClassConstructor ORDER BY NbILInstructions DESC
#Methods: 336 908 Average: 24.66 IL Instructions Std Dev: 58.05
SELECT TOP 5 METHODS WHERE NbILInstructions > 0 AND !IsClassConstructor ORDER BY NbILInstructions DESC
|
Full Name
|
# IL
instructions
|
|
System.Windows.Markup.KnownTypes.GetKnownTypeConverterIdForProperty( KnownElements,String)
|
6228
|
|
MS.Internal.Markup.KnownTypes.GetKnownTypeConverterIdForProperty( KnownElements,String)
|
6228
|
|
System.Security.Cryptography.RIPEMD160Managed.MDTransform( UInt32*,UInt32*,Byte*)
|
5294
|
|
MS.Internal.Markup.TypeIndexer.InitializeOneType(KnownElements)
|
3766
|
|
System.Web.Configuration.BrowserCapabilitiesFactory. PopulateBrowserElements(IDictionary)
|
3596
|
The class
constructors are not taken account because they biase the result since they
all the bunch of code that initialize the value of static fields.
Cyclomatic Complexity and
nested depth of methods
As we don’t
have source code of the .NET Fx, we infer the Cyclomatic Complexity from the IL
code as explained here.
We generally obtain values slightly higher than the Cyclomatic Complexity
obtained from source code.
The second
practical metric to measure complexity is the Nesting Depth, here also inferred
from IL, whose definition is available read here.
SELECT METHODS WHERE NbILInstructions > 0 ORDER BY ILCyclomaticComplexity DESC,ILNestingDepth DESC
#Methods: 341 842 Average IL Cyclomatic Complexity: 1.68 Std
Dev: 5.48
Average IL Nesting
Depth: 0.68 Std
Dev: 1.80
These
numbers attest from a great overall quality, but it is still possible to find
some monsters (I don’t know if these monsters are generated method or
handcrafted methods?):
SELECT TOP 5 METHODS WHERE NbILInstructions > 0 ORDER BY ILCyclomaticComplexity DESC
|
Full Name
|
# IL instructions
|
IL Cyclomatic Complexity (ILCC)
|
|
System.Windows.Markup.KnownTypes.GetKnownTypeConverterIdForProperty( KnownElements,String)
|
6228
|
872
|
|
MS.Internal.Markup.KnownTypes.GetKnownTypeConverterIdForProperty( KnownElements,String)
|
6228
|
872
|
|
MS.Internal.Markup.TypeIndexer.InitializeOneType(KnownElements)
|
3766
|
761
|
|
System.Windows.Markup.TypeIndexer.InitializeOneType( KnownElements)
|
3046
|
760
|
|
System.Windows.Markup.KnownTypes.CreateKnownElement(KnownElements)
|
1729
|
549
|
SELECT TOP 5 METHODS WHERE NbILInstructions > 0 ORDER BY ILNestingDepth DESC
|
Full Name
|
# IL instructions
|
IL Nesting Depth
|
|
Microsoft.JScript.Convert.Coerce2WithNoTrunctation(Object,TypeCode)
|
1539
|
268
|
|
System.Windows.Markup.KnownTypes.GetKnownPropertyAttributeId( KnownElements,String)
|
1979
|
190
|
|
MS.Internal.Markup.KnownTypes.GetKnownPropertyAttributeId( KnownElements,String)
|
1979
|
190
|
|
System.Web.Configuration.BrowserCapabilitiesFactory.UpProcess( NameValueCollection,HttpBrowserCapabilities)
|
1129
|
181
|
|
MS.Internal.Markup.KnownTypes.GetKnownTypeConverterIdForProperty( KnownElements,String)
|
6228
|
172
|
Number of Parameters of public
methods and Variables
The number
of parameters of a method represents an easy way to measure quality:
SELECT METHODS WHERE IsPublic ORDER BY NbParameters DESC
#Methods: 219 234 Average #Parameters: 0.98 Std Dev: 1.32
Here also
the overall quality is pretty good, but here also it is easy to find terrific values:
SELECT TOP 5 METHODS WHERE IsPublic ORDER BY NbParameters DESC
|
Full
Name
|
#
Parameters
|
|
MS.Internal.PtsHost.UnsafeNativeMethods.PTS+FormatLine. BeginInvoke(IntPtr,IntPtr,IntPtr,Int32,Int32,IntPtr,UInt32,Int32, Int32,Int32,Int32,Int32,Int32,Int32,Int32,Int32,Int32,Int32,IntPtr&, Int32&,IntPtr&,Int32&,PTS+FSFLRES&,Int32&,Int32&,Int32&,Int32&, Int32&,Int32&,AsyncCallback,Object)
|
31
|
|
MS.Internal.PtsHost.UnsafeNativeMethods.PTS+ReconstructLineVariant. BeginInvoke(IntPtr,IntPtr,IntPtr,Int32,Int32,IntPtr,Int32,UInt32,Int32,Int32 ,Int32,Int32,Int32,Int32,Int32,Int32,Int32,Int32,Int32,IntPtr&,IntPtr& ,Int32&,PTS+FSFLRES&,Int32&,Int32&,Int32&,Int32&,Int32&,Int32&, AsyncCallback,Object)
|
31
|
|
MS.Internal.PtsHost.UnsafeNativeMethods.PTS+FormatLineForced. BeginInvoke(IntPtr,IntPtr,IntPtr,Int32,Int32,IntPtr,UInt32,Int32,Int32 ,Int32,Int32,Int32,Int32,Int32,Int32,Int32,Int32,Int32,IntPtr&,Int32& ,IntPtr&,PTS+FSFLRES&,Int32&,Int32&,Int32&,Int32&,Int32&, AsyncCallback,Object)
|
29
|
|
MS.Internal.PtsHost.UnsafeNativeMethods.PTS+ReconstructLineVariant. Invoke(IntPtr,IntPtr,IntPtr,Int32,Int32,IntPtr,Int32,UInt32,Int32,Int32, Int32,Int32,Int32,Int32,Int32,Int32,Int32,Int32,Int32,IntPtr&,IntPtr&,Int32& ,PTS+FSFLRES&,Int32&,Int32&,Int32&,Int32&,Int32&,Int32&)
|
29
|
|
MS.Internal.PtsHost.UnsafeNativeMethods.PTS+FormatLine.Invoke(IntPtr, IntPtr,IntPtr,Int32,Int32,IntPtr,UInt32,Int32,Int32,Int32,Int32,Int32,Int32 ,Int32,Int32,Int32,Int32,Int32,IntPtr&,Int32&,IntPtr&,Int32&, PTS+FSFLRES&,Int32&,Int32&,Int32&,Int32&,Int32&,Int32&)
|
29
|
And the
following CQL query returns 568 methods!
SELECT METHODS WHERE IsPublic AND NbParameters > 8
Similarly,
you can also measure the quality from the number of internal variables used by
a method and we obtain some similar results:
SELECT METHODS WHERE IsPublic AND NbILInstructions > 0 ORDER BY NbVariables DESC
#Methods: 184 149 Average #Parameters: 0.86 Std Dev: 2.14
SELECT TOP 5 METHODS WHERE IsPublic ORDER BY NbVariables DESC
|
methods
|
# Variables
|
|
java.awt.GridBagLayout.GetLayoutInfo(Container,Int32)
|
105
|
|
Microsoft.VisualBasic.CompilerServices.VBBinder.BindToMethod( BindingFlags,MethodBase[],Object[]&,ParameterModifier[],CultureInfo,String[],Object&)
|
97
|
|
javax.swing.plaf.basic.BasicLookAndFeel.initComponentDefaults(UIDefaults)
|
64
|
|
System.ServiceModel.Description.DispatcherBuilder.InitializeServiceHost( ServiceDescription,ServiceHostBase)
|
62
|
|
System.Windows.Forms.ControlPaint.DrawBorder( Graphics,Rectangle,Color,Int32,ButtonBorderStyle,Color,Int32,ButtonBorderStyle,Color, Int32,ButtonBorderStyle,Color,Int32,ButtonBorderStyle)
|
61
|
Efferent Coupling
NDepend is
far from being just a metric software and can help you deal with dependencies, layering
and componentization, can compare 2 snapshots of your code base, can check some
custom rules on your design and code and more (see the list of features here). It does support some exotic (but still useful) metrics like Efferent Coupling
(Ce).
The Efferent Coupling for a particular type is the number of types it directly
depends on. As I explained in a previous post, the more types a given type is using, the more responsibilities it has.
SELECT TYPES WHERE NbILInstructions > 0 ORDER BY TypeCe DESC
#Types: 28 738 Average # of types used: 22.88 Std
Dev: 20.71
Here I
found the average a little high, but we have to take account that primitive
types such as int, string or bool are taken account in this result.
And who are
the monsters types?
SELECT TOP 5 TYPES WHERE NbILInstructions > 0 ORDER BY TypeCe DESC
|
Full
Name
|
#
IL instructions
|
Efferent coupling at
type level (TypeCe)
|
|
System.Windows.Markup.KnownTypes
|
12593
|
582
|
|
System.Windows.Forms.Control
|
18246
|
332
|
|
System.Windows.Forms.DataGridView
|
67320
|
331
|
|
MS.Internal.PtsHost.UnsafeNativeMethods.PTS
|
249
|
264
|
|
System.Windows.Forms.ListView
|
10824
|
254
|
NDepend
also support Ce on methods
(and here we get the number of methods
used):
SELECT METHODS WHERE NbILInstructions > 0 ORDER BY MethodCe DESC
#Methods: 341 842 Average # of methods used: 3.41 methods Std
Dev: 5.65
SELECT TOP 5 METHODS WHERE NbILInstructions > 0 ORDER BY MethodCe DESC
|
methods
|
#
IL instructions
|
Efferent coupling at
method level (MethodCe)
|
|
System.Windows.Markup.KnownTypes.CreateKnownElement(KnownElements)
|
1729
|
523
|
|
System.Windows.SystemResourceKey.GetResourceKey(Int16)
|
459
|
225
|
|
System.Windows.SystemResourceKey.get_Resource()
|
595
|
210
|
|
System.Web.Configuration.BrowserCapabilitiesFactory.UpProcess( NameValueCollection,HttpBrowserCapabilities)
|
1129
|
201
|
|
Microsoft.JScript.BuiltinFunction.QuickCall(Object[],Object, JSBuiltin,MethodInfo,VsaEngine)
|
1097
|
163
|
Can you
believe a method that calls 523 other methods? Actually, by decompiling it with
Reflector
(NDepend is integrated with Reflector, but also with VisualStudio), I found out
that CreatKnownElements() is actually
a giant switch that calls 523 properties getter. The same for GetResourceKey() and get_Resource(). So if you decompile UpProcess() you’ll see what looks like a method
that calls 201 others methods! But as you can see it is not by chance, it is
this kind of method that has to support hundreds of browser capabilities.
Most popular types and methods
Alternatively,
NDepend also support the Afferent Coupling (Ca)
that tells about the number of types that use a particular types, in other words the type popularity. Ao measure the most popular types and methods, NDepend
support a ranking metric. The same way as Google knows about most popular
pages, NDepend knows about the most popular types and methods.And the winner
are:
SELECT TOP 10 TYPES ORDER BY TypeRank DESC
|
Full Name
|
Type Rank
|
|
System.Runtime.InteropServices.ComVisibleAttribute
|
2471.8
|
|
System.Object
|
2338.9
|
|
System.Runtime.InteropServices.ClassInterfaceAttribute
|
2028.4
|
|
System.Void
|
1774.7
|
|
System.CLSCompliantAttribute
|
1137.5
|
|
System.Boolean
|
1032.3
|
|
System.Int32
|
1026.2
|
|
System.Runtime.InteropServices.GuidAttribute
|
948.05
|
|
System.String
|
900.83
|
|
System.Runtime.InteropServices.InterfaceTypeAttribute
|
889.51
|
Obviously Object, Boolean, Int32 or String are very popular, but more surprisingly,
the deep integration of .NET with win32 and COM make it so that we get several
interop types in the top 10 most popular types list.
And for the
methods…
SELECT TOP 10 METHODS ORDER BY MethodRank DESC
|
Full Name
|
Method Rank
|
|
System.Object..ctor()
|
16987.3
|
|
System.Exception.set_HResult(Int32)
|
6014.5
|
|
System.Exception.SetErrorCode(Int32)
|
5795.3
|
|
System.Environment.GetResourceString(String)
|
3831.7
|
|
System.Environment.GetResourceFromDefault(String)
|
3300.4
|
|
System.ArgumentNullException..ctor(String)
|
2965.8
|
|
System.String.get_Length()
|
2960.1
|
|
System.SystemException..ctor(String)
|
2862.4
|
|
System.Type.GetTypeFromHandle(RuntimeTypeHandle)
|
2412.9
|
|
System.Exception..ctor(String)
|
2001.5
|
Number of methods / fields for
types
Let’s go back
to more traditional metrics:
SELECT TYPES WHERE IsPublic AND (IsClass OR IsStructure OR IsInterface) ORDER BY NbMethods DESC
#Types: 14 625 Average: 14.35 methods Std Dev: 31.73
SELECT TYPES WHERE IsPublic AND IsInterface ORDER BY NbMethods DESC
#Interfaces:
1 790 Average: 8.55 methods Std Dev: 16.04
Here, I
would say that interfaces are a little bit too chatty, but consider that there
is a lot of interfaces such as com.ms.wfc.html.om.IHTMLStyle
that have 179 methods because of the complexity of the underlying domain represented.
Depth of inheritance
SELECT TYPES WHERE IsPublic AND IsClass ORDER BY DepthOfInheritance DESC
#Types: 12 324 Average: 2.32 Std Dev: 1.48
The depth
of inheritance is not a problem in the design of the .NET framework.
There are no abnormal values, the deepest complex graphical controls have a depth of
around 8.
Conclusion
Personally
I think that the quality is not really a matter of metrics but more a matter of
design, layering, componentization, in a word a matter of dependencies. The
quality also directly depends on good automatic testing and that’s why the next
version of NDepend will soon support test coverage metrics gathered from NCover and TeamSystem.
Imagine the power of this simple query:
SELECT METHODS WHERE CodeWasChanged AND PercentageCoverage < 100
If we just
take account of metric exposed in this post, I think that the .NET Fx v3.5 is
all in all well coded and designed. However, it contains many monsters that would make a sailor blush.
I like to think that when you are responsible for such a vital piece of code,
you sometime need to sacrifice good object models to some super complex methods
or types in order to get better performance at runtime. Are these monsters generated
methods? Are they 100% covered by automatic tests? Who knows?
Posted
Wed, Mar 19 2008 2:05 PM
by
Patrick Smacchia