Lohnt sich Dependency Injection außerhalb von UnitTesting?

17

Ist es angesichts eines Konstruktors, der niemals unterschiedliche Implementierungen mehrerer von ihm initialisierter Objekte verwenden muss, immer noch praktisch, DI zu verwenden? Schließlich möchten wir vielleicht noch einen Unit-Test durchführen.

Die betreffende Klasse initialisiert einige andere Klassen in ihrem Konstruktor, und die von ihr verwendeten Klassen sind ziemlich spezifisch. Es wird niemals eine andere Implementierung verwenden. Sind wir berechtigt, den Versuch zu vermeiden, auf eine Schnittstelle zu programmieren?

Seph
quelle
3
KISS - ist immer der beste Ansatz.
Reactgular
5
Meiner Meinung nach ist diese Frage konkreter als das vorgeschlagene Duplikat. Design-for-future-Änderungen-oder-lösen-das-Problem-at-Hand , also bin Abstimmung nicht in der Nähe dieses
k3b
1
Ist Testbarkeit nicht Grund genug?
ConditionRacer
Klingt nicht wirklich nach einem Duplikat, sondern nach einer Antwort. Wenn es niemals passieren wird, ist das Entwerfen nicht für zukünftige Änderungen vorgesehen. Sogar Architekturastronauten entwerfen nur für Veränderungen, die niemals aus einem schlechten Urteil heraus geschehen.
Steve314

Antworten:

13

Es hängt davon ab, wie sicher Sie sind, dass "nie, nie" richtig ist (während der Zeit, in der Ihre App verwendet wird).

Allgemein:

  • Ändern Sie Ihr Design nicht nur aus Gründen der Testbarkeit. Unit Tests sind da, um Ihnen zu dienen, nicht umgekehrt.
  • Wiederholen Sie sich nicht. Eine Schnittstelle zu erstellen, die immer nur einen Erben hat, ist nutzlos.
  • Wenn eine Klasse nicht testbar ist, ist sie wahrscheinlich auch für reale Anwendungsfälle nicht flexibel / erweiterbar. Manchmal ist das in Ordnung - es ist unwahrscheinlich, dass Sie diese Flexibilität benötigen, aber Einfachheit / Zuverlässigkeit / Wartbarkeit / Leistung / was auch immer wichtiger ist.
  • Wenn Sie nicht wissen, codieren Sie die Schnittstelle. Anforderungen ändern sich.
Telastyn
quelle
12
Fast haben Sie -1 für "Ändern Sie Ihr Design nicht nur aus Gründen der Testbarkeit". Testbarkeit zu erlangen, ist meiner Meinung nach einer der wichtigsten Gründe, ein Design zu ändern! Aber deine anderen Punkte sind in Ordnung.
Doc Brown
5
@docBrown - (und andere) Ich unterscheide mich hier von vielen und vielleicht sogar von der Mehrheit. ~ 95% der Zeit machen Dinge testbar und machen sie flexibel oder erweiterbar. Sie sollten das die meiste Zeit in Ihrem Design haben (oder das zu Ihrem Design hinzufügen) - Testbarkeit ist verdammt. Die anderen ~ 5% haben entweder ungerade Anforderungen, die diese Art von Flexibilität zugunsten von etwas anderem verhindern, oder sind so wichtig / unveränderlich, dass Sie ziemlich schnell feststellen können, ob sie kaputt sind, auch ohne spezielle Tests.
Telastyn
4
Möglicherweise sicher, aber Ihre erste obige Aussage könnte zu leicht als "schlecht gestalteten Code belassen, wie er ist, wenn Ihre einzige Sorge die Testbarkeit ist" fehlinterpretiert werden.
Doc Brown
1
Warum würden Sie sagen "Eine Schnittstelle zu erstellen, die immer nur einen Erben hat, ist nutzlos."? Schnittstelle kann als Vertrag arbeiten.
Kaptan
2
@kaptan - weil das konkrete Objekt genauso gut funktionieren kann wie ein Vertrag. Wenn Sie nur zwei machen, müssen Sie den Code zweimal schreiben (und den Code zweimal ändern, wenn er sich ändert). Zugegeben, die überwiegende Mehrheit der Schnittstellen verfügt über mehrere (nicht testbasierte) Implementierungen, oder Sie müssen sich auf diese Situation vorbereiten. Aber für den seltenen Fall, dass Sie nur ein einfaches versiegeltes Objekt benötigen, verwenden Sie es einfach direkt.
Telastyn
12

Sind wir berechtigt, den Versuch zu vermeiden, auf eine Schnittstelle zu programmieren?

Während der Vorteil der Codierung gegenüber einer Schnittstelle ziemlich offensichtlich ist, sollten Sie sich fragen, was genau dadurch erreicht wird, dass Sie dies nicht tun.

Die nächste Frage lautet: Liegt es in der Verantwortung der betreffenden Klasse, die Implementierungen auszuwählen, oder nicht? Das kann der Fall sein oder nicht, und Sie sollten entsprechend handeln.

