Communitygithub.com

SOLID-Entwurfsüberprüfung

Weist den Agenten an, eine C#-Klasse oder ein Modul anhand jedes SOLID-Prinzips mit konkreten .NET-Linsen zu bewerten, den Code-Geruch und das verletzte Prinzip zu benennen und die minimale Refaktorisierung vorzuschlagen – mit Priorisierung von Verstößen, die echte Kopplung oder Änderungsaufwand verursachen.

Was ist SOLID-Entwurfsüberprüfung?

Diese KI-Agentenfertigkeit führt eine gründliche SOLID-Designüberprüfung für C#-Klassen und -Module durch, die speziell auf .NET- und Azure-Anwendungen zugeschnitten ist. Sie wandert jedes der fünf SOLID-Prinzipien (Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, Dependency Inversion) systematisch durch und identifiziert Code-Gerüche wie "God Services", wachsende switch-Anweisungen oder versteckte Abhängigkeiten von statischen Zuständen. Die Fertigkeit priorisiert echte Auswirkungen gegenüber akademischen Spitzfindigkeiten und schlägt minimale Refaktorisierungen vor, die auf konkrete C#-Mechanismen wie DI-Registrierung, Strategiemuster oder die Verwendung von `TimeProvider` und `IHttpClientFactory` verweisen. Sie ist für Entwickler konzipiert, die mit Claude Code, Codex, Cursor oder Gemini arbeiten, und hilft dabei, produktionsreife, wartbare und testbare .NET-Codebasen durchzusetzen, indem sie häufige Anti-Patterns hervorhebt und durchsetzbare, praktische Korrekturen anbietet.

Funktioniert mit✓Claude Code✓Codex CLI✓Cursor✓Gemini CLI
npx skills add tunahanaliozturk/secure-dotnet-skills

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

SOLID-Designüberprüfung

Weist den Agenten an, eine C#-Klasse oder ein Modul anhand jedes SOLID-Prinzips mit konkreten .NET-Linsen zu bewerten, den Code-Geruch und das verletzte Prinzip zu benennen und die minimale Refaktorisierung vorzuschlagen – wobei Verstöße priorisiert werden, die echte Kopplung oder Änderungsaufwand verursachen, nicht akademische Spitzfindigkeiten.

Wann verwenden

  • Eine Klasse wird unhandlich – sie umfasst I/O, Geschäftslogik, Mapping und Seiteneffekte in einer einzigen Datei.
  • Ein PR führt eine neue Typvariante ein, und eine vorhandene switch- oder if-else-Kette muss bearbeitet werden, um sie zu unterstützen.
  • Konstruktoren erzeugen ihre eigenen Abhängigkeiten mit new oder referenzieren direkt DateTime.Now / static-Helfer.
  • Ein Interface hat Member angesammelt, um mehrere nicht verwandte Aufrufer zu bedienen, was No-op-Implementierungen erzwingt.

Ablauf

  1. Identifiziere die zu überprüfende Klasse/das Modul. Nenne ihre erklärte Verantwortung. Wenn der Name „und" oder „oder" zur Beschreibung benötigt, ist das bereits ein Signal.
  2. Gehe jedes SOLID-Prinzip der Reihe nach durch unter Verwendung der nachstehenden C#-Linsen. Behandle jeden Durchgang separat; stoppe nicht nach dem ersten Fund.
  3. Benenne den Geruch und das verletzte Prinzip. „Fetter Konstruktor, der sein eigenes HttpClient und SqlConnection erstellt" → DIP; „Basis-Override, das NotSupportedException wirft" → LSP. Sei präzise.
  4. Schlage die minimale Refaktorisierung mit dem C#-Mechanismus vor. Nenne den Typ, das Muster oder die Änderung der DI-Registrierung – nicht nur „wende das Prinzip an." Allgemeine „Befolge SOLID"-Ratschläge sind kein Fund.
  5. Priorisiere nach tatsächlicher Auswirkung. Ein God Service, der EF-Persistenz mit E-Mail-Versand mischt, verursacht in jedem Sprint Änderungsaufwand; ein IOrderService mit 12 Membern, die kein Aufrufer vollständig nutzt, ist eine echte ISP-Verletzung. Eine sealed-Klasse, die zufällig zwei lose verwandte Anwendungsfälle erfüllt, ist kein SRP-Verstoß, wenn sie nur einen Grund zur Änderung hat. Unterscheide Verstöße, die schaden, von solchen, die nur unrein sind.

.NET / Azure-Prüfungen

