We have a large Java/Groovy application with a JUnit test suite containing thousands of tests, that now takes several minutes to run. We wanted to try to reduce the test suite run time, and wanted a report of test times sorted longest to shortest, allowing us to focus on the worst offenders.

We used the JUnit RunListener facility to calculate the test run times and then sort and display a report at the end of the test suite. Specify the test suite class name in the testSuite property (field).

This class is implemented in Groovy, but the Java version would be very similar.

import org.junit.runner.Description
import org.junit.runner.JUnitCore
import org.junit.runner.Result
import org.junit.runner.notification.RunListener

/**
 * Run a JUnit test suite and build a list of all tests (test methods) sorted
 * by run time, in descending order. Run this class as a Java Application.
 * Specify the test suite class in the testSuite property (field).
 */
class RunTestSuiteTimingReport {

  private static Class testSuite = MyTestSuite
  private static long showTestTimesGreaterThanOrEqualToMilliseconds = 1000L

  private static class TimingListener extends RunListener {

    private Map<Description, Long> testStartTimes = [:]
    private Map<Description, Long> testRunTimes = [:]

    @Override
    void testStarted(Description description) throws Exception {
      testStartTimes[description] = System.currentTimeMillis()
    }

    @Override
    void testFinished(Description description) throws Exception {
      long startTime = testStartTimes[description]
      testRunTimes[description] = System.currentTimeMillis() - startTime
    }

    @Override
    void testRunFinished(Result result) throws Exception {
      println "Test suite finished; ${testRunTimes.size()} total tests"
      // descending order, so reverse comparison
      def sortedRunTimes = testRunTimes.sort { a, b -> b.value <=> a.value } 
      for(Description description: sortedRunTimes.keySet()) {
        long runTime = sortedRunTimes[description]
        if (runTime < showTestTimesGreaterThanOrEqualToMilliseconds) {
          break;
        }
        println " - $description: $runTime ms"
      }
    }
  }

  static void main(String... args) {
    JUnitCore core= new JUnitCore()
    core.addListener(new TimingListener())
    core.run(testSuite)
    Runtime.runtime.exit(0)
  }
}

(This article originally appeared in the June 2013 issue of GroovyMag)

This article provides examples of using CodeNarc’s “generic” rules to enforce your own custom best practices.

CodeNarc is a static analysis tool for Groovy source code, enabling monitoring and enforcement of many coding standards and best practices. It contains a set of rules, each of which can be individually enabled and configured.

In addition to the more obvious and straightforward rules for naming, size/complexity, unused code, etc., CodeNarc also contains a set of generic rules that require more specific configuration and customization in order to be useful (and enabled). In this article, we will look at six of these generic rules and how they can be used to enforce your organization’s coding and design standards and best practices.

          StatelessClass rule

The StatelessClass rule checks for non-final fields on a class. The intent of this rule is to check a configured set of classes that should remain “stateless” and reentrant. This rule ignores final fields (either instance or static), and all fields annotated with the @Inject annotation, as well as all classes annotated with the @Immutable transformation.

One example where this can be useful is DAO classes, if you define them as singletons (e.g., Spring beans). In that case, they need to be reentrant, and the only fields on the classes should be for configuration or injecting dependencies.

You can configure this rule to ignore certain fields either by name (ignoreFieldNames) or by type (ignoreFieldTypes). This can be used to ignore fields that hold references to (static) dependencies (such as DataSources) or static configuration.

See Listing 1 for an example StatelessClass rule configuration. Note that we override the rule name property (from its default “StatelessClass”), to give it a name more reflective of its customized purpose. That is usually a good idea for any of the CodeNarc generic rules. The rule is configured to ignore any fields named “dataSource” or fields with names ending in “Dao”. We also give it a custom description, which will be shown in the rule descriptions section of the HTML or XML reports.

StatelessClass {
    name = 'StatelessDao'
    priority = 2
    ignoreFieldNames = 'dataSource, *Dao'
    applyToClassNames = '*Dao'
    description = 'DAO classes should be stateless and reentrant. (Custom)'
}

Listing 1: StatelessClass rule configuration for DAOs

Note that this rule will not catch violations of true statelessness/reentrancy if you define a final field whose value is itself mutable, for example a final HashMap.

          IllegalPackageReference rule

The IllegalPackageReference rule checks for references to any of the packages configured in its packageNames property. This rule can be useful for governance and enforcement of architectural layering. Listing 2 contains an example IllegalPackageReference rule configuration that checks for references to any JDBC-specific packages (groovy.sql, java.sql or javax.sql) from non-DAO classes.

