Jedes Beispiel ist eine komplette .ccr-Datei. Kopieren Sie, was Sie brauchen,
passen Sie die Schwellen oder Strings an und committen Sie es in rules/ Ihres Repos.
Hinweis zu eingebauten Regeln: Mehrere Beispiele unten decken Pattern ab, die CodeCharter bereits als eingebaute Regeln liefert (120+ Regeln ab Werk). Wenn eine eingebaute Regel das Pattern mit dem gewünschten Schwellwert abdeckt, ist keine eigene Regel nötig; die eingebaute Regel zu aktivieren genügt. Eine eigene Regel für dasselbe Pattern erzeugt doppelte Findings. Bei den betroffenen Beispielen steht der Hinweis "(eingebaute Regel vorhanden)".
Async
Async-Methode ohne Async-Suffix
Eingebaute Regel vorhanden: Async method missing Async suffix. Dieses Beispiel nur nutzen, wenn eine andere Severity oder ein anderer Geltungsbereich nötig ist.
@name "Async method missing Async suffix"
@severity warn
@category "Async"
@description "Async methods should end with 'Async' so callers spot await sites"
@recommendation "Rename Foo to FooAsync when the method returns Task or ValueTask"
from m in Methods
where m.IsAsync
where !m.Name.EndsWith("Async")
select m
Public-Async ohne CancellationToken
Eingebaute Regel vorhanden: Async method without CancellationToken. Dieses Beispiel nur nutzen, wenn ein anderer Geltungsbereich oder eine andere Severity nötig ist.
@name "Public async method missing CancellationToken"
@severity warn
@category "Async"
@description "Public async APIs should accept a CancellationToken so callers can cancel"
@recommendation "Add CancellationToken parameter with default value"
from m in Methods
where m.IsAsync
where m.AccessModifier == "Public"
where !m.Parameters.Any(p => p.TypeShortName == "CancellationToken")
select m
Fire-and-forget Async
Eingebaute Regel vorhanden: Fire-and-forget async call. Dieses Beispiel nur nutzen, wenn eine andere Severity oder Meldung nötig ist.
@name "Async call not awaited"
@severity error
@category "Async"
@description "Calling an async method without awaiting silently drops exceptions"
@recommendation "Await the result, or assign to '_ = ...' if discard is intentional"
AllBodies
.SelectMany(m => m.Invocations.Where(i => i.IsUnobservedStatement
&& (i.ResolvedType.FullName == "System.Threading.Tasks.Task"
|| i.ResolvedType.FullName.StartsWith("System.Threading.Tasks.Task<")
|| i.ResolvedType.FullName == "System.Threading.Tasks.ValueTask"
|| i.ResolvedType.FullName.StartsWith("System.Threading.Tasks.ValueTask<"))))
Fehlerbehandlung
Leerer Catch-Block
Eingebaute Regel vorhanden: Empty catch block. Dieses Beispiel nur nutzen, wenn eine andere Severity oder ein anderer Geltungsbereich nötig ist.
@name "Empty catch block"
@severity error
@category "ErrorHandling"
@description "Empty catch silently swallows exceptions"
@recommendation "Log and rethrow, or catch the specific exception you can recover from"
AllBodies.SelectMany(m => m.Catches.Where(c => c.IsEmpty))
Generic Exception-Catch ohne Rethrow
Eingebaute Regel vorhanden: Catching System.Exception directly. Dieses Beispiel nur nutzen, wenn eine andere Severity nötig ist.
@name "Generic exception caught without rethrow"
@severity warn
@category "ErrorHandling"
@description "Catching Exception swallows everything, including bugs"
@recommendation "Catch the narrowest exception type the caller can recover from"
AllBodies
.SelectMany(m => m.Catches.Where(c => c.ExceptionType.FullName == "System.Exception"))
Rethrow, das den Stacktrace verliert
Eingebaute Regel vorhanden: Rethrow loses the original stack trace. Dieses Beispiel zeigt zusätzlich, wie eine Regel auf Anweisungsebene mit
m.Syntaxformuliert wird. Nutzen Sie es nur, wenn eine andere Severity oder Formulierung nötig ist.
@name "Rethrow loses the original location"
@severity error
@category "ErrorHandling"
@description "throw ex; resets the original stack trace and hides where the error came from"
@recommendation "Use a bare throw; to keep the original stack trace"
Methods.Where(m =>
m.Syntax.Descendants.CatchClause.Any(c =>
c.Block.Descendants.ThrowStatement.Any(t =>
t.Expression.Text == c.Declaration.Identifier.Text)))
Das Finding zeigt auf die genaue throw-Anweisung, auch wenn sie tief in
if-Zweigen oder Schleifen steckt. Die vollständige m.Syntax-Oberfläche steht
im Prädikat-Katalog.
Design
Konstruktor mit zu vielen Parametern
Eingebaute Regel vorhanden: Constructor has too many parameters (Schwellwert: mehr als 4 Parameter, wobei Querschnitts-Abhängigkeiten wie
ILoggeroderCancellationTokennicht mitzählen). Dieses Beispiel nur nutzen, wenn ein anderer Schwellwert oder keine Ausnahmen gewünscht sind.
Konstruktoren sind nicht Teil der Collection Methods; sie sind über
Types und die Property Constructors erreichbar.
@name "Constructor with too many parameters"
@severity warn
@category "Design"
@description "Constructors with 5+ parameters usually indicate too many responsibilities"
@recommendation "Split the class or use a parameter object"
from t in Types
where t.Constructors.Any(c => c.NumberOfParameters > 4)
select t
Klasse zu groß
Eingebaute Regel vorhanden: Class too large (Schwellwert: 500 Zeilen). Dieses Beispiel nutzen, wenn ein anderer Schwellwert gewünscht ist.
@name "Class too large"
@severity warn
@category "Design"
@description "Classes over 500 lines tend to mix responsibilities"
@recommendation "Extract collaborators or split by responsibility"
from t in Types
where t.Kind == "Class"
where t.LinesOfCode > 500
select t
Static-Klasse mit nicht-Extension public Methoden
Eingebaute Regel vorhanden: Static class has public non-extension methods. Die eingebaute Regel greift nur bei öffentlichen Static-Klassen; dieses Beispiel erfasst zusätzlich interne. Dieses Beispiel nur nutzen, wenn eine andere Severity oder ein anderer Geltungsbereich nötig ist.
@name "Static class has public non-extension methods"
@severity warn
@category "Design"
@description "Static utility classes should host extension methods only"
@recommendation "Convert to instance methods on a real type, or mark the parameter with 'this'"
from t in Types
where t.IsStatic
where t.Kind == "Class"
where t.Methods.Any(m => m.AccessModifier == "Public" && !m.IsExtensionMethod)
select t
Naming
Boolean-Property ohne Verb-Präfix
Eingebaute Regel vorhanden: Boolean property missing verb prefix. Dieses Beispiel nur nutzen, wenn andere erlaubte Präfixe gewünscht sind.
@name "Boolean property missing verb prefix"
@severity warn
@category "Naming"
@description "Booleans should be readable as questions: Is, Has, Can, Should, Enable, Disable"
@recommendation "Prefix the property to convey the state it represents"
from p in Properties
where p.Type == "bool"
where !p.Name.StartsWith("Is") && !p.Name.StartsWith("Has")
where !p.Name.StartsWith("Can") && !p.Name.StartsWith("Should")
where !p.Name.StartsWith("Enable") && !p.Name.StartsWith("Disable")
select p
Interface ohne I-Präfix
Eingebaute Regel vorhanden: Interface not prefixed with I. Dieses Beispiel nur nutzen, wenn eine andere Severity nötig ist.
@name "Interface missing I prefix"
@severity warn
@category "Naming"
@description "Interface names should start with I, following C# convention"
@recommendation "Rename Foo to IFoo and update implementations"
from t in Types
where t.Kind == "Interface"
where !t.Name.StartsWith("I")
select t
Klasse mit "Manager"-Suffix
Eingebaute Regel vorhanden: Avoid 'Manager' class suffix. Dieses Beispiel nur nutzen, wenn eine andere Severity oder Kombination mit anderen Prädikaten nötig ist.
@name "Class named with vague Manager suffix"
@severity warn
@category "Naming"
@description "Classes called *Manager are vague: name them after what they actually do"
@recommendation "Pick a verb-based name: OrderProcessor, EmailDispatcher, PriceCalculator"
from t in Types
where t.Kind == "Class"
where t.Name.EndsWith("Manager")
select t
Determinismus
Direkte DateTime-Nutzung
Eingebaute Regel vorhanden: Direct DateTime/DateTimeOffset usage instead of TimeProvider. Dieses Beispiel nur nutzen, wenn eine andere Severity oder Empfehlung nötig ist.
@name "Direct DateTime.Now / DateTime.UtcNow usage"
@severity error
@category "Determinism"
@description "Direct DateTime access makes tests non-deterministic"
@recommendation "Inject IClock and call _clock.UtcNow instead"
AllBodies.SelectMany(m => m.MemberAccesses.Where(a => (a.Name == "Now" || a.Name == "UtcNow")
&& (a.ResolvedType.FullName == "System.DateTime" || a.ResolvedType.FullName == "System.DateTimeOffset")))
Konstruktor ruft new auf
Eingebaute Regel vorhanden: Direct instantiation in constructor or field initializer. Dieses Beispiel nur nutzen, wenn eine andere Severity nötig ist.
@name "Constructor allocates dependency directly"
@severity warn
@category "Determinism"
@description "Allocating dependencies in a constructor bypasses DI and breaks testability"
@recommendation "Inject the dependency through the constructor"
AllConstructorInjectableCreations
.Where(c => !c.SourceFile.Contains(".g.cs") && !c.SourceFile.Contains(".Designer.cs"))
Architektur
Domain hängt von Infrastructure ab
@name "Domain layer references Infrastructure"
@severity error
@category "Architecture"
@description "Domain layer must not depend on infrastructure concerns"
@recommendation "Move the call behind an interface in Domain that Infrastructure implements"
from t in Types
where t.Namespace.FullName.Contains(".Domain.")
where t.UsedTypes.Any(u => u.Namespace.FullName.Contains(".Infrastructure."))
select t
Controller ruft Repository direkt
@name "Controller calls Repository directly"
@severity warn
@category "Architecture"
@description "Controllers should go through the application layer, not access data directly"
@recommendation "Introduce an application service that wraps the repository call"
from t in Types
where t.Name.EndsWith("Controller")
where t.UsedTypes.Any(u => u.Name.EndsWith("Repository"))
select t
Komplexität
Methode zu lang
Eingebaute Regel vorhanden: Method too large (Schwellwert: 80 Zeilen). Dieses Beispiel nutzen, wenn ein anderer Schwellwert (z.B. 60 Zeilen) gewünscht ist.
@name "Method too long"
@severity info
@category "Style"
@description "Methods over 60 lines are harder to read and harder to test"
@recommendation "Extract sub-routines once a method passes ~60 lines"
from m in Methods
where m.LinesOfCode > 60
select m
Hohe zyklomatische Komplexität
Eingebaute Regel vorhanden: High cyclomatic complexity (Schwellwert: 15). Dieses Beispiel nutzen, wenn ein anderer Schwellwert gewünscht ist.
@name "High cyclomatic complexity"
@severity warn
@category "Style"
@description "Methods with branching depth above 10 are hard to test and reason about"
@recommendation "Extract guard clauses, replace nested ifs with early returns, or split the method"
from m in Methods
where m.CyclomaticComplexity > 10
select m
Hohe kognitive Komplexität
Eingebaute Regel vorhanden: High cognitive complexity (Schwellwert: 15). Dieses Beispiel nutzen, wenn ein anderer Schwellwert gewünscht ist.
@name "High cognitive complexity"
@severity warn
@category "Style"
@description "Cognitive complexity above 15 indicates the method is hard for humans to follow"
@recommendation "Flatten nesting, extract intermediate variables with meaningful names"
from m in Methods
where m.CognitiveComplexity > 15
select m
Wo geht's weiter
- Einstieg: Wegweiser zu den restlichen Doku-Seiten rund ums Regelschreiben.
- Prädikat-Katalog: vollständige Property-Liste.
- Best Practices: was eine gute Regel ausmacht.
- codecharter test: eine
<regel>.spec.mdneben die.ccr-Datei legen undcodecharter testausführen, um zu prüfen, ob eine kopierte oder angepasste Regel wirklich anschlägt.