Unambiguously analyzing metrics

Software metrics are objective measurements of particular aspects of code– for instance, Cyclomatic complexity measures complexity without any regard for why code contains a certain number of paths. For metrics to be useful, baby, they must be applied subjectively. In the case of complexity, there may be circumstances that warrant such code (although, I’ve yet to find complex code that still can’t benefit from refactoring). I’ve also found that, on the whole, metrics are more copasetic when combined with other metrics and trended– for instance, complexity alone is somewhat interesting, but pairing complexity with code coverage paints a much more detailed metric that bears understanding. High complexity with low coverage is clearly more risky than the same complexity with high code coverage– even the CRAP metric holds this relationship.

One particular hip metric that I find helpful is the ratio of copy and pasted code within a code base as unknown copy and pasted code will haunt you, man. For instance, copy and pasted code replicates bugs and poorly coded algorithms to name a few nefarious aspects; consequently, understanding what code has been replicated can help teams refactor offending code. Having run various copy and paste analyzers on more code bases than I care to admit, (and because it’s my bag) I’ve found that all code bases have a certain level of offending code that triggers a copy and paste detection. One particular tool, CPD, is nice enough to create a report containing the offending code like so:

<duplication lines="7" tokens="53">
 <file line="36" path="cbd4/blackjack/src/com/stelligent/blackjack/Hand.java"/>
 <file line="42" path="cbd4/blackjack/src/com/stelligent/blackjack/Hand.java"/>
 <file line="48" path="cbd4/blackjack/src/com/stelligent/blackjack/Hand.java"/>
 <codefragment>
 <![CDATA[
    } else if (first.equals("Jack")) {
      if(!second.equals("King") && !second.equals("Queen") && !second.equals("Jack")){
         return Integer.parseInt(second) + 10;
     } else {
      return 20;
    }
   }else if(first.equals("9")){
]]>
</codefragment>
</duplication>

As you can see, CPD reports the total number of lines of copy and pasted code and where that bogue code can be found. This data is certainly helpful; however, it doesn’t paint the entire story– while 7 lines doesn’t seem like all that much code, you’d probably reconsider if it were 7 lines of code in a 30 line code base or more realistically– 700 lines in a few thousand line code base. Therein lies the catch– CPD’s data is really only helpful when viewed on the whole (or a ratio– that is, total lines of copy and pasted code over total lines of code). Unfortunately, CPD doesn’t report the total lines of code scanned– only the total lines of copy and pasted code. For instance, in this sample code base, there were 9 suspected copy and pasted code fragments totaling about 120 lines of code (or CPLOC).

Luckily, there’s another handy tool which reports the total lines of code (or LOC) in a code base– JavaNCSS. Running JavaNCSS yielded a value of about 610 LOC; therefore the ratio of copy and pasted code is CPLOC/LOC or 120/610, which is roughly 20%.

20% CPLOC is probably a bad thing– at a minimum is is worth knowing about. 20% today might not be too important to know, but knowing that it increased to 25% next week would be an indication that things are degrading– likewise, seeing a value decrease over time indicates the code base is actively being improved. Yet, how can teams possibly monitor this trippin’ data?

Reports are hip, but in truth, reports by tools like CPD are essentially read once– the first time they are generated. After that, it’s anyone’s guess when someone will actively read the report again. Hence, I find it particularly helpful to essentially throw the report out and let the build itself proactively tell me when a particular metric gets out of hand. This essentially means that my build has to monitor a particular metric– and in the case of the CPLOC ratio, my build has to gather data from two sources– JavaNCSS’s report and CPD’s.

Fortunately, this is easy with Groovy– if your instance, you are using Ant for builds, you can first generate the two reports as follows:

<target name="cpd">
 <mkdir dir="target/reports"/>
 <taskdef name="cpd" classname="net.sourceforge.pmd.cpd.CPDTask"
   classpathref="classpath"/>
 <cpd minimumTokenCount="10" outputFile="target/reports/cpd.xml" format="xml">
  <fileset dir="src">
   <include name="**/*.java"/>
  </fileset>
 </cpd>
</target>

The code above generates a CPD XML report from all the code in a src directory and the following code creates a JavaNCSS report from the same code base:

<target name="javancss">
 <taskdef name="javancss" classname="javancss.JavancssAntTask"
   classpathref="classpath" />
 <javancss srcdir="src" generateReport="true"
   abortOnFail="true" ccnPerFuncMax="100"
   outputfile="target/reports/javancss_metrics.xml" format="xml" />
</target>

The only high-level step left to do is to put the two metrics together; however, this step actually takes a few sub-steps, baby. For instance, obtaining the total lines of CPLOC requires iterating over a collection of duplication elements in the CPD xml file. Consequently, the following steps detail the effort required to obtain this metric:

  • parse the JavaNCSS xml report and obtain the total LOC
  • parse the CPD xml report and obtain the total CPLOC
  • divide the two and compare the result to some threshold
  • if the threshold is exceeded, fail the build

Groovy, by the way, is particularly well suited for such a task (as if you didn’t know that, man?)– parsing XML with Groovy is practically effortless– like disco dancing, eh? For instance, obtaining the total LOC from JavaNCSS’s xml file is as easy as

int ncss = Integer.parseInt(jncssroot.packages.total.ncss.text())

Note, I’m coercing integer values as I’d like to divide (and round) my result– if I don’t explicitly specify int’s I’ll be left with String division, which doesn’t work so well.

Parsing CPD’s xml document is slightly more complex– slightly in that it takes 3 times as much code:

def cpdtot = 0
cpdroot.duplication.each { elem ->
 cpdtot += Integer.parseInt(elem.@lines.text())
}

Again, parsing an XML document yields String values; accordingly, I need to use Integer’s parseInt method.

Next, all I need to do is divide the two and, in my case, I’m aggressively rounding up via Java’s ceiling call as follows:

def ratio = Math.ceil((cpdtot / ncss) * 100)

Multiplying the result by 100 gives me a percentage value, of course, and lastly, I compare that to a threshold value:

if(ratio > Double.parseDouble(properties.cpd_threshold)){
  ant.fail(message:
   "cut and paste ratio was greater than ${properties.cpd_threshold}%, it was ${ratio}%")
}

Puttin’ it all together, baby, yields a groovy Ant script with a hip target:

<target name="cpd-threshold" depends="metrics">
 <groovy>
 def jncssroot = new XmlSlurper().parse("target/reports/javancss_metrics.xml")
 int ncss = Integer.parseInt(jncssroot.packages.total.ncss.text())

 def cpdroot = new XmlSlurper().parse("target/reports/cpd.xml")

 def cpdtot = 0
 cpdroot.duplication.each { elem ->
  cpdtot += Integer.parseInt(elem.@lines.text())
 }

 def ratio = Math.ceil((cpdtot / ncss) * 100)

 if(ratio > Double.parseDouble(properties.cpd_threshold)){
  ant.fail(message:
   "cut and paste ratio was greater than ${properties.cpd_threshold}%, it was ${ratio}%")
 }
 </groovy>
</target>

Reports are hip, but they are usually only read once– the first time they are generated. Rather than waiting to find out that there’s a problem, proactively analyzing a hip metric (such CPLOC/LOC) enables rapid feedback and rapid corrections– is that unambiguous or what?

Related odds and ends
 

One Response to “Unambiguously analyzing metrics”


  1. [...] And we should always keep the value of code coverage (or any isolated metric for that matter) in perspective, as Andy Glover reminds us in his post on unambiguously analyzing metrics: [...]

Trackback this Post | Feed on comments to this Post

Leave a Reply