Zum Inhalt springen

Fertige CodeCharter-Regelbeispiele nach Kategorie

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

Jedes Beispiel ist eine komplette .ccr-Datei. Kopiert, was ihr braucht, passt die Schwellen oder Strings an und committet es in rules/ eures 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. Nutzt 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.