Specialization Index of a Type

The specialization index metric measures the extent to which subclasses override (replace) the behavior of their ancestor classes. If they override many methods, it is an indication that the original abstraction in the superclasses may have been inappropriate. On the whole, subclasses should add behavior to their superclasses, but not alter that behavior dramatically.

This metric was proposed by Mark Lorenz and Jeff Kidd. The idea is to weight each overridden method by the depth in the inheritance hierarchy at which it occurs, and then take the average over all methods in the type.

Formally, we compute the number of overridden methods in a class, multiply by the depth in the inheritance hierarchy, and then divide by the total number of callables. It is common (for instance in Frank Sauer's Metrics 1.3.6) to exclude certain commonly overridden methods from the calculation of the number of overridden methods, for instance equals, toString and hashCode.

A specialization index of greater than 5 is generally considered suspect and might warrant further investigation.

Pre-packaged Query

Query name = "Semmle/Metrics/Types/Inheritance/RefTypes that are highly specialized"

Returns reference types that have specialization index greater than 5, in order of decreasing index, as a bar chart.

from MetricRefType t, float f
where t.fromSource() and 
      f = t.getSpecialisationIndex() and 
      f > 5
select t, f order by f desc

.QL Source of Metric

This metric is defined in MetricRefType. The definition has a separate predicate for the excluded overridden methods, so that it is easy to give a new definition in a subclass where desired.

    // exclusions from the number of overriding methods
   predicate ignoreOverride(Method c) {
       c.hasName("equals") or c.hasName("hashCode") or c.hasName("toString")
       or c.hasName("finalize") or c.hasName("clone")
   }

   // get some method that overrides a non-abstract method in a super type
   Method  getOverrides() {
      this.getAMethod() = result and
      exists(Callable c | result.overrides(c) and not(c.hasModifier("abstract"))) and
      not(this.ignoreOverride(result))
   }
 
   // the number of methods that are overridden by this class (NORM)
   int getNumberOverridden() {
     result = count(this.getOverrides())
   }

   // specialisation index
   float getSpecialisationIndex() {
     this.getNumberOfCallables() != 0
     and
     result = (this.getNumberOverridden() * this.getInheritanceDepth())
              /
              this.getNumberOfCallables()
   }

Criticisms and Variations

Arguably when method m overrides n but calls n via a super call, it is not altering the behavior of its super class, just adding to it. From that point of view, such super-calling methods m should be ignored when computing the number of overridden methods. Indeed, in his Metrics 1.3.6 Eclipse plugin, Frank Sauer offers precisely such an option. In .QL, we just override the existing definition of the ignoreOverride predicate:

class MyMetricRefType extends MetricRefType {
  predicate ignoreOverride(Method m) {
    super.ignoreOverride(m) or
    // m makes a super call
    exists(Method n |
      n = m.getACall() and m.overrides(n))  
  }
} 

References

Mark Lorenz and Jeff Kidd. Object-Oriented Software Metrics . Prentice Hall, 1994.

Mark Schroeder. A Practical Guide to Object-Oriented Metrics . IEEE IT Professional 1(6), 30-36, 1999.

Digg!Reddit!Del.icio.us!Facebook!Slashdot!Technorati!StumbleUpon!Ma.gnolia!

Comments
Only registered users can write comments!
 
< Prev   Next >