IllegalPackageReference {
    name = 'UseJdbcOnlyInDaoClasses'
    priority = 2
    packageNames = 'groovy.sql, java.sql, javax.sql'
    doNotApplyToClassNames = '*Dao*, com.example.dao.*'
    description = 'Reference to JDBC packages should be restricted to DAO classes. (Custom)'
}

Listing 2: IllegalPackageReference rule configuration

          IllegalClassReference rule

The IllegalClassReference rule checks for reference to any of the classes configured in its classNames property. Like the IllegalPackageReference rule, it can be used for governance and enforcement of architectural layering. Listing 3 contains an example IllegalClassReference rule configuration that checks for illegal DAO references from within model classes.

IllegalClassReference {
    name = 'DoNotReferenceDaoFromModelClasses'
    priority = 2
    classNames = '*Dao'
    applyToClassNames = 'com.example.model.*'
    description = 'Do not reference DAOs from model classes.'
}

Listing 3: IllegalClassReference rule configuration: Do not reference DAOs from model classes

The IllegalClassReference rule can also be used to flag references to obsolete or deprecated classes. Listing 4 contains an example rule configuration that checks for references to an old XmlUtil class that was used in our Java code to parse XML, but that is now obsolete because of the XML parsing provided by Groovy’s wonderful XmlSlurper (or XmlParser).

IllegalClassReference {
    name = 'DoNotUseXmlUtil'
    priority = 2
    classNames = 'com.example.xml.XmlUtil'
    applyToClassNames = '*'
    description = 'Do not use the XmlUtil class to parse XML. Use the Groovy XmlSlurper class instead. (Custom)'
}

Listing 4: IllegalClassReference rule configuration: Do not use obsolete class

          RequiredRegex rule

The RequiredRegex rule checks for a specified regular expression that must exist within the source code. Listing 5 contains an example rule configuration that verifies that each source file contains a javadoc comment containing @version … $Revision … @author … $Author … $Date. The (?s) prefix within the regular expression string enables dotall mode, which means that the expression . matches any character, including a line terminator, allowing the pattern to span multiple lines.

RequiredRegex {
    name = 'ClassJavadocTags'
    priority = 2
    regex = '(?s)\\/\\*\\*.*\\@version.*\\$Revision.*\\$.*\\@author.*\\$Author.*\\$.*\\$Date.*\\$'
    description = 'Class javadoc must include $Revision, $Author and $Date tags. (Custom)'
}

Listing 5: RequiredRegex rule configuration

          IllegalRegex rule

The IllegalRegex rule checks for a specified illegal regular expression within a source file. Listing 6 contains an example rule configuration that checks for empty @see tags within javadoc. We had an old class javadoc template in place for a few years that included an empty @see tag, so our codebase was littered with them. Perhaps you have similar junk scattered around your codebase? The (?m) prefix within the regular expression string enables multiline mode, which means that the $ expression matches the end of the line.

IllegalRegex {
    name = 'EmptySeeTag'
    priority = 2
    regex = /(?m)\@see\s*$/
    description = 'Class javadoc must not include empty @see tags. (Custom)'
}

Listing 6: IllegalRegex rule configuration

          RequiredString rule

The RequiredString rule checks for a specified text string that must exist within the source files. Listing 7 contains an example rule configuration that checks that each source file contains the Apache 2.0 License header. Note that, as configured, it only checks for a single line of the license. That should be good enough to ensure that you don’t forget to include the license within your source files. You can change the configuration to check for the exact, entire license, if you wish, but you must configure the whitespace exactly, and the copyright year must be correct and consistent across all source files.

RequiredStringRule {
    name = 'ApacheLicenseRequired'
    string = 'Licensed under the Apache License, Version 2.0 (the "License");'
    violationMessage = 'The Apache License 2.0 comment is missing from the source file'
    description = 'Checks that all source files contain the Apache License 2.0 comment'
}

Listing 7: RequiredString rule configuration

          Learn More

(This article originally appeared in the November 2012 issue of GroovyMag)

The ABC metric can be used to assess the size of Groovy code, from single methods all the way up through entire codebases. In this article, we see how to measure ABC using GMetrics, and how to enforce an upper limit using CodeNarc to improve the maintainability of our code.

The ABC metric was developed by Jerry Fitzpatrick in 1997 to provide a measure of software size and also address some of the deficiencies of using lines of code and other size metrics. It is defined as a function of the number of assignments, branches and conditions within a program [1]. It can be used to objectively and consistently measure the size of methods, classes and entire programs or libraries. The ABC metric can be used to compare relative code size across multiple codebases and even across different implementation languages.

          Other Options for measuring code size

The most familiar and most-used approach for measuring code size is lines of code (LOC). It is simple and quick to calculate, and has a straightforward, intuitive appeal. But it also has several drawbacks, including:

  • It is affected by coding style, including stylistic conventions such as placement of opening braces (in C-based languages) and standards for line-breaks and line-length.
  • It is not comparable across different implementation languages or platforms.
  • There are several variations in how LOC can be calculated. Should it include blank lines? Should it include comments?

