2. Object Oriented Complexity Metrics
The Chidamber/Kemerer (CK) metrics are the best-known metrics for object-oriented Software.
There are six CK metrics; some can be derived from a Call Graph, others use the unit level
complexity
3. CK Metrics
WMC—Weighted Methods per Class
DIT—Depth of Inheritance Tree
NOC—Number of Child Classes
CBO—Coupling between Classes
RFC—Response for Class
LCOM—Lack of Cohesion on Methods
4. CK Metrics
WMC—Weighted Methods per Class: The WMC metric counts the number of methods in a
class and weights them by their cyclomatic complexity.
DIT—Depth of Inheritance Tree: The name says it all. If we made another call graph to show
inheritance, this is the length of the longest inheritance path from root to leaf node. This is directly
derivable from a standard UML Class Inheritance Diagram. While comparatively large values of
the DIT metric imply good reuse, this also increases testing difficulty. One strategy is to “flatten”
the inheritance classes such that all inherited methods are in one class for testing purposes.
Current guidelines recommend a limit of DIT = 3.
NOC—Number of Child Classes: The Number Of Child classes of a class in the inheritance
diagram for the DIT metric is simply the outdegree of each node. This is very analogous to the
cyclomatic complexity of the call graph. The Number of Child Classes for the Java version of
NextDate is 0, not including implicit inheritance from the Java Object class.
5. CK Metrics
CBO—Coupling Between Classes: Coupling is increased when one unit refers to variables in
another unit. Greater coupling implies both greater testing and greater maintenance difficulty. Well-
designed classes should reduce the CBO values.
RFC—Response for Class: The RFC method refers to the length of the message sequence that
results from an initial message.
LCOM—Lack of Cohesion on Methods: Coupling and Cohesion are somewhat diametrically
opposed—methods should have very low coupling with other methods, and at the same time,
should be cohesive in the sense that a method has a single purpose. LCOM describes the extent
to which methods are focused on a single purpose—highly cohesive methods are (or should be) a
consequence of good encapsulation.
6. Implications of Composition and
Encapsulation
Composition (as opposed to decomposition) is the central design strategy in object-oriented
software development.
Together with the goal of reuse, composition creates the need for very strong unit testing.
Because a unit (class) may be composed with previously unknown other units, the traditional
notions of coupling and cohesion are applicable.
Encapsulation has the potential to resolve this concern, but only if the units (classes) are highly
cohesive and very loosely coupled.
highly cohesive units that are loosely coupled not only indicate a maintainable design but are also
require less testing and are generally easier to test.
7. Implications of Composition and
Encapsulation
As coupling increases, each reference to another unit must be tested, increasing the number of
required tests.
Similarly, as cohesion decreases, additional, but unrelated, functionality is included and requires
additional tests.
At the unit level, better object-oriented complexity metrics lead to reduced tests.
However, there is a point where the inherent complexity necessary to create the desired
functionality is pushed out of the individual units and into the composition of the units.
The main implication of composition is that, even presuming very good unit-level testing, the real
burden is at the integration testing level.
8. Implications of Inheritence
Although the choice of classes as units seems natural, the role of inheritance complicates this
choice. If a given class inherits attributes and/or operations from super classes, the stand-alone
compilation criterion of a unit is sacrificed.
Binder suggests “flattened classes” as an answer [Binder, 1996].
A flattened class is an original class expanded to include all the attributes and operations it
inherits.
9. Implications of Inheritence
Unit testing on a flattened class solves the inheritance problem, but it raises another.
A flattened class will not be part of a final system, so some uncertainty remains.
Also, the methods in a flattened class might not be sufficient to test the class. The next work-
around is to add special-purpose test methods. This facilitates class-as-unit testing but raises a
final problem: a class with test methods is not (or should not be) part of the delivered system.
Some ambiguity is also introduced: the test methods can also be faulty. What if a test method
falsely reports a fault, or worse, incorrectly reports success? Test methods are subject to the same
false positive and false negative outcomes as medical experiments.
This leads to an unending chain of methods testing other methods, very much like the attempt to
provide external proofs of consistency of a formal system.
12. Implications of Polymorphism
The essence of polymorphism is that the same method applies to different objects.
Considering classes as units implies that any issues of polymorphism will be covered by the
class/unit testing.
it is necessary to verify the behavior of each of the derived child classes to ensure correct
functionality.
Again, the redundancy of testing polymorphic operations sacrifices hoped-for economies.
14. Next Date problem
abstract class ValidRange {
protected int minimum, maximum;
public ValidRange(int minimum, int maximum) {
this.minimum = minimum;
this.maximum = maximum;
}
public boolean validRange() {
return minimum <= getValue() && getValue() <= maximum;
}
protected abstract int getValue();
}
15. Next Date problem
class Date extends ValidRange {
protected Day day; protected Month month; protected Year year;
public Date(int month, int day, int year) { }
public String getDate() { }
public Date nextDate() { }
@Override
public boolean validRange() {
// Inherited value overwritten
return year.validRange() && month.validRange() && day.validRange();
}
@Override
protected int getValue() {
return 0;
}
}
16. Next Date problem
class Day extends ValidRange {
private int day; private Month month;
public Day(int day, Month month) { }
public int getDay() { }
public Day getNextDay() { }
public Month getMonth() { }
@Override
public boolean validRange() {
return super.validRange() && month.validRange();
}
@Override
protected int getValue() {
return day;
}
}
17. Next Date problem
class Month extends ValidRange {
public static final int JANUARY = 1;
public static final int FEBRUARY = 2;
public static final int MARCH = 3;
public static final int APRIL = 4;
public static final int MAY = 5;
public static final int JUNE = 6;
public static final int JULY = 7;
public static final int AUGUST = 8;
public static final int SEPTEMBER = 9;
public static final int OCTOBER = 10;
public static final int NOVEMBER = 11;
public static final int DECEMBER = 12;
private int month;
private Year year;
public Month(int month, Year year) { }
public int getMonth() { }
public int numberOfDays() { }
public Month getNextMonth() { }
public Year getYear() { }
@Override
public boolean validRange() {
return super.validRange() &&
year.validRange();
}
@Override
protected int getValue() {
return month;
}
}
18. Next Date problem
class Year extends ValidRange {
private int year;
public Year(int year) { }
public int getYear() { }
public boolean isLeapYear() { }
public Year getNextYear() { }
@Override
protected int getValue() {
return year;
}
}