In the previous articles in this series we’ve seen:
- How to identify a God Class
- How to identify Feature Envy
- How to identify a Data Class
- How to identify a Brain Method
- How to identify Intensive Coupling and Dispersed Coupling
- How to identify Shotgun Surgery
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 }