From now, all methods added should respect basic quality principles
Runs only in diff mode (baseline defined), on methods added since baseline (easily modified to also match refactored methods).
Matched methods violate basic quality principles: too large, too complex (many if, switch case, loops…), too many variables, parameters or overloads.
Matched methods violate basic quality principles: too large, too complex (many if, switch case, loops…), too many variables, parameters or overloads.
How to fix
Split the method into smaller methods or even new classes. Question each local variable's scope: it may become an instance field of the new class(es). Large switch…case can become types implementing a common interface, polymorphism replacing the switch cases tests. Write tests for each method before extracting it.
Constant 5 minutes Debt: the fix effort is already estimated by the Code Smells rules. But severity is High, higher for new methods: the time to improve them is now, before the next production release.
Constant 5 minutes Debt: the fix effort is already estimated by the Code Smells rules. But severity is High, higher for new methods: the time to improve them is now, before the next production release.
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>From now, all methods added should respect basic quality principles</Name>
// <Id>ND1102:FromNowAllMethodsAddedShouldRespectBasicQualityPrinciples</Id>
warnif count > 0
// Gather typesMoved, types whose parent namespaces changed since baseline:
// the methods of such a type are added on one side and removed on the other, not new.
let typesRemoved = context.CompareContext.OlderCodeBase.Application.Types
.Where(t => t.WasRemoved()).Select(t => t.Name).ToHashSetEx()
let typesMoved = Application.Types
.Where(t => t.WasAdded() && typesRemoved.Contains(t.Name)).ToHashSetEx()
// Gather methodsMoved keys: a method removed from a type of the baseline and added, with the
// same name (signature embedded), lines of code and IL size, to another type is relocated
// code (a class split into helpers), not a new method.
let methodsRemovedKeys = context.CompareContext.OlderCodeBase.Application.Methods
.Where(m => m.WasRemoved() && m.NbILInstructions >= 10)
.Select(m => m.Name + "|" + m.NbLinesOfCode.ToString() + "|" + m.NbILInstructions.ToString()).ToHashSetEx()
from m in JustMyCode.Methods where
// Only match methods added since Baseline.
// Uncomment this line to match also refactored methods since Baseline.
// (m.WasAdded() || m.CodeWasChanged()) &&
m.WasAdded() &&
// Don't match methods in types moved, nor methods relocated to another type
!typesMoved.Contains(m.ParentType) &&
!methodsRemovedKeys.Contains(m.Name + "|" + m.NbLinesOfCode.ToString() + "|" + m.NbILInstructions.ToString()) &&
// Don't match async methods here to avoid
// false positives because of special compiler tricks.
!m.IsAsync &&
!m.IsAbstract &&
// Only match methods with enough statements
m.NbLinesOfCode > 20 &&
// Low Quality methods// Metrics' definitions
((m.CyclomaticComplexity >= 12 && // https://www.ndepend.com/docs/code-metrics#CC
m.ILNestingDepth >= 4) || // https://www.ndepend.com/docs/code-metrics#ILNestingDepth
m.NbParameters >= 8) // https://www.ndepend.com/docs/code-metrics#NbParameters
// Don't match methods considered as new because its parameters changed
&& (!m.ParentType.IsPresentInBothBuilds() ||
!m.ParentType.OlderVersion().MethodsAndConstructors.Any(m1 => m1.WasRemoved() && m1.SimpleNameEquals(m.SimpleName)))
select new {
m,
m.NbLinesOfCode,
m.CyclomaticComplexity,
m.ILCyclomaticComplexity,
m.ILNestingDepth,
m.NbParameters,
// Constant Debt estimation, since for such method rules in category "Code Smells"
// accurately estimate the Debt.
Debt = 5.ToMinutes().ToDebt(),
// The Severity is higher for new methods than for refactored methods
AnnualInterest= (m.WasAdded() ? 3 : 1) *
Severity.High.AnnualInterestThreshold()
}
//<Expl>
// The new *{0}* doesn't meet quality standard detailed in other issues.
//</Expl>
//<Desc>
// Runs only in *diff mode* (baseline defined), on methods added since
// baseline (easily modified to also match refactored methods).
//
// Matched methods violate basic quality principles: too large, too complex
// (many *if*, *switch case*, loops…), too many variables, parameters or
// overloads.
//</Desc>
//<HowToFix>
// Split the method into smaller methods or even new classes. Question each
// local variable's scope: it may become an instance field of the new
// class(es). Large *switch…case* can become types implementing a common
// interface, polymorphism replacing the *switch cases tests*. Write tests
// for each method before extracting it.
//
// Constant 5 minutes Debt: the fix effort is already estimated by the
// **Code Smells** rules. But severity is **High**, higher for new methods:
// the time to improve them is **now**, before the next production release.
//</HowToFix>
Open in the interactive Rules Explorer · All default rules
Code Smells Regression: all 9 items
- From now, all types added should respect basic quality principles ND1100
- From now, all types added should be 100% covered by tests ND1101
- From now, all methods added should respect basic quality principles ND1102
- Avoid decreasing code coverage by tests of types ND1103
- Avoid making complex methods even more complex ND1104
- Avoid making large methods even larger ND1105
- Avoid adding methods to a type that already had many methods ND1106
- Avoid adding instance fields to a type that already had many instance fields ND1107
- Avoid transforming an immutable type into a mutable one ND1108