SRP – Single Responsibility Principle

  • God Services / God Classes. Eine einzelne Klasse, die EF Core (_context.SaveChangesAsync), E-Mail-Versand (_emailSender.SendAsync), Geschäftsregeln validiert und in DTOs mapped, hat mehrere Gründe für Änderungen. Extrahiere einen IOrderValidator, ein IOrderRepository (oder verwende den DbContext direkt in einer dedizierten Persistenzklasse) und einen IOrderNotifier als separate Kollaborateure.
  • Fette Controller. Ein ASP.NET Core-Controller, der if/Validierungslogik enthält, direkt die Datenbank aufruft und Antwort-Payloads formatiert, erledigt drei Aufgaben. Controller sollten orchestrieren: einen Service aufrufen, das Ergebnis mappen, IActionResult zurückgeben. Geschäftsregeln gehören in die Anwendungs-/Domänenschicht.
  • „Und" im Typnamen oder in der Verantwortung. OrderValidationAndPersistenceService ist ein Namensgeruch, der auf SRP-Verstoß hinweist. Wenn du die Klasse nur mit „und" beschreiben kannst, teile sie auf.
  • Mehrere Änderungsgründe. Frage: „Müsste diese Klasse geändert werden, wenn wir den E-Mail-Anbieter wechseln? Wenn wir das Datenbankschema ändern? Wenn sich Geschäftsregeln ändern?" Mehr als ein „Ja" bedeutet mehr als eine Verantwortung.

OCP – Open/Closed Principle

  • Wachsende switch / if-else auf einem Typunterscheidungsmerkmal. Ein switch(order.Type)-Block innerhalb eines Service, der für jeden neuen OrderType-Enumwert wieder geöffnet werden muss, verletzt OCP. Ersetze durch eine Strategie: Definiere IOrderHandler (oder IOrderProcessor) und registriere eine Implementierung pro Typ; löse über IEnumerable<IOrderHandler> auf, die in den Koordinator injiziert wird, und dispatche basierend auf handler.Handles(order.Type).
  • Hartcodierte Verhaltenserweiterung. Wenn das Hinzufügen einer neuen Rabattregel, eines Benachrichtigungskanals oder eines Exportformats die Bearbeitung einer vorhandenen Klasse erfordert, anstatt eine neue IDiscountRule oder IExporter einzufügen, ist die Klasse nicht für Modifikation geschlossen. Führe die Abstraktion ein, bevor der zweite Fall eintritt.
  • Anti-Pattern des Typunterscheidungsmerkmals. if (order is PremiumOrder) oder if (typeof(T) == typeof(FooOrder)) innerhalb einer Verarbeitungsklasse ist ein Code-Geruch, der dem obigen switch entspricht. Polymorphie oder ein Strategie-Register eliminieren diese.

LSP – Liskov Substitution Principle

  • Overrides, die throw new NotSupportedException() / NotImplementedException() werfen. Ein Subtyp, der einen Basisvertrag nicht einhalten kann (z. B. ReadOnlyCollection<T>, das bei Add wirft), verletzt LSP. Wenn das Override nicht sinnvoll implementiert werden kann, ist die Hierarchie falsch – bevorzuge Komposition oder separate Interfaces.
  • Subtypen, die Nachbedingungen schwächen. Wenn die Basisklasse garantiert, dass GetOrders() nie null zurückgibt, verletzt ein Override, das null zurückgibt, den Vertrag, auf den sich Aufrufer verlassen. Dokumentiere Invarianten (XML-Doc oder Contracts) und erzwinge sie in Overrides.
  • Subtypen, die Vorbedingungen verschärfen. Ein Override, das eine in der Basis nicht vorhandene Null-Prüfung hinzufügt oder einen gültigen Parameterbereich ablehnt, den die Basis akzeptierte, unterbricht Code, der gültige Basisvertragseingaben übergibt. Der Subtyp muss alles akzeptieren, was die Basis akzeptiert hat.
  • Durchgängige is/as-Downcasts im aufrufenden Code. if (handler is SpecialHandler sh) sh.SpecialMethod() im Aufrufer bedeutet, dass die Abstraktion kaputt ist – entweder gehört die Methode zum Basis-Interface, oder die Typen sollten keine gemeinsame Hierarchie haben. Häufige Downcasts sind ein Signal für LSP-/Abstraktionsfehler.

ISP – Interface Segregation Principle

  • Fette Interfaces (10+ Member) mit nicht zusammenhängenden Belangen. Ein IOrderService, das CreateOrder, CancelOrder, GetInvoice, SendConfirmation, ExportToCsv, CalculateTax, ValidateAddress und GetShippingRates bereitstellt, zwingt jeden Implementierer und Test-Double, Methoden zu stubben, die sie nicht verwenden. Teile in Rollen-Interfaces auf: IOrderWriter, IOrderReader, IOrderNotifier, IOrderExporter.
  • No-op-Implementierungen / NotImplementedException-Stubs. Wenn eine Mock- oder Testimplementierung für die Hälfte der Interface-Member throw new NotImplementedException() zurückgibt, gehören diese Member zu einem anderen Rollen-Interface. Das Vorhandensein von No-ops ist ein direkter Beweis für ISP-Verstoß.
  • Clients, die Methoden importieren, die sie nie aufrufen. Wenn PaymentService von IOrderService abhängt, aber nur GetOrder aufruft, sollte es von IOrderReader abhängen. Die Verengung der Abhängigkeit macht die Kopplung explizit und das Testen billiger.

