Sind so genannte Querschnittsthemen eine gute Entschuldigung, um SOLID / DI / IoC zu brechen?

43

Meine Kollegen sagen gerne "Protokollierung / Caching / etc. Ist ein Querschnittsthema" und verwenden dann überall den entsprechenden Singleton. Dennoch lieben sie IoC und DI.

Ist es wirklich eine gute Entschuldigung, das SOLI D- Prinzip zu brechen ?

Den
quelle
27
Ich habe festgestellt, dass die SOLID-Prinzipien zumeist nur für diejenigen nützlich sind, die bereits die notwendige Erfahrung haben, um ihnen unwissentlich zu folgen.
Svidgen
1
Ich habe festgestellt, dass der Begriff "Querschnittsthema" ein ziemlich spezifischer Begriff ist (der an bestimmte Aspekte gebunden ist) und nicht unbedingt mit Singleton synonym ist. Die Protokollierung kann ein Querschnittsthema sein, da Sie manchmal eine allgemeine Nachricht auf dieselbe Weise an mehreren Stellen protokollieren möchten (z. B. jeden Anruf bei einer Dienstmethode protokollieren, die den Anruf getätigt hat, oder eine andere Überwachungsart protokollieren). Ich würde jedoch argumentieren, dass die Protokollierung (im allgemeinen Sinne) kein Querschnittsthema ist, nur weil sie überall verwendet wird. Ich denke, das ist eher ein "allgegenwärtiges Anliegen", obwohl das eigentlich kein Standardbegriff ist.
Schritt
4
@svidgen die SOLID-Prinzipien sind auch nützlich, wenn diejenigen, die sie nicht kennen, Ihren Code überprüfen und Sie fragen, warum Sie das so gemacht haben. Es ist schön, auf ein Prinzip hinweisen zu können und zu sagen: "Deshalb"
candied_orange
1
Haben Sie Beispiele, warum die Protokollierung Ihrer Meinung nach ein Sonderfall ist? Es ist ein ziemlich simpler Kanal, einige Daten umzuleiten, normalerweise als Teil des von Ihnen verwendeten X-Frameworks.
Ksiimson

Antworten:

42

Nein.

SOLID ist eine Richtlinie zur Berücksichtigung unvermeidlicher Änderungen. Werden Sie Ihre Protokollbibliothek, Ihr Ziel, Ihre Filterung, Ihre Formatierung oder ... wirklich nie ändern? Werden Sie Ihre Cachebibliothek, Ihr Ziel, Ihre Strategie oder Ihren Umfang wirklich nicht ändern oder ...?

Natürlich bist du. Am allerwenigsten , Sie gehen diese Dinge in einer vernünftigen Art und Weise verspotten wollen , dass sie zu isolieren , den Zweck zu testen. Und wenn Sie sie zum Testen isolieren möchten, werden Sie wahrscheinlich auf einen Geschäftsgrund stoßen, bei dem Sie sie aus Gründen des realen Lebens isolieren möchten.

Und Sie erhalten dann das Argument, dass der Logger selbst die Änderung handhaben wird. "Oh, wenn sich das Ziel / Filtern / Formatieren / Strategie ändert, ändern wir einfach die Konfiguration!" Das ist Müll. Sie haben jetzt nicht nur ein God-Objekt, das all diese Dinge erledigt, sondern Sie schreiben Ihren Code auch in XML (oder ähnlichem), wobei Sie keine statische Analyse erhalten, keine Fehler bei der Kompilierung auftreten und keine t wirklich effektive Unit-Tests erhalten.

Gibt es Fälle, in denen SOLID-Richtlinien verletzt werden? Absolut. Manchmal ändern sich die Dinge nicht (ohne dass eine vollständige Neuschreibung erforderlich ist). Manchmal ist eine leichte Verletzung von LSP die sauberste Lösung. Manchmal ist es nicht sinnvoll, eine isolierte Schnittstelle zu erstellen.

Protokollierung und Zwischenspeicherung (und andere allgegenwärtige Querschnittsthemen) sind jedoch nicht diese Fälle. Es handelt sich in der Regel um hervorragende Beispiele für Kupplungs- und Konstruktionsprobleme, die auftreten, wenn Sie die Richtlinien ignorieren.