Function points are another well-known approach for measuring program size. They allow estimating the amount of business functionality of a program based on the number of outputs, inquiries, inputs, internal files, and external interfaces. The approach analyzes a program specification rather than the source code, enabling estimation of program size before its implementation. That has several benefits, but function points suffer from limitations that restrict their applicability and popularity, including:

  • Function points are difficult and costly to calculate. They cannot be automatically generated through static analysis of source code or specifications. They require labor-intensive, manual, skilled analysis.
  • Depending on how it is performed, the function point analysis can results in a subjective function point counts. Different function point counters may arrive at different results.
  • There are several variations, standards and customizations of the original function point definition.
  • Function points are not appropriate for measuring the size of individual classes or methods.
  • Function point analysis discounts the complexity and effort required in modern user interfaces.

          ABC: Why should you care?

The ABC metric was designed to specifically address many of the shortcomings described above and provide a language-agnostic measurement of program size.

Here are some reasons why you should care:

  • The ABC metric provides an objective, consistent, easy and automatic way to measure code size.
  • The ABC metric is independent of coding style and implementation language. It can be used to compare relative sizes of programs and libraries across different projects and different technology stacks.
  • You can use the ABC metric value (magnitude) as the unit of measurement to assess and enforce size thresholds for methods and classes, with the goal of producing clean code – specifically small, simple methods and classes. Smaller methods and classes tend to be easier to understand, test and maintain.
  • You can use the ABC size metric in combination with other metrics, such as cyclomatic complexity or code coverage, as inputs for more holistic analysis and planning.
  • Measuring and monitoring method and class size can be used as another layer of automated code review. It can flag new code containing excessively-large methods or classes, identifying candidates for refactoring and/or more intensive testing. Similarly, you can use it to assess an existing (legacy) code base and point toward potential trouble spots.

          The formula

For Groovy code, the formula for calculating the ABC metric is:

Start with A=0, B=0, C=0, and then:

  • Add one to the assignment count (A) for each occurrence of an assignment      operator, excluding constant declarations: =,      *=, /=, %=, +=, <<=, >>=,      &=, |=, ^=, and >>>=.
  • Add one to the assignment count (A) for each occurrence of an increment or      decrement operator (prefix or postfix): ++      and –.
  • Add one to the branch count (B) for each method call or property access.
  • Add one to the branch count (B) for each occurrence of the new operator.
  • Add one to the condition count (C) for each use of a conditional operator: ==, !=, <=, >=, <, >, <=>, =~ and ==~.
  • Add one to the condition count (C) for each use of the following keywords: else, case, default, try, catch and ?.
  • Add one to the condition count (C) for each unary conditional expression.      These are cases where a single variable/field/value is treated as a      boolean value. Examples include if (x) and return !ready.

The resulting raw ABC value represents a three-dimensional vector, and is written as an ordered triplet of integers: <A, B, C>. For example, a raw ABC value of <7, 2, 4> would represent A=7, B=2 and C=4.

That ABC vector is converted into a single, scalar value by calculating the magnitude of the vector:

|ABC| = sqrt((A*A)+(B*B)+(C*C))

For example, the ABC vector of <7, 2, 4> would be converted to sqrt(49 + 4 + 16) == sqrt(69) == 8.3066. The ABC score is conventionally rounded to a single decimal place, so that would be 8.3.

          Some examples

Listing 1 shows a simple Groovy method. The source lines of the methods are annotated to indicate assignments (A), branches (B) and conditions (C). The findAdministrator() method contains two branches (B) and one condition (C). Note that the line PlanAdministrator.findById(plan.id) contains both a method call and a property access, and so contributes two to the branch (B) count. The method’s ABC vector is <0, 2, 1> which results in a magnitude of 2.2.

PlanAdministrator findAdministrator(Plan plan) {
    if (!plan) {                            // C
        return null
    }
    PlanAdministrator.findById(plan.id)     // BB
}

Listing 1: Sample Groovy method with an ABC vector of <0, 2, 1> and magnitude of 2.2

Listing 2 shows another annotated method, from a Grails application. Its ABC vector is <2, 8, 1> which results in a magnitude of 8.3.

boolean isPlanReferencedByActivity(Plan plan) {
    def c = PlanActivity.createCriteria()   // AB
    def refCount = c.count {                // AB
       eq("plan", plan)                      // B
       or {                                  // B
         eq("standardCode", plan.code)       // BB
         eq("overrideCode", plan.code)       // BB
       }
    }
    return (refCount > 0)                   // C
}

