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 ?
Antworten:
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.
quelle
String
oderInt32
oder sogarList
aus 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 .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.
quelle
HttpContextBase
(was genau aus diesem Grund eingeführt wurde). Ich weiß mit Sicherheit, dass meine Realität ohne diese Klasse wirklich sauer wäre.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.
quelle
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 dieLogger
Benutzeroberfläche schreiben . Sie konnten kein neuesLogger
Produkt 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,
Logger
ist 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
Adapter
fü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 dasAdapter
. Und sicher, injizieren Sie den echten Logger in dieAdapter
- aber nicht in jede Kleinigkeit, die es brauchen könnte.quelle
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.
quelle
Echtes Protokollieren ist ein Sonderfall.
@Telastyn schreibt:
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:
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.
quelle
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.
quelle
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
class
es 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.
quelle
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:
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
IConverter
eine istSqlConnection
, 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 einSQLConnection
in einer Anwendung ziemlich "übergreifend" sein kann, also hätte ich diese genauen Wörter nicht verwendet.Ist die Protokollierung eher wie ein
SQLConnection
oderConvert.ToInt32
? Ich würde eher "SQLConnection" sagen.Sie sollten sich über Logging lustig machen . Es spricht mit der Außenwelt. Wenn
Convert.ToIn32
ich eine Methode mit schreibe , verwende ich sie als Werkzeug, um eine andere, separat testbare Ausgabe der Klasse zu berechnen. Ich muss nicht überprüfen,Convert
wurde 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 aussiehtLogger.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
_logger
herausgefunden 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.
quelle
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.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.
quelle
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.
quelle