|
The aim of this metric is to try and determine whether a class
represents one abstraction (good) or multiple abstractions (bad).
If a class represents multiple abstractions, it should be split
up into multiple classes. The correct way to measure this property
is a matter of hot debate among software metrics experts, so
SemmleCode provides two ways of doing it: one due to Henderson-Sellers
(described below), and another one due to
Chidamber and Kemerer.
The intuition underlying the Henderson-Sellers method of calculating Lack of Cohesion of Methods (LCOM) is that in a cohesive class C, many methods access the same fields of C. Formally, let
- M = set of methods in class
- F = set of fields in class
- r(f) = number of methods that access field f
- ar = mean of r(f) over f in F
We then define LCOM of the class under consideration to be
LCOM = (ar - |M|) / (1 - |M|)
We follow Lance Walton (author of the State of Flow Eclipse Metrics Plugin) in restricting M to methods that read some field in the same class, and F to fields that are read by some method in the same class.
A value greater than 0.9 indicates a class that may deserve some further
scrutiny.
Pre-packaged Query
Query name = "Semmle/Metrics/Types/Cohesion/Reftypes that lack cohesion of methods (LCOM) in the Henderson-Sellers sense"
Reports reftypes that have lack of cohesion greater than 0.9, in decreasing order.
from MetricRefType t, float loc
where loc = t.getLackOfCohesionHS() and loc > 0.9
select t, loc order by loc desc
.QL Source of Metric
This metric is defined in class MetricRefType.
// does m access field f defined in the same type?
predicate accessesLocalField(Method m,Field f) {
m.accesses(f) and
m.getDeclaringType() = this and
f.getDeclaringType() = this
}
// returns any method that accesses some local field
Method getAccessingMethod() {
exists(Field f | this.accessesLocalField(result,f))
}
// returns any field that is accessed by a local method
Field getAccessedField() {
exists(Method m | this.accessesLocalField(m,result))
}
// compute Henderson-Sellers lack of cohesion metric
float getLackOfCohesionHS() {
exists(int m, float r |
// m = number of methods that access some field
m = count(this.getAccessingMethod())
and
// r = average (over f) of number of methods that access field f
r = avg(Field f |
f = this.getAccessedField() |
count(Method x | this.accessesLocalField(x,f)))
and
// avoid division by zero
m != 1
and
// compute LCOM
result = ((r-m)/(1-m))
)
}
Criticisms and Variations
The definition of the metric does not distinguish between static methods and
instance methods, or between static fields and instance fields. Indeed,
the Metrics 1.3.6 Eclipse Plugin
provides an option for disregarding static fields and static methods.
To program that option in .QL, we define a new class of our own that
overrides the accessLocalField predicate.
class MyMetricRefType extends MetricRefType {
predicate accessesLocalField(Method m,Field f) {
super.accessesLocalField(m,f) and
not(m.hasModifier("static")) and
not(f.hasModifier("static"))
}
}
Another criticism is that the metric discourages the use of getter
and setter methods: surely these should could as field accesses just
the same. Again that variation is easily coded by overriding the
accessesLocalField predicate. In fact, arguably any
direct call to a method that accesses a local field should count
as an access. Hence we define:
class MyMetricRefType2 extends MetricRefType {
predicate accessesLocalField(Method m,Field f) {
super.accessesLocalField(m,f)
or
exists(Method n |
m.getACall() = n and
n.getDeclaringType() = this and
super.accessesLocalField(n,f))
}
}
from MyMetricRefType2 t, float loc
where t.fromSource() and
loc = t.getLackOfCohesionHS() and
loc > 0.9
select t, loc order by loc desc
References
Brian Henderson-Sellers.
Object-Oriented Metrics: Measures of Complexity.
Prentice-Hall 1996.
Frank Sauer.
Metrics 1.3.6 - Getting Started.
2005.
Renaat Verbruggen. Object-oriented patterns and metrics (Lecture Notes). (updated regularly)
Lance Walton.
Eclipse Metrics Plugin.
State of Flow, 2005.
|