Implementing Additional Metrics Using the Sonargraph Script API

With the Sonargraph 9.10 release, we added support for two additional OO-metrics “Depth of Inheritance” and “Number of Children” as described by Chidamber and Kemerer. Sonargraph provides a powerful Script API that allows implementing new metrics as Groovy scripts and I needed surprisingly little amount of code for the implementation. This blog post will explain the scripts’ code and the used Sonargraph Script API in detail.

Sonargraph offers the powerful feature to add custom metrics and issues via scripts and I demonstrated in a previous blog post how this can be used to detect “Singletons”. This post concentrates on the metric part of the API. The metric “Depth of Inheritance” is defined as the depth of the inheritance tree as defined by Chidamber and Kemerer, i.e. the maximum length of the current type to the root of the inheritance tree.

The core idea of the API is to process the Sonargraph parser model, which forms a tree structure, with the visitor pattern, i.e. the script provides hooks to visit the parts of the model that are needed for the specific task. For the calculation of the metric “Depth of Inheritance” we need to provide functionality for the onType() method.

Creation of the Script

As a first step, we open a Sonargraph system and create a new script. The generated script template already contains the correct visitor and implementation for the onType-closure. The script does not need any programming language-specific information, so it is best to stick to  ‘coreAccess’. The script is already executable and simply puts all types in the result set. The following screenshot (click to enlarge!) shows the Script view and the generated result containing all types of the NHibernate project.

Script skeleton

Creation of the Metric

As we want to create a new metric, we need to call the method coreAccess.getOrCreateMetricId().

For details and the different overloaded methods, check the JavaDoc for CoreAccess. For our purpose, it’s enough to use the simplest variant of the method getOrCreateMetricId():

def metricId = coreAccess.getOrCreateMetricId("DepthOfInheritance", "Depth of Inheritance", "The depth of the inheritance tree as defined by Chidamber and Kemerer, ...", false, "OO Metrics");

The metric value is added by calling result.addMetricValue() as shown in the highlighted line in the screenshot. Here we simply add a constant value “1.0” for testing purposes.

Adding Custom Script Metric

Getting Supertype Information

Groovy scripts can contain definitions of classes and methods and we will use this feature to implement the metric calculation. To get the depth of the inheritance, we need to extract the supertypes of the current element by using TypeAccess.getReferencedElements() method:

java.util.List<INamedElementAccess> getReferencedElements(Aggregator aggregator, boolean excludeSelf, IDependencyKind... types)

The first parameter “aggregator” specifies the level to which the outgoing dependencies are aggregated. The screenshot below shows the outgoing dependencies of “CacheBatcher.cs” in Sonargraph’s Exploration view. Endpoints are the constructor of CachePutBatch, the field CachePutData.Key and the interface INHibernateLogger to name a few. The “aggregator” provides the common level to which the endpoints of the dependencies are rolled-up. The varargs “types” specifies the dependency types. For the inheritance information, we are only interested in “extends” and “implements”.

Outgoing Dependencies Shown in Exploration View

The next screenshot shows how the function is used to gather the inheritance dependencies. It also demonstrates how throwing a RuntimeException can short-circuit the script execution. This is useful, if you develop the script for a large system and don’t want to get overwhelmed by the debugging information printed to the Console view with the “println” statements. The exception stacktrace within the script is indicated by the error markers.

Extracted Method

We see here that NHibernate.Action.AbstractEntityInsertAction extends two types which appear to have the same name. To check this, we simply open an Exploration view via the context menu of the single element shown in the Metrics Preview tab. We chose the “Advanced” menu option and select “Out” and the dependency types “Extends” and “Implements”:

Exploration View to Examine Inheritance Information

The Exploration view shows that there are two EntityAction types in different source files. Opening those via double-click reveals that they both are partial classes, so the information retrieved from the script is correct in the sense that Sonargraph treats partial classes individually if viewed from the “physical” perspective. The Namespace view allows examining the system from the “logical” perspective, and opening the Exploration view from the Namespace view will show the unified type EntityAction.

Final Computation of the Inheritance Information

The method getDepthOfHierarchy() must obviously be called recursively to gather the complete inheritance information. Since we are mainly interested in the depth of inheritance of the code within the Software system, we stop at the first external type, i.e. a type that is referenced by our code, but that is not part of the workspace. Thus, the calculation includes the first external type in the hierarchy, but stops there. The screenshot below shows the final script that is only 46 lines long with generous formatting.

Script “Depth Of Inheritance”

If the metric values should be calculated automatically with every refresh, add the script to the automatically executed scripts. The easiest way to do this is by opening the context menu of the script’s run configuration “[Default]” and select the item “Add To Script Runner”. The metric and its values are then visible in the Metrics view alongside the other “standard” metrics for the level “Type” and will also be available when SonargraphBuild is used within the CI-pipeline:

It is also possible to add a threshold to the metric by using the alternative method

CoreAccess.getOrCreateMetricId(final String id, final String presentationName, final String description, final boolean isFloat,
final Number lowerThreshold, final Number upperThreshold, final String... categoryIds)

The final script uses the Groovy shorthand notation for method invocations like type.external instead of type.isExternal(). For the NHibernate project with ~570k lines of code, the script finishes within 264 ms on a Intel(R) Core(TM) i9-8950HK CPU @ 2,90GHz.
The top-scorers with a “Depth Of Inheritance” of 9 are the types NHibernate.Test::NHibernate.Test.NHSpecificTest.NH1941.SexEnumStringType and NHibernate.Test::NHibernate.Test.NHSpecificTest.NH2394.EnumStringUserType. The inheritance tree can be examined in detail by opening the Graph view from the context menu, by selecting the “Advanced” menu entry and specifying “Out”, “Transitively” and dependency types “Extends” and “Implements”.

Examine Result with Graph View

The Graph view shows the information about the “levels” which represents the depth of inheritance in this case: We have 10 levels, because the type SexEnumStringType itself is also represented in a separate level which is different from the script, where it is not included.
The metric “Number of Children” is defined as the number of immediated children. This means that no recursion is needed and the script for this metric is even simpler and only takes 22 lines of code. Here the method getReferencingElements() is the inverse of the method getReferencedElements() used in the other script and returns the starting points of the “extends” and “implements” dependencies, i.e. all types that have the current type as a parent:

Script “Number Of Children”

Both scripts are contained in the “core” quality model of the Sonargraph 9.10 release.

Conclusion

This blog post demonstrated how the Script API of Sonargraph can be used to implement new metrics with very little code. The editor within Sonargraph allows the interactive implementation and provides quick feedback about the results. An in-depth analysis of the inheritance tree can be done for the top-scorers individually via the graphical representations in Sonargraph.

Leave a Reply

Your email address will not be published.