Listing 2: Another sample Groovy method, with an ABC vector of <2, 8, 1> and magnitude of 8.3

In Groovy and Grails, closures are often used in place of methods. If a class field is initialized to a closure, then the ABC value for that closure can be analyzed just like a method. Listing 3 shows a Grails controller action (closure). Its ABC vector is <15, 39, 6> which results in a magnitude of 42.2. It was reduced somewhat from the original, to fit in the listing. Even in its reduced form, it includes a several assignments (15, actually), and branches, including nested if statements. The closure contains several distinct sections. Perhaps it is worth reviewing and possibly refactoring?

def updatePid = { CustomerCommand cmd ->
   def customer = getCurrentCustomer()
   if (customer) {
      cmd.id = customer.id
      cmd.origCustNum = customer.custNum
      cmd.origSsn = customer.ssn
      cmd.origPid = customer.pid
      cmd.plan = customer.plan
      boolean isSsn = isPidFromSSN(cmd.plan)
      if (isSsn) {
         cmd.pid = cmd.ssn
      } else {
         cmd.pid = cmd.custNum
      }
      if (!cmd.pid.equals(cmd.origPid)) {
         def results = changePid(cmd, isSsn)
         if (results.success) {
            flash.message = "Updated PID ${cmd.pid}"
            def planId = cmd.plan.id
            def p = [planId:planId, partId:cmd.id]
            redirect(action:'show', params:p)
         } else {

           def errMsg = results.errorMessage
           if (results.field) {
              cmd.errors.rejectValue(results.field,
                 "ignore", errMsg)
           } else {
              cmd.errors.reject("part.bad", errMsg)
           }       
        }
      } else {
         flash.message = "No change in PID"
         redirect(action:'show',
         params:[partId:cmd.id])
      }   
      renderForCustomer(cmd, 'changePid')
   }  
}

Listing 3: Groovy method, with an ABC vector of <15, 39, 6> and magnitude of 42.2

          How much is too much?

Guidelines and thresholds for method and class size often generate debate and disagreement. It is impossible to establish limits that are acceptable to everyone and that apply in every situation. Nevertheless, having guidelines in place can help to improve code quality, consistency and maintainability. The specific numbers chosen are inevitably somewhat arbitrary. In some cases, methods or classes that exceed the size threshold can be justified, but many times such code warrants further attention and review for refactoring opportunities.

A blog post by Jake Scruggs specifies risk classifications for interpreting ABC magnitude values from the Flog tool (for Ruby). His guidelines for ABC values include:

  • 0 – 10 = Awesome
  • 11 – 20 = Good enough
  • 21 – 40 = Might need refactoring
  • 41 – 60 = Possible to justify

Anything above 60 is dangerous or worse. [4]

The folks behind the eXcentia Sonar ABC Metric Plugin (for Java) define a table of risk classifications for ABC scores for both methods and classes. They have based their classifications on “exhaustive experiments with real and very large projects” (translated):

  • 0 – 15 = No risk
  • 15 – 30 = Moderate risk
  • 30 – 45 = High risk
  • 45 – 60 = Very high risk

They also provide a similar table for class-level ABC values. [5]

Even though the Flog tool is for Ruby code, and the Sonar ABC Plugin is for Java code, the ABC score is calculated similarly (though adapted somewhat to account for language specifics) and the guidelines are transferable. By any of the above guidelines, the method in Listing 3 is too big.

Of course, you will need to come up with your own threshold based on your team’s conventions and sensibilities.

          Measurement: GMetrics