Telastyn
quelle
22
Welche Alternative hast du dann? Injizieren Sie einen ILogger in jede Klasse, die Sie schreiben? Einen Dekorateur für jede Klasse schreiben, die einen Logger braucht?
Robert Harvey
16
Ja. Wenn etwas eine Abhängigkeit ist, machen Sie es zu einer Abhängigkeit . Und wenn das zu viele Abhängigkeiten bedeutet oder Sie irgendwo einen Logger übergeben, sollte es nicht funktionieren - gut , jetzt können Sie Ihr Design korrigieren.
Telastyn
20
Das eigentliche Problem, das ich mit der dogmatischen IoC habe, ist, dass so ziemlich jeder moderne Code von etwas abhängt , das weder injiziert noch abstrahiert wird. Wenn Sie schreiben , C #, zum Beispiel, sind Sie nicht oft zu abstrakt gehen Stringoder Int32oder sogar Listaus Ihrem Modul. Es gibt nur ein Maß, in dem es vernünftig und vernünftig ist , Veränderungen zu planen. Und jenseits der meist offensichtlichen "Kern" -Typen ist es wirklich nur eine Frage der Erfahrung und des Urteilsvermögens , zu erkennen, was Sie wahrscheinlich ändern werden .
Svidgen
6
@svidgen - Ich hoffe, Sie haben meine Antwort nicht als Verfechter der dogmatischen IoC gelesen. Und nur weil wir diese Kerntypen (und andere Dinge, bei denen Vorsicht geboten ist) nicht abstrahieren, heißt das nicht, dass es in Ordnung ist, eine globale Liste / Zeichenfolge / Int / etc zu haben.
Telastyn
38

Ja

Dies ist der springende Punkt des Begriffs "Querschnittsthema" - es bedeutet etwas, das nicht genau in das SOLID-Prinzip passt.

Hier trifft Idealismus auf Realität.

SOLID-Neulinge und Querschnittstalente sind häufig mit dieser mentalen Herausforderung konfrontiert. Es ist in Ordnung, nicht ausflippen. Bemühen Sie sich, alles in Bezug auf SOLID zu bringen, aber es gibt einige Stellen wie Protokollierung und Zwischenspeicherung, an denen SOLID einfach keinen Sinn ergibt. Cross-Cutting ist der Bruder von SOLID, sie gehen Hand in Hand.

Klom Dark
quelle
9
Eine begründete, praktische und nicht dogmatische Antwort.
user1936
11
Können Sie uns bitte erklären, warum alle fünf SOLID-Prinzipien durch Querschnittsthemen verletzt werden? Ich habe Probleme, das zu verstehen.
Doc Brown
4
Ja. Ich könnte mir einen guten Grund vorstellen, jedes Prinzip einzeln zu "brechen". aber kein einziger Grund, alle 5 zu brechen!
Svidgen
1
Es wäre ideal für mich, wenn ich nicht jedes Mal meinen Kopf gegen die Wand schlagen müsste, wenn ich einen Singleton-Logger / Cache für einen Unit-Test verspotten müsste. Stellen Sie sich vor, wie es wäre, eine Webanwendung ohne Unit-Test zu testen HttpContextBase(was genau aus diesem Grund eingeführt wurde). Ich weiß mit Sicherheit, dass meine Realität ohne diese Klasse wirklich sauer wäre.
Devnull
2
@devnull vielleicht ist das dann das Problem, und nicht der Querschnitt betrifft sich selbst ...
Boris the Spider
25

Zum loggen finde ich es. Die Protokollierung ist allgegenwärtig und im Allgemeinen unabhängig von der Dienstfunktionalität. Es ist allgemein bekannt und bekannt, das Protokollierungsframework als Singleton-Muster zu verwenden. Wenn nicht, erstellen und injizieren Sie überall Logger , und das wollen Sie nicht.

Ein Problem ist, dass jemand sagt: "Aber wie kann ich die Protokollierung testen?" . Ich bin der Meinung, dass ich die Protokollierung normalerweise nicht teste, sondern nur behaupten kann, dass ich die Protokolldateien tatsächlich lesen und verstehen kann. Wenn ich gesehen habe, wie die Protokollierung getestet wurde, muss normalerweise jemand behaupten, dass eine Klasse tatsächlich etwas getan hat, und er verwendet die Protokollnachrichten, um dieses Feedback zu erhalten. Ich würde viel lieber einen Zuhörer / Beobachter für diese Klasse registrieren und in meinen Tests behaupten, dass das aufgerufen wird. Sie können dann Ihre Ereignisprotokollierung in diesem Beobachter ablegen.

