Clean Code

How to identify a Brain Method using NDepend

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.