In the previous articles in this series we’ve seen:
In this article we’ll see how to identify the Brain Method code smell.
Brain Method Detection Strategy
Brain Methods are methods that centralize the intelligence of a class. Object-Oriented Metrics in Practice, by Michele Lanza and Radu Marinescu, proposes the following detection strategy for Brain Methods:
(LOC > HighLocForClass/2) AND (CYCLO >= High) AND (MAXNESTING >= Several) AND (NOAV > Many)
This detection strategy uses four metrics:
- LOC – Lines of Code – to measure the number of lines of code of a method
- CYCLO – McCabe’s Cyclomatic Number – to measure the complexity of a method
- MAXNESTING – Maximum Nesting Level – to measure the maximum nesting depth of a method
- NOAV – Number of Accessed Variables – to measure the number of accessed variables
This detection strategy uses two types of thresholds:
- MAXNESTING and NOAV use Generally-Accepted Meaning Thresholds. Several is defined between 2 and 5. Many is the short term memory capacity, so it’s 7 or 8.
- LOC and CYCLO use a Statistics-Based Threshold. 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 High threshold for LOC for a class is 130. The High threshold for CYCLO for a method is 3.1.
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.
LOC – Lines of Code
This metric counts the number of lines of code. As it’s defined in Object-Oriented Metrics in Practice, it counts blank lines and comments. Since NDepend counts logical lines, I decided to go with that:
from m in JustMyCode.Methods let loc = m.NbLinesOfCode orderby loc descending select new { m, loc }
CYCLO – McCabe’s Cyclomatic Number
This metric measures the complexity of the method. It does this by counting the number of possible execution paths through a method. NDepend computes this metric out of the box:
from m in JustMyCode.Methods let cyclo = m.CyclomaticComplexity orderby cyclo descending select new { m, cyclo }
MAXNESTING – Maximum Nesting Level
This metric measures the maximum nesting depth of a method. NDepend already computes this metric:
from m in JustMyCode.Methods let maxnesting = m.ILNestingDepth orderby maxnesting descending select new { m, maxnesting }
NOAV – Number of Accessed Variables
This metric counts the number of accessed variables. This includes fields, parameters, variables. I’ve also included properties. Here is the query:
// <Name>NOAV</Name> // ** Helper Functions ** let isProperty = new Func<ICodeElement, bool>(member => member.IsMethod && (member.AsMethod.IsPropertyGetter || member.AsMethod.IsPropertySetter)) let isPropertyOrField = new Func<ICodeElement, bool>(member => isProperty(member) || member.IsField) // ** Metric Functions ** let ownAttributesAccessed = new Func<IMethod, IEnumerable<IMember>>(m => m.MembersUsed.Where(member=> isPropertyOrField(member) && member.ParentType == m.ParentType)) let noavFor = new Func<IMethod, int>(m => ownAttributesAccessed(m).Count() + m.NbVariables.GetValueOrDefault() + m.NbParameters) // ** Sample Usage ** from m in JustMyCode.Methods let noav = noavFor(m) orderby noav descending select new { m, noav }
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>Brain Method</Name> warnif count > 0 // *** NOAV *** // ** Helper Functions ** let isProperty = new Func<ICodeElement, bool>(member => member.IsMethod && (member.AsMethod.IsPropertyGetter || member.AsMethod.IsPropertySetter)) let isPropertyOrField = new Func<ICodeElement, bool>(member => isProperty(member) || member.IsField) // ** Metric Functions ** let ownAttributesAccessed = new Func<IMethod, IEnumerable<IMember>>(m => m.MembersUsed.Where(member=> isPropertyOrField(member) && member.ParentType == m.ParentType)) let noavFor = new Func<IMethod, int>(m => ownAttributesAccessed(m).Count() + m.NbVariables.GetValueOrDefault() + m.NbParameters) // ** Thresholds ** let Several = 3 let Many = 7 let cycloPerMethodHigh = 3.1 let locPerClassHigh = 100 // ** Detection Strategy ** from m in JustMyCode.Methods let loc = m.NbLinesOfCode let cyclo = m.CyclomaticComplexity let maxnesting = m.ILNestingDepth let noav = noavFor(m) where // Method is excessively large loc > locPerClassHigh / 2 && // Method has many conditionals branches cyclo >= cycloPerMethodHigh && // Method has deep nesting maxnesting >= Several && // Method used many variables noav > Many select new { m, loc, cyclo, maxnesting, noav }
Conclusion
NDepend computes most of the needed metrics for this detection strategy out of the box. The NDepend implementation for LOC differs slightly than the one in the book. This is why I’ve decided to lower the High threshold to 100.
Pingback: How to identify common Code Smells using NDepend - Simple Oriented Architecture