Avoid types with too many fields

Rule ND1002 Code Smells

Matches types with more than 15 fields (constants, static-readonly and enumerations excluded): hard to understand and maintain, symptom of too many responsibilities.

How to fix

Group subsets of fields into smaller types and dispatch the methods logic into them. More advices in Types too Big, HowToFix section.

Estimated Debt: linear from 1 hour (15 fields) to 10 hours (200+).

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 types with too many fields</Name>
// <Id>ND1002:AvoidTypesWithTooManyFields</Id>
warnif count > 0 
// Unity class tend to have more fields. 
// Hence set a threshold of 30 instead of 15 for other kinds of projects.
let threshold = ThirdParty.Assemblies.Any(a => a.Name.StartsWith("UnityEngine")) ? 30 : 15
from t in JustMyCode.Types 

  // Optimization: Fast discard of non-relevant types 
  where !t.IsEnumeration &&
         t.Fields.Count() > threshold 
        
  // Count instance fields and non-constant static fields
  let fields = t.Fields.Where(f =>
          !f.IsGeneratedByCompiler &&
          !f.IsLiteral &&
          !(f.IsStatic && f.IsInitOnly) &&
           JustMyCode.Contains(f) )

  where fields.Count() > threshold 
 
  let methodsAssigningFields = fields.SelectMany(f => f.MethodsAssigningMe)

  orderby fields.Count() descending
select new { 
   t, 
   instanceFields = fields.Where(f => !f.IsStatic),
   staticFields = fields.Where(f => f.IsStatic),
   nbFields = fields.Count(),
   methodsAssigningFields ,   

   // See definition of Size of Instances metric here:
   // https://www.ndepend.com/docs/code-metrics#SizeOfInst
   t.SizeOfInst,

   Debt = fields.Count().Linear(15, 1, 200, 10).ToHours().ToDebt(),

   // The annual interest varies linearly from interest for severity major for 30 methods
   // to interest for severity critical for 200 methods
   AnnualInterest = fields.Count().Linear(15,  Severity.Medium.AnnualInterestThreshold().Value.TotalMinutes, 
                                          200, Severity.Critical.AnnualInterestThreshold().Value.TotalMinutes).ToMinutes().ToAnnualInterest()
}

//<Expl>The *{0}* has {3} fields.
// This may be excessive, and you might need to consider grouping 
// related fields into smaller and more cohesive implementations.
// </Expl>

//<Desc>
// Matches types with more than 15 fields (constants, static-readonly and
// enumerations excluded): hard to understand and maintain, symptom of too
// many responsibilities.
//</Desc>

//<HowToFix>
// Group subsets of fields into smaller types and dispatch the methods logic
// into them. More advices in **Types too Big**, *HowToFix* section.
//
// Estimated Debt: linear from 1 hour (15 fields) to 10 hours (200+).
//</HowToFix>

Code Smells: all 10 items