Table of Contents

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 nur T).

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. ein None = 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

  • partial nur 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/EqualityComparer oder Equatable (für IEquatable<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, ArgumentException etc.).
  • private und internal Methoden 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/internal APIs.
  • [ ] partial nur mit klarem Zweck (Codegen/Designer).