Wie viele Injektionen sind in einer Klasse akzeptabel, wenn die Abhängigkeitsinjektion verwendet wird

9

Ich verwende Unity in C # für die Abhängigkeitsinjektion, aber die Frage sollte für jede Sprache und jedes Framework gelten, das die Abhängigkeitsinjektion verwendet.

Ich versuche dem SOLID-Prinzip zu folgen und habe deshalb viele Abstraktionen bekommen. Aber jetzt frage ich mich, ob es eine bewährte Methode für die Anzahl der Injektionen gibt, die eine Klasse injizieren sollte.

Zum Beispiel habe ich ein Repository mit 9 Injektionen. Wird dies für einen anderen Entwickler schwer zu lesen sein?

Die Injektionen haben folgende Verantwortlichkeiten:

  • IDbContextFactory - Erstellt den Kontext für die Datenbank.
  • IMapper - Zuordnung von Entitäten zu Domänenmodellen.
  • IClock - Abstracts DateTime.Now hilft bei Unit-Tests.
  • IPerformanceFactory - misst die Ausführungszeit für bestimmte Methoden.
  • ILog - Log4net zur Protokollierung.
  • ICollectionWrapperFactory - Erstellt Sammlungen (die IEnumerable erweitern).
  • IQueryFilterFactory - Generiert Abfragen basierend auf Eingaben, die db abfragen.
  • IIdentityHelper - Ruft den angemeldeten Benutzer ab.
  • IFaultFactory - Erstellen Sie verschiedene FaultExceptions (ich verwende WCF).

Ich bin nicht wirklich enttäuscht darüber, wie ich die Verantwortlichkeiten delegiert habe, aber ich mache mir langsam Sorgen um die Lesbarkeit.

Also meine Fragen:

Gibt es eine Grenze für die Anzahl der Injektionen, die eine Klasse haben sollte? Und wenn ja, wie kann man das vermeiden?

Schränken viele Injektionen die Lesbarkeit ein oder verbessern sie sie tatsächlich?

raucht
quelle
2
Das Messen der Qualität anhand von Zahlen ist normalerweise so schlecht wie das Bezahlen mit Codezeilen, die Sie pro Monat schreiben. Sie haben jedoch das eigentliche Beispiel für die Abhängigkeiten angegeben, was eine hervorragende Idee ist. Wenn ich Sie wäre, würde ich Ihre Frage neu formulieren, um das Konzept des Zählens und der strengen Begrenzung zu entfernen und statt der Qualität an sich zu fokussieren. Achten Sie bei der Neuformulierung darauf, Ihre Frage nicht zu spezifisch zu machen.
Arseni Mourzenko
3
Wie viele Konstruktorargumente sind akzeptabel? IoC ändert das nicht .
Telastyn
Warum wollen die Menschen immer absolute Grenzen?
Marjan Venema
1
@ Telastyn: nicht unbedingt. Was IoC ändert, ist, dass anstatt sich auf statische Klassen / Singletons / globale Variablen zu verlassen, alle Abhängigkeiten zentraler und „sichtbarer“ sind.
Arseni Mourzenko
@ MarjanVenema: weil es das Leben viel einfacher macht. Wenn Sie genau den maximalen LOC pro Methode oder die maximale Anzahl von Methoden in einer Klasse oder eine maximale Anzahl von Variablen in einer Methode kennen und dies das einzige ist, was zählt, wird es einfach, den Code als gut oder schlecht zu qualifizieren sowie den fehlerhaften Code "reparieren". Dies ist bedauerlich, dass das wirkliche Leben viel komplexer ist und viele Metriken größtenteils irrelevant sind.
Arseni Mourzenko

Antworten:

11

Zu viele Abhängigkeiten können darauf hinweisen, dass die Klasse selbst zu viel tut. Um festzustellen, ob es nicht zu viel tut:

  • Schau dir die Klasse selbst an. Wäre es sinnvoll, es in zwei, drei, vier aufzuteilen? Ist es insgesamt sinnvoll?

  • Schauen Sie sich die Arten von Abhängigkeiten an. Welche sind domänenspezifisch und welche „global“? Zum Beispiel würde ich nicht ILogauf der gleichen Ebene betrachten wie IQueryFilterFactory: Die erste wäre ohnehin in den meisten Geschäftsklassen verfügbar, wenn sie die Protokollierung verwenden. Wenn Sie andererseits viele domänenspezifische Abhängigkeiten finden, kann dies darauf hinweisen, dass die Klasse zu viel tut.

  • Schauen Sie sich die Abhängigkeiten an, die durch Werte ersetzt werden können.

    IClock - Abstracts DateTime.Now hilft bei Unit-Tests.

    Dies könnte leicht ersetzt werden, indem DateTime.Nowes direkt an die Methoden übergeben wird, die die aktuelle Zeit kennen müssen.