Ich denke, Caching ist jedoch ein völlig anderes Szenario.

Brian Agnew
quelle
3
Nachdem ich mich inzwischen ein wenig mit FP beschäftigt habe, ist mein Interesse an der Verwendung von (vielleicht monadischer) Funktionskomposition gewachsen, um Dinge wie Protokollierung, Fehlerbehandlung usw. in Ihre Anwendungsfälle einzubetten, ohne dass etwas davon in die Kerngeschäftslogik aufgenommen wird ( siehe fsharpforfunandprofit.com/rop )
sara
5
Protokollierung sollte nicht allgegenwärtig sein. Wenn dies der Fall ist, protokollieren Sie viel zu viel und verursachen nur Geräusche, wenn Sie sich diese Protokolle tatsächlich ansehen müssen. Indem Sie die Logger-Abhängigkeit einschleusen, müssen Sie sich überlegen, ob Sie tatsächlich etwas protokollieren müssen oder nicht.
Rubberduck
12
@RubberDuck - Sagen Sie mir, dass "Protokollierung nicht allgegenwärtig sein sollte", wenn ich einen Fehlerbericht aus dem Feld erhalte, und die einzige Debug-Fähigkeit, die ich herausfinden muss, ist die Protokolldatei, die ich nicht allgegenwärtig machen wollte. Gelernte Lektion, sollte die Protokollierung "allgegenwärtig", sehr allgegenwärtig sein.
Dunk
15
@RubberDuck: Mit Server-Software ist die Protokollierung der einzige Weg, um zu überleben. Dies ist die einzige Möglichkeit, einen Fehler herauszufinden, der vor 12 Stunden statt jetzt auf Ihrem eigenen Laptop aufgetreten ist. Mach dir keine Sorgen über Lärm. Verwenden Sie Log - Management - Software , so dass Sie Ihre Login abfragen können ( im Idealfall sollten Sie auch Alarme einrichten, die Sie auf Fehlerprotokolle E - Mail wird)
slebetman
6
Unsere Support-Mitarbeiter rufen mich an und teilen mir mit, dass "der Kunde auf einige Seiten geklickt hat und ein Fehler aufgetreten ist". Ich frage sie, was der Fehler gesagt hat, sie wissen es nicht. Ich frage, was der Kunde konkret angeklickt hat, er weiß es nicht. Ich frage, ob sie sich reproduzieren können, sie können nicht. Ich bin damit einverstanden, dass die Protokollierung größtenteils mit gut eingesetzten Protokollierern an einigen wichtigen Eckpunkten erreicht werden kann, aber die kleinen seltsamen Nachrichten hier und da sind ebenfalls von unschätzbarem Wert. Die Angst vor der Protokollierung führt zu schlechterer Softwarequalität. Wenn Sie am Ende zu viele Daten haben, kürzen Sie diese. Nicht vorzeitig optimieren.
Schritt
12

Meine 2 Cent ...

Ja und nein.

Sie sollten niemals wirklich gegen die Grundsätze verstoßen, die Sie annehmen. Ihre Prinzipien sollten jedoch immer nuanciert und im Dienste eines höheren Ziels übernommen werden. Bei einem angemessen konditionierten Verständnis sind einige offensichtliche Verstöße möglicherweise keine tatsächlichen Verstöße gegen den "Geist" oder den "Gesamtkörper der Prinzipien".

Insbesondere die SOLID-Prinzipien erfordern nicht nur viel Nuance, sondern sind letztendlich auch dem Ziel unterworfen, "funktionierende, wartbare Software zu liefern". Die Einhaltung eines bestimmten SOLID-Prinzips ist daher selbstzerstörerisch und widersprüchlich, wenn dies im Widerspruch zu den Zielen von SOLID steht. Und hier stelle ich oft fest, dass das Liefern von Trümpfen die Wartbarkeit übertrifft .

Was ist also mit dem D in SOLID ? Nun, es trägt zu einer verbesserten Wartbarkeit bei, indem Ihr wiederverwendbares Modul relativ unabhängig von seinem Kontext ist. Und wir können das "wiederverwendbare Modul" als "eine Sammlung von Code, den Sie in einem anderen bestimmten Kontext verwenden möchten" definieren. Dies gilt für einzelne Funktionen, Klassen, Klassengruppen und Programme.

Und ja, durch das Ändern der Logger-Implementierungen wird Ihr Modul wahrscheinlich in einen "anderen eindeutigen Kontext" gebracht.

