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
- How to identify Refused Parent Bequest
In this article we’ll see how to identify the Tradition Breaker code smell.
Tradition Breaker Detection Strategy
A class suffers from Tradition Breaker 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 Tradition Breaker:
((NAS >= Average NOM per class) AND (PNAS >= Two Thirds)) AND
(((AMW > Average) OR (WMC >= Very High)) AND (NOM >= High)) AND
((Parent’s AMW > Average) AND (Parent’s NOM > High/2) AND (Parent’s WMC >= Very High/2))
This might seem complex on a first look. After we go over the definition for each metric, we’ll break this detection strategy in three distinct parts. This way we’ll see why the authors picked these conditions and it will make more sense.
This detection strategy uses five metrics:
- NAS – Number of Added Services – to measure the number of methods that are not overridden from a class’s ancestors
- PNAS – Percentage of Newly Added Services – to measure the relative number of newly added services compared to all services
- 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:
- PNAS uses a Common Fraction Threshold. Two Thirds is 0.66.
- NAS, 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. The Very High threshold for WMC is 47. The Average threshold for NOM is 7 and the High threshold is 10.
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.
NAS – Number of Added Services
This metric measures the number of methods that are not overridden from a class’s ancestors.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // <Name>NAS</Name> let isPublicNonStaticMethod = new Func<IMethod, bool >(m => m.IsPublic && !m.IsClassConstructor && !m.IsConstructor && !m.IsStatic) let newlyAddedMethodsFor = new Func<IType, IEnumerable<IMethod>>(c => c.Methods.Where(m => isPublicNonStaticMethod(m) && m.OverriddensBase.Count() == 0)) from c in JustMyCode.Types let newlyAddedMethods = newlyAddedMethodsFor(c) let nas = newlyAddedMethods.Count() orderby nas descending select new { c, nas, newlyAddedMethods } |
PNAS – Percentage of Newly Added Services
This metric measures the relative number of newly added services compared to all services. It’s the number of public methods that are not overridden from the ancestors, divided by the total number of public methods.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | // <Name>PNAS</Name> let isPublicNonStaticMethod = new Func<IMethod, bool >(m => m.IsPublic && !m.IsClassConstructor && !m.IsConstructor && !m.IsStatic) let publicMethodsFor = new Func<IType, IEnumerable<IMethod>>(c => c.Methods.Where(m => isPublicNonStaticMethod(m))) let newlyAddedMethodsFor = new Func<IType, IEnumerable<IMethod>>(c => c.Methods.Where(m => isPublicNonStaticMethod(m) && m.OverriddensBase.Count() == 0)) from c in JustMyCode.Types let newlyAddedMethods = newlyAddedMethodsFor(c) let publicMethodsCount = publicMethodsFor(c).Count() let pnas = publicMethodsCount == 0 ? null : ( double ?) newlyAddedMethods.Count()/publicMethodsCount orderby pnas select new { c, pnas, newlyAddedMethods } |
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.
1 2 3 4 5 6 7 8 | // <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.
1 2 3 4 5 6 7 8 9 10 11 | // <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:
1 2 3 4 5 | // <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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | // <Name>Tradition Breaker</Name> warnif count > 0 // ** Helper Functions ** // ** NAS & PNAS ** let isPublicNonStaticMethod = new Func<IMethod, bool >(m => m.IsPublic && !m.IsClassConstructor && !m.IsConstructor && !m.IsStatic) let publicMethodsFor = new Func<IType, IEnumerable<IMethod>>(c => c.Methods.Where(m => isPublicNonStaticMethod(m))) let newlyAddedMethodsFor = new Func<IType, IEnumerable<IMethod>>(c => c.Methods.Where(m => isPublicNonStaticMethod(m) && m.OverriddensBase.Count() == 0)) // ** 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 TwoThirds = 0.66 let amwAverage = 2 let wmcVeryHigh = 47 let nomAverage = 7 let nomHigh = 10 // ** Detection Strategy ** from c in JustMyCode.Types.Where(t => t.IsClass && t.DepthOfDeriveFrom("System.Object") > 1) // ** NAS & PNAS ** let newlyAddedMethods = newlyAddedMethodsFor(c) let publicMethodsCount = publicMethodsFor(c).Count() let nas = newlyAddedMethods.Count() let pnas = publicMethodsCount == 0 ? null : ( double ?) nas/publicMethodsCount // ** WMC ** let wmc = wmcFor(c) let wmcParent = wmcFor(c.BaseClass) // ** NOM ** let nom = c.NbMethods let nomParent = c.BaseClass.NbMethods // ** AMW ** let amw = amwFor(c) let amwParent = amwFor(c.BaseClass) // ** Componenets ** let excessiveIncreaseOfChildClassInterface = // More newly added services than average NOM per class nas > nomAverage && // Newly added services are dominant in child class pnas > TwoThirds let childClassHasSubstantialSizeAndComplexity = ( // Method complexity in child class above average amw > amwAverage || // Functional complexity of child class is very high wmc >= wmcVeryHigh ) && // Class has substantial number of methods nom >= nomHigh let parentClassIsNeitherSmallNorDumb = // Parent functional complexity above average amwParent > amwAverage && // Parent has more than half of child's methods nomParent > (nomHigh / 2) && // Parent's complexity more than half of child' wmcParent > (wmcVeryHigh / 2) where excessiveIncreaseOfChildClassInterface && childClassHasSubstantialSizeAndComplexity && parentClassIsNeitherSmallNorDumb select new { c, nas, pnas, amw, wmc, nom, amwParent, wmcParent, nomParent } |
Pingback: How to identify common Code Smells using NDepend - Simple Oriented Architecture