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