Lassen Sie mich also meine zwei großen Vorbehalte aussprechen :

Erstens: Das Zeichnen der Linien um die Codeblöcke, die "ein wiederverwendbares Modul" bilden, ist eine Frage der professionellen Beurteilung. Und Ihr Urteil beschränkt sich notwendigerweise auf Ihre Erfahrung.

Wenn Sie derzeit nicht vorhaben, ein Modul in einem anderen Kontext zu verwenden, ist es wahrscheinlich in Ordnung, dass es hilflos davon abhängt. Vorbehalt zum Vorbehalt: Ihre Pläne sind wahrscheinlich falsch - aber das ist auch in Ordnung. Je länger Sie Modul für Modul schreiben, desto intuitiver und präziser wird Ihr Gefühl, ob "Ich werde das eines Tages wieder brauchen". Aber Sie werden wahrscheinlich nie im Nachhinein sagen können: "Ich habe alles so weit wie möglich modularisiert und entkoppelt, aber ohne Übermaß ."

Wenn Sie sich wegen Ihrer Urteilsfehler schuldig fühlen, gehen Sie zum Geständnis und fahren Sie fort ...

Zweitens: Das Invertieren der Steuerung entspricht nicht dem Einspeisen von Abhängigkeiten .

Dies gilt insbesondere dann, wenn Sie damit beginnen, Abhängigkeiten ohne Übelkeit zu injizieren . Die Abhängigkeitsinjektion ist eine nützliche Taktik für die übergreifende IoC-Strategie. Ich würde jedoch argumentieren, dass DI von geringerer Wirksamkeit ist als einige andere Taktiken - wie die Verwendung von Schnittstellen und Adaptern -, um einzelne Punkte innerhalb des Moduls dem Kontext auszusetzen.

Und lassen Sie uns für eine Sekunde wirklich darauf konzentrieren. Denn selbst wenn Sie eine Logger Anzeige mit Übelkeit einschleusen , müssen Sie Code für die LoggerBenutzeroberfläche schreiben . Sie konnten kein neues LoggerProdukt eines anderen Herstellers verwenden, das Parameter in einer anderen Reihenfolge verwendet. Diese Fähigkeit ergibt sich aus der Codierung innerhalb des Moduls gegenüber einer innerhalb des Moduls vorhandenen Schnittstelle, die ein einziges Submodul (Adapter) zum Verwalten der Abhängigkeit enthält.

Und wenn Sie gegen einen Adapter codieren, Loggerist es für das Gesamtziel der Wartbarkeit im Allgemeinen verdammt unbedeutend , ob das in diesen Adapter injiziert oder vom Adapter entdeckt wird. Und was noch wichtiger ist: Wenn Sie einen Adapter auf Modulebene haben, ist es wahrscheinlich absurd, ihn in irgendetwas zu injizieren. Es ist für das Modul geschrieben.

tl; dr - Hören Sie auf, sich mit Prinzipien zu beschäftigen, ohne darüber nachzudenken, warum Sie diese Prinzipien verwenden. Und, praktischer, bauen Sie einfach ein Adapterfür jedes Modul. Verwenden Sie Ihr Urteilsvermögen, wenn Sie entscheiden, wo Sie die "Modul" -Grenzen zeichnen. Gehen Sie von jedem Modul aus weiter und beziehen Sie sich direkt auf das Adapter. Und sicher, injizieren Sie den echten Logger in die Adapter- aber nicht in jede Kleinigkeit, die es brauchen könnte.

Svidgen
quelle
4
Mann ... ich hätte eine kürzere Antwort gegeben, aber ich hatte nicht die Zeit.
Svidgen
2
+1 für die Verwendung eines Adapters. Sie müssen Abhängigkeiten von Komponenten von Drittanbietern isolieren, damit diese nach Möglichkeit ersetzt werden können. Die Protokollierung ist hierfür ein einfaches Ziel - es gibt viele Protokollierungsimplementierungen, die zumeist ähnliche APIs aufweisen. Mit einer einfachen Adapterimplementierung können Sie sie daher sehr einfach ändern. Und für Leute, die sagen, dass Sie Ihren Protokollanbieter niemals ändern werden: Ich musste dies tun, und es verursachte große Schmerzen, weil ich keinen Adapter verwendete.
Jules
"Insbesondere die SOLID-Prinzipien erfordern nicht nur eine Menge Nuancen, sondern sind letztendlich auch dem Ziel unterworfen," funktionierende, wartbare Software zu liefern "." - Dies ist eine der besten Aussagen, die ich je gesehen habe. Als mild-OCD-Programmierer hatte ich einige Probleme mit Idealismus vs. Produktivität.
DVK
Jedes Mal, wenn ich eine +1 oder einen Kommentar zu einer alten Antwort sehe, lese ich meine Antwort erneut und stelle erneut fest, dass ich ein furchtbar unorganisierter und unklarer Schriftsteller bin ... Ich schätze den Kommentar jedoch, @DVK.
Svidgen
Adapter sind Lebensretter, sie kosten nicht viel und erleichtern Ihnen das Leben erheblich, insbesondere, wenn Sie SOLID anwenden möchten. Testen ist mit ihnen ein Kinderspiel.
Alan
8

