Coding Guidelines
Als Grundlage dienen die Microsoft Framework-Entwurfsrichtlinien.
Namenskonventionen
- Namen sind auszuschreiben. Abkürzungen bringen Missverständnisse mit sich.
✅ Beispiel:
public class GraphApiService { }
var graphApiService = new GraphApiService(); // Empfehlung ist für die Variable den Namen der Klasse zu verwenden.
var service = new GraphApiService(); // Auch zulässig, wenn die nutzende Klasse nur diesen einen Service nutzt.
❌ Falsch:
public class GraphApiSvc { } // Svc muss ausgeschrieben werden.
var svc = new GraphApiService(); // Auch Variablennamen müssen ausgeschrieben werden.
Geltungsbereich
Klassen, Interfaces, Enums, Records, Structs in C# (.NET Framework 4.7.2, C# 9).
Dateiorganisation
- Ein Typ pro Datei. Der Dateiname entspricht exakt dem Typnamen (inkl. Suffix, z. B.
OrderService.cs,UserException.cs). - Namespace folgt der Ordnerstruktur (
Company.Product.Module.[…]). Keine „God-Namespaces“.
Typnamen (PascalCase)
- Klassen/Records/Structs/Enums: PascalCase, keine technischen Präfixe/Suffixe wie
Impl,Base,Util,Manager. - Interfaces:
I+ PascalCase (IRepository,IClock).
💡 Das Präfix „I“ bei Interfaces ist ein fester Bestandteil der C#-Namenskonventionen (vgl. .NET Design Guidelines). - Enums: Typname singular (
Color). [Flags]-Enums plural (FileAccesses) und nur Potenzen von zwei. - Attribute-Typen: enden auf
Attribute(RequiredAttribute). Beim Anwenden wird das Suffix weggelassen ([Required]). - Exception-Typen: enden auf
Exception(UserNotFoundException). - Generische Typparameter: aussagekräftig, PascalCase, mit
T-Prefix, z. B.TEntity,TKey,TResult(nicht nurT).
Semantik & Einsatzzweck
- Structs nur für kleine, unveränderliche Value Types mit Wertsemantik (z. B.
Money,DateRange). Keine Service-/Manager-Typen. - Records bevorzugt für Value Objects/DTOs (Immutability, with-Semantik). Keine vererbungsgetriebenen Hierarchien erzwingen.
- Enums haben benannte, pascalgeschriebene Members (keine Präfixe, keine „Enum“-Suffixe). Bei
[Flags]Members kombinierbar und ggf. einNone = 0.
Sichtbarkeit & Verschachtelung
- Öffentliche Typen nur ein Typ pro Datei. Verschachtelte (nested) Typen nur, wenn sie ausschließlich vom umgebenden Typ genutzt werden.
- Sichtbarkeit minimieren (internal vor public). Kein „public by default“. Ausnahme bei Tests.
Partials & Codegenerierung
partialnur für Code-Generierung oder klar getrennte technische Belange (z. B. Designer, Source-Generated Clients). Keine willkürliche Aufsplittung von Domänenlogik. PitInterface-Klassen und deren Wrapperklassen sind eine Ausnahme.
Dokumentation & Anmerkungen
- Öffentliche/Interna-API: XML-Dokukommentare (
/// <summary>…) für Typen verpflichtend;<remarks>,<example>bei Bedarf. - Obsolete sparsam und konsistent einsetzen; klare Migrationshinweise liefern.
Weitere Richtlinien für spezielle Typen
- Comparer/Equality: Implementierungen enden auf
Comparer/EqualityCompareroderEquatable(fürIEquatable<T>). - Event-Pattern: EventArgs-Typen enden auf
EventArgs(z. B.OrderPlacedEventArgs).
Beispiele
namespace Contoso.Sales.Orders.Domain;
public interface IClock { DateTime UtcNow { get; } }
public sealed class OrderService { /* … */ }
[Flags]
public enum FileAccesses
{
None = 0,
Read = 1 << 0,
Write = 1 << 1,
Execute = 1 << 2
}
public readonly record struct Money(decimal Amount, string Currency);
public sealed class UserNotFoundException : Exception
{
public UserNotFoundException(string userId)
: base($"User '{userId}' was not found.") { }
}
[AttributeUsage(AttributeTargets.Property)]
public sealed class RequiredAttribute : Attribute { }
Variablen
- Variablen haben aussagekräftige, beschreibende Namen.
💡 Sprechende Variablennamen fördern Lesbarkeit, vereinfachen Refactoring und reduzieren Fehlinterpretationen. - Variablennamen werden ausgeschrieben.
- Ungarische Notation wird nicht verwendet.
💡 Ungarische Notation widerspricht modernen Typinformationsmechanismen des Compilers und IDEs.
✅ Beispiele:
int customerCount;
string userName;
DateTime lastLoginDate;
❌ Falsch:
int iCustomerCount; // ungarische Notation
string strName; // ungarische Notation
var x; // nichtssagender Name
Weitere Empfehlungen
Public Methoden sollten Argumente auf null prüfen
- Öffentliche und geschützte Methoden validieren ihre Eingaben (
ArgumentNullException,ArgumentOutOfRangeException,ArgumentExceptionetc.). privateundinternalMethoden dürfen davon ausgehen, dass ihre Vorbedingungen durch den aufrufenden Code erfüllt werden.- Kommentare sollen das Vertragsprinzip erläutern („weil öffentlich – Contract muss geprüft werden“), nicht nur das Verhalten beschreiben.
- Immer
nameof(parameter)im Exception-Konstruktor verwenden.
public sealed class CurrencyConverter
{
...
/// <summary>
/// Converts the specified amount from Euro to USD.
/// </summary>
/// <param name="context">The current currency context (must not be null).</param>
/// <param name="amount">The Euro amount to convert.</param>
/// <returns>The converted amount in USD.</returns>
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="context"/> is <c>null</c>.
/// </exception>
public decimal ConvertEuroToUsd(CurrencyContext context, decimal amount)
{
// Öffentliche Methode → Vertragsprüfung erforderlich.
// Da diese Methode öffentlich ist und von jedem aufgerufen werden kann, sollte hier auf null geprüft werden.
if (context is null)
{
throw new ArgumentNullException(nameof(context), "The currency context must not be null.");
}
...
}
// Private Methoden dürfen interne Vorbedingungen annehmen:
// Nullprüfung entfällt, weil context nur intern korrekt übergeben wird.
private void SetupContext(CurrencyContext context)
{
context.EuroToUsdRate = 1.12m;
...
}
...
}
Hintergrund / Prinzipien
- Design by Contract: Öffentliche Methoden sichern ihren Vertrag durch Eingabevalidierung. Private Methoden dürfen davon ausgehen, dass ihre Vorbedingungen erfüllt sind (vgl. Framework Design Guidelines).
- Fail Fast: Null-Checks möglichst früh, um Fehler klar zu signalisieren.
- Code-Kommentare: Besser erklären, warum geprüft wird, nicht wie.
Anti-Patterns (vermeiden)
- Mehrere öffentliche Typen in einer Datei / Dateiname ≠ Typname.
- Abkürzungen/Slang im Typnamen (
Mgr,Svc,Util). - Künstliche Hierarchien (
BaseCustomer,CommonHelper). - Enums mit Mehrzahl ohne
[Flags]oder nicht-potenzierte Werte.
Prüfliste (Definition of Done)
- [ ] Typname passt semantisch (Record/Struct nur für Value-Semantik).
- [ ] Interface
I*, Attribute*Attribute, Exceptions*Exception. - [ ] Enums: Singular;
[Flags]→ Plural + Potenzen von 2 +None = 0. - [ ] Generische Typparameter sinnvoll benannt (
TEntity,TKey, …). - [ ] Namespace ↔ Ordnerstruktur konsistent.
- [ ] XML-Docs vorhanden für öffentliche/
internalAPIs. - [ ]
partialnur mit klarem Zweck (Codegen/Designer).