Wenn ich mir die tatsächlichen Abhängigkeiten anschaue, sehe ich nichts, was darauf hindeutet, dass schlimme Dinge passieren:

  • IDbContextFactory - Erstellt den Kontext für die Datenbank.

    OK, wir befinden uns wahrscheinlich in einer Geschäftsschicht, in der Klassen mit der Datenzugriffsschicht interagieren. Sieht gut aus.

  • IMapper - Zuordnung von Entitäten zu Domänenmodellen.

    Ohne das Gesamtbild ist es schwierig, etwas zu sagen. Es kann sein, dass die Architektur falsch ist und die Zuordnung direkt von der Datenzugriffsschicht erfolgen sollte, oder dass die Architektur vollkommen in Ordnung ist. In allen Fällen ist es sinnvoll, diese Abhängigkeit hier zu haben.

    Eine andere Möglichkeit wäre, die Klasse in zwei Klassen aufzuteilen: eine für die Zuordnung, die andere für die eigentliche Geschäftslogik. Dies würde eine De-facto-Schicht erzeugen, die das BL weiter vom DAL trennt. Wenn Zuordnungen komplex sind, könnte dies eine gute Idee sein. In den meisten Fällen würde dies jedoch nur zu einer nutzlosen Komplexität führen.

  • IClock - Abstracts DateTime.Now hilft bei Unit-Tests.

    Es ist wahrscheinlich nicht sehr nützlich, eine separate Schnittstelle (und Klasse) zu haben, um die aktuelle Zeit abzurufen. Ich würde das einfach DateTime.Nowan die Methoden weitergeben, die die aktuelle Zeit benötigen.

    Eine separate Klasse kann sinnvoll sein, wenn andere Informationen wie die Zeitzonen oder die Datumsbereiche usw. vorhanden sind.

  • IPerformanceFactory - misst die Ausführungszeit für bestimmte Methoden.

    Siehe den nächsten Punkt.

  • ILog - Log4net zur Protokollierung.

    Solche transzendenten Funktionen sollten zum Framework gehören und die tatsächlichen Bibliotheken sollten zur Laufzeit austauschbar und konfigurierbar sein (z. B. über app.config in .NET).

    Leider ist dies (noch) nicht der Fall, sodass Sie entweder eine Bibliothek auswählen und dabei bleiben oder eine Abstraktionsschicht erstellen können, um die Bibliotheken später bei Bedarf austauschen zu können. Wenn Sie speziell von der Wahl der Bibliothek unabhängig sein möchten, entscheiden Sie sich dafür. Wenn Sie ziemlich sicher sind, dass Sie die Bibliothek jahrelang weiter verwenden, fügen Sie keine Abstraktion hinzu.

    Wenn die Bibliothek zu komplex für die Verwendung ist, ist ein Fassadenmuster sinnvoll.

  • ICollectionWrapperFactory - Erstellt Sammlungen (die IEnumerable erweitern).

    Ich würde annehmen, dass dies sehr spezifische Datenstrukturen erzeugt, die von der Domänenlogik verwendet werden. Es sieht aus wie eine Utility-Klasse. Verwenden Sie stattdessen eine Klasse pro Datenstruktur mit relevanten Konstruktoren. Wenn die Initialisierungslogik etwas kompliziert ist, um in einen Konstruktor zu passen, verwenden Sie statische Factory-Methoden. Wenn die Logik noch komplexer ist, verwenden Sie das Factory- oder Builder-Muster.

  • IQueryFilterFactory - Generiert Abfragen basierend auf Eingaben, die db abfragen.

    Warum ist das nicht in der Datenzugriffsschicht? Warum steht Filterim Namen ein?

  • IIdentityHelper - Ruft den angemeldeten Benutzer ab.

    Ich bin mir nicht sicher, warum es ein HelperSuffix gibt. In allen Fällen sind auch andere Suffixe nicht besonders explizit ( IIdentityManager?)

    Auf jeden Fall ist es durchaus sinnvoll, diese Abhängigkeit hier zu haben.

  • IFaultFactory - Erstellen Sie verschiedene FaultExceptions (ich verwende WCF).

    Ist die Logik so komplex, dass ein Fabrikmuster verwendet werden muss? Warum wird dafür Dependency Injection verwendet? Würden Sie die Erstellung von Ausnahmen zwischen Produktionscode und Tests austauschen? Warum?

    Ich würde versuchen, das in einfach umzugestalten throw new FaultException(...). Wenn einige globale Informationen zu allen Ausnahmen hinzugefügt werden sollen, bevor sie an den Client weitergegeben werden, verfügt WCF wahrscheinlich über einen Mechanismus, mit dem Sie eine nicht behandelte Ausnahme abfangen und sie ändern und erneut an den Client senden können.

Gibt es eine Grenze für die Anzahl der Injektionen, die eine Klasse haben sollte? Und wenn ja, wie kann man das vermeiden?

Das Messen der Qualität anhand von Zahlen ist normalerweise so schlecht wie das Bezahlen mit Codezeilen, die Sie pro Monat schreiben. Möglicherweise haben Sie eine hohe Anzahl von Abhängigkeiten in einer gut gestalteten Klasse, da Sie eine beschissene Klasse mit wenigen Abhängigkeiten haben können.

