Avoid namespaces dependency cycles

Rule ND1401 Architecture

Lists all application namespace dependency cycles; each row shows a cycle indexed with one entangled namespace. Avoiding cycles is a simple yet efficient way to clean a code base architecture; see our white books: https://www.ndepend.com/docs/white-books

How to fix

First fix the rule Avoid namespaces mutually dependent: this eliminates most cycles. Remaining cycles involve 3+ namespaces, like A is using B is using C is using A.

Right-click a cycle cell to export the namespaces to the dependency graph or matrix, illustrated here: https://www.ndepend.com/docs/visual-studio-dependency-graph#Entangled Then decide which namespaces are low-level vs high-level and remove the offending dependencies (e.g. if A is low-level, C shouldn't use A), using the patterns in Avoid namespaces mutually dependent HowToFix.

The matrix shows cycles as red squares and black cells; use the option Display Direct and Indirect Dependencies. See: https://www.ndepend.com/docs/dependency-structure-matrix-dsm#Cycle https://www.ndepend.com/docs/dependency-structure-matrix-dsm#Mutual

Estimated Debt doesn't depend on cycle length: even a long cycle can be broken by removing a few dependencies.

CQLinq source

This rule is written in CQLinq, C# LINQ over the NDepend code model. It ships with every new NDepend project: you can read it, adapt it, or use it as a starting point for your own custom rules.

// <Name>Avoid namespaces dependency cycles</Name>
// <Id>ND1401:AvoidNamespacesDependencyCycles</Id>

warnif count > 0

// Optimization: restraint application assemblies set
// If some namespaces are mutually dependent
//  - They must be declared in the same assembly
//  - The parent assembly must ContainsNamespaceDependencyCycle
from assembly in Application.Assemblies
                 .Where(a => a.ContainsNamespaceDependencyCycle != null && 
                             a.ContainsNamespaceDependencyCycle.Value)

// Optimization: restraint namespaces set
let namespacesSuspect = assembly.ChildNamespaces.Where(n => 
   // A namespace involved in a cycle necessarily have a null Level.
   n.Level == null &&
   // Also require the namespace to be JustMyCode to avoid generated namespaces like My / My.Resources in VB.NET
   JustMyCode.Contains(n))

// hashset is used to avoid iterating again on namespaces already caught in a cycle.
let hashset = new HashSet<INamespace>()


from suspect in namespacesSuspect
   // By commenting in this line, the query matches all namespaces involved in a cycle.
   where !hashset.Contains(suspect)

   // Define 2 code metrics
   // - Namespaces depth of is using indirectly the suspect namespace.
   // - Namespaces depth of is used by the suspect namespace indirectly.
   // Note: for direct usage the depth is equal to 1.
   let namespacesUserDepth = namespacesSuspect.DepthOfIsUsing(suspect)
   let namespacesUsedDepth = namespacesSuspect.DepthOfIsUsedBy(suspect)

   // Select namespaces that are both using and used by namespaceSuspect
   let usersAndUsed = from n in namespacesSuspect where 
                         namespacesUserDepth[n] > 0 && 
                         namespacesUsedDepth[n] > 0 
                      select n

   where usersAndUsed.Count() > 0

   // Here we've found namespace(s) both using and used by the suspect namespace.
   // A cycle involving the suspect namespace is found!
   // v2017.3.2: don't call Append() as an extension method else ambiguous syntax error 
   //            with the new extension method in .NET Fx v4.7.1 / .NET Standard 2.0: System.Linq.Enumerable.Append()
   let cycle = ExtensionMethodsEnumerable.Append(usersAndUsed,suspect)

   // Fill hashset with namespaces in the cycle.
   // .ToArray() is needed to force the iterating process.
   let unused1 = (from n in cycle let unused2 = hashset.Add(n) select n).ToArray()

select new { 
   suspect, 
   cycle,
   Debt = 120.ToMinutes().ToDebt(),
   Severity = Severity.High
}

//<Expl>
// The *{1}* form a **dependency cycle**: they all depend on each other.
// This cycle prevents **clean layered architecture** and reduces maintainability.
// To fix it, first address all issues stemming from the rule
// *Avoid mutually dependent namespaces*.
// Then read this rule documentation that explains how to use 
// the *Dependency Inversion Principle* to fix cycles involving 
// 3 or more namespaces.
//</Expl>

//<Desc>
// Lists all *application namespace dependency cycles*; each row shows a
// cycle indexed with one entangled namespace. Avoiding cycles is a *simple
// yet efficient* way to clean a code base architecture; see our white books:
// https://www.ndepend.com/docs/white-books
//</Desc>

//<HowToFix>
// First fix the rule **Avoid namespaces mutually dependent**: this
// eliminates most cycles. Remaining cycles involve 3+ namespaces, like
// **A is using B is using C is using A**.
//
// Right-click a cycle cell to export the namespaces to the dependency graph
// or matrix, illustrated here:
// https://www.ndepend.com/docs/visual-studio-dependency-graph#Entangled
// Then decide which namespaces are low-level vs high-level and remove the
// offending dependencies (e.g. if A is low-level, C shouldn't use A), using
// the patterns in *Avoid namespaces mutually dependent* HowToFix.
//
// The matrix shows cycles as red squares and black cells; use the option
// **Display Direct and Indirect Dependencies**. See:
// https://www.ndepend.com/docs/dependency-structure-matrix-dsm#Cycle
// https://www.ndepend.com/docs/dependency-structure-matrix-dsm#Mutual
//
// Estimated Debt doesn't depend on cycle length: even a long cycle can be
// broken by removing a few dependencies.
//</HowToFix>

Architecture: all 13 items