Die Idee, dass die Protokollierung immer als Singleton implementiert werden sollte , ist eine dieser Lügen, die so oft erzählt wurden, dass sie Anklang gefunden hat.

So lange es moderne Betriebssysteme gibt, wurde erkannt, dass Sie sich je nach Art der Ausgabe möglicherweise an mehreren Stellen anmelden möchten .

Systementwickler sollten ständig die Wirksamkeit früherer Lösungen hinterfragen, bevor sie blindlings in neue Lösungen einbezogen werden. Wenn sie nicht so fleißig sind, dann machen sie ihren Job nicht.

Robbie Dee
quelle
2
Was ist dann Ihre vorgeschlagene Lösung? Wird jeder Klasse, die Sie schreiben, ein ILogger hinzugefügt? Was ist mit einem Decorator-Muster?
Robert Harvey
4
Ich beantworte meine Frage jetzt nicht wirklich, oder? Ich habe die Muster nur als Beispiele angegeben ... Es müssen nicht die sein, wenn Sie etwas Besseres im Sinn haben.
Robert Harvey
8
Ich verstehe diese Antwort in Bezug auf konkrete Vorschläge nicht wirklich
Brian Agnew,
3
@RobbieDee - Sie sagen also, dass die Protokollierung auf die verschlungenste und unbequemste Art und Weise implementiert werden sollte, da es keine Chance gibt, dass wir uns an mehreren Orten anmelden möchten? Auch wenn eine solche Änderung eingetreten ist, ist das Hinzufügen dieser Funktionalität zu der vorhandenen Logger-Instanz Ihrer Meinung nach mehr Arbeit als der gesamte Aufwand, den Logger zwischen Klassen und wechselnden Schnittstellen weiterzugeben, wenn Sie sich für einen Logger entscheiden oder nicht In den Dutzenden von Projekten, in denen diese Protokollierung an mehreren Orten niemals stattfinden wird?
Dunk
2
Betreff: "Abhängig von der Art der Ausgabe möchten Sie möglicherweise an mehreren Stellen protokollieren": Sicher, aber die beste Vorgehensweise liegt im Protokollierungsframework , anstatt zu versuchen, mehrere separate Protokollierungsabhängigkeiten einzufügen. (Die gemeinsamen Protokollierungs-Frameworks unterstützen dies bereits.)
Ruakh
4

Echtes Protokollieren ist ein Sonderfall.

@Telastyn schreibt:

Werden Sie Ihre Protokollbibliothek, Ihr Ziel, Ihre Filterung, Ihre Formatierung oder ... wirklich nie ändern?

Wenn Sie damit rechnen, dass Sie möglicherweise Ihre Protokollbibliothek ändern müssen, sollten Sie eine Fassade verwenden. dh SLF4J, wenn Sie in der Java-Welt sind.

Im Übrigen sorgt eine anständige Protokollierungsbibliothek dafür, dass geändert wird, wo die Protokollierung stattfindet, welche Ereignisse gefiltert werden, wie Protokollereignisse mithilfe von Protokollkonfigurationsdateien und (falls erforderlich) benutzerdefinierten Plug-in-Klassen formatiert werden. Es gibt eine Reihe von Standardalternativen.

Kurz gesagt, dies sind gelöste Probleme ... für die Protokollierung ... und daher ist die Verwendung von Dependency Injection nicht erforderlich, um sie zu lösen.