DIP – Dependency Inversion Principle

  • new-Erzeugung konkreter Abhängigkeiten innerhalb einer Klasse. new HttpClient(), das innerhalb eines Service erstellt wird, erzeugt eine versteckte Abhängigkeit, umgeht den DI-Container, verursacht Socket-Leaks (kein Verbindungspooling) und macht die Klasse ohne Reflektionstricks untestbar. Injiziere IHttpClientFactory (typisierter Client) oder einen benannten Client über den Konstruktor. Gleiches Muster für new SqlConnection(connectionString) – injiziere IDbConnectionFactory oder lasse EF Core / Dapper die Verbindung über den registrierten DbContext verwalten.
  • Abhängigkeit von Konkreta statt Abstraktionen. Ein Konstruktor, der auf SqlOrderRepository statt IOrderRepository typisiert ist, koppelt die Klasse an die Speichertechnologie. Jede Klasse, die wiederverwendbar oder isoliert testbar sein soll, darf nur von Interfaces oder abstrakten Typen abhängen.
  • Versteckte Kopplung an DateTime.Now / statischen Zustand. var now = DateTime.Now innerhalb einer Geschäftsmethode macht die Klasse nicht-deterministisch und ohne Zeitreise-Hacks untestbar. Führe ISystemClock ein (oder verwende TimeProvider ab .NET 8+) und injiziere es. Gleiches gilt für Environment.MachineName, Random.Shared oder andere ambient statische Werte.
  • Statische veränderliche Singletons. Ein static Dictionary<string, Order> _cache in einer Serviceklasse ist versteckter gemeinsam genutzter Zustand, der das DI-Lebenszyklusmanagement umgeht und nicht sicher für gleichzeitige Nutzung ist. Ersetze durch IMemoryCache / IDistributedCache, die über DI injiziert werden und ordnungsgemäße Bereichsbildung und Räumung unterstützen.

Urteilsnotiz: Nicht jedes new ist ein DIP-Verstoß. new OrderDto(order) (ein einfacher Datenbehälter) oder new List<T>() (eine Standardauflistung) sind in Ordnung. DIP ist relevant, wenn das konstruierte Objekt Verhalten, externe I/O oder Konfiguration hat – d. h., wenn das Austauschen gegen ein Test-Double oder eine alternative Implementierung wertvoll wäre.

Rote Flaggen

SignalWarum es wichtig ist
new HttpClient(), erstellt innerhalb einer Service-Methode oder eines KonstruktorsUmgeht das Verbindungspooling von IHttpClientFactory (Socket-Erschöpfung unter Last), versteckt die Abhängigkeit und macht die Klasse ohne Produktions-I/O untestbar.
switch(order.Type) oder if (x is ConcreteType) in einer Koordinator-KlasseMuss für jede neue Variante wieder geöffnet werden; Aufrufer wissen zu viel über die Typhierarchie. Ersetzen durch IEnumerable<IHandler>, das aus DI aufgelöst wird.
throw new NotImplementedException() oder throw new NotSupportedException() in einem nicht-abstrakten OverrideDer Subtyp kann den Basisvertrag nicht einhalten – LSP-Verstoß. Implementiere die Methode korrekt oder strukturiere die Hierarchie um.
Ein Interface mit 10+ Membern, von denen Aufrufer 2–3 verwendenErzwingt große Test-Doubles; signalisiert, dass mehrere Rollen-Interfaces in eines zusammengefasst wurden.
Eine Klasse, die ihren eigenen DbContext über new AppDbContext(options) erstelltUmgeht das DI-Lebenszyklusmanagement (Scoped vs. Transient vs. Singleton), verhindert Verbindungspooling und bricht die Testbarkeit. Injiziere AppDbContext oder IDbContextFactory<AppDbContext>.
DateTime.Now, direkt in einer Geschäftsmethode aufgerufenNicht-deterministisch unter Tests; Kopplung an die Systemuhr verbirgt eine logische Abhängigkeit. Injiziere TimeProvider (.NET 8+) oder ISystemClock.
Eine Controller-Aktion, die if/switch-Geschäftslogik oder EF-Abfragen enthältDer Controller hat Verantwortungen übernommen, die in die Anwendungs-/Domänenschicht gehören; eine einzelne Änderung an Geschäftsregeln erfordert, den Controller zu berühren.
Veränderliches static-Feld, das als prozessinterner Cache verwendet wirdNicht threadsicher ohne explizite Sperren; umgeht DI-Scoping; kann nicht evakuiert oder beobachtet werden. Verwende IMemoryCache.

Beispiel

Siehe examples/solid-review/.

Verwandte Skills