blob: f30d8ad96194a74c5f8988a597ab35529ffbc0b9 [file] [log] [blame]
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<NDepend AppName="Apache.Ignite" Platform="DotNet" FileWrittenByProductVersion="2017.2.2.8962">
<OutputDir KeepXmlFiles="False">c:\w\incubator-ignite\modules\platforms\dotnet\NDependOut</OutputDir>
<Assemblies>
<Name>Apache.Ignite.Core</Name>
</Assemblies>
<FrameworkAssemblies>
<Name>mscorlib</Name>
<Name>System.Core</Name>
<Name>System.Xml</Name>
<Name>System</Name>
<Name>System.Configuration</Name>
<Name>System.Transactions</Name>
</FrameworkAssemblies>
<Dirs>
<Dir>C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319</Dir>
<Dir>C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\WPF</Dir>
<Dir>$(NdProjectDir)\Apache.Ignite.Core\bin\Debug</Dir>
</Dirs>
<MergeCodeGeneratedByCompiler>True</MergeCodeGeneratedByCompiler>
<Report Kind="0" SectionsEnabled="110591" XslPath="" Flags="261120" />
<BuildComparisonSetting ProjectMode="CurrentProject" BuildMode="NDaysAgoAnalysisResult" ProjectFileToCompareWith="" BuildFileToCompareWith="" NDaysAgo="30" />
<BaselineInUISetting ProjectMode="CurrentProject" BuildMode="NDaysAgoAnalysisResult" ProjectFileToCompareWith="" BuildFileToCompareWith="" NDaysAgo="30" />
<CoverageFiles CoverageDir="" UncoverableAttribute="" />
<TrendMetrics UseCustomLog="False" LogRecurrence="3" LogLabel="2" UseCustomDir="False" CustomDir="">
<Chart Name="Size" ShowInReport="True">
<Serie MetricName="# Lines of Code" MetricUnit="Loc" Color="#FF00BFFF" ChartType="Line" ScaleExp="0" />
<Serie MetricName="# Lines of Code Covered" MetricUnit="Loc" Color="#FF32CD32" ChartType="Area" ScaleExp="0" />
<Serie MetricName="# Lines of Code (NotMyCode)" MetricUnit="Loc" Color="#FFA9A9A9" ChartType="Area" ScaleExp="0" />
<Serie MetricName="# Lines of Comments" MetricUnit="Lines" Color="#FF008000" ChartType="Line" ScaleExp="0" />
</Chart>
<Chart Name="% Coverage and % Debt" ShowInReport="True">
<Serie MetricName="Percentage Code Coverage" MetricUnit="%" Color="#FF32CD32" ChartType="Area" ScaleExp="0" />
<Serie MetricName="Percentage Debt (Metric)" MetricUnit="%" Color="#FFFF0000" ChartType="Line" ScaleExp="0" />
</Chart>
<Chart Name="Issues" ShowInReport="True">
<Serie MetricName="# New Issues since Baseline" MetricUnit="issues" Color="#FFFF0000" ChartType="Line" ScaleExp="0" />
<Serie MetricName="# Issues Fixed since Baseline" MetricUnit="issues" Color="#FF32CD32" ChartType="Line" ScaleExp="0" />
<Serie MetricName="# Blocker/Critical/Major Issues" MetricUnit="issues" Color="#FFFF8C00" ChartType="Line" ScaleExp="0" />
<Serie MetricName="# Issues" MetricUnit="issues" Color="#FFFFD700" ChartType="Line" ScaleExp="-2" />
</Chart>
<Chart Name="Rules" ShowInReport="True">
<Serie MetricName="# Rules" MetricUnit="Rules" Color="#FF66CDAA" ChartType="Line" ScaleExp="0" />
<Serie MetricName="# Rules Violated" MetricUnit="Rules" Color="#FFFF8C00" ChartType="Area" ScaleExp="0" />
<Serie MetricName="# Critical Rules Violated" MetricUnit="Rules" Color="#FFFF0000" ChartType="Area" ScaleExp="0" />
</Chart>
<Chart Name="Quality Gates" ShowInReport="True">
<Serie MetricName="# Quality Gates Fail" MetricUnit="quality gates" Color="#FFFF0000" ChartType="Line" ScaleExp="0" />
<Serie MetricName="# Quality Gates Warn" MetricUnit="quality gates" Color="#FFFF8C00" ChartType="Line" ScaleExp="0" />
<Serie MetricName="# Quality Gates" MetricUnit="quality gates" Color="#FF32CD32" ChartType="Line" ScaleExp="0" />
</Chart>
<Chart Name="Debt" ShowInReport="True">
<Serie MetricName="Debt (Metric)" MetricUnit="man-days" Color="#FFFF0000" ChartType="Line" ScaleExp="0" />
<Serie MetricName="Annual Interest (Metric)" MetricUnit="man-days" Color="#FFFF8C00" ChartType="Line" ScaleExp="0" />
</Chart>
</TrendMetrics>
<HistoricAnalysisResult PersistRecurrence="3" UseCustomDir="False" CustomDir="" />
<SourceFileRebasing FromPath="" ToPath="" />
<PathVariables />
<RuleFiles />
<ProjectRules AreActive="True" />
<ProjectDebtSettings DebtSettingsStorage="0" SettingsFilePath="">
<DebtSettings>
<DebtFactor>1</DebtFactor>
<AnnualInterestFactor>1</AnnualInterestFactor>
<DebtDefault>0</DebtDefault>
<AnnualInterestDefault>0</AnnualInterestDefault>
<DebtStringFormat>$ManDay$</DebtStringFormat>
<MoneyPerManHour>50</MoneyPerManHour>
<Currency>USD</Currency>
<CurrencyLocation>After</CurrencyLocation>
<EstimatedNumberOfManDayToDevelop1000LogicalLinesOfCode>18</EstimatedNumberOfManDayToDevelop1000LogicalLinesOfCode>
<NumberOfWorkDayPerYear>240</NumberOfWorkDayPerYear>
<NumberOfWorkHourPerDay>8</NumberOfWorkHourPerDay>
<A2B_RatingThreshold>5</A2B_RatingThreshold>
<B2C_RatingThreshold>10</B2C_RatingThreshold>
<C2D_RatingThreshold>20</C2D_RatingThreshold>
<D2E_RatingThreshold>50</D2E_RatingThreshold>
<Low2Medium_SeverityThreshold>1200000000</Low2Medium_SeverityThreshold>
<Medium2High_SeverityThreshold>12000000000</Medium2High_SeverityThreshold>
<High2Critical_SeverityThreshold>72000000000</High2Critical_SeverityThreshold>
<Critical2Blocker_SeverityThreshold>360000000000</Critical2Blocker_SeverityThreshold>
</DebtSettings>
</ProjectDebtSettings>
<Queries>
<Group Name="Quality Gates" Active="True" ShownInReport="True">
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Quality Gates Evolution</Name>
from qg in QualityGates
let qgBaseline = qg.OlderVersion()
let relyOnDiff = qgBaseline == null
let evolution = relyOnDiff ? (TrendIcon?)null :
// When a quality gate relies on diff between now and baseline
// it is not executed against the baseline
qg.ValueDiff() == 0d ?
TrendIcon.Constant :
(qg.ValueDiff() > 0 ?
( qg.MoreIsBad ? TrendIcon.RedUp: TrendIcon.GreenUp) :
(!qg.MoreIsBad ? TrendIcon.RedDown: TrendIcon.GreenDown))
select new { qg,
Evolution = evolution,
BaselineStatus = relyOnDiff? (QualityGateStatus?) null : qgBaseline.Status,
Status = qg.Status,
BaselineValue = relyOnDiff? (null) : qgBaseline.ValueString,
Value = qg.ValueString,
}
// <Description>
// Show quality gates evolution between baseline and now.
//
// When a quality gate relies on diff between now and baseline (like *New Debt since Baseline*)
// it is not executed against the baseline and as a consequence its evolution is not available.
//
// Double-click a quality gate for editing.
// </Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <QualityGate Name="Percentage Coverage" Unit="%" />
failif value < 70%
warnif value < 80%
codeBase.PercentageCoverage
//<Description>
// Code coverage is a measure used to describe the degree to which the source code of a program
// is tested by a particular test suite. A program with high code coverage, measured as a percentage,
// has had more of its source code executed during testing which suggests it has a lower chance of
// containing undetected software bugs compared to a program with low code coverage.
//
// Code coverage is certainly the most important quality code metric. But coverage is not enough
// the team needs to ensure that results are checked at test-time. These checks can be done both
// in test code, and in application code through assertions. The important part is that a test
// must fail explicitely when a check gets unvalidated during the test execution.
//
// This quality gate define a warn threshold (70%) and a fail threshold (80%). These are
// indicative thresholds and in practice the more the better. To achieve high coverage and
// low risk, make sure that new and refactored classes gets 100% covered by tests and that
// the application and test code contains as many checks/assertions as possible.
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <QualityGate Name="Percentage Coverage on New Code" Unit="%" />
failif value < 70%
warnif value < 80%
let newMethods = Application.Methods.Where(m => m.WasAdded() && m.NbLinesOfCode > 0)
let locCovered = newMethods.Sum(m => m.NbLinesOfCodeCovered)
let loc = newMethods.Sum(m => m.NbLinesOfCode)
select 100d * locCovered / loc
//<Description>
// *New Code* is defined as methods added since the baseline.
//
// To achieve high code coverage it is essential that new code gets properly
// tested and covered by tests. It is advised that all non-UI new classes gets
// 100% covered.
//
// Typically 90% of a class is easy to cover by tests and 10% is hard to reach
// through tests. It means that this 10% remaining is not easily testable, which
// means it is not well designed, which often means that this code is especially
// **error-prone**. This is the reason why it is important to reach 100% coverage
// for a class, to make sure that potentially *error-prone* code gets tested.
//</Description>
]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <QualityGate Name="Percentage Coverage on Refactored Code" Unit="%" />
failif value < 70%
warnif value < 80%
let newMethods = Application.Methods.Where(m => m.CodeWasChanged() && m.NbLinesOfCode > 0)
let locCovered = newMethods.Sum(m => m.NbLinesOfCodeCovered)
let loc = newMethods.Sum(m => m.NbLinesOfCode)
select 100d * locCovered / loc
//<Description>
// *Refactored Code* is defined as methods where *code was changed* since the baseline.
//
// Comment changes and formatting changes are not considerd as refactoring.
//
// To achieve high code coverage it is essential that refactored code gets properly
// tested and covered by tests. It is advised that when refactoring a class
// or a method, it is important to also write tests to make sure it gets 100% covered.
//
// Typically 90% of a class is easy to cover by tests and 10% is hard to reach
// through tests. It means that this 10% remaining is not easily testable, which
// means it is not well designed, which often means that this code is especially
// **error-prone**. This is the reason why it is important to reach 100% coverage
// for a class, to make sure that potentially *error-prone* code gets tested.
//</Description>
]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <QualityGate Name="Blocker Issues" Unit="issues" />
failif count > 0 issues
from i in Issues
where i.Severity == Severity.Blocker
select new { i, i.Severity, i.Debt, i.AnnualInterest }
//<Description>
// An issue with the severity **Blocker** cannot move to production, it must be fixed.
//
// The severity of an issue is either defined explicitely in the rule source code,
// either inferred from the issue *annual interest* and thresholds defined in the
// NDepend Project Properties > Issue and Debt.
//</Description>
]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <QualityGate Name="Critical Issues" Unit="issues" />
failif count > 10 issues
warnif count > 0 issues
from i in Issues
where i.Severity == Severity.Critical
select new { i, i.Severity, i.Debt, i.AnnualInterest }
//<Description>
// An issue with a severity level **Critical** shouldn't move to production.
// It still can for business imperative needs purposes, but at worst it must
// be fixed during the next iterations.
//
// The severity of an issue is either defined explicitely in the rule source code,
// either inferred from the issue *annual interest* and thresholds defined in the
// NDepend Project Properties > Issue and Debt.
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <QualityGate Name="New Blocker / Critical / High Issues" Unit="issues" />
failif count > 0 issues
from i in Issues
where i.Severity.EqualsAny(Severity.Blocker, Severity.Critical, Severity.High) &&
// Count both the new issues and the issues that became at least Critical
(i.WasAdded() || i.OlderVersion().Severity < Severity.High)
select new { i, i.Severity, i.Debt, i.AnnualInterest }
//<Description>
// An issue with the severity **Blocker** cannot move to production, it must be fixed.
//
// An issue with a severity level **Critical** shouldn't move to production.
// It still can for business imperative needs purposes, but at worth it must be fixed
// during the next iterations.
//
// An issue with a severity level **High** should be fixed quickly, but can wait until
// the next scheduled interval.
//
// The severity of an issue is either defined explicitely in the rule source code,
// either inferred from the issue *annual interest* and thresholds defined in the
// NDepend Project Properties > Issue and Debt.
//</Description>
]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <QualityGate Name="Critical Rules Violated" Unit="rules" />
failif count > 0 rules
from r in Rules where r.IsCritical && r.IsViolated()
select new { r, issues = r.Issues() }
//<Description>
// The concept of critical rule is useful to pinpoint certain rules that
// should not be violated.
//
// A rule can be made critical just by checking the *Critical button* in the
// rule edition control and then saving the rule.
//
// This quality gate fails if any critical rule gets any violations.
//
// When no baseline is available, rules that rely on diff are not counted.
// If you observe that this quality gate count slightly decreases with no apparent reason,
// the reason is certainly that rules that rely on diff are not counted
// because the baseline is not defined.
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <QualityGate Name="Percentage Debt" Unit="%" />
failif value > 30%
warnif value > 20%
let timeToDev = codeBase.EffortToDevelop()
let debt = Issues.Sum(i => i.Debt)
select 100d * debt.ToManDay() / timeToDev.ToManDay()
// <Description>
// % Debt total is defined as a percentage on:
//
// • the estimated total effort to develop the code base
//
// • and the the estimated total time to fix all issues (the Debt)
//
// Estimated total effort to develop the code base is inferred from
// # lines of code of the code base and from the
// *Estimated number of man-day to develop 1000 logicial lines of code*
// setting found in NDepend Project Properties > Issue and Debt.
//
// Debt documentation: http://www.ndepend.com/docs/technical-debt#Debt
//
// This quality gates fails if the estimated debt is more than 30%
// of the estimated effort to develop the code base, and warns if the
// estimated debt is more than 20% of the estimated effort to develop
// the code base
// </Description>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <QualityGate Name="Debt" Unit="man-days" />
failif value > 50 man-days
warnif value > 30 man-days
Issues.Sum(i => i.Debt).ToManDay()
//<Description>
// This Quality Gate is disabled per default because the fail and warn
// thresholds of unacceptable Debt in man-days can only depend on the
// project size, number of developers and overall context.
//
// However you can refer to the default Quality Gate **Percentage Debt**.
//
// The Debt is defined as the sum of estimated effort to fix all issues.
// Debt documentation: http://www.ndepend.com/docs/technical-debt#Debt
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <QualityGate Name="New Debt since Baseline" Unit="man-days" />
failif value > 2 man-days
warnif value > 0 man-days
let debt = Issues.Sum(i => i.Debt)
let debtInBaseline = IssuesInBaseline.Sum(i => i.Debt)
select (debt - debtInBaseline).ToManDay()
//<Description>
// This Quality Gate fails if the estimated effort to fix new or worsened
// issues (what is called the *New Debt since Baseline*) is higher
// than 2 man-days.
//
// This Quality Gate warns if this estimated effort is positive.
//
// Debt documentation: http://www.ndepend.com/docs/technical-debt#Debt
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <QualityGate Name="Debt Rating per Namespace" Unit="namespaces" />
failif count > 0 namespaces
from n in Application.Namespaces
where n.DebtRating() != null &&
n.DebtRating().Value.EqualsAny(DebtRating.E, DebtRating.D)
select new {
n,
debtRating = n.DebtRating(),
debtRatio = n.DebtRatio(), // % of debt from which DebtRating is inferred
devTimeInManDay = n.EffortToDevelop().ToDebt(),
debtInManDay = n.AllDebt(),
issues = n.AllIssues()
}
// <Description>
// Forbid namespaces with a poor Debt Rating equals to **E** or **D**.
//
// The **Debt Rating** for a code element is estimated by the value of the **Debt Ratio**
// and from the various rating thresholds defined in this project *Debt Settings*.
//
// The **Debt Ratio** of a code element is a percentage of **Debt Amount** (in floating man-days)
// compared to the **estimated effort to develop the code element** (also in floating man-days).
//
// The **estimated effort to develop the code element** is inferred from the code elements
// number of lines of code, and from the project *Debt Settings* parameters
// *estimated number of man-days to develop 1000* **logical lines of code**.
//
// The **logical lines of code** corresponds to the number of debug breakpoints in a method
// and doesn't depend on code formatting nor comments.
//
// The Quality Gate can be modified to match assemblies, types or methods
// with a poor Debt Rating, instead of matching namespaces.
// </Description>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <QualityGate Name="Annual Interest" Unit="man-days" />
failif value > 50 man-days
warnif value > 30 man-days
Issues.Sum(i => i.AnnualInterest).ToManDay()
//<Description>
// This Quality Gate is disabled per default because the fail and warn
// thresholds of unacceptable Annual-Interest in man-days can only depend
// on the project size, number of developers and overall context.
//
// However you can refer to the default Quality Gate
// **New Annual Interest since Baseline**.
//
// The Annual-Interest is defined as the sum of estimated annual cost
// in man-days, to leave all issues unfixed.
//
// Each rule can either provide a formula to compute the Annual-Interest
// per issue, or assign a **Severity** level for each issue. Some thresholds
// defined in *Project Properties > Issue and Debt > Annual Interest* are
// used to infer an Annual-Interest value from a Severity level.
// Annual Interest documentation: http://www.ndepend.com/docs/technical-debt#AnnualInterest
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <QualityGate Name="New Annual Interest since Baseline" Unit="man-days" />
failif value > 2 man-days
warnif value > 0 man-days
let ai = Issues.Sum(i => i.AnnualInterest)
let aiInBaseline = IssuesInBaseline.Sum(i => i.AnnualInterest)
select (ai - aiInBaseline).ToManDay()
//<Description>
// This Quality Gate fails if the estimated annual cost to leave all issues
// unfixed, increased from more than 2 man-days since the baseline.
//
// This Quality Gate warns if this estimated annual cost is positive.
//
// This estimated annual cost is named the **Annual-Interest**.
//
// Each rule can either provide a formula to compute the Annual-Interest
// per issue, or assign a **Severity** level for each issue. Some thresholds
// defined in *Project Properties > Issue and Debt > Annual Interest* are
// used to infer an Annual-Interest value from a Severity level.
// Annual Interest documentation: http://www.ndepend.com/docs/technical-debt#AnnualInterest
//</Description>]]></Query>
</Group>
<Group Name="Hot Spots" Active="True" ShownInReport="True">
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Types Hot Spots</Name>
from t in JustMyCode.Types
where t.AllDebt() > Debt.Zero &&
t.AllAnnualInterest() > AnnualInterest.Zero
orderby t.AllDebt().Value.TotalMinutes descending
select new { t,
Debt = t.AllDebt(),
Issues = t.AllIssues(), // AllIssues = {types issues} union {members issues}
AnnualInterest = t.AllAnnualInterest(),
BreakingPoint = t.AllBreakingPoint(),
t.NbLinesOfCode,
// t.PercentageCoverage, to uncomment if coverage data is imported
DebtRating = t.DebtRating(),
DebtRatio = t.DebtRatio()
}
//<Description>
// This query lists **types with most Debt**,
// or in other words, types with issues that would need
// the largest effort to get fixed.
//
// Both issues on the type and its members are
// taken account.
//
// Since untested code often generates a lot of
// Debt, the type size and percentage coverage is shown
// (just uncomment *t.PercentageCoverage* in the query
// source code once you've imported the coverage data).
//
// The *Debt Rating* and *Debt Ratio* are also shown
// for informational purpose.
//
// --
//
// The amount of *Debt* is not a measure to prioritize
// the effort to fix issues, it is an estimation of how far
// the team is from clean code that abides by the rules set.
//
// For each issue the *Annual Interest* estimates the annual
// cost to leave the issues unfixed. The *Severity* of an issue
// is estimated through thresholds from the *Annual Interest*.
//
// The **Debt Breaking Point** represents the duration
// from now when the estimated cost to leave the issue unfixed
// costs as much as the estimated effort to fix it.
//
// Hence the shorter the **Debt Breaking Point**
// the largest the **Return on Investment** for fixing
// the issue. The **Breaking Point is the right metric
// to prioritize issues fix**.
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Types to Fix Priority</Name>
from t in JustMyCode.Types
where t.AllBreakingPoint() > TimeSpan.Zero &&
t.AllDebt().Value > 30.ToMinutes()
orderby t.AllBreakingPoint().TotalMinutes ascending
select new { t,
BreakingPoint = t.AllBreakingPoint(),
Debt = t.AllDebt(),
AnnualInterest = t.AllAnnualInterest(),
Issues = t.AllIssues(),
t.NbLinesOfCode,
// t.PercentageCoverage, to uncomment if coverage data is imported
DebtRating = t.DebtRating(),
DebtRatio = t.DebtRatio()
}
//<Description>
// This query lists types per increasing
// **Debt Breaking Point**.
//
// For each issue the *Debt* estimates the
// effort to fix the issue, and the *Annual Interest*
// estimates the annual cost to leave the issue unfixed.
// The *Severity* of an issue is estimated through
// thresholds from the *Annual Interest* of the issue.
//
// The **Debt Breaking Point** represents the duration
// from now when the estimated cost to leave the issue unfixed
// costs as much as the estimated effort to fix it.
//
// Hence the shorter the **Debt Breaking Point**
// the largest the **Return on Investment** for fixing
// the issues.
//
// Often new and refactored types since baseline will be
// listed first, because issues on these types get a
// higher *Annual Interest* because it is important to
// focus first on new issues.
//
//
// --
//
// Both issues on the type and its members are
// taken account.
//
// Only types with at least 30 minutes of Debt are listed
// to avoid parasiting the list with the numerous
// types with small *Debt*, on which the *Breaking Point*
// value makes less sense.
//
// The *Annual Interest* estimates the cost per year
// in man-days to leave these issues unfixed.
//
// Since untested code often generates a lot of
// Debt, the type size and percentage coverage is shown
// (just uncomment *t.PercentageCoverage* in the query
// source code once you've imported the coverage data).
//
// The *Debt Rating* and *Debt Ratio* are also shown
// for informational purpose.
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Issues to Fix Priority</Name>
from i in Issues
// Don't show first issues with BreakingPoint equals to zero.
orderby i.BreakingPoint != TimeSpan.Zero ? i.BreakingPoint : TimeSpan.MaxValue
select new { i,
Debt = i.Debt,
AnnualInterest = i.AnnualInterest,
BreakingPoint = i.BreakingPoint,
CodeElement = i.CodeElement
}
//<Description>
// This query lists issues per increasing
// **Debt Breaking Point**.
//
// Double-click an issue to edit its rule and
// select the issue in the rule result. This way
// you can view all information concerning the issue.
//
// For each issue the *Debt* estimates the
// effort to fix the issue, and the *Annual Interest*
// estimates the annual cost to leave the issue unfixed.
// The *Severity* of an issue is estimated through
// thresholds from the *Annual Interest* of the issue.
//
// The **Debt Breaking Point** represents the duration
// from now when the estimated cost to leave the issue unfixed
// costs as much as the estimated effort to fix it.
//
// Hence the shorter the **Debt Breaking Point**
// the largest the **Return on Investment** for fixing
// the issue.
//
// Often issues on new and refactored code elements since
// baseline will be listed first, because such issues get a
// higher *Annual Interest* because it is important to
// focus first on new issues on recent code.
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Debt and Issues per Rule</Name>
from r in Rules
where r.IsViolated()
orderby r.Debt().Value descending
select new {
r,
Issues = r.Issues(),
Debt = r.Debt(),
AnnualInterest = r.AnnualInterest(),
BreakingPoint = r.BreakingPoint(),
Category = r.Category
}
//<Description>
// This query lists violated rules with most *Debt* first.
//
// A rule violated has issues. For each issue the *Debt*
// estimates the effort to fix the issue.
//
// --
//
// The amount of *Debt* is not a measure to prioritize
// the effort to fix issues, it is an estimation of how far
// the team is from clean code that abides by the rules set.
//
// For each issue the *Annual Interest* estimates the annual
// cost to leave the issues unfixed. The *Severity* of an issue
// is estimated through thresholds from the *Annual Interest*.
//
// The **Debt Breaking Point** represents the duration
// from now when the estimated cost to leave the issue unfixed
// costs as much as the estimated effort to fix it.
//
// Hence the shorter the **Debt Breaking Point**
// the largest the **Return on Investment** for fixing
// the issue. The **Breaking Point is the right metric
// to prioritize issues fix**.
//
// --
//
// Notice that rules can be grouped in *Rule Category*. This
// way you'll see categories that generate most *Debt*.
//
// Typically the rules that generate most *Debt* are the
// ones related to *Code Coverage by Tests*, *Architecture*
// and *Code Smells*.
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>New Debt and Issues per Rule</Name>
from r in Rules
where r.IsViolated() && r.IssuesAdded().Count() > 0
orderby r.DebtDiff().Value descending
select new {
r,
IssuesAdded = r.IssuesAdded(),
IssuesFixed = r.IssuesFixed(),
Issues = r.Issues(),
Debt = r.Debt(),
DebtDiff = r.DebtDiff(),
Category = r.Category
}
//<Description>
// This query lists violated rules that have new issues
// since baseline, with most **new Debt** first.
//
// A rule violated has issues. For each issue the *Debt*
// estimates the effort to fix the issue.
//
// --
//
// New issues since the baseline are consequence of recent code
// refactoring sessions. They represent good opportunities
// of fix because the code recently refactored is fresh in
// the developers mind, which means fixing now costs less
// than fixing later.
//
// Fixing issues on recently touched code is also a good way
// to foster practices that will lead to higher code quality
// and maintainability, including writing unit-tests
// and avoiding unnecessary complex code.
//
// --
//
// Notice that rules can be grouped in *Rule Category*. This
// way you'll see categories that generate most *Debt*.
//
// Typically the rules that generate most *Debt* are the
// ones related to *Code Coverage by Tests*, *Architecture*
// and *Code Smells*.
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Debt and Issues per Code Element</Name>
from elem in CodeElements
where elem.HasIssue()
orderby elem.Debt().Value descending
select new {
elem,
Issues = elem.Issues(),
Debt = elem.Debt(),
AnnualInterest = elem.AnnualInterest(),
BreakingPoint = elem.BreakingPoint()
}
//<Description>
// This query lists code elements that have issues,
// with most *Debt* first.
//
// For each code element the *Debt* estimates
// the effort to fix the element issues.
//
// The amount of *Debt* is not a measure to prioritize
// the effort to fix issues, it is an estimation of how far
// the team is from clean code that abides by the rules set.
//
// For each element the *Annual Interest* estimates the annual
// cost to leave the elements issues unfixed. The *Severity* of an
// issue is estimated through thresholds from the *Annual Interest*
// of the issue.
//
// The **Debt Breaking Point** represents the duration
// from now when the estimated cost to leave the issues unfixed
// costs as much as the estimated effort to fix it.
//
// Hence the shorter the **Debt Breaking Point**
// the largest the **Return on Investment** for fixing
// the issue. The **Breaking Point is the right metric
// to prioritize issues fix**.
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>New Debt and Issues per Code Element</Name>
from elem in CodeElements
where elem.HasIssue() && elem.IssuesAdded().Count() > 0
orderby elem.DebtDiff().Value descending
select new {
elem,
IssuesAdded = elem.IssuesAdded(),
IssuesFixed = elem.IssuesFixed(),
Issues = elem.Issues(),
Debt = elem.Debt(),
DebtDiff = elem.DebtDiff()
}
//<Description>
// This query lists code elements that have new issues
// since baseline, with most **new Debt** first.
//
// For each code element the *Debt* estimates
// the effort to fix the element issues.
//
// New issues since the baseline are consequence of recent code
// refactoring sessions. They represent good opportunities
// of fix because the code recently refactored is fresh in
// the developers mind, which means fixing now costs less
// than fixing later.
//
// Fixing issues on recently touched code is also a good way
// to foster practices that will lead to higher code quality
// and maintainability, including writing unit-tests
// and avoiding unnecessary complex code.
//</Description>
]]></Query>
</Group>
<Group Name="Code Smells" Active="True" ShownInReport="False">
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="True"><![CDATA[// <Name>Avoid types too big</Name>
warnif count > 0 from t in JustMyCode.Types where
// First filter on type to optimize
t.NbLinesOfCode > 200
// # IL Instructions is commented, because with LINQ syntax, a few lines of code can compile to hundreds of IL instructions.
// || t.NbILInstructions > 3000
// What matters is the # lines of code in JustMyCode
let locJustMyCode = t.MethodsAndContructors.Where(m => JustMyCode.Contains(m)).Sum(m => m.NbLinesOfCode)
where locJustMyCode > 200
let isStaticWithNoMutableState = (t.IsStatic && t.Fields.Any(f => !f.IsImmutable))
let staticFactor = (isStaticWithNoMutableState ? 0.2 : 1)
orderby locJustMyCode descending
select new {
t,
locJustMyCode,
t.NbILInstructions,
t.Methods,
t.Fields,
Debt = (staticFactor*locJustMyCode.Linear(200, 1, 2000, 10)).ToHours().ToDebt(),
// The annual interest varies linearly from interest for severity major for 300 loc
// to interest for severity critical for 2000 loc
AnnualInterest = staticFactor*(locJustMyCode.Linear(
200, Severity.Medium.AnnualInterestThreshold().Value.TotalMinutes,
2000, Severity.Critical.AnnualInterestThreshold().Value.TotalMinutes)).ToMinutes().ToAnnualInterest()
}
//<Description>
// This rule matches types with more than 200 lines of code.
// **Only lines of code in JustMyCode methods are taken account.**
//
// Types where *NbLinesOfCode > 200* are extremely complex
// to develop and maintain.
// See the definition of the NbLinesOfCode metric here
// http://www.ndepend.com/docs/code-metrics#NbLinesOfCode
//
// Maybe you are facing the **God Class** phenomenon:
// A **God Class** is a class that controls way too many other classes
// in the system and has grown beyond all logic to become
// *The Class That Does Everything*.
//</Description>
//<HowToFix>
// Types with many lines of code
// should be split in a group of smaller types.
//
// To refactor a *God Class* you'll need patience,
// and you might even need to recreate everything from scratch.
// Here are a few refactoring advices:
//
// • The logic in the *God Class* must be splitted in smaller classes.
// These smaller classes can eventually become private classes nested
// in the original *God Class*, whose instances objects become
// composed of instances of smaller nested classes.
//
// • Smaller classes partitioning should be driven by the multiple
// responsibilities handled by the *God Class*. To identify these
// responsibilities it often helps to look for subsets of methods
// strongly coupled with subsets of fields.
//
// • If the *God Class* contains way more logic than states, a good
// option can be to define one or several static classes that
// contains no static field but only pure static methods. A pure static
// method is a function that computes a result only from inputs
// parameters, it doesn't read nor assign any static or instance field.
// The main advantage of pure static methods is that they are easily
// testable.
//
// • Try to maintain the interface of the *God Class* at first
// and delegate calls to the new extracted classes.
// In the end the *God Class* should be a pure facade without its own logic.
// Then you can keep it for convenience or throw it away and
// start to use the new classes only.
//
// • Unit Tests can help: write tests for each method before extracting it
// to ensure you don't break functionality.
//
// The estimated Debt, which means the effort to fix such issue,
// varies linearly from 1 hour for a 200 lines of code type,
// up to 10 hours for a type with 2.000 or more lines of code.
//
// In Debt and Interest computation, this rule takes account of the fact
// that static types with no mutable fields are just a collection of
// static methods that can be easily splitted and moved from one type
// to another.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Avoid types with too many methods</Name>
warnif count > 0 from t in JustMyCode.Types
// Optimization: Fast discard of non-relevant types
where t.Methods.Count() > 20
// Don't match these methods
let methods = t.Methods.Where(
m => !(m.IsGeneratedByCompiler ||
m.IsConstructor || m.IsClassConstructor ||
m.IsPropertyGetter || m.IsPropertySetter ||
m.IsEventAdder || m.IsEventRemover))
where methods.Count() > 20
orderby methods.Count() descending
let isStaticWithNoMutableState = (t.IsStatic && t.Fields.Any(f => !f.IsImmutable))
let staticFactor = (isStaticWithNoMutableState ? 0.2 : 1)
select new {
t,
nbMethods = methods.Count(),
instanceMethods = methods.Where(m => !m.IsStatic),
staticMethods = methods.Where(m => m.IsStatic),
t.NbLinesOfCode,
Debt = (staticFactor*methods.Count().Linear(20, 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 = (staticFactor*methods.Count().Linear(
20, Severity.Medium.AnnualInterestThreshold().Value.TotalMinutes,
200, Severity.Critical.AnnualInterestThreshold().Value.TotalMinutes)).ToMinutes().ToAnnualInterest()
}
//<Description>
// This rule matches types with more than 20 methods.
// Such type might be hard to understand and maintain.
//
// Notice that methods like constructors or property
// and event accessors are not taken account.
//
// Having many methods for a type might be a symptom
// of too many responsibilities implemented.
//
// Maybe you are facing the **God Class** phenomenon:
// A **God Class** is a class that controls way too many other classes
// in the system and has grown beyond all logic to become
// *The Class That Does Everything*.
//</Description>
//<HowToFix>
// To refactor properly a *God Class* please read *HowToFix advices*
// from the default rule **Types to Big**.
////
// The estimated Debt, which means the effort to fix such issue,
// varies linearly from 1 hour for a type with 20 methods,
// up to 10 hours for a type with 200 or more methods.
//
// In Debt and Interest computation, this rule takes account of the fact
// that static types with no mutable fields are just a collection of
// static methods that can be easily splitted and moved from one type
// to another.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Avoid types with too many fields</Name>
warnif count > 0 from t in JustMyCode.Types
// Optimization: Fast discard of non-relevant types
where !t.IsEnumeration &&
t.Fields.Count() > 15
// 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() > 15
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),
methodsAssigningFields ,
// See definition of Size of Instances metric here:
// http://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()
}
//<Description>
// This rule matches types with more than 15 fields.
// Such type might be hard to understand and maintain.
//
// Notice that constant fields and static-readonly fields are not counted.
// Enumerations types are not counted also.
//
// Having many fields for a type might be a symptom
// of too many responsibilities implemented.
//</Description>
//<HowToFix>
// To refactor such type and increase code quality and maintainability,
// certainly you'll have to group subsets of fields into smaller types
// and dispatch the logic implemented into the methods
// into these smaller types.
//
// More refactoring advices can be found in the default rule
// **Types to Big**, *HowToFix* section.
//
// The estimated Debt, which means the effort to fix such issue,
// varies linearly from 1 hour for a type with 15 fields,
// to up to 10 hours for a type with 200 or more fields.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="True"><![CDATA[// <Name>Avoid methods too big, too complex</Name>
warnif count > 0 from m in JustMyCode.Methods where
m.ILNestingDepth > 2 &&
(m.NbLinesOfCode > 35 ||
m.CyclomaticComplexity > 20 ||
m.ILCyclomaticComplexity > 60)
let complexityScore = m.NbLinesOfCode/2 + m.CyclomaticComplexity + m.ILCyclomaticComplexity/3 + 3*m.ILNestingDepth
orderby complexityScore descending,
m.CyclomaticComplexity descending,
m.ILCyclomaticComplexity descending,
m.ILNestingDepth descending
select new {
m,
m.NbLinesOfCode,
m.CyclomaticComplexity,
m.ILCyclomaticComplexity,
m.ILNestingDepth,
complexityScore,
Debt = complexityScore.Linear(30, 40, 400, 8*60).ToMinutes().ToDebt(),
// The annual interest varies linearly from interest for severity minor
// to interest for severity major
AnnualInterest = complexityScore .Linear(30, Severity.Medium.AnnualInterestThreshold().Value.TotalMinutes,
200, 2*(Severity.High.AnnualInterestThreshold().Value.TotalMinutes)).ToMinutes().ToAnnualInterest()
}
//<Description>
// This rule matches methods where *ILNestingDepth* > 2
// and (*NbLinesOfCode* > 35
// or *CyclomaticComplexity* > 20
// or *ILCyclomaticComplexity* > 60)
// Such method is typically hard to understand and maintain.
//
// Maybe you are facing the **God Method** phenomenon.
// A "God Method" is a method that does way too many processes in the system
// and has grown beyond all logic to become *The Method That Does Everything*.
// When need for new processes increases suddenly some programmers realize:
// why should I create a new method for each processe if I can only add an *if*.
//
// See the definition of the *CyclomaticComplexity* metric here:
// http://www.ndepend.com/docs/code-metrics#CC
//
// See the definition of the *ILCyclomaticComplexity* metric here:
// http://www.ndepend.com/docs/code-metrics#ILCC
//
// See the definition of the *ILNestingDepth* metric here:
// http://www.ndepend.com/docs/code-metrics#ILNestingDepth
//</Description>
//<HowToFix>
// A large and complex method should be split in smaller methods,
// or even one or several classes can be created for that.
//
// During this process it is important to question the scope of each
// variable local to the method. This can be an indication if
// such local variable will become an instance field of the newly created class(es).
//
// Large *switch…case* structures might be refactored through the help
// of a set of types that implement a common interface, the interface polymorphism
// playing the role of the *switch cases tests*.
//
// Unit Tests can help: write tests for each method before extracting it
// to ensure you don't break functionality.
//
// The estimated Debt, which means the effort to fix such issue,
// varies from 40 minutes to 8 hours, linearly from a weighted complexity score.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="True"><![CDATA[// <Name>Avoid methods with too many parameters</Name>
warnif count > 0 from m in JustMyCode.Methods where
m.NbParameters >= 7
orderby m.NbParameters descending
select new {
m,
m.NbParameters,
Debt = m.NbParameters.Linear(7, 1, 40, 6).ToHours().ToDebt(),
// The annual interest varies linearly from interest for severity Medium for 7 parameters
// to interest for severity Critical for 40 parameters
AnnualInterest = m.NbParameters.Linear(7, Severity.Medium.AnnualInterestThreshold().Value.TotalMinutes,
40, Severity.Critical.AnnualInterestThreshold().Value.TotalMinutes).ToMinutes().ToAnnualInterest()
}
//<Description>
// This rule matches methods with more than 8 parameters.
// Such method is painful to call and might degrade performance.
// See the definition of the *NbParameters* metric here:
// http://www.ndepend.com/docs/code-metrics#NbParameters
//</Description>
//<HowToFix>
// More properties/fields can be added to the declaring type to
// handle numerous states. An alternative is to provide
// a class or a structure dedicated to handle arguments passing.
// For example see the class *System.Diagnostics.ProcessStartInfo*
// and the method *System.Diagnostics.Process.Start(ProcessStartInfo)*.
//
// The estimated Debt, which means the effort to fix such issue,
// varies linearly from 1 hour for a method with 7 parameters,
// up to 6 hours for a methods with 40 or more parameters.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Avoid methods with too many local variables</Name>
warnif count > 0 from m in JustMyCode.Methods where
m.NbVariables > 15
orderby m.NbVariables descending
select new {
m,
m.NbVariables,
Debt = m.NbVariables.Linear(15, 1, 80, 6).ToHours().ToDebt(),
// The annual interest varies linearly from interest for severity Medium for 15 variables
// to interest for severity Critical for 80 variables
AnnualInterest = m.NbVariables.Linear(15, Severity.Medium.AnnualInterestThreshold().Value.TotalMinutes,
80, Severity.Critical.AnnualInterestThreshold().Value.TotalMinutes).ToMinutes().ToAnnualInterest()
}
//<Description>
// This rule matches methods with more than 15 variables.
//
// Methods where *NbVariables > 8* are hard to understand and maintain.
// Methods where *NbVariables > 15* are extremely complex and must be refactored.
//
// See the definition of the *Nbvariables* metric here:
// http://www.ndepend.com/docs/code-metrics#Nbvariables
//</Description>
//<HowToFix>
// To refactor such method and increase code quality and maintainability,
// certainly you'll have to split the method into several smaller methods
// or even create one or several classes to implement the logic.
//
// During this process it is important to question the scope of each
// variable local to the method. This can be an indication if
// such local variable will become an instance field of the newly created class(es).
//
// The estimated Debt, which means the effort to fix such issue,
// varies linearly from 10 minutes for a method with 15 variables,
// up to 2 hours for a methods with 80 or more variables.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Avoid methods with too many overloads</Name>
warnif count > 0
let lookup = JustMyCode.Methods.Where(m =>
m.NbOverloads >= 6 &&
!m.IsOperator && // Don't report operator overload
// Don't match overloads due tu the visitor pattern, based on a naming convention.
!m.SimpleName.ToLower().StartsWithAny("visit", "dispatch")
).ToLookup(m => m.ParentType.FullName + "."+ m.SimpleName)
from @group in lookup
let overloads = @group.ToArray()
orderby overloads.Length descending
select new {
m = @group.First(),
overloads,
Debt = (3*overloads.Length).ToMinutes().ToDebt(),
Severity = Severity.Medium
}
//<Description>
// Method overloading is the ability to create multiple methods of the same name
// with different implementations, and various set of parameters.
//
// This rule matches sets of methods with 6 overloads or more.
//
// Such method set might be a problem to maintain
// and provokes coupling higher than necessary.
//
// See the definition of the *NbOverloads* metric here
// http://www.ndepend.com/docs/code-metrics#NbOverloads
//</Description>
//<HowToFix>
// Typically the *too many overloads* phenomenon appears when an algorithm
// takes a various set of in-parameters. Each overload is presented as
// a facility to provide a various set of in-parameters.
// In such situation, the C# and VB.NET language feature named
// *Named and Optional arguments* should be used.
//
// The *too many overloads* phenomenon can also be a consequence of the usage
// of the **visitor design pattern** http://en.wikipedia.org/wiki/Visitor_pattern
// since a method named *Visit()* must be provided for each sub type.
// For this reason, the default version of this rule doesn't match overloads whose name
// start with "visit" or "dispatch" (case-unsensitive) to avoid match
// overload visitors, and you can adapt this rule to your own naming convention.
//
// Sometime *too many overloads* phenomenon is not the symptom of a problem,
// for example when a *numeric to something conversion* method applies to
// all numeric and nullable numeric types.
//
// The estimated Debt, which means the effort to fix such issue,
// is of 3 minutes per method overload.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Avoid methods potentially poorly commented</Name>
warnif count > 0 from m in JustMyCode.Methods where
m.PercentageComment < 10 &&
m.NbLinesOfCode > 20
let nbLinesOfCodeNotCommented = m.NbLinesOfCode - m.NbLinesOfComment
orderby nbLinesOfCodeNotCommented descending
select new {
m,
m.PercentageComment,
m.NbLinesOfCode,
m.NbLinesOfComment,
nbLinesOfCodeNotCommented,
Debt = nbLinesOfCodeNotCommented .Linear(20, 2, 200, 20).ToMinutes().ToDebt(),
// The annual interest varies linearly from interest for severity major for 300 loc
// to interest for severity critical for 2000 loc
AnnualInterest = m.PercentageComment.Linear(
0, 8 *(Severity.Medium.AnnualInterestThreshold().Value.TotalMinutes),
20, Severity.Medium.AnnualInterestThreshold().Value.TotalMinutes).ToMinutes().ToAnnualInterest()
}
//<Description>
// This rule matches methods with less than 10% of comment lines and that have
// at least 20 lines of code. Such method might need to be more commented.
//
// See the definitions of the *Comments metric* here:
// http://www.ndepend.com/docs/code-metrics#PercentageComment
// http://www.ndepend.com/docs/code-metrics#NbLinesOfComment
//
// Notice that only comments about the method implementation
// (comments in method body) are taken account.
//</Description>
//<HowToFix>
// Typically add more comment. But code commenting is subject to controversy.
// While poorly written and designed code would needs a lot of comment
// to be understood, clean code doesn't need that much comment, especially
// if variables and methods are properly named and convey enough information.
// Unit-Test code can also play the role of code commenting.
//
// However, even when writing clean and well-tested code, one will have
// to write **hacks** at a point, usually to circumvent some API limitations or bugs.
// A hack is a non-trivial piece of code, that doesn't make sense at first glance,
// and that took time and web research to be found.
// In such situation comments must absolutely be used to express the intention,
// the need for the hacks and the source where the solution has been found.
//
// The estimated Debt, which means the effort to comment such method,
// varies linearly from 2 minutes for 10 lines of code not commented,
// up to 20 minutes for 200 or more, lines of code not commented.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Avoid types with poor cohesion</Name>
warnif count > 0 from t in JustMyCode.Types where
t.LCOM > 0.8 &&
t.NbFields > 10 &&
t.NbMethods >10
let poorCohesionScore = 1/(1.01 - t.LCOM)
orderby poorCohesionScore descending
select new {
t,
t.LCOM,
t.NbMethods,
t.NbFields,
poorCohesionScore,
Debt = poorCohesionScore.Linear(5, 5, 50, 4*60).ToMinutes().ToDebt(),
// The annual interest varies linearly from interest for severity Medium for low poorCohesionScore
// to 4 times interest for severity High for high poorCohesionScore
AnnualInterest = poorCohesionScore.Linear(5, Severity.Medium.AnnualInterestThreshold().Value.TotalMinutes,
50, 4*(Severity.High.AnnualInterestThreshold().Value.TotalMinutes)).ToMinutes().ToAnnualInterest()
}
//<Description>
// This rule is based on the *LCOM code metric*,
// LCOM stands for **Lack Of Cohesion of Methods**.
// See the definition of the LCOM metric here
// http://www.ndepend.com/docs/code-metrics#LCOM
//
// The LCOM metric measures the fact that most methods are using most fields.
// A class is considered utterly cohesive (which is good)
// if all its methods use all its instance fields.
//
// Only types with enough methods and fields are taken account to avoid bias.
// The LCOM takes its values in the range [0-1].
//
// This rule matches types with LCOM higher than 0.8.
// Such value generally pinpoints a **poorly cohesive class**.
//</Description>
//<HowToFix>
// To refactor a poorly cohesive type and increase code quality and maintainability,
// certainly you'll have to split the type into several smaller and more cohesive types
// that together, implement the same logic.
//
// The estimated Debt, which means the effort to fix such issue,
// varies linearly from 5 minutes for a type with a low poorCohesionScore,
// up to 4 hours for a type with high poorCohesionScore.
//</HowToFix>]]></Query>
</Group>
<Group Name="Code Smells Regression" Active="True" ShownInReport="False">
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>From now, all types added should respect basic quality principles</Name>
warnif count > 0 from t in JustMyCode.Types where
// Only match types added since Baseline.
// Uncomment this line to match also refactored types since Baseline.
// (t.WasAdded() || t.CodeWasChanged()) &&
t.WasAdded() &&
// Eliminate interfaces, enumerations or types only with constant fields
// by making sure we are matching type with code.
t.NbLinesOfCode > 10 &&
// Optimization: Fast discard of non-relevant types
(t.Fields.Count() > 20 || t.Methods.Count() > 20)
// Count instance fields and non-constant static fields
let fields = t.Fields.Where(f =>
!f.IsLiteral &&
!(f.IsStatic && f.IsInitOnly))
// Don't match these methods
let methods = t.Methods.Where(
m => !(m.IsConstructor || m.IsClassConstructor ||
m.IsGeneratedByCompiler ||
m.IsPropertyGetter || m.IsPropertySetter ||
m.IsEventAdder || m.IsEventRemover))
where
// Low Quality types Metrics' definitions are available here:
// http://www.ndepend.com/docs/code-metrics#MetricsOnTypes
( // Types with too many methods
fields.Count() > 20 ||
methods.Count() > 20 ||
// Complex Types that use more than 50 other types
t.NbTypesUsed > 50
)
select new {
t,
t.NbLinesOfCode,
instanceMethods = methods.Where(m => !m.IsStatic),
staticMethods = methods.Where(m => m.IsStatic),
instanceFields = fields.Where(f => !f.IsStatic),
staticFields = fields.Where(f => f.IsStatic),
t.TypesUsed,
// Constant Debt estimation, since for such type rules in category "Code Smells"
// accurately estimate the Debt.
Debt = 10.ToMinutes().ToDebt(),
// The Severity is higher for new types than for refactored types
AnnualInterest= (t.WasAdded() ? 3 : 1) *
Severity.High.AnnualInterestThreshold()
}
//<Description>
// This rule is executed only if a *baseline for comparison* is defined (*diff mode*).
// This rule operates only on types added since baseline.
//
// This rule can be easily modified to also match types refactored since baseline,
// that don't satisfy all quality criterions.
//
// Types matched by this rule not only have been recently added or refactored,
// but also somehow violate one or several basic quality principles,
// whether it has too many methods,
// it has too many fields,
// or is using too many types.
// Any of these criterions is often a symptom of a type with too many responsibilities.
//
// Notice that to count methods and fields, methods like constructors
// or property and event accessors are not taken account.
// Notice that constants fields and static-readonly fields are not counted.
// Enumerations types are not counted also.
//</Description>
//<HowToFix>
// To refactor such type and increase code quality and maintainability,
// certainly you'll have to split the type into several smaller types
// that together, implement the same logic.
//
// Issues of this rule have a constant 10 minutes Debt, because the Debt,
// which means the effort to fix such issue, is already estimated for issues
// of rules in the category **Code Smells**.
//
// However issues of this rule have a **High** severity, with even more
// interests for issues on new types since baseline, because the proper time
// to increase the quality of these types is **now**, before they get commited
// in the next production release.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>From now, all types added should be 100% covered by tests</Name>
warnif count > 0 from t in JustMyCode.Types where
// Only match types added since Baseline.
// Uncomment this line to match also refactored types since Baseline.
// (t.WasAdded() || t.CodeWasChanged()) &&
t.WasAdded() &&
// …that are not 100% covered by tests
t.PercentageCoverage < 100
let methodsCulprit = t.Methods.Where(m => m.PercentageCoverage < 100)
select new {
t,
t.PercentageCoverage,
methodsCulprit,
t.NbLinesOfCode,
// Constant Debt estimation, since for such type rules in category "Coverage"
// accurately estimate the untested code Debt.
Debt = 10.ToMinutes().ToDebt(),
// The Severity is higher for new types than for refactored types
AnnualInterest= (t.WasAdded() ? 3 : 1) *
Severity.High.AnnualInterestThreshold()
}
//<Description>
// This rule is executed only if a *baseline for comparison* is defined (*diff mode*).
// This rule operates only on types added since baseline.
//
// This rule can be easily modified to also match types refactored since baseline,
// that are not 100% covered by tests.
//
// This rule is executed only if some code coverage data is imported
// from some code coverage files.
//
// Often covering 10% of remaining uncovered code of a class,
// requires as much work as covering the first 90%.
// For this reason, typically teams estimate that 90% coverage is enough.
// However *untestable code* usually means *poorly written code*
// which usually leads to *error prone code*.
// So it might be worth refactoring and making sure to cover the 10% remaining code
// because **most tricky bugs might come from this small portion of hard-to-test code**.
//
// Not all classes should be 100% covered by tests (like UI code can be hard to test)
// but you should make sure that most of the logic of your application
// is defined in some *easy-to-test classes*, 100% covered by tests.
//
// In this context, this rule warns when a type added or refactored since the baseline,
// is not fully covered by tests.
//</Description>
//<HowToFix>
// Write more unit-tests dedicated to cover code not covered yet.
// If you find some *hard-to-test code*, it is certainly a sign that this code
// is not *well designed* and hence, needs refactoring.
//
// You'll find code impossible to cover by unit-tests, like calls to *MessageBox.Show()*.
// An infrastructure must be defined to be able to *mock* such code at test-time.
//
// Issues of this rule have a constant 10 minutes Debt, because the Debt,
// which means the effort to write tests for the culprit type, is already
// estimated for issues in the category **Code Coverage**.
//
// However issues of this rule have a **High** severity, with even more
// interests for issues on new types since baseline, because the proper time
// to write tests for these types is **now**, before they get commited
// in the next production release.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>From now, all methods added should respect basic quality principles</Name>
warnif count > 0 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() &&
// Low Quality methods// Metrics' definitions
( m.NbLinesOfCode > 30 || // http://www.ndepend.com/docs/code-metrics#NbLinesOfCode
m.NbILInstructions > 200 || // http://www.ndepend.com/docs/code-metrics#NbILInstructions
m.CyclomaticComplexity > 20 || // http://www.ndepend.com/docs/code-metrics#CC
m.ILCyclomaticComplexity > 50 || // http://www.ndepend.com/docs/code-metrics#ILCC
m.ILNestingDepth > 4 || // http://www.ndepend.com/docs/code-metrics#ILNestingDepth
m.NbParameters > 5 || // http://www.ndepend.com/docs/code-metrics#NbParameters
m.NbVariables > 8 || // http://www.ndepend.com/docs/code-metrics#NbVariables
m.NbOverloads > 6 )
select new {
m,
m.NbLinesOfCode,
m.NbILInstructions,
m.CyclomaticComplexity,
m.ILCyclomaticComplexity,
m.ILNestingDepth,
m.NbParameters,
m.NbVariables,
m.NbOverloads, // http://www.ndepend.com/docs/code-metrics#NbOverloads
// 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()
}
//<Description>
// This rule is executed only if a *baseline for comparison* is defined (*diff mode*).
// This rule operates only on methods added or refactored since the baseline.
//
// This rule can be easily modified to also match methods refactored since baseline,
// that don't satisfy all quality criterions.
//
// Methods matched by this rule not only have been recently added or refactored,
// but also somehow violate one or several basic quality principles,
// whether it is too large (too many *lines of code*),
// too complex (too many *if*, *switch case*, loops…)
// has too many variables, too many parameters
// or has too many overloads.
//</Description>
//<HowToFix>
// To refactor such method and increase code quality and maintainability,
// certainly you'll have to split the method into several smaller methods
// or even create one or several classes to implement the logic.
//
// During this process it is important to question the scope of each
// variable local to the method. This can be an indication if
// such local variable will become an instance field of the newly created class(es).
//
// Large *switch…case* structures might be refactored through the help
// of a set of types that implement a common interface, the interface polymorphism
// playing the role of the *switch cases tests*.
//
// Unit Tests can help: write tests for each method before extracting it
// to ensure you don't break functionality.
//
// Issues of this rule have a constant 5 minutes Debt, because the Debt,
// which means the effort to fix such issue, is already estimated for issues
// of rules in the category **Code Smells**.
//
// However issues of this rule have a **High** severity, with even more
// interests for issues on new methods since baseline, because the proper time
// to increase the quality of these methods is **now**, before they get commited
// in the next production release.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Avoid decreasing code coverage by tests of types</Name>
warnif count > 0
from t in JustMyCode.Types where
t.IsPresentInBothBuilds() && t.CoverageDataAvailable && t.OlderVersion().CoverageDataAvailable
let locDiff = (int)t.NbLinesOfCode.Value - (int)t.OlderVersion().NbLinesOfCode.Value
where locDiff >= 0
let uncoveredLoc = (int)t.NbLinesOfCodeNotCovered.Value - ((int)t.OlderVersion().NbLinesOfCodeNotCovered.Value + locDiff)
where uncoveredLoc > 0
orderby uncoveredLoc descending
select new {
t,
OldCoveragePercent = t.OlderVersion().PercentageCoverage,
NewCoveragePercent = t.PercentageCoverage,
OldLoc = t.OlderVersion().NbLinesOfCode,
NewLoc = t.NbLinesOfCode,
uncoveredLoc,
Debt = uncoveredLoc.Linear(1, 15, 100, 3*60).ToMinutes().ToDebt(),
// The annual interest varies linearly from interest for severity High for one line of code that is not covered by tests anymore
// to interest for severity Critical for 50 lines of code that are not covered by tests anymore
AnnualInterest = uncoveredLoc.Linear(1, Severity.High.AnnualInterestThreshold().Value.TotalMinutes,
50, 2*Severity.Critical.AnnualInterestThreshold().Value.TotalMinutes).ToMinutes().ToAnnualInterest()
}
//<Description>
// This rule is executed only if a *baseline for comparison* is defined (*diff mode*).
//
// This rule is executed only if some code coverage data is imported
// from some code coverage files.
//
// This rule warns when the number of lines of a type covered by tests
// decreased since the baseline. In case the type faced some refactoring
// since the baseline, this loss in coverage is estimated only for types
// with more lines of code, where # lines of code covered now is lower
// than # lines of code covered in baseline + the extra number of
// lines of code.
//
// Such situation can mean that some tests have been removed
// but more often, this means that the type has been modified,
// and that changes haven't been covered properly by tests.
//
// To visualize changes in code, right-click a matched type and select:
//
// • Compare older and newer versions of source file
//
// • or Compare older and newer versions disassembled with Reflector
//</Description>
//<HowToFix>
// Write more unit-tests dedicated to cover changes in matched types
// not covered yet.
// If you find some *hard-to-test code*, it is certainly a sign that this code
// is not *well designed* and hence, needs refactoring.
//
// The estimated Debt, which means the effort to cover by test
// code that used to be covered, varies linearly 15 minutes to 3 hours,
// depending on the number of lines of code that are not covered by tests anymore.
//
// Severity of issues of this rule varies from **High** to **Critical**
// depending on the number of lines of code that are not covered by tests anymore.
// Because the loss in code coverage happened since the baseline,
// the severity is high because it is important to focus on these issues
// **now**, before such code gets released in production.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Avoid making complex methods even more complex</Name>
warnif count > 0
let complexityScoreProc = new Func<IMethod, double>(m =>
(m.CyclomaticComplexity + m.ILCyclomaticComplexity/3 + 5*m.ILNestingDepth).Value)
from m in JustMyCode.Methods where
!m.IsAbstract &&
m.IsPresentInBothBuilds() &&
m.CodeWasChanged() &&
m.OlderVersion().CyclomaticComplexity > 6
let complexityScore = complexityScoreProc(m)
let oldComplexityScore = complexityScoreProc(m.OlderVersion())
where complexityScore > oldComplexityScore
let complexityScoreDiff = complexityScoreProc(m) - complexityScoreProc(m.OlderVersion())
orderby complexityScoreDiff descending
select new {
m,
oldComplexityScore ,
complexityScore ,
diff= complexityScoreDiff,
Debt = complexityScoreDiff.Linear(1, 15, 50, 60).ToMinutes().ToDebt(),
// The annual interest varies linearly from interest for severity Medium for a tiny complexity increment
// to interest for severity critical for 2000 loc
AnnualInterest = complexityScoreDiff.Linear(1, Severity.High.AnnualInterestThreshold().Value.TotalMinutes,
50, 4*(Severity.High.AnnualInterestThreshold().Value.TotalMinutes)).ToMinutes().ToAnnualInterest()
}
//<Description>
// This rule is executed only if a *baseline for comparison* is defined (*diff mode*).
//
// The method complexity is measured through the code metric
// *Cyclomatic Complexity* defined here:
// http://www.ndepend.com/docs/code-metrics#CC
//
// This rule warns when a method already complex
// (i.e with *Cyclomatic Complexity* higher than 6)
// become even more complex since the baseline.
//
// This rule needs assemblies PDB files and source code
// to be available at analysis time, because the *Cyclomatic Complexity*
// is inferred from the source code and source code location
// is inferred from PDB files. See:
// http://www.ndepend.com/docs/ndepend-analysis-inputs-explanation
//
// To visualize changes in code, right-click a matched method and select:
//
// • Compare older and newer versions of source file
//
// • or Compare older and newer versions disassembled with Reflector
//</Description>
//<HowToFix>
// A large and complex method should be split in smaller methods,
// or even one or several classes can be created for that.
//
// During this process it is important to question the scope of each
// variable local to the method. This can be an indication if
// such local variable will become an instance field of the newly created class(es).
//
// Large *switch…case* structures might be refactored through the help
// of a set of types that implement a common interface, the interface polymorphism
// playing the role of the *switch cases tests*.
//
// Unit Tests can help: write tests for each method before extracting it
// to ensure you don't break functionality.
//
// The estimated Debt, which means the effort to fix such issue,
// varies linearly from 15 to 60 minutes depending on the extra complexity added.
//
// Issues of this rule have a **High** severity, because it is important to focus
// on these issues **now**, before such code gets released in production.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Avoid making large methods even larger</Name>
warnif count > 0
from m in JustMyCode.Methods where
!m.IsAbstract &&
// Eliminate constructors from match, since they get larger
// as soons as some fields initialization are added.
!m.IsConstructor &&
!m.IsClassConstructor &&
// Filter just here for optimization
m.NbLinesOfCode > 15 &&
m.IsPresentInBothBuilds() &&
m.CodeWasChanged()
let oldLoc = m.OlderVersion().NbLinesOfCode
where oldLoc > 15 && m.NbLinesOfCode > oldLoc
let diff = m.NbLinesOfCode - oldLoc
where diff > 0
orderby diff descending
select new {
m,
oldLoc,
newLoc = m.NbLinesOfCode,
diff,
Debt = diff.Linear(1, 10, 100, 60).ToMinutes().ToDebt(),
// The annual interest varies linearly from interest for severity Medium for a tiny complexity increment
// to interest for severity critical for 2000 loc
AnnualInterest = diff .Linear(1, Severity.High.AnnualInterestThreshold().Value.TotalMinutes,
100, 4*(Severity.High.AnnualInterestThreshold().Value.TotalMinutes)).ToMinutes().ToAnnualInterest()
}
//<Description>
// This rule is executed only if a *baseline for comparison* is defined (*diff mode*).
//
// This rule warns when a method already large
// (i.e with more than 15 lines of code)
// become even larger since the baseline.
//
// The method size is measured through the code metric
// *# Lines of Code* defined here:
// http://www.ndepend.com/docs/code-metrics#NbLinesOfCode
//
// This rule needs assemblies PDB files
// to be available at analysis time, because the *# Lines of Code*
// is inferred from PDB files. See:
// http://www.ndepend.com/docs/ndepend-analysis-inputs-explanation
//
// To visualize changes in code, right-click a matched method and select:
//
// • Compare older and newer versions of source file
//
// • or Compare older and newer versions disassembled with Reflector
//</Description>
//<HowToFix>
// Usually too big methods should be split in smaller methods.
//
// But long methods with no branch conditions, that typically initialize some data,
// are not necessarily a problem to maintain, and might not need refactoring.
//
// The estimated Debt, which means the effort to fix such issue,
// varies linearly from 5 to 20 minutes depending
// on the number of lines of code added.
//
// The estimated Debt, which means the effort to fix such issue,
// varies linearly from 10 to 60 minutes depending on the extra complexity added.
//
// Issues of this rule have a **High** severity, because it is important to focus
// on these issues **now**, before such code gets released in production.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Avoid adding methods to a type that already had many methods</Name>
warnif count > 0
// Don't count constructors and methods generated by the compiler!
let getMethodsProc = new Func<IType, IList<IMethod>>(
t => t.Methods.Where(m =>
!m.IsConstructor && !m.IsClassConstructor &&
!m.IsGeneratedByCompiler).ToArray())
from t in JustMyCode.Types where
t.NbMethods > 30 && // Just here for optimization
t.IsPresentInBothBuilds()
// Optimization: fast discard of non-relevant types
where t.OlderVersion().NbMethods > 30
let oldMethods = getMethodsProc(t.OlderVersion())
where oldMethods.Count > 30
let newMethods = getMethodsProc(t)
where newMethods.Count > oldMethods.Count
let addedMethods = newMethods.Where(m => m.WasAdded())
let removedMethods = oldMethods.Where(m => m.WasRemoved())
orderby addedMethods.Count() descending
select new {
t,
nbOldMethods = oldMethods.Count,
nbNewMethods = newMethods.Count,
addedMethods,
removedMethods,
Debt = (10*addedMethods.Count()).ToMinutes().ToDebt(),
AnnualInterest = addedMethods.Count().Linear(
1, Severity.Medium.AnnualInterestThreshold().Value.TotalMinutes,
100, 4*(Severity.High.AnnualInterestThreshold().Value.TotalMinutes)).ToMinutes().ToAnnualInterest()
}
//<Description>
// This rule is executed only if a *baseline for comparison* is defined (*diff mode*).
//
// Types where number of methods is greater than 15
// might be hard to understand and maintain.
//
// This rule lists types that already had more than 15 methods
// at the baseline time, and for which new methods have been added.
//
// Having many methods for a type might be a symptom
// of too many responsibilities implemented.
//
// Notice that constructors and methods generated by the compiler
// are not taken account.
//</Description>
//<HowToFix>
// To refactor such type and increase code quality and maintainability,
// certainly you'll have to split the type into several smaller types
// that together, implement the same logic.
//
// The estimated Debt, which means the effort to fix such issue,
// is equal to 10 minutes per method added.
//
// Issues of this rule have a **High** severity, because it is important to focus
// on these issues **now**, before such code gets released in production.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Avoid adding instance fields to a type that already had many instance fields</Name>
warnif count > 0
let getFieldsProc = new Func<IType, IList<IField>>(
t => t.Fields.Where(f =>
!f.IsLiteral &&
!f.IsGeneratedByCompiler &&
!f.IsStatic).ToArray())
from t in JustMyCode.Types where
!t.IsEnumeration &&
t.IsPresentInBothBuilds()
// Optimization: fast discard of non-relevant types
where t.OlderVersion().NbFields > 15
let oldFields = getFieldsProc(t.OlderVersion())
where oldFields.Count > 15
let newFields = getFieldsProc(t)
where newFields.Count > oldFields.Count
let addedFields = newFields.Where(f => f.WasAdded())
let removedFields = oldFields.Where(f => f.WasRemoved())
orderby addedFields.Count() descending
select new {
t,
nbOldFields = oldFields.Count,
nbNewFields = newFields.Count,
addedFields,
removedFields,
Debt = (10*addedFields.Count()).ToMinutes().ToDebt(),
AnnualInterest = addedFields.Count().Linear(
1, Severity.High.AnnualInterestThreshold().Value.TotalMinutes,
100, 4*(Severity.High.AnnualInterestThreshold().Value.TotalMinutes)).ToMinutes().ToAnnualInterest()
}
//<Description>
// This rule is executed only if a *baseline for comparison* is defined (*diff mode*).
//
// Types where number of fields is greater than 15
// might be hard to understand and maintain.
//
// This rule lists types that already had more than 15 fields
// at the baseline time, and for which new fields have been added.
//
// Having many fields for a type might be a symptom
// of too many responsibilities implemented.
//
// Notice that *constants* fields and *static-readonly* fields are not taken account.
// Enumerations types are not taken account also.
//</Description>
//<HowToFix>
// To refactor such type and increase code quality and maintainability,
// certainly you'll have to group subsets of fields into smaller types
// and dispatch the logic implemented into the methods
// into these smaller types.
//
// The estimated Debt, which means the effort to fix such issue,
// is equal to 10 minutes per field added.
//
// Issues of this rule have a **High** severity, because it is important to focus
// on these issues **now**, before such code gets released in production.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[//<Name>Avoid transforming an immutable type into a mutable one</Name>
warnif count > 0
from t in Application.Types where
t.CodeWasChanged() &&
t.OlderVersion().IsImmutable &&
!t.IsImmutable &&
// Don't take account of immutable types transformed into static types (not deemed as immutable)
!t.IsStatic
let culpritFields = t.InstanceFields.Where(f => f.IsImmutable)
select new {
t,
culpritFields,
Debt = (10 + 10*culpritFields.Count()).ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// This rule is executed only if a *baseline for comparison* is defined (*diff mode*).
//
// A type is considered as *immutable* if its instance fields
// cannot be modified once an instance has been built by a constructor.
//
// Being immutable has several fortunate consequences for a type.
// For example its instance objects can be used concurrently
// from several threads without the need to synchronize accesses.
//
// Hence users of such type often rely on the fact that the type is immutable.
// If an immutable type becomes mutable, there are chances that this will break
// users code.
//
// This is why this rule warns about such immutable type that become mutable.
//
// The estimated Debt, which means the effort to fix such issue,
// is equal to 2 minutes per instance field that became mutable.
//</Description>
//<HowToFix>
// If being immutable is an important property for a matched type,
// then the code must be refactored to preserve immutability.
//
// The estimated Debt, which means the effort to fix such issue,
// is equal to 10 minutes plus 10 minutes per instance fields of
// the matched type that is now mutable.
//
// Issues of this rule have a **High** severity, because it is important to focus
// on these issues **now**, before such code gets released in production.
//</HowToFix>]]></Query>
</Group>
<Group Name="Object Oriented Design" Active="True" ShownInReport="True">
<Query Active="False" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Avoid interfaces too big</Name>
warnif count > 0
from i in JustMyCode.Types
where i.IsInterface && i.NbMethods >= 10 // Optimization First threshold
// A get;set; property count as one method
let properties = i.Methods.Where(m => m.SimpleName.Length > 4 && (m.IsPropertyGetter || m.IsPropertySetter))
.Distinct(m => m.SimpleName.Substring(4, m.SimpleName.Length -4))
// An event count as one method
let events = i.Methods.Where(m => (m.IsEventAdder|| m.IsEventRemover))
.Distinct(m => m.SimpleName.Replace("add_","").Replace("remove_",""))
let methods = i.Methods.Where(m => !m.IsPropertyGetter && !m.IsPropertySetter && !m.IsEventAdder && !m.IsEventRemover)
let methodsCount = methods.Count() + properties.Count() + events.Count()
where methodsCount >= 10
let publicFactor = i.IsPubliclyVisible ? 1 : 0.5
orderby methodsCount descending
select new {
i,
Methods= methods,
Properties = properties,
Events = events,
Debt = (publicFactor*methodsCount.Linear(10, 20, 100, 7*60)).ToMinutes().ToDebt(),
// The annual interest varies linearly from interest for severity Medium for an interface with 10 methods
// to interest for severity Critical for an interface with 100 methods and more
AnnualInterest = (publicFactor*methodsCount.Linear(
10, Severity.Medium.AnnualInterestThreshold().Value.TotalMinutes,
100, Severity.Critical.AnnualInterestThreshold().Value.TotalMinutes))
.ToMinutes().ToAnnualInterest()
}
//<Description>
// This rule matches interfaces with more than 10 methods.
// Interfaces are abstractions and are meant to simplify the code structure.
// An interface should represent a single responsibility.
// Making an interface too large, too complex, necessarily means
// that the interface has too many responsibilities.
//
// A property with getter or setter or both count as one method.
// An event count as one method.
//</Description>
//<HowToFix>
// Typically to fix such issue, the interface must be refactored
// in a grape of smaller *single-responsibility* interfaces.
//
// A classic example is a *ISession* large interface, responsible
// for holding states, run commands and offer various accesses
// and facilities.
//
// The classic problem for a large public interface is that it has
// many clients that consume it. As a consequence splitting it in
// smaller interfaces has an important impact and it is not always
// feasible.
//
// The estimated Debt, which means the effort to fix such issue,
// varies linearly from 20 minutes for an interface with 10 methods,
// up to 7 hours for an interface with 100 or more methods.
// The Debt is divided by two if the interface is not publicly
// visible, because in such situation only the current project is impacted
// by the refactoring.
//</HowToFix>
]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Base class should not use derivatives</Name>
warnif count > 0
let excludedTypes = new[] {"TcpDiscoveryIpFinderBase", "EvictionPolicyBase", "PlatformTargetAdapter"}
from baseClass in JustMyCode.Types
where baseClass.IsClass && !excludedTypes.Contains(baseClass.Name)
&& baseClass.NbChildren > 0 // <-- for optimization!
let derivedClassesUsed = baseClass.DerivedTypes.UsedBy(baseClass)
// Don't warn when a base class is using nested private derived class
.Where(derivedClass =>
!(derivedClass.IsNested &&
derivedClass.Visibility == Visibility.Private &&
derivedClass.ParentType == baseClass
))
where derivedClassesUsed.Count() > 0
let derivedClassesMemberUsed = derivedClassesUsed.SelectMany(c => c.Members).UsedBy(baseClass)
orderby derivedClassesMemberUsed.Count() descending
select new {
baseClass,
derivedClassesUsed,
derivedClassesMemberUsed,
Debt = 3*(derivedClassesUsed.Count()+derivedClassesMemberUsed.Count()).ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// In *Object-Oriented Programming*, the **open/closed principle** states:
// *software entities (components, classes, methods, etc.) should be open
// for extension, but closed for modification*.
// http://en.wikipedia.org/wiki/Open/closed_principle
//
// Hence a base class should be designed properly to make it easy to derive from,
// this is *extension*. But creating a new derived class, or modifying an
// existing one, shouldn't provoke any *modification* in the base class.
// And if a base class is using some derivative classes somehow, there
// are good chances that such *modification* will be needed.
//
// Extending the base class is not anymore a simple operation,
// this is not good design.
//
// Note that this rule doesn't warn when a base class is using a derived class
// that is nested in the base class and declared as private. In such situation
// we consider that the derived class is an encapsulated implementation
// detail of the base class.
//</Description>
//<HowToFix>
// Understand the need for using derivatives,
// then imagine a new design, and then refactor.
//
// Typically an algorithm in the base class needs to access something
// from derived classes. You can try to encapsulate this access behind
// an abstract or a virtual method.
//
// If you see in the base class some conditions on *typeof(DerivedClass)*
// not only *urgent refactoring* is needed. Such condition can easily
// be replaced through an abstract or a virtual method.
//
// Sometime you'll see a base class that creates instance of some derived classes.
// In such situation, certainly using the *factory method pattern*
// http://en.wikipedia.org/wiki/Factory_method_pattern
// or the *abstract factory pattern*
// http://en.wikipedia.org/wiki/Abstract_factory_pattern
// will improve the design.
//
// The estimated Debt, which means the effort to fix such issue,
// is equal to 3 minutes per derived class used by the base class +
// 3 minutes per member of a derived class used by the base class.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Class shouldn't be too deep in inheritance tree</Name>
warnif count > 0 from t in JustMyCode.Types
where t.IsClass
let baseClasses = t.BaseClasses.ExceptThirdParty()
where baseClasses.Count() >= 3
orderby baseClasses.Count() descending
select new {
t,
baseClasses,
// The metric value DepthOfInheritance takes account
// of third-party base classessee its definition here:
// http://www.ndepend.com/docs/code-metrics#DIT
t.DepthOfInheritance,
Debt = (baseClasses.Count() -2)*3.ToMinutes().ToDebt(),
Severity = Severity.Medium
}
//<Description>
// This rule warns about classes having 3 or more base classes.
// Notice that third-party base classes are not counted
// because this rule is about your code design, not
// third-party libraries consumed design.
//
// *In theory*, there is nothing wrong having a *long inheritance chain*,
// if the modelization has been well thought out,
// if each base class is a well-designed refinement of the domain.
//
// *In practice*, modeling properly a domain demands a lot of effort
// and experience and more often than not, a *long inheritance chain*
// is a sign of confused design, that will be hard to work with and maintain.
//</Description>
//<HowToFix>
// In *Object-Oriented Programming*, a well-known motto is
// **Favor Composition over Inheritance**.
//
// This is because *inheritance* comes with pitfalls.
// In general, the implementation of a derived class is very bound up with
// the base class implementation. Also a base class exposes implementation
// details to its derived classes, that's why it's often said that
// inheritance breaks encapsulation.
//
// On the other hands, *Composition* favors binding with interfaces
// over binding with implementations. Hence, not only the encapsulation
// is preserved, but the design is clearer, because interfaces make it explicit
// and less coupled.
//
// Hence, to break a *long inheritance chain*, *Composition* is often
// a powerful way to enhance the design of the refactored underlying logic.
//
// You can also read:
// http://en.wikipedia.org/wiki/Composition_over_inheritance and
// http://stackoverflow.com/questions/49002/prefer-composition-over-inheritance
//
// The estimated Debt, which means the effort to fix such issue,
// depends linearly upon the depth of inheritance.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Class with no descendant should be sealed if possible</Name>
warnif count > 0 from t in JustMyCode.Types where
t.IsClass &&
t.NbChildren ==0 &&
!t.IsSealed &&
!t.IsStatic &&
!t.IsPubliclyVisible // You might want to comment this condition
// if you are developing an application,
// instead of developing a library
// with public classes that are intended to be
// sub-classed by your clients.
orderby t.NbLinesOfCode descending
select new {
t,
t.NbLinesOfCode,
Debt = 30.ToSeconds().ToDebt(),
Severity = Severity.Medium
}
//<Description>
// If a *non-static* class isn't declared with the keyword *sealed*,
// it means that it can be subclassed everywhere the *non-sealed*
// class is visible.
//
// Making a class a *base class* requires significant design effort.
// Subclassing a *non-sealed* class, not initially designed
// to be subclassed, will lead to unanticipated design issue.
//
// Most classes are *non-sealed* because developers don't care about
// the keyword *sealed*, not because the primary intention was to write
// a class that can be subclassed.
//
// There are minor performance gain in declaring a class as *sealed*.
// But the real benefit of doing so, is actually to **express the
// intention**: *this class has not be designed to be a base class,
// hence it is not allowed to subclass it*.
//
// Notice that by default this rule doesn't match *public* class
// to avoid matching classes that are intended to be sub-classed by
// third-party code using your library.
// If you are developing an application and not a library,
// just uncomment the clause *!t.IsPubliclyVisible*.
//</Description>
//<HowToFix>
// For each matched class, take the time to assess if it is really
// meant to be subclassed. Certainly most matched class will end up
// being declared as *sealed*.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Overrides of Method() should call base.Method()</Name>
warnif count > 0
from t in Types // Take account of third-party base classes also
// Bother only classes with descendant
where t.IsClass && t.NbChildren > 0
from mBase in t.InstanceMethods
where mBase.IsVirtual &&
!mBase.IsThirdParty &&
!mBase.IsAbstract &&
!mBase.IsExplicitInterfaceImpl
from mOverride in mBase.OverridesDirectDerived
where !mOverride.IsUsing(mBase) &&
JustMyCode.Contains(mOverride) // Don't warn on generated code
select new {
mOverride,
shouldCall = mBase,
definedInBaseClass = mBase.ParentType,
Debt = 5.ToMinutes().ToDebt(),
Severity = Severity.Medium
}
//<Description>
// Typically overrides of a base method, should **refine** or **complete**
// the behavior of the base method. If the base method is not called,
// the base behavior is not refined but it is *replaced*.
//
// Violations of this rule are a sign of *design flaw*,
// especially if the actual design provides valid reasons
// that advocates that the base behavior must be replaced and not refined.
//</Description>
//<HowToFix>
// You should investigate if *inheritance* is the right choice
// to bind the base class implementation with the derived classes
// implementations. Does presenting the method with polymorphic
// behavior through an interface, would be a better design choice?
//
// In such situation, often using the design pattern **template method**
// http://en.wikipedia.org/wiki/Template_method_pattern might help
// improving the design.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="True"><![CDATA[// <Name>Do not hide base class methods</Name>
warnif count > 0
// Define a lookup table indexing methods by their name including parameters signature.
let lookup = Methods.Where(m => !m.IsConstructor && !m.IsStatic && !m.IsGeneratedByCompiler)
.ToLookup(m1 => m1.Name)
from t in Application.Types
where !t.IsStatic && t.IsClass &&
// Discard classes deriving directly from System.Object
t.DepthOfInheritance > 1
where t.BaseClasses.Any()
// For each methods not overriding any methods (new slot),
// let's check if it hides by name some methods defined in base classes.
from m in t.InstanceMethods
where m.IsNewSlot && !m.IsExplicitInterfaceImpl && !m.IsGeneratedByCompiler
// Notice how lookup is used to quickly retrieve methods with same name as m.
// This makes the query 10 times faster than iterating each base methods to check their name.
let baseMethodsHidden = lookup[m.Name].Where(m1 => m1 != m && t.DeriveFrom(m1.ParentType))
where baseMethodsHidden.Count() > 0
select new {
m,
baseMethodsHidden,
Debt = 10.ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// Method hiding is when a base class has a non-virtual method *M()*,
// and a derived class has also a method *M()* with the same signature.
// In such situation, calling *base.M()* does something different
// than calling *derived.M()*.
//
// Notice that this is not *polymorphic* behavior. With *polymorphic*
// behavior, calling both *base.M()* and *derived.M()* on an instance
// object of *derived*, invoke the same implementation.
//
// This situation should be avoided because it obviously leads to confusion.
// This rule warns about all method hiding cases in the code base.
//</Description>
//<HowToFix>
// To fix a violation of this rule, remove or rename the method,
// or change the parameter signature so that the method does
// not hide the base method.
//
// However *method hiding is for those times when you need to have two
// things to have the same name but different behavior*. This is a very
// rare situations, described here:
// http://blogs.msdn.com/b/ericlippert/archive/2008/05/21/method-hiding-apologia.aspx
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>A stateless class or structure might be turned into a static type</Name>
warnif count > 0 from t in JustMyCode.Types where
!t.IsStatic &&
!t.IsGeneric &&
t.InstanceFields.Count() == 0 &&
// Don't match:
// --> types that implement some interfaces.
t.NbInterfacesImplemented == 0 &&
// --> or classes that have sub-classes children.
t.NbChildren == 0 &&
// --> or classes that have a base class
((t.IsClass && t.DepthOfDeriveFrom("System.Object".AllowNoMatch()) == 1) ||
t.IsStructure)
let methodsUsingMe = t.TypesUsingMe.ChildMethods().Where(m => m.IsUsing(t))
select new {
t,
methodsUsingMe,
Debt = (1 + methodsUsingMe.Count()).ToMinutes().ToDebt(),
Severity = Severity.Low
}
//<Description>
// This rule matches classes and structures that are not static, nor generic,
// that doesn't have any instance fields, that doesn't implement any interface
// nor has a base class (different than *System.Object*).
//
// Such class or structure is a *stateless* collection of *pure* functions,
// that doesn't act on any *this* object data. Such collection of *pure* functions
// is better hosted in a **static class**. Doing so simplifies the client code
// that doesn't have to create an object anymore to invoke the *pure* functions.
//</Description>
//<HowToFix>
// Declare all methods as *static* and transform the class or structure
// into a *static* class.
//
// By default issues of this rule have an **Low** severity
// because they reflect more an advice than a problem.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Non-static classes should be instantiated or turned to static</Name>
warnif count > 0
from t in JustMyCode.Types
where t.IsClass &&
//!t.IsPublic && // if you are developing a framework,
// you might not want to match public classes
!t.IsStatic &&
!t.IsAttributeClass && // Attributes class are never seen as instantiated
// Don't suggest to turn to static, classes that implement interfaces
t.InterfacesImplemented.Count() == 0 &&
!t.DeriveFrom("System.MarshalByRefObject".AllowNoMatch()) && // Types instantiated through remoting infrastructure
// XML serialized type might never be seen as instantiated.
!t.HasAttribute("System.Xml.Serialization.XmlRootAttribute".AllowNoMatch()) &&
!t.IsUsing("System.Xml.Serialization.XmlElementAttribute".AllowNoMatch()) &&
!t.IsUsing("System.Xml.Serialization.XmlAttributeAttribute".AllowNoMatch()) &&
// Serialized type might never be seen as instantiated.
!t.HasAttribute("System.Runtime.Serialization.DataContractAttribute".AllowNoMatch()) &&
!t.IsUsing("System.Runtime.Serialization.DataMemberAttribute".AllowNoMatch())
// find the first constructor of t called
let ctorCalled = t.Constructors.FirstOrDefault(ctor => ctor.NbMethodsCallingMe > 0)
// match t if none of its constructors is called.
where ctorCalled == null
select new {
t,
t.Visibility,
Debt = 2.ToMinutes().ToDebt(),
Severity = Severity.Medium
}
// Notice that classes only instantiated through reflection, like plug-in root classes
// are matched by this rules.
//<Description>
// If the constructors of a class are never called, the class is
// never instantiated, and should be defined as a *static class*.
//
// However this rule doesn't match instantiation through reflection.
// As a consequence, plug-in root classes, instantiated through reflection
// via *IoC frameworks*, can be *false positives* for this rule.
//
// Notice that by default this rule matches also *public* class.
// If you are developing a framework with classes that are intended
// to be instantiated by your clients, just uncomment the line
// *!t.IsPublic*.
//</Description>
//<HowToFix>
// First it is important to investigate why the class is never instantiated.
// If the reason is *the class hosts only static methods* then the class
// can be safely declared as *static*.
//
// Others reasons like, *the class is meant to be instantiated via reflection*,
// or *is meant to be instantiated only by client code* should lead to
// adapt this rule code to avoid these matches.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Methods should be declared static if possible</Name>
warnif count > 0
from t in JustMyCode.Types.Where(t =>
!t.IsStatic && !t.IsInterface &&
!t.IsEnumeration && !t.IsDelegate &&
!t.IsGeneratedByCompiler &&
!t.FullName.Contains(".Jni."))
let methodsThatCanBeMadeStatic =
from m in t.InstanceMethods
// An instance method can be turned to static if it is not virtual,
// not using the this reference and also, not using
// any of its class or base classes instance fields or instance methods.
where !m.IsAbstract && !m.IsVirtual &&
!m.AccessThis && !m.IsExplicitInterfaceImpl &&
m.Name != "get_IsLateAffinityAssignment()" &&
m.Name != "set_IsLateAffinityAssignment(Boolean)" &&
// Optimization: Using FirstOrDefault() avoid to check all members,
// as soon as one member is found
// we know the method m cannot be made static.
m.MembersUsed.FirstOrDefault(
mUsed => !mUsed.IsStatic &&
(mUsed.ParentType == t ||
t.DeriveFrom(mUsed.ParentType))
) == null
select m
from m in methodsThatCanBeMadeStatic
let staticFieldsUsed = m.ParentType.StaticFields.UsedBy(m).Where(f => !f.IsGeneratedByCompiler)
let methodsCallingMe = m.MethodsCallingMe
select new {
m,
staticFieldsUsed,
methodsCallingMe,
Debt = (1 + methodsCallingMe.Count())*30.ToSeconds().ToDebt(),
Severity = Severity.Medium
}
//<Description>
// When an instance method can be *safely* declared as static you should declare it as static.
//
// Whenever you write a method, you fulfill a contract in a given scope.
// The narrower the scope is, the smaller the chance is that you write a bug.
//
// When a method is static, you can't access non-static members; hence, your scope is
// narrower. So, if you don't need and will never need (even in subclasses) instance
// fields to fulfill your contract, why give access to these fields to your method?
// Declaring the method static in this case will let the compiler check that you
// don't use members that you do not intend to use.
//
// Declaring a method as static if possible is also good practice because clients can
// tell from the method signature that calling the method can't alter the object's state.
//
// Doing so, is also a micro performance optimization, since a static method is a
// bit cheaper to invoke than an instance method, because the *this* reference*
// doesn't need anymore to be passed.
//
// Notice that if a matched method is a handler, bound to an event through code
// generated by a designer, declaring it as static might break the designer
// generated code, if the generated code use the *this* invocation syntax,
// (like *this.Method()*).
//</Description>
//<HowToFix>
// Declare matched methods as static.
//
// Since such method doesn't use any instance fields and methods of its type and
// base-types, you should consider if it makes sense, to move such a method
// to a static utility class.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Constructor should not call a virtual method</Name>
warnif count > 0
from t in Application.Types where
t.IsClass &&
!t.IsGeneratedByCompiler &&
!t.IsSealed
from ctor in t.Constructors
let virtualMethodsCalled =
from mCalled in ctor.MethodsCalled
where mCalled.IsVirtual && !mCalled.IsFinal &&
// Only take care of just-my-code virtual methods called
JustMyCode.Contains(mCalled) &&
( mCalled.ParentType == t ||
(t.DeriveFrom(mCalled.ParentType) &&
// Don't accept Object methods since they can be called
// from another reference than the 'this' reference.
mCalled.ParentType.FullName != "System.Object")
)
select mCalled
where virtualMethodsCalled.Count() > 0
select new {
ctor ,
virtualMethodsCalled,
// If there is no derived type, it might be
// an opportunity to mark t as sealed.
t.DerivedTypes,
Debt = ((virtualMethodsCalled.Count())*6).ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// This rule matches constructors of a non-sealed class that call one or
// several virtual methods.
//
// When an object written in C# is constructed, what happens is that constructors
// run in order from the base class to the most derived class.
//
// Also objects do not change type as they are constructed, but start out as
// the most derived type, with the method table being for the most derived type.
// This means that virtual method calls always run on the most derived type,
// even when calls are made from the constructor.
//
// When you combine these two facts you are left with the problem that if you
// make a virtual method call in a constructor, and it is not the most derived
// type in its inheritance hierarchy, then it will be called on a class whose
// constructor has not been run, and therefore may not be in a suitable state
// to have that method called.
//
// Hence this situation makes the class *fragile to derive from*.
//</Description>
//<HowToFix>
// Violations reported can be solved by re-designing object initialisation
// or by declaring the parent class as *sealed*, if possible.
//</HowToFix>
]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[//<Name>Avoid the Singleton pattern</Name>
warnif count > 0
from t in Application.Types
where !t.IsStatic && !t.IsAbstract && (t.IsClass || t.IsStructure) && t.Name != "Jvm" && t.Name != "JvmDll"
// All ctors of a singleton are private
where t.Constructors.Where(ctor => !ctor.IsPrivate).Count() == 0
// A singleton contains one static field of its parent type, to reference the unique instance
let staticFieldInstances = t.StaticFields.WithFieldType(t)
where staticFieldInstances.Count() == 1
let staticFieldInstance = staticFieldInstances.Single()
let methodsUsingField = staticFieldInstance.MethodsUsingMe
let methodsUsingField2 = methodsUsingField.Concat(methodsUsingField.SelectMany(m => m.MethodsCallingMe))
select new {
t,
staticFieldInstance,
methodsUsingField2,
Debt = (3*methodsUsingField2.Count()).ToMinutes().ToDebt(),
AnnualInterest = (10+methodsUsingField2.Count()).ToMinutes().ToAnnualInterest()
}
//<Description>
// The *singleton pattern* consists in enforcing that a class has just
// a single instance: http://en.wikipedia.org/wiki/Singleton_pattern
// At first glance, this pattern looks appealing, it is simple to implement,
// it adresses a common situation, and as a consequence it is widely used.
//
// However, we discourage you from using singleton classes because experience
// shows that **singleton often results in less testable and less maintainable code**.
// Singleton is *by-design*, not testable. Each unit test should use their own objects
// while singleton forces multiple unit-tests to use the same instance object.
//
// Also the singleton static *GetInstance()* method allows *magic* access to that
// single object and its state from wherever developers want! This potentially
// attractive facility unfortunatly ends up into *unorganized*/*messy* code that
// will require effort to be refactored.
//
// More details available in these discussions:
// http://codebetter.com/patricksmacchia/2011/05/04/back-to-basics-usage-of-static-members/
// http://adamschepis.com/blog/2011/05/02/im-adam-and-im-a-recovering-singleton-addict/
//</Description>
//<HowToFix>
// This rule matches *the classic syntax of singletons*, where one
// static field hold the single instance of the parent class. We underline that
// *the problem is this particular syntax*, that plays against testability.
// The problem is not the fact that a single instance of the class lives
// at runtime.
//
// Hence to fix matches fo this rule, creates the single instance
// at the startup of the program, and pass it to all classes and methods
// that need to access it.
//
// If multiple singletons are identified, they actually form together a
// *program execution context*. Such context can be unified in a unique
// singleton context. Doing so will make it easier to propagate the
// context across the various program units.
//
// The estimated Debt, which means the effort to fix such issue,
// is equal to 3 minutes per method relying on the singleton.
// It is not rare that hundreds of methods rely on the singleton
// and that it takes hours to get rid of a singleton, refactoring
// the way just explained above.
//
// The severity of each singleton issue is **Critical** because as
// explained, using a the singleton pattern can really prevent the
// whole program to be testable.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Don't assign static fields from instance methods</Name>
warnif count > 0
from f in Application.Fields where
f.IsStatic &&
!f.IsLiteral &&
!f.IsInitOnly &&
!f.IsGeneratedByCompiler &&
// Contract API define such a insideContractEvaluation static field
f.Name != "insideContractEvaluation" &&
!f.HasAttribute("System.ThreadStaticAttribute")
let assignedBy = f.MethodsAssigningMe.Where(m => !m.IsStatic)
where assignedBy .Count() > 0
select new {
f,
assignedBy,
Debt = 5.ToMinutes().ToDebt(),
Severity = Severity.Medium
}
//<Description>
// Assigning static fields from instance methods leads to
// poorly maintainable and non-thread-safe code.
//
// More discussion on the topic can be found here:
// http://codebetter.com/patricksmacchia/2011/05/04/back-to-basics-usage-of-static-members/
//</Description>
//<HowToFix>
// If the *static* field is just assigned once in the program
// lifetime, make sure to declare it as *readonly* and assign
// it inline, or from the static constructor.
//
// In *Object-Oriented-Programming* the natural artifact
// to hold states that can be modified is **instance fields**.
//
// Hence to fix violations of this rule, make sure to
// hold assignable states through *instance* fields, not
// through *static* fields.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Avoid empty interfaces</Name>
warnif count > 0 from t in JustMyCode.Types where
t.IsInterface &&
t.NbMethods == 0
select new {
t,
t.TypesThatImplementMe,
Debt = (10 + 3*t.TypesThatImplementMe.Count()).ToMinutes().ToDebt(),
Severity = t.TypesThatImplementMe.Any() ? Severity.Medium : Severity.Low
}
//<Description>
// Interfaces define members that provide
// a behavior or usage contract.
// The functionality that is described by the interface
// can be adopted by any type, regardless of where the type
// appears in the inheritance hierarchy.
// A type implements an interface by providing implementations
// for the members of the interface.
// An empty interface does not define any members.
// Therefore, it does not define a contract that can be implemented.
//
// If your design includes empty interfaces that types
// are expected to implement, you are probably using an interface
// as a marker or a way to identify a group of types.
// If this identification will occur at run time,
// the correct way to accomplish this is to use a custom attribute.
// Use the presence or absence of the attribute,
// or the properties of the attribute, to identify the target types.
// If the identification must occur at compile time,
// then it is acceptable to use an empty interface.
//</Description>
//<HowToFix>
// Remove the interface or add members to it.
// If the empty interface is being used to label a set of types,
// replace the interface with a custom attribute.
//
// The estimated Debt, which means the effort to fix such issue,
// is equal to 10 minutes to discard an empty interface plus
// 3 minutes per type implementing an empty interface.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Avoid types initialization cycles</Name>
warnif count > 0
// Types initialization cycle can only happen between types of an assembly.
from assembly in Application.Assemblies
let cctorSuspects = assembly.ChildMethods.Where(
m => m.IsClassConstructor &&
// Optimization: types involved in a type cycle necessarily don't have type level.
m.ParentType.Level == null)
let excludedTypes = new []
{
"Apache.Ignite.Core.Impl.Binary.BinaryObjectBuilder",
"Apache.Ignite.Core.Impl.Binary.BinarySystemHandlers"
}.ToHashSet()
where cctorSuspects.Count() > 1
let typesSuspects = cctorSuspects.ParentTypes()
.Where(t => !excludedTypes.Contains(t.FullName))
.ToHashSet()
//
// dicoTmp associates to each type suspect T, a set of types from typesSuspects
// that contains at least a method or a field used directly or indirectly by the cctor of T.
//
let dicoTmp = cctorSuspects.ToDictionary(
cctor => cctor.ParentType,
cctor => ((IMember)cctor).ToEnumerable().FillIterative(
members => from m in members
from mUsed in (m is IMethod) ? (m as IMethod).MembersUsed : new IMember[0]
where mUsed.ParentAssembly == assembly
select mUsed)
.DefinitionDomain
.Select(m => m.ParentType) // Don't need .Distinct() here, because of ToHashSet() below.
.Except(cctor.ParentType)
.Intersect(typesSuspects)
.ToHashSet()
)
//
// dico associates to each type suspect T, the set of types initialized (directly or indirectly)
// by the initialization of T. This second step is needed, because if a cctor of a type T1
// calls a member of a type T2, not only the cctor of T1 triggers the initialization of T2,
// but also it triggers the initialization of all types that are initialized by T2 initialization.
//
let dico = typesSuspects.Where(t => dicoTmp[t].Count() > 0).ToDictionary(
typeSuspect => typeSuspect,
typeSuspect => typeSuspect.ToEnumerable().FillIterative(
types => from t in types
from tUsed in dicoTmp[t]
select tUsed)
.DefinitionDomain
.Except(typeSuspect)
.ToHashSet()
)
//
// Now that dico is prepared, detect the cctor cycles
//
from t in dico.Keys
// Thanks to the work done to build dico, it is now pretty easy
// to spot types involved in an initialization cyle with t!
let usersAndUseds = from tTmp in dico[t]
where dico.ContainsKey(tTmp) && dico[tTmp].Contains(t)
select tTmp
where usersAndUseds.Count() > 0
// Here we've found type(s) both using and used by the suspect type.
// A cycle involving the type t is found!
let typeInitCycle = usersAndUseds.Append(t)
// Compute methodsCalled and fieldsUsed, useful to explore
// how a cctor involved in a type initialization cycle, triggers other type initialization.
let methodsCalledDepth = assembly.ChildMethods.DepthOfIsUsedBy(t.ClassConstructor)
let fieldsUsedDepth = assembly.ChildFields.DepthOfIsUsedBy(t.ClassConstructor)
let methodsCalled = methodsCalledDepth.DefinitionDomain.OrderBy(m => methodsCalledDepth[m]).ToArray()
let fieldsUsed = fieldsUsedDepth.DefinitionDomain.OrderBy(f => fieldsUsedDepth[f]).ToArray()
// Use the tick box to: Group cctors methods By parent types
select new {
t.ClassConstructor,
cctorsCycle= typeInitCycle.Select(tTmp => tTmp.ClassConstructor),
// methodsCalled and fieldsUsed are members used directly and indirectly by the cctor.
// Export these members to the dependency graph (right click the cell Export/Append … to the Graph)
// and see how the cctor trigger the initialization of other types
methodsCalled,
fieldsUsed,
Debt = (20+10*typeInitCycle.Count()).ToMinutes().ToDebt(),
Severity = Severity.Critical
}
//<Description>
// The *class constructor* (also called *static constructor*, and named *cctor* in IL code)
// of a type, if any, is executed by the CLR at runtime, the first time the type is used.
// A *cctor* doesn't need to be explicitly declared in C# or VB.NET, to exist in compiled IL code.
// Having a static field inline initialization is enough to have
// the *cctor* implicitly declared in the parent class or structure.
//
// If the *cctor* of a type *t1* is using the type *t2* and if the *cctor* of *t2* is using *t1*,
// some type initialization unexpected and hard-to-diagnose buggy behavior can occur.
// Such a cyclic chain of initialization is not necessarily limited to two types
// and can embrace *N* types in the general case.
// More information on types initialization cycles can be found here:
// http://codeblog.jonskeet.uk/2012/04/07/type-initializer-circular-dependencies/
//
// The present code rule enumerates types initialization cycles.
// Some *false positives* can appear if some lambda expressions are defined
// in *cctors* or in methods called by *cctors*. In such situation, this rule
// considers these lambda expressions as executed at type initialization time,
// while it is not necessarily the case.
//</Description>
//<HowToFix>
// Types initialization cycles create confusion and unexpected behaviors.
// If several states hold by several classes must be initialized during the first
// access of any of those classes, a better design option is to create a dedicated
// class whose responsibility is to initialize and hold all these states.
//
// The estimated Debt, which means the effort to fix such issue,
// is equal to 20 minutes per cycle plus 10 minutes per type class constructor
// involved in the cycle.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Rules violated</Name>
from r in Rules
where r.IsViolated()
orderby r.Debt().Value descending
select new {
r,
Issues = r.Issues(),
Debt = r.Debt(),
AnnualInterest = r.AnnualInterest(),
BreakingPoint = r.BreakingPoint(),
Category = r.Category
}
//<Description>
// **Debt**: Estimated effort to fix all rule issues.
//
// **Annual Interest**: Estimated annual cost to leave all rule issues unfixed.
//
// **Breaking Point**: Estimated point in time from now, when leaving the rule issues unfixed cost as much as fixing the rule issues.
// A low value indicates an a rule with easy-to-fix issue with high annual interest.
// This value can be used to prioritize issues fix, to significantly reduce interest with minimum effort.
//
// More documentation: http://www.ndepend.com/docs/technical-debt
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Critical issues (grouped per rules)</Name>
let issues = Issues.Where(i => i.Severity == Severity.Critical)
let rules = issues.ToLookup(i => i.Rule)
from grouping in rules
let r = grouping.Key
let ruleIssues = grouping.ToArray()
orderby ruleIssues.Length descending
let debt = ruleIssues.Sum(i => i.Debt)
let annualInterest = ruleIssues.Sum(i => i.AnnualInterest)
let breakingPoint = debt.BreakingPoint(annualInterest)
select new { r,
ruleIssues,
debt,
annualInterest,
breakingPoint,
Category = r.Category
}
//<Description>
// Issues with a Critical severity level shouldn't move to production. It still can for business imperative needs purposes, but at worth it must be fixed during the next iterations.
//
// **Debt**: Estimated effort to fix the rule issues.
//
// **Annual Interest**: Estimated annual cost to leave the rule issues unfixed.
//
// **Breaking Point**: Estimated point in time from now, when leaving the
// rule issues unfixed cost as much as fixing the issues.
// A low value indicates easy-to-fix issues with high annual interest.
// This value can be used to prioritize issues fix, to significantly
// reduce interest with minimum effort.
//
// **Unfold** the cell in *ruleIssues* column to preview all its issues.
//
// **Double-click a rule** to edit the rule and list all its issues.
//
// More documentation: http://www.ndepend.com/docs/technical-debt
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Rules violated</Name>
from r in Rules
where r.IsViolated()
orderby r.Debt().Value descending
select new {
r,
Issues = r.Issues(),
Debt = r.Debt(),
AnnualInterest = r.AnnualInterest(),
BreakingPoint = r.BreakingPoint(),
Category = r.Category
}
//<Description>
// **Debt**: Estimated effort to fix all rule issues.
//
// **Annual Interest**: Estimated annual cost to leave all rule issues unfixed.
//
// **Breaking Point**: Estimated point in time from now, when leaving the rule issues unfixed cost as much as fixing the rule issues.
// A low value indicates an a rule with easy-to-fix issue with high annual interest.
// This value can be used to prioritize issues fix, to significantly reduce interest with minimum effort.
//
// More documentation: http://www.ndepend.com/docs/technical-debt
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Quality Gates Evolution</Name>
from qg in QualityGates
let qgBaseline = qg.OlderVersion()
let relyOnDiff = qgBaseline == null
let evolution = relyOnDiff ? (TrendIcon?)null :
// When a quality gate relies on diff between now and baseline
// it is not executed against the baseline
qg.ValueDiff() == 0d ?
TrendIcon.Constant :
(qg.ValueDiff() > 0 ?
( qg.MoreIsBad ? TrendIcon.RedUp: TrendIcon.GreenUp) :
(!qg.MoreIsBad ? TrendIcon.RedDown: TrendIcon.GreenDown))
select new { qg,
Evolution = evolution,
BaselineStatus = relyOnDiff? (QualityGateStatus?) null : qgBaseline.Status,
Status = qg.Status,
BaselineValue = relyOnDiff? (null) : qgBaseline.ValueString,
Value = qg.ValueString,
}
// <Description>
// Show quality gates evolution between baseline and now.
//
// When a quality gate relies on diff between now and baseline (like *New Debt since Baseline*)
// it is not executed against the baseline and as a consequence its evolution is not available.
//
// Double-click a quality gate for editing.
// </Description>]]></Query>
</Group>
<Group Name="Design" Active="True" ShownInReport="False">
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Avoid custom delegates</Name>
warnif count > 0
from t in JustMyCode.Types where t.IsDelegate
let invokeMethod = (from m in t.Methods where m.SimpleName == "Invoke" select m).Single()
let signature1 = invokeMethod.Name.Substring(
invokeMethod.SimpleName.Length,
invokeMethod.Name.Length - invokeMethod.SimpleName.Length)
// 'ref' and 'out' parameters cannot be supported
where !signature1.Contains("&")
let signature2 = signature1.Replace("(","<").Replace(")",">")
let signature3 = signature2 == "<>" ? "" : signature2
let resultTypeName = invokeMethod.ReturnType == null ? "????" :
invokeMethod.ReturnType.FullName == "System.Void" ? "" :
invokeMethod.ReturnType.Name
let replaceWith =
resultTypeName == "Boolean" && invokeMethod.NbParameters == 1 ?
"Predicate" + signature3 : resultTypeName == "" ?
"Action" + signature3 : invokeMethod.NbParameters ==0 ?
"Func<" + resultTypeName + ">" :
"Func" + signature3.Replace(">", "," + resultTypeName + ">")
let methodsUser = t.TypesUsingMe.ChildMethods().Where(m => m.IsUsing(t))
select new {
t,
replaceWith,
methodsUser,
Debt = (5 + 3*methodsUser.Count()).ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// Generic delegates sould be preferred over custom delegates.
// Generic delegates are:
//
// • *Action<…>* to represent any method with *void* return type.
//
// • *Func<…>* to represent any method with a return type. The last
// generic argument is the return type of the prototyped methods.
//
// • *Predicate<T>* to represent any method that takes an instance
// of *T* and that returns a *boolean*.
//
// • Expression<…> that represents function definitions that can be
// compiled and subsequently invoked at runtime but can also be
// serialized and passed to remote processes.
//
// Thanks to generic delegates, not only the code using these custom
// delegates will become clearer, but you'll be relieved from the
// maintenance of these delegate types.
//
// Notice that delegates that are consumed by *DllImport* extern methods
// must not be converted, else this could provoke marshalling issues.
//</Description>
//<HowToFix>
// Remove custom delegates and replace them with generic
// delegates shown in the **replaceWith** column.
//
// The estimated Debt, which means the effort to fix such issue,
// is 5 minutes per custom delegates plus 3 minutes per method
// using such custom delegate.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Types with disposable instance fields must be disposable</Name>
warnif count > 0
// Several IDisposable types can be found if several .NET Fx are referenced.
let iDisposables = ThirdParty.Types.WithFullName("System.IDisposable")
where iDisposables.Any() // in case the code base doesn't use at all System.IDisposable
from t in Application.Types.Except(Application.Types.ThatImplementAny(iDisposables))
where !t.IsGeneratedByCompiler
let instanceFieldsDisposable =
t.InstanceFields.Where(f => f.FieldType != null &&
f.FieldType.InterfacesImplemented.Intersect(iDisposables).Any())
where instanceFieldsDisposable.Any()
select new {
t,
instanceFieldsDisposable,
Debt = (5 + 2*instanceFieldsDisposable.Count()).ToMinutes().ToDebt(),
Severity = Severity.Medium
}
//<Description>
// This rule warns when a class declares and implements an instance field that
// is a *System.IDisposable* type and the class does not implement *IDisposable*.
//
// A class implements the *IDisposable* interface to dispose of unmanaged resources
// that it owns. An instance field that is an *IDisposable* type indicates that
// the field owns an unmanaged resource. A class that declares an *IDisposable*
// field indirectly owns an unmanaged resource and should implement the
// *IDisposable* interface. If the class does not directly own any unmanaged
// resources, it should not implement a finalizer.
//
// This rules might report false positive in case the lifetime of the disposable
// objects referenced, is longer than the lifetime of the object that hold the
// disposable references.
//</Description>
//<HowToFix>
// To fix a violation of this rule, implement *IDisposable* and from the
// *IDisposable.Dispose()* method call the *Dispose()* method of the field(s).
//
// The estimated Debt, which means the effort to fix such issue,
// is 5 minutes per type matched plus 3 minutes per disposable instance field.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="True"><![CDATA[// <Name>Disposable types with unmanaged resources should declare finalizer</Name>
// This default rule is disabled by default,
// see in the rule description (below) why.
// warnif count > 0
// Several IDisposable type can be found if several .NET Fx are referenced.
let iDisposables = ThirdParty.Types.WithFullName("System.IDisposable")
where iDisposables.Any() // in case the code base doesn't use at all System.IDisposable
let disposableTypes = Application.Types.ThatImplementAny(iDisposables)
let unmanagedResourcesFields = disposableTypes.ChildFields().Where(f =>
!f.IsStatic &&
f.FieldType != null &&
f.FieldType.FullName.EqualsAny(
"System.IntPtr",
"System.UIntPtr",
"System.Runtime.InteropServices.HandleRef")).ToHashSet()
let disposableTypesWithUnmanagedResource = unmanagedResourcesFields.ParentTypes()
from t in disposableTypesWithUnmanagedResource
where !t.HasFinalizer
let unmanagedResourcesTypeFields = unmanagedResourcesFields.Intersect(t.InstanceFields)
select new {
t,
unmanagedResourcesTypeFields,
//Debt = 10.ToMinutes().ToDebt(),
//Severity = Severity.Critical
}
//<Description>
//A type that implements *System.IDisposable*,
//and has fields that suggest the use of unmanaged resources,
//does not implement a finalizer as described by *Object.Finalize()*.
//A violation of this rule is reported
//if the disposable type contains fields of the following types:
//
// • *System.IntPtr*
//
// • *System.UIntPtr*
//
// • *System.Runtime.InteropServices.HandleRef*
//
// Notice that this default rule is disabled by default,
// because it typically reports *false positive* for classes
// that just hold some references to managed resources,
// without the responsibility to dispose them.
//
// To enable this rule just uncomment *warnif count > 0*.
//</Description>
//<HowToFix>
//To fix a violation of this rule,
//implement a finalizer that calls your *Dispose()* method.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="True"><![CDATA[// <Name>Methods that create disposable object(s) and that don't call Dispose()</Name>
// Uncomment this to transform this code query into a code rule.
// warnif count > 0
// Several IDisposable types can be found if several .NET Fx are referenced.
let iDisposables = ThirdParty.Types.WithFullName("System.IDisposable")
where iDisposables.Any() // in case the code base doesn't use at all System.IDisposable
// Build sequences of disposableTypes and disposeMethods
let disposableTypes = Types.ThatImplementAny(iDisposables).Concat(iDisposables)
let disposeMethods = disposableTypes.ChildMethods().WithName("Dispose()").ToHashSet()
// -> You can refine this code query by assigning to disposableTypesToLookAfter something like:
// disposableTypes.WithFullNameIn("Namespace.TypeName1", "Namespace.TypeName2", ...)
let disposableTypesToLookAfter = disposableTypes
// -> You can refine this code query by assigning to methodsToLookAfter something like:
// Application.Assemblies.WithNameLike("Asm").ChildMethods()
let methodsToLookAfter = Application.Methods
// Enumerate methods that create any disposable type, without calling Dispose()
from m in methodsToLookAfter.ThatCreateAny(disposableTypesToLookAfter )
where !m.MethodsCalled.Intersect(disposeMethods).Any()
select new {
m,
disposableObjectsCreated = disposableTypes.Where(t => m.CreateA(t)),
m.MethodsCalled,
//Debt = 10.ToMinutes().ToDebt(),
//Severity = Severity.Low
}
//<Description>
// This code query enumerates methods that create one or several disposable object(s),
// without calling any Dispose() method.
//
// This code query is not a code rule because it is acceptable to do so,
// as long as disposable objects are disposed somewhere else.
//
// This code query is designed to be be easily refactored
// to look after only specific disposable types, or specific caller methods.
//
// You can then refactor this code query to adapt it to your needs and transform it into a code rule.
//</Description>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Classes that are candidate to be turned into structures</Name>
warnif count > 0 from t in JustMyCode.Types where
t.IsClass &&
!t.IsGeneratedByCompiler &&
!t.IsStatic &&
t.SizeOfInst > 0 &&
t.SizeOfInst <= 16 && // Structure instance must not be too big,
// else it degrades performance.
t.NbChildren == 0 && // Must not have children
// Must not implement interfaces to avoid boxing mismatch
// when structures implements interfaces.
t.InterfacesImplemented.Count() == 0 &&
// Must derive directly from System.Object
t.DepthOfDeriveFrom("System.Object".AllowNoMatch()) == 1 &&
// Must not be a serializable class because a structure should be immutable
// and serialized types are mutable.
!t.HasAttribute("System.Xml.Serialization.XmlRootAttribute".AllowNoMatch()) &&
!t.IsUsing("System.Xml.Serialization.XmlElementAttribute".AllowNoMatch()) &&
!t.IsUsing("System.Xml.Serialization.XmlAttributeAttribute".AllowNoMatch()) &&
!t.HasAttribute("System.Runtime.Serialization.DataContractAttribute".AllowNoMatch()) &&
!t.IsUsing("System.Runtime.Serialization.DataMemberAttribute".AllowNoMatch())
// && t.IsSealed <-- You might want to add this condition
// to restraint the set.
// && t.IsImmutable <-- Structures should be immutable type.
// && t.!IsPublic <-- You might want to add this condition if
// you are developping a framework with classes
// that are intended to be sub-classed by
// your clients.
let methodsUser = t.TypesUsingMe.ChildMethods().Where(m => m.IsUsing(t))
select new {
t,
t.SizeOfInst,
t.InstanceFields,
methodsUser,
Debt = (5 + 1*methodsUser.Count()).ToMinutes().ToDebt(),
Severity = Severity.Low
}
//<Description>
// *Int32*, *Double*, *Char* or *Boolean* are structures and not classes.
// Structures are particularly suited to implement **lightweight values**.
// Hence a class is candidate to be turned into a structure
// when its instances are *lightweight values*.
//
// This is a matter of *performance*. It is expected that a program
// works with plenty of *short lived lightweight values*.
// In such situation, the advantage of using *struct* instead of
// *class*, (in other words, the advantage of using *values* instead
// of *objects*), is that *values* are not managed by the garbage collector.
// This means that values are cheaper to deal with.
//
// This rule matches classes that looks like being *lightweight values*.
// The characterization of such class is:
//
// • Its instances have a small *memory footprint* (at most 16 bytes)
//
// • Its instances have a memory footprint greater than zero
// (i.e the class has at least one instance field).
//
// • It implements no interfaces.
//
// • It has no derived classes.
//
// • It derives directly from *System.Object*.
//
// This rule doesn't take account if instances of matched
// classes are numerous *short-lived* objects.
// These criterions are just indications. Only you can decide if it is
// *performance wise* to transform a class into a structure.
//
// A related case-study of using *class* or *struct* for *Tuple<…>* generic
// types can be found here:
// http://stackoverflow.com/questions/2410710/why-is-the-new-tuple-type-in-net-4-0-a-reference-type-class-and-not-a-value-t
//</Description>
//<HowToFix>
// Just use the keyword *struct* instead of the keyword *class*.
//
// **CAUTION:** Before applying this rule, make sure to understand
// the **deep implications** of transforming a class into a structure.
// http://msdn.microsoft.com/en-us/library/aa664471(v=vs.71).aspx
//
// The estimated Debt, which means the effort to fix such issue,
// is 5 minutes per class matched plus one minute per method
// using such class transformed into a structure.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Avoid namespaces with few types</Name>
warnif count > 0 from n in JustMyCode.Namespaces
where n.Name.Length > 0 // Don't match anonymous namespaces
let types = n.ChildTypes.Where(t => !t.IsGeneratedByCompiler)
where
types.Count() < 5
orderby types.Count() ascending
select new {
n,
types,
Debt = 5.ToMinutes().ToDebt(),
Severity = Severity.Low
}
//<Description>
// This rule warns about namespaces other than the global namespace
// that contain less than five types.
//
// Make sure that each of your namespaces has a logical organization
// and that a valid reason exists to put types in a sparsely
// populated namespace.
//
// Namespaces should contain types that are used together in most
// scenarios. When their applications are mutually exclusive,
// types should be located in separate namespaces. For example,
// the *System.Web.UI* namespace contains types that are used
// in Web applications, and the *System.Windows.Forms* namespace
// contains types that are used in Windows-based applications.
// Even though both namespaces have types that control aspects
// of the user interface, these types are not designed for
// use in the same application. Therefore, they are located in
// separate namespaces.
//
// Careful namespace organization can also be helpful because
// it increases the discoverability of a feature. By examining the
// namespace hierarchy, library consumers should be able to locate
// the types that implement a feature.
//</Description>
//<HowToFix>
// To fix a violation of this rule, try to combine namespaces
// that contain just a few types into a single namespace.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Nested types should not be visible</Name>
warnif count > 0 from t in JustMyCode.Types where
t.IsNested &&
!t.IsGeneratedByCompiler &&
!t.IsPrivate
let typesUser = t.TypesUsingMe.Where(t1 => t1 != t.ParentType && t1.ParentType != t.ParentType)
select new {
t,
t.Visibility,
typesUser,
Debt = (2 + 4*typesUser.Count()).ToMinutes().ToDebt(),
Severity = Severity.Medium
}
//<Description>
// This rule warns about nested types not declared as private.
//
// A nested type is a type declared within the scope of another
// type. Nested types are useful for encapsulating private
// implementation details of the containing type. Used
// for this purpose, nested types should not be externally visible.
//
// Do not use externally visible nested types for logical
// grouping or to avoid name collisions; instead use namespaces.
//
// Nested types include the notion of member accessibility,
// which some programmers do not understand clearly.
//
// Protected types can be used in subclasses and nested types
// in advanced customization scenarios.
//</Description>
//<HowToFix>
// If you do not intend the nested type to be externally visible,
// change the type's accessibility.
//
// Otherwise, remove the nested type from its parent and make it
// *non-nested*.
//
// If the purpose of the nesting is to group some nested types,
// use a namespace to create the hierarchy instead.
//
// The estimated Debt, which means the effort to fix such issue,
// is 2 minutes per nested type plus 4 minutes per outter type
// using such nesting type.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Declare types in namespaces</Name>
warnif count > 0 from n in Application.Namespaces where
// If an anonymous namespace can be found,
// it means that it contains types outside of namespaces.
n.Name == ""
// Eliminate anonymous namespaces that contains
// only generated types.
let childTypes = n.ChildTypes.Where(t => !t.IsGeneratedByCompiler)
where childTypes.Count() > 0
select new {
n,
childTypes,
n.NbLinesOfCode,
Debt = 2*childTypes.Count().ToMinutes().ToDebt(),
Severity = Severity.Medium
}
//<Description>
// Types are declared within namespaces to prevent name collisions,
// and as a way of organizing related types in an object hierarchy.
//
// Types outside any named namespace are in a *global
// namespace* that cannot be referenced in code.
//
// The *global namespace* has no name, hence it is qualified as
// being the *anonymous namespace*.
//
// This rule warns about *anonymous namespaces*.
//</Description>
//<HowToFix>
// To fix a violation of this rule,
// declare all types of all anonymous
// namespaces in some named namespaces.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Empty static constructor can be discarded</Name>
warnif count > 0 from m in Application.Methods where
m.IsClassConstructor &&
m.NbLinesOfCode == 0
select new {
m,
Debt = 3.ToMinutes().ToDebt(),
Severity = Severity.Medium
}
//<Description>
// The *class constructor* (also called *static constructor*, and named *cctor* in IL code)
// of a type, if any, is executed by the CLR at runtime, just before the first time the type is used.
//
// This rule warns about the declarations of *static constructors*
// that don't contain any lines of code. Such *cctors* are useless
// and can be safely removed.
//</Description>
//<HowToFix>
// Remove matched empty *static constructors*.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Instances size shouldn't be too big</Name>
warnif count > 0 from t in JustMyCode.Types where
t.SizeOfInst > 64
orderby t.SizeOfInst descending
select new {
t,
t.SizeOfInst,
t.InstanceFields,
Debt = t.SizeOfInst.Linear(64,2, 2048, 60).ToMinutes().ToDebt(),
// The annual interest varies linearly from interests for severity Medium for 64 bytes per instance
// to twice interests for severity High for 2048 bytes per instance
AnnualInterest = t.SizeOfInst.Linear(64, Severity.Medium.AnnualInterestThreshold().Value.TotalMinutes,
2048, 2*(Severity.High.AnnualInterestThreshold().Value.TotalMinutes)).ToMinutes().ToAnnualInterest()
}
//<Description>
// Types where *SizeOfInst > 64* might degrade performance
// if many instances are created at runtime.
// They can also be hard to maintain.
//
// Notice that a class with a large *SizeOfInst* value
// doesn't necessarily have a lot of instance fields.
// It might derive from a class with a large *SizeOfInst* value.
//
// See the definition of the *SizeOfInst* metric here
// http://www.ndepend.com/docs/code-metrics#SizeOfInst
//</Description>
//<HowToFix>
// A type with a large *SizeOInst* value hold *directly*
// a lot of data. Typically, you can group this data into
// smaller types that can then be composed.
//
// The estimated Debt, which means the effort to fix such issue,
// varies linearly from interests for severity Medium for 64 bytes per instance
// to twice interests for severity High for 2048 bytes per instance.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Boxing/unboxing should be avoided</Name>
warnif percentage > 5 from m in JustMyCode.Methods where
m.IsUsingBoxing ||
m.IsUsingUnboxing
select new {
m,
m.NbLinesOfCode,
m.IsUsingBoxing,
m.IsUsingUnboxing,
Debt = 5.ToMinutes().ToDebt(),
Severity = Severity.Low
}
//<Description>
// *Boxing* is the process of converting a value type to the type
// *object* or to any interface type implemented by this value
// type. When the CLR boxes a value type, it wraps the value
// inside a *System.Object* and stores it on the managed heap.
//
// *Unboxing* extracts the value type from the object. Boxing
// is implicit; unboxing is explicit.
//
// The concept of boxing and unboxing underlies the C# unified
// view of the type system in which a value of any type can
// be treated as an object. More about *boxing* and *unboxing*
// here: https://msdn.microsoft.com/en-us/library/yz2be5wk.aspx
//
// This rule warns when more than 5% of methods in a code base
// are using *boxing* or *unboxing*. Because *boxing* and
// *unboxing* come with a performance penalty and can be often
// avoided.
//</Description>
//<HowToFix>
// Thanks to .NET generic, and especially thanks to
// generic collections, *boxing* and *unboxing* should
// be rarely used. Hence in most situations the code can
// be refactored to avoid relying on *boxing* and *unboxing*.
// See for example:
// http://stackoverflow.com/questions/4403055/boxing-unboxing-and-generics
//
// With a performance profiler, indentify methods that consume
// a lot of CPU time. If such method uses *boxing* or
// *unboxing*, especially in a **loop**, make sure to refactor it.
//
// By default issues of this rule have an **Low** severity
// because they reflect more an advice than a problem.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Attribute classes should be sealed</Name>
warnif count > 0 from t in Application.Types where
t.IsAttributeClass &&
!t.IsSealed &&
!t.IsAbstract &&
t.IsPublic
select new {
t,
t.NbLinesOfCode,
Debt = 5.ToMinutes().ToDebt(),
Severity = Severity.Medium
}
//<Description>
// The .NET Framework class library provides methods
// for retrieving custom attributes. By default,
// these methods search the attribute inheritance
// hierarchy; for example
// *System.Attribute.GetCustomAttribute()*
// searches for the specified attribute type, or any
// attribute type that extends the specified attribute
// type.
//
// Sealing the attribute eliminates the search
// through the inheritance hierarchy, and can improve
// performance.
//</Description>
//<HowToFix>
// To fix a violation of this rule, seal the attribute
// type or make it abstract.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Don't use obsolete types, methods or fields</Name>
warnif count > 0
let obsoleteTypes = Types.Where(t => t.IsObsolete)
let obsoleteMethods = Methods.Where(m => m.IsObsolete).ToHashSet()
let obsoleteFields = Fields.Where(f => f.IsObsolete)
from m in JustMyCode.Methods.UsingAny(obsoleteTypes).Union(
JustMyCode.Methods.UsingAny(obsoleteMethods)).Union(
JustMyCode.Methods.UsingAny(obsoleteFields))
let obsoleteTypesUsed = obsoleteTypes.UsedBy(m)
// Optimization: MethodsCalled + Intersect() is faster than using obsoleteMethods.UsedBy()
let obsoleteMethodsUsed = m.MethodsCalled.Intersect(obsoleteMethods)
let obsoleteFieldsUsed = obsoleteFields.UsedBy(m)
let obsoleteUsage = obsoleteTypesUsed.Cast<IMember>().Concat(obsoleteMethodsUsed).Concat(obsoleteFieldsUsed)
select new {
m,
obsoleteUsage,
Debt = (5*obsoleteUsage.Count()).ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// The attribute *System.ObsoleteAttribute* is used to tag
// types, methods or fields of an API that clients shouldn't
// use because these code elements will be removed sooner
// or later.
//
// This rule warns about methods that use a type, a method
// or a field, tagged with *System.ObsoleteAttribute*.
//</Description>
//<HowToFix>
// Typically when a code element is tagged with
// *System.ObsoleteAttribute*, a *workaround message*
// is provided to clients.
//
// This *workaround message* will tell you what to do
// to avoid using the obsolete code element.
//
// The estimated Debt, which means the effort to fix such issue,
// is 5 minutes per type, method or field used.
//
// Issues of this rule have a severity **High**
// because it is important to not rely anymore on obsolete code.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Do implement methods that throw NotImplementedException</Name>
warnif count > 0
from m in Application.Methods
where m.CreateA("System.NotImplementedException".AllowNoMatch())
select new {
m,
m.NbLinesOfCode,
Debt = (m.NbLinesOfCode == 1 ? 10 : 3).ToMinutes().ToDebt(),
Severity = m.NbLinesOfCode == 1 ? Severity.High : Severity.Medium
}
//<Description>
// The exception *NotImplementedException* is used to declare
// a method *stub* that can be invoked, and defer the
// development of the method implementation.
//
// This exception is especially useful when doing **TDD**
// (*Test Driven Development*) when tests are written first.
// This way tests fail until the implementation is written.
//
// Hence using *NotImplementedException* is a *temporary*
// facility, and before releasing, will come a time when
// this exception shouldn't be used anywhere in code.
//
// *NotImplementedException* should not be used permanently
// to mean something like *this method should be overriden*
// or *this implementation doesn't support this facility*.
// Artefact like *abstract method* or *abstract class* should
// be used instead, to favor a *compile time* error over a
// *run-time* error.
//
// This rule warns about method still using
// *NotImplementedException*.
//</Description>
//<HowToFix>
// Investigate why *NotImplementedException* is still
// thrown.
//
// Such issue has a **High** severity if the method code
// consists only in throwing *NotImplementedException*.
// Such situation means either that the method should be
// implemented, either that what should be a *compile time*
// error is a *run-time* error *by-design*,
// and this is not good design. Sometime this situation
// also pinpoints a method stub that can be safely removed.
//
// If *NotImplementedException* is thrown from a method
// with significant logic, the severity is considered as
// **Medium**, because often the fix consists in throwing
// another exception type, like **InvalidOperationException**.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Override equals and operator equals on value types</Name>
warnif count > 0
from t in JustMyCode.Types where
t.IsStructure &&
t.InstanceFields.Count() > 0
let equalsMethod = t.InstanceMethods.Where(m0 => m0.Name == "Equals(Object)").SingleOrDefault()
where equalsMethod == null
select new {
t,
t.InstanceFields,
Debt = (15 + 2*t.InstanceFields.Count()).ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// For value types, the inherited implementation of *Equals()* uses
// the Reflection library, and compares the contents of all instances
// fields. Reflection is computationally expensive, and comparing
// every field for equality might be unnecessary.
//
// If you expect users to compare or sort instances, or use them
// as hash table keys, your value type should implement *Equals()*.
// In C# and VB.NET, you should also provide an implementation of
// *GetHashCode()* and of the equality and inequality operators.
//</Description>
//<HowToFix>
// To fix a violation of this rule, provide an implementation
// of *Equals()* and *GetHashCode()* and implement the equality
// and inequality operators.
//
// The estimated Debt, which means the effort to fix such issue,
// is equal to 15 minutes plus 2 minutes per instance field.
//</HowToFix>]]></Query>
</Group>
<Group Name="Architecture" Active="True" ShownInReport="False">
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="True"><![CDATA[// <Name>Avoid namespaces mutually dependent</Name>
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>()
// Optimization: restraint namespaces set
// 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.
let namespacesSuspect = assembly.ChildNamespaces.Where(n => n.Level == null)
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
// 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.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.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) / 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) :
tmCulprit.AsMethod.MembersUsed.Where(m => m.ParentNamespace == highLevelNamespace))
.ToArray() // Enumerate once
select new {
tmCulprit,
shouldntUse = used,
becauseNamespace = lowLevelNamespace,
shouldntUseNamespace = highLevelNamespace,
Debt = used.Length.Linear(1, 15, 10, 60).ToMinutes().ToDebt(),
AnnualInterest = annualInterestPerIssue
}
//<Description>
// This rule lists types and methods from a low-level namespace
// that use types and methods from higher-level namespace.
//
// The pair of low and high level namespaces is made of two
// namespaces that use each other.
//
// For each pair of namespaces, to infer which one is low-level
// and which one is high-level, the rule computes the two coupling
// [from A to B] and [from B to A] in terms of number of types,
// methods and fields involved in the coupling. Typically
// the coupling from low-level to high-level namespace is significantly
// lower than the other legitimate coupling.
//
// Following this rule is useful to avoid **namespaces dependency
// cycles**. This will get the code architecture close to a
// *layered architecture*, where *low-level* code is not allowed
// to use *high-level* code.
//
// In other words, abiding by this rule will help significantly
// getting rid of what is often called **spagetthi code:
// Entangled code that is not properly layered and structured**.
//
// More on this in our white books relative to partitioning code.
// http://www.ndepend.com/docs/white-books
//</Description>
//<HowToFix>
// Refactor the code to make sure that **the low-level namespace
// doesn't use the high-level namespace**.
//
// The rule lists in detail which low-level types and methods
// shouldn't use which high-level types and methods. The refactoring
// patterns that help getting rid of each listed dependency include:
//
// • Moving one or several types from the *low-level* namespaces
// to the *high-level* one, or do the opposite.
//
// • Use *Inversion of Control (IoC)*:
// http://en.wikipedia.org/wiki/Inversion_of_control
// This consists in creating new interfaces in the
// *low-level* namespace, implemented by classes
// in the *high-level* namespace. This way *low-level*
// code can consume *high-level* code through interfaces,
// without using directly *high-level* implementations.
// Interfaces can be passed to *low-level* code through
// the *high-level* namespace code, or through even
// higher-level code. In related documentations
// you can see these interfaces named as *callbacks*,
// and the overall pattern is also known as
// *Dependency Injection (DI)*:
// http://en.wikipedia.org/wiki/Dependency_injection
//
// That rule might not be applicable for frameworks
// that present public namespaces mutually dependent.
// In such situation the cost to break the API can be
// higher than the cost to let the code entangled.
//
// -
//
// The estimated **Debt**, which means the effort to fix such issue
// to make sure that the first namespace doesn't rely anymore
// on the second one, depends on the number of types and methods used.
//
// Because both namespace are now forming a *super-component*
// that cannot be partitioned in smaller components, the cost to
// unfix each issue is proportional to the size of this super-component.
// As a consequence, the estimated **Annual Interest**, which means
// the annual cost to let both namespaces mutually dependend, is equal
// to an hour plus a number of minutes proportional to the size
// (in lines of code) of both namespaces. The obtained *Annual Interest*
// value is then divided by the number of detailled issues listed.
//
// Often the estimated *Annual Interest* for each listed issue
// is higher than the *Debt*, which means that leaving such issue
// unfixed for a year costs more than taking the time to fix issue once.
//
// --
//
// To explore the coupling between the two namespaces mutually
// dependent:
//
// 1) from the *becauseNamespace right-click menu* choose
// *Copy to Matrix Columns* to export this low-level namespace
// to the horizontal header of the dependency matrix.
//
// 2) from the *shouldntUseNamespace right-click menu* choose
// *Copy to Matrix Rows* to export this high-level namespace to
// the vertical header of the dependency matrix.
//
// 3) double-click the black matrix cell (it is black because of
// the mutual dependency).
//
// 4) in the matrix command bar, click the button:
// *Remove empty Row(s) and Column(s)*.
//
// At this point, the dependency matrix shows types involved
// into the coupling.
//
// • Blue cells represent types from low-level namespace using types
// from high-level namespace
//
// • Green cells represent types from high-level namespace using
// types from low-level namespace
//
// • Black cells represent types from low-level and high-level
// namespaces that use each other.
//
// There are more green cells than blue and black cells because
// green cell represents correct coupling from high-level to low-level.
// **The goal is to eliminate incorrect dependencies represented by
// blue and black cells.**
//</HowToFix>
]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Avoid namespaces dependency cycles</Name>
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
// A namespace involved in a cycle necessarily have a null Level.
let namespacesSuspect = assembly.ChildNamespaces.Where(n => n.Level == null)
// 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!
let cycle = usersAndUsed.Append(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
}
//<Description>
// This rule lists all *application namespace dependency cycles*.
// Each row shows a different cycle, indexed with one of the namespace entangled
// in the cycle.
//
// To browse a cycle on the dependency graph or the dependency matrix, right click
// a cycle cell and export the matched namespaces to the dependency graph or matrix.
//
// In the matrix, dependency cycles are represented with red squares and black cells.
// To easily browse dependency cycles, the dependency matrix comes with an option:
// *Display Direct and Indirect Dependencies*
//
// Read our white books relative to partitioning code,
// to know more about namespaces dependency cycles, and why avoiding them
// is a *simple yet efficient* solution to clean the architecture of a code base.
// http://www.ndepend.com/docs/white-books
//</Description>
//<HowToFix>
// Removing first pairs of *mutually dependent namespaces* will eliminate
// most *namespaces dependency cycles*. This is why it is recommended
// focusing on matches of the default rule
// **Avoid namespaces mutually dependent** before dealing
// with the present rule.
//
// Once solving all *mutually dependent namespaces*, remaining cycles
// matched by the present rule necessarily involve 3 or more namespaces
// like in: *A is using B is using C is using A*.
// Such cycle can be broken by identifying which namespace should
// be at the *lower-level*. For example if B should be at the
// *lower-level*, then it means C should be at the *higher-level*
// and to break the cycle, you just have to remove the dependency
// from B to C, with a pattern described in the *HowToFix* section
// of the rule *Avoid namespaces mutually dependent*.
//
// The estimated Debt, which means the effort to fix such issue,
// doesn't depend on the cycle length. First because fixing the rule
// **Avoid namespaces mutually dependent** will fix most cycle reported
// here, second because even a long cycle can be broken by removing
// a few dependency.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Avoid partitioning the code base through many small library Assemblies</Name>
warnif count > 10
from a in Application.Assemblies where
( a.NbLinesOfCode < 1000 ||
a.NbILInstructions < 7000 ) &&
a.FilePath.FileExtension.ToLower() == ".dll"
select new {
a,
a.NbLinesOfCode,
a.NbILInstructions,
Debt = 40.ToMinutes().ToDebt(),
Severity = Severity.Medium
}
//<Description>
// Each .NET Assembly compiled represents one or several physical file(s).
// Having too many library .NET Assemblies is a symptom of
// considering **physical** .NET Assemblies as **logical** components.
//
// We advise having less, and bigger, .NET Assemblies
// and using the concept of namespaces to define logical components.
// Benefits are:
//
// • Faster compilation time.
//
// • Faster startup time for your program.
//
// • Easier deployment thanks to less files to manage.
//
// • If you are developing a Framework,
// less .NET assemblies to reference and manage for your clients.
//</Description>
//<HowToFix>
// Consider using the *physical* concept of assemblies for physical needs
// only.
//
// Our white book about **Partitioning code base through .NET assemblies
// and Visual Studio projects** explains in details valid and invalid
// reasons to use assemblies.
// Download it here:
// http://www.ndepend.com/Res/NDependWhiteBook_Assembly.pdf
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>UI layer shouldn't use directly DB types</Name>
warnif count > 0
// UI layer is made of types using a UI framework
let uiTypes = Application.Types.UsingAny(Assemblies.WithNameIn("PresentationFramework", "System.Windows", "System.Windows.Forms", "System.Web"))
// You can easily customize this part to define what are DB types.
let dbTypes = ThirdParty.Assemblies.WithNameIn("System.Data", "EntityFramework", "NHibernate").ChildTypes()
// Ideally even DataSet and associated, usage should be forbidden from UI layer:
// http://stackoverflow.com/questions/1708690/is-list-better-than-dataset-for-ui-layer-in-asp-net
.Except(ThirdParty.Types.WithNameIn("DataSet", "DataTable", "DataRow"))
from uiType in uiTypes.UsingAny(dbTypes)
let dbTypesUsed = dbTypes.Intersect(uiType.TypesUsed)
let dbTypesAndMembersUsed = dbTypesUsed.Union(dbTypesUsed.ChildMembers().UsedBy(uiType))
// Per defaut this rule estimates a technical debt
// proportional to the coupling between the UI and DB types.
let couplingPerUIType = 2 +
uiType.Methods.UsingAny(dbTypesUsed).Count() +
dbTypesAndMembersUsed.Count()
select new {
uiType,
dbTypesAndMembersUsed,
Debt = (4 * couplingPerUIType).ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// This rule is more a *sample rule to adapt to your need*,
// than a rigid rule you should abide by. It shows how to
// define code layers in a rule and how to be warned about
// layers dependencies violations.
//
// This rule first defines the UI layer and the DB framework
// layer. Second it checks if any UI layer type is using
// directly any DB framework layer type.
//
// • The **DB framework layer** is defined as the set of *third-party*
// types in the framework *ADO.NET*, *EntityFramework*,
// *NHibernate* types, that the application is consuming.
// It is easy to append and suppress any DB framework.
//
// • The UI layer (**User Interface Layer**) is defined as the
// set of types that use *WPF*, *Windows Form*, *ASP.NET*.
//
// *UI using directly DB frameworks* is generally considered
// as *poor design* because DB frameworks accesses should be
// a concept hidden to UI, encapsulated into a **dedicated
// Data Access Layer (DAL)**.
//
// Notice that per defaut this rule estimates a technical debt
// proportional to the coupling between the UI and DB types.
//</Description>
//<HowToFix>
// This rule lists precisely which UI type uses which
// DB framework type. Instead of fixing matches one by one,
// first imagine how DB framework accesses could be
// encapsulated into a dedicated layer.
//</HowToFix>
]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>UI layer shouldn't use directly DAL layer</Name>
warnif count > 0
// UI layer is made of types using a UI framework
let uiTypes = Application.Types.UsingAny(Assemblies.WithNameIn("PresentationFramework", "System.Windows", "System.Windows.Forms", "System.Web"))
// Exclude commonly used DataSet and associated, from ADO.Net types
// You can easily customize this part to define what are DB types.
let dbTypes = ThirdParty.Assemblies.WithNameIn("System.Data", "EntityFramework", "NHibernate").ChildTypes()
.Except(ThirdParty.Types.WithNameIn("DataSet", "DataTable", "DataRow"))
// DAL layer is made of types using a DB framework
// .ToHashSet() results to faster execution of dalTypes.Intersect(uiType.TypesIUse).
let dalTypes = Application.Types.UsingAny(dbTypes).ToHashSet()
from uiType in uiTypes.UsingAny(dalTypes)
let dalTypesUsed = dalTypes.Intersect(uiType.TypesUsed)
let dalTypesAndMembersUsed = dalTypesUsed.Union(dalTypesUsed.ChildMembers().UsedBy(uiType))
// Per defaut this rule estimates a technical debt
// proportional to the coupling between the UI with the DAL layer.
let couplingPerUIType = 2 +
uiType.Methods.UsingAny(dalTypesUsed).Count() +
dalTypesAndMembersUsed.Count()
select new {
uiType,
// if dalTypesUsed is empty, it means that the uiType is part of the DAL
dalTypesAndMembersUsed,
Debt = (4 * couplingPerUIType).ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// This rule is more a *sample rule to adapt to your need*,
// than a rigid rule you should abide by. It shows how to
// define code layers in a rule and how to be warned about
// layers dependencies violations.
//
// This rule first defines the UI layer and the DAL layer.
// Second it checks if any UI layer type is using directly
// any DAL layer type.
//
// • The DB layer (the DAL, **Data Access Layer**) is defined as
// the set of types of the application that use *ADO.NET*,
// *EntityFramework*, *NHibernate* types. It is easy to append
// and suppress any DB framework.
//
// • The UI layer (**User Interface Layer**) is defined as the
// set of types that use *WPF*, *Windows Form*, *ASP.NET*.
//
// *UI using directly DAL* is generally considered as *poor
// design* because DAL accesses should be a concept
// hidden to UI, encapsulated into an **intermediary domain
// logic**.
//
// Notice that per defaut this rule estimates a technical debt
// proportional to the coupling between the UI with the DAL layer.
//</Description>
//<HowToFix>
// This rule lists precisely which UI type uses which DAL type.
//
// More about this particular design topic here:
// http://www.kenneth-truyers.net/2013/05/12/the-n-layer-myth-and-basic-dependency-injection/
//</HowToFix>
]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Assemblies with poor cohesion (RelationalCohesion)</Name>
warnif count > 0 from a in Application.Assemblies
// Build the types list on which we want to check cohesion
// This is the assembly 'a' type, minus enumeration
// and types generated by the compiler.
let types = a.ChildTypes.Where(
t => !t.IsGeneratedByCompiler &&
!t.IsEnumeration)
// Absolutly need ToHashet() to have fast Intersect() calls below.
.ToHashSet()
// Relational Cohesion metrics is relevant only if there are enough types
where types.LongCount()> 20
// R is the total number of relationship between types of the assemblies.
let R = types.Sum(t => t.TypesUsed.Intersect(types).Count())
// Relational Cohesion formula
let relationalCohesion = (double)R / types.Count
where
(relationalCohesion < 1.5 ||
relationalCohesion > 4.0)
select new {
a,
a.ChildTypes,
relationalCohesion,
a.RelationalCohesion,
Debt = 10.ToMinutes().ToDebt(),
Severity = Severity.Low
}
//<Description>
// This rule computes the *Relational Cohesion* metric for
// the application assemblies, and warns about wrong values.
//
// The *Relational Cohesion* for an assembly, is the total number
// of relationship between types of the assemblies, divided
// by the number of types. In other words it is the average
// number of types in the assembly used by a type in the assembly.
//
// As classes inside an assembly should be strongly related,
// the cohesion should be high. On the other hand, a value
// which is too high may indicate over-coupling. A good range
// for *Relational Cohesion* is **1.5 to 4.0**.
//
// Notice that assemblies with less than 20 types are ignored.
//</Description>
//<HowToFix>
// Matches of this present rule might reveal either assemblies
// with specific coding constraints (like code generated that
// have particular structure) either issues in design.
//
// In the second case, large refactoring can be planned
// not to respect this rule in particular, but to increase
// the overall design and code maintainability.
//
// The severity of issues of this rule is **Low** because
// the code metric *Relational Cohesion* is an information
// about the code structure state but **is not actionable**,
// it doesn't tell precisely what to do obtain a better score.
//
// Fixing actionable issues of others **Architecture** and
// **Code Smells** default rules will necessarily increase
// the *Relational Cohesion* scores.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Namespaces with poor cohesion (RelationalCohesion)</Name>
warnif count > 0 from n in Application.Namespaces
// Build the types list on which we want to check cohesion
// This is the namespace children types, minus enumerations
// and types generated by the compiler.
let types = n.ChildTypes.Where(
t => !t.IsGeneratedByCompiler &&
!t.IsEnumeration)
// Absolutly need ToHashet() to have fast Intersect() calls below.
.ToHashSet()
// Relational Cohesion metrics is relevant only if there are enough types
where types.LongCount() > 20
// R is the total number of relationship between types of the namespaces.
let R = types.Sum(t => t.TypesUsed.Intersect(types).Count())
// Relational Cohesion formula
let relationalCohesion = (double)R / types.Count
where
(relationalCohesion < 1.5 ||
relationalCohesion > 4.0)
select new {
n,
n.ChildTypes,
relationalCohesion,
Debt = 10.ToMinutes().ToDebt(),
Severity = Severity.Low
}
//<Description>
// This rule computes the *Relational Cohesion* metric for
// the application namespaces, and warns about wrong values.
//
// The *Relational Cohesion* for a namespace, is the total number
// of relationship between types of the namespaces, divided
// by the number of types. In other words it is the average
// number of types in the namespace used by a type in the namespace.
//
// As classes inside an namespace should be strongly related,
// the cohesion should be high. On the other hand, a value
// which is too high may indicate over-coupling. A good range
// for *Relational Cohesion* is **1.5 to 4.0**.
//
// Notice that namespaces with less than 20 types are ignored.
//</Description>
//<HowToFix>
// Matches of this present rule might reveal either namespaces
// with specific coding constraints (like code generated that
// have particular structure) either issues in design.
//
// In the second case, refactoring sessions can be planned
// to increase the overall design and code maintainability.
//
// The severity of issues of this rule is **Low** because
// the code metric *Relational Cohesion* is an information
// about the code structure state but **is not actionable**,
// it doesn't tell precisely what to do obtain a better score.
//
// Fixing actionable issues of others **Architecture** and
// **Code Smells** default rules will necessarily increase
// the *Relational Cohesion* scores.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Assemblies that don't satisfy the Abstractness/Instability principle</Name>
warnif count > 0 from a in Application.Assemblies
where a.NormDistFromMainSeq > 0.7
orderby a.NormDistFromMainSeq descending
select new {
a,
a.NormDistFromMainSeq,
Debt = 10.ToMinutes().ToDebt(),
Severity = Severity.Low
}
//<Description>
// The **Abstractness versus Instability Diagram** that is shown in the NDepend
// report helps to assess which assemblies are **potentially painful to maintain**
// (i.e concrete and stable) and which assemblies are **potentially useless**
// (i.e abstract and instable).
//
// • **Abstractness**: If an assembly contains many abstract types
// (i.e interfaces and abstract classes) and few concrete types,
// it is considered as abstract.
//
// • **Stability**: An assembly is considered stable if its types
// are used by a lot of types from other assemblies. In this context
// stable means *painful to modify*.
//
// From these metrics, we define the *perpendicular normalized distance of
// an assembly from the idealized line* **A + I = 1** (called *main sequence*).
// This metric is an indicator of the assembly's balance between abstractness
// and stability. We precise that the word *normalized* means that the range
// of values is [0.0 … 1.0].
//
// This rule warns about assemblies with a *normalized distance* greater than
// than 0.7.
//
// This rules use the default code metric on assembly
// *Normalized Distance from the Main Sequence* explained here:
// http://www.ndepend.com/docs/code-metrics#DitFromMainSeq
//
// These concepts have been originally introduced by *Robert C. Martin*
// in 1994 in this paper: http://www.objectmentor.com/resources/articles/oodmetrc.pdf
//</Description>
//<HowToFix>
// Violations of this rule indicate assemblies with an improper
// *abstractness / stability* balance.
//
// • Either the assembly is *potentially painful to maintain* (i.e is massively
// used and contains mostly concrete types). This can be fixed by creating
// abstractions to avoid too high coupling with concrete implementations.
//
// • Either the assembly is *potentially useless* (i.e contains mostly
// abstractions and is not used enough). In such situation, the design
// must be reviewed to see if it can be enhanced.
//
// The severity of issues of this rule is **Low** because
// the *Abstractness/Instability principle* is an information
// about the code structure state but **is not actionable**,
// it doesn't tell precisely what to do obtain a better score.
//
// Fixing actionable issues of others **Architecture** and
// **Code Smells** default rules will necessarily push
// the *Abstractness/Instability principle* scores in the
// right direction.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Higher cohesion - lower coupling</Name>
// warnif count > 0
let abstractNamespaces = JustMyCode.Namespaces.Where(
n => n.ChildTypes.Where(t => !t.IsInterface && !t.IsEnumeration && !t.IsDelegate).Count() == 0
).ToHashSet()
let concreteNamespaces = JustMyCode.Namespaces.Except(abstractNamespaces).ToHashSet()
from n in concreteNamespaces
let namespacesUsed = n.NamespacesUsed.ExceptThirdParty()
let concreteNamespacesUsed = namespacesUsed.Except(abstractNamespaces)
let abstractNamespacesUsed = namespacesUsed.Except(concreteNamespaces)
orderby concreteNamespacesUsed.Count() descending
select new {
n,
concreteNamespacesUsed ,
abstractNamespacesUsed,
// Debt = 50.ToMinutes().ToDebt(),
// Severity = Severity.High
}
//<Description>
// It is deemed as a good software architecture practice to clearly separate
// *abstract* namespaces that contain only abstractions (interfaces, enumerations, delegates)
// from *concrete* namespaces, that contain classes and structures.
//
// Typically, the more concrete namespaces rely on abstract namespaces *only*,
// the more **Decoupled** is the architecture, and the more **Cohesive** are
// classes inside concrete namespaces.
//
// The present code query defines sets of abstract and concrete namespaces
// and show for each concrete namespaces, which concrete and abstract namespaces
// are used.
//</Description>
//<HowToFix>
// This query can be transformed into a code rule, depending if you wish to
// constraint your code structure *coupling / cohesion* ratio.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Example of custom rule to check for dependency</Name>
warnif count > 0 from a in Assemblies
where
a.IsUsing("Foo1.Foo2".AllowNoMatch().MatchNamespace()) &&
(a.Name == @"Foo3")
select new {
a,
a.NbLinesOfCode,
// Debt and Severity / Annual Interest can be modified to estimate well the
// effort to fix the issue and the annual cost to leave the issue unfixed.
Debt = 30.ToMinutes().ToDebt(),
Severity = Severity.High
}
// the assembly Foo3
// shouldn't use directly
// the namespace Foo3.Foo4
// because (TODO insert your reason)
//<Description>
// This rule is a **sample rule** that shows how to
// check if a particular dependency exists or not,
// from a code element **A** to a code element **B**,
// **A** and **B** being an *Assembly*, a *Namespace*, a *Type*,
// a *Method* or a *Field*, **A** and **B** being not
// necessarily of same kind (i.e two Assemblies or
// two Namespaces…).
//
// Such rule can be generated:
//
// • by right clicking the cell in the *Dependency Matrix*
// with **B** in row and **A** in column,
//
// • or by right-clicking the concerned arrow in the *Dependency
// Graph* from **A** to **B**,
//
// and in both cases, click the menu
// **Generate a code rule that warns if this dependency exists**
//
// The generated rule will look like this one.
// It is now up to you to adapt this rule to check exactly
// your needs.
//</Description>
//<HowToFix>
// This is a *sample rule* there is nothing to fix *as is*.
//</HowToFix>]]></Query>
</Group>
<Group Name="API Breaking Changes" Active="True" ShownInReport="True">
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="True"><![CDATA[// <Name>API Breaking Changes: Types</Name>
warnif count > 0 from t in codeBase.OlderVersion().Application.Types
where t.IsPubliclyVisible &&
// The type has been removed, it was not tagged as obsolete
// and its parent assembly hasn't been removed …
( ( t.WasRemoved() &&
!t.ParentAssembly.WasRemoved() &&
!t.IsObsolete) ||
// … or the type is not publicly visible anymore
!t.WasRemoved() && !t.NewerVersion().IsPubliclyVisible)
select new {
t,
NewVisibility =
(t.WasRemoved() ? " " :
t.NewerVersion().Visibility.ToString()),
Debt = 20.ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// This rule is executed only if a *baseline for comparison* is defined (*diff mode*).
//
// This rule warns if a type publicly visible in the *baseline*,
// is not publicly visible anymore or if it has been removed.
// Clients code using such type will be broken.
//</Description>
//<HowToFix>
// Make sure that public types that used to be presented to
// clients, still remain public now, and in the future.
//
// If a public type must really be removed, you can tag it
// with *System.ObsoleteAttribute* with a *workaround message*
// during a few public releases, until it gets removed definitely.
// Notice that this rule doesn't match types removed that were
// tagged as obsolete.
//
// Issues of this rule have a severity equal to **High**
// because an API Breaking change can provoque significant
// friction with consumers of the API.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="True"><![CDATA[// <Name>API Breaking Changes: Methods</Name>
warnif count > 0 from m in codeBase.OlderVersion().Application.Methods
where m.IsPubliclyVisible &&
// The method has been removed, it was not tagged as obsolete
// and its parent type hasn't been removed …
( ( m.WasRemoved() &&
!m.ParentType.WasRemoved() &&
!m.IsObsolete )
// … or the method is not publicly visible anymore
|| (!m.WasRemoved() && !m.NewerVersion().IsPubliclyVisible)
// … or the method return type has changed
|| (!m.WasRemoved() && m.ReturnType != null && m.NewerVersion().ReturnType != null
&& m.ReturnType.FullName != m.NewerVersion().ReturnType.FullName)
)
//--------------------------------------
// Handle special case: if between two versions a regular property becomes
// an auto-property (or vice-versa) the property getter/setter method have
// a different value for IMethod.IsGeneratedByCompiler
// since auto-property getter/setter are marked as generated by the compiler.
//
// If a method IsGeneratedByCompiler value changes between two versions,
// NDepend doesn't pair the newer/older occurences of the method.
//
// Hence in such situation, a public method is seen as added
// and a public method is seen as removed, but the API is not broken!
// The equivalentMethod-check below avoids reporting such
// API Breaking Change false-positive.
let equivalentMethod = m.WasRemoved() && m.ParentType.IsPresentInBothBuilds() ?
m.ParentType.NewerVersion().Methods
.FirstOrDefault(m1 =>
m1.IsPubliclyVisible &&
m1.Name == m.Name &&
m1.IsGeneratedByCompiler != m.IsGeneratedByCompiler &&
(m1.ReturnType == null || m.ReturnType == null || m1.ReturnType.FullName == m.ReturnType.FullName)
)
: null
where equivalentMethod == null
//--------------------------------------
select new {
m,
NewVisibility =
(m.WasRemoved() ? " " :
m.NewerVersion().Visibility.ToString()),
Debt = 10.ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// This rule is executed only if a *baseline for comparison* is defined (*diff mode*).
//
// This rule warns if a method publicly visible in the *baseline*,
// is not publicly visible anymore or if it has been removed.
// Clients code using such method will be broken.
//</Description>
//<HowToFix>
// Make sure that public methods that used to be presented to
// clients, still remain public now, and in the future.
//
// If a public method must really be removed, you can tag it
// with *System.ObsoleteAttribute* with a *workaround message*
// during a few public releases, until it gets removed definitely.
// Notice that this rule doesn't match methods removed that were
// tagged as obsolete.
//
// Issues of this rule have a severity equal to **High**
// because an API Breaking change can provoque significant
// friction with consumers of the API.
//</HowToFix>
]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="True"><![CDATA[// <Name>API Breaking Changes: Fields</Name>
warnif count > 0 from f in codeBase.OlderVersion().Application.Fields
where f.IsPubliclyVisible &&
// The field has been removed, it was not tagged as obsolete
// and its parent type hasn't been removed …
( ( f.WasRemoved() &&
!f.ParentType.WasRemoved() &&
!f.IsObsolete)
// … or the field is not publicly visible anymore
|| !f.WasRemoved() && !f.NewerVersion().IsPubliclyVisible)
// … or the field type has changed
|| (!f.WasRemoved() && f.FieldType != null && f.NewerVersion().FieldType != null
&& f.FieldType.FullName != f.NewerVersion().FieldType.FullName)
select new {
f,
NewVisibility =
(f.WasRemoved() ? " " :
f.NewerVersion().Visibility.ToString()),
Debt = 10.ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// This rule is executed only if a *baseline for comparison* is defined (*diff mode*).
//
// This rule warns if a field publicly visible in the *baseline*,
// is not publicly visible anymore or if it has been removed.
// Clients code using such field will be broken.
//</Description>
//<HowToFix>
// Make sure that public fields that used to be presented to
// clients, still remain public now, and in the future.
//
// If a public field must really be removed, you can tag it
// with *System.ObsoleteAttribute* with a *workaround message*
// during a few public releases, until it gets removed definitely.
// Notice that this rule doesn't match fields removed that were
// tagged as obsolete.
//
// Issues of this rule have a severity equal to **High**
// because an API Breaking change can provoque significant
// friction with consumers of the API.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="True"><![CDATA[// <Name>API Breaking Changes: Interfaces and Abstract Classes</Name>
warnif count > 0 from tNewer in Application.Types where
(tNewer.IsInterface || tNewer.IsClass && tNewer.IsAbstract) &&
tNewer.IsPubliclyVisible &&
tNewer.IsPresentInBothBuilds()
let tOlder = tNewer.OlderVersion() where tOlder.IsPubliclyVisible
let methodsRemoved = tOlder.Methods.Where(m => m.IsAbstract && m.WasRemoved())
let methodsAdded = tNewer.Methods.Where(m => m.IsAbstract && m.WasAdded())
where methodsAdded.Count() > 0 || methodsRemoved.Count() > 0
select new {
tNewer,
methodsAdded,
methodsRemoved,
Debt = 10.ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// This rule is executed only if a *baseline for comparison* is defined (*diff mode*).
//
// This rule warns if a publicly visible interface or abstract class
// has been changed and contains new abstract methods or
// if some abstract methods have been removed.
//
// Clients code that implement such interface or derive from
// such abstract class will be broken.
//</Description>
//<HowToFix>
// Make sure that the public contracts of interfaces and abstract classes
// that used to be presented to clients, remain stable now, and in the future.
//
// If a public contract must really be changed, you can tag
// abstract methods that will be removed with *System.ObsoleteAttribute*
// with a *workaround message* during a few public releases, until it gets
// removed definitely.
//
// Issues of this rule have a severity equal to **High**
// because an API Breaking change can provoque significant
// friction with consummers of the API.
// The severity is not set to **Critical** because an interface
// is not necessarily meant to be implemented by the consummer
// of the API.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="True"><![CDATA[// <Name>Broken serializable types</Name>
warnif count > 0
from t in Application.Types where
// Collect types tagged with SerializableAttribute
t.HasAttribute("System.SerializableAttribute".AllowNoMatch()) &&
!t.IsDelegate &&
t.IsPresentInBothBuilds() &&
t.HasAttribute(t)
// Find newer and older versions of NonSerializedAttribute
let newNonSerializedAttribute = ThirdParty.Types.WithFullName("System.NonSerializedAttribute").SingleOrDefault()
let oldNonSerializedAttribute = newNonSerializedAttribute == null ? null : newNonSerializedAttribute.OlderVersion()
// Find added or removed fields not marked with NonSerializedAttribute
let addedInstanceField = from f in t.InstanceFields where
f.WasAdded() &&
(newNonSerializedAttribute == null || !f.HasAttribute(newNonSerializedAttribute))
select f
let removedInstanceField = from f in t.OlderVersion().InstanceFields where
f.WasRemoved() &&
(oldNonSerializedAttribute == null || !f.HasAttribute(oldNonSerializedAttribute))
select f
where addedInstanceField.Count() > 0 || removedInstanceField.Count() > 0
select new {
t,
addedInstanceField,
removedInstanceField,
Debt = 20.ToMinutes().ToDebt(),
Severity = Severity.Critical
}
//<Description>
// This rule is executed only if a *baseline for comparison* is defined (*diff mode*).
//
// This rule warns about breaking changes in types tagged with
// *SerializableAttribute*.
//
// To do so, this rule searches for serializable type with serializable
// instance fields added or removed. Notice that it doesn't take account
// of fields tagged with *NonSerializedAttribute*.
//
// From http://msdn.microsoft.com/library/system.serializableattribute.aspx :
// "All the public and private fields in a type that are marked by the
// *SerializableAttribute* are serialized by default, unless the type
// implements the *ISerializable* interface to override the serialization process.
// The default serialization process excludes fields that are marked
// with the *NonSerializedAttribute* attribute."
//</Description>
//<HowToFix>
// Make sure that the serialization process of serializable types remains
// stable now, and in the future.
//
// Else you'll have to deal with **Version Tolerant Serialization**
// that is explained here:
// https://msdn.microsoft.com/en-us/library/ms229752(v=vs.110).aspx
//
// Issues of this rule have a severity equal to **High**
// because an API Breaking change can provoque significant
// friction with consummers of the API.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="True"><![CDATA[// <Name>Avoid changing enumerations Flags status</Name>
warnif count > 0
let oldFlags = codeBase.OlderVersion().ThirdParty.Types.WithFullName("System.FlagsAttribute").SingleOrDefault()
let newFlags = ThirdParty.Types.WithFullName("System.FlagsAttribute").SingleOrDefault()
where oldFlags != null && newFlags != null
from t in Application.Types where
t.IsEnumeration &&
t.IsPresentInBothBuilds()
let hasFlagsAttributeNow = t.HasAttribute(newFlags)
let usedToHaveFlagsAttribute = t.OlderVersion().HasAttribute(oldFlags)
where hasFlagsAttributeNow != usedToHaveFlagsAttribute
select new {
t,
hasFlagsAttributeNow,
usedToHaveFlagsAttribute,
Debt = 10.ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// This rule is executed only if a *baseline for comparison* is defined (*diff mode*).
//
// This rule matches enumeration types that used to be tagged
// with *FlagsAttribute* in the *baseline*, and not anymore.
// It also matches the opposite, enumeration types that are now
// tagged with *FlagsAttribute*, and were not tagged in the *baseline*.
//
// Being tagged with *FlagsAttribute* is a strong property for an enumeration.
// Not so much in terms of *behavior* (only the *enum.ToString()* method
// behavior changes when an enumeration is tagged with *FlagsAttribute*)
// but in terms of *meaning*: is the enumeration a **range of values**
// or a **range of flags**?
//
// As a consequence, changing the *FlagsAttribute*s status of an enumeration can
// have significant impact for its clients.
//</Description>
//<HowToFix>
// Make sure the *FlagsAttribute* status of each enumeration remains stable
// now, and in the future.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>API: New publicly visible types</Name>
from t in Application.Types
where t.IsPubliclyVisible &&
// The type has been removed and its parent assembly hasn't been removed …
( (t.WasAdded() && !t.ParentAssembly.WasAdded()) ||
// … or the type existed but was not publicly visible
!t.WasAdded() && !t.OlderVersion().IsPubliclyVisible)
select new {
t,
OldVisibility =
(t.WasAdded() ? " " :
t.OlderVersion().Visibility.ToString()),
}
//<Description>
// This query is executed only if a *baseline for comparison* is defined (*diff mode*).
//
// This code query lists types that are new in the public
// surface of the analyzed assemblies.
//</Description>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>API: New publicly visible methods</Name>
from m in Application.Methods
where m.IsPubliclyVisible &&
// The method has been removed and its parent assembly hasn't been removed …
( (m.WasAdded() && !m.ParentType.WasAdded()) ||
// … or the method existed but was not publicly visible
!m.WasAdded() && !m.OlderVersion().IsPubliclyVisible)
//--------------------------------------
// Handle special case: if between two versions a regular property becomes
// an auto-property (or vice-versa) the property getter/setter method have
// a different value for IMethod.IsGeneratedByCompiler
// since auto-property getter/setter are marked as generated by the compiler.
//
// If a method IsGeneratedByCompiler value changes between two versions,
// NDepend doesn't pair the newer/older occurences of the method.
//
// Hence in such situation, a public method is seen as added
// and a public method is seen as removed, but the API is not broken!
// The equivalentMethod-check below avoids reporting such
// API Breaking Change false-positive.
let equivalentMethod = m.WasAdded() && m.ParentType.IsPresentInBothBuilds() ?
m.ParentType.OlderVersion().Methods
.FirstOrDefault(m1 =>
m1.IsPubliclyVisible &&
m1.Name == m.Name &&
m1.IsGeneratedByCompiler != m.IsGeneratedByCompiler)
: null
where equivalentMethod == null
//--------------------------------------
select new {
m,
OldVisibility =
(m.WasAdded() ? " " :
m.OlderVersion().Visibility.ToString())
}
//<Description>
// This query is executed only if a *baseline for comparison* is defined (*diff mode*).
//
// This code query lists methods that are new in the public
// surface of the analyzed assemblies.
//</Description>
]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>API: New publicly visible fields</Name>
from f in Application.Fields
where f.IsPubliclyVisible &&
// The method has been removed and its parent assembly hasn'f been removed …
( (f.WasAdded() && !f.ParentType.WasAdded()) ||
// … or the t existed but was not publicly visible
!f.WasAdded() && !f.OlderVersion().IsPubliclyVisible)
select new {
f,
OldVisibility =
(f.WasAdded() ? " " :
f.OlderVersion().Visibility.ToString())
}
//<Description>
// This query is executed only if a *baseline for comparison* is defined (*diff mode*).
//
// This code query lists fields that are new in the public
// surface of the analyzed assemblies.
//</Description>]]></Query>
</Group>
<Group Name="Code Coverage" Active="True" ShownInReport="True">
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Code should be tested</Name>
warnif count > 0
// This lambda infers a factor in the range [0,1] from a sequence of distinct types used,
// based on how many of these types are actually concrete (i.e are not interfaces or enumeration).
// Primitive types (int, bool...) are eliminated from the sequence of types used
// by filtering types declared in the namespace System.
let abstractionUsageFactorFormula = new Func<IEnumerable<IType>,double>(typesUsed =>
typesUsed.Count(t => t.ParentNamespace.Name != "System" && !t.IsInterface && !t.IsEnumeration)
/ (1 + typesUsed.Count(t => t.ParentNamespace.Name != "System")))
from method in Application.Methods
where !method.IsExcludedFromCoverage && method.NbLinesOfCode >= 0 && method.PercentageCoverage < 100
// Factor in case method is partially covered
let uncoverageFactor = ((100 - method.PercentageCoverage) / 100).Value
// Complexity factor
let complexityFactor = ((method.CyclomaticComplexity ?? method.ILCyclomaticComplexity) + method.ILNestingDepth).Linear(0, 0.1, 10, 1).Value
// Not my code is often generated code and is in general easier to get tested since test can be generated as well.
let justMyCodeFactor = (JustMyCode.Contains(method) ? 1 : 0.4)
// abstractionUsageFactor reflects the fact that code that relies on interfaces
// is easier to test that code that relies on concrete classes.
let abstractionUsageFactor = 0.7 + 0.3 *abstractionUsageFactorFormula(method.MembersUsed.Select(m => m.ParentType).Distinct())
// The usageFactor depends on the method 'rank' that is a value
// indicating if the method is often used or not
let usageFactor = (method.Rank / (method.Rank + 4)).Value
// It is more complicated to write tests for non publicly visible methods
let visibilityFactor = method.Visibility.EqualsAny(Visibility.Public, Visibility.Internal) ? 1 :
method.Visibility != Visibility.Private ? 1.1 : 1.2
// Is is more complicated to write tests for methods that read mutable static fields
// whose changing state is shared across tests executions.
let staticFieldUsageFactor = method.ReadsMutableTypeState ? 1.3 : 1.0
// Both "effort to write tests" and "annual cost to not test" for a method
// is determined by several factors in the range [0,1] that multiplies the effortToDevelop
let effortToDevelopInMinutes = method.EffortToDevelop().Value.TotalMinutes
let effortToWriteTests = Math.Max(2, // Minimum 2 minutes per method not tested
effortToDevelopInMinutes *
uncoverageFactor *
complexityFactor *
justMyCodeFactor *
abstractionUsageFactor *
visibilityFactor *
staticFieldUsageFactor).ToMinutes().ToDebt()
let annualCostToNotFix = Math.Max(2, // Minimum 2 minutes per method not tested
effortToDevelopInMinutes *
usageFactor *
uncoverageFactor *
justMyCodeFactor).ToMinutes().ToAnnualInterest()
orderby annualCostToNotFix.Value descending
select new {
method,
method.PercentageCoverage,
method.NbLinesOfCode,
method.NbLinesOfCodeNotCovered,
method.CyclomaticComplexity,
Debt = effortToWriteTests,
AnnualInterest = annualCostToNotFix,
// BreakingPoint = effortToWriteTests.BreakingPoint(annualCostToNotFix),
// Uncomment the line below to tinker with various factors
// uncoverageFactor, complexityFactor , justMyCodeFactor , abstractionUsageFactor, visibilityFactor, staticFieldUsageFactor
}
//<Description>
// This rule lists methods not covered at all by test
// or partially covered by tests.
//
// For each match, the rules estimates the **technical debt**, i.e
// the effort to write unit and integration tests for the method.
// The estimation is based on the effort to develop the code element
// multiplied by factors in the range ]0,1.3] based on
//
// • the method code size and complexity
//
// • the actual percentage coverage
//
// • the abstracness of types used, because relying on classes instead of
// interfaces makes the code more difficult to test
//
// • the method visibility because testing private or protected
// methods is more difficult than testing public and internal ones
//
// • the fields used by the method, because is is more complicated to
// write tests for methods that read mutable static fields whose changing
// state is shared across tests executions.
//
// • whether the method is considered *JustMyCode* or not because *NotMyCode*
// is often generated easier to get tested since tests can be generated as well.
//
// This rule is necessarily a large source of technical debt, since
// the code left untested is by definition part of the technical debt.
//
// This rule also estimates the **annual interest**, i.e the annual cost
// to let the code uncovered, based on the effort to develop the
// code element, multiplied by factors based on usage of the code element.
//</Description>
//<HowToFix>
// Write unit tests to test and cover the methods and their parent classes
// matched by this rule.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>New Methods should be tested</Name>
warnif count > 0
from m in Application.Methods where
m.NbLinesOfCode > 0 &&
m.PercentageCoverage < 30 &&
m.WasAdded()
orderby m.NbLinesOfCode descending,
m.NbLinesOfCodeNotCovered ,
m.PercentageCoverage
select new {
m,
m.PercentageCoverage,
m.NbLinesOfCode,
m.NbLinesOfCodeNotCovered,
// Simplistic Debt estimation, because the effort to write tests for a method not 100% tested
// is already estimated properly with the rule "Code should be tested".
Debt = m.NbLinesOfCodeNotCovered.Linear(1,2, 10,10).ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// This rule is executed only if a *baseline for comparison* is defined (*diff mode*).
// This rule operates only on methods added or refactored since the baseline.
//
// This rule is executed only if some code coverage data is imported
// from some code coverage files.
//
// It is important to write code mostly covered by tests
// to achieve *maintainable* and *non-error-prone* code.
//
// In real-world, many code bases are poorly covered by tests.
// However it is not practicable to stop the development for months
// to refactor and write tests to achieve high code coverage ratio.
//
// Hence it is recommended that each time a method (or a type) gets added,
// the developer takes the time to write associated unit-tests to cover it.
//
// Doing so will help to increase significantly the maintainability of the code base.
// You'll notice that quickly, refactoring will also be driven by testability,
// and as a consequence, the overall code structure and design will increase as well.
//
// Issues of this rule have a **High** severity because they reflect
// an actual trend to not care about writing tests on refactored code.
//</Description>
//<HowToFix>
// Write unit-tests to cover the code of most methods and classes added.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Methods refactored should be tested</Name>
warnif count > 0
from m in Application.Methods where
m.PercentageCoverage < 30 &&
m.CodeWasChanged()
orderby m.NbLinesOfCode descending,
m.NbLinesOfCodeNotCovered ,
m.PercentageCoverage
select new {
m,
m.PercentageCoverage,
m.NbLinesOfCode,
m.NbLinesOfCodeNotCovered,
// Simplistic Debt estimation, because the effort to write tests for a method not 100% tested
// is already estimated properly with the rule "Code should be tested".
Debt = m.NbLinesOfCodeNotCovered.Linear(1,2, 10,10).ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// This rule is executed only if a *baseline for comparison* is defined (*diff mode*).
// This rule operates only on methods added or refactored since the baseline.
//
// This rule is executed only if some code coverage data is imported
// from some code coverage files.
//
// It is important to write code mostly covered by tests
// to achieve *maintainable* and *non-error-prone* code.
//
// In real-world, many code bases are poorly covered by tests.
// However it is not practicable to stop the development for months
// to refactor and write tests to achieve high code coverage ratio.
//
// Hence it is recommended that each time a method (or a type) gets refactored,
// the developer takes the time to write associated unit-tests to cover it.
//
// Doing so will help to increase significantly the maintainability of the code base.
// You'll notice that quickly, refactoring will also be driven by testability,
// and as a consequence, the overall code structure and design will increase as well.
//
// Issues of this rule have a **High** severity because they reflect
// an actual trend to not care about writing tests on refactored code.
//</Description>
//<HowToFix>
// Write unit-tests to cover the code of most methods and classes refactored.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Assemblies Namespaces and Types should be tested</Name>
warnif count > 0
from elem in CodeElementParents
where elem.NbLinesOfCode > 0 &&
elem.PercentageCoverage == 0 &&
elem.Parent.PercentageCoverage != 0
orderby elem.NbLinesOfCode descending
select new {
elem,
elem.NbLinesOfCodeNotCovered,
Debt = 4.ToMinutes().ToDebt(),
Severity = Severity.Low
}
//<Description>
// This rule lists assemblies, namespaces and types that are not
// covered at all by unit tests.
//
// If a parent is matched its children are not matched. For example
// if a namespace is matched, its child types are not matched.
//
// This rule goal is not to collide with the **Code should be tested**
// rule that lists uncovered code for each method and infer the effort
// to write unit tests (the *Debt*) and the annual cost to let the code
// untested (the Annual Interest).
//
// This rule goal is to inform of large code elements left untested.
// As a consequence the *Debt* per issue is only 4 minutes and the
// severity of the issues is *Low*.
//</Description>
//
//<HowToFix>
// Write unit and integration tests to cover, even partially,
// code elements matched by this rule.
//
// Then use issues of the rules **Code should be tested**,
// **New Methods should be tested** and
// **Methods refactored should be tested**
// to write more tests where it matters most, and eventually
// refactor some code to make it more testable.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Types almost 100% tested should be 100% tested</Name>
warnif count > 0
from t in Application.Types where
t.PercentageCoverage >= 95 &&
t.PercentageCoverage <= 99 &&
!t.IsGeneratedByCompiler
let methodsCulprit = t.Methods.Where(m => m.PercentageCoverage < 100)
orderby t.NbLinesOfCode descending ,
t.NbLinesOfCodeNotCovered ,
t.PercentageCoverage
select new {
t,
t.PercentageCoverage,
t.NbLinesOfCode,
t.NbLinesOfCodeNotCovered,
methodsCulprit,
// Simplistic Debt estimation, because the effort to write tests for a type not 100% tested
// is already estimated properly with the rule "Code should be tested".
Debt = t.NbLinesOfCodeNotCovered.Linear(1,2, 20,20).ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// This rule is executed only if some code coverage data is imported
// from some code coverage files.
//
// Often covering the few percents of remaining uncovered code of a class,
// requires as much work as covering the first 90%.
// For this reason, often teams estimate that 90% coverage is enough.
// However *untestable code* usually means *poorly written code*
// which usually leads to *error prone code*.
// So it might be worth refactoring and making sure to cover the few uncovered lines of code
// **because most tricky bugs might come from this small portion of hard-to-test code**.
//
// Not all classes should be 100% covered by tests (like UI code can be hard to test)
// but you should make sure that most of the logic of your application
// is defined in some *easy-to-test classes*, 100% covered by tests.
//
// Issues of this rule have a **High** severity because as explained,
// such situation is *bug-prone*.
//</Description>
//<HowToFix>
// Write more unit-tests dedicated to cover code not covered yet.
// If you find some *hard-to-test code*, it is certainly a sign that this code
// is not *well designed* and hence, needs refactoring.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Namespaces almost 100% tested should be 100% tested</Name>
warnif count > 0
from n in Application.Namespaces where
n.PercentageCoverage >= 95 &&
n.PercentageCoverage <= 99
let methodsCulprit = n.ChildMethods.Where(m => m.PercentageCoverage < 100)
orderby n.NbLinesOfCode descending ,
n.NbLinesOfCodeNotCovered ,
n.PercentageCoverage
select new {
n,
n.PercentageCoverage,
n.NbLinesOfCode,
n.NbLinesOfCodeNotCovered,
methodsCulprit,
// Simplistic Debt estimation, because the effort to write tests for a type not 100% tested
// is already estimated properly with the rule "Code should be tested".
Debt = n.NbLinesOfCodeNotCovered.Linear(1,2, 50,60).ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// This rule is executed only if some code coverage data is imported
// from some code coverage files.
//
// Often covering the few percents of remaining uncovered code of
// one or several classes in a namespace
// requires as much work as covering the first 90%.
// For this reason, often teams estimate that 90% coverage is enough.
// However *untestable code* usually means *poorly written code*
// which usually leads to *error prone code*.
// So it might be worth refactoring and making sure to cover the few uncovered lines of code
// **because most tricky bugs might come from this small portion of hard-to-test code**.
//
// Not all classes should be 100% covered by tests (like UI code can be hard to test)
// but you should make sure that most of the logic of your application
// is defined in some *easy-to-test classes*, 100% covered by tests.
//
// Issues of this rule have a **High** severity because as explained,
// such situation is *bug-prone*.
//</Description>
//<HowToFix>
// Write more unit-tests dedicated to cover code not covered yet in the namespace.
// If you find some *hard-to-test code*, it is certainly a sign that this code
// is not *well designed* and hence, needs refactoring.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Types that used to be 100% covered by tests should still be 100% covered</Name>
warnif count > 0
from t in JustMyCode.Types where
t.IsPresentInBothBuilds() &&
t.OlderVersion().PercentageCoverage == 100 &&
t.PercentageCoverage < 100
from m in t .MethodsAndContructors where
m.NbLinesOfCode> 0 &&
m.PercentageCoverage < 100 &&
!m.IsExcludedFromCoverage
select new {
m,
m.PercentageCoverage,
// Simplistic Debt estimation, because the effort to write tests for a method not 100% tested
// is already estimated properly with the rule "Code should be tested".
Debt = t.NbLinesOfCodeNotCovered.Linear(1,2, 10,10).ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// This rule is executed only if a *baseline for comparison* is defined (*diff mode*).
//
// This rule is executed only if some code coverage data is imported
// from some code coverage files.
//
// Often covering 10% of remaining uncovered code of a class,
// requires as much work as covering the first 90%.
// For this reason, typically teams estimate that 90% coverage is enough.
// However *untestable code* usually means *poorly written code*
// which usually leads to *error prone code*.
// So it might be worth refactoring and making sure to cover the 10% remaining code
// **because most tricky bugs might come from this small portion of hard-to-test code**.
//
// Not all classes should be 100% covered by tests (like UI code can be hard to test)
// but you should make sure that most of the logic of your application
// is defined in some *easy-to-test classes*, 100% covered by tests.
//
// In this context, this rule warns when a type fully covered by tests is now only partially covered.
//
// Issues of this rule have a **High** severity because often,
// a type that used to be 100% and is not covered anymore
// is a bug-prone situation that should be carefully handled.
//</Description>
//<HowToFix>
// Write more unit-tests dedicated to cover code not covered anymore.
// If you find some *hard-to-test code*, it is certainly a sign that this code
// is not *well designed* and hence, needs refactoring.
//
// You'll find code impossible to cover by unit-tests, like calls to *MessageBox.Show()*.
// An infrastructure must be defined to be able to *mock* such code at test-time.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Types tagged with FullCoveredAttribute should be 100% covered</Name>
warnif count > 0
from t in Application.Types where
t.HasAttribute ("NDepend.Attributes.FullCoveredAttribute".AllowNoMatch()) &&
t.PercentageCoverage < 100
from m in t .MethodsAndContructors where
m.NbLinesOfCode> 0 &&
m.PercentageCoverage < 100 &&
!m.IsExcludedFromCoverage
select new {
m,
m.PercentageCoverage,
m.NbLinesOfCodeNotCovered,
m.NbLinesOfCode,
// Simplistic Debt estimation, because the effort to write tests for a method not 100% tested
// is already estimated properly with the rule "Code should be tested".
Debt = m.NbLinesOfCodeNotCovered.Linear(1,2, 10,10).ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// This rule lists methods partially covered by tests, of types tagged with
// **FullCoveredAttribute**.
//
// This rule is executed only if some code coverage data is imported
// from some code coverage files.
//
// By using a **FullCoveredAttribute**, you can express in source code the intention
// that a class is 100% covered by tests, and should remain 100% covered in the future.
// If you don't want to link *NDepend.API.dll*,
// you can use your own attribute and adapt the source code of this rule.
//
// Benefits of using a **FullCoveredAttribute** are twofold:
// Not only the intention is expressed in source code,
// but it is also continuously checked by the present rule.
//
// Often covering 10% of remaining uncovered code of a class,
// requires as much work as covering the first 90%.
// For this reason, often teams estimate that 90% coverage is enough.
// However *untestable code* usually means *poorly written code* which usually means *error prone code*.
// So it might be worth refactoring and making sure to cover the 10% remaining code
// **because most tricky bugs might come from this small portion of hard-to-test code**.
//
// Not all classes should be 100% covered by tests (like UI code can be hard to test)
// but you should make sure that most of the logic of your application
// is defined in some *easy-to-test classes*, 100% covered by tests.
//
// Issues of this rule have a **High** severity because often,
// a type that used to be 100% and is not covered anymore
// is a bug-prone situation that should be carefully handled.
//</Description>
//<HowToFix>
// Write more unit-tests dedicated to cover code of matched classes not covered yet.
// If you find some *hard-to-test code*, it is certainly a sign that this code
// is not *well designed* and hence, needs refactoring.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Types 100% covered should be tagged with FullCoveredAttribute</Name>
warnif count > 0 from t in JustMyCode.Types where
!t.HasAttribute ("NDepend.Attributes.FullCoveredAttribute".AllowNoMatch()) &&
t.PercentageCoverage == 100 &&
!t.IsGeneratedByCompiler
select new {
t,
t.NbLinesOfCode,
Debt = 3.ToMinutes().ToDebt(), // It is fast to add such attribute to a type.
Severity = Severity.Low
}
//<Description>
// This rule is executed only if some code coverage data is imported
// from some code coverage files.
//
// By using a **FullCoveredAttribute**, you can express in source code the intention
// that a class is 100% covered by tests, and should remain 100% covered in the future.
//
// Benefits of using a **FullCoveredAttribute** are twofold:
// Not only the intention is expressed in source code,
// but it is also continuously checked by the present rule.
//
// Issues of this rule have an **Low** severity because they don't reflect
// a problem, but provide an advice for potential improvement.
//</Description>
//<HowToFix>
// Just tag types 100% covered by tests with the **FullCoveredAttribute**
// that can be found in *NDepend.API.dll*,
// or by an attribute of yours defined in your own code
// (in which case this rule must be adapted).
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Methods should have a low C.R.A.P score</Name>
warnif count > 0
from m in JustMyCode.Methods
// Don't match too short methods
where m.NbLinesOfCode > 10 && m.CoverageDataAvailable
let CC = m.CyclomaticComplexity
let uncov = (100 - m.PercentageCoverage) / 100f
let CRAP = (double)(CC * CC * uncov * uncov * uncov) + CC
where CRAP != null && CRAP > 30
orderby CRAP descending, m.NbLinesOfCode descending
select new {
m,
CRAP,
CC,
m.PercentageCoverage, m.NbLinesOfCode,
// CRAP score equals 30 => 10 minutes debt
// CRAP score equals 3000 => 3 hours to write tests
Debt = CRAP.Linear(30,10, 3000, 3*60).ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// This rule is executed only if some code coverage data is imported
// from some code coverage files.
//
// So far this rule is disabled because other code coverage rules
// assess properly code coverage issues.
//
// **Change Risk Analyzer and Predictor** (i.e. CRAP) is a code metric
// that helps in pinpointing overly both complex and untested code.
// Is has been first defined here:
// http://www.artima.com/weblogs/viewpost.jsp?thread=215899
//
// The Formula is: **CRAP(m) = CC(m)^2 * (1 – cov(m)/100)^3 + CC(m)**
//
// • where *CC(m)* is the *cyclomatic complexity* of the method *m*
//
// • and *cov(m)* is the *percentage coverage* by tests of the method *m*
//
// Matched methods cumulates two highly *error prone* code smells:
//
// • A complex method, difficult to develop and maintain.
//
// • Non 100% covered code, difficult to refactor without introducing any regression bug.
//
// The higher the CRAP score, the more painful to maintain and error prone is the method.
//
// An arbitrary threshold of 30 is fixed for this code rule as suggested by inventors.
//
// Notice that no amount of testing will keep methods with a Cyclomatic Complexity
// higher than 30, out of CRAP territory.
//
// Notice that this rule doesn't match too short method
// with less than 10 lines of code.
//</Description>
//<HowToFix>
// In such situation, it is recommended to both refactor the complex method logic
// into several smaller and less complex methods
// (that might belong to some new types especially created),
// and also write unit-tests to full cover the refactored logic.
//
// You'll find code impossible to cover by unit-tests, like calls to *MessageBox.Show()*.
// An infrastructure must be defined to be able to *mock* such code at test-time.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Test Methods</Name>
let testAttr = ThirdParty.Types.WithNameIn("TestAttribute", "TestCaseAttribute")
let testMethods = Methods.TaggedWithAnyAttributes(testAttr)
from m in testMethods
select m
//<Description>
// We advise to not include test assemblies in code analyzed by NDepend.
// We estimate that it is acceptable and practical to lower the quality gate of test code,
// because the important measures for tests are:
//
// • The coverage ratio,
//
// • And the amount of logic results asserted: This includes both
// assertions in test code, and assertions in code covered by tests,
// like *Code Contract* assertions and *Debug.Assert(…)* assertions.
//
// But if you wish to enforce the quality of test code, you'll need to
// consider test assemblies in your list of application assemblies
// analyzed by NDepend.
//
// In such situation, this code query lists tests methods and you can
// reuse this code in custom rules.
//</Description>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Methods directly called by test Methods</Name>
let testAttr = ThirdParty.Types.WithNameIn("TestAttribute", "TestCaseAttribute")
let testMethods = Methods.TaggedWithAnyAttributes(testAttr).ToHashSet()
// --- Uncomment this line if your test methods are in dedicated test assemblies ---
//let testAssemblies = testMethods.ParentAssemblies().ToHashSet()
from m in Application.Methods.UsedByAny(testMethods)
// --- Uncomment this line if your test methods are in dedicated test assemblies ---
//where !testAssemblies.Contains(m.ParentAssembly)
select new { m ,
calledByTests = m.MethodsCallingMe.Intersect(testMethods ),
// --- Uncomment this line if your project import some coverage data ---
// m.PercentageCoverage
}
//<Description>
// This query lists all methods directly called by tests methods.
// Overrides of virtual and abstract methods, called through polymorphism, are not listed.
// Methods solely invoked through a delegate are not listed.
// Methods solely invoked through reflection are not listed.
//
// We advise to not include test assemblies in code analyzed by NDepend.
// We estimate that it is acceptable and practical to lower the quality gate of test code,
// because the important measures for tests are:
//
// • The coverage ratio,
//
// • And the amount of logic results asserted: This includes both
// assertions in test code, and assertions in code covered by tests,
// like *Code Contract* assertions and *Debug.Assert(…)* assertions.
//
// But if you wish to run this code query,
// you'll need to consider test assemblies in your list of
// application assemblies analyzed by NDepend.
//</Description>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Methods directly and indirectly called by test Methods</Name>
let testAttr = from t in ThirdParty.Types.WithNameIn("TestAttribute", "TestCaseAttribute") select t
let testMethods = Methods.TaggedWithAnyAttributes(testAttr)
// --- Uncomment this line if your test methods are in dedicated test assemblies ---
// let testAssemblies = testMethods.ParentAssemblies().ToHashSet()
let depthOfCalledByTest = Application.Methods.DepthOfIsUsedByAny(testMethods)
from pair in depthOfCalledByTest
where pair.Value > 0
orderby pair.Value ascending
// --- Uncomment this line if your test methods are in dedicated test assemblies ---
//&& !testAssemblies.Contains(pair.CodeElement.ParentAssembly)
select new {
method = pair.CodeElement,
// (depthOfCalledByTests == 1) means that the method is directly called by tests
// (depthOfCalledByTests == 2) means that the method is directly called by a method directly called by tests
// …
depthOfCalledByTests = pair.Value,
nbLinesOfCode = pair.CodeElement.NbLinesOfCode,
// --- Uncomment this line if your project import some coverage data ---
// m.PercentageCoverage
}
//<Description>
// This query lists all methods *directly or indirectly* called by tests methods.
// *Indirectly* called by a test means that a test method calls a method, that calls a method…
// From this recursion, a code metric named *depthOfCalledByTests* is inferred,
// The value *1* means directly called by test,
// the value *2* means called by a method that is called by a test…
//
// Overrides of virtual and abstract methods, called through polymorphism, are not listed.
// Methods solely invoked through a delegate are not listed.
// Methods solely invoked through reflection are not listed.
//
// We advise to not include test assemblies in code analyzed by NDepend.
// We estimate that it is acceptable and practical to lower the quality gate of test code,
// because the important measures for tests are:
//
// • The coverage ratio,
//
// • And the amount of logic results asserted: This includes both
// assertions in test code, and assertions in code covered by tests,
// like *Code Contract* assertions and *Debug.Assert(…)* assertions.
//
// But if you wish to run this code query,
// you'll need to consider test assemblies in your list of
// application assemblies analyzed by NDepend.
//</Description>]]></Query>
</Group>
<Group Name="Dead Code" Active="True" ShownInReport="True">
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Potentially Dead Types</Name>
warnif count > 0
// Filter procedure for types that should'nt be considered as dead
let canTypeBeConsideredAsDeadProc = new Func<IType, bool>(
t => !t.IsPublic && // Public types might be used by client applications of your assemblies.
t.Name != "Program" &&
!t.IsGeneratedByCompiler &&
// If you don't want to link NDepend.API.dll, you can use your own IsNotDeadCodeAttribute
// and adapt the source code of this rule.
!t.HasAttribute("NDepend.Attributes.IsNotDeadCodeAttribute".AllowNoMatch()) &&
// Exclude static types that define only const fields
// because they cannot be seen as used in IL code.
!(t.IsStatic && t.NbMethods == 0 && !t.Fields.Where(f => !f.IsLiteral).Any()))
// Select types unused
let typesUnused =
from t in JustMyCode.Types where
t.NbTypesUsingMe == 0 && canTypeBeConsideredAsDeadProc(t)
select t
// Dead types = types used only by unused types (recursive)
let deadTypesMetric = typesUnused.FillIterative(
types => from t in codeBase.Application.Types.UsedByAny(types).Except(types)
where canTypeBeConsideredAsDeadProc(t) &&
t.TypesUsingMe.Intersect(types).Count() == t.NbTypesUsingMe
select t)
from t in deadTypesMetric.DefinitionDomain
select new {
t,
depth = deadTypesMetric[t],
t.TypesUsingMe,
Debt = 15.ToMinutes().ToDebt(),
AnnualInterest = (10 + (t.NbLinesOfCode ?? 1)).ToMinutes().ToAnnualInterest()
}
//<Description>
// This rule lists *potentially* **dead types**.
// A dead type is a type that can be removed
// because it is never used by the program.
//
// This rule lists not only types not used anywhere in code,
// but also types used only by types not used anywhere in code.
// This is why this rule comes with a column *TypesusingMe* and
// this is why there is a code metric named *depth*:
//
// • A *depth* value of *0* means the type is not used.
//
// • A *depth* value of *1* means the type is used only by types not used.
//
// • etc…
//
// By reading the source code of this rule, you'll see that by default,
// *public* types are not matched, because such type might not be used
// by the analyzed code, but still be used by client code, not analyzed by NDepend.
// This default behavior can be easily changed.
//</Description>
//<HowToFix>
// *Static analysis* cannot provide an *exact* list of dead types,
// because there are several ways to use a type *dynamically* (like through reflection).
//
// For each type matched by this query, first investigate if the type is used somehow
// (like through reflection).
// If the type is really never used, it is important to remove it
// to avoid maintaining useless code.
// If you estimate the code of the type might be used in the future,
// at least comment it, and provide an explanatory comment about the future intentions.
//
// If a type is used somehow,
// but still is matched by this rule, you can tag it with the attribute
// **IsNotDeadCodeAttribute** found in *NDepend.API.dll* to avoid matching the type again.
// You can also provide your own attribute for this need,
// but then you'll need to adapt this code rule.
//
// Issues of this rule have a **Debt** equal to 15 minutes because it only
// takes a short while to investigate if a type can be safely discarded.
// The **Annual Interest** of issues of this rule, the annual cost to not
// fix such issue, is proportional to the type #lines of code, because
// the bigger the type is, the more it slows down maintenance.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Potentially Dead Methods</Name>
warnif count > 0
// Filter procedure for methods that should'nt be considered as dead
let canMethodBeConsideredAsDeadProc = new Func<IMethod, bool>(
m => !m.IsPubliclyVisible && // Public methods might be used by client applications of your assemblies.
!m.IsEntryPoint && // Main() method is not used by-design.
!m.IsExplicitInterfaceImpl && // The IL code never explicitly calls explicit interface methods implementation.
!m.IsClassConstructor && // The IL code never explicitly calls class constructors.
!m.IsFinalizer && // The IL code never explicitly calls finalizers.
!m.IsVirtual && // Only check for non virtual method that are not seen as used in IL.
!(m.IsConstructor && // Don't take account of protected ctor that might be call by a derived ctors.
m.IsProtected) &&
!m.IsEventAdder && // The IL code never explicitly calls events adder/remover.
!m.IsEventRemover &&
!m.IsGeneratedByCompiler &&
!m.ParentType.IsDelegate &&
// Methods tagged with these two attributes are called by the serialization infrastructure.
!m.HasAttribute("System.Runtime.Serialization.OnSerializingAttribute".AllowNoMatch()) &&
!m.HasAttribute("System.Runtime.Serialization.OnDeserializedAttribute".AllowNoMatch()) &&
// If you don't want to link NDepend.API.dll, you can use your own IsNotDeadCodeAttribute
// and adapt the source code of this rule.
!m.HasAttribute("NDepend.Attributes.IsNotDeadCodeAttribute".AllowNoMatch()))
// Get methods unused
let methodsUnused =
from m in JustMyCode.Methods where
m.NbMethodsCallingMe == 0 &&
canMethodBeConsideredAsDeadProc(m)
select m
// Dead methods = methods used only by unused methods (recursive)
let deadMethodsMetric = methodsUnused.FillIterative(
methods => // Unique loop, just to let a chance to build the hashset.
from o in (new object()).ToEnumerable()
// Use a hashet to make Intersect calls much faster!
let hashset = methods.ToHashSet()
from m in codeBase.Application.Methods.UsedByAny(methods).Except(methods)
where canMethodBeConsideredAsDeadProc(m) &&
// Select methods called only by methods already considered as dead
hashset.Intersect(m.MethodsCallingMe).Count() == m.NbMethodsCallingMe
select m)
from m in JustMyCode.Methods.Intersect(deadMethodsMetric.DefinitionDomain)
let depth = deadMethodsMetric[m]
select new {
m,
depth,
m.MethodsCallingMe,
Debt = (10 + 3*depth).ToMinutes().ToDebt(),
AnnualInterest = (8 + (m.NbLinesOfCode ?? 1)).ToMinutes().ToAnnualInterest()
}
//<Description>
// This rule lists *potentially* **dead methods**.
// A dead method is a method that can be removed
// because it is never called by the program.
//
// This rule lists not only methods not called anywhere in code,
// but also methods called only by methods not called anywhere in code.
// This is why this rule comes with a column *MethodsCallingMe* and
// this is why there is a code metric named *depth*:
//
// • A *depth* value of *0* means the method is not called.
//
// • A *depth* value of *1* means the method is called only by methods not called.
//
// • etc…
//
// By reading the source code of this rule, you'll see that by default,
// *public* methods are not matched, because such method might not be called
// by the analyzed code, but still be called by client code, not analyzed by NDepend.
// This default behavior can be easily changed.
//</Description>
//<HowToFix>
// *Static analysis* cannot provide an *exact* list of dead methods,
// because there are several ways to invoke a method *dynamically* (like through reflection).
//
// For each method matched by this query, first investigate if the method is invoked somehow
// (like through reflection).
// If the method is really never invoked, it is important to remove it
// to avoid maintaining useless code.
// If you estimate the code of the method might be used in the future,
// at least comment it, and provide an explanatory comment about the future intentions.
//
// If a method is invoked somehow,
// but still is matched by this rule, you can tag it with the attribute
// **IsNotDeadCodeAttribute** found in *NDepend.API.dll* to avoid matching the method again.
// You can also provide your own attribute for this need,
// but then you'll need to adapt this code rule.
//
// Issues of this rule have a **Debt** equal to 10 minutes because it only
// takes a short while to investigate if a method can be safely discarded.
// On top of these 10 minutes, the depth of usage of such method adds up
// 3 minutes per unity because dead method only called by dead code
// takes a bit more time to be investigated.
//
// The **Annual Interest** of issues of this rule, the annual cost to not
// fix such issue, is proportional to the type #lines of code, because
// the bigger the method is, the more it slows down maintenance.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Potentially Dead Fields</Name>
warnif count > 0
from f in JustMyCode.Fields where
f.NbMethodsUsingMe == 0 &&
!f.IsPublic && // Although not recommended, public fields might be used by client applications of your assemblies.
!f.IsLiteral && // The IL code never explicitly uses literal fields.
!f.IsEnumValue && // The IL code never explicitly uses enumeration value.
f.Name != "value__" && // Field named 'value__' are relative to enumerations and the IL code never explicitly uses them.
!f.HasAttribute("NDepend.Attributes.IsNotDeadCodeAttribute".AllowNoMatch()) &&
!f.IsGeneratedByCompiler &&
!f.ParentType.Name.StartsWith("Jvm")
// If you don't want to link NDepend.API.dll, you can use your own IsNotDeadCodeAttribute
// and adapt the source code of this rule.
select new {
f,
Debt = 10.ToMinutes().ToDebt(),
AnnualInterest = 8.ToMinutes().ToAnnualInterest()
}
//<Description>
// This rule lists *potentially* **dead fields**.
// A dead field is a field that can be removed
// because it is never used by the program.
//
// By reading the source code of this rule, you'll see that by default,
// *public* fields are not matched, because such field might not be used
// by the analyzed code, but still be used by client code, not analyzed by NDepend.
// This default behavior can be easily changed.
// Some others default rules in the *Visibility* group, warn about public fields.
//
// More restrictions are applied by this rule because of some *by-design* limitations.
// NDepend mostly analyzes compiled IL code, and the information that
// an enumeration value or a literal constant (which are fields) is used
// is lost in IL code. Hence by default this rule won't match such field.
//</Description>
//<HowToFix>
// *Static analysis* cannot provide an *exact* list of dead fields,
// because there are several ways to assign or read a field *dynamically*
// (like through reflection).
//
// For each field matched by this query, first investigate
// if the field is used somehow (like through reflection).
// If the field is really never used, it is important to remove it
// to avoid maintaining a useless code element.
//
// If a field is used somehow,
// but still is matched by this rule, you can tag it with the attribute
// **IsNotDeadCodeAttribute** found in *NDepend.API.dll*
// to avoid matching the field again.
// You can also provide your own attribute for this need,
// but then you'll need to adapt this code rule.
//
// Issues of this rule have a **Debt** equal to 10 minutes because it only
// takes a short while to investigate if a method can be safely discarded.
// The **Annual Interest** of issues of this rule, the annual cost to not
// fix such issue, is set by default to 8 minutes per unused field matched.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Wrong usage of IsNotDeadCodeAttribute</Name>
warnif count > 0
let tAttr = Types.WithFullName("NDepend.Attributes.IsNotDeadCodeAttribute").FirstOrDefault()
where tAttr != null
// Get types that do a wrong usage of IsNotDeadCodeAttribute
let types = from t in Application.Types where
t.HasAttribute("NDepend.Attributes.IsNotDeadCodeAttribute".AllowNoMatch()) &&
( // types used don't need to be tagged with IsNotDeadCodeAttribute!
t.TypesUsingMe.Count(t1 =>
!t.NestedTypes.Contains(t1) &&
!t1.HasAttribute("NDepend.Attributes.IsNotDeadCodeAttribute".AllowNoMatch()) ) > 0 ||
// Static types that define only const fields cannot be seen as used in IL code.
// They don't need to be tagged with IsNotDeadCodeAttribute.
(t.IsStatic && t.NbMethods == 0 && !t.Fields.Where(f => !f.IsLiteral).Any())
)
select t
// Get methods that do a wrong usage of IsNotDeadCodeAttribute
let methods = from m in Application.Methods where
m.HasAttribute("NDepend.Attributes.IsNotDeadCodeAttribute".AllowNoMatch()) &&
m.MethodsCallingMe.Count(m1 => !m1.HasAttribute("NDepend.Attributes.IsNotDeadCodeAttribute".AllowNoMatch())) > 0
select m
// Get fields that do a wrong usage of IsNotDeadCodeAttribute
let fields = from f in Application.Fields where
f.HasAttribute("NDepend.Attributes.IsNotDeadCodeAttribute".AllowNoMatch()) &&
f.MethodsUsingMe.Count(m1 => !m1.HasAttribute("NDepend.Attributes.IsNotDeadCodeAttribute".AllowNoMatch())) > 0
select f
from member in types.Cast<IMember>().Concat(methods).Concat(fields)
select new {
member,
Debt = 4.ToMinutes().ToDebt(),
Severity = Severity.Medium
}
//<Description>
// The attribute **NDepend.Attributes.IsNotDeadCodeAttribute**
// is defined in *NDepend.API.dll*. This attribute is used
// to mean that a code element is not used directly, but is used
// somehow, like through reflection.
//
// This attribute is used in the dead code rules,
// *Potentially dead Types*, *Potentially dead Methods*
// and *Potentially dead Fields*.
// If you don't want to link *NDepend.API.dll*, you can use
// your own *IsNotDeadCodeAttribute* and adapt the source code of
// this rule, and the source code of the *dead code* rules.
//
// In this context, this code rule matches code elements
// (types, methods, fields) that are tagged with this attribute,
// but still used directly somewhere in the code.
//</Description>
//<HowToFix>
// Just remove *IsNotDeadCodeAttribute* tagging of
// types, methods and fields matched by this rule
// because this tag is not useful anymore.
//</HowToFix>]]></Query>
</Group>
<Group Name="Visibility" Active="True" ShownInReport="False">
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Methods that could have a lower visibility</Name>
warnif count > 0 from m in JustMyCode.Methods where
m.Visibility != m.OptimalVisibility &&
!m.HasAttribute("NDepend.Attributes.CannotDecreaseVisibilityAttribute".AllowNoMatch()) &&
!m.HasAttribute("NDepend.Attributes.IsNotDeadCodeAttribute".AllowNoMatch()) &&
// If you don't want to link NDepend.API.dll, you can use your own attributes
// and adapt the source code of this rule.
// methods of serialized type must remain public.
!m.ParentType.HasAttribute("System.Runtime.Serialization.DataContractAttribute".AllowNoMatch()) &&
!m.ParentType.HasAttribute("System.Xml.Serialization.XmlRootAttribute".AllowNoMatch()) &&
// Eliminate public methods visible outside of their assembly
// because the rule cannot know if the developer left the method public
// intentionally or not.
!m.IsPubliclyVisible &&
// Eliminate default constructor from the result.
// Whatever the visibility of the declaring class,
// default constructors are public and introduce noise
// in the current rule.
!( m.IsConstructor && m.IsPublic && m.NbParameters == 0) &&
// Don't decrease the visibility of Main() methods.
!m.IsEntryPoint
select new {
m,
m.Visibility ,
CouldBeDeclared = m.OptimalVisibility,
m.MethodsCallingMe,
Debt = 30.ToSeconds().ToDebt(), // It is fast to change the method visibility
Severity = Severity.Medium
}
//<Description>
// This rule warns about methods that can be declared with a lower visibility
// without provoking any compilation error.
// For example *private* is a visibility lower than *internal*
// which is lower than *public*.
//
// **Narrowing visibility** is a good practice because doing so **promotes encapsulation**.
// The scope from which methods can be called is then reduced to a minimum.
//
// By default, this rule doesn't match publicly visible methods that could have a
// lower visibility because it cannot know if the developer left the method public
// intentionally or not. Public methods matched are declared in non-public types.
//
// Notice that methods tagged with one of the attribute
// *NDepend.Attributes.CannotDecreaseVisibilityAttribute* or
// *NDepend.Attributes.IsNotDeadCodeAttribute*, found in *NDepend.API.dll*
// are not matched. If you don't want to link *NDepend.API.dll* but still
// wish to rely on this facility, you can declare these attributes in your code.
//</Description>
//<HowToFix>
// Declare each matched method with the specified *optimal visibility*
// in the *CouldBeDeclared* rule result column.
//
// By default, this rule matches *public methods*. If you are publishing an API
// many public methods matched should remain public. In such situation,
// you can opt for the *coarse solution* to this problem by adding in the
// rule source code *&& !m.IsPubliclyVisible* or you can prefer the
// *finer solution* by tagging each concerned method with
// *CannotDecreaseVisibilityAttribute*.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Types that could have a lower visibility</Name>
warnif count > 0 from t in JustMyCode.Types where
t.Visibility != t.OptimalVisibility &&
// If you don't want to link NDepend.API.dll, you can use your own attributes
// and adapt the source code of this rule.
!t.HasAttribute("NDepend.Attributes.CannotDecreaseVisibilityAttribute".AllowNoMatch()) &&
!t.HasAttribute("NDepend.Attributes.IsNotDeadCodeAttribute".AllowNoMatch()) &&
// XML serialized type must remain public.
!t.HasAttribute("System.Xml.Serialization.XmlRootAttribute".AllowNoMatch()) &&
// Eliminate public types visible outside of their assembly
// because the rule cannot know if the developer left the type public
// intentionally or not.
!t.IsPubliclyVisible &&
// Static types that define only const fields cannot be seen as used in IL code.
// They don't have to be tagged with CannotDecreaseVisibilityAttribute.
!( t.IsStatic &&
!t.Methods.Any(m => !m.IsClassConstructor) &&
!t.Fields.Any(f => !f.IsLiteral && !(f.IsStatic && f.IsInitOnly))) &&
// A type used by an interface that has the same visibility
// cannot have its visibility decreased, else a compilation error occurs!
!t.TypesUsingMe.Any(tUser =>
tUser.IsInterface &&
tUser.Visibility == t.Visibility) &&
// Don't change the visibility of a type that contain an entry point method.
!t.Methods.Any(m =>m.IsEntryPoint)
select new {
t,
t.Visibility ,
CouldBeDeclared = t.OptimalVisibility,
t.TypesUsingMe,
Debt = 30.ToSeconds().ToDebt(), // It is fast to change the method visibility
Severity = Severity.Medium
}
//<Description>
// This rule warns about types that can be declared with a lower visibility
// without provoking any compilation error.
// For example *private* is a visibility lower than *internal*
// which is lower than *public*.
//
// **Narrowing visibility** is a good practice because doing so **promotes encapsulation**.
// The scope from which types can be consumed is then reduced to a minimum.
//
// By default, this rule doesn't match publicly visible types that could have
// a lower visibility because it cannot know if the developer left the type public
// intentionally or not. Public types matched are nested in non-public types.
//
// Notice that types tagged with one of the attribute
// *NDepend.Attributes.CannotDecreaseVisibilityAttribute* or
// *NDepend.Attributes.IsNotDeadCodeAttribute*, found in *NDepend.API.dll*
// are not matched. If you don't want to link *NDepend.API.dll* but still
// wish to rely on this facility, you can declare these attributes in your code.
//</Description>
//<HowToFix>
// Declare each matched type with the specified *optimal visibility*
// in the *CouldBeDeclared* rule result column.
//
// By default, this rule matches *public types*. If you are publishing an API
// many public types matched should remain public. In such situation,
// you can opt for the *coarse solution* to this problem by adding in the
// rule source code *&& !m.IsPubliclyVisible* or you can prefer the
// *finer solution* by tagging each concerned type with
// *CannotDecreaseVisibilityAttribute*.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Fields that could have a lower visibility</Name>
warnif count > 0 from f in JustMyCode.Fields where
f.Visibility != f.OptimalVisibility &&
!f.HasAttribute("NDepend.Attributes.CannotDecreaseVisibilityAttribute".AllowNoMatch()) &&
!f.HasAttribute("NDepend.Attributes.IsNotDeadCodeAttribute".AllowNoMatch()) &&
// If you don't want to link NDepend.API.dll, you can use your own attributes
// and adapt the source code of this rule.
// XML serialized fields must remain public.
!f.HasAttribute("System.Xml.Serialization.XmlElementAttribute".AllowNoMatch()) &&
!f.HasAttribute("System.Xml.Serialization.XmlAttributeAttribute".AllowNoMatch()) &&
!f.HasAttribute("System.Xml.Serialization.XmlArrayAttribute".AllowNoMatch()) &&
!f.HasAttribute("System.Xml.Serialization.XmlArrayItemAttribute".AllowNoMatch()) &&
// Don't check for serialized fields visibility
!f.HasAttribute("System.Runtime.Serialization.DataMemberAttribute".AllowNoMatch()) &&
// Eliminate public fields visible outside of their assembly
// because the rule cannot know if the developer left the field public
// intentionally or not.
!f.IsPubliclyVisible
select new {
f,
f.Visibility ,
CouldBeDeclared = f.OptimalVisibility,
f.MethodsUsingMe,
Debt = 30.ToSeconds().ToDebt(), // It is fast to change the field visibility
Severity = Severity.Medium
}
//<Description>
// This rule warns about fields that can be declared with a lower visibility
// without provoking any compilation error.
// For example *private* is a visibility lower than *internal*
// which is lower than *public*.
//
// **Narrowing visibility** is a good practice because doing so **promotes encapsulation**.
// The scope from which fields can be consumed is then reduced to a minimum.
//
// By default, this rule doesn't match publicly visible fields that could have a
// lower visibility because it cannot know if the developer left the field public
// intentionally or not. Public fields matched are declared in non-public types.
//
// Notice that fields tagged with one of the attribute
// *NDepend.Attributes.CannotDecreaseVisibilityAttribute* or
// *NDepend.Attributes.IsNotDeadCodeAttribute*, found in *NDepend.API.dll*
// are not matched. If you don't want to link *NDepend.API.dll* but still
// wish to rely on this facility, you can declare these attributes in your code.
//</Description>
//<HowToFix>
// Declare each matched field with the specified *optimal visibility*
// in the *CouldBeDeclared* rule result column.
//
// By default, this rule matches *public fields*. If you are publishing an API
// some public fields matched should remain public. In such situation,
// you can opt for the *coarse solution* to this problem by adding in the
// rule source code *&& !m.IsPubliclyVisible* or you can prefer the
// *finer solution* by tagging eah concerned field with
// *CannotDecreaseVisibilityAttribute*.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Types that could be declared as private, nested in a parent type</Name>
warnif count > 0
from t in JustMyCode.Types
where !t.IsGeneratedByCompiler &&
!t.IsNested &&
!t.IsPubliclyVisible &&
!t.IsEnumeration &&
// Only one type user…
t.TypesUsingMe.Count() == 1
let couldBeNestedIn = t.TypesUsingMe.Single()
where !couldBeNestedIn.IsGeneratedByCompiler &&
// …declared in the same namespace
couldBeNestedIn.ParentNamespace == t.ParentNamespace
select new {
t,
couldBeNestedIn,
Debt = 3.ToMinutes().ToDebt(), // It is fast to nest a type into another one
Severity = Severity.Low // This rule proposes advices, not potential problems
}
//<Description>
// This rule matches types that can be potentially
// *nested* and declared *private* into another type.
//
// The conditions for a type to be potentially nested
// into a *parent type* are:
//
// • the *parent type* is the only type consuming it,
//
// • the type and the *parent type* are declared in the same namespace.
//
// Declaring a type as private into a parent type **promotes encapsulation**.
// The scope from which the type can be consumed is then reduced to a minimum.
//</Description>
//<HowToFix>
// Nest each matched *type* into the specified *parent type* and
// declare it as private.
//
// However *nested private types* are hardly testable. Hence this rule
// might not be applied to types consumed directly by tests.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Avoid publicly visible constant fields</Name>
warnif count > 0
from f in JustMyCode.Fields
where f.IsLiteral &&
f.IsPubliclyVisible &&
!f.IsEnumValue
select new {
f,
Debt = 30.ToSeconds().ToDebt(), // It is fast to update field declaration
Severity = Severity.Medium
}
//<Description>
// This rule warns about constant fields that are visible outside their
// parent assembly. Such field, when used from outside its parent assembly,
// has its constant value *hard-coded* into the client assembly.
// Hence, when changing the field's value, it is *mandatory* to recompile
// all assemblies that consume the field, else the program will run
// with different constant values in-memory. Certainly in such situation
// bugs are lurking.
//</Description>
//<HowToFix>
// Declare matched fields as **static readonly** instead of **constant**.
// This way, the field value is *safely changeable* without the need to
// recompile client assemblies.
//
// Notice that enumeration value fields suffer from the same *potential
// pitfall*. But enumeration values cannot be declared as
// *static readonly* hence the rule comes with the condition
// **&& !f.IsEnumValue** to avoid matching these. Unless you decide
// to banish public enumerations, just let the rule *as is*.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Fields should be declared as private</Name>
warnif count > 0 from f in JustMyCode.Fields where
!f.IsPrivate &&
// These conditions filter cases where fields
// doesn't represent state that should be encapsulated.
!f.IsGeneratedByCompiler &&
!f.IsSpecialName &&
!f.IsInitOnly &&
!f.IsLiteral &&
!f.IsEnumValue &&
!f.HasAttribute("System.Xml.Serialization.XmlAttributeAttribute".AllowNoMatch()) &&
!f.HasAttribute("System.Runtime.Serialization.DataMemberAttribute".AllowNoMatch())
// A non-private field assigned from outside its class,
// usually leads to complicated field state management.
let outsideMethodsAssigningMe =
f.MethodsAssigningMe.Where(m => m.ParentType != f.ParentType)
select new {
f,
f.Visibility,
outsideMethodsAssigningMe,
Debt = (60+20*outsideMethodsAssigningMe.Count()).ToSeconds().ToDebt(),
// The cost to leave such issue unfixed is higher if the field is publicly visible!
AnnualInterest = Severity.Medium.AnnualInterestThreshold() * (f.IsPubliclyVisible ? 3 : 1)
}
//<Description>
// This rule matches **non-private and mutable fields**.
// *Mutable* means that the field value can be modified.
// Typically mutable fields are *non-constant*,
// *non-readonly* fields.
//
// Fields should be considered as **implementation details**
// and as a consequence they should be declared as private.
//
// If something goes wrong with a *non-private field*,
// the culprit can be anywhere, and so in order to track down
// the bug, you may have to look at quite a lot of code.
//
// A private field, by contrast, can only be assigned from
// inside the same class, so if something goes wrong with that,
// there is usually only one source file to look at.
//
// Issues of this rule are fast to get fixed, and they have
// a debt proportional to the number of methods assigning
// the field.
//</Description>
//<HowToFix>
// Declare a matched mutable field as *private*, or declare it
// as *readonly*.
//
// If code outside the type needs to access the field
// you can encapsulate the field accesses in a read-write property.
// At least with a read-write property you can set a debug breakpoint
// on the property setter, which makes easier to track write-accesses
// in case of problem.
//</HowToFix>
]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[//<Name>Constructors of abstract classes should be declared as protected or private</Name>
warnif count > 0
from t in Application.Types where
t.IsClass &&
t.IsAbstract
let ctors = t.Constructors.Where(c => !c.IsProtected && !c.IsPrivate)
where ctors.Count() > 0
select new {
t,
ctors,
Debt = 30.ToSeconds().ToDebt(),
Severity = Severity.Medium
}
//<Description>
// Constructors of abstract classes can only be called from derived
// classes.
//
// Because a public constructor is creating instances of its class,
// and it is forbidden to create instances of an *abstract* type,
// as a consequence an abstract class with a public constructor is
// wrong design.
//</Description>
//<HowToFix>
// To fix a violation of this rule,
// either declare the constructor as *protected*,
// or do not declare the type as *abstract*.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[//<Name>Avoid public methods not publicly visible</Name>
warnif count > 0
from m in JustMyCode.Methods where
!m.IsPubliclyVisible && m.IsPublic &&
// Eliminate virtual methods
!m.IsVirtual &&
// Eliminate interface and delegate types
!m.ParentType.IsInterface &&
!m.ParentType.IsDelegate &&
// Eliminate default constructors
!(m.IsConstructor && m.NbParameters == 0) &&
// Eliminate operators that must be declared public
!m.IsOperator &&
// Eliminate methods generated by compiler
!m.IsGeneratedByCompiler
let calledOutsideParentType =
m.MethodsCallingMe.FirstOrDefault(mCaller => mCaller.ParentType != m.ParentType) != null
select new {
m,
parentTypeVisibility = m.ParentType.Visibility,
declareMethodAs = (Visibility) (calledOutsideParentType ? Visibility.Internal : Visibility.Private),
methodsCaller = m.MethodsCallingMe,
Debt = 30.ToSeconds().ToDebt(),
Severity = Severity.Low
}
//<Description>
// This rule warns about methods declared as *public*
// whose parent type is not declared as *public*.
//
// In such situation *public* means, *can be accessed
// from anywhere my parent type is visible*. Some
// developers think this is an elegant language construct,
// some others find it misleading.
//
// This rule can be deactivated if you don't agree with it.
// Read the whole debate here:
// http://ericlippert.com/2014/09/15/internal-or-public/
//
// By default issues of this rule have an **Low** severity
// because they reflect more an advice than a problem.
//</Description>
//<HowToFix>
// Declare the method as *internal* if it is used outside of
// its type, else declare it as *private*.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Event handler methods should be declared private</Name>
warnif count > 0
from m in Application.Methods where
!m.IsPrivate &&
!m.IsGeneratedByCompiler &&
// A method is considered as an event handler if…
m.NbParameters == 2 && // … it has two parameters …
m.Name.Contains("Object") && // … of types Object …
m.Name.Contains("EventArgs") && // … and EventArgs
// Discard special cases
!m.ParentType.IsDelegate &&
!m.IsGeneratedByCompiler
select new {
m,
m.Visibility,
Debt = 2.ToMinutes().ToDebt(),
Severity = Severity.Medium
}
//<Description>
// Think of a event handler like for example *OnClickButton()*.
// Typically such method must be declared as private
// and shouldn't be invoked in other context than event firing.
//</Description>
//<HowToFix>
// If you have the need that event handler method should be called
// from another class, then find a code structure that more
// closely matches the concept of what you're trying to do.
// Certainly you don't want the other class to click a button; you
// want the other class to do something that clicking a button
// also do.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Wrong usage of CannotDecreaseVisibilityAttribute</Name>
warnif count > 0
let tAttr = Types.WithFullName("NDepend.Attributes.CannotDecreaseVisibilityAttribute").FirstOrDefault()
where tAttr != null
// Get types that do a wrong usage of CannotDecreaseVisibilityAttribute
let types = from t in Application.Types where
t.HasAttribute("NDepend.Attributes.CannotDecreaseVisibilityAttribute".AllowNoMatch()) &&
( t.Visibility == t.OptimalVisibility ||
// Static types that define only const fields cannot be seen as used in IL code.
// They don't need to be tagged with CannotDecreaseVisibilityAttribute.
(t.IsStatic && t.NbMethods == 0 && !t.Fields.Any(f => !f.IsLiteral))
)
select t
// Get methods that do a wrong usage of CannotDecreaseVisibilityAttribute
let methods = from m in Application.Methods where
m.HasAttribute("NDepend.Attributes.CannotDecreaseVisibilityAttribute".AllowNoMatch()) &&
m.Visibility == m.OptimalVisibility
select m
// Get fields that do a wrong usage of CannotDecreaseVisibilityAttribute
let fields = from f in Application.Fields where
f.HasAttribute("NDepend.Attributes.CannotDecreaseVisibilityAttribute".AllowNoMatch()) &&
f.Visibility == f.OptimalVisibility
select f
from member in types.Cast<IMember>().Concat(methods).Concat(fields)
select new {
member,
Debt = 30.ToSeconds().ToDebt(),
Severity = Severity.Low
}
//<Description>
// The attribute **NDepend.Attributes.CannotDecreaseVisibilityAttribute**
// is defined in *NDepend.API.dll*. If you don't want to reference
// *NDepend.API.dll* you can declare it in your code.
//
// Usage of this attribute means that a code element visibility is not
// optimal (it can be lowered like for example from *public* to *internal*)
// but shouldn’t be modified anyway. Typical reasons to do so include:
//
// • Public code elements consumed through reflection, through a mocking
// framework, through XML or binary serialization, through designer,
// COM interfaces…
//
// • Non-private code element invoked by test code, that would be difficult
// to reach from test if it was declared as *private*.
//
// In such situation *CannotDecreaseVisibilityAttribute* is used to avoid
// that default rules about not-optimal visibility warn. Using this
// attribute can be seen as an extra burden, but it can also be seen as
// an opportunity to express in code: **Don't change the visibility else
// something will be broken**
//
// In this context, this code rule matches code elements
// (types, methods, fields) that are tagged with this attribute,
// but still already have an optimal visibility.
//</Description>
//<HowToFix>
// Just remove *CannotDecreaseVisibilityAttribute* tagging of
// types, methods and fields matched by this rule
// because this tag is not useful anymore.
//
// By default issues of this rule have an **Low** severity
// because they reflect more an advice than a problem.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Methods that should be declared as 'public' in C#, 'Public' in VB.NET</Name>
from m in Application.Methods where
m.ShouldBePublic
let usedInAssemblies = m.MethodsCallingMe.ParentAssemblies().Except(m.ParentAssembly)
select new {
m,
m.ParentAssembly,
usedInAssemblies,
m.MethodsCallingMe
}
//<Description>
// This code query lists methods that *should* be declared
// as *public*. Such method is actually declared as *internal*
// and is consumed from outside its parent assembly
// thanks to the attribute
// *System.Runtime.CompilerServices.InternalsVisibleToAttribute*.
//
// This query relies on the property
// *NDepend.CodeModel.IMember.ShouldBePublic*
// http://www.ndepend.com/api/webframe.html?NDepend.API~NDepend.CodeModel.IMember~ShouldBePublic.html
//
// This is just a code query, it is not intended to advise
// you to declare the method as *public*, but to inform you
// that the code actually relies on the peculiar behavior
// of the attribute *InternalsVisibleToAttribute*.
//</Description>]]></Query>
</Group>
<Group Name="Immutability" Active="True" ShownInReport="False">
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Fields should be marked as ReadOnly when possible</Name>
warnif count > 0
from f in JustMyCode.Fields where
f.IsImmutable &&
!f.IsInitOnly && // The condition IsInitOnly matches fields that
// are marked with the C# readonly keyword
// (ReadOnly in VB.NET).
!f.IsGeneratedByCompiler &&
!f.IsEventDelegateObject &&
// Don't warn if a method using the field is also calling a method that has 'ref' and 'out' parameters.
// This could lead to false positive. A field used in a 'ref' or 'out' parameter cannot be set as read-only.
f.MethodsUsingMe.SelectMany(m => m.MethodsCalled).FirstOrDefault(m => m.Name.Contains("&")) == null
select new {
f,
f.MethodsReadingMeButNotAssigningMe,
f.MethodsAssigningMe,
Debt = 5.ToMinutes().ToDebt(),
Severity = Severity.Medium
}
//<Description>
// This rule warns about instance and static fields that
// can be declared as **readonly**.
//
// This source code of this rule is based on the conditon
// *IField.IsImmutable*.
// http://www.ndepend.com/api/webframe.html?NDepend.API~NDepend.CodeModel.IField~IsImmutable.html
//
// A field that matches the condition *IsImmutable*
// is a field that is assigned only by constructors
// of its class.
//
// For an *instance field*, this means its value
// will remain constant through the lifetime
// of the object.
//
// For a *static field*, this means its value will
// remain constant through the lifetime of the
// program.
//</Description>
//<HowToFix>
// Declare the field with the C# *readonly* keyword
// (*ReadOnly* in VB.NET). This way the intention
// that the field value shouldn't change is made
// explicit.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="True"><![CDATA[// <Name>Avoid non-readonly static fields</Name>
warnif count > 0
from f in Application.Fields
where f.IsStatic &&
!f.IsEnumValue &&
!f.IsGeneratedByCompiler &&
!f.IsLiteral &&
!f.IsInitOnly
let methodAssigningField = f.MethodsAssigningMe
select new {
f,
methodAssigningField,
Debt = (2+8*methodAssigningField.Count()).ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// This rule warns about static fields that are not
// declared as read-only.
//
// In *Object-Oriented-Programming* the natural artifact
// to hold states that can be modified is **instance fields**.
// Such mutable static fields create *confusion* about
// the expected state at runtime and impairs the code
// testability since the same mutable state is re-used for
// each test.
//
// More discussion on the topic can be found here:
// http://codebetter.com/patricksmacchia/2011/05/04/back-to-basics-usage-of-static-members/
//</Description>
//<HowToFix>
// If the *static* field is just assigned once in the program
// lifetime, make sure to declare it as *readonly* and assign
// it inline, or from the static constructor.
//
// Else if methods other than the static constructor need to
// assign the state hold by the static field, refactoring must
// occur to ensure that this state is hold through an instance
// field.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Avoid static fields with a mutable field type</Name>
warnif count > 0
from f in Application.Fields
where f.IsStatic &&
!f.IsEnumValue &&
!f.IsGeneratedByCompiler &&
!f.IsLiteral
let fieldType = f.FieldType
where fieldType != null &&
!fieldType.IsThirdParty &&
!fieldType.IsInterface &&
!fieldType.IsImmutable
select new {
f,
mutableFieldType = fieldType ,
isFieldImmutable = f.IsImmutable ? "Immutable" : "Mutable",
isFieldReadOnly = f.IsInitOnly ? "ReadOnly" : "Not ReadOnly",
Debt = 10.ToMinutes().ToDebt(),
Severity = Severity.Medium
}
//<Description>
// This rule warns about static fields whose field type
// is mutable. In such case the static field is
// holding a state that can be modified.
//
// In *Object-Oriented-Programming* the natural artifact
// to hold states that can be modified is **instance fields**.
// Hence such static fields create *confusion* about
// the expected state at runtime.
//
// More discussion on the topic can be found here:
// http://codebetter.com/patricksmacchia/2011/05/04/back-to-basics-usage-of-static-members/
//</Description>
//<HowToFix>
// To fix violations of this rule, make sure to
// hold mutable states through objects that are passed
// **explicitly** everywhere they need to be consumed, in
// opposition to mutable object hold by a static field that
// makes it modifiable from a bit everywhere in the program.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Structures should be immutable</Name>
warnif count > 0 from t in JustMyCode.Types where
t.IsStructure &&
!t.IsImmutable
let mutableFields = t.Fields.Where(f => !f.IsImmutable)
select new {
t,
t.NbLinesOfCode,
mutableFields,
Debt = (3+2*mutableFields.Count()).ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// An object is immutable if its state doesn’t change once the
// object has been created. Consequently, a structure or a class
// is immutable if its instances fields are only assigned inline
// or from constructor(s).
//
// But for structure it is a bit different. **Structures are value
// types** which means instances of structures are copied when they are
// passed around (like through a method argument).
//
// So if you change a copy you are changing only that copy, not the
// original and not any other copies which might be around. Such
// situation is very different than what happen with instances of
// classes. Hence developers are not used to work with modified values
// and doing so introduces *confusion* and is *error-prone*.
//</Description>
//<HowToFix>
// Make sure matched structures are immutable. This way, all
// automatic copies of an original instance, resulting from being
// *passed by value* will hold the same values and there will be
// no surprises.
//
// If your structure is immutable then if you want to change
// a value, you have to consciously do it by creating a new instance
// of the structure with the modified data.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Property Getters should be immutable</Name>
warnif count > 0 from m in Application.Methods where
!m.IsGeneratedByCompiler &&
m.IsPropertyGetter &&
( ( !m.IsStatic && m.ChangesObjectState) ||
( m.IsStatic && m.ChangesTypeState) )
let setterSimpleName = "set_" + m.SimpleName.Substring(4,m.SimpleName.Length-4)
// Don't count field that are assigned only by the property getter and the related property setter.
let fieldsAssigned = m.FieldsAssigned.Where(f =>
f.MethodsAssigningMe.Any(m1 => m1 != m &&
!(m1.IsPropertySetter && m1.SimpleName == setterSimpleName)))
where fieldsAssigned.Any()
let otherMethodsAssigningSameFields = fieldsAssigned.SelectMany(f => f.MethodsAssigningMe.Where(m1 => m1 != m))
select new {
m,
m.NbLinesOfCode,
fieldsAssigned,
otherMethodsAssigningSameFields,
Debt = (2 + 5*fieldsAssigned.Count() + 5*otherMethodsAssigningSameFields.Count() ).ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// It is not expected that a state gets modified when
// accessing a property getter. Hence doing so create
// confusion and property getters should be pure methods,
// they shouldn't assign any field.
//
// This rule doesn't match property getters that assign a field
// not assigned by any other methods than the getter itself
// and the corresponding property setter. Hence this rule avoids
// matching *lazy initialization at first access* of a state.
// In such situation the getter assigns a field at first access
// and from the client point of view, lazy initialization
// is an invisible implementation detail.
//</Description>
//<HowToFix>
// Make sure that matched property getters don't assign any
// field.
//
// The estimated Debt, which means the effort to fix such issue,
// is equal to 2 minutes plus 5 minutes per field assigned and
// 5 minutes per other method assigning such field.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>A field must not be assigned from outside its parent hierarchy types</Name>
warnif count > 0
from f in JustMyCode.Fields.Where(f =>
(f.IsInternal || f.IsPublic) &&
!f.IsGeneratedByCompiler &&
!f.IsImmutable &&
!f.IsEnumValue)
let methodsAssignerOutsideOfMyType = f.MethodsAssigningMe.Where(
m =>!m.IsGeneratedByCompiler &&
m.ParentType != f.ParentType &&
!m.ParentType.DeriveFrom(f.ParentType) )
where methodsAssignerOutsideOfMyType.Any()
select new {
f,
methodsAssignerOutsideOfMyType,
Debt = (5*methodsAssignerOutsideOfMyType.Count()).ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// This rule is related to the rule *Fields should be declared as
// private*. It matches any **public or internal, mutable field**
// that is assigned from outside its parent class and subclasses.
//
// Fields should be considered as **implementation details**
// and as a consequence they should be declared as private.
//
// If something goes wrong with a *non-private field*,
// the culprit can be anywhere, and so in order to track down
// the bug, you may have to look at quite a lot of code.
//
// A private field, by contrast, can only be assigned from
// inside the same class, so if something goes wrong with that,
// there is usually only one source file to look at.
//</Description>
//<HowToFix>
// Matched fields must be declared as *protected* and even better
// as *private*.
//
// Alternatively, if the field can reference immutable states,
// it can remain visible from the outside, but then must be
// declared as *readonly*.
//
// The estimated Debt, which means the effort to fix such issue,
// is equal to 5 minutes per method outside the parent hierarchy
// that assigns the matched field.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Don't assign a field from many methods</Name>
warnif count > 0
from f in JustMyCode.Fields where
!f.IsEnumValue &&
!f.IsImmutable &&
!f.IsInitOnly &&
!f.IsGeneratedByCompiler &&
!f.IsEventDelegateObject
let methodsAssigningMe = f.MethodsAssigningMe.Where(m => !m.IsConstructor)
// The threshold 4 is arbitrary and it should avoid matching too many fields.
// Threshold is even lower for static fields because this reveals situations even more complex.
where methodsAssigningMe.Count() >= (!f.IsStatic ? 4 : 2)
select new {
f,
methodsAssigningMe,
f.MethodsReadingMeButNotAssigningMe,
f.MethodsUsingMe,
Debt = (4+(f.IsStatic ? 10 : 5)).ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// A field assigned from many methods is a symptom of **bug-prone code**.
// Notice that:
//
// • For an instance field, constructor(s) of its class that assign the field are not counted.
//
// • For a static field, the class constructor that assigns the field is not counted.
//
// The default threshold for *instance fields* is equal to *4 or more than 4 methods
// assigning the instance field*. Such situation makes harder to anticipate the
// field state at runtime. The code is then complicated to read, hard to debug
// and hard to maintain. Hard-to-solve bugs due to corrupted state are often the
// consequence of fields *anarchically assigned*.
//
// The situation is even more complicated if the field is *static*.
// Indeed, such situation potentially involves global random accesses from
// various parts of the application. This is why this rule provides a lower
// threshold equals to *2 or more than 2 methods assigning the static field*.
//
// If the object containing such field is meant to be used from multiple threads,
// there are **alarming chances** that the code is unmaintainable and bugged.
// When multiple threads are involved, the rule of thumb is to use immutable objects.
//
// If the field type is a reference type (interfaces, classes, strings, delegates)
// corrupted state might result in a *NullReferenceException*.
// If the field type is a value type (number, boolean, structure)
// corrupted state might result in wrong result not even signaled by an exception
// thrown.
//</Description>
//<HowToFix>
// There is no straight advice to refactor the number of methods responsible
// for assigning a field. Sometime the situation is simple enough, like when
// a field that hold an indentation state is assigned by many writer methods.
// Such situation only requires to define two methods *IndentPlus()/IndentMinus()*
// that assign the field, called from all writers methods.
//
// Sometime the solution involves rethinking and then rewriting
// a complex algorithm. Such field can sometime become just a variable accessed
// locally by a method or a *closure*. Sometime, just rethinking the life-time
// and the role of the parent object allows the field to become immutable
// (i.e assigned only by the constructor).
//
// The estimated Debt, which means the effort to fix such issue,
// is equal to 4 minutes plus 5 minutes per method assigning the instance field
// or 10 minutes per method assigning the static field.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Do not declare read only mutable reference types</Name>
warnif count > 0
from f in JustMyCode.Fields where
f.IsInitOnly &&
!f.ParentType.IsPrivate &&
!f.IsPrivate &&
f.FieldType != null &&
f.FieldType.IsClass &&
!f.FieldType.IsThirdParty &&
!f.FieldType.IsImmutable
select new {
f,
f.FieldType,
FieldVisibility = f.Visibility,
Debt = 3.ToMinutes().ToDebt(),
Severity = Severity.Low
}
//<Description>
// This rule is violated when a *public* or *internal*
// type contains a *public* or *internal* read-only field
// whose field type is a mutable reference type.
//
// This situation gives the false impression that the
// value can't change, when actually it's only the field
// value that can't change, but the object state
// can still change.
//</Description>
//<HowToFix>
// To fix a violation of this rule,
// replace the field type with an immutable type,
// or declare the field as *private*.
//
// By default issues of this rule have an **Low** severity
// because they reflect more an advice than a problem.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="False" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Array fields should not be read only</Name>
warnif count > 0
from f in Application.Fields where
f.IsInitOnly &&
f.IsPubliclyVisible &&
f.FieldType != null &&
f.FieldType.FullName == "System.Array"
select new {
f,
FieldVisibility = f.Visibility,
Debt = 3.ToMinutes().ToDebt(),
Severity = Severity.Low
}
//<Description>
// This rule is violated when a publicly visible field
// that holds an array, is declared read-only.
//
// This situation represents a *security vulnerability*.
// Because the field is read-only it cannot be changed to refer
// to a different array. However, the elements of the array
// that are stored in a read-only field can be changed.
// Code that makes decisions or performs operations that are
// based on the elements of a read-only array that can be publicly
// accessed might contain an exploitable security vulnerability.
//</Description>
//<HowToFix>
// To fix the security vulnerability that is identified by
// this rule do not rely on the contents of a read-only array
// that can be publicly accessed. It is strongly recommended
// that you use one of the following procedures:
//
// • Replace the array with a strongly typed collection
// that cannot be changed. See for example:
// *System.Collections.Generic.IReadOnlyList<T>* ;
// *System.Collections.Generic.IReadOnlyCollection<T>* ;
// *System.Collections.ReadOnlyCollectionBase*
//
// • Or replace the public field with a method that returns a clone
// of a private array. Because your code does not rely on
// the clone, there is no danger if the elements are modified.
//
// By default issues of this rule have an **Low** severity
// because they reflect more an advice than a problem.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Types tagged with ImmutableAttribute must be immutable</Name>
warnif count > 0
from t in Application.Types where
t.HasAttribute ("NDepend.Attributes.ImmutableAttribute".AllowNoMatch()) &&
!t.IsImmutable
let culpritFields = t.Fields.Where(f => !f.IsStatic && !f.IsImmutable)
select new {
t,
culpritFields,
Debt = (5+10*culpritFields.Count()).ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// An object is immutable if its state doesn’t change once the
// object has been created. Consequently, a structure or a class
// is immutable if its instances fields are only assigned inline
// or from constructor(s).
//
// An attribute **NDepend.Attributes.ImmutableAttribute** can be
// used to express in code that a type is immutable. In such
// situation, the present code rule checks continuously that the
// type remains immutable whatever the modification done.
//
// This rule warns when a type that is tagged with
// *ImmutableAttribute* is actually not immutable anymore.
//
// Notice that *FullCoveredAttribute* is defined in *NDepend.API.dll*
// and if you don't want to link this assembly, you can create your
// own *FullCoveredAttribute* and adapt the rule.
//</Description>
//<HowToFix>
// First understand which modification broke the type immutability.
// The list of *culpritFields* provided in this rule result can help.
// Then try to refactor the type to bring it back to immutability.
//
// The estimated Debt, which means the effort to fix such issue,
// is equal to 5 minutes plus 10 minutes per culprit field.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="False" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Types immutable should be tagged with ImmutableAttribute</Name>
// warnif count > 0 <-- not a code rule per default
from t in Application.Types where
!t.HasAttribute ("NDepend.Attributes.ImmutableAttribute".AllowNoMatch()) &&
t.IsImmutable
select new {
t,
t.NbLinesOfCode,
Debt = 3.ToMinutes().ToDebt(),
Severity = Severity.Low
}
//<Description>
// An object is immutable if its state doesn’t change once the
// object has been created. Consequently, a structure or a class
// is immutable if its instances fields are only assigned inline
// or from constructor(s).
//
// This code query lists immutable type that are not tagged with
// an **ImmutableAttribute**. By using such attribute, you can express
// in source code the intention that a class is immutable, and
// should remain immutable in the future. Benefits of using
// an **ImmutableAttribute** are twofold:
//
// • Not only the intention is expressed in source code,
//
// • but it is also continuously checked by the rule
// *Types tagged with ImmutableAttribute must be immutable*.
//</Description>
//<HowToFix>
// Just tag types matched by this code query with **ImmutableAttribute**
// that can be found in *NDepend.API.dll*,
// or by an attribute of yours defined in your own code
// (in which case this code query must be adapted).
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Methods tagged with PureAttribute must be pure</Name>
warnif count > 0
from m in Application.Methods where
( m.HasAttribute ("NDepend.Attributes.PureAttribute".AllowNoMatch()) ||
m.HasAttribute ("System.Diagnostics.Contract.PureAttribute".AllowNoMatch()) ) &&
( m.ChangesObjectState || m.ChangesTypeState ) &&
m.NbLinesOfCode > 0
let fieldsAssigned = m.FieldsAssigned
select new {
m,
m.NbLinesOfCode,
fieldsAssigned,
Debt = 15.ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// A method is pure if its execution doesn’t change
// the value of any instance or static field.
// A pure method is just a **function** that output
// a result from inputs.
// Pure methods naturally simplify code by **limiting
// side-effects**.
//
// An attribute **PureAttribute** can be
// used to express in code that a method is pure. In such
// situation, the present code rule checks continuously that the
// method remains pure whatever the modification done.
//
// This rule warns when a method that is tagged with
// *PureAttribute* is actually not pure anymore.
//
// Notice that *NDepend.Attributes.PureAttribute* is defined
// in *NDepend.API.dll* and if you don't want to link this
// assembly, you can also use
// *System.Diagnostics.Contract.PureAttribute*
// or create your own *PureAttribute* and adapt the rule.
//
// Notice that *System.Diagnostics.Contract.PureAttribute* is
// taken account by the compiler only when the VS project has
// Microsoft Code Contract enabled.
//</Description>
//<HowToFix>
// First understand which modification broke the method purity.
// Then refactor the method to bring it back to purity.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="False" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Pure methods should be tagged with PureAttribute</Name>
// warnif count > 0 <-- not a code rule per default
from m in Application.Methods where
!m.IsGeneratedByCompiler &&
!m.HasAttribute ("NDepend.Attributes.PureAttribute".AllowNoMatch()) &&
!m.HasAttribute ("System.Diagnostics.Contract.PureAttribute".AllowNoMatch()) &&
!m.ChangesObjectState && !m.ChangesTypeState &&
m.NbLinesOfCode > 0
select new {
m,
m.NbLinesOfCode,
Debt = 3.ToMinutes().ToDebt(),
Severity = Severity.Low
}
//<Description>
// A method is pure if its execution doesn’t change
// the value of any instance or static field.
// Pure methods naturally simplify code by **limiting
// side-effects**.
//
// This code query lists pure methods that are not tagged with
// a **PureAttribute**. By using such attribute, you can express
// in source code the intention that a method is pure, and
// should remain pure in the future. Benefits of using
// a **PureAttribute** are twofold:
//
// • Not only the intention is expressed in source code,
//
// • but it is also continuously checked by the rule
// *Methods tagged with PureAttribute must be pure*.
//
// This code query is not by default defined as a code rule
// because certainly many of the methods of the code base
// are matched. Hence fixing all matches and then
// maintaining the rule unviolated might require a lot of
// work. This may *counter-balance* such rule benefits.
//</Description>
//<HowToFix>
// Just tag methods matched by this code query with
// *NDepend.Attributes.PureAttribute*
// that can be found in *NDepend.API.dll*,
// or with *System.Diagnostics.Contract.PureAttribute*,
// or with an attribute of yours defined in your own code
// (in which case this code query must be adapted).
//
// Notice that *System.Diagnostics.Contract.PureAttribute* is
// taken account by the compiler only when the VS project has
// Microsoft Code Contract enabled.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Rules violated</Name>
from r in Rules
where r.IsViolated()
orderby r.Debt().Value descending
select new {
r,
Issues = r.Issues(),
Debt = r.Debt(),
AnnualInterest = r.AnnualInterest(),
BreakingPoint = r.BreakingPoint(),
Category = r.Category
}
//<Description>
// **Debt**: Estimated effort to fix all rule issues.
//
// **Annual Interest**: Estimated annual cost to leave all rule issues unfixed.
//
// **Breaking Point**: Estimated point in time from now, when leaving the rule issues unfixed cost as much as fixing the rule issues.
// A low value indicates an a rule with easy-to-fix issue with high annual interest.
// This value can be used to prioritize issues fix, to significantly reduce interest with minimum effort.
//
// More documentation: http://www.ndepend.com/docs/technical-debt
//</Description>]]></Query>
</Group>
<Group Name="Naming Conventions" Active="True" ShownInReport="False">
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Instance fields naming convention</Name>
warnif count > 0 from f in JustMyCode.Fields where
!(
// Instance field name starting with a lower-case letter
(f.Name.Length >= 1 && char.IsLetter(f.Name[0]) && char.IsLower(f.Name[0])) ||
// Instance field name starting with "_" followed with a lower-case letter
(f.Name.Length >= 2 && f.Name.StartsWith("_") && char.IsLetter(f.Name[1]) && char.IsLower(f.Name[1])) ||
// Instance field name starting with "m_" followed with an upper-case letter
(f.Name.Length >= 3 && f.Name.StartsWith("m_") && char.IsLetter(f.Name[2]) && char.IsUpper(f.Name[2]))
) &&
!f.IsStatic &&
!f.IsLiteral &&
!f.IsGeneratedByCompiler &&
!f.IsSpecialName &&
!f.IsEventDelegateObject &&
// Don't check naming convention on serializable fields
!f.HasAttribute("System.Xml.Serialization.XmlAttributeAttribute".AllowNoMatch()) &&
!f.HasAttribute("System.Runtime.Serialization.DataMemberAttribute".AllowNoMatch()) &&
// Don't warn if a method using the field is also calling a method that has 'ref' and 'out' parameters.
// This could lead to false positive. A field used in a 'ref' or 'out' parameter cannot be set as read-only.
f.MethodsUsingMe.SelectMany(m => m.MethodsCalled).FirstOrDefault(m => m.Name.Contains("&")) == null
select new {
f,
f.SizeOfInst,
Debt = 3.ToMinutes().ToDebt(),
Severity = Severity.Medium
}
//<Description>
// By default the presents rule supports the 3 most used naming
// conventions for instance fields:
//
// • Instance field name starting with a lower-case letter
//
// • Instance field name starting with "_" followed with a lower-case letter
//
// • Instance field name starting with "m_" followed with an upper-case letter
//
// The rule can be easily adapted to your own company naming convention.
//
// In terms of behavior, a *static field* is something completely different
// than an *instance field*, so it is interesting to differentiate them at
// a glance through a naming convetion.
//
// This is why it is advised to use a specific naming convention for instance
// field like name that starts with **m_**.
//
// Related discussion:
// http://codebetter.com/patricksmacchia/2013/09/04/on-hungarian-notation-for-instance-vs-static-fields-naming/
//</Description>
//<HowToFix>
// Once the rule has been adapted to your own naming convention
// make sure to name all matched instance fields adequately.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Static fields naming convention</Name>
warnif count > 0 from f in Application.Fields where
!(
// Static field name starting with an upper-case letter
(f.Name.Length >= 1 && char.IsLetter(f.Name[0]) && char.IsUpper(f.Name[0])) ||
// Static field name starting with "_" followed with an upper-case letter
(f.Name.Length >= 2 && f.Name.StartsWith("_") && char.IsLetter(f.Name[1]) && char.IsUpper(f.Name[1])) ||
// Static field name starting with "s_" followed with an upper-case letter
(f.Name.Length >= 3 && f.Name.StartsWith("s_") && char.IsLetter(f.Name[2]) && char.IsUpper(f.Name[2]))
) &&
f.IsStatic &&
!f.IsLiteral &&
!f.IsGeneratedByCompiler &&
!f.IsSpecialName &&
!f.IsEventDelegateObject
select new {
f,
f.SizeOfInst,
Debt = 3.ToMinutes().ToDebt(),
Severity = Severity.Medium
}
//<Description>
// By default the presents rule supports the 3 most used naming
// conventions for static fields:
//
// • Static field name starting with an upper-case letter
//
// • Static field name starting with "_" followed with an upper-case letter
//
// • Static field name starting with "s_" followed with an upper-case letter
//
// The rule can be easily adapted to your own company naming convention.
//
// In terms of behavior, a *static field* is something completely different
// than an *instance field*, so it is interesting to differentiate them at
// a glance through a naming convetion.
//
// This is why it is advised to use a specific naming convention for static
// field like name that starts with **s_**.
//
// Related discussion:
// http://codebetter.com/patricksmacchia/2013/09/04/on-hungarian-notation-for-instance-vs-static-fields-naming/
//</Description>
//<HowToFix>
// Once the rule has been adapted to your own naming convention
// make sure to name all matched static fields adequately.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="True"><![CDATA[// <Name>Interface name should begin with a 'I'</Name>
// Don't query all Application.Types because some interface generated might not start with an 'I'
// like for example when adding a WSDL reference to a project.
warnif count > 0 from t in JustMyCode.Types where
t.IsInterface &&
// Don't apply this rule for COM interfaces.
!t.HasAttribute("System.Runtime.InteropServices.ComVisibleAttribute".AllowNoMatch())
// Discard outter type(s) name prefix for nested types
let name = !t.IsNested ?
t.Name :
t.Name.Substring(t.Name.LastIndexOf('+') + 1, t.Name.Length - t.Name.LastIndexOf('+') - 1)
where name[0] != 'I'
select new {
t,
Debt = 5.ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// In the .NET world, interfaces names are commonly prefixed
// with an upper case **I**. This rule warns about interfaces
// whose names don't follow this convention. Because this
// naming convention is widely used and accepted, we
// recommend abiding by this rule.
//
// Typically COM interfaces names don't follow this rule.
// Hence this code rule doesn't take care of interfaces tagged
// with *ComVisibleAttribute*.
//</Description>
//<HowToFix>
// Make sure that matched interfaces names are prefixed with
// an upper **I**.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Abstract base class should be suffixed with 'Base'</Name>
warnif count > 0 from t in Application.Types where
t.IsAbstract &&
t.IsClass &&
t.BaseClass != null &&
t.BaseClass.FullName == "System.Object" &&
((!t.IsGeneric && !t.NameLike (@"Base$")) ||
( t.IsGeneric && !t.NameLike (@"Base<")))
select new {
t,
Debt = 5.ToMinutes().ToDebt(),
Severity = Severity.Medium
}
//<Description>
// This rule warns about *abstract classes* whose names are not
// suffixed with **Base**. It is a common practice in the .NET
// world to suffix base classes names with **Base**.
//
// Notice that this rule doesn't match abstract classes that
// are in a middle of a hierarchy chain.
// In other words, only base classes that derive directly
// from *System.Object* are matched.
//</Description>
//<HowToFix>
// Suffix the names of matched base classes with **Base**.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="True"><![CDATA[// <Name>Exception class name should be suffixed with 'Exception'</Name>
warnif count > 0 from t in Application.Types where
t.IsExceptionClass &&
// We use SimpleName, because in case of generic Exception type
// SimpleName suppresses the generic suffix (like <T>).
!t.SimpleNameLike(@"Exception$") &&
!t.SimpleNameLike(@"ExceptionBase$") // Allow the second suffix Base
// for base exception classes.
select new {
t,
Debt = 5.ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// This rule warns about *exception classes* whose names are not
// suffixed with **Exception**. It is a common practice in the .NET
// world to suffix exception classes names with **Exception**.
//
// For exception base classes, the suffix **ExceptionBase**
// is also accepted.
//</Description>
//<HowToFix>
// Suffix the names of matched exception classes with **Exception**.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="True"><![CDATA[// <Name>Attribute class name should be suffixed with 'Attribute'</Name>
warnif count > 0 from t in Application.Types where
t.IsAttributeClass &&
!t.NameLike (@"Attribute$")
select new {
t,
Debt = 5.ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// This rule warns about *attribute classes* whose names are not
// suffixed with **Attribute**. It is a common practice in the .NET
// world to suffix attribute classes names with **Attribute**.
//</Description>
//<HowToFix>
// Suffix the names of matched attribute classes with **Attribute**.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Types name should begin with an Upper character</Name>
warnif count > 0
from t in JustMyCode.Types where
// The name of a type should begin with an Upper letter.
!t.SimpleNameLike (@"^[A-Z]") &&
// Except if it is generated by compiler.
!t.IsSpecialName &&
!t.IsGeneratedByCompiler
select new {
t,
// We show the type simple name
// that doesn't include the parent type name
// for nested types.
t.SimpleName,
Debt = 5.ToMinutes().ToDebt(),
Severity = Severity.Medium
}
//<Description>
// This rule warns about *types* whose names don't start
// with an Upper character. It is a common practice in the .NET
// world to use **Pascal Casing Style** to name types.
//
// **Pascal Casing Style** : The first letter in the identifier
// and the first letter of each subsequent concatenated word
// are capitalized. For example: *BackColor*
//</Description>
//<HowToFix>
// *Pascal Case* the names of matched types.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Methods name should begin with an Upper character</Name>
warnif count > 0
from m in JustMyCode.Methods where
!m.NameLike (@"^[A-Z]") &&
!m.IsSpecialName &&
!m.IsGeneratedByCompiler
select new {
m,
Debt = 3.ToMinutes().ToDebt(),
Severity = Severity.Medium
}
//<Description>
// This rule warns about *methods* whose names don't start
// with an Upper character. It is a common practice in the .NET
// world to use **Pascal Casing Style** to name methods.
//
// **Pascal Casing Style** : The first letter in the identifier
// and the first letter of each subsequent concatenated word
// are capitalized. For example: *ComputeSize*
//</Description>
//<HowToFix>
// *Pascal Case* the names of matched methods.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Do not name enum values 'Reserved'</Name>
warnif count > 0
from f in Application.Fields where
f.IsEnumValue &&
f.NameLike (@"Reserved")
select new {
f,
Debt = 3.ToMinutes().ToDebt(),
Severity = Severity.Medium
}
//<Description>
// This rule assumes that an enumeration member
// with a name that contains **"Reserved"**
// is not currently used but is a placeholder to
// be renamed or removed in a future version.
// Renaming or removing a member is a breaking
// change. You should not expect users to ignore
// a member just because its name contains
// **"Reserved"** nor can you rely on users to read or
// abide by documentation. Furthermore, because
// reserved members appear in object browsers
// and smart integrated development environments,
// they can cause confusion as to which members
// are actually being used.
//
// Instead of using a reserved member, add a
// new member to the enumeration in the future
// version.
//
// In most cases, the addition of the new
// member is not a breaking change, as long as the
// addition does not cause the values of the
// original members to change.
//</Description>
//<HowToFix>
// To fix a violation of this rule, remove or
// rename the member.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Avoid types with name too long</Name>
warnif count > 0
from t in JustMyCode.Types
where !t.IsGeneratedByCompiler
where t.SimpleName.Length > 40
orderby t.SimpleName.Length descending
select new {
t,
t.SimpleName,
NameLength = t.SimpleName.Length,
Debt = 3.ToMinutes().ToDebt(),
Severity = Severity.Medium
}
//<Description>
// Types with a name too long tend to decrease code readability.
// This might also be an indication that a type is doing too much.
//
// This rule matches types with names with more than 40 characters.
//</Description>
//<HowToFix>
// To fix a violation of this rule, rename the type with a shortest name
// or eventually split the type in several more fine-grained types.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Avoid methods with name too long</Name>
warnif count > 0 from m in JustMyCode.Methods where
// Explicit Interface Implementation methods are
// discarded because their names are prefixed
// with the interface name.
!m.IsExplicitInterfaceImpl &&
!m.IsGeneratedByCompiler &&
((!m.IsSpecialName && m.SimpleName.Length > 40) ||
// Property getter/setter are prefixed with "get_" "set_" of length 4.
( m.IsSpecialName && m.SimpleName.Length - 4 > 40))
orderby m.SimpleName.Length descending
select new {
m,
m.SimpleName,
NameLength = m.SimpleName.Length - (m.IsSpecialName ? 4 : 0),
Debt = 3.ToMinutes().ToDebt(),
Severity = Severity.Medium
}
//<Description>
// Methods with a name too long tend to decrease code readability.
// This might also be an indication that a method is doing too much.
//
// This rule matches methods with names with more than 40 characters.
//</Description>
//<HowToFix>
// To fix a violation of this rule, rename the method with a shortest name
// that equally conveys the behavior of the method.
// Or eventually split the method into several smaller methods.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Avoid fields with name too long</Name>
warnif count > 0 from f in JustMyCode.Fields where
!f.IsGeneratedByCompiler &&
f.Name.Length > 40
orderby f.Name descending
select new {
f,
NameLength = f.Name.Length,
Debt = 3.ToMinutes().ToDebt(),
Severity = Severity.Medium
}
//<Description>
// Fields with a name too long tend to decrease code readability.
//
// This rule matches fields with names with more than 40 characters.
//</Description>
//<HowToFix>
// To fix a violation of this rule, rename the field with a shortest name
// that equally conveys the same information.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="True"><![CDATA[// <Name>Avoid having different types with same name</Name>
warnif count > 0
// Special type names for which multiple types with such name are allowed.
let nonReportedTypeName = new [] { "Program", "NamespaceDoc" }
// This rule matches also collisions between
// application and third-party types sharing a same name.
let groups = JustMyCode.Types.Union(ThirdParty.Types)
// Discard nested types, whose name is
// prefixed with the parent type name.
.Where(t => !t.IsNested && !nonReportedTypeName.Contains(t.Name))
// Group types by name.
.GroupBy(t => t.Name)
from @group in groups
where @group.Count() > 1
// Let's see if types with the same name are declared
// in different namespaces.
// (t.FullName is {namespaceName}.{typeName} )
let groupsFullName = @group.GroupBy(t => t.FullName)
where groupsFullName.Count() > 1
// If several types with same name are declared in different namespaces
// eliminate the case where all types are declared in third-party assemblies.
let types= groupsFullName.SelectMany(g => g)
where types.Any(t => !t.IsThirdParty)
// Uncomment this line, to only gets naming collision involving
// both application and third-party types.
// && types.Any(t => t.IsThirdParty)
orderby types.Count() descending
select new {
// Order types by parent namespace and assembly name,this way we always get the same type across sessions.
// Without this astute, the same issue would be seen as added/removed when the first type choosen in the group
// was not always the same, a situation that actually happens.
// Also make sure that the chosen type is not a third-party one.
t = types.OrderBy(t => (t.IsThirdParty ? "1" : "0") + t.ParentNamespace.Name + t.ParentAssembly.Name).First(),
// In the 'types' column, make sure to group matched types
// by parent assemblies and parent namespaces, to get a result
// more readable.
types,
Debt = 10.ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// This rule warns about multiple types with same name,
// that are defined in different *application* or
// *third-party* namespaces or assemblies.
//
// Such practice create confusion and also naming collision
// in source files that use different types with same name.
//</Description>
//<HowToFix>
// To fix a violation of this rule, rename concerned types.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Avoid prefixing type name with parent namespace name</Name>
warnif count > 0
from n in JustMyCode.Namespaces
where n.Name.Length > 0
from t in n.ChildTypes
where
JustMyCode.Contains(t) && // Don't warn about generated code
!t.IsGeneratedByCompiler &&
!t.IsNested &&
t.Name.IndexOf(n.SimpleName) == 0 &&
// The type name is equal to namespace name or the type name contains another
// word that starts with an upper-case letter after the namespace name.
// This way we avoid matching false-positive where namespace name is "Stat" and type name is "Statistic".
(t.Name.Length == n.SimpleName.Length || char.IsUpper(t.Name[n.SimpleName.Length]))
select new {
t,
namespaceName = n.SimpleName,
Debt = 10.ToMinutes().ToDebt(),
Severity = Severity.Medium
}
//<Description>
// This rule warns about situations where the parent namespace name
// is used as the prefix of a contained type.
//
// For example a type named "RuntimeEnvironment"
// declared in a namespace named "Foo.Runtime"
// should be named "Environment".
//
// Such situation creates naming redundancy with no readability gain.
//</Description>
//<HowToFix>
// To fix a violation of this rule, remove the prefix from the type name.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Avoid naming types and namespaces with the same identifier</Name>
warnif count > 0
let hashsetShortNames = Namespaces.Where(n => n.Name.Length > 0).Select(n => n.SimpleName).ToHashSet()
from t in JustMyCode.Types
where hashsetShortNames.Contains(t.Name)
select new {
t,
namespaces = Namespaces.Where(n => n.SimpleName == t.Name),
Debt = 12.ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// This rule warns when a type and a namespace have the same name.
//
// For example when a type is named *Environment*
// and a namespace is named *Foo.Environment*.
//
// Such situation provokes tedious compiler resolution collision,
// and makes the code less readable because concepts are not
// concisely identified.
//</Description>
//<HowToFix>
// To fix a violation of this rule, renamed the concerned type or namespace.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="True"><![CDATA[// <Name>Don't call your method Dispose</Name>
warnif count > 0
from m in JustMyCode.Methods.WithSimpleName("Dispose")
where !m.ParentType.Implement("System.IDisposable".AllowNoMatch()) &&
m.OverriddensBase.Count() == 0 // Can't change the name of an override
select new {
m,
Debt = 15.ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// In .NET programming, the identifier *Dispose* should be kept
// only for implementations of *System.IDisposable*.
//
// This rule warns when a method is named *Dispose()*,
// but the parent type doesn't implement *System.IDisposable*.
//</Description>
//<HowToFix>
// To fix a violation of this rule,
// either make the parent type implements *System.IDisposable*,
// or rename the *Dispose()* method with another identifier like:
// *Close() Terminate() Finish() Quit() Exit() Unlock() ShutDown()*…
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Methods prefixed with 'Try' should return a boolean</Name>
warnif count > 0
from m in Application.Methods where
m.SimpleNameLike("^Try") &&
m.ReturnType != null &&
m.ReturnType.FullName != "System.Boolean"
select new {
m,
m.ReturnType,
Debt = 10.ToMinutes().ToDebt(),
Severity = Severity.Medium
}
//<Description>
// When a method has a name prefixed with **Try**, it is expected that
// it returns a *boolean*, that reflects the method execution status,
// *success* or *failure*.
//
// Such method usually returns a result through an *out parameter*.
// For example: *System.Int32.TryParse(int,out string):bool*
//</Description>
//<HowToFix>
// To fix a violation of this rule,
// Rename the method, or transform it into an operation that can fail.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Quality Gates Evolution</Name>
from qg in QualityGates
let qgBaseline = qg.OlderVersion()
let relyOnDiff = qgBaseline == null
let evolution = relyOnDiff ? (TrendIcon?)null :
// When a quality gate relies on diff between now and baseline
// it is not executed against the baseline
qg.ValueDiff() == 0d ?
TrendIcon.Constant :
(qg.ValueDiff() > 0 ?
( qg.MoreIsBad ? TrendIcon.RedUp: TrendIcon.GreenUp) :
(!qg.MoreIsBad ? TrendIcon.RedDown: TrendIcon.GreenDown))
select new { qg,
Evolution = evolution,
BaselineStatus = relyOnDiff? (QualityGateStatus?) null : qgBaseline.Status,
Status = qg.Status,
BaselineValue = relyOnDiff? (null) : qgBaseline.ValueString,
Value = qg.ValueString,
}
// <Description>
// Show quality gates evolution between baseline and now.
//
// When a quality gate relies on diff between now and baseline (like *New Debt since Baseline*)
// it is not executed against the baseline and as a consequence its evolution is not available.
//
// Double-click a quality gate for editing.
// </Description>]]></Query>
</Group>
<Group Name="Source Files Organization" Active="True" ShownInReport="False">
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Avoid referencing source file out of Visual Studio project directory</Name>
warnif count > 0
from a in Application.Assemblies
where a.VisualStudioProjectFilePath != null
let vsProjDirPathLower = a.VisualStudioProjectFilePath.ParentDirectoryPath.ToString().ToLower()
from t in a.ChildTypes
where JustMyCode.Contains(t) && t.SourceFileDeclAvailable
from decl in t.SourceDecls
let sourceFilePathLower = decl.SourceFile.FilePath.ToString().ToLower()
where sourceFilePathLower.IndexOf(vsProjDirPathLower) != 0
select new {
t,
sourceFilePathLower,
projectFilePath = a.VisualStudioProjectFilePath.ToString(),
Debt = 10.ToMinutes().ToDebt(),
Severity = Severity.Low
}
//<Description>
// A source file located outside of the VS project directory can be added through:
// *> Add > Existing Items… > Add As Link*
//
// Doing so can be used to share types definitions across several assemblies.
// This provokes type duplication at binary level.
// Hence maintainability is degraded and subtle versioning bug can appear.
//
// This rule matches types whose source files are not declared under the
// directory that contains the related Visual Studio project file, or under
// any sub-directory of this directory.
//
// This practice can be tolerated for certain types shared across executable assemblies.
// Such type can be responsible for startup related concerns,
// such as registering custom assembly resolving handlers or
// checking the .NET Framework version before loading any custom library.
//</Description>
//<HowToFix>
// To fix a violation of this rule, prefer referencing from a VS project
// only source files defined in sub-directories of the VS project file location.
//
// By default issues of this rule have an **Low** severity
// because they reflect more an advice than a problem.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Avoid duplicating a type definition across assemblies</Name>
warnif count > 0
let groups = Application.Types
.Where(t => !t.IsGeneratedByCompiler)
.GroupBy(t => t.FullName)
from @group in groups
where @group.Count() > 1
// Tricky: This rule is executed on both current snapshot and baseline snapshot (if any).
// Taking t as @group.First() could return any type in the group, and potentially
// it can return different types for current and baseline snapshot.
// As a result issues wouldn't corresponds (since types are different) and the user
// would see issues of this rule as a couple of issues added and removed.
// Ordering types by parent assembly name and then taking the first type, discards this risk.
let types = @group.OrderBy(t => t.ParentAssembly.Name)
select new {
t = types.First(),
// In the 'types' column, make sure to group matched types by parent assemblies.
typesDefs = types.ToArray(),
Debt = 15.ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// A source file located outside of the VS project directory can be added through:
// *> Add > Existing Items… > Add As Link*
//
// This rule warns about using this feature to share code across several assemblies.
// This provokes type duplication at binary level.
// Hence maintainability is degraded and subtle versioning bug can appear.
//
// This practice can be tolerated for certain types shared across executable assemblies.
// Such type can be responsible for startup related concerns,
// such as registering custom assembly resolving handlers or
// checking the .NET Framework version before loading any custom library.
//</Description>
//<HowToFix>
// To fix a violation of this rule, prefer sharing types through DLLs.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Avoid defining multiple types in a source file</Name>
warnif count > 0
// Build a lookup indexed by source files, values being a sequence of types defined in the source file.
let lookup = JustMyCode.Types
// When a source file is referenced by several assemblies,
// type(s) contained in the source file are seen as distinct types, both by the CLR and by NDepend.
// This Distinct clause based on type full-name (type name prefixed with namespace)
// avoids matching the multiple versions of such type.
.Distinct(t => t.FullName)
.Where(t => t.SourceFileDeclAvailable &&
// except enumerations, nested types and types generated by compilers!
!t.IsEnumeration &&
!t.IsNested &&
!t.IsGeneratedByCompiler)
// We use multi-key, since a type can be declared in multiple source files.
.ToMultiKeyLookup(t => t.SourceDecls.Select(d => d.SourceFile))
from @group in lookup where @group.Count() > 1
let sourceFile = @group.Key
// CQLinq doesn't let indexing result with sourceFile
// so we choose a typeIndex in types,
// preferably the type that has the file name.
let typeWithSourceFileName = @group.FirstOrDefault(t => t.SimpleName == sourceFile.FileNameWithoutExtension)
let typeIndex = typeWithSourceFileName ?? @group.First()
select new {
typeIndex,
TypesInSourceFile = @group as IEnumerable<IType>,
SourceFilePathString = sourceFile.FilePathString,
Debt = 3.ToMinutes().ToDebt(),
Severity = Severity.Low
}
//<Description>
// Defining multiple types in a single source file decreases code readability,
// because developers are used to see all types in a namespace,
// when expanding a folder in the *Visual Studio Solution Explorer*.
// Also doing so, leads to source files with too many lines.
//
// Each match of this rule is a source file that contains several types
// definitions, indexed by one of those types, preferably the one with
// the same name than the source file name without file extension, if any.
//</Description>
//<HowToFix>
// To fix a violation of this rule, create a source file for each type.
//
// By default issues of this rule have an **Low** severity
// because they reflect more an advice than a problem.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Namespace name should correspond to file location</Name>
warnif count > 0
from n in Application.Namespaces
// Replace dots by spaces in namespace name
let dirCorrespondingAll = n.Name.Replace('.', ' ')
// Remove the first namespace component that is typically the product name
// and that typically interfere with assemblies names.
let dirCorresponding = !dirCorrespondingAll.Contains(' ') ?
dirCorrespondingAll :
dirCorrespondingAll.Substring(dirCorrespondingAll.IndexOf(' ')+1, dirCorrespondingAll.Length - dirCorrespondingAll.IndexOf(' ') - 1)
// Look at source file decl of JustMyCode type's declared in n
from t in n.ChildTypes
where JustMyCode.Contains(t) && t.SourceFileDeclAvailable
let sourceDeclConcerned = (from decl in t.SourceDecls
let sourceFilePath = decl.SourceFile.FilePath.ToString()
// Replace dots and path separators by spaces in source files names
where !sourceFilePath.Replace('.',' ').Replace('\\',' ').Contains(dirCorresponding)
select sourceFilePath).ToArray()
where sourceDeclConcerned.Length > 0
select new {
t,
dirCorresponding ,
sourceFilePath = sourceDeclConcerned[0],
nbSourceDeclConcerned = sourceDeclConcerned.Length,
Debt = (2 + sourceDeclConcerned.Length).ToMinutes().ToDebt(),
Severity = Severity.Medium
}
//<Description>
// For a solid code structure and organization,
// do mirror the namespaces hierarchy and the directories hierarchy containing source files.
//
// Doing so is a widely accepted convention, and not respecting this convention
// will lead to less maintainable and less browsable source code.
//
// This rule matches all types in such source file, whose location doesn't correspond
// to the type parent namespace. If a source file contains several such types (that
// are not necessarily in the same namespace) each type will result in a violation.
//
// If a type is declared in several such source files, the value for the column
// *nbSourceDeclConcerned* in the result, is greater than 1.
// The technical-debt per issue is proportional to *nbSourceDeclConcerned*.
//</Description>
//<HowToFix>
// To fix a violation of this rule, make sure that the type parent namespace and
// the directory sub-paths that contains the type source file, are mirrored.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Types with source files stored in the same directory, should be declared in the same namespace</Name>
warnif count > 0
// Group JustMyCode types in a lookup
// where groups are keyed with directories that contain the types' source file(s).
// Note that a type can be contained in several groups
// if it is declared in several source files stored in different directories.
let lookup = JustMyCode.Types.Where(t => t.SourceFileDeclAvailable)
.ToMultiKeyLookup(
t => t.SourceDecls.Select(
decl => decl.SourceFile.FilePath.ParentDirectoryPath).Distinct()
)
from groupOfTypes in lookup
let parentNamespaces = groupOfTypes.ParentNamespaces()
// Select group of types (with source files stored in the same directory) …
// … but contained in several namespaces
where parentNamespaces.Count() > 1
// mainNamespaces is the namespace that contains many types
// declared in the directory groupOfTypes .key
let mainNamespace = groupOfTypes
.ToLookup(t => t.ParentNamespace)
.OrderByDescending(g => g.Count()).First().Key
// Select types with source files stored in the same directory,
// but contained in namespaces different than mainNamespace.
let typesOutOfMainNamespace = groupOfTypes
.Where(t => t.ParentNamespace != mainNamespace &&
t.ParentAssembly == mainNamespace.ParentAssembly)
// Filter types declared on several source files that contain generated methods
// because typically such type contains one or several partial definitions generated.
// These partially generated types would be false positive for the present rule.
.Where(t => t.SourceDecls.Count() == 1 ||
t.Methods.Count(m => JustMyCode.Contains(m)) == 0)
where typesOutOfMainNamespace.Count() > 0
let typesInMainNamespace = groupOfTypes.Where(t => t.ParentNamespace == mainNamespace)
select new {
mainNamespace,
typesOutOfMainNamespace,
typesInMainNamespace,
Debt = (2+5*typesOutOfMainNamespace.Count()).ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// For a solid code structure and organization, do mirror the namespaces
// hierarchy and the directories hierarchy containing source files.
//
// Doing so is a widely accepted convention, and not respecting this convention
// will lead to less maintainable and less browsable code.
//
// Respecting this convention means that types with source files stored in the same directory,
// should be declared in the same namespace.
//
// For each directory that contains several source files, where most types are declared
// in a namespace (what we call the **main namespace**) and a few types are declared
// out of the *main namespace*, this code rule matches:
//
// • The *main namespace*
//
// • **typesOutOfMainNamespace**: Types declared in source files in the *main namespace*'s directory
// but that are not in the *main namespace*.
//
// • *typesInMainNamespace*: And for informational purposes, types declared in source files in the
// *main namespace*'s directory, and that are in the *main namespace*.
//</Description>
//<HowToFix>
// Violations of this rule are types in the *typesOutOfMainNamespace* column.
// Typically such type …
//
// • … is contained in the wrong namespace but its source file is stored in the right directory.
// In such situation the type should be contained in *main namespace*.
//
// • … is contained in the right namespace but its source file is stored in the wrong directory
// In such situation the source file of the type must be moved to the proper parent namespace directory.
//
// • … is declared in multiple source files, stored in different directories.
// In such situation it is preferable that all source files are stored in a single directory.
//
// The estimated Debt, which means the effort to fix such issue,
// is equal to 2 minutes plus 5 minutes per type in *typesOutOfMainNamespace*.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Types declared in the same namespace, should have their source files stored in the same directory</Name>
warnif count > 0
from @namespace in Application.Namespaces
// Group types of @namespace in a lookup
// where groups are keyed with directories that contain the types' source file(s).
// Note that a type can be contained in several groups
// if it is declared in several source files stored in different directories.
let lookup = @namespace.ChildTypes.Where(t => t.SourceFileDeclAvailable && JustMyCode.Contains(t))
.ToMultiKeyLookup(
t => t.SourceDecls.Select(
decl => decl.SourceFile.FilePath.ParentDirectoryPath).Distinct()
)
// Are types of @namespaces declared in more than one directory?
where lookup.Count > 1
// Infer the main folder, preferably the one that has the same name as the namespace.
let dirs = lookup.Select(types => types.Key)
let mainDirNullable = dirs.Where(d => d.DirectoryName == @namespace.SimpleName).FirstOrDefault()
let mainDir = mainDirNullable ?? dirs.First()
// Types declared out of mainDir, are types in group of types declared in a directory different than mainDir!
let typesDeclaredOutOfMainDir =
lookup.Where(types => types.Key != mainDir)
.SelectMany(types => types)
// Filter types declared on several source files that contain generated methods
// because typically such type contains one or several partial definitions generated.
// These partially generated types would be false positive for the present rule.
.Where(t => t.SourceDecls.Count() == 1 ||
t.Methods.Count(m => JustMyCode.Contains(m)) == 0)
where typesDeclaredOutOfMainDir.Count() > 0
let typesDeclaredInMainDir =
lookup.Where(types => types.Key == mainDir)
.SelectMany(types => types)
select new {
@namespace,
typesDeclaredOutOfMainDir,
mainDir = mainDir.ToString(),
typesDeclaredInMainDir,
Debt = (2+5*typesDeclaredOutOfMainDir.Count()).ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// For a solid code structure and organization,
// do mirror the namespaces hierarchy and the directories hierarchy containing source files.
//
// Doing so is a widely accepted convention, and not respecting this convention
// will lead to less maintainable and less browsable code.
//
// Respecting this convention means that types declared in the same namespace,
// should have their source files stored in the same directory.
//
// For each namespace that contains types whose source files
// are declared in several directories, infer the **main directory**,
// the directory that naturally hosts source files of types,
// preferably the directory whose name corresponds with the namespace
// name. In this context, this code rule matches:
//
// • The namespace
//
// • **typesDeclaredOutOfMainDir**: types in the namespace whose source files
// are stored out of the *main directory*.
//
// • The *main directory*
//
// • *typesDeclaredInMainDir*: for informational purposes, types declared
// in the namespace, whose source files are stored in the *main directory*.
//</Description>
//<HowToFix>
// Violations of this rule are types in the **typesDeclaredOutOfMainDir** column.
// Typically such type…
//
// • … is contained in the wrong namespace but its source file is stored in the right directory.
// In such situation the type should be contained in the namespace corresponding to
// the parent directory.
//
// • … is contained in the right namespace but its source file is stored in the wrong directory.
// In such situation the source file of the type must be moved to the *main directory*.
//
// • … is declared in multiple source files, stored in different directories.
// In such situation it is preferable that all source files are stored in a single directory.
//
// The estimated Debt, which means the effort to fix such issue,
// is equal to 2 minutes plus 5 minutes per type in *typesDeclaredOutOfMainDir*.
//</HowToFix>]]></Query>
</Group>
<Group Name=".NET Framework Usage" Active="True" ShownInReport="False">
<Group Name="System" Active="True" ShownInReport="False">
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Mark ISerializable types with SerializableAttribute</Name>
warnif count > 0
from t in Application.Types where
t.IsPublic &&
!t.IsDelegate &&
!t.IsExceptionClass && // Don't match exceptions, since the Exception class
// implements ISerializable, this would generate
// too many false positives.
t.Implement ("System.Runtime.Serialization.ISerializable".AllowNoMatch()) &&
!t.HasAttribute ("System.SerializableAttribute".AllowNoMatch())
select new {
t,
t.NbLinesOfCode,
Debt = 5.ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// To be recognized by the CLR as serializable,
// types must be marked with the *SerializableAttribute*
// attribute even if the type uses a custom
// serialization routine through implementation of
// the *ISerializable* interface.
//
// This rule matches types that implement *ISerializable* and
// that are not tagged with *SerializableAttribute*.
//</Description>
//<HowToFix>
// To fix a violation of this rule, tag the matched type
// with *SerializableAttribute* .
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Mark assemblies with CLSCompliant (deprecated)</Name>
warnif count > 0 from a in Application.Assemblies where
!a.HasAttribute ("System.CLSCompliantAttribute".AllowNoMatch())
select new {
a,
Debt = 5.ToMinutes().ToDebt(),
Severity = Severity.Medium
}
//<Description>
// This rule has been deprecated and, as a consequence, it is disabled by default.
// Feel free to re-enable it if it makes sense in your dev environment.
//
// The *Common Language Specification* (CLS) defines naming restrictions,
// data types, and rules to which assemblies must conform if they are to
// be used across programming languages. Good design dictates that all
// assemblies explicitly indicate CLS compliance with **CLSCompliantAttribute**.
// If the attribute is not present on an assembly, the assembly is not compliant.
//
// Notice that it is possible for a CLS-compliant assembly to contain types or
// type members that are not compliant.
//
// This rule matches assemblies that are not tagged with
// **System.CLSCompliantAttribute**.
//</Description>
//<HowToFix>
// To fix a violation of this rule, tag the assembly with *CLSCompliantAttribute*.
//
// Instead of marking the whole assembly as non-compliant, you should determine
// which type or type members are not compliant and mark these elements as such.
// If possible, you should provide a CLS-compliant alternative for non-compliant
// members so that the widest possible audience can access all the functionality
// of your assembly.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Mark assemblies with ComVisible (deprecated)</Name>
warnif count > 0 from a in Application.Assemblies where
!a.HasAttribute ("System.Runtime.InteropServices.ComVisibleAttribute".AllowNoMatch())
select new {
a,
Debt = 5.ToMinutes().ToDebt(),
Severity = Severity.Medium
}
//<Description>
// This rule has been deprecated and, as a consequence, it is disabled by default.
// Feel free to re-enable it if it makes sense in your dev environment.
//
// The **ComVisibleAttribute** attribute determines how COM clients access
// managed code. Good design dictates that assemblies explicitly indicate
// COM visibility. COM visibility can be set for a whole assembly and then
// overridden for individual types and type members. If the attribute is not
// present, the contents of the assembly are visible to COM clients.
//
// This rule matches assemblies that are not tagged with
// **System.Runtime.InteropServices.ComVisibleAttribute**.
//</Description>
//<HowToFix>
// To fix a violation of this rule, tag the assembly with *ComVisibleAttribute*.
//
// If you do not want the assembly to be visible to COM clients, set the
// attribute value to **false**.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Mark attributes with AttributeUsageAttribute</Name>
warnif count > 0
from t in JustMyCode.Types where
t.DeriveFrom ("System.Attribute".AllowNoMatch()) &&
!t.HasAttribute ("System.AttributeUsageAttribute".AllowNoMatch())
select new {
t,
Debt = 5.ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// When you define a custom attribute, mark it by using **AttributeUsageAttribute**
// to indicate where in the source code the custom attribute can be applied. The
// meaning and intended usage of an attribute will determine its valid locations
// in code. For example, you might define an attribute that identifies the person
// who is responsible for maintaining and enhancing each type in a library, and
// that responsibility is always assigned at the type level. In this case, compilers
// should enable the attribute on classes, enumerations, and interfaces, but should
// not enable it on methods, events, or properties. Organizational policies and
// procedures would dictate whether the attribute should be enabled on assemblies.
//
// The **System.AttributeTargets** enumeration defines the targets that you can
// specify for a custom attribute. If you omit *AttributeUsageAttribute*, your
// custom attribute will be valid for all targets, as defined by the **All** value of
// *AttributeTargets* enumeration.
//
// This rule matches attribute classes that are not tagged with
// **System.AttributeUsageAttribute**.
//</Description>
//<HowToFix>
// To fix a violation of this rule, specify targets for the attribute by using
// *AttributeUsageAttribute* with the proper *AttributeTargets* values.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Remove calls to GC.Collect()</Name>
warnif count > 0
let gcCollectMethods = ThirdParty.Methods.WithFullNameWildcardMatch(
"System.GC.Collect(*)").ToHashSet()
from m in Application.Methods.UsingAny(gcCollectMethods)
select new {
m,
gcCollectMethodCalled = m.MethodsCalled.Intersect(gcCollectMethods),
Debt = 5.ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// It is preferable to avoid calling **GC.Collect()**
// explicitly in order to avoid some performance pitfall.
//
// More in information on this here:
// http://blogs.msdn.com/ricom/archive/2004/11/29/271829.aspx
//
// This rule matches application methods that call an
// overload of the method *GC.Collect()*.
//</Description>
//<HowToFix>
// Remove matched calls to *GC.Collect()*.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Don't call GC.Collect() without calling GC.WaitForPendingFinalizers()</Name>
warnif count > 0
let gcCollectMethods = ThirdParty.Methods.WithFullNameWildcardMatch(
"System.GC.Collect(*)").ToHashSet()
from m in Application.Methods.UsingAny(gcCollectMethods) where
!m.IsUsing ("System.GC.WaitForPendingFinalizers()".AllowNoMatch())
select new {
m,
gcCollectMethodCalled = m.MethodsCalled.Intersect(gcCollectMethods),
Debt = 10.ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// It is preferable to avoid calling **GC.Collect()**
// explicitly in order to avoid some performance
// pitfall. This situation is checked through the
// default rules: *Remove calls to GC.Collect()*
//
// But if you wish to call *GC.Collect()* anyway,
// you must do it this way:
//
// GC.Collect();
//
// GC.WaitForPendingFinalizers();
//
// GC.Collect();
//
// To make sure that finalizer got executed, and
// object with finalizer got cleaned properly.
//
// This rule matches application methods that call an
// overload of the method *GC.Collect()*, without calling
// *GC.WaitForPendingFinalizers()*.
//</Description>
//<HowToFix>
// To fix a violation of this rule, if you really
// need to call *GC.Collect()*, make sure to call
// *GC.WaitForPendingFinalizers()* properly.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Enum Storage should be Int32</Name>
warnif count > 0 from f in JustMyCode.Fields where
f.ParentType.IsEnumeration &&
f.Name == @"value__" &&
f.FieldType != null &&
f.FieldType.FullName != "System.Int32" &&
!f.IsThirdParty
select new {
f,
f.SizeOfInst,
f.FieldType,
Debt = 7.ToMinutes().ToDebt(),
Severity = Severity.Medium
}
//<Description>
// An enumeration is a value type that defines a set of related named constants.
// By default, the **System.Int32** data type is used to store the constant value.
//
// Even though you can change this underlying type, it is not necessary or
// recommended for most scenarios. Note that *no significant performance gain* is
// achieved by using a data type that is smaller than *Int32*. If you cannot use
// the default data type, you should use one of the Common Language System
// (CLS)-compliant integral types, *Byte*, *Int16*, *Int32*, or *Int64* to make
// sure that all values of the enumeration can be represented in CLS-compliant
// programming languages.
//
// This rule matches enumerations whose underlying type used to store
// values is not *System.Int32*.
//</Description>
//<HowToFix>
// To fix a violation of this rule, unless size or compatibility issues exist,
// use *Int32*. For situations where *Int32* is not large enough to hold the values,
// use *Int64*. If backward compatibility requires a smaller data type, use
// *Byte* or *Int16*.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Do not raise too general exception types</Name>
warnif count > 0
let tooGeneralExceptionTypes = ThirdParty.Types.WithFullNameIn(
"System.Exception",
"System.ApplicationException",
"System.SystemException")
from m in Application.Methods.ThatCreateAny(tooGeneralExceptionTypes)
// Make sure we don't match constructor of exception types
// that actually instantiate System.Exception.
where !m.IsConstructor || tooGeneralExceptionTypes.All(t => !m.ParentType.DeriveFrom(t))
let exceptionsCreated = tooGeneralExceptionTypes.Where(t => m.IsUsing(t))
select new {
m,
exceptionsCreated,
Debt = (15 + 5*exceptionsCreated.Count()).ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// The following exception types are too general
// to provide sufficient information to the user:
//
// • System.Exception
//
// • System.ApplicationException
//
// • System.SystemException
//
// If you throw such a general exception type in a library or framework,
// it forces consumers to catch all exceptions,
// including unknown exceptions that they do not know how to handle.
//
// This rule matches methods that create an instance of
// such general exception class.
//</Description>
//<HowToFix>
// To fix a violation of this rule, change the type of the thrown exception
// to either a more derived type that already exists in the framework,
// or create your own type that derives from *System.Exception*.
//
// The estimated Debt, which means the effort to fix such issue,
// is equal to 15 minutes per method matched, plus 5 minutes per too general
// exception types instantiated by the method.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Do not raise reserved exception types</Name>
warnif count > 0
let reservedExceptions = ThirdParty.Types.WithFullNameIn(
"System.NullReferenceException",
"System.ExecutionEngineException",
"System.IndexOutOfRangeException",
"System.OutOfMemoryException",
"System.StackOverflowException",
"System.InvalidProgramException",
"System.AccessViolationException",
"System.CannotUnloadAppDomainException",
"System.BadImageFormatException",
"System.DataMisalignedException")
from m in Application.Methods.ThatCreateAny(reservedExceptions)
let reservedExceptionsCreated = reservedExceptions.Where(t => m.IsUsing(t))
select new {
m,
reservedExceptionsCreated,
Debt = 10.ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// The following exception types are reserved
// and should be thrown only by the Common Language Runtime:
//
// • System.ExecutionEngineException
//
// • System.IndexOutOfRangeException
//
// • System.NullReferenceException
//
// • System.OutOfMemoryException
//
// • System.StackOverflowException
//
// • System.InvalidProgramException
//
// • System.AccessViolationException
//
// • System.CannotUnloadAppDomainException
//
// • System.BadImageFormatException
//
// • System.DataMisalignedException
//
// Do not throw an exception of such reserved type.
//
// This rule matches methods that create an instance of
// such reserved exception class.
//</Description>
//<HowToFix>
// To fix a violation of this rule, change the type of the
// thrown exception to a specific type that is not one of
// the reserved types.
//
// Concerning the particular case of a method throwing
// *System.NullReferenceException*, often the fix will be either
// to throw instead *System.ArgumentNullException*, either to
// use a contract (through MS Code Contracts API or *Debug.Assert()*)
// to signify that a null reference at that point can only be
// the consequence of a bug.
//
// More generally the idea of using a contract instead of throwing
// an exception in case of *corrupted state / bug consequence* detected
// is a powerful idea. It replaces a behavior (throwing exception)
// with a declarative assertion that basically means: at that point a bug
// somehow provoqued the detected corrupted state and continuing
// any processing from now is potentially harmful. The process should be
// shutdown and the circonstances of the failure should be reported
// as a bug to the product team.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Use integral or string argument for indexers</Name>
warnif count > 0
from m in Application.Methods where
m.IsIndexerGetter &&
!( (m.Name == @"get_Item(String)") ||
m.NameLike (@"get_Item\(Int") ||
m.NameLike (@"get_Item\(Byte") ||
m.NameLike (@"get_Item\(SByte") )
select new {
m,
Debt = 15.ToMinutes().ToDebt(),
Severity = Severity.Medium
}
//<Description>
// Indexers, that is, indexed properties, should use *integer* or *string*
// types for the index. These types are typically used for indexing data
// structures and increase the usability of the library. Use of the *Object*
// type should be restricted to those cases where the specific *integer* or
// *string* type cannot be specified at design time. If the design requires
// other types for the index, reconsider whether the type represents a
// logical data store. If it does not represent a logical data store,
// use a method.
//
// This rule matches indexer getter methods that whose index type
// is not *string*, *int*, *byte* or *sbyte*.
//</Description>
//<HowToFix>
// To fix a violation of this rule, change the index to an *integer* or *string*
// type, or use a method instead of the indexer.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Uri fields should be of type System.Uri</Name>
warnif count > 0 from f in Application.Fields where
(f.NameLike (@"Uri$") ||
f.NameLike (@"Url$")) &&
f.FieldType != null &&
f.FieldType.FullName != "System.Uri"
select new {
f,
f.FieldType,
Debt = 5.ToMinutes().ToDebt(),
Severity = Severity.Low
}
//<Description>
// A field with the name ending with *'Uri'* or *'Url'* is deemed
// to represent a *Uniform Resource Identifier or Locator*.
// Such field should be of type **System.Uri**.
//
// This rule matches fields with the name ending with *'Uri'* or
// *'Url'* that are not typed with *System.Uri*.
//</Description>
//<HowToFix>
// Rename the field, or change the field type to *System.Uri*.
//
// By default issues of this rule have an **Low** severity
// because they reflect more an advice than a problem.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Types should not extend System.ApplicationException</Name>
warnif count > 0 from t in Application.Types where
t.DeriveFrom("System.ApplicationException".AllowNoMatch())
select new {
t,
Debt = 5.ToMinutes().ToDebt(),
Severity = Severity.Medium
}
//<Description>
// At .NET Framework version 1 time, it was
// recommended to derive new exceptions from
// *ApplicationException*.
//
// The recommendation has changed and new
// exceptions should derive from **System.Exception**
// or one of its subclasses in the *System* namespace.
//
// This rule matches application exception classes
// that derive from *ApplicationException*.
//</Description>
//<HowToFix>
// Make sure that matched exception types,
// derive from **System.Exception** or one of its
// subclasses in the *System* namespace.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[from n in Namespaces
where n.Name == "Apache.Ignite.Core.Communication"
from i in n.AllIssues() // If we call Issues() instead of AllIssue() we only get issues on the namespace
select new {
i,
Debt = i.Debt,
AnnualInterest = i.AnnualInterest,
BreakingPoint = i.BreakingPoint
}]]></Query>
</Group>
<Group Name="System.Collection" Active="True" ShownInReport="False">
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Collection properties should be read only</Name>
warnif count > 0
// First find collectionTypes
let collectionInterfaces = ThirdParty.Types.WithFullNameIn(
"System.Collections.ICollection",
"System.Collections.Generic.ICollection<T>")
where collectionInterfaces.Count() > 0
let collectionTypes = Types.ThatImplementAny(collectionInterfaces)
.Union(collectionInterfaces)
.ToHashSet()
// Then find all property setters that have an associated
// getter that returns a collection type.
from propGetter in JustMyCode.Methods.Where(
m => m.IsPropertyGetter &&
m.ReturnType != null &&
collectionTypes.Contains(m.ReturnType))
let propSetter = propGetter.ParentType.Methods.WithSimpleName(
propGetter.SimpleName.Replace("get_","set_")
).FirstOrDefault()
where propSetter != null &&
!propSetter.IsPrivate &&
!propSetter.ParentType.IsPrivate && // Ignore setters of private types
// Ignore properties of serializable types
!propSetter.ParentType.HasAttribute("System.Runtime.Serialization.DataContractAttribute".AllowNoMatch()) &&
!propSetter.ParentType.HasAttribute("System.Xml.Serialization.XmlRootAttribute".AllowNoMatch())
select new {
propSetter,
CollectionType = propGetter.ReturnType,
Debt = 10.ToMinutes().ToDebt(),
Severity = Severity.Medium
}
//<Description>
// A writable collection property allows a user to replace the collection with
// a completely different collection. A read-only property stops the collection
// from being replaced but still allows the individual members to be set. If
// replacing the collection is a goal, the preferred *design pattern* is to include
// a method to remove all the elements from the collection and a method to
// re-populate the collection. See the *Clear()* and *AddRange()* methods of the
// *System.Collections.Generic.List<T>* class for an example of this pattern.
//
// Both binary and XML serialization support read-only properties that are
// collections. The *System.Xml.Serialization.XmlSerializer* class has specific
// requirements for types that implement *ICollection* and *System.Collections.IEnumerable*
// in order to be serializable.
//
// This rule matches property setter methods that assign a collection object.
//</Description>
//<HowToFix>
// To fix a violation of this rule, make the property read-only and, if
// the design requires it, add methods to clear and re-populate the collection.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Don't use .NET 1.x HashTable and ArrayList (deprecated)</Name>
warnif count > 0
let forbiddenTypes = ThirdParty.Types.WithFullNameIn(
"System.Collections.HashTable",
"System.Collections.ArrayList",
"System.Collections.Queue",
"System.Collections.Stack",
"System.Collections.SortedList")
where forbiddenTypes.Count() > 0
from m in Application.Methods.ThatCreateAny(forbiddenTypes)
let forbiddenTypesUsed = m.MethodsCalled.Where(m1 => m1.IsConstructor && forbiddenTypes.Contains(m1.ParentType)).ParentTypes()
select new {
m,
forbiddenTypesUsed,
Debt = 15.ToMinutes().ToDebt(),
Severity = Severity.Medium
}
//<Description>
// This rule has been deprecated and, as a consequence, it is disabled by default.
// Feel free to re-enable it if it makes sense in your dev environment.
//
// This rule warns about application methods that use a non-generic
// collection class, including **ArrayList**, **HashTable**, **Queue**,
// **Stack** or **SortedList**.
//</Description>
//<HowToFix>
// **List<T>** should be preferred over **ArrayList**.
// It is generic hence you get strongly typed elements.
// Also, it is faster with *T* as a value types since it avoids boxing.
//
// For the same reasons:
//
// • **Dictionary<K,V>** should be prevered over **HashTable**.
//
// • **Queue<T>** should be prevered over **Queue**.
//
// • **Stack<T>** should be prevered over **Stack**.
//
// • **SortedDictionary<K,V>** or **SortedList<K,V>** should be prevered over **SortedList**.
//
// You can be forced to use *non generic* collections
// because you are using third party code that requires
// working with these classes or because you are
// coding with .NET 1.x, but nowadays this situation should
// question about using newer updates of .NET.
// .NET 1.x is an immature platform conpared to newer .NET
// updates.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="False" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Caution with List.Contains()</Name>
// warnif count > 0 // This query is n ot a rule per default
let containsMethods = ThirdParty.Methods.WithFullNameIn(
"System.Collections.Generic.List<T>.Contains(T)",
"System.Collections.Generic.IList<T>.Contains(T)",
"System.Collections.ArrayList.Contains(Object)")
from m in Application.Methods.UsingAny(containsMethods)
select new {
m,
Debt = 5.ToMinutes().ToDebt(),
Severity = Severity.Low
}
//<Description>
// This code query matches calls to *List<T>.Contains()* method.
//
// The cost of checking if a list contains an object is proportional
// to the size of the list. In other words it is a *O(N)* operation.
// For large lists and/or frequent calls to *Contains()*, prefer using
// the *System.Collections.Generic.HashSet<T>* class
// where calls to *Contains()* take a constant
// time (*O(0)* operation).
//
// This code query is not a code rule, because more often than not,
// calling *O(N) Contains()* is not a mistake. This code query
// aims at pointing out this potential performance pitfall.
//</Description>]]></Query>
<Query Active="True" DisplayList="False" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Prefer return collection abstraction instead of implementation</Name>
// warnif count > 0 // This query is n ot a rule per default
let implTypes = ThirdParty.Types.WithFullNameIn(
"System.Collections.Generic.List<T>",
"System.Collections.Generic.HashSet<T>",
"System.Collections.Generic.Dictionary<TKey,TValue>")
from m in Application.Methods.WithReturnTypeIn(implTypes)
select new {
m,
m.ReturnType,
Debt = 5.ToMinutes().ToDebt(),
Severity = Severity.Low
}
//<Description>
// This code query matches methods that return a
// collection implementation, such as *List<T>*
// *HashSet<T>* or *Dictionary<TKey,TValue>*.
//
// Most often than not, clients of a method don't
// need to know the exact implementation of the
// collection returned. It is preferable to return
// a collection interface such as *IList<T>*,
// *ICollection<T>*, *IEnumerable<T>* or
// *IDictionary<TKey,TValue>*.
//
// Using the collection interface instead of the
// implementation shouldn't applies to all cases,
// hence this code query is not code rule.
//</Description>]]></Query>
</Group>
<Group Name="System.Runtime.InteropServices" Active="True" ShownInReport="False">
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>P/Invokes should be static and not be publicly visible</Name>
warnif count > 0 from m in Application.Methods where
!m.IsThirdParty &&
(m.HasAttribute ("System.Runtime.InteropServices.DllImportAttribute".AllowNoMatch())) &&
( m.IsPublic ||
!m.IsStatic)
select new {
m,
m.Visibility,
m.IsStatic,
Debt = 5.ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// Methods that are marked with the **DllImportAttribute** attribute
// (or methods that are defined by using the **Declare** keyword in Visual Basic)
// use **Platform Invocation Services** to access unmanaged code.
//
// Such methods should not be exposed. By keeping these methods *private* or *internal*,
// you make sure that your library cannot be used to breach security by allowing
// callers access to unmanaged APIs that they could not call otherwise.
//
// This rule matches methods tagged with *DllImportAttribute* attribute
// that are declared as *public* or declared as *non-static*.
//</Description>
//<HowToFix>
// To fix a violation of this rule, change the access level of the method
// and/or declare it as static.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Move P/Invokes to NativeMethods class</Name>
warnif count > 0 from m in Application.Methods where
m.HasAttribute ("System.Runtime.InteropServices.DllImportAttribute".AllowNoMatch()) &&
m.ParentType.SimpleName != "NativeMethods"
select new {
m,
Debt = 5.ToMinutes().ToDebt(),
Severity = Severity.Medium
}
//<Description>
// **Platform Invocation methods**, such as those that are marked by using the
// **System.Runtime.InteropServices.DllImportAttribute** attribute, or methods
// that are defined by using the **Declare** keyword in Visual Basic, access
// unmanaged code. These methods should be in one of the following classes:
//
// • **NativeMethods** - This class does not suppress stack walks for unmanaged
// code permission. (*System.Security.SuppressUnmanagedCodeSecurityAttribute*
// must not be applied to this class.) This class is for methods that can be
// used anywhere because a stack walk will be performed.
//
// • **SafeNativeMethods** - This class suppresses stack walks for unmanaged
// code permission. (*System.Security.SuppressUnmanagedCodeSecurityAttribute*
//is applied to this class.) This class is for methods that are safe for anyone
// to call. Callers of these methods are not required to perform a full security
// review to make sure that the usage is secure because the methods are harmless
// for any caller.
//
// • **UnsafeNativeMethods** - This class suppresses stack walks for unmanaged
// code permission. (*System.Security.SuppressUnmanagedCodeSecurityAttribute*
// is applied to this class.) This class is for methods that are potentially
// dangerous. Any caller of these methods must perform a full security review
// to make sure that the usage is secure because no stack walk will be performed.
//
// These classes are declared as *static internal*. The methods in these
// classes are *static* and *internal*.
//
// This rule matches *P/Invoke* methods not declared in such *NativeMethods*
// class.
//</Description>
//<HowToFix>
// To fix a violation of this rule, move the method to the appropriate
// **NativeMethods** class. For most applications, moving P/Invokes to a new
// class that is named **NativeMethods** is enough.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>NativeMethods class should be static and internal</Name>
warnif count > 0 from t in Application.Types.WithNameIn(
@"NativeMethods", "SafeNativeMethods", "UnsafeNativeMethods") where
t.IsPublic || !t.IsStatic
select new {
t,
t.Visibility,
t.IsStatic,
Debt = 5.ToMinutes().ToDebt(),
Severity = Severity.Medium
}
//<Description>
// In the description of the default rule *Move P/Invokes to NativeMethods class*
// it is explained that *NativeMethods* classes that host *P/Invoke* methods,
// should be declared as *static* and *internal*.
//
// This code rule warns about *NativeMethods* classes that are not declared
// *static* and *internal*.
//</Description>
//<HowToFix>
// Matched *NativeMethods* classes must be declared as *static* and *internal*.
//</HowToFix>]]></Query>
</Group>
<Group Name="System.Threading" Active="True" ShownInReport="False">
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="True"><![CDATA[// <Name>Don't create threads explicitly</Name>
warnif count > 0 from m in Application.Methods where
m.CreateA ("System.Threading.Thread".AllowNoMatch())
select new {
m,
Debt = 60.ToMinutes().ToDebt(),
Severity = Severity.Critical
}
//<Description>
// This code rule warns about methods that create *threads* explicitly
// by creating an instance of the class *System.Threading.Thread*.
//
// Prefer using the thread pool instead of creating manually your
// own threads. Threads are costly objects. They take approximately
// 200,000 cycles to create and about 100,000 cycles to destroy.
// By default they reserve 1 Mega Bytes of virtual memory for its
// stack and use 2,000-8,000 cycles for each context switch.
//
// As a consequence, it is preferable to let the thread pool
// recycle threads.
//</Description>
//<HowToFix>
// Instead of creating explicitly threads, use the **Task Parralel
// Library** *(TPL)* that relies on the CLR thread pool.
//
// Introduction to TPL: https://msdn.microsoft.com/en-us/library/dd460717(v=vs.110).aspx
//
// TPL and the CLR v4 thread pool:
// http://www.danielmoth.com/Blog/New-And-Improved-CLR-4-Thread-Pool-Engine.aspx
//
// By default issues of this rule have a **Critical** severity
// because creating threads can have severe consequences.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Don't use dangerous threading methods</Name>
warnif count > 0
let wrongMethods = ThirdParty.Methods.WithFullNameIn(
"System.Threading.Thread.Abort()",
"System.Threading.Thread.Abort(Object)",
"System.Threading.Thread.Sleep(Int32)",
"System.Threading.Thread.Suspend()",
"System.Threading.Thread.Resume()")
from m in Application.Methods.UsingAny(wrongMethods)
select new {
m,
suppressCallsTo = m.MethodsCalled.Intersect(wrongMethods),
Debt = 40.ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// This rule warns about using the methods
// *Abort()*, *Sleep()*, *Suspend()* or *Resume()*
// declared by the *Thread* class.
//
// • Usage of *Thread.Abort()* is dangerous.
// More information on this here:
// http://www.interact-sw.co.uk/iangblog/2004/11/12/cancellation
//
// • Usage of *Thread.Sleep()* is a sign of
// flawed design. More information on this here:
// http://msmvps.com/blogs/peterritchie/archive/2007/04/26/thread-sleep-is-a-sign-of-a-poorly-designed-program.aspx
//
// • *Suspend()* and *Resume()* are dangerous threading methods, marked as obsolete.
// More information on workaround here:
// http://stackoverflow.com/questions/382173/what-are-alternative-ways-to-suspend-and-resume-a-thread
//</Description>
//<HowToFix>
// Suppress calls to *Thread* methods exposed in the
// *suppressCallsTo* column in the rule result.
//
// Use instead facilities offered by the **Task Parralel
// Library** *(TPL)* :
// https://msdn.microsoft.com/en-us/library/dd460717(v=vs.110).aspx
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="True"><![CDATA[// <Name>Monitor TryEnter/Exit must be both called within the same method</Name>
warnif count > 0
let enterMethods = ThirdParty.Methods.WithFullNameWildcardMatchIn(
"System.Threading.Monitor.Enter(*",
"System.Threading.Monitor.TryEnter(*")
from m in Application.Methods.UsingAny(enterMethods)
where
!m.IsUsing ("System.Threading.Monitor.Exit(Object)".AllowNoMatch())
select new {
m,
enterMethodsCalled = m.MethodsCalled.Intersect(enterMethods),
Debt = 20.ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// This rule warns when **System.Threading.Monitor** *Enter()*
// (or *TryEnter()*) and *Exit() methods are not called within
// the same method.
//
// Doing so makes the code *less readable*, because it gets harder
// to locate when **critical sections** begin and end.
//
// Also, you expose yourself to complex and error-prone scenarios.
//</Description>
//<HowToFix>
// Refactor matched methods to make sure that *Monitor critical
// sections* begin and end within the same method. Basics scenarios
// can be handled through the C# **lock** keyword. Using explicitly
// the class *Monitor* should be left for advanced situations,
// that require calls to methods like *Wait()* and *Pulse()*.
//
// More information on using the *Monitor* class can be found here:
// http://www.codeproject.com/Articles/13453/Practical-NET-and-C-Chapter
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="True"><![CDATA[// <Name>ReaderWriterLock AcquireLock/ReleaseLock must be both called within the same method</Name>
warnif count > 0
let acquireLockMethods = ThirdParty.Methods.WithFullNameWildcardMatch(
"System.Threading.ReaderWriterLock.Acquire*Lock(*")
let releaseLockMethods = ThirdParty.Methods.WithFullNameWildcardMatch(
"System.Threading.ReaderWriterLock.Release*Lock(*")
from m in Application.Methods.UsingAny(acquireLockMethods)
.Except(Application.Methods.UsingAny(releaseLockMethods))
select new {
m,
acquireLockMethods = m.MethodsCalled.Intersect(acquireLockMethods),
Debt = 20.ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// This rule warns when **System.Threading.ReaderWriterLock**
// acquire and release, reader or writer locks methods are not called
// within the same method.
//
// Doing so makes the code *less readable*, because it gets harder
// to locate when **critical sections** begin and end.
//
// Also, you expose yourself to complex and error-prone scenarios.
//</Description>
//<HowToFix>
// Refactor matched methods to make sure that *ReaderWriterLock
// read or write critical sections* begin and end within the
// same method.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="True"><![CDATA[// <Name>Don't tag instance fields with ThreadStaticAttribute</Name>
warnif count > 0
from f in Application.Fields
where !f.IsStatic &&
f.HasAttribute ("System.ThreadStaticAttribute".AllowNoMatch())
select new {
f,
Debt = 10.ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// This rule warns when the attribute **System.ThreadStaticAttribute**
// is tagging *instance* fields. As explained in documentation, this attribute
// is designed to tag only *static* fields.
// https://msdn.microsoft.com/en-us/library/system.threadstaticattribute
//</Description>
//<HowToFix>
// Refactor the code to make sure that all fields tagged with
// *ThreadStaticAttribute* are *static*.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="False" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Method non-synchronized that read mutable states</Name>
from m in Application.Methods where
(m.ReadsMutableObjectState || m.ReadsMutableTypeState) &&
!m.IsUsing ("System.Threading.Monitor".AllowNoMatch()) &&
!m.IsUsing ("System.Threading.ReaderWriterLock".AllowNoMatch())
select new {
m,
mutableFieldsUsed = m.FieldsUsed.Where(f => !f.IsImmutable)
}
//<Description>
// Mutable object states are instance fields that
// can be modified through the lifetime of the object.
//
// Mutable type states are static fields that can be
// modified through the lifetime of the program.
//
// This query lists methods that read mutable state
// without synchronizing access. In the case of
// multi-threaded program, doing so can lead to
// state corruption.
//
// This code query is not a code rule because more often
// than not, a match of this query is not an issue.
//</Description>]]></Query>
</Group>
<Group Name="System.Xml" Active="True" ShownInReport="False">
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Method should not return concrete XmlNode</Name>
warnif count > 0
let concreteXmlTypes = ThirdParty.Types.ThatDeriveFromAny(
ThirdParty.Types.WithFullName("System.Xml.XmlNode"))
from m in Application.Methods.WithReturnTypeIn(concreteXmlTypes)
select new {
m,
m.ReturnType,
Debt = 10.ToMinutes().ToDebt(),
Severity = Severity.Medium
}
//<Description>
// This rule warns about method whose return type is
// **System.Xml.XmlNode** or any type derived from *XmlNode*.
//
// *XmlNode* implements the interface **System.Xml.Xpath.IXPathNavigable**.
// In most situation, returning this interface instead of the concrete
// type is a better *design* choice that will abstract client code
// from implementation details.
//</Description>
//<HowToFix>
// To fix a violation of this rule, change the concrete returned type
// to the suggested interface *IXPathNavigable* and refactor clients
// code if possible.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Types should not extend System.Xml.XmlDocument</Name>
warnif count > 0 from t in Application.Types where
t.DeriveFrom("System.Xml.XmlDocument".AllowNoMatch())
select new {
t,
Debt = 20.ToMinutes().ToDebt(),
Severity = Severity.Medium
}
//<Description>
// This rule warns aboud subclasses of **System.Xml.XmlDocument**.
//
// Do not create a subclass of *XmlDocument* if you want to
// create an XML view of an underlying object model or data source.
//</Description>
//<HowToFix>
// Instead of subclassing *XmlDocument*, you can use the interface
// **System.Xml.XPath.IXPathNavigable** implemented by the class
// *XmlDocument*.
//
// An alternative of using *XmlDocument*, is to use
// **System.Xml.Linq.XDocument**, aka **LINQ2XML**.
// More information on this can be found here:
// http://stackoverflow.com/questions/1542073/xdocument-or-xmldocument
//</HowToFix>]]></Query>
</Group>
<Group Name="System.Globalization" Active="True" ShownInReport="False">
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Float and Date Parsing must be culture aware</Name>
warnif count > 0
let cultureUnawareMethods =
(from m in ThirdParty.Types.WithFullNameIn(
"System.DateTime",
"System.Single",
"System.Double",
"System.Decimal").ChildMethods()
where m.NbParameters > 0 &&
(m.SimpleName.EqualsAny(
"Parse", "TryParse", "ToString")) &&
!m.Name.Contains("IFormatProvider")
select m).ToHashSet()
from m in Application.Methods.UsingAny(cultureUnawareMethods)
let cultureUnawareMethodsCalled = m.MethodsCalled.Intersect(cultureUnawareMethods)
select new {
m,
shouldntCall = cultureUnawareMethodsCalled,
Debt = (5 + 3*cultureUnawareMethodsCalled.Count()).ToMinutes().ToDebt(),
Severity = 5*cultureUnawareMethodsCalled.Count().ToMinutes().ToAnnualInterest()
}
//<Description>
// Globalization is the design and development of applications that support
// localized user interfaces and regional data for users in multiple cultures.
//
// This rule warns about the usage of *non-globalized overloads* of
// the methods **Parse()**, **TryParse()** and **ToString()**,
// of the types **DateTime**, **float**, **double** and **decimal**.
// This is the symptom that your application is *at least partially*
// not globalized.
//
// *Non-globalized overloads* of these methods are the overloads
// that don't take a parameter of type **IFormatProvider**.
//</Description>
//<HowToFix>
// Globalize your applicaton and make sure to use the globalized overloads
// of these methods. In the column **MethodsCallingMe** of this rule result
// are listed the methods of your application that call the
// *non-globalized overloads*.
//
// More information on **Creating Globally Aware Applications** here:
// https://msdn.microsoft.com/en-us/library/cc853414(VS.95).aspx
//
// The estimated Debt, which means the effort to fix such issue,
// is equal to 5 minutes per application method calling at least one
// non-culture aware method called, plus 3 minutes per non-culture aware
// method called.
//</HowToFix>]]></Query>
</Group>
<Group Name="System.Reflection" Active="True" ShownInReport="False">
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Mark assemblies with assembly version</Name>
warnif count > 0 from a in Application.Assemblies where
!a.HasAttribute ("System.Reflection.AssemblyVersionAttribute".AllowNoMatch())
select new {
a,
Debt = 10.ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// The identity of an assembly is composed of the following information:
//
// • Assembly name
//
// • Version number
//
// • Culture
//
// • Public key (for strong-named assemblies).
//
// The .NET Framework uses the version number to uniquely identify an
// assembly, and to bind to types in strong-named assemblies. The
// version number is used together with version and publisher policy.
// By default, applications run only with the assembly version with
// which they were built.
//
// This rule matches assemblies that are not tagged with
// **System.Reflection.AssemblyVersionAttribute**.
//</Description>
//<HowToFix>
// To fix a violation of this rule, add a version number to the assembly
// by using the *System.Reflection.AssemblyVersionAttribute* attribute.
//</HowToFix>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Assemblies should have the same version</Name>
warnif count > 0
let versionsLookup = Application.Assemblies.ToLookup(a => a.Version, a=> a)
let mostRepresentedVersion = versionsLookup.OrderByDescending(v => v.Count()).First().Key
from v in versionsLookup
where v.Key != mostRepresentedVersion
from a in v.ToArray()
select new {
a ,
version = v.Key,
mostRepresentedVersion,
Debt = 10.ToMinutes().ToDebt(),
Severity = Severity.Medium
}
//<Description>
// This rule reports application assemblies that have a version different
// than the version shared by most of application assemblies.
//
// Before fixing these issues, double check if there is a valid reason
// for dealing with more than one assembly version number.
// Typically this happens when the analyzed code base is made of assemblies
// that are not *compiled, developed or deployed* together.
//</Description>
//<HowToFix>
// If all assemblies of your application should have the same version number,
// just use the attribute **System.Reflection.AssemblyVersion** in a source
// file shared by the assemblies.
//
// Typically this source file is generated by a dedicated *MSBuild* task
// like this one http://www.msbuildextensionpack.com/help/4.0.5.0/html/d6c3b5e8-00d4-c826-1a73-3cfe637f3827.htm.
//
// Here you can find interesting assemblies versioning advices.
// http://stackoverflow.com/a/3905443/27194
//
// By default issues of this rule have a severity set to **major** since
// unproper assemblies versioning can lead to complicated deployment problem.
//</HowToFix>]]></Query>
</Group>
<Group Name="Microsoft.Contracts" Active="True" ShownInReport="False">
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Public methods returning a reference needs a contract to ensure that a non-null reference is returned</Name>
warnif count > 0
let ensureMethods = Application.Methods.WithFullName(
"System.Diagnostics.Contracts.__ContractsRuntime.Ensures(Boolean,String,String)")
from ensureMethod in ensureMethods
from m in ensureMethod.ParentAssembly.ChildMethods where
m.IsPubliclyVisible &&
!m.IsAbstract &&
m.ReturnType != null &&
// Identify that the return type is a reference type
(m.ReturnType.IsClass || m.ReturnType.IsInterface) &&
!m.IsUsing(ensureMethod) &&
// Don't match method not implemented yet!
!m.CreateA("System.NotImplementedException".AllowNoMatch())
select new {
m,
ReturnTypeReference = m.ReturnType,
Debt = (5+3*m.MethodsCallingMe.Count()).ToMinutes().ToDebt(),
Severity = Severity.Medium
}
//<Description>
// **Code Contracts** are useful to decrease ambiguity between callers and callees.
// Not ensuring that a reference returned by a method is *non-null* leaves ambiguity
// for the caller. This rule matches methods returning an instance of a reference type
// (class or interface) that doesn't use a **Contract.Ensure()** method.
//
// *Contract.Ensure()* is defined in the **Microsoft Code Contracts for .NET**
// library, and is typically used to write a code contract on returned reference:
// *Contract.Ensures(Contract.Result<ReturnType>() != null, "returned reference is not null");*
// https://visualstudiogallery.msdn.microsoft.com/1ec7db13-3363-46c9-851f-1ce455f66970
//</Description>
//<HowToFix>
// Use *Microsoft Code Contracts for .NET* on the public surface of your API,
// to remove most ambiguity presented to your client. Most of such ambiguities
// are about *null* or *not null* references.
//
// Don't use *null* reference if you need to define a method that might not
// return a result. Use instead the **TryXXX()** pattern exposed for example
// in the *System.Int32.TryParse()* method.
//
// The estimated Debt, which means the effort to fix such issue, is equal
// to 5 minutes per public application method that might return a null reference
// plus 3 minutes per method calling such method.
//</HowToFix>]]></Query>
</Group>
</Group>
<Group Name="Defining JustMyCode" Active="True" ShownInReport="False">
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Discard generated Assemblies from JustMyCode</Name>
notmycode
from a in Application.Assemblies where
// Assemblies generated for Xsl IL compilation for example are tagged with this attribute
a.HasAttribute ("System.CodeDom.Compiler.GeneratedCodeAttribute".AllowNoMatch())
select a
//<Description>
// This code query is prefixed with **notmycode**.
// This means that all application assemblies matched by this
// code query are removed from the *code base view* **JustMyCode.Assemblies**.
// It also means that all *namespaces*, *types*, *methods* and
// *fields* contained in a matched assembly are removed from
// the code base view *JustMyCode*.
// The code base view *JustMyCode* is used by most default code queries
// and rules.
//
// So far this query only matches application assemblies tagged
// with *System.CodeDom.Compiler.GeneratedCodeAttribute*.
// Make sure to make this query richer to discard your generated
// assemblies from the NDepend rules results.
//
// *notmycode* queries are executed before running others
// queries and rules. Also modifying a *notmycode* query
// provokes re-run of queries and rules that rely
// on the *JustMyCode* code base view.
//
// Several *notmycode* queries can be written to match *assemblies*,
// in which case this results in cumulative effect.
//
// Online documentation:
// http://www.ndepend.com/docs/cqlinq-syntax#NotMyCode
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Discard generated Namespaces from JustMyCode</Name>
notmycode
// First gather assemblies written with VB.NET
let vbnetAssemblies = Application.Assemblies.Where(
a => a.SourceDecls.Any(decl => decl.SourceFile.FileNameExtension.ToLower() == ".vb"))
// Then find the My namespace and its child namespaces.
let vbnetMyNamespaces = vbnetAssemblies.ChildNamespaces().Where(
n => n.SimpleName == "My" ||
n.ParentNamespaces.Any(nParent => nParent.SimpleName == "My"))
from n in vbnetMyNamespaces
select n
//<Description>
// This code query is prefixed with **notmycode**.
// This means that all application namespaces matched by this
// code query are removed from the *code base view* **JustMyCode.Namespaces**.
// It also means that all *types*, *methods* and *fields* contained in a
// matched namespace are removed from the code base view *JustMyCode*.
// The code base view *JustMyCode* is used by most default code queries
// and rules.
//
// So far this query matches the **My** namespaces generated
// by the VB.NET compiler.
//
// *notmycode* queries are executed before running others
// queries and rules. Also modifying a *notmycode* query
// provokes re-run of queries and rules that rely
// on the *JustMyCode* code base view.
//
// Several *notmycode* queries can be written to match *namespaces*,
// in which case this results in cumulative effect.
//
// Online documentation:
// http://www.ndepend.com/docs/cqlinq-syntax#NotMyCode
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Discard generated Types from JustMyCode</Name>
notmycode
// Define some sets to quickly test EntityFramework generated types
let efContexts = Application.Types.Where(t =>
t.DeriveFrom("System.Data.Entity.DbContext".AllowNoMatch()) &&
t.SourceDecls.Count(sd => sd.SourceFile.FileName.ToLower().EndsWithAny(".context.cs", ".context.vb")) == 1).ToHashSet()
let efEntities = Application.Types.UsedByAny(efContexts).ToHashSet()
let efMigrations = Application.Types.Where(t => t.DeriveFrom("System.Data.Entity.Migrations.DbMigration".AllowNoMatch())).ToHashSet()
from t in Application.Types where
// Don't consider anonymous types as JustMyCode
// C# and VB.NET anonymous types generated by the compiler satisfies these conditions
(t.IsGeneratedByCompiler &&
t.ParentNamespace.Name.Length == 0 &&
t.SimpleNameLike("AnonymousType")) ||
// Resources, Settings, or typed DataSet generated types for example, are tagged with this attribute
t.HasAttribute ("System.CodeDom.Compiler.GeneratedCodeAttribute".AllowNoMatch()) ||
// This attribute identifies a type or member that is not part of the user code for an application.
t.HasAttribute ("System.Diagnostics.DebuggerNonUserCodeAttribute".AllowNoMatch()) ||
// Delegate types are always generated
t.IsDelegate ||
// Discard ASP.NET page types generated by aspnet_compiler.exe
// See: http://www.ndepend.com/FAQ.aspx#ASPNET
t.ParentNamespace.Name.EqualsAny("ASP", "__ASP") ||
// Discard Xamarin form generated types that contain the method LoadDataTemplate()
(t.IsNested &&
t.SimpleName.StartsWith("<InitializeComponent>") &&
t.Methods.Count(m => m.SimpleName == "LoadDataTemplate") == 1 &&
t.ParentAssembly.AssembliesUsed.Count(a => a.Name.StartsWith("Xamarin")) > 0) ||
// Discard Xamarin Resource types
(t.IsNested &&
t.ParentType != null &&
t.ParentType.Name == "Resource" &&
t.ParentAssembly.AssembliesUsed.Count(a => a.Name.StartsWith("Xamarin")) > 0) ||
// Discard Entity Framework generated DB context types and entities types used by DB context types!
efContexts.Contains(t) ||
efEntities.Contains(t) ||
efMigrations.Contains(t) ||
// Discard types generated for code contract
t.FullName.StartsWith("System.Diagnostics.Contracts.__ContractsRuntime") ||
t.FullName == "System.Diagnostics.Contracts.RuntimeContractsAttribute" ||
// Discard all types declared in a folder path containing the word "generated"
(t.SourceFileDeclAvailable &&
t.SourceDecls.All(s => s.SourceFile.FilePath.ParentDirectoryPath.ToString().ToLower().Contains("generated")))
select t
//<Description>
// This code query is prefixed with **notmycode**.
// This means that all application types matched by this
// code query are removed from the *code base view* **JustMyCode.Types**.
// It also means that all *methods* and *fields* contained in a
// matched type are removed from the code base view *JustMyCode*.
// The code base view *JustMyCode* is used by most default code queries
// and rules.
//
// So far this query matches several well-identified generated
// types, like the ones tagged with *System.CodeDom.Compiler.GeneratedCodeAttribute*.
// Make sure to make this query richer to discard your generated
// types from the NDepend rules results.
//
// *notmycode* queries are executed before running others
// queries and rules. Also modifying a *notmycode* query
// provokes re-run of queries and rules that rely
// on the *JustMyCode* code base view.
//
// Several *notmycode* queries can be written to match *types*,
// in which case this results in cumulative effect.
//
// Online documentation:
// http://www.ndepend.com/docs/cqlinq-syntax#NotMyCode
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Discard generated and designer Methods from JustMyCode</Name>
notmycode
//
// First define source files paths to discard
//
from a in Application.Assemblies
where a.SourceFileDeclAvailable
let asmSourceFilesPaths = a.SourceDecls.Select(s => s.SourceFile.FilePath)
let sourceFilesPathsToDiscard = (
from filePath in asmSourceFilesPaths
let filePathLower= filePath.ToString().ToLower()
where
filePathLower.EndsWithAny(
".g.cs", // Popular pattern to name generated files.
".g.vb",
".generated.cs",
".generated.vb",
".xaml", // notmycode WPF xaml code
".designer.cs", // notmycode C# Windows Forms designer code
".designer.vb") // notmycode VB.NET Windows Forms designer code
||
// notmycode methods in source files in a directory containing generated
filePathLower.Contains("generated")
select filePath
).ToHashSet()
//
// Second: discard methods in sourceFilesPathsToDiscard
//
from m in a.ChildMethods
where (m.SourceFileDeclAvailable &&
sourceFilesPathsToDiscard.Contains(m.SourceDecls.First().SourceFile.FilePath)) ||
// Generated methods might be tagged with this attribute
m.HasAttribute ("System.CodeDom.Compiler.GeneratedCodeAttribute".AllowNoMatch()) ||
// This attributes identifies a type or member that is not part of the user code for an application.
m.HasAttribute ("System.Diagnostics.DebuggerNonUserCodeAttribute".AllowNoMatch())
select new { m, m.NbLinesOfCode }
//<Description>
// This code query is prefixed with **notmycode**.
// This means that all application methods matched by this
// code query are removed from the *code base view* **JustMyCode.Methods**.
// The code base view *JustMyCode* is used by most default code queries
// and rules.
//
// So far this query matches several well-identified generated
// methods, like the ones tagged with *System.CodeDom.Compiler.GeneratedCodeAttribute*,
// or the ones declared in a source file suffixed with *.designer.cs*.
// Make sure to make this query richer to discard your generated
// methods from the NDepend rules results.
//
// *notmycode* queries are executed before running others
// queries and rules. Also modifying a *notmycode* query
// provokes re-run of queries and rules that rely
// on the *JustMyCode* code base view.
//
// Several *notmycode* queries can be written to match *methods*,
// in which case this results in cumulative effect.
//
// Online documentation:
// http://www.ndepend.com/docs/cqlinq-syntax#NotMyCode
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Discard generated Fields from JustMyCode</Name>
notmycode
from f in Application.Fields where
f.HasAttribute ("System.CodeDom.Compiler.GeneratedCodeAttribute".AllowNoMatch()) ||
// Eliminate "components" generated in Windows Form Control context
f.Name == "components" && f.ParentType.DeriveFrom("System.Windows.Forms.Control".AllowNoMatch())
select f
//<Description>
// This code query is prefixed with **notmycode**.
// This means that all application fields matched by this
// code query are removed from the *code base view* **JustMyCode.Fields**.
// The code base view *JustMyCode* is used by most default code queries
// and rules.
//
// So far this query only matches application fields tagged
// with *System.CodeDom.Compiler.GeneratedCodeAttribute*, and
// *Windows Form* generated fields named *components*.
// Make sure to make this query richer to discard your generated
// fields from the NDepend rules results.
//
// *notmycode* queries are executed before running others
// queries and rules. Also modifying a *notmycode* query
// provokes re-run of queries and rules that rely
// on the *JustMyCode* code base view.
//
// Several *notmycode* queries can be written to match *fields*,
// in which case this results in cumulative effect.
//
// Online documentation:
// http://www.ndepend.com/docs/cqlinq-syntax#NotMyCode
//</Description>]]></Query>
<Query Active="False" DisplayList="False" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>JustMyCode code elements</Name>
from elem in JustMyCode.CodeElements
select new {
elem,
loc = elem.IsMethod? elem.AsMethod.NbLinesOfCode : null
}
//<Description>
// This code query enumerates all
// *assemblies*, *namespaces*, *types*, *methods* and *fields*
// in your application, that are considered as being your code.
//
// This means concretely that the *ICodeBaseView* **JustMyCode**
// only shows these code elements. This code base view is used by
// many default code rule to avoid being warned on code elements
// that you don't consider as your code - typically the code
// elements generated by a tool.
//
// These code elements are the ones that are not matched
// by any quere prefixed with **notmycode**.
//</Description>]]></Query>
<Query Active="False" DisplayList="False" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>NotMyCode code elements</Name>
from elem in Application.CodeElements.Where(element => !JustMyCode.Contains(element))
select new {
elem,
loc = elem.IsMethod? elem.AsMethod.NbLinesOfCode : null
}
//<Description>
// This code query enumerates all
// *assemblies*, *namespaces*, *types*, *methods* and *fields*
// in your application, that are considered as not being your code.
//
// This means concretely that the *ICodeBaseView* **JustMyCode**
// hide these code elements. This code base view is used by
// many default code rules to avoid being warned on code elements
// that you don't consider as your code - typically the code
// elements generated by a tool.
//
// These code elements are the ones matched by queries prefixed with
// **notmycode**.
//</Description>]]></Query>
</Group>
<Group Name="Trend Metrics" Active="True" ShownInReport="False">
<Group Name="Issues" Active="True" ShownInReport="False">
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# New Issues since Baseline" Unit="issues"/>
from issue in Issues
where issue.WasAdded()
select new { issue, issue.Debt, issue.AnnualInterest, issue.Severity }]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# Issues Fixed since Baseline" Unit="issues"/>
from issue in Issues
where issue.WasFixed()
select new { issue, issue.Debt, issue.AnnualInterest, issue.Severity }]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# Issues Worsened since Baseline" Unit="issues"/>
from issue in Issues
where !issue.WasAdded() &&
(issue.DebtDiff() > Debt.Zero || issue.AnnualInterestDiff() > AnnualInterest.Zero)
select new {
issue,
issue.Debt, debtDiff = issue.DebtDiff(),
issue.AnnualInterest, annualInterestDiff = issue.AnnualInterestDiff(),
issue.Severity
}
//<Description>
// An issue is considered worsened if its *debt* increased since the baseline.
//
// Debt documentation: http://www.ndepend.com/docs/technical-debt#Debt
//</Description>
]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# Issues with severity Blocker" Unit="issues"/>
from issue in Issues
where issue.Severity == Severity.Blocker
select new { issue, issue.Debt, issue.AnnualInterest, issue.Severity }
//<Description>
// An issue with the severity **Blocker** cannot move to production, it must be fixed.
//
// The severity of an issue is inferred from the issue *annual interest*
// and thresholds defined in the NDepend Project Properties > Issue and Debt.
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# Issues with severity Critical" Unit="issues"/>
from issue in Issues
where issue.Severity == Severity.Critical
select new { issue, issue.Debt, issue.AnnualInterest, issue.Severity }
//<Description>
// An issue with a severity level **Critical** shouldn't move to production.
// It still can for business imperative needs purposes, but at worth it must be fixed during the next iterations.
//
// The severity of an issue is inferred from the issue *annual interest*
// and thresholds defined in the NDepend Project Properties > Issue and Debt.
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# Issues with severity High" Unit="issues"/>
from issue in Issues
where issue.Severity == Severity.High
select new { issue, issue.Debt, issue.AnnualInterest, issue.Severity }
//<Description>
// An issue with a severity level **High** should be fixed quickly, but can wait until the next scheduled interval.
//
// The severity of an issue is inferred from the issue *annual interest*
// and thresholds defined in the NDepend Project Properties > issue and Debt.
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# Issues with severity Medium" Unit="issues"/>
from issue in Issues
where issue.Severity == Severity.Medium
select new { issue, issue.Debt, issue.AnnualInterest, issue.Severity }
//<Description>
// An issue with a severity level **Medium** is a warning that if not fixed, won't have a significant impact on development.
//
// The severity of an issue is inferred from the issue *annual interest*
// and thresholds defined in the NDepend Project Properties > issue and Debt.
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# Issues with severity Low" Unit="issues"/>
from issue in Issues
where issue.Severity == Severity.Low
select new { issue, issue.Debt, issue.AnnualInterest, issue.Severity }
//<Description>
// The severity level **Low** is used by issues that have a zero, or close to zero,
// value for **Annual Interest**.
//
// Issues with a **Low** or **Medium** severity level represents small improvements,
// ways to make the code looks more elegant.
//
// The **Broken Window Theory** https://en.wikipedia.org/wiki/Broken_windows_theory states that:
//
// *"Consider a building with a few broken windows.
// If the windows are not repaired, the tendency is for vandals to break a few more windows.
// Eventually, they may even break into the building, and if it's unoccupied, perhaps become
// squatters or light fires inside."*
//
// Issues with a *Low* or *Medium* severity level represents the *broken windows* of a code base.
// If they are not fixed, the tendency is for developers to not care for living
// in an elegant code, which will result in extra-maintenance-cost in the long term.
//
// The severity of an issue is inferred from the issue *annual interest*
// and thresholds defined in the NDepend Project Properties > issue and Debt.
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# Blocker/Critical/High Issues" Unit="issues"/>
from issue in Issues
where issue.Severity.EqualsAny(Severity.Blocker, Severity.Critical, Severity.High)
select new { issue, issue.Debt, issue.AnnualInterest, issue.Severity }
//<Description>
// The number of issues with a severity Blocker, Critical or High.
//
// An issue with the severity **Blocker** cannot move to production, it must be fixed.
//
// An issue with a severity level **Critical** shouldn't move to production.
// It still can for business imperative needs purposes, but at worth it must be fixed during the next iterations.
//
// An issue with a severity level **High** should be fixed quickly, but can wait until the next scheduled interval.
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# Issues" Unit="issues"/>
from issue in Issues
select new { issue, issue.Debt, issue.AnnualInterest, issue.Severity }
//<Description>
// The number of issues no matter the issue severity.
//</Description>]]></Query>
</Group>
<Group Name="Rules" Active="True" ShownInReport="False">
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# Rules" Unit="rules"/>
from rule in Rules
select new {
rule,
issues = rule.Issues(),
debt = rule.Debt(),
annualInterest = rule.AnnualInterest(),
maxSeverity = rule.IsViolated() ? (Severity?)rule.Issues().Max(i => i.Severity) : null
}
//<Description>
// This trend metric counts the number of active rules.
// This count includes violated and not violated rules.
// This count includes critical and non critical rules.
//
// When no baseline is available, rules that rely on diff are not counted.
// If you observe that this count slightly decreases with no apparent reason,
// the reason is certainly that rules that rely on diff are not counted
// because the baseline is not defined.
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# Rules Violated" Unit="rules"/>
from rule in Rules
where rule.IsViolated()
select new {
rule,
issues = rule.Issues(),
debt = rule.Debt(),
annualInterest = rule.AnnualInterest(),
maxSeverity = rule.Issues().Max(i => i.Severity)
}
//<Description>
// This trend metric counts the number of active rules that are violated.
// This count includes critical and non critical rules.
//
// When no baseline is available, rules that rely on diff are not counted.
// If you observe that this count slightly decreases with no apparent reason,
// the reason is certainly that rules that rely on diff are not counted
// because the baseline is not defined.
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# Critical Rules Violated" Unit="rules"/>
from rule in Rules
where rule.IsViolated() && rule.IsCritical
select new {
rule,
issues = rule.Issues(),
debt = rule.Debt(),
annualInterest = rule.AnnualInterest(),
maxSeverity = rule.Issues().Max(i => i.Severity)
}
//<Description>
// This trend metric counts the number of critical active rules that are violated.
//
// The concept of critical rule is useful to pinpoint certain rules that should not be violated.
//
// When no baseline is available, rules that rely on diff are not counted.
// If you observe that this count slightly decreases with no apparent reason,
// the reason is certainly that rules that rely on diff are not counted
// because the baseline is not defined.
//</Description>]]></Query>
</Group>
<Group Name="Quality Gates" Active="True" ShownInReport="False">
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# Quality Gates" Unit="quality gates"/>
from qualityGate in QualityGates
select new {
qualityGate ,
qualityGate.ValueString,
qualityGate.Status,
}
//<Description>
// This trend metric counts the number of active quality gates,
// no matter the gate status (Pass, Warn, Fail).
//
// When no baseline is available, quality gates that rely on diff are not counted.
// If you observe that this count slightly decreases with no apparent reason,
// the reason is certainly that quality gates that rely on diff are not counted
// because the baseline is not defined.
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# Quality Gates Warn" Unit="quality gates"/>
from qualityGate in QualityGates
where qualityGate.Warn
select new {
qualityGate ,
qualityGate.ValueString,
}
//<Description>
// This trend metric counts the number of active quality gates that warns.
//
// When no baseline is available, quality gates that rely on diff are not counted.
// If you observe that this count slightly decreases with no apparent reason,
// the reason is certainly that quality gates that rely on diff are not counted
// because the baseline is not defined.
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# Quality Gates Fail" Unit="quality gates"/>
from qualityGate in QualityGates
where qualityGate.Fail
select new {
qualityGate ,
qualityGate.ValueString,
}
//<Description>
// This trend metric counts the number of active quality gates that fails.
//
// When no baseline is available, quality gates that rely on diff are not counted.
// If you observe that this count slightly decreases with no apparent reason,
// the reason is certainly that quality gates that rely on diff are not counted
// because the baseline is not defined.
//</Description>]]></Query>
</Group>
<Group Name="Debt" Active="True" ShownInReport="False">
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="Percentage Debt (Metric)" Unit="%" />
let timeToDev = codeBase.EffortToDevelop()
let debt = Issues.Sum(i => i.Debt)
select 100d * debt.ToManDay() / timeToDev.ToManDay()
// <Description>
// This Trend Metric name is suffixed with (Metric)
// to avoid query name collision with the Quality Gate with same name.
//
// Infer a percentage from:
//
// • the estimated total time to develop the code base
//
// • and the the estimated total time to fix all issues (the Debt).
//
// Estimated total time to develop the code base is inferred from
// # lines of code of the code base and from the
// *Estimated number of man-day to develop 1000 logicial lines of code*
// setting found in NDepend Project Properties > Issue and Debt.
//
// Debt documentation: http://www.ndepend.com/docs/technical-debt#Debt
// </Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="Debt (Metric)" Unit="man-days" />
Issues.Sum(i => i.Debt).ToManDay()
//<Description>
// This Trend Metric name is suffixed with (Metric)
// to avoid query name collision with the Quality Gate with same name.
//
// Debt documentation: http://www.ndepend.com/docs/technical-debt#Debt
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="New Debt since Baseline (Metric)" Unit="man-days" />
let debt = Issues.Sum(i => i.Debt)
let debtInBaseline = IssuesInBaseline.Sum(i => i.Debt)
select (debt - debtInBaseline).ToManDay()
//<Description>
// This Trend Metric name is suffixed with (Metric)
// to avoid query name collision with the Quality Gate with same name.
//
// Debt added (or fixed if negative) since baseline.
//
// Debt documentation: http://www.ndepend.com/docs/technical-debt#Debt
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="Annual Interest (Metric)" Unit="man-days" />
Issues.Sum(i => i.AnnualInterest).ToManDay()
//<Description>
// This Trend Metric name is suffixed with (Metric)
// to avoid query name collision with the Quality Gate with same name.
//
// Annual Interest documentation: http://www.ndepend.com/docs/technical-debt#AnnualInterest
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="New Annual Interest since Baseline (Metric)" Unit="man-days" />
let ai = Issues.Sum(i => i.AnnualInterest)
let aiInBaseline = IssuesInBaseline.Sum(i => i.AnnualInterest)
select (ai - aiInBaseline).ToManDay()
//<Description>
// This Trend Metric name is suffixed with (Metric)
// to avoid query name collision with the Quality Gate with same name.
//
// Annual Interest added (or fixed if negative) since baseline.
//
// Annual Interest documentation: http://www.ndepend.com/docs/technical-debt#AnnualInterest
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="Breaking Point" Unit="years"/>
(Issues.Sum(i =>i.Debt).BreakingPoint(Issues.Sum(i =>i.AnnualInterest))).TotalYears()
// <Description>
// The **breaking point** of a set of issues is the **debt** divided by the **annual interest**.
//
// The *debt* is the estimated cost-to-fix the issues.
//
// The *annual interest* is the estimated cost-to-**not**-fix the issues, per year.
//
// Hence the *breaking point* is the point in time from now, when not fixing the issues cost as much as fixing the issue.
//
// Breaking Point documentation: http://www.ndepend.com/docs/technical-debt#BreakingPoint
// </Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="Breaking Point of Blocker / Critical / High Issues" Unit="years"/>
let issues = Issues.Where(i => i.Severity.EqualsAny(Severity.Blocker, Severity.Critical, Severity.High))
select (issues.Sum(i =>i.Debt).BreakingPoint(issues.Sum(i =>i.AnnualInterest))).TotalYears()
// <Description>
// The **breaking point** of a set of issues is the **debt** divided by the **annual interest**.
//
// The *debt* is the estimated cost-to-fix the issues.
//
// The *annual interest* is the estimated cost-to-**not**-fix the issues, per year.
//
// Hence the *breaking point* is the point in time from now, when not fixing the issues cost as much as fixing the issue.
//
// Breaking Point documentation: http://www.ndepend.com/docs/technical-debt#BreakingPoint
// </Description>]]></Query>
</Group>
<Group Name="Code Size" Active="True" ShownInReport="False">
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# Lines of Code" Unit="LoC" />
codeBase.NbLinesOfCode]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# Lines of Code (JustMyCode)" Unit="LoC" />
JustMyCode.Methods.Sum(m => m.NbLinesOfCode)
// JustMyCode is defined by code queries prefixed with 'notmycode'
// in the group 'Defining JustMyCode'.
]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# Lines of Code (NotMyCode)" Unit="LoC" />
Application.Methods.Except(JustMyCode.Methods).Sum(m => m.NbLinesOfCode)
// JustMyCode is defined by code queries prefixed with 'notmycode'
// in the group 'Defining JustMyCode'.
]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# Lines of Code Added since the Baseline" Unit="LoC" />
from a in Application.Assemblies
let nbLocAdded = !a.IsPresentInBothBuilds()
? a.NbLinesOfCode
: (a.NbLinesOfCode != null && a.OlderVersion().NbLinesOfCode != null)
? a.NbLinesOfCode - (int)a.OlderVersion().NbLinesOfCode
: 0
select (double?)nbLocAdded
// A value is computed by this Trend Metric query
// only if a Baseline for Comparison is provided.
// See Project Properties > Analysis > Baseline for Comparison
]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# Source Files" Unit="Source Files" />
Application.Assemblies.SelectMany(
a => a.SourceDecls.Select(sd => sd.SourceFile.FilePathString.ToLower()))
.Distinct()
.Count()
//<Description>
// This trend metric counts the number of source files.
//
// If a value 0 is obtained, it means that at analysis time,
// assemblies PDB files were not available.
// http://www.ndepend.com/docs/ndepend-analysis-inputs-explanation
//
// So far source files cannot be matched by a code query.
// However editing the query "Application Types" and then
// *Group by source file declarations* will list source files
// with types source declarations.
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# IL Instructions" Unit="IL Instructions" />
codeBase.NbILInstructions
]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# IL Instructions (NotMyCode)" Unit="IL Instructions" />
Application.Methods.Except(JustMyCode.Methods).Sum(m => m.NbILInstructions)
// JustMyCode is defined by code queries prefixed with 'notmycode'
// in the group 'Defining JustMyCode'.
]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# Lines of Comments" Unit="Lines" />
codeBase.NbLinesOfComment
//<Description>
// This trend metric returns the number of lines of comment
// counted in application source files.
//
// So far commenting information is only extracted from C# source code
// and VB.NET support is planned.
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="Percentage of Comments" Unit="%" />
codeBase.PercentageComment
//<Description>
// This trend metric returns the percentage of comment
// compared to the number of **logical**lines of code.
//
// So far commenting information is only extracted from C# source code
// and VB.NET support is planned.
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# Assemblies" Unit="Assemblies" />
from a in Application.Assemblies
select new {
a,
Debt = a.AllDebt(),
Issues = a.AllIssues()
}
//<Description>
// This trend metric query counts all application assemblies.
// For each assembly it shows the estimated **all** technical-debt and **all** issues.
// **All** means debt and issues of the assembly and of its child namespaces, types and members.
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# Namespaces" Unit="Namespaces" />
from n in Application.Namespaces
select new {
n,
Debt = n.AllDebt(),
Issues = n.AllIssues()
}
//<Description>
// This trend metric query counts all application namespaces.
// For each namespace it shows the estimated **all** technical-debt and **all** issues.
// **All** means debt and issues of the namespace and of its child types and members.
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# Types" Unit="Types" />
from t in Application.Types.Where(t => !t.IsGeneratedByCompiler)
select new {
t,
Debt = t.AllDebt(),
Issues = t.AllIssues()
}
//<Description>
// This trend metric query counts all application types non-generated by compiler.
// For each type it shows the estimated **all** technical-debt and **all** issues.
// **All** means debt and issues of the type and of its child members.
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# Public Types" Unit="Types" />
Application.Types.Where(t => t.IsPubliclyVisible && !t.IsGeneratedByCompiler)]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# Classes" Unit="Types" />
Application.Types.Where(t => t.IsClass && !t.IsGeneratedByCompiler)]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# Abstract Classes" Unit="Types" />
Application.Types.Where(t => t.IsClass && t.IsAbstract && !t.IsGeneratedByCompiler)]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# Interfaces" Unit="Types" />
Application.Types.Where(t => t.IsInterface)]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# Structures" Unit="Types" />
Application.Types.Where(t => t.IsStructure && !t.IsGeneratedByCompiler)]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# Methods" Unit="Methods" />
from m in Application.Methods.Where(m => !m.IsGeneratedByCompiler)
select new {
m,
Debt = m.Debt(),
Issues = m.Issues()
}
//<Description>
// This trend metric query counts all application methods non-generated by compiler.
// For each method it shows the estimated technical-debt and the issues.
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# Abstract Methods" Unit="Methods" />
Application.Methods.Where(m => m.IsAbstract)]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# Concrete Methods" Unit="Methods" />
Application.Methods.Where(m => !m.IsAbstract && !m.IsGeneratedByCompiler)]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# Fields" Unit="Fields" />
from f in Application.Fields.Where(f =>
!f.IsEnumValue &&
!f.ParentType.IsEnumeration &&
!f.IsGeneratedByCompiler &&
!f.IsLiteral)
select new {
f,
Debt = f.AllDebt(),
Issues = f.AllIssues()
}
//<Description>
// This trend metric query counts all application fields non-generated by compiler
// that are not enumeration values nor constant values.
// For each field it shows the estimated technical-debt and the issues.
//</Description>]]></Query>
</Group>
<Group Name="Maximum and Average" Active="True" ShownInReport="False">
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="Max # Lines of Code for Methods (JustMyCode)" Unit="LoC" />
JustMyCode.Methods
.Max(m => m.NbLinesOfCode)
// Here is the code query to get the (JustMyCode) method with largest # Lines of Code
// JustMyCode.Methods.OrderByDescending(m => m.NbLinesOfCode).Take(1).Select(m => new {m, m.NbLinesOfCode})]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="Average # Lines of Code for Methods" Unit="LoC" />
Application.Methods.Where(m => m.NbLinesOfCode > 0)
.Average(m => m.NbLinesOfCode)]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="Average # Lines of Code for Methods with at least 3 Lines of Code" Unit="LoC" />
Application.Methods.Where(m => m.NbLinesOfCode >= 3)
.Average(m => m.NbLinesOfCode)]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="Max # Lines of Code for Types (JustMyCode)" Unit="LoC" />
JustMyCode.Types
.Max(t => t.NbLinesOfCode)
// Here is the code query to get the (JustMyCode) type with largest # Lines of Code
// JustMyCode.Types.OrderByDescending(t => t.NbLinesOfCode).Take(1).Select(t => new {t, t.NbLinesOfCode})]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="Average # Lines of Code for Types" Unit="LoC" />
Application.Types.Where(t => t.NbLinesOfCode > 0)
.Average(t => t.NbLinesOfCode)]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="Max Cyclomatic Complexity for Methods" Unit="Paths" />
Application.Methods
.Max(m => m.CyclomaticComplexity)
// Here is the code query to get the most complex method, according to Cyclomatic Complexity
// Application.Methods.OrderByDescending(m => m.CyclomaticComplexity).Take(1).Select(m => new {m, m.CyclomaticComplexity})]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="Average Cyclomatic Complexity for Methods" Unit="Paths" />
Application.Methods.Where(m => m.NbLinesOfCode> 0)
.Average(m => m.CyclomaticComplexity)]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="Max IL Cyclomatic Complexity for Methods" Unit="Paths" />
Application.Methods
.Max(m => m.ILCyclomaticComplexity)
// Here is the code query to get the most complex method, according to Cyclomatic Complexity computed from IL code.
// Application.Methods.OrderByDescending(m => m.ILCyclomaticComplexity).Take(1).Select(m => new {m, m.CyclomaticComplexity})]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="Average IL Cyclomatic Complexity for Methods" Unit="Paths" />
Application.Methods.Where(m => m.NbILInstructions> 0)
.Average(m => m.ILCyclomaticComplexity)]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="Max IL Nesting Depth for Methods" Unit="Scopes" />
Application.Methods
.Max(m => m.ILNestingDepth)
// Here is the code query to get the method with higher ILNestingDepth.
// Application.Methods.OrderByDescending(m => m.ILNestingDepth).Take(1).Select(m => new {m, m.ILNestingDepth})]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="Average IL Nesting Depth for Methods" Unit="Scopes" />
Application.Methods.Where(m => m.NbILInstructions> 0)
.Average(m => m.ILNestingDepth)]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="Max # of Methods for Types" Unit="Methods" />
Application.Types
.Max(t => t.NbMethods)
// Here is the code query to get the (JustMyCode) type with largest # of Methods
// JustMyCode.Types.OrderByDescending(t => t.NbMethods).Take(1).Select(t => new {t, t.Methods})]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="Average # Methods for Types" Unit="Methods" />
Application.Types.Average(t => t.NbMethods)]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="Max # of Methods for Interfaces" Unit="Methods" />
Application.Types.Where(t => t.IsInterface)
.Max(t => t.NbMethods)
// Here is the code query to get the (JustMyCode) type with largest # of Methods
// JustMyCode.Types.OrderByDescending(t => t.NbMethods).Take(1).Select(t => new {t, t.Methods})]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="Average # Methods for Interfaces" Unit="Methods" />
JustMyCode.Types.Where(t => t.IsInterface)
.Average(t => t.NbMethods)]]></Query>
</Group>
<Group Name="Coverage" Active="True" ShownInReport="False">
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="Percentage Code Coverage" Unit="%" />
codeBase.PercentageCoverage]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# Lines of Code Covered" Unit="LoC" />
codeBase.NbLinesOfCodeCovered]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# Lines of Code Not Covered" Unit="LoC" />
codeBase.NbLinesOfCodeNotCovered]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# Lines of Code Uncoverable" Unit="LoC" />
(codeBase.NbLinesOfCode - codeBase.NbLinesOfCodeCovered - codeBase.NbLinesOfCodeNotCovered).ToNullableDouble()
//<Description>
// **Lines of Code Uncoverable** are lines of code in methods tagged with the *Uncoverable attribute*
// or methods in types or assemblies tagged with the *Uncoverable attribute*.
//
// The *Uncoverable attribute* is defined in the:
// NDepend Project Properties > Analysis > Code Coverage > Un-Coverable attributes.
//
// These methods can be listed with the code query:
// *from m in Methods where !m.CoverageDataAvailable && m.NbLinesOfCode > 0 select new { m, m.NbLinesOfCode }*
//
// If coverage data imported at analysis time is not *in-sync* with the analyzed code base,
// this code query will also list methods not defined in the coverage data imported.
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# Lines of Code in Types 100% Covered" Unit="LoC" />
Application.Types.Where(t => t.PercentageCoverage == 100)
.Sum(t => t.NbLinesOfCodeCovered)
//<Description>
// A line of code covered by tests is *even more valuable* if it is in a type 100% covered by test.
//
// Covering 90% of a class is not enough.
//
// • It means that this 10% uncovered code is hard-to-test,
//
// • which means that this code is not well-designed,
//
// • which means that it is error-prone.
//
// Better test error-prone code, isn't it?
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# Lines of Code in Methods 100% Covered" Unit="LoC" />
Application.Methods.Where(m => m.PercentageCoverage == 100)
.Sum(m => m.NbLinesOfCodeCovered)
//<Description>
// The same remark than in the Trend Metric **# Lines of Code in Types 100% Covered**
// applies for method 100% covered.
//
// A line of code covered by tests is *even more valuable* if it is in a method 100% covered by test.
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="Max C.R.A.P Score" />
(from m in JustMyCode.Methods
// Don't match too short methods
where m.NbLinesOfCode > 10
let CC = m.CyclomaticComplexity
let uncov = (100 - m.PercentageCoverage) / 100f
let CRAP = (CC * CC * uncov * uncov * uncov) + CC
where CRAP != null && CRAP > 30 select CRAP)
.Max(CRAP => CRAP)
//<Description>
// **Change Risk Analyzer and Predictor** (i.e. CRAP) is a code metric
// that helps in pinpointing overly complex and untested code.
// Is has been first defined here:
// http://www.artima.com/weblogs/viewpost.jsp?thread=215899
//
// The Formula is: **CRAP(m) = CC(m)^2 * (1 – cov(m)/100)^3 + CC(m)**
//
// • where *CC(m)* is the *cyclomatic complexity* of the method *m*
//
// • and *cov(m)* is the *percentage coverage* by tests of the method *m*
//
// Matched methods cumulates two highly *error prone* code smells:
//
// • A complex method, difficult to develop and maintain.
//
// • Non 100% covered code, difficult to refactor without any regression bug.
//
// The higher the CRAP score, the more painful to maintain and error prone is the method.
//
// An arbitrary threshold of 30 is fixed for this code rule as suggested by inventors.
//
// Notice that no amount of testing will keep methods with a Cyclomatic Complexity
// higher than 30, out of CRAP territory.
//
// Notice that CRAP score is not computed for too short methods
// with less than 10 lines of code.
//
// To list methods with higher C.R.A.P scores, please refer to the default rule:
// *Test and Code Coverage* > *C.R.A.P method code metric*
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="Average C.R.A.P Score" />
(from m in JustMyCode.Methods
// Don't match too short methods
where m.NbLinesOfCode > 10
let CC = m.CyclomaticComplexity
let uncov = (100 - m.PercentageCoverage) / 100f
let CRAP = (CC * CC * uncov * uncov * uncov) + CC
where CRAP != null && CRAP > 30 select CRAP)
.Average(CRAP => CRAP)
//<Description>
// **Change Risk Analyzer and Predictor** (i.e. CRAP) is a code metric
// that helps in pinpointing overly complex and untested code.
// Is has been first defined here:
// http://www.artima.com/weblogs/viewpost.jsp?thread=215899
//
// The Formula is: **CRAP(m) = CC(m)^2 * (1 – cov(m)/100)^3 + CC(m)**
//
// • where *CC(m)* is the *cyclomatic complexity* of the method *m*
//
// • and *cov(m)* is the *percentage coverage* by tests of the method *m*
//
// Matched methods cumulates two highly *error prone* code smells:
//
// • A complex method, difficult to develop and maintain.
//
// • Non 100% covered code, difficult to refactor without any regression bug.
//
// The higher the CRAP score, the more painful to maintain and error prone is the method.
//
// An arbitrary threshold of 30 is fixed for this code rule as suggested by inventors.
//
// Notice that no amount of testing will keep methods with a Cyclomatic Complexity
// higher than 30, out of CRAP territory.
//
// Notice that CRAP score is not computed for too short methods
// with less than 10 lines of code.
//
// To list methods with higher C.R.A.P scores, please refer to the default rule:
// *Test and Code Coverage* > *C.R.A.P method code metric*
//</Description>]]></Query>
</Group>
<Group Name="Third-Party Usage" Active="True" ShownInReport="False">
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# Third-Party Assemblies Used" Unit="Assemblies" />
from a in ThirdParty.Assemblies
select new { a, a.AssembliesUsingMe }]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# Third-Party Namespaces Used" Unit="Namespaces" />
from n in ThirdParty.Namespaces
select new { n, n.NamespacesUsingMe }]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# Third-Party Types Used" Unit="Types" />
from t in ThirdParty.Types
select new { t, t.TypesUsingMe }]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# Third-Party Methods Used" Unit="Methods" />
from m in ThirdParty.Methods
select new { m, m.MethodsCallingMe }]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# Third-Party Fields Used" Unit="Fields" />
from f in ThirdParty.Fields
where !f.ParentType.IsEnumeration
select new { f, f.MethodsUsingMe }]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="True" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <TrendMetric Name="# Third-Party Code Elements Used" Unit="Code Elements" />
from elem in ThirdParty.CodeElements
where !(elem.IsField && elem.AsField.ParentType.IsEnumeration)
let users = elem.IsMethod ? elem.AsMethod.MethodsCallingMe.Cast<ICodeElement>() :
elem.IsField ? elem.AsField.MethodsUsingMe.Cast<ICodeElement>() :
elem.IsType ? elem.AsType.TypesUsingMe.Cast<ICodeElement>() :
elem.IsNamespace ? elem.AsNamespace.NamespacesUsingMe.Cast<ICodeElement>() :
elem.AsAssembly.AssembliesUsingMe.Cast<ICodeElement>()
select new { elem, users }
]]></Query>
</Group>
</Group>
<Group Name="Code Diff Summary" Active="True" ShownInReport="True">
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>New assemblies</Name>
from a in Application.Assemblies where a.WasAdded()
select new { a, a.NbLinesOfCode }
//<Description>
// This query is executed only if a *baseline for comparison* is defined (*diff mode*).
//
// This code query lists *assemblies* that have been added since the *baseline*.
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Assemblies removed</Name>
from a in codeBase.OlderVersion().Application.Assemblies where a.WasRemoved()
select new { a, a.NbLinesOfCode }
//<Description>
// This query is executed only if a *baseline for comparison* is defined (*diff mode*).
//
// This code query lists *assemblies* that have been removed since the *baseline*.
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Assemblies where code was changed</Name>
from a in Application.Assemblies where a.CodeWasChanged()
select new {
a,
a.NbLinesOfCode,
oldNbLinesOfCode = a.OlderVersion().NbLinesOfCode.GetValueOrDefault() ,
delta = (int) a.NbLinesOfCode.GetValueOrDefault() - a.OlderVersion().NbLinesOfCode.GetValueOrDefault()
}
//<Description>
// This query is executed only if a *baseline for comparison* is defined (*diff mode*).
//
// This code query lists *assemblies* in which, code has been changed since the *baseline*.
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>New namespaces</Name>
from n in Application.Namespaces where
!n.ParentAssembly.WasAdded() &&
n.WasAdded()
select new { n, n.NbLinesOfCode }
//<Description>
// This query is executed only if a *baseline for comparison* is defined (*diff mode*).
//
// This code query lists *namespaces* that have been added since the *baseline*.
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Namespaces removed</Name>
from n in codeBase.OlderVersion().Application.Namespaces where
!n.ParentAssembly.WasRemoved() &&
n.WasRemoved()
select new { n, n.NbLinesOfCode }
//<Description>
// This query is executed only if a *baseline for comparison* is defined (*diff mode*).
//
// This code query lists *namespaces* that have been removed since the *baseline*.
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Namespaces where code was changed</Name>
from n in Application.Namespaces where n.CodeWasChanged()
select new {
n,
n.NbLinesOfCode,
oldNbLinesOfCode = n.OlderVersion().NbLinesOfCode.GetValueOrDefault() ,
delta = (int) n.NbLinesOfCode.GetValueOrDefault() - n.OlderVersion().NbLinesOfCode.GetValueOrDefault()
}
//<Description>
// This query is executed only if a *baseline for comparison* is defined (*diff mode*).
//
// This code query lists *namespaces* in which, code has been changed since the *baseline*.
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>New types</Name>
from t in Application.Types where
!t.ParentNamespace.WasAdded() &&
t.WasAdded() &&
!t.IsGeneratedByCompiler
select new { t, t.NbLinesOfCode }
//<Description>
// This query is executed only if a *baseline for comparison* is defined (*diff mode*).
//
// This code query lists *types* that have been added since the *baseline*.
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Types removed</Name>
from t in codeBase.OlderVersion().Application.Types where
!t.ParentNamespace.WasRemoved() &&
t.WasRemoved() &&
!t.IsGeneratedByCompiler
select new { t, t.NbLinesOfCode }
//<Description>
// This query is executed only if a *baseline for comparison* is defined (*diff mode*).
//
// This code query lists *types* that have been removed since the *baseline*.
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Types where code was changed</Name>
from t in Application.Types where t.CodeWasChanged()
//select new { t, t.NbLinesOfCode }
select new {
t,
t.NbLinesOfCode,
oldNbLinesOfCode = t.OlderVersion().NbLinesOfCode ,
delta = (int?) t.NbLinesOfCode - t.OlderVersion().NbLinesOfCode
}
//<Description>
// This query is executed only if a *baseline for comparison* is defined (*diff mode*).
//
// This code query lists *types* in which, code has been changed since the *baseline*.
//
// To visualize changes in code, right-click a matched type and select:
//
// • Compare older and newer versions of source file
//
// • Compare older and newer versions disassembled with Reflector
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Heuristic to find types moved from one namespace or assembly to another</Name>
let typesRemoved = codeBase.OlderVersion().Types.Where(t => t.WasRemoved())
let typesAdded = Types.Where(t => t.WasAdded())
from tMoved in typesAdded.Join(
typesRemoved,
t => t.Name,
t => t.Name,
(tNewer, tOlder) => new { tNewer,
OlderParentNamespace = tOlder.ParentNamespace,
OlderParentAssembly = tOlder.ParentAssembly } )
select tMoved
//<Description>
// This query is executed only if a *baseline for comparison* is defined (*diff mode*).
//
// This code query lists *types* moved from one namespace or assembly to another.
// The heuristic implemented consists in making a **join LINQ query** on
// type name (without namespace prefix), applied to the two sets of types *added*
// and types *removed*.
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Types directly using one or several types changed</Name>
let typesChanged = Application.Types.Where(t => t.CodeWasChanged()).ToHashSet()
from t in JustMyCode.Types.UsingAny(typesChanged) where
!t.CodeWasChanged() &&
!t.WasAdded()
let typesChangedUsed = t.TypesUsed.Intersect(typesChanged)
select new { t, typesChangedUsed }
//<Description>
// This query is executed only if a *baseline for comparison* is defined (*diff mode*).
//
// This code query lists types *unchanged* since the *baseline*
// but that use directly some *types* where code has been changed
// since the *baseline*.
//
// For such matched type, the code hasen't been changed, but still the overall
// behavior might have been changed.
//
// The query result includes types changed directly used,
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Types indirectly using one or several types changed</Name>
let typesChanged = Application.Types.Where(t => t.CodeWasChanged()).ToHashSet()
// 'depth' represents a code metric defined on types using
// directly or indirectly any type where code was changed.
let depth = JustMyCode.Types.DepthOfIsUsingAny(typesChanged)
from t in depth.DefinitionDomain where
!t.CodeWasChanged() &&
!t.WasAdded()
let typesChangedDirectlyUsed = t.TypesUsed.Intersect(typesChanged)
let depthOfUsingTypesChanged = depth[t]
orderby depthOfUsingTypesChanged
select new {
t,
depthOfUsingTypesChanged,
typesChangedDirectlyUsed
}
//<Description>
// This query is executed only if a *baseline for comparison* is defined (*diff mode*).
//
// This code query lists types *unchanged* since the *baseline*
// but that **use directly or indirectly** some *types* where
// code has been changed since the *baseline*.
//
// For such matched type, the code hasen't been changed, but still the overall
// behavior might have been changed.
//
// The query result includes types changed directly used, and the **depth of usage**
// of types indirectly used, *depth of usage* as defined in the documentation of
// *DepthOfIsUsingAny()* NDepend API method:
// http://www.ndepend.com/api/webframe.html?NDepend.API~NDepend.CodeModel.ExtensionMethodsSequenceUsage~DepthOfIsUsingAny.html
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>New methods</Name>
from m in Application.Methods where
!m.ParentType.WasAdded() &&
m.WasAdded() &&
!m.IsGeneratedByCompiler
select new { m, m.NbLinesOfCode }
//<Description>
// This query is executed only if a *baseline for comparison* is defined (*diff mode*).
//
// This code query lists *methods* that have been added since the *baseline*.
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Methods removed</Name>
from m in codeBase.OlderVersion().Application.Methods where
!m.ParentType.WasRemoved() &&
m.WasRemoved() &&
!m.IsGeneratedByCompiler
select new { m, m.NbLinesOfCode }
//<Description>
// This query is executed only if a *baseline for comparison* is defined (*diff mode*).
//
// This code query lists *methods* that have been removed since the *baseline*.
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Methods where code was changed</Name>
from m in Application.Methods where m.CodeWasChanged()
select new {
m,
m.NbLinesOfCode,
oldNbLinesOfCode = m.OlderVersion().NbLinesOfCode ,
delta = (int?) m.NbLinesOfCode - m.OlderVersion().NbLinesOfCode
}
//<Description>
// This query is executed only if a *baseline for comparison* is defined (*diff mode*).
//
// This code query lists *methods* in which, code has been changed since the *baseline*.
//
// To visualize changes in code, right-click a matched method and select:
//
// • Compare older and newer versions of source file
//
// • Compare older and newer versions disassembled with Reflector
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Methods directly calling one or several methods changed</Name>
let methodsChanged = Application.Methods.Where(m => m.CodeWasChanged()).ToHashSet()
from m in JustMyCode.Methods.UsingAny(methodsChanged ) where
!m.CodeWasChanged() &&
!m.WasAdded()
let methodsChangedCalled = m.MethodsCalled.Intersect(methodsChanged)
select new {
m,
methodsChangedCalled
}
//<Description>
// This query is executed only if a *baseline for comparison* is defined (*diff mode*).
//
// This code query lists methods *unchanged* since the *baseline*
// but that call directly some *methods* where code has been changed
// since the *baseline*.
//
// For such matched method, the code hasen't been changed, but still the overall
// behavior might have been changed.
//
// The query result includes methods changed directly used,
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Methods indirectly calling one or several methods changed</Name>
let methodsChanged = Application.Methods.Where(m => m.CodeWasChanged()).ToHashSet()
// 'depth' represents a code metric defined on methods using
// directly or indirectly any method where code was changed.
let depth = JustMyCode.Methods.DepthOfIsUsingAny(methodsChanged)
from m in depth.DefinitionDomain where
!m.CodeWasChanged() &&
!m.WasAdded()
let methodsChangedDirectlyUsed = m.MethodsCalled.Intersect(methodsChanged)
let depthOfUsingMethodsChanged = depth[m]
orderby depthOfUsingMethodsChanged
select new {
m,
depthOfUsingMethodsChanged,
methodsChangedDirectlyUsed
}
//<Description>
// This query is executed only if a *baseline for comparison* is defined (*diff mode*).
//
// This code query lists methods *unchanged* since the *baseline*
// but that **use directly or indirectly** some *methods* where
// code has been changed since the *baseline*.
//
// For such matched method, the code hasen't been changed, but still the overall
// behavior might have been changed.
//
// The query result includes methods changed directly used, and the **depth of usage**
// of methods indirectly used, *depth of usage* as defined in the documentation of
// *DepthOfIsUsingAny()* NDepend API method:
// http://www.ndepend.com/api/webframe.html?NDepend.API~NDepend.CodeModel.ExtensionMethodsSequenceUsage~DepthOfIsUsingAny.html
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>New fields</Name>
from f in Application.Fields where
!f.ParentType.WasAdded() &&
f.WasAdded() &&
!f.IsGeneratedByCompiler
select f
//<Description>
// This query is executed only if a *baseline for comparison* is defined (*diff mode*).
//
// This code query lists *fields* that have been added since the *baseline*.
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Fields removed</Name>
from f in codeBase.OlderVersion().Application.Fields where
!f.ParentType.WasRemoved() &&
f.WasRemoved() &&
!f.IsGeneratedByCompiler
select f
//<Description>
// This query is executed only if a *baseline for comparison* is defined (*diff mode*).
//
// This code query lists *fields* that have been removed since the *baseline*.
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Third party types that were not used and that are now used</Name>
from t in ThirdParty.Types where t.IsUsedRecently()
select new {
t,
t.Methods,
t.Fields,
t.TypesUsingMe
}
//<Description>
// This query is executed only if a *baseline for comparison* is defined (*diff mode*).
//
// This code query lists *types* defined in **third-party assemblies**, that were not
// used at *baseline* time, and that are now used.
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Third party types that were used and that are not used anymore</Name>
from t in codeBase.OlderVersion().Types where t.IsNotUsedAnymore()
select new {
t,
t.Methods,
t.Fields,
TypesThatUsedMe = t.TypesUsingMe
}
//<Description>
// This query is executed only if a *baseline for comparison* is defined (*diff mode*).
//
// This code query lists *types* defined in **third-party assemblies**, that were
// used at *baseline* time, and that are not used anymore.
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Third party methods that were not used and that are now used</Name>
from m in ThirdParty.Methods where
m.IsUsedRecently() &&
!m.ParentType.IsUsedRecently()
select new {
m,
m.MethodsCallingMe
}
//<Description>
// This query is executed only if a *baseline for comparison* is defined (*diff mode*).
//
// This code query lists *methods* defined in **third-party assemblies**, that were not
// used at *baseline* time, and that are now used.
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Third party methods that were used and that are not used anymore</Name>
from m in codeBase.OlderVersion().Methods where
m.IsNotUsedAnymore() &&
!m.ParentType.IsNotUsedAnymore()
select new {
m,
MethodsThatCalledMe = m.MethodsCallingMe
}
//<Description>
// This query is executed only if a *baseline for comparison* is defined (*diff mode*).
//
// This code query lists *methods* defined in **third-party assemblies**, that were
// used at *baseline* time, and that are not used anymore.
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Third party fields that were not used and that are now used</Name>
from f in ThirdParty.Fields where
f.IsUsedRecently() &&
!f.ParentType.IsUsedRecently()
select new {
f,
f.MethodsUsingMe
}
//<Description>
// This query is executed only if a *baseline for comparison* is defined (*diff mode*).
//
// This code query lists *fields* defined in **third-party assemblies**, that were not
// used at *baseline* time, and that are now used.
//</Description>]]></Query>
<Query Active="True" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Third party fields that were used and that are not used anymore</Name>
from f in codeBase.OlderVersion().Fields where
f.IsNotUsedAnymore() &&
!f.ParentType.IsNotUsedAnymore()
select new {
f,
MethodsThatUsedMe = f.MethodsUsingMe
}
//<Description>
// This query is executed only if a *baseline for comparison* is defined (*diff mode*).
//
// This code query lists *fields* defined in **third-party assemblies**, that were
// used at *baseline* time, and that are not used anymore.
//</Description>]]></Query>
</Group>
<Group Name="Statistics" Active="False" ShownInReport="False">
<Query Active="True" DisplayList="False" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Most used types (Rank)</Name>
(from t in Application.Types
where !t.IsGeneratedByCompiler
orderby t.Rank descending
select new { t, t.Rank, t.TypesUsingMe }).Take(100)
//<Description>
// **TypeRank** values are computed by applying
// the **Google PageRank** algorithm on the
// graph of types' dependencies. Types with
// high *Rank* are the most used ones. Not necessarily
// the ones with the most users types, but the ones
// used by many types, themselves having a lot of
// types users.
//
// See the definition of the TypeRank metric here:
// http://www.ndepend.com/docs/code-metrics#TypeRank
//
// This code query lists the 100 application types
// with the higher rank.
//
// The main consequence of being used a lot for a
// type is that each change (both *syntax change*
// and *behavior change*) will result in potentially
// a lot of **pain** since most types clients will be
// **impacted**.
//
// Hence it is preferable that types with higher
// *TypeRank*, are **interfaces**, that are typically
// less subject changes.
//
// Also interfaces avoid clients relying on
// implementations details. Hence, when the behavior of
// classes implementing an interface changes, this
// shouldn't impact clients of the interface.
// This is *in essence* the
// **Liskov Substitution Principle**.
// http://en.wikipedia.org/wiki/Liskov_substitution_principle
//</Description>]]></Query>
<Query Active="True" DisplayList="False" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Most used methods (Rank)</Name>
(from m in Application.Methods
where !m.IsGeneratedByCompiler
orderby m.Rank descending
select new { m, m.Rank, m.MethodsCallingMe }).Take(100)
//<Description>
// **MethodRank** values are computed by applying
// the **Google PageRank** algorithm on the
// graph of methods' dependencies. Methods with
// high *Rank* are the most used ones. Not necessarily
// the ones with the most callers methods, but the ones
// called by many methods, themselves having a lot
// of callers.
//
// See the definition of the MethodRank metric here:
// http://www.ndepend.com/docs/code-metrics#MethodRank
//
// This code query lists the 100 application methods
// with the higher rank.
//
// The main consequence of being used a lot for a
// method is that each change (both *signature change*
// and *behavior change*) will result in potentially
// a lot of **pain** since most methods callers will be
// **impacted**.
//
// Hence it is preferable that methods with highest
// *MethodRank*, are **abstract methods**, that are
// typically less subject to signature changes.
//
// Also abstract methods avoid callers relying on
// implementations details. Hence, when the code
// of a method implementing an abstract method changes,
// this shouldn't impact callers of the abstract method.
// This is *in essence* the
// **Liskov Substitution Principle**.
// http://en.wikipedia.org/wiki/Liskov_substitution_principle
//</Description>]]></Query>
<Query Active="True" DisplayList="False" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Most used assemblies (#AssembliesUsingMe)</Name>
(from a in Assemblies orderby a.AssembliesUsingMe.Count() descending
select new { a, a.AssembliesUsingMe }).Take(100)
//<Description>
// This code query lists the 100 *application* and *third-party*
// assemblies, with the higher number of assemblies users.
//</Description>]]></Query>
<Query Active="True" DisplayList="False" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Most used namespaces (#NamespacesUsingMe )</Name>
(from n in Namespaces orderby n.NbNamespacesUsingMe descending
select new { n, n.NamespacesUsingMe }).Take(100)
//<Description>
// This code query lists the 100 *application* and *third-party*
// namespaces, with the higher number of namespaces users.
//</Description>]]></Query>
<Query Active="True" DisplayList="False" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Most used types (#TypesUsingMe )</Name>
(from t in Types orderby t.NbTypesUsingMe descending
where !t.IsGeneratedByCompiler
select new { t, t.TypesUsingMe }).Take(100)
//<Description>
// This code query lists the 100 *application* and *third-party*
// types, with the higher number of types users.
//</Description>]]></Query>
<Query Active="True" DisplayList="False" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Most used methods (#MethodsCallingMe )</Name>
(from m in Methods orderby m.NbMethodsCallingMe
where !m.IsGeneratedByCompiler
select new { m, m.MethodsCallingMe }).Take(100)
//<Description>
// This code query lists the 100 *application* and *third-party*
// methods, with the higher number of methods callers.
//</Description>]]></Query>
<Query Active="True" DisplayList="False" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Namespaces that use many other namespaces (#NamespacesUsed )</Name>
(from n in Application.Namespaces orderby n.NbNamespacesUsed descending
select new { n, n.NamespacesUsed }).Take(100)
//<Description>
// This code query lists the 100 *application* namespaces
// with the higher number of namespaces used.
//</Description>]]></Query>
<Query Active="True" DisplayList="False" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Types that use many other types (#TypesUsed )</Name>
(from t in Application.Types orderby t.NbTypesUsed descending
select new { t, t.TypesUsed, isMyCode = JustMyCode.Contains(t) }).Take(100)
//<Description>
// This code query lists the 100 *application* types
// with the higher number of types used.
//</Description>]]></Query>
<Query Active="True" DisplayList="False" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Methods that use many other methods (#MethodsCalled )</Name>
(from m in Application.Methods orderby m.NbMethodsCalled descending
select new { m, m.MethodsCalled, isMyCode = JustMyCode.Contains(m) }).Take(100)
//<Description>
// This code query lists the 100 *application* methods
// with the higher number of methods called.
//</Description>]]></Query>
<Query Active="True" DisplayList="False" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>High-level to low-level assemblies (Level)</Name>
from a in Application.Assemblies orderby a.Level descending
select new { a, a.Level }
//<Description>
// This code query lists assemblies ordered by **Level** values.
// See the definition of the *AssemblyLevel* metric here:
// http://www.ndepend.com/docs/code-metrics#Level
//</Description>]]></Query>
<Query Active="True" DisplayList="False" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>High-level to low-level namespaces (Level)</Name>
from n in Application.Namespaces orderby n.Level descending
select new { n, n.Level }
//<Description>
// This code query lists namespaces ordered by **Level** values.
// See the definition of the *NamespaceLevel* metric here:
// http://www.ndepend.com/docs/code-metrics#Level
//</Description>]]></Query>
<Query Active="True" DisplayList="False" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>High-level to low-level types (Level)</Name>
from t in Application.Types orderby t.Level descending
select new { t, t.Level }
//<Description>
// This code query lists types ordered by **Level** values.
// See the definition of the *TypeLevel* metric here:
// http://www.ndepend.com/docs/code-metrics#Level
//</Description>]]></Query>
<Query Active="True" DisplayList="False" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>High-level to low-level methods (Level)</Name>
from m in Application.Methods orderby m.Level descending
select new { m, m.Level }
//<Description>
// This code query lists methods ordered by **Level** values.
// See the definition of the *MethodLevel* metric here:
// http://www.ndepend.com/docs/code-metrics#Level
//</Description>]]></Query>
</Group>
<Group Name="Samples of Custom rules" Active="False" ShownInReport="False">
<Group Name="Check Architecture" Active="False" ShownInReport="False">
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Check that the assembly Asm1 is not using the assembly Asm2</Name>
warnif count > 0 from a in Application.Assemblies where
a.IsUsing ("Asm2".AllowNoMatch().MatchAssembly()) &&
(a.Name == @"Asm1")
select new {
a,
Debt = 30.ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// This rule is a *sample rule that can be adapted to your need*.
//
// It shows how to be warned if a particular assembly is using
// another particular assembly.
//
// Such rule can be generated for assemblies **A** and **B**:
//
// • by right clicking the cell in the *Dependency Matrix*
// with **B** in row and **A** in column,
//
// • or by right-clicking the concerned arrow in the *Dependency
// Graph* from **A** to **B**,
//
// and in both cases, click the menu
// **Generate a code rule that warns if this dependency exists**
//
// The generated rule will look like this one.
// It is now up to you to adapt this rule to check exactly
// your needs.
//</Description>
//<HowToFix>
// This is a *sample rule* there is nothing to fix *as is*.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Check that the namespace N1.N2 is not using the namespace N3.N4.N5</Name>
warnif count > 0 from n in Application.Namespaces where
n.IsUsing ("N3.N4.N5".AllowNoMatch().MatchNamespace()) &&
(n.Name == @"N1.N2")
select new {
n,
Debt = 30.ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// This rule is a *sample rule that can be adapted to your need*.
//
// It shows how to be warned if a particular namespace is using
// another particular namespace.
//
// Such rule can be generated for namespaces **A** and **B**:
//
// • by right clicking the cell in the *Dependency Matrix*
// with **B** in row and **A** in column,
//
// • or by right-clicking the concerned arrow in the *Dependency
// Graph* from **A** to **B**,
//
// and in both cases, click the menu
// **Generate a code rule that warns if this dependency exists**
//
// The generated rule will look like this one.
// It is now up to you to adapt this rule to check exactly
// your needs.
//</Description>
//<HowToFix>
// This is a *sample rule* there is nothing to fix *as is*.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Check that the assembly Asm1 is only using the assemblies Asm2, Asm3 or mscorlib</Name>
warnif count > 0 from a in Application.Assemblies where
( !a.IsUsing ("Asm2".AllowNoMatch().MatchAssembly()) ||
!a.IsUsing ("Asm3".AllowNoMatch().MatchAssembly()) ||
!a.IsUsing ("mscorlib".MatchAssembly()) ||
a.AssembliesUsed.Count() != 3) // Must not be used more than 3 assemblies
&&
(a.Name == @"Asm1")
select new {
a,
a.AssembliesUsed,
Debt = 30.ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// This rule is a *sample rule that can be adapted to your need*.
//
// It shows how to enforce that a particular assembly
// is only using a particular set of assemblies.
//</Description>
//<HowToFix>
// This is a *sample rule* there is nothing to fix *as is*.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Check that the namespace N1.N2 is only using the namespaces N3.N4, N5 or System</Name>
warnif count > 0 from n in Application.Namespaces where
( !n.IsUsing("N3.N4".AllowNoMatch().MatchNamespace()) ||
!n.IsUsing("N5".AllowNoMatch().MatchNamespace()) ||
!n.IsUsing("System".MatchNamespace()) ||
n.NamespacesUsed.Count() != 3) // Must not be used more than 3 assemblies
// AsmCe = Efferent Coupling for assembly
&&
(n.Name == @"N1.N2")
select new {
n,
n.NamespacesUsed,
Debt = 30.ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// This rule is a *sample rule that can be adapted to your need*.
//
// It shows how to enforce that a particular namespace
// **is only using** a particular set of namespaces.
//</Description>
//<HowToFix>
// This is a *sample rule* there is nothing to fix *as is*.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Check that AsmDrawing is the only assembly that is using System.Drawing</Name>
warnif count> 0 from a in Application.Assemblies where
a.IsUsing ("System.Drawing".AllowNoMatch().MatchAssembly()) &&
!(a.Name == @"AsmDrawing")
select new {
a,
Debt = 30.ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// This rule is a *sample rule that can be adapted to your need*.
//
// It shows how to enforce that a particular assembly
// is **only used by** another particular assembly.
//</Description>
//<HowToFix>
// This is a *sample rule* there is nothing to fix *as is*.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Check that only 3 assemblies are using System.Drawing</Name>
warnif count != 3 from a in Application.Assemblies where
a.IsUsing ("System.Drawing".AllowNoMatch().MatchAssembly())
select new {
a,
Debt = 30.ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// This rule is a *sample rule that can be adapted to your need*.
//
// It shows how to enforce that a particular assembly
// is **only used by** 3 any others assemblies.
//</Description>
//<HowToFix>
// This is a *sample rule* there is nothing to fix *as is*.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Check that all methods that call Foo.Fct1() also call Foo.Fct2(Int32)</Name>
warnif count > 0 from m in Application.Methods where
m.IsUsing ("Foo.Fct1()".AllowNoMatch()) &&
!m.IsUsing ("Foo.Fct2(Int32)".AllowNoMatch())
select new {
m,
Debt = 10.ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// This rule is a *sample rule that can be adapted to your need*.
//
// It shows how to enforce that if a method calls a particular method,
// it must call another particular method.
//</Description>
//<HowToFix>
// This is a *sample rule* there is nothing to fix *as is*.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Check that all types that derive from Foo, also implement IFoo</Name>
warnif count > 0 from t in Application.Types where
t.DeriveFrom ("Foo".AllowNoMatch().MatchType()) &&
!t.Implement ("IFoo".AllowNoMatch().MatchType())
select new {
t,
Debt = 5.ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// This rule is a *sample rule that can be adapted to your need*.
//
// It shows how to enforce that all classes that derive from a particular base class,
// also implement a particular interface.
//</Description>
//<HowToFix>
// This is a *sample rule* there is nothing to fix *as is*.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Check that all types that has the attribute FooAttribute are declared in the namespace N1.N2*</Name>
warnif count > 0 from t in
Application.Namespaces.WithNameWildcardMatchNotIn("N1.N2*").ChildTypes()
where
t.HasAttribute ("FooAttribute".AllowNoMatch())
select new {
t,
Debt = 10.ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// This rule is a *sample rule that can be adapted to your need*.
//
// It shows how to enforce that all types that are tagged
// with a particular attribute, are declared in a
// particular namespace.
//</Description>
//<HowToFix>
// This is a *sample rule* there is nothing to fix *as is*.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Check that all synchronization objects are only used from the namespaces under MyNamespace.Sync</Name>
warnif count > 0 from n in Application.Namespaces
where
(n.IsUsing ("System.Threading.Monitor".AllowNoMatch()) ||
n.IsUsing ("System.Threading.ReaderWriterLock".AllowNoMatch()) ||
n.IsUsing ("System.Threading.Mutex".AllowNoMatch()) ||
n.IsUsing ("System.Threading.EventWaitHandle".AllowNoMatch()) ||
n.IsUsing ("System.Threading.Semaphore".AllowNoMatch()) ||
n.IsUsing ("System.Threading.Interlocked".AllowNoMatch()))
&& !n.NameLike (@"^MyNamespace.Sync")
select new {
n,
Debt = 10.ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// This rule is a *sample rule that can be adapted to your need*.
//
// It shows how to enforce that all synchronization objects
// are used from a particular namespace.
//</Description>
//<HowToFix>
// This is a *sample rule* there is nothing to fix *as is*.
//</HowToFix>]]></Query>
</Group>
<Group Name="Check Coverage on particular Code Elements" Active="False" ShownInReport="False">
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Check that the namespace N1.N2 is 100% covered by tests</Name>
warnif count > 0 from n in Application.Namespaces where
(n.Name == @"N1.N2") &&
n.PercentageCoverage < 100
select new {
n,
n.PercentageCoverage,
Debt = n.NbLinesOfCodeNotCovered.ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// This is a sample rule that shows how to check
// if a particular namespace is 100% covered by tests.
// Both the string **@"N1.N2"** and the threshold **100** can be adapted to your own needs.
//
// To execute this sample rule, coverage data must be imported.
// More info here: http://www.ndepend.com/docs/code-coverage
//</Description>
//<HowToFix>
// This is a *sample rule* there is nothing to fix *as is*.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Check that the assembly Asm is 100% covered by tests</Name>
warnif count > 0 from a in Application.Assemblies where
(a.Name == @"Asm") &&
a.PercentageCoverage < 100
select new {
a,
a.PercentageCoverage,
Debt = a.NbLinesOfCodeNotCovered.ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// This is a sample rule that shows how to check
// if a particular assembly is 100% covered by tests.
// Both the string **@"Asm"** and the threshold **100** can be adapted to your own needs.
//
// To execute this sample rule, coverage data must be imported.
// More info here: http://www.ndepend.com/docs/code-coverage
//</Description>
//<HowToFix>
// This is a *sample rule* there is nothing to fix *as is*.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Check that the class Namespace.Foo is 100% covered by tests</Name>
warnif count > 0 from t in Application.Types where
(t.FullName == @"Namespace.Foo") &&
t.PercentageCoverage < 100
select new {
t,
t.PercentageCoverage,
Debt = t.NbLinesOfCodeNotCovered.ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// This is a sample rule that shows how to check
// if a particular class is 100% covered by tests.
// Both the string **@"Namespace.Foo"** and the threshold **100**
// can be adapted to your own needs.
//
// To execute this sample rule, coverage data must be imported.
// More info here: http://www.ndepend.com/docs/code-coverage
//</Description>
//<HowToFix>
// This is a *sample rule* there is nothing to fix *as is*.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Check that the class Namespace.Foo.Method(Int32) is 100% covered by tests</Name>
warnif count > 0 from t in Application.Types where
(t.FullName == @"Namespace.Foo.Method(Int32)") &&
t.PercentageCoverage < 100
select new {
t,
t.PercentageCoverage,
Debt = t.NbLinesOfCodeNotCovered.ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// This is a sample rule that shows how to check
// if a particular method is 100% covered by tests.
// Both the string **@"Namespace.Foo.Method(Int32)"** and the threshold **100**
// can be adapted to your own needs.
//
// To execute this sample rule, coverage data must be imported.
// More info here: http://www.ndepend.com/docs/code-coverage
//</Description>
//<HowToFix>
// This is a *sample rule* there is nothing to fix *as is*.
//</HowToFix>]]></Query>
</Group>
<Group Name="Custom Naming Conventions" Active="False" ShownInReport="False">
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Check that all types that derive from Foo, has a name that ends up with Foo</Name>
warnif count > 0 from t in Application.Types where
t.DeriveFrom ("Foo".AllowNoMatch().MatchType()) &&
!t.NameLike (@"Foo$")
select new {
t,
t.NbLinesOfCode,
Debt = 5.ToMinutes().ToDebt(),
Severity = Severity.Medium
}
//<Description>
// This rule is a *sample rule that can be adapted to your need*.
//
// It shows how to enforce that all classes that derive from
// a particular class, are named with a particular suffix.
//</Description>
//<HowToFix>
// This is a *sample rule* there is nothing to fix *as is*.
//</HowToFix>]]></Query>
<Query Active="False" DisplayList="True" DisplayStat="False" DisplaySelectionView="False" IsCriticalRule="False"><![CDATA[// <Name>Check that all namespaces begins with CompanyName.ProductName</Name>
warnif count > 0 from n in Application.Namespaces where
!n.NameLike (@"^CompanyName.ProductName")
select new {
n,
n.NbLinesOfCode,
Debt = 10.ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// A practice widely adopted is that, in a product source code,
// all namespaces start with "CompanyName.ProductName".
//
// This rule must be adapted with your own **"CompanyName.ProductName"**.
//</Description>
//<HowToFix>
// Update all namespaces definitions in source code to satisfy this rule.
//</HowToFix>]]></Query>
</Group>
</Group>
</Queries>
<WarnFilter>
</WarnFilter>
</NDepend>