Avoid namespaces with few types

Rule ND1305 Design

Warns about non-global namespaces containing fewer than five types. Numerous sparsely populated namespaces require many using clauses in client code. Namespaces should group types used together (separate ones for mutually exclusive uses) and organize features for discoverability.

The rule source contains a completable list of common infrastructure namespace names; namespaces ending with these names are not matched.

How to fix

Combine namespaces containing just a few types into a single namespace.

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 with few types</Name>
// <Id>ND1305:AvoidNamespacesWithFewTypes</Id>

warnif count > 0 

// Common infrastructure namespace names not matched by the rule.
// Complete this list to your need.
let infraNamespaceNames = new HashSet<string>() {
      "Services","Exceptions","Logging", "Domain",
      "Identity", "Migrations", "Controllers",
      "Specifications", "Interfaces", "Components",
      "Bus", "Models", "EventHandlers", "Mappings",
      "ViewModels", "ViewComponents", "Notifications",
      "Configurations", "Extensions", "Events",
      "Context", "Data", "EventSourcing", "Repository",
      "IoC", "Manage", "Commands", "CommandHandlers",
      "Validations", "EventStoreSQL", "Authorization",
      "Formatters", "Xml", "Json", "Enums",
      "Abstractions", "BaseClasses", "Settings",
      "Helpers", "Validators", "Entities", "Pages"
}

// Only warn for namespaces with few types declared in assemblies with more than 15 types.
// This way the rule avoids matching namespace with few types in assemblies with few types.
from a in JustMyCode.Assemblies
where a.NbTypes >= 15

from n in a.ChildNamespaces 
where n.Name.Length > 0 // Don't match anonymous namespaces
 && !infraNamespaceNames.Contains(n.SimpleName)  // Don't match common infrastructure namespaces
 && !n.ParentNamespaces.Any(pn => infraNamespaceNames.Contains(pn.SimpleName)) // Don't match children of common infrastructure namespaces
 &&  n.Name != n.ParentAssembly.Name  // Don't warn on namespace named as assembly to avoid warning on new VS projects
let types = n.ChildTypes.Where(t => !t.IsGeneratedByCompiler).ToArray()
where 
  types.Length > 0 &&  // Don't match namespaces that contain only types GeneratedByCompiler
  types.Length < 5 &&
  // Only match namespaces that have all types in JustMyCode
  types.All(t => JustMyCode.Contains(t))
  orderby types.Length ascending
select new { 
   n, 
   types,
   Debt = 5.ToMinutes().ToDebt(),
   Severity = Severity.Low
} 

//<Expl>The *{0}* contains only the *{1}*. To prevent an excessive number of *using* clauses in client code, refrain from segregating classes into numerous small namespaces.</Expl>

//<Desc>
// Warns about non-global namespaces containing fewer than five types.
// Numerous sparsely populated namespaces require many *using* clauses in
// client code. Namespaces should group types used together (separate ones
// for mutually exclusive uses) and organize features for discoverability.
//
// The rule source contains a completable list of common infrastructure
// namespace names; namespaces ending with these names are not matched.
//</Desc>

//<HowToFix>
// Combine namespaces containing just a few types into a single namespace.
//</HowToFix>

Design: all 16 items