Schränken viele Injektionen die Lesbarkeit ein oder verbessern sie sie tatsächlich?

Viele Abhängigkeiten erschweren es, der Logik zu folgen. Wenn die Logik schwer zu befolgen ist, tut die Klasse wahrscheinlich zu viel und sollte aufgeteilt werden.

Arseni Mourzenko
quelle
Vielen Dank für Ihre Kommentare und Ihre Zeit. Hauptsächlich über FaultFactory, die stattdessen in die WCF-Logik verschoben wird. Ich werde IClock behalten, da es ein Lebensretter ist, wenn eine Anwendung TDD-fähig ist. Es gibt mehrere Fälle, in denen Sie sicherstellen möchten, dass ein bestimmter Wert mit einer bestimmten Zeit festgelegt wurde. Dann reicht DateTime.Now nicht immer aus, da es nicht verspottbar ist.
Smoksnes
7

Dies ist ein klassisches Beispiel für DI, das Ihnen sagt, dass Ihre Klasse wahrscheinlich zu groß wird, um eine einzelne Klasse zu sein. Dies wird oft interpretiert als "Wow, DI macht meine Konstruktoren größer als Jupiter, diese Technik ist schrecklich", aber was es WIRKLICH sagt, ist, dass "Ihre Klasse eine Schiffsladung von Abhängigkeiten hat". Wenn wir das wissen, können wir es auch

  • Fegen Sie das Problem unter den Teppich, indem Sie stattdessen neue Abhängigkeiten aufbauen
  • Überdenken Sie unser Design. Vielleicht kommen einige Abhängigkeiten immer zusammen und sollten hinter einer anderen Abstraktion verborgen sein. Vielleicht sollte Ihre Klasse in 2 aufgeteilt werden. Vielleicht sollte sie aus mehreren Klassen bestehen, für die jeweils eine kleine Teilmenge der Abhängigkeiten erforderlich ist.

Es gibt unzählige Möglichkeiten, Abhängigkeiten zu verwalten, und es ist unmöglich zu sagen, was in Ihrem Fall am besten funktioniert, ohne Ihren Code und Ihre Anwendung zu kennen.

So beantworten Sie Ihre letzten Fragen:

  • Gibt es eine Obergrenze für die Anzahl der Abhängigkeiten, die eine Klasse haben sollte?

Ja, die Obergrenze ist "zu viele". Wie viele sind "zu viele"? "Zu viele" ist, wenn der Zusammenhalt der Klasse "zu gering" wird. Es hängt alles ab. Normalerweise, wenn Ihre Reaktion auf eine Klasse "Wow, dieses Ding hat viele Abhängigkeiten" ist, ist das zu viel.

  • Verbessert oder beeinträchtigt das Injizieren von Abhängigkeiten die Lesbarkeit?

Ich denke, diese Frage ist irreführend. Die Antwort kann ja oder nein sein. Aber es ist nicht der interessanteste Teil. Das Einfügen von Abhängigkeiten besteht darin, sie SICHTBAR zu machen. Es geht darum, eine API zu erstellen, die nicht lügt. Es geht darum, den globalen Staat zu verhindern. Es geht darum, Code testbar zu machen. Es geht darum, die Kopplung zu reduzieren.

Gut gestaltete Klassen mit vernünftig gestalteten und benannten Methoden sind besser lesbar als schlecht gestaltete. DI verbessert oder beeinträchtigt die Lesbarkeit an sich nicht wirklich, es hebt nur Ihre Designentscheidungen hervor, und wenn sie schlecht sind, stechen Ihre Augen. Dies bedeutet nicht, dass DI Ihren Code weniger lesbar gemacht hat. Es hat Ihnen nur gezeigt, dass Ihr Code bereits ein Chaos war. Sie hatten ihn gerade versteckt.

Sara
quelle
1
Gute Rückmeldung. Vielen Dank. Ja, die Obergrenze ist "zu viele". - Fantastisch und so wahr.
Smoksnes
3

Laut Steve McConnel, Autor des allgegenwärtigen "Code Complete", gilt als Faustregel, dass mehr als 7 ein Codegeruch sind, der die Wartbarkeit beeinträchtigt. Ich persönlich denke, dass die Anzahl in den meisten Fällen niedriger ist, aber wenn Sie DI richtig ausführen, müssen Sie viele Abhängigkeiten injizieren, wenn Sie sich sehr nahe an der Kompositionswurzel befinden. Dies ist normal und wird erwartet. Dies ist einer der Gründe, warum IoC-Container eine nützliche Sache sind und die Komplexität wert sind, die sie einem Projekt hinzufügen.

Wenn Sie sich also sehr nahe am Einstiegspunkt Ihres Programms befinden, ist dies normal und akzeptabel. Wenn Sie tiefer in die Logik des Programms eintauchen, ist es wahrscheinlich ein Geruch, der angegangen werden sollte.

Badeente
quelle