Clean Code

How to identify efferent coupling code smells using NDepend

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

In this article we’ll see how to identify two types of efferent (outgoing) coupling code smells: Intensive Coupling and Dispersed Coupling.

Detection Strategies

Intensive Coupling

A method suffers from Intensive Coupling when it calls many other methods from a few classes. Object-Oriented Metrics in Practice, by Michele Lanza and Radu Marinescu, proposes the following detection strategy for Intensive Coupling:

(((CINT > Short Memory Cap) AND (CDISP < Half)) OR
  ((CINT > Few) AND (CDISP < A Quarter))) AND
  (MAXNESTING > Shallow)

Dispersed Coupling

A method suffers from Dispersed Coupling when it calls many other methods that are dispersed among many classes. The detection strategy for Dispersed Coupling is:

(CINT > Short Memory Cap) AND (CDISP >= Half) AND (MAXNESTING > Shallow)

These detection strategies use three metrics:

  • CINT – Coupling Intensity – to measure how many methods is the measured method calling
  • CDISP – Coupling Dispersion – to measure in how many classes are the called methods dispersed
  • MAXNESTING – Maximum Nesting Level – to measure the maximum nesting depth of a method

The detection strategies uses two types of thresholds:

  • CINT and MAXNESTING use Generally-Accepted Meaning Thresholds. Shallow is 1. Few is defined between 2 and 5. Short term memory capacity is 7 or 8.
  • CDISP uses a Common Fraction Threshold. A Quarter is 0.25 and Half is 0.5.

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.

CINT – Coupling Intensity

CINT measures the coupling intensity. This is computed by counting the number of distinct methods called by the measured method. NDepend computes the NbMethodsCalled metric that counts the total number of methods called. We can’t use this metric though, because we are interested only in methods defined by us. But, we can use the ExceptThirdParty() extension method to filter out third party methods:

// <Name>CINT</Name>
from m in JustMyCode.Methods
let methods = m.MethodsCalled
 .Where(method => method.ParentType != m.ParentType)
 .ExceptThirdParty()
let cint = methods.Count()
orderby cint descending
select new { m, cint }

CDISP – Coupling Dispersion

CDISP measures the coupling dispersion. This is the number of classes in which the called operations are defined , divided by CINT. This basically builds upon CINT:

// <Name>CDISP</Name>
from m in JustMyCode.Methods
let methods = m.MethodsCalled
 .Where(method => method.ParentType != m.ParentType)
 .ExceptThirdParty()
let providers = methods.Select(method => method.ParentType).ToHashSet()
let cint = methods.Count()
let cdisp = (double) providers.Count()/cint
orderby cdisp descending
select new { m, cdisp }

MAXNESTING – Maximum Nesting Level

This metric measures the maximum nesting depth of a method. NDepend already computes this metric out of the box:

from m in JustMyCode.Methods
let maxnesting = m.ILNestingDepth
orderby maxnesting descending 
select new { m, maxnesting }

Putting it all together

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

Intensive Coupling

// <Name>Intensive Coupling</Name>
warnif count > 0
// ** Thresholds **
let Shallow = 1
let Few = 3
let ShortMemoryCap = 7
let AQuarter = 0.25
let Half = 0.5

// ** Detection Strategy **
from m in JustMyCode.Methods
let maxnesting = m.ILNestingDepth
// CINT & CDISP
let methods = m.MethodsCalled
 .Where(method => method.ParentType != m.ParentType)
 .ExceptThirdParty()
let providers = methods.Select(method => method.ParentType).ToHashSet()
let cint = methods.Count()
let cdisp = (double) providers.Count()/cint

where 
 (
  (
   // Operation calls too many methods
   cint > ShortMemoryCap && 
   // Calls are dispersed in few classes
   cdisp < Half
  ) ||
  (
   // Operation calls more than a few methods
   cint > Few && 
   // Calls are dispersed in very few classes
   cdisp < AQuarter
  )
 ) &&
 // Method has few nested conditionals
 (maxnesting > Shallow)
select new { m, cint, cdisp, maxnesting, methods, providers }

Dispersed Coupling

// <Name>Dispersed Coupling</Name>
warnif count > 0
// ** Thresholds **
let Shallow = 1
let ShortMemoryCap = 7
let Half = 0.5

// ** Detection Strategy **
from m in JustMyCode.Methods
let maxnesting = m.ILNestingDepth
// CINT & CDISP
let methods = m.MethodsCalled
 .Where(method => method.ParentType != m.ParentType)
 .ExceptThirdParty()
let providers = methods.Select(method => method.ParentType).ToHashSet()
let cint = methods.Count()
let cdisp = (double) providers.Count()/cint

where 
 // Operation calls too many methods
 cint > ShortMemoryCap && 
 // Calls are dispersed in many classes
 cdisp >= Half &&
 // Operation has few nested conditionals
 maxnesting > Shallow
select new { m, cint, cdisp, maxnesting, methods, providers }