Clean Code

How to identify Refused Parent Bequest using NDepend

In the previous articles in this series we’ve seen:

In this article we’ll see how to identify the Refused Parent Bequest code smell.

Refused Parent Bequest Detection Strategy

A class suffers from Refused Parent Bequest when it doesn’t use the protected members of its parent. Object-Oriented Metrics in Practice, by Michele Lanza and Radu Marinescu, proposes the following detection strategy for Refused Parent Bequest:

(((NProtM > Few) AND (BUR < A Third)) OR (BOvR < A Third)) AND
(((AMW > AVerage) OR (WMC > Average)) AND (NOM > Average))

This detection strategy uses six metrics:

  • NProtM – Number of Protected Members
  • BUR – Base Class Usage Ratio – to measure how much is the child class using inherited members from the base class
  • BOvR – Base Class Overriding Ratio – to measure how much is the child class overriding members from the base class
  • AMW – Average Method Weight – to measure the average complexity of all methods of a class
  • WMC – Weighted Method Count – to measure class complexity
  • NOM – Number of Methods

This detection strategy uses three types of thresholds:

  • NProtM uses a Generally-Accepted Meaning Threshold. Few is defined between 2 and 5.
  • BUR and BOvR use Common Fraction Thresholds. One Third is 0.33.
  • AMW, WMC and NOM use Statistics-Based Thresholds. For these types of thresholds, a large number of projects needs to be analyzed. The authors of Object-Oriented Metrics in Practice analyzed 45 Java projects and extracted Low, Average, High and Very High thresholds for some basic metrics. The Average threshold for AMW is 2, for WMC is 14 and for NOM is 7.

Metrics Definitions

Let’s go over the definitions for the used metrics and how to implement them with NDepend. For a more detailed definition, be sure to check Appendix A.2 of Object-Oriented Metrics in Practice. If you’re not familiar with CQLinq, check out the NDepend documentation or my blog post on how to query your code base.

NProtM – Number of Protected Members

This metric counts the number of protected members of a class:

// <Name>NProtM</Name>
from c in JustMyCode.Types.Where(t => t.IsClass)
let protectedMembers = c.Members.Where(m => m.IsProtected)
let nprotm = protectedMembers.Count()
orderby nprotm descending
select new { c, nprotm, protectedMembers }

BUR – Base Class Usage Ratio

This metric measures how much is the child class using inherited members from the base class. It’s the number of inheritance specific members used by the measure class, divided by the total number of inheritance specific members defined in the base class.

// <Name>BUR</Name>
from c in JustMyCode.Types.Where(t => 
 t.IsClass && t.DepthOfDeriveFrom("System.Object") > 1)
let protectedMembers = c.BaseClass.Members.Where(m => m.IsProtected).ToHashSet()
let protectedMembersUsed = protectedMembers.UsedBy(c)
let bur = (double) protectedMembersUsed.Count()/protectedMembers.Count()
orderby bur
select new { c, bur, protectedMembers, protectedMembersUsed }

BOvR – Base Class Overriding Ratio

This metric measures how much is the child class overriding members from the base class. It’s the number of methods from the measured class that override methods from its base class, divided by the total number of methods from the measured class.

// <Name>BOvR</Name>
let overidingMethodsFor = new Func<IType, IEnumerable<IMethod>>(c =>
 c.Methods.Where(m => 
  !m.IsClassConstructor && 
  !m.IsConstructor && 
  !m.IsStatic && 
  m.OverriddensBase.ParentTypes().Contains(c.BaseClass)))

from c in JustMyCode.Types
let overidingMethods = overidingMethodsFor(c)
let bovr = (double) overidingMethods.Count() / c.NbMethods
orderby bovr
select new { c, bovr, overidingMethods }

AMW – Average Method Weight

This metrics measures the average complexity of all methods of a class. McCabe’s cyclomatic number is used to quantify a method’s complexity.

// <Name>AMW</Name>
let amwFor = new Func<IType, double?>(c =>
 (double?) c.CyclomaticComplexity / c.NbMethods)

from c in JustMyCode.Types
let amw = amwFor(c)
orderby amw descending
select new { c, amw }

WMC – Weighted Method Count

This metric measures the complexity of a class. This is done by summing the complexity of all methods of a class. McCabe’s Cyclomatic Complexity is used to measure the complexity of a method.

// <Name>WMC</Name>
let wmcFor = new Func<IType, int>(t => 
 t.MethodsAndContructors
  .Select(m => (int) m.CyclomaticComplexity.GetValueOrDefault())
  .Sum())

// ** Sample Usage **
from t in JustMyCode.Types
let wmc = wmcFor(t)
orderby wmc descending 
select new { t, wmc }

NOM – Number of Methods

This metric counts the number of methods in a class. This metric is computed out of the box by NDepend:

// <Name>NOM</Name>
from c in JustMyCode.Types
let nom = c.NbMethods
orderby nom descending
select new { c, nom }

Putting it all together

Now that we know how to compute each of the required metrics, let’s see how the detection strategy looks like:

// <Name>Refused Parent Bequest</Name>
warnif count > 0 
// ** Helper Functions **
// ** BOvR **
let overidingMethodsFor = new Func<IType, IEnumerable<IMethod>>(c =>
 c.Methods.Where(m => 
   !m.IsClassConstructor && 
   !m.IsConstructor && 
   !m.IsStatic && 
   m.OverriddensBase.ParentTypes().Contains(c.BaseClass)))

// ** WMC **
let wmcFor = new Func<IType, int>(t => 
 t.MethodsAndContructors
  .Select(m => (int) m.CyclomaticComplexity.GetValueOrDefault())
  .Sum())

// ** AMW **
let amwFor = new Func<IType, double?>(c =>
 (double?) c.CyclomaticComplexity / c.NbMethods)

// ** Thresholds **
let Few = 3
let OneThird = 0.33
let amwAverage = 2
let wmcAverage = 14
let nomAverage = 7

// ** Detection Strategy **
from c in JustMyCode.Types.Where(t => 
 t.IsClass && t.DepthOfDeriveFrom("System.Object") > 1)

// ** BUR ** 
let protectedMembers = c.BaseClass.Members.Where(m => m.IsProtected).ToHashSet()
let protectedMembersUsed = protectedMembers.UsedBy(c)
let bur = (double) protectedMembersUsed.Count()/protectedMembers.Count()

// ** BOvR **
let overidingMethods = overidingMethodsFor(c)
let bovr = (double) overidingMethods.Count() / c.NbMethods

// ** NProtM **
let nprotm = protectedMembers.Count()

// ** WMC **
let wmc = wmcFor(c)

// ** NOM **
let nom = c.NbMethods

// ** AMW **
let amw = amwFor(c)

// ** Componenets **
let childClassIgnoresBequest = 
 (
  // parent provides more than a few protected members 
  ((nprotm > Few) && 
  // child uses only little of parent's bequest
  (bur < OneThird)
 ) ||
 // overiding methods are rare in child 
 (bovr < OneThird)) 

let childClassIsNotTooSmallAndSimple = 
( 
 // functional complexity above average 
 (amw > amwAverage) ||
 // class complexity not lower than average
 (wmc > wmcAverage)
) &&
// class size is above average
nom > nomAverage

where 
 childClassIgnoresBequest && childClassIsNotTooSmallAndSimple 

select new { c, 
 nprotm, bur, bovr, amw, wmc, nom, 
 protectedMembers, protectedMembersUsed, overidingMethods }