Skip to content

Regel-Beispiele

Sammlung fertig komponierter CodeCharter-Regeln, nach Kategorie sortiert. Kopierbar als Startpunkt für Ihre eigenen Konventionen.

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.Syntax formuliert 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 ILogger oder CancellationToken nicht 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.md neben die .ccr-Datei legen und codecharter test ausführen, um zu prüfen, ob eine kopierte oder angepasste Regel wirklich anschlägt.