Fortunately, it is quite easy to calculate the ABC metric, either in an ad hoc way, or as part of an automated build process, including continuous integration. There are tools for several popular languages. The GMetrics (http://gmetrics.sourceforge.net/) tool calculates the ABC metric for Groovy source code (among other metrics).

Figure 1 shows an example of the standard GMetrics HTML report. It uses the MetricSet shown in Listing 4, which includes only the ABC metric. The report includes the default columns for both the total and average metric values at the method, class and package levels.

Note that the ABC metric value is only calculated at the method level. The values for the class and package levels are aggregated from the method-level values.

Figure_1

Figure 1: GMetrics HTML Report.

metricset {
   ABC {
   }
}

Listing 4: GMetrics metric set specifying only the ABC metric

Figure 2 shows a GMetrics Single-Series HTML report of the methods with the highest ABC values, with a value of at least 20.0, in descending order. This type of report requires that we configure a metric, level and function to specify a single series of data. In this case, we specified the “ABC” metric, at the “method” level and the “total” function value. We also specified a sort value (“descending”) and a greaterThan value (“20.0”) and a maxResults (“10”) to further configure and customize the report. Listing 5 shows the corresponding Ant script that generates the GMetrics reports in Figures 1 and 2.

Figure_2

Figure 2: GMetrics Single-Series HTML Report: largest methods (ABC)

<taskdef name="gmetrics" classname="org.gmetrics.ant.GMetricsTask"/>
<target name="gmetrics-abc">
   <gmetrics metricSetFile="gmetrics-abc-metricset.groovy">
      <report type="org.gmetrics.report.BasicHtmlReportWriter">
         <option name="outputFile"
            value="reports/GMetricsReport.html" />
         <option name="title" value="ABC Metric Example" />
      </report>
      <report type="org.gmetrics.report.SingleSeriesHtmlReportWriter">
         <option name="outputFile"
            value="reports/LargestABCMethods.html" />
         <option name="title" value="Largest Methods (ABC)" />
        <option name="metric" value="ABC" />
        <option name="level" value="method" />
        <option name="function" value="total" />
        <option name="maxResults" value="10" />
        <option name="sort" value="descending" />
        <option name="greaterThan" value="20.0" />
      </report>
      <fileset dir="../src/grails">
         <include name="**/*.groovy"/>
      </fileset>
   </gmetrics>
</target>

Listing 5: Ant script for GMetrics

The GMetrics Ant Task is included with the GMetrics distribution. There are also GMetrics plugins for Grails, Griffon and Sonar. See [6] for more information.

          Enforcement: CodeNarc

The CodeNarc (http://codenarc.sourceforge.net) tool analyzes Groovy source code for defects, bad practices, inconsistencies, unused code, etc. Like GMetrics, it comes with an Ant Task.

CodeNarc includes an AbcComplexity rule, which uses the GMetrics ABC metric under the covers. The CodeNarc rule enables specifying upper limits for method size as well as class-level average method size. Note that the AbcComplexity rule was improperly named within CodeNarc, because the ABC metric measures program size, not complexity.

Listing 6 shows the CodeNarc ruleset containing the AbcComplexity rule. The ruleset customizes the maxMethodComplexity rule property to a value of 20, rather than the overly-generous default of 60. It also explicitly sets the maxClassAverageMethodComplexity rule property to 60 (which also happens to be the default), so that we do not see any class-level violations for now.

ruleset {
   AbcComplexity {
      maxMethodComplexity = 20
      maxClassAverageMethodComplexity = 60
   }
}

Listing 6: CodeNarc ruleset

Listing 7 shows the Ant build file that executes CodeNarc with that ruleset. Figure 3 shows the CodeNarc report (excerpt), including the violations of the AbcComplexity rule. Note that we have configured the CodeNarc Ant Task to fail the build of there are any priority 1 or 2 violations. Thus, we are enforcing an upper limit on the size of the methods within our code base.

<taskdef name="codenarc" classname="org.codenarc.ant.CodeNarcTask"/>
<target name="codenarc-abc">
   <codenarc
      ruleSetFiles="codenarc-abc-ruleset.groovy"
      maxPriority1Violations="0"
      maxPriority2Violations="0">
      <report type="html" toFile="reports/CodeNarcReport.html" title="ABC Example"/>
      <fileset dir="../src/grails">
         <include name="**/*.groovy"/>
      </fileset>
   </codenarc>
</target>

Listing 7: Ant script for CodeNarc

Figure_3

Figure 3: CodeNarc AbcComplexity rule violations

The CodeNarc Ant Task is included with the CodeNarc distribution. There are also CodeNarc plugins for Grails, Griffon, Gradle, Maven, Hudon/Jenkins and Sonar, as well as IDE plugins for IntelliJ IDEA and Eclipse. See [7] for more information.

          Conclusion

The ABC metric is a useful and easy way to assess a code base and get a valuable perspective on its maintainability. For Groovy code, we can measure method and class size and identify potential trouble spots with GMetrics, and enforce an upper threshold for method size with CodeNarc.

          References

[1] The ABC Metric specification – originally published in The C++ Report, June 1997.

[2] Function Point Analysis – definition and description.

[3] Function Points Are Fantasy Points – opinionated but compelling blog post about the deficiencies of function points.

[4] This blog post describes some guidelines for interpreting the ABC score. The post refers to the Flog tool for Ruby code, but the ABC score is calculated similarly (though adapted somewhat to account for language specifics) and the guidelines should be transferable.

[5] eXcentia Sonar ABC Metric Plugin – the web page includes a table of risk classifications for ABC scores for both methods and classes.

[6] GMetrics – provides calculation and reporting of several metrics for Groovy source code, including the ABC metric.

[7] CodeNarc – analyzes Groovy source code for defects, bad practices, unused code and more.

(This article originally appeared in the May 2012 issue of GroovyMag)

The cyclomatic complexity metric can be used to assess the complexity and maintainability of Groovy code. In this article, we see how to measure cyclomatic complexity using GMetrics, and how to enforce an upper limit for complexity using CodeNarc.

There are many ways to evaluate software, and a large number of metrics that we can apply to source code. One particularly useful and popular metric is cyclomatic complexity, proposed by Thomas McCabe in 1976 [1].

Cyclomatic complexity is defined in terms of the control flow graph for a piece of code (e.g. a method). But in practice, it measures the number of distinct (linearly independent) paths through the code. It does this by counting the number of branching statements and operators within the source code. See [1] for a formal definition of the metric and a discussion of the mathematical foundations. Despite the theoretical underpinnings, the cyclomatic complexity metric is quite straightforward and intuitive.

Why should you care?

Cyclomatic complexity is by no means a silver bullet, but it has proven its value over several decades, and continues to be one of the most popular metrics applied to source code. It is language-agnostic – cyclomatic complexity applies just as well to Java and Groovy as it did to C or Fortran.

Here are some reasons why you should care:

  • There is a strong correlation with defects. Code with higher cyclomatic complexity tends to be more likely to contain defects and cause problems [3].
  • The cyclomatic complexity value can serve as a guideline for the minimum number of required tests to properly cover the branches within a method.
  • Lower-complexity methods tend to be easier to understand, test and maintain.
  • Measuring and monitoring cyclomatic complexity can be used as another layer of automated code review. It can flag new code that is overly complex, identifying candidates for refactoring and/or more intensive testing. Similarly, you can use it to assess an existing (legacy) code base and point toward potential trouble spots.

The formula

For Groovy code, the formula for calculating cyclomatic complexity is:

Start with a initial (default) value of one (1). Add one (1) for each occurrence of the following:

  • if statement
  • while statement
  • for statement
  • case statement
  • catch statement
  • && and || boolean operations
  • ?: ternary operator and ?: Elvis operator.
  • ?. null-check operator

Some examples

Listing 1 shows a simple Groovy method with a cyclomatic complexity of four (4). Based on the formula described above, we can see that the result is the sum of:

  • The initial/default value of one
  • Plus one for the if statement
  • Plus one for the && operator
  • Plus one for the ternary operator (?:)
private String checkForError() {
    if (isReady() && count > 1) {
        return count < 10 ? 'Too small' : 'Too big'
    }
    return 'None'
}

Listing 1: Sample Groovy method with a cyclomatic complexity value of 4

Now let us briefly turn to the dark side. Listing 2 shows a Groovy method with a much higher cyclomatic complexity value (21). It is a long method, with lots of branching, including nested if statements. The method contains several distinct sections, and is clearly doing a bunch of different things, recklessly violating the Single Responsibility Principle along the way. Oh, the humanity! Hopefully, you can agree that this is not the way we should be writing code. The method’s high cyclomatic complexity value is a clue that something is wrong, and indicates the potential for issues with quality and maintainability.

private int processIncomeRecords(List records) {
  int processedRecordCount = 0
  records?.each {IncomeRecord rec ->
    int errorCount = 0
    def isDataEmpty = false
    if (!isDataEmpty) {
      if ((isNullOrEmpty(rec.planId)) ||
          (isNullOrEmpty(rec.productId)) ||
          (isNullOrEmpty(rec.custId)) ||
          (isNullOrEmpty(rec.custType)) ||
          (isNullOrEmpty(rec.custAccount)) ||
          (isNullOrEmpty(rec.pptIdType)) ||
          (isNullOrEmpty(rec.withdrawalAmt)) ||
          (isNullOrEmpty(rec.benefitBase))) {
        errors.add("Empty cust: $rec.custId")
        errorCount++
      }
      if (rec.pptIdType == "S") {
        if (rec.custId?.trim().length() == 9) {
          def ssn =
            (rec.custId?.trim()).substring(0,3)+'-'+
            (rec.custId?.trim()).substring(3,5)+'-'+
            (rec.custId?.trim()).substring(5,9)
          rec.pid = incomeDao.getPid(ssn,rec.planId)
          if (rec.pid == null) {
            errors.add("No PID $rec.custId")
            errorCount++
          }
        } else {
          errorCount++
        }
      } else if (rec.pptIdType == "O") {
        if (rec.custId.isInteger()) {
          rec.pid = Integer.parseInt(rec.custId)
        } else {
          errors.add("Invalid PID;plan $rec.planId")
          errorCount++
        }
        if (incomeDao.validatePid(rec) == 0) {
          errors.add("No PID $rec.custId")
          errorCount++
        }
      } else {
        errors.add("No PID Cust $rec.custId")
        errorCount++
      }
      def productIdList = incomeDao.getProductIds(
          rec.planId?.trim(),rec.providerId?.trim())
      if (!productIdList.contains(rec.productId)) {
        errors.add("No match ProdID $rec.productId")
        errorCount++
      }
      if (hasDuplicateKeys(records, rec.custId,
          rec.productId, rec.custType)) {
        errors.add("Dup:$rec.custId;$rec.productId")
        errorCount++
      }
      IncomeRecord convertedRecord
      if (isValidDataRecord(rec)) {
        convertedRecord = convertRecord(rec)
      } else {
        isDataEmpty = true
        errors.add("Invalid Record $rec.custId")
      }
      if ((!isDataEmpty) && (errorCount == 0)) {
        processRecord(convertedRecord)
        processedRecordCount++
      }
    }
  }
  return processedRecordCount
}

Listing 2: Sample Groovy method with a high cyclomatic complexity value (21)

As another example, Listing 3 shows a method with an even higher cyclomatic complexity score (28). At first glance, this method may not look so bad, but it contains subtle complexity and duplication. The same convoluted and dense pattern of null-check, Elvis operators and calls to trim() is repeated eight times. One way to significantly reduce the cyclomatic complexity of this method (and the class) would be to abstract and consolidate that complicated property access into a helper method. As seen in this example, duplication is often a significant contributor to a high complexity score.

List<Participant> getParticipants(Date runDate) {
  final SQL = '{ call ReadParticipants (?) }'
  def sql = new Sql(dataSource)
  def participants=[]
  def list = sql.rows(SQL, [asSqlDate(runDate)])
  list.each { result ->
    Participant part = new Participant()
    part.recordType = 'D'
    part.iid = (result?.IID?:'')
    part.firstName = (result?.FName?:'')?.trim()
    part.lastName = (result?.LName?:'')?.trim()
    part.initials = (result?.MName?:'')?.trim()
    part.address1 = (result?.Addr1?:'')?.trim()
    part.address2 = (result?.Addr2?:'')?.trim()
    part.city = (result?.City?:'')?.trim()
    part.state = (result?.State?:'')?.trim()
    part.zip = (result?.Zip?:'')?.trim()
    participants << part
  }
  return participants
}

Listing 3: Groovy method with a high cyclomatic complexity value (28) from duplication

How much is too much?

The value of ten (10) is often considered as the threshold between acceptable (low risk) and unacceptably-complex (higher risk) code. As McCabe ([1]) says, “The particular upper bound that has been used for cyclomatic complexity is 10 which seems like a reasonable, but not magical, upper limit.”. Other sources cite 15 as a useful threshold, and/or draw further delineations between low/medium/high/unacceptable complexity values.

By pretty much everyone’s guidelines, the methods in Listing 2 and Listing 3 are too complex.

Under-promise and over-deliver

Keep in mind that cyclomatic complexity focuses on the conditional logic (branching) within the code. That is reasonable because much of the effort and potential for errors in code is typically centered around the decisions and branches within the logic. That means that cyclomatic complexity is not a measure of the size or amount of code. You can have a thousand-line method with little or no conditional logic and it will have a low cyclomatic complexity score. As with any metric, it can be a valuable data point, but it does not tell the entire story.

Measuring and minimizing complexity is in line with the preference for clean code. Long methods are  bad. Short methods are good. Deep nesting of conditional code is bad. Straightforward logic is good. It encourages small, simple methods and promotes the practice of refactoring to reduce complexity.

By definition, refactoring to “simpler” methods does not affect the external behavior of the class/method. The real benefit is in the improved clarity and simplicity of the code. Smaller chunks are typically easier to understand, and there are fewer dark corners for bugs and incorrect assumptions to hide.

Measurement: GMetrics

Fortunately, it is quite easy to calculate cyclomatic complexity, either in an ad hoc way, or as part of an automated build process, including continuous integration. There are tools for most popular languages. The GMetrics (http://gmetrics.sourceforge.net/) tool calculates cyclomatic complexity for Groovy source code (among other metrics).

Figure 1 shows an example of the standard GMetrics HTML report. It uses the default MetricSet, which includes three metrics: CyclomaticComplexity, ClassLineCount and MethodLineCount. The report includes columns for both the total and average metric values at the method, class and package levels.

Note that the cyclomatic complexity value is only calculated at the method level. The values for the class and package levels are aggregated from the method-level values. Incidentally, the sum (total) of the cyclomatic complexity for the methods within a class is a related metric called weighted methods per class, and not surprisingly, represents the complexity of the class as a whole.

Sample GMetrics report

Figure 1: GMetrics Standard HTML Report.

Figure 2 shows a GMetrics Single-Series HTML report of the methods with the highest cyclomatic complexity, with a value of at least 10.0, in descending order. This type of report requires that we configure a metric, level and function to specify a single series of data. In this case, we specified the “CyclomaticComplexity” metric, at the “method” level and the “total” function value. We also specified a sort value (“descending”) and a greaterThan value (“10.0”) and a maxResults (“10”) to further configure and customize the report. Listing 4 shows the corresponding Ant script that generates the GMetrics reports in Figures 1 and 2.

Highest complexity methods (GMetrics report)

Figure 2: GMetrics Single-Series HTML Report: highest-complexity methods

<taskdef name="gmetrics" classname="org.gmetrics.ant.GMetricsTask"/>
<target name="gmetrics-cc">
  <gmetrics>
     <report type="org.gmetrics.report.BasicHtmlReportWriter">
        <option name="outputFile"
           value="reports/GMetricsReport.html" />
        <option name="title" value="Sample" />
     </report>
     <report type="org.gmetrics.report.SingleSeriesHtmlReportWriter">
        <option name="outputFile"
           value="reports/HighestComplexityMethods.html" />
        <option name="title" value="Highest Complexity Methods" />
        <option name="metric" value="CyclomaticComplexity" />
        <option name="level" value="method" />
        <option name="function" value="total" />
        <option name="maxResults" value="10" />
        <option name="sort" value="descending" />
        <option name="greaterThan" value="10.0" />
     </report>
     <fileset dir="../src/main/java">
        <include name="**/*.groovy"/>
     </fileset>
  </gmetrics>
</target>

Listing 4: Ant script for GMetrics

The GMetrics Ant Task is included with the GMetrics distribution. There are also GMetrics plugins for Grails, Griffon and Sonar. See [4] for more information.

Enforcement: CodeNarc

The CodeNarc (http://codenarc.sourceforge.net) tool analyzes Groovy source code for defects, bad practices, inconsistencies, unused code, etc.. Like GMetrics, it comes with an Ant Task.

CodeNarc includes a CyclomaticComplexity rule, which uses the GMetrics CyclomaticComplexity metric under the covers. The CodeNarc rule enables specifying upper limits for method complexity as well as class-level average method complexity.

Listing 5 shows the CodeNarc ruleset containing the CyclomaticComplexity rule. The ruleset customizes the maxMethodComplexity rule property to a value of 10, rather than the overly-generous default of 20. It also sets the maxClassAverageMethodComplexity rule property to 30, so that we do not see any class-level violations for now.

ruleset {
    CyclomaticComplexity {
        maxMethodComplexity = 10
        maxClassAverageMethodComplexity = 30
    }
}

Listing 5: CodeNarc ruleset

Listing 6 shows the Ant build file that executes CodeNarc with that ruleset. Figure 3 shows the CodeNarc report (excerpt), including the violations of the CyclomaticComplexity rule. Note that we have configured the CodeNarc Ant Task to fail the build of there are any priority 1 or 2 violations. Thus, we are enforcing an upper limit on the complexity of the methods within our code base.

<taskdef name="codenarc" classname="org.codenarc.ant.CodeNarcTask"/>
<target name="codenarc-cc">
    <codenarc
            ruleSetFiles="codenarc-cc-ruleset.groovy"
            maxPriority1Violations="0"
            maxPriority2Violations="0">
        <report type="html" toFile="reports/CodeNarcReport.html"
           title="Sample"/>
        <fileset dir="../src/main/java">
        <include name="**/*.groovy"/>
        </fileset>
    </codenarc>
</target>

Listing 6: Ant script for CodeNarc

CodeNarc report

Figure 3: CodeNarc CyclomaticComplexity rule violations

The CodeNarc Ant Task is included with the CodeNarc distribution. There are also CodeNarc plugins for Grails, Griffon, Gradle, Maven, Hudon/Jenkins and Sonar, as well as IDE plugins for IntelliJ IDEA and Eclipse. See [5] for more information.

Conclusion

The cyclomatic complexity metric is a useful and easy way to assess a code base and get a valuable perspective on its maintainability. For Groovy code, we can measure complexity and identify potential trouble spots with GMetrics, and enforce an upper threshold for complexity with CodeNarc.

References

[1] “A Complexity Measure” – the original paper describing cyclomatic complexity in IEEE Transactions on Software Engineering Vol. 2, No. 4, p. 308 (1976).

[2] “In pursuit of code quality: Monitoring cyclomatic complexity” – Andrew Glover, developerWorks, 2006.

[3] “McCabe Cyclomatic Complexity: the proof in the pudding” – Rich Sharpe, ENERJY, 2008.

[4] GMetrics – provides calculation and reporting of several metrics for Groovy source code, including cyclomatic complexity.

[5] CodeNarc – analyzes Groovy source code for defects, bad practices, unused code and more.

Follow

Get every new post delivered to your Inbox.