Letztendlich besteht immer die Möglichkeit, dass eine doofe Einschränkung aus heiterem Himmel kommt. Möglicherweise möchten Sie denselben Code gleichzeitig ausführen, und die Möglichkeit, die Implementierung einzufügen, kann bei der Synchronisierung hilfreich sein. Oder Sie stellen nach der Profilerstellung Ihrer App möglicherweise fest, dass Sie eine andere Zuordnungsstrategie als die einfache Instanziierung wünschen. Oder es treten Querschnittsthemen auf, und Sie haben keine AOP-Unterstützung zur Hand. Wer weiß?

YAGNI empfiehlt, beim Schreiben von Code nichts Überflüssiges hinzuzufügen. Eines der Dinge, die Programmierer ihrem Code hinzufügen, ohne sich dessen bewusst zu sein, sind überflüssige Annahmen. Als "diese Methode könnte nützlich sein" oder "das wird sich nie ändern". Beides verleiht Ihrem Design Unordnung.

back2dos
quelle
2
+1 für das Zitieren von YAGNI hier als Argument für DI, nicht dagegen.
Doc Brown
4
@DocBrown: In Anbetracht dessen, dass YAGNI hier verwendet werden kann, um auch zu rechtfertigen , dass DI nicht verwendet wird. Ich denke, das ist eher ein Argument dafür, dass YAGNI eine nützliche Maßnahme für alles ist.
Steven Evers
1
+1 für überflüssige Annahmen in YAGNI, die uns ein paar Mal in den Kopf getroffen haben
Izkata
@SteveEvers: Ich denke, die Verwendung von YAGNI zur Rechtfertigung des Ignorierens des Abhängigkeitsinversionsprinzips schließt einen Mangel an Verständnis für YAGNI oder DIP oder vielleicht sogar einige der grundlegenderen Prinzipien des Programmierens und Denkens aus. Sie sollten das DIP nur dann ignorieren, wenn dies erforderlich ist - ein Umstand, bei dem YAGNI von Natur aus einfach nicht anwendbar ist.
back2dos
@ back2dos: Die Vermutung, dass DI wegen 'Vielleicht-Anforderungen' nützlich ist und "wer weiß?" ist genau der Gedankengang, den YAGNI bekämpfen soll, stattdessen verwenden Sie ihn als Rechtfertigung für zusätzliche Werkzeuge und Infrastruktur.
Steven Evers
7

Es hängt von verschiedenen Parametern ab, aber hier sind einige Gründe, warum Sie DI immer noch verwenden möchten:

  • Testen ist an sich schon ein guter Grund, glaube ich
  • Die für Ihr Objekt erforderlichen Klassen werden möglicherweise an anderen Stellen verwendet. Wenn Sie sie einfügen, können Sie die Ressourcen bündeln (z. B. wenn es sich bei den betreffenden Klassen um Threads oder Datenbankverbindungen handelt). Sie möchten nicht, dass jede Ihrer Klassen neue Klassen erstellt Anschlüsse)
  • Wenn das Initialisieren dieser anderen Objekte fehlschlagen kann, kann Code, der höher im Stapel liegt, wahrscheinlich besser mit Problemen umgehen als Ihre Klasse, die die Fehler dann wahrscheinlich einfach weiterleitet

Fazit: In diesem Fall können Sie wahrscheinlich ohne DI leben, aber es besteht die Möglichkeit, dass die Verwendung von DI zu einem saubereren Code führt.

Assylias
quelle
3

Wenn Sie ein Programm oder eine Funktion entwerfen, sollte ein Teil des Denkprozesses sein , wie Sie es testen werden. Abhängigkeitsinjektion ist eines der Testwerkzeuge und sollte als Teil der Mischung betrachtet werden.

Die zusätzlichen Vorteile von Dependency Injection und die Verwendung von Interfaces anstelle von Klassen sind auch bei der Erweiterung einer Anwendung von Vorteil. Sie können aber auch später mithilfe von Suchen und Ersetzen ganz einfach und sicher in den Quellcode nachgerüstet werden. - Auch bei sehr großen Projekten.

Michael Shaw
quelle
2
 > Dependency Injection worth it **outside** of UnitTesting?
 > Are we justified in avoiding trying to program to an interface?

Viele Antworten auf diese Frage können als "Sie könnten es brauchen, weil ..." als YAGNI diskutiert werden, wenn Sie nicht unittesting machen möchten.

Wenn Sie sich fragen, welche Gründe neben Unittests das Programmieren auf eine Schnittstelle erfordern

Ja , Dependency Injection lohnt sich außerhalb von UnitTesting, wenn Sie die Steuerung umkehren müssen . Zum Beispiel, wenn eine Modulimplementierung ein anderes Modul benötigt, auf das in seiner Ebene nicht zugegriffen werden kann.

Beispiel: Wenn Sie die Module haben gui=> businesslayer=> driver=> common.

In diesem Szenario businesslayerkann verwendet, driveraber drivernicht verwendet werden businesslayer.