Der einzige Fall, in dem DI von Vorteil sein kann (gegenüber den Standardprotokollierungsansätzen), besteht darin, dass Sie die Protokollierung Ihrer Anwendung einem Komponententest unterziehen möchten. Aber ich vermute , die meisten Entwickler würden sagen , dass die Protokollierung nicht Teil einer Klasse - Funktionalität ist, und nicht etwas , dass Bedürfnisse getestet werden.

@Telastyn schreibt:

Und Sie erhalten dann das Argument, dass der Logger selbst die Änderung handhaben wird. "Oh, wenn sich das Ziel / Filtern / Formatieren / Strategie ändert, ändern wir einfach die Konfiguration!" Das ist Müll. Sie haben jetzt nicht nur ein God-Objekt, das all diese Dinge erledigt, sondern Sie schreiben Ihren Code auch in XML (oder ähnlichem), wobei Sie keine statische Analyse erhalten, keine Fehler bei der Kompilierung auftreten und keine t wirklich effektive Unit-Tests erhalten.

Ich fürchte, das ist ein sehr theoretischer Gegenposten. In der Praxis mögen die meisten Entwickler und Systemintegratoren die Tatsache, dass Sie die Protokollierung über eine Konfigurationsdatei konfigurieren können. Und sie MÖGEN die Tatsache, dass von ihnen nicht erwartet wird, dass sie die Protokollierung eines Moduls einem Komponententest unterziehen.

Sicher, wenn Sie die Protokollierungskonfigurationen vollstopfen, können Probleme auftreten, die sich jedoch als Fehler der Anwendung während des Startvorgangs oder als zu viel / zu wenig Protokollierung äußern. 1) Diese Probleme können leicht behoben werden, indem der Fehler in der Konfigurationsdatei behoben wird. 2) Die Alternative ist ein vollständiger Erstellungs- / Analyse- / Test- / Bereitstellungszyklus bei jeder Änderung der Protokollierungsstufen. Das ist nicht akzeptabel.

Stephen C
quelle
3

Ja und Nein !

Ja: Ich halte es für vernünftig, dass verschiedene Subsysteme (oder semantische Schichten oder Bibliotheken oder andere Begriffe der modularen Bündelung) während ihrer Initialisierung jeweils (denselben oder) potenziell unterschiedlichen Logger akzeptieren, anstatt alle Subsysteme, die denselben gemeinsamen gemeinsamen Singleton verwenden .

Jedoch,

Nein: Gleichzeitig ist es nicht zumutbar, die Protokollierung in jedem kleinen Objekt (nach Konstruktor oder Instanzmethode) zu parametrisieren. Um unnötiges und sinnloses Aufblähen zu vermeiden, sollten kleinere Entitäten den Singleton-Logger ihres umschließenden Kontexts verwenden.


Dies ist unter anderem ein Grund, über Modularität in Ebenen nachzudenken: Methoden werden in Klassen gebündelt, während Klassen in Subsysteme und / oder semantische Schichten gebündelt werden. Diese größeren Bündel sind wertvolle Werkzeuge der Abstraktion; Wir sollten innerhalb modularer Grenzen andere Überlegungen anstellen als beim Überschreiten.

Erik Eidt
quelle
3

Zunächst beginnt es mit einem starken Singleton-Cache. Das nächste, was Sie sehen, sind starke Singletons für die Datenbankebene, die den globalen Status, nicht beschreibende APIs von classes und nicht testbaren Code einführen.

Wenn Sie sich dafür entscheiden, kein Singleton für eine Datenbank zu haben, ist es wahrscheinlich keine gute Idee, ein Singleton für einen Cache zu haben, schließlich handelt es sich um ein sehr ähnliches Konzept, die Datenspeicherung, die nur unterschiedliche Mechanismen verwendet.

Durch die Verwendung eines Singletons in einer Klasse wird eine Klasse mit einer bestimmten Anzahl von Abhängigkeiten zu einer Klasse mit theoretisch einer unendlichen Anzahl von Abhängigkeiten , da Sie nie wissen, was sich wirklich hinter der statischen Methode verbirgt.

In den letzten zehn Jahren, in denen ich programmiert habe, gab es nur einen Fall, in dem ich Zeuge eines Versuchs wurde, die Protokollierungslogik zu ändern (die dann als Singleton geschrieben wurde). Also, obwohl ich Abhängigkeitsinjektionen liebe, ist die Protokollierung wirklich kein so großes Problem. Cache würde ich dagegen definitiv immer als Abhängigkeit machen.

