Using ScriptCs.Metrics

Tags: ScriptCS, roslyn, metrics

I wrote a previous announcement about a code metrics script pack for ScriptCs. I have been working on it and developing the API since the initial release, so I can now describe how to use it. But first, why have a script pack for something that Visual Studio and the official code metrics calculator already does quite well? ScriptCs is about being able to code in the environment that you want, but nobody in their right mind would consider developing an actual professional application using ScriptCs. The point of the script pack is to expand the capabilities of ScriptCs to support the features that you get in Visual Studio. Then why not simply use the official metrics calculator? Because it is closed source and requires that you calculate metrics on your compiled assemblies only to get your results printed in some obscure XML report. This goes against the spirit of the open coding form in ScriptCs. Furthermore, with programmatic access, you can perform queries over this data using standard LINQ syntax.

Now that we have gotten past the why, we can talk about the what. The script pack will return different metrics based on what you are providing as input, but they will all inherit from ICodeMetric.

public interface ICodeMetric
	int LinesOfCode { get; }
	double MaintainabilityIndex { get; }
	int CyclomaticComplexity { get; }
	string Name { get; }

public interface ITypeCoupling
	string TypeName { get; }

	string Namespace { get; }

	string Assembly { get; }
	IEnumerable<string> UsedMethods { get; }

	IEnumerable<string> UsedProperties { get; }

	IEnumerable<string> UsedEvents { get; }

The individual properties should be self-explanatory. If not, you can read about them on here:

If you pass in the absolute path to a project or a solution, it will return an IProjectMetric. Roslyn requires absolute paths, so you will need to call Path.GetFullPath on your relative path before you pass it. When you have the absolute path, call the calculator like so:

var fullPath = Path.GetFullPath(@"..\..\MySolution.sln");
var projectMetrics = metrics.Calculate(fullPath);

This will return a Task<IEnumerable<ICodeMetric>> IProjectMetric object, where the elemens can be cast to IProjectMetric, which has the following definition:

public interface IProjectMetric : ICodeMetric
	IEnumerable<TypeCoupling> ClassCouplings { get; }

	IEnumerable<INamespaceMetric> NamespaceMetrics { get; }

	IEnumerable<string> ReferencedProjects { get; }

	double RelationalCohesion { get; set; }

In addition to the default metrics mentioned above, project metrics includes:

  • An enumeration of which external types are used,
  • Metrics for included namespaces (more about those later),
  • An enumeration of referenced projects / assemblies,
  • The relational cohesion of the types in the assembly. Relational cohesion is an indicator of whether the types in the assembly are strongly related. NDepend recommends a value between 1. 5 and 4.0 (and who am I to argue with NDepend).

If you pass an arbitrary code snippet to the calculator, it will return an INamespaceMetric object, which is similar to drilling into the NamespaceMetrics property above. Be aware that a namespace may be defined across multiple locations (projects / snippets), so the returned information is only related to the passed input. The INamespaceMetric is defined as follows:

public interface INamespaceMetric : ICodeMetric
	IEnumerable<ITypeCoupling> ClassCouplings { get; }

	int DepthOfInheritance { get; }
	IEnumerable<ITypeMetric> TypeMetrics { get; }

As can be seen, it follows a similar structure as IProjectMetrics:

  • ClassCouplings is an enumeration of the types external to the namespace which are referenced.
  • DepthOfInheritance is that maximum depth of inheritance of any type in the namespace.
  • TypeMetrics are the metrics for the types defined in the namespace.

If we continue to drill down, you get to the type metrics and member metrics. As you can see below the structure is the same.

public interface ITypeMetric : ICodeMetric
	TypeMetricKind Kind { get; }
	IEnumerable<IMemberMetric> MemberMetrics { get; }

	int DepthOfInheritance { get; }
	IEnumerable<ITypeCoupling> ClassCouplings { get; }
	int ClassCoupling { get; }

public interface IMemberMetric : ICodeMetric
	MemberMetricKind Kind { get; }

	IEnumerable<ITypeCoupling> ClassCouplings { get; }

	int NumberOfParameters { get; }

	int NumberOfLocalVariables { get; }

	int? AfferentCoupling { get; }

Now that we've gone through the data definitions I'll leave it up to you to define how you wish to query for the metrics. One thing to note here though, is that the metrics calculations are not as fast as the offical metrics calculator. This is mainly because the Roslyn engine is not very fast when it comes to calculating references. Since a lot of the metrics depend on usage references (ex. coupling and cohesion), this will slow down the calculations.

You can download the script pack from Nuget.

Latest Tweets