|
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.
|