Andy
quelle
Ja, die Protokollierung ändert sich selten und muss normalerweise kein austauschbares Modul sein. Ich habe jedoch einmal versucht, eine Protokollierungshilfeklasse, die eine statische Abhängigkeit vom Protokollierungssystem hatte, einem Komponententest zu unterziehen. Ich entschied, dass der einfachste Mechanismus, um es testbar zu machen, darin besteht, die zu testende Klasse in einem separaten Prozess auszuführen, ihren Logger so zu konfigurieren, dass er an STDOUT schreibt, und diese Ausgabe in meinem Testfall zu analysieren. würde natürlich nie etwas anderes wollen als die Echtzeit, oder? Außer beim Testen von Zeitzonen- / Sommerzeit-
Randfällen
@amon: Uhren sind wie das Einloggen, da es bereits einen anderen Mechanismus gibt, der dem gleichen Zweck dient wie DI, nämlich Joda-Time und seine vielen Ports. (Nicht, dass irgendetwas falsch daran ist, stattdessen DI zu verwenden; es ist jedoch einfacher, Joda-Time direkt zu verwenden, als einen benutzerdefinierten injizierbaren Adapter zu schreiben, und ich habe noch nie gesehen, dass es jemand bereut hat.)
ruakh
@amon "Ich habe ähnliche Erfahrungen mit Uhren gemacht, bei denen Sie offensichtlich nie etwas anderes als die Echtzeit wollen würden, oder? Außer natürlich beim Testen von Zeitzonen- / DST-Randfällen ..." - oder wenn Sie feststellen, dass ein Fehler vorliegt Ihre Datenbank beschädigt und die einzige Hoffnung, sie wiederzugewinnen, besteht darin, Ihr Ereignisprotokoll zu analysieren und es ab der letzten Sicherung erneut abzuspielen. Plötzlich muss der gesamte Code auf der Grundlage des Zeitstempels des aktuellen Protokolleintrags und nicht des aktuellen funktionieren Zeit.
Jules
3

Ja und Nein, aber meistens Nein

Ich gehe davon aus, dass der Großteil der Konversation auf statischen oder injizierten Instanzen basiert. Niemand schlägt vor, dass die Protokollierung die SRP unterbricht, die ich annehme? Wir sprechen hauptsächlich über das "Prinzip der Abhängigkeitsinversion". Tbh Ich stimme größtenteils mit Telastyns Antwort nicht überein.

Wann ist es in Ordnung, Statik zu verwenden? Denn klar ist es manchmal okay. Die Ja-Antworten der Vorteile der Abstraktion und die Nein-Antworten weisen darauf hin, dass Sie dafür bezahlen. Einer der Gründe, warum Ihr Job schwierig ist, ist, dass Sie keine Antwort aufschreiben und auf alle Situationen anwenden können.

Nehmen: Convert.ToInt32("1")

Ich bevorzuge dies:

private readonly IConverter _converter;

public MyClass(IConverter converter)
{
   Guard.NotNull(converter)
   _converter = conveter
}

.... 
var foo = _converter.ToInt32("1");

Warum? Ich bin damit einverstanden, dass ich den Code umgestalten muss, wenn ich die Flexibilität benötige, den Conversion-Code auszutauschen. Ich akzeptiere, dass ich das nicht verspotten kann. Ich glaube, dass die Einfachheit und Knappheit diesen Handel wert ist.

Mit Blick auf dem anderen Ende des Spektrums, wenn IConvertereine ist SqlConnection, würde ich ziemlich entsetzt werden , um zu sehen , dass als statischer Aufruf. Die Gründe dafür liegen auf der Hand. Ich würde darauf hinweisen, dass ein SQLConnectionin einer Anwendung ziemlich "übergreifend" sein kann, also hätte ich diese genauen Wörter nicht verwendet.

Ist die Protokollierung eher wie ein SQLConnectionoder Convert.ToInt32? Ich würde eher "SQLConnection" sagen.

Sie sollten sich über Logging lustig machen . Es spricht mit der Außenwelt. Wenn Convert.ToIn32ich eine Methode mit schreibe , verwende ich sie als Werkzeug, um eine andere, separat testbare Ausgabe der Klasse zu berechnen. Ich muss nicht überprüfen, Convertwurde korrekt aufgerufen, wenn überprüft wurde, dass "1" + "2" == "3". Protokollierung ist anders, es ist eine völlig unabhängige Ausgabe der Klasse. Ich gehe davon aus, dass es sich um eine Ausgabe handelt, die für Sie, das Support-Team und das Unternehmen von Wert ist. Ihre Klasse funktioniert nicht, wenn die Protokollierung nicht korrekt ist. Daher sollten die Komponententests nicht bestanden werden. Sie sollten testen, was Ihre Klasse protokolliert. Ich denke, das ist das Killerargument, ich könnte hier wirklich einfach aufhören.

