Avoid namespaces mutually dependent
Lists types and methods from a low-level namespace that use types and methods from a higher-level namespace, for each pair of namespaces using each other. Which is low-level is inferred by comparing the couplings [A to B] and [B to A] (types, methods and fields involved): the low-to-high coupling is typically the significantly lower one. Dependencies on the compiler's synthesized types of the global namespace (anonymous types, collection-expression lists) are ignored.
This avoids namespaces dependency cycles, moving toward a layered architecture where low-level code cannot use high-level code — i.e. getting rid of spaghetti code: entangled code not properly layered and structured. More in our white books: https://www.ndepend.com/docs/white-books
This avoids namespaces dependency cycles, moving toward a layered architecture where low-level code cannot use high-level code — i.e. getting rid of spaghetti code: entangled code not properly layered and structured. More in our white books: https://www.ndepend.com/docs/white-books
How to fix
Refactor so that the low-level namespace doesn't use the high-level namespace. The rule lists each culprit dependency; patterns to remove them:
- Move types between the low-level and high-level namespaces.
- Use the Dependency Inversion Principle (DIP): https://blog.ndepend.com/solid-design-the-dependency-inversion-principle-dip Create interfaces in the low-level namespace, implemented in the high-level one, so low-level code consumes high-level code through interfaces (callbacks), passed in via higher-level code — a.k.a. Dependency Injection (DI): http://en.wikipedia.org/wiki/Dependency_injection
May not apply to libraries with public mutually dependent namespaces, where breaking the API can cost more than the entangled code.
Debt depends on the number of types and methods used. Both namespaces form an unpartitionable super-component, so Annual Interest is one hour plus minutes proportional to their size in lines of code, divided by the number of issues listed. It often exceeds the Debt: a year unfixed costs more than fixing once.
- Move types between the low-level and high-level namespaces.
- Use the Dependency Inversion Principle (DIP): https://blog.ndepend.com/solid-design-the-dependency-inversion-principle-dip Create interfaces in the low-level namespace, implemented in the high-level one, so low-level code consumes high-level code through interfaces (callbacks), passed in via higher-level code — a.k.a. Dependency Injection (DI): http://en.wikipedia.org/wiki/Dependency_injection
May not apply to libraries with public mutually dependent namespaces, where breaking the API can cost more than the entangled code.
Debt depends on the number of types and methods used. Both namespaces form an unpartitionable super-component, so Annual Interest is one hour plus minutes proportional to their size in lines of code, divided by the number of issues listed. It often exceeds the Debt: a year unfixed costs more than fixing once.
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 mutually dependent</Name>
// <Id>ND1400:AvoidNamespacesMutuallyDependent</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)
// hashset is used to avoid reporting both A <-> B and B <-> A
let hashset = new HashSet<INamespace>()
// The compiler's own synthesized types live in the global namespace (anonymous types,
// collection-expression lists): a usage of one is not a dependency the developer wrote.
let isSynthesized = new Func<IType, bool>(t => t.IsGeneratedByCompiler && t.ParentNamespace.Name.Length == 0)
// Optimization: restraint namespaces set
let namespacesSuspect = assembly.ChildNamespaces.Where(
// If a namespace doesn't have a Level value, it must be in a dependency cycle
// or it must be using directly or indirectly a dependency cycle.
n => n.Level == null &&
// Also require the namespace to be JustMyCode to avoid generated namespaces like My / My.Resources in VB.NET
JustMyCode.Contains(n))
from nA in namespacesSuspect
// Select namespaces mutually dependent with nA
let unused = hashset.Add(nA) // Populate hashset
let namespacesMutuallyDependentWith_nA = nA.NamespacesUsed.Using(nA)
.Except(hashset) // <-- avoid reporting both A <-> B and B <-> A
where namespacesMutuallyDependentWith_nA.Count() > 0
from nB in namespacesMutuallyDependentWith_nA.Where(
// Don't match namespaces generated by PostSharp
n => !n.Name.StartsWith("PostSharp.ImplementationDetails"))
// nA and nB are mutually dependent
// Infer which one is low level and which one is high level,
// for that we need to compute the coupling from A to B
// and from B to A in terms of number of types and methods
// usages involved in the coupling.
let typesOfBUsedByA = nB.ChildTypes.Where(t => !isSynthesized(t)).UsedBy(nA).ToArray() // Enumerate once
let couplingA2B =
typesOfBUsedByA.Sum(t => t.TypesUsingMe.Count(t1 => t1.ParentNamespace == nA)) +
typesOfBUsedByA.ChildMethods().Sum(m => m.MethodsCallingMe.Count(m1 => m1.ParentNamespace == nA))
let typesOfAUsedByB = nA.ChildTypes.Where(t => !isSynthesized(t)).UsedBy(nB).ToArray() // Enumerate once
let couplingB2A =
typesOfAUsedByB.Sum(t => t.TypesUsingMe.Count(t1 => t1.ParentNamespace == nB)) +
typesOfAUsedByB.ChildMethods().Sum(m => m.MethodsCallingMe.Count(m1 => m1.ParentNamespace == nB))
// The lowLevelNamespace is inferred from the fact that
// [coupling lowLevel -> highLevel] is lower than [coupling highLevel -> lowLevel]
let lowLevelNamespace = (couplingA2B < couplingB2A) ? nA : nB
let highLevelNamespace = (lowLevelNamespace == nA) ? nB : nA
let highLevelTypesUsed = (lowLevelNamespace == nA) ? typesOfBUsedByA : typesOfAUsedByB
let lowLevelTypesUser = lowLevelNamespace.ChildTypes.UsingAny(highLevelTypesUsed)
let lowLevelTypesMethodsUser = lowLevelTypesUser.Cast<IMember>()
.Concat(lowLevelTypesUser.ChildMethods().Using(highLevelNamespace))
.ToArray() // Enumerate once
// Make the rule works also when lines of code is not available
let lowLevelNamespaceLoc = lowLevelNamespace.NbLinesOfCode ?? (lowLevelNamespace.NbILInstructions / 7)
let highLevelNamespaceLoc = highLevelNamespace.NbLinesOfCode ?? (highLevelNamespace.NbILInstructions / 7)
let annualInterestPerIssue =
// Such issue has at least a Severity.Medium, never a Severity.Low
Math.Max(Severity.Medium.AnnualInterestThreshold().Value.TotalSeconds,
((3600 + lowLevelNamespaceLoc + highLevelNamespaceLoc) / Math.Max(1,lowLevelTypesMethodsUser.Length)).Value)
.ToSeconds().ToAnnualInterest()
// Select in details types and methods involved in the coupling lowLevelNamespace using highLevelNamespace
from tmCulprit in lowLevelTypesMethodsUser
let used = (tmCulprit.IsType ? tmCulprit.AsType.TypesUsed.Where(t => t.ParentNamespace == highLevelNamespace && !isSynthesized(t)) :
tmCulprit.AsMethod.MembersUsed.Where(m => m.ParentNamespace == highLevelNamespace &&
!isSynthesized(m.IsType ? m.AsType : m.ParentType)))
.ToArray() // Enumerate once
// A culprit whose only usages of the high-level namespace are synthesized types is not reported
where used.Length > 0
select new {
tmCulprit,
shouldntUse = used,
becauseNamespace = lowLevelNamespace,
shouldntUseNamespace = highLevelNamespace,
Debt = used.Length.Linear(1, 15, 10, 60).ToMinutes().ToDebt(),
AnnualInterest = annualInterestPerIssue
}
//<Expl>The *{0}* should not use the *{1}* because its parent *{2}* is at lower level than the *{3}*
// that contains the element{1?:s} used.
// This situation prevents **clean layered architecture** because both namespaces use each other.</Expl>
//<Desc>
// Lists types and methods from a low-level namespace that use types and
// methods from a higher-level namespace, for each pair of namespaces using
// each other. Which is low-level is inferred by comparing the couplings
// [A to B] and [B to A] (types, methods and fields involved): the low-to-high
// coupling is typically the significantly lower one. Dependencies on the
// compiler's synthesized types of the global namespace (anonymous types,
// collection-expression lists) are ignored.
//
// This avoids **namespaces dependency cycles**, moving toward a *layered
// architecture* where *low-level* code cannot use *high-level* code — i.e.
// getting rid of **spaghetti code: entangled code not properly layered and
// structured**. More in our white books:
// https://www.ndepend.com/docs/white-books
//</Desc>
//<HowToFix>
// Refactor so that **the low-level namespace doesn't use the high-level
// namespace**. The rule lists each culprit dependency; patterns to remove
// them:
//
// - Move types between the *low-level* and *high-level* namespaces.
//
// - Use the *Dependency Inversion Principle (DIP)*:
// https://blog.ndepend.com/solid-design-the-dependency-inversion-principle-dip
// Create interfaces in the *low-level* namespace, implemented in the
// *high-level* one, so low-level code consumes high-level code through
// interfaces (*callbacks*), passed in via higher-level code — a.k.a.
// *Dependency Injection (DI)*:
// http://en.wikipedia.org/wiki/Dependency_injection
//
// May not apply to libraries with public mutually dependent namespaces,
// where breaking the API can cost more than the entangled code.
//
// **Debt** depends on the number of types and methods used. Both namespaces
// form an unpartitionable *super-component*, so **Annual Interest** is one
// hour plus minutes proportional to their size in lines of code, divided by
// the number of issues listed. It often exceeds the *Debt*: a year unfixed
// costs more than fixing once.
//</HowToFix>
Open in the interactive Rules Explorer · All default rules
Architecture: all 13 items
- Avoid namespaces mutually dependent ND1400
- Avoid namespaces dependency cycles ND1401
- Avoid partitioning the code base through many small library Assemblies ND1402
- Enforcing Clean Architecture ND1412
- UI layer shouldn't use directly DB types ND1403
- UI layer shouldn't use directly DAL layer ND1404
- Assemblies with poor cohesion (RelationalCohesion) ND1405
- Namespaces with poor cohesion (RelationalCohesion) ND1406
- Assemblies that don't satisfy the Abstractness/Instability principle ND1407
- Avoid excessive class coupling ND1410
- Higher cohesion - lower coupling ND1408
- Avoid mutually-dependent types ND1409
- Example of custom rule to check for dependency ND9900