Wenn Sie drivereine höhere Funktionalität benötigen, können Sie diese Funktionalität businesslayerimplementieren, indem Sie eine Schnittstelle in commonLayer implementieren . drivermuss nur die Schnittstelle in der commonSchicht kennen.

k3b
quelle
1

Ja, erstens: Vergessen Sie Unit-Tests als Grund, Ihren Code um die Unit-Test-Tools herum zu entwerfen. Es ist niemals eine gute Idee, Ihr Code-Design so zu biegen, dass es einer künstlichen Einschränkung entspricht. Wenn Ihre Tools Sie dazu zwingen, sollten Sie sich bessere Tools besorgen (z. B. Microsoft Fakes / Moles, mit denen Sie viel mehr Optionen zum Erstellen von Scheinobjekten haben).

Würden Sie Ihre Klassen beispielsweise in nur öffentliche Methoden aufteilen, nur weil die Testtools nicht mit privaten Methoden funktionieren? (Ich weiß, dass die vorherrschende Weisheit darin besteht, so zu tun, als müssten Sie keine privaten Methoden testen, aber ich bin der Meinung, dass dies eine Reaktion auf die Schwierigkeit ist, dies mit aktuellen Tools zu tun, und keine echte Reaktion darauf, keine privaten Methoden testen zu müssen.)

Alles in allem kommt es darauf an, was für ein TDDer Sie sind - der "Spötter", wie Fowler ihn beschreibt , muss den Code an die verwendeten Tools anpassen , während die "klassischen" Tester Tests erstellen, die stärker in die Natur integriert sind (dh testen Sie die Klasse als Einheit, nicht jede Methode), sodass weniger Schnittstellen erforderlich sind, insbesondere wenn Sie Tools verwenden, mit denen sich konkrete Klassen verspotten lassen.

gbjbaanb
quelle
3
Ich denke nicht, dass die vorherrschende Weisheit, dass private Methoden nicht getestet werden sollten, etwas mit der Schwierigkeit zu tun hat, sie zu testen. Wenn in einer privaten Methode ein Fehler aufgetreten ist, der sich jedoch nicht auf das Verhalten des Objekts ausgewirkt hat, hat dies keinerlei Auswirkungen. Wenn in einer privaten Methode ein Fehler aufgetreten ist, der sich auf das Verhalten des Objekts ausgewirkt hat, liegt ein fehlgeschlagener oder fehlender Test für mindestens eine der öffentlichen Methoden eines Objekts vor.
Michael Shaw
Es kommt darauf an, ob Sie auf Verhalten oder Zustand testen. Die privaten Methoden müssen auf irgendeine Weise getestet werden, aber wenn Sie eine klassische TDD-Person sind, testen Sie sie, indem Sie die Klasse "als Ganzes" testen. Die meisten Testtools konzentrieren sich auf das Testen jeder Methode einzeln ... und mein Punkt ist, dass letztere effektiv nicht richtig testen, da sie die Chance verpassen, einen vollständigen Test durchzuführen. Ich stimme Ihnen also zu, während ich versuche hervorzuheben, wie TDD von einigen (den meisten?) Leuten, die heute Unit-Tests durchführen, untergraben wurde.
gbjbaanb
1

Dependency Injection macht es auch deutlicher , dass Ihr Objekt HAS Abhängigkeiten.

Wenn jemand anderes Ihr Objekt naiv verwendet (ohne auf den Konstruktor zu schauen), wird er feststellen, dass er Dienste, Verbindungen usw. einrichten muss. Dies war nicht offensichtlich, da er sie nicht an den Konstruktor übergeben musste . Durch die Übergabe der Abhängigkeiten wird klarer, was benötigt wird.

Sie verbergen möglicherweise auch die Tatsache, dass Ihre Klasse möglicherweise die SRP verletzt. Wenn Sie in vielen Abhängigkeiten bestehen (mehr als 3), kann Ihre Klasse zu viel tun und sollte überarbeitet werden. Wenn Sie sie jedoch im Konstruktor erstellen, wird dieser Codegeruch ausgeblendet.

Schleis
quelle
0

Ich stimme beiden Lagern hier zu und die Angelegenheit steht meiner Meinung nach noch zur Debatte. YAGNI verlangt, dass ich meinen Code nicht so ändere, dass er der Annahme entspricht. Unit-Tests sind hier kein Problem, da ich fest davon überzeugt bin, dass sich die Abhängigkeit niemals ändern wird. Aber wenn ich zu Beginn Unit-Tests durchgeführt hätte, wäre ich ohnehin nie an diesem Punkt angelangt. Als hätte ich mich irgendwann in DI eingeschlichen.

Lassen Sie mich speziell für mein Szenario eine andere Alternative anbieten

Mit einem Factory-Muster kann ich Abhängigkeiten an einem Ort lokalisieren. Beim Testen kann ich die Implementierung basierend auf dem Kontext ändern, und das ist gut genug. Dies widerspricht YAGNI nicht aufgrund der Vorteile der Abstraktion mehrerer Klassenkonstruktionen.

Seph
quelle