Ich denke auch, dass sich dies wahrscheinlich ändern wird. Gute Protokollierung gibt nicht nur Zeichenfolgen aus, sondern zeigt auch, was Ihre Anwendung tut (ich bin ein großer Fan der ereignisbasierten Protokollierung). Ich habe gesehen, wie sich die grundlegende Protokollierung in ziemlich ausgefeilte Benutzeroberflächen für die Berichterstellung verwandelt hat. Es ist offensichtlich viel einfacher, in diese Richtung zu gehen, wenn Ihre Protokollierung ähnlich _logger.Log(new ApplicationStartingEvent())und weniger ähnlich aussieht Logger.Log("Application has started"). Man könnte argumentieren, dass dies Inventar für eine Zukunft schafft, die möglicherweise nie eintrifft. Dies ist ein Urteilsspruch, und ich denke, dass es sich lohnt.

Tatsächlich habe ich in einem meiner persönlichen Projekte eine nicht protokollierende Benutzeroberfläche erstellt, in der ich lediglich _loggerherausgefunden habe, was die Anwendung tat. Dies bedeutete, dass ich keinen Code schreiben musste, um herauszufinden, was die Anwendung tat, und ich landete in einer absolut soliden Protokollierung. Ich habe das Gefühl, dass mir diese Idee nicht in den Sinn gekommen wäre, wenn ich mich für das Protokollieren entschieden hätte, dass es einfach und unveränderlich ist.

Daher stimme ich Telastyn für den Fall der Protokollierung zu.

Nathan Cooper
quelle
So logge ich mich übrigens ein . Ich möchte einen Link zu einem Artikel oder etwas anderem erstellen, kann jedoch keinen finden. Wenn Sie sich in der .NET-Welt befinden und die Ereignisprotokollierung nachschlagen, werden Sie feststellen Semantic Logging Application Block. Verwenden Sie es nicht, wie den größten Teil des Codes, der vom MS-Team "patterns and pratice" erstellt wurde. Ironischerweise handelt es sich um ein Antimuster.
Nathan Cooper
3

Erste Querschnittsthemen sind keine wesentlichen Bausteine ​​und sollten nicht als Abhängigkeiten in einem System behandelt werden. Ein System sollte funktionieren, wenn zB der Logger nicht initialisiert ist oder der Cache nicht funktioniert. Wie werden Sie das System weniger gekoppelt und kohärent machen? Hier kommt SOLID im OO-Systemdesign ins Spiel.

Das Behalten eines Objekts als Singleton hat nichts mit SOLID zu tun. Dies ist Ihr Objektlebenszyklus, wie lange das Objekt im Speicher verbleiben soll.

Eine Klasse, die zum Initialisieren eine Abhängigkeit benötigt, sollte nicht wissen, ob die angegebene Klasseninstanz singleton oder transient ist. Aber tldr; Wenn Sie in jeder Methode oder Klasse Logger.Instance.Log () schreiben, dann ist es ein problematischer Code (Codegeruch / harte Kopplung), der wirklich chaotisch ist. Dies ist der Moment, in dem SOLID missbraucht wird. Und Mitentwickler wie OP beginnen, echte Fragen wie diese zu stellen.

vendettamit
quelle
2

Ich habe dieses Problem mithilfe einer Kombination aus Vererbung und Merkmalen (in einigen Sprachen auch Mixins genannt) gelöst. Traits sind sehr praktisch, um diese Art von Querschnittsthemen zu lösen. In der Regel handelt es sich jedoch um ein Sprachmerkmal. Ich denke, die eigentliche Antwort ist, dass es von den Sprachmerkmalen abhängt.

RibaldEddie
quelle
Interessant. Ich habe noch nie wesentliche Arbeit mit einer Sprache geleistet, die Merkmale unterstützt, aber ich habe überlegt, solche Sprachen für einige zukünftige Projekte zu verwenden. Daher wäre es für mich hilfreich, wenn Sie dies erweitern und zeigen könnten, wie Merkmale das Problem lösen.
Jules