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
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 }
Pingback: How to identify a Tradition Breaker using NDepend - Simple Oriented Architecture