Ist die Protokollierung neben einer Implementierung eine SRP-Verletzung?

19

Wenn ich an agile Softwareentwicklung und alle Prinzipien (SRP, OCP, ...) denke, frage ich mich, wie ich mit Protokollierung umgehen soll.

Ist die Protokollierung neben einer Implementierung eine SRP-Verletzung?

Ich würde sagen, yesweil die Implementierung auch ohne Protokollierung laufen soll. Wie kann ich die Protokollierung besser implementieren? Ich habe einige Muster überprüft und bin zu dem Schluss gekommen, dass der beste Weg, die Prinzipien nicht auf benutzerdefinierte Weise zu verletzen, sondern jedes Muster zu verwenden, das bekanntermaßen gegen ein Prinzip verstößt, die Verwendung eines Dekorationsmusters ist.

Nehmen wir an, wir haben eine Reihe von Komponenten, die keine SRP-Verletzung aufweisen, und möchten dann die Protokollierung hinzufügen.

  • Komponente A
  • Komponente B verwendet A

Wir möchten die Protokollierung für A durchführen, also erstellen wir eine weitere Komponente D, die mit A dekoriert ist und eine Schnittstelle I implementiert.

  • Schnittstelle I
  • Komponente L (Protokollierungskomponente des Systems)
  • Komponente A implementiert I
  • Komponente D implementiert I, dekoriert / verwendet A, verwendet L für die Protokollierung
  • Komponente B verwendet ein I

Vorteile: - Ich kann A ohne Protokollierung verwenden - Testen A bedeutet, dass ich keine Protokollierungsversuche benötige - Tests sind einfacher

Nachteil: - mehr Komponenten und mehr Tests

Ich weiß, dass dies eine weitere offene Diskussionsfrage zu sein scheint, aber ich möchte wissen, ob jemand bessere Protokollierungsstrategien als ein Dekorateur oder eine SRP-Verletzung verwendet. Was ist mit statischen Singleton-Loggern, die standardmäßig NullLogger sind, und wenn Syslog-Logging gewünscht wird, ändert man das Implementierungsobjekt zur Laufzeit?

Aitch
quelle
Ich habe es bereits gelesen und die Antwort ist nicht zufriedenstellend, sorry.
Aitch
@MarkRogers danke, dass Sie diesen interessanten Artikel geteilt haben. Onkel Bob sagt in 'Clean Code', dass eine nette SRP-Komponente mit anderen Komponenten auf derselben Abstraktionsebene zu tun hat. Für mich ist diese Erklärung leichter zu verstehen, da der Kontext auch zu groß sein kann. Aber ich kann die Frage nicht beantworten, denn was ist der Kontext oder die Abstraktionsstufe eines Loggers?
Aitch
3
"ist keine Antwort auf mich" oder "die Antwort ist nicht zufriedenstellend" ist ein bisschen abweisend. Sie könnten darüber nachdenken, was spezifisch unbefriedigend ist (welche Anforderung wurde von dieser Antwort nicht erfüllt? Was spezifisch ist an Ihrer Frage einzigartig?) Und dann Ihre Frage bearbeiten, um sicherzustellen, dass diese Anforderung / dieser einzigartige Aspekt klar erläutert wird. Der Zweck ist es, Sie zu veranlassen, Ihre Frage zu bearbeiten, um sie zu verbessern, um sie klarer und gezielter zu gestalten, und nicht um eine Aussage zu bitten, dass Ihre Frage anders ist / sollte nicht ohne Begründung geschlossen werden, warum. (Sie können auch die andere Antwort kommentieren.)
DW

Antworten:

-1

Ja, es ist ein Verstoß gegen SRP, da die Protokollierung ein Querschnittsthema ist.

Die korrekte Methode besteht darin, die Protokollierung an eine Protokollierungsklasse (Interception) zu delegieren, deren einziger Zweck darin besteht, die Protokollierung gemäß der SRP durchzuführen.

Unter diesem Link finden Sie ein gutes Beispiel: https://msdn.microsoft.com/en-us/library/dn178467%28v=pandp.30%29.aspx

Hier ist ein kurzes Beispiel :

public interface ITenantStore
{
    Tenant GetTenant(string tenant);
    void SaveTenant(Tenant tenant);
}

public class TenantStore : ITenantStore
{
    public Tenant GetTenant(string tenant)
    {....}

    public void SaveTenant(Tenant tenant)
    {....}
} 

public class TenantStoreLogger : ITenantStore
{
    private readonly ILogger _logger; //dep inj
    private readonly ITenantStore _tenantStore;

    public TenantStoreLogger(ITenantStore tenantStore)
    {
        _tenantStore = tenantStore;
    }

    public Tenant GetTenant(string tenant)
    {
        _logger.Log("reading tenant " + tenant.id);
        return _tenantStore.GetTenant(tenant);
    }

    public void SaveTenant(Tenant tenant)
    {
        _tenantStore.SaveTenant(tenant);
        _logger.Log("saving tenant " + tenant.id);
    }
}

Vorteile sind

  • Sie können dies ohne Protokollierung testen - echte Einheitentests
  • Sie können die Protokollierung problemlos ein- und ausschalten - auch zur Laufzeit
  • Sie können die Protokollierung durch andere Protokollierungsformen ersetzen, ohne jemals die TenantStore-Datei ändern zu müssen.
z0mbi3
quelle
Danke für den netten Link. Abbildung 1 auf dieser Seite ist eigentlich das, was ich meine Lieblingslösung nennen würde. Die Liste der Querschnittsthemen (Protokollierung, Zwischenspeicherung usw.) und ein Dekorationsmuster ist die allgemeinste Lösung, und ich bin froh, dass ich mich nicht völlig geirrt habe, obwohl die größere Community diese Abstraktion und Inline-Protokollierung gerne fallen lassen würde .
Aitch
2
Ich sehe nicht, dass Sie die Variable _logger irgendwo zuweisen. Wollten Sie Konstruktor-Injection verwenden und haben es einfach vergessen? In diesem Fall erhalten Sie wahrscheinlich eine Compiler-Warnung.
user2023861
27
Anstatt TenantStore mit einem Allzweck-Logger abzurufen, der N + 1 Klassen erfordert (wenn Sie einen LandlordStore, einen FooStore, einen BarStore usw. hinzufügen), wird TenantStoreLogger mit TenantStore und FooStoreLogger mit FooStore DIP-fähig , etc ... erfordern 2N Klassen. Soweit ich das beurteilen kann, zum Nullnutzen. Wenn Sie Unit-Tests ohne Protokollierung durchführen möchten, müssen Sie N Klassen neu auslösen, anstatt nur einen NullLogger zu konfigurieren. IMO, das ist ein sehr schlechter Ansatz.
user949300
6
Wenn Sie dies für jede einzelne Klasse tun, die eine Protokollierung benötigt, wird die Komplexität Ihrer Codebasis drastisch erhöht (es sei denn, nur so wenige Klassen verfügen über eine Protokollierung, dass Sie sie nicht einmal mehr als Querschnittsaufgabe bezeichnen würden). Dies führt letztendlich dazu, dass der Code nur aufgrund der großen Anzahl zu wartender Schnittstellen weniger wartbar ist, was gegen alles verstößt, für das das Prinzip der Einzelverantwortung erstellt wurde.
jpmc26
9
Abgestimmt. Sie haben das Problem mit der Protokollierung aus der Tenant-Klasse entfernt, aber jetzt TenantStoreLoggerwird es bei jeder TenantStoreÄnderung geändert. Sie trennen Bedenken nicht mehr als in der ursprünglichen Lösung.
Laurent LA RIZZA
61

Ich würde sagen, dass Sie SRP viel zu ernst nehmen. Wenn Ihr Code ordentlich genug ist, dass die Protokollierung die einzige "Verletzung" von SRP darstellt, sind Sie besser als 99% aller anderen Programmierer, und Sie sollten sich auf die Schulter klopfen.

Der Sinn von SRP ist es, schrecklichen Spaghetti-Code zu vermeiden, bei dem Code, der verschiedene Dinge tut, alle miteinander verwechselt werden. Das Mischen von Protokollierung mit Funktionscode läutet für mich keine Alarmglocken.

grahamparks
quelle
19
@Aitch: Sie können die Protokollierung fest in Ihre Klasse einbinden, ein Handle an einen Protokollierer übergeben oder gar nichts protokollieren. Wenn Sie die SRP auf Kosten von allem anderen streng nehmen wollen, würde ich empfehlen, niemals etwas zu protokollieren. Was immer Sie wissen müssen, was Ihre Software tut, können Sie mit einem Debugger herausfinden. Das P in SRP steht für "Prinzip", nicht für "physikalisches Naturgesetz, das niemals gebrochen werden darf".
Blrfl
3
@Aitch: Sie sollten in der Lage sein, die Protokollierung in Ihrer Klasse auf eine bestimmte Anforderung zurückzuführen, da Sie sonst gegen YAGNI verstoßen. Wenn sich die Protokollierung in der Tabelle befindet, stellen Sie sie in einem gültigen Protokollierungs-Handle bereit, genau wie Sie es für alles andere tun würden, das die Klasse benötigt, vorzugsweise für eine Klasse, die bereits den Test bestanden hat. Ob es sich dabei um eine Instanz handelt, die tatsächliche Protokolleinträge erstellt oder diese in den Bit-Bucket abspeichert, ist das Problem, das die Instanz Ihrer Klasse instanziiert hat. Die Klasse selbst sollte sich nicht darum kümmern.
Blrfl
3
@Aitch Um Ihre Frage zu Unit-Tests zu beantworten: Do you mock the logger?Das ist genau das, was Sie tun. Sie sollten eine ILoggerSchnittstelle haben, die definiert, WAS der Logger macht. Dem zu testenden Code wird ein von ILoggerIhnen angegebener Code hinzugefügt. Zum Testen haben Sie class TestLogger : ILogger. Das Tolle daran ist TestLogger, dass man Dinge wie den letzten String oder den protokollierten Fehler anzeigen kann. Die Tests können überprüfen, ob der zu testende Code korrekt protokolliert wird. Zum Beispiel könnte ein Test sein UserSignInTimeGetsLogged(), bei dem der Test TestLoggernach dem Protokoll sucht.
CurtisHx
5
99% scheint ein bisschen niedrig. Sie sind wahrscheinlich besser als 100% aller Programmierer.
Paul Draper
2
+1 für geistige Gesundheit. Wir brauchen mehr von dieser Art des Denkens: weniger Konzentration auf Wörter und abstrakte Prinzipien und mehr Konzentration auf eine wartbare Codebasis .
jpmc26
15

Nein, es ist keine Verletzung von SRP.

Die Nachrichten, die Sie an das Protokoll senden, sollten sich aus den gleichen Gründen ändern wie der umgebende Code.

Was eine Verletzung von SRP ist, ist die Verwendung einer bestimmten Bibliothek für die Protokollierung direkt im Code. Wenn Sie die Art der Protokollierung ändern möchten, gibt SRP an, dass dies keinen Einfluss auf Ihren Geschäftscode haben soll.

Eine Art von Zusammenfassung Loggersollte für Ihren Implementierungscode zugänglich sein, und das einzige, was Ihre Implementierung sagen sollte, ist "Diese Nachricht an das Protokoll senden", ohne Bedenken, wie es gemacht wird. Die Entscheidung über die genaue Art der Protokollierung (auch Zeitstempel) liegt nicht in der Verantwortung Ihrer Implementierung.

Ihre Implementierung sollte dann auch nicht wissen, ob es sich bei dem Logger, an den sie Nachrichten sendet, um einen handelt NullLogger.

Das gesagt.

Ich würde das Abmelden als Querschnittsthema nicht zu schnell streichen . Das Senden von Protokollen zur Verfolgung bestimmter Ereignisse in Ihrem Implementierungscode gehört zum Implementierungscode.

Ein Querschnittsthema, OTOH, ist die Ausführungsverfolgung : Die Protokollierung wird bei jeder Methode ein- und ausgeschaltet. AOP ist dafür am besten geeignet.

Laurent LA RIZZA
quelle
Angenommen, die Loggernachricht lautet "Login-Benutzer xyz" und wird an einen Logger gesendet, der einen Zeitstempel usw. voranstellt. Wissen Sie, was "Login" für die Implementierung bedeutet? Beginnt eine Sitzung mit einem Cookie oder einem anderen Mechanismus? Ich denke, es gibt viele verschiedene Möglichkeiten, eine Anmeldung zu implementieren. Eine Änderung der Implementierung hat logischerweise nichts mit der Tatsache zu tun, dass sich ein Benutzer anmeldet. Dies ist ein weiteres großartiges Beispiel für das Dekorieren verschiedener Komponenten (z. B. OAuthLogin, SessionLogin, BasicAuthorizationLogin), die dasselbe tun als Login-schnittstelle mit dem gleichen logger verziert.
Aitch
Es kommt darauf an, was die Meldung "login user xyz" bedeutet. Wenn es die Tatsache anzeigt, dass eine Anmeldung erfolgreich ist, gehört das Senden der Nachricht an das Protokoll zum Anwendungsfall der Anmeldung. Die spezifische Art der Darstellung der Anmeldeinformationen als Zeichenfolge (OAuth, Sitzung, LDAP, NTLM, Fingerabdruck, Hamsterrad) gehört zu der spezifischen Klasse, die die Anmeldeinformationen oder die Anmeldestrategie darstellt. Es besteht keine zwingende Notwendigkeit, es zu entfernen. Dieser Einzelfall ist kein Querschnittsthema. Dies ist spezifisch für den Anwendungsfall der Anmeldung.
Laurent LA RIZZA
7

Da die Protokollierung häufig als Querschnittsthema betrachtet wird, würde ich vorschlagen, AOP zu verwenden, um die Protokollierung von der Implementierung zu trennen.

Abhängig von der Sprache würden Sie einen Interceptor oder ein AOP-Framework (z. B. AspectJ in Java) verwenden, um dies durchzuführen.

Die Frage ist, ob sich das wirklich lohnt. Beachten Sie, dass diese Trennung die Komplexität Ihres Projekts erhöht und nur einen geringen Nutzen bringt.

Oliver Weiler
quelle
2
Der größte Teil des AOP-Codes, den ich sah, handelte von der Protokollierung jedes Eingangs- und Ausgangsschritts jeder Methode. Ich möchte nur einige Teile der Geschäftslogik protokollieren. Vielleicht ist es also möglich, nur mit Anmerkungen versehene Methoden zu protokollieren, aber AOP kann überhaupt nur in Skriptsprachen und Umgebungen mit virtuellen Maschinen vorhanden sein, oder? In zB C ++ ist das unmöglich. Ich gebe zu, dass ich mit AOP-Ansätzen nicht sehr zufrieden bin, aber vielleicht gibt es keine sauberere Lösung.
Aitch
1
@Aitch. "C ++ ist es unmöglich." : Wenn Sie nach "aop c ++" googeln, finden Sie Artikel darüber. "... der AOP-Code, den ich sah, handelte von der Protokollierung jedes Eingangs- und Ausgangsschritts jeder Methode. Ich möchte nur einige Geschäftslogikteile protokollieren." Mit Aop können Sie Muster definieren, um die zu ändernden Methoden zu finden. dh alle Methoden aus dem Namespace "my.busininess. *"
k3b
1
Die Protokollierung ist häufig KEIN Querschnittsthema, insbesondere wenn Ihr Protokoll interessante Informationen enthalten soll, dh mehr Informationen als in einem Ausnahmestapel-Trace enthalten sind.
Laurent LA RIZZA
5

Das hört sich gut an. Sie beschreiben einen ziemlich Standard-Protokollierungsdekorateur. Du hast:

Komponente L (Protokollierungskomponente des Systems)

Dies hat eine Verantwortung: Protokollieren von Informationen, die an sie übergeben werden.

Komponente A implementiert I

Dies hat eine Verantwortung: Bereitstellung einer Implementierung von Schnittstelle I (vorausgesetzt, ich bin ordnungsgemäß SRP-konform, das heißt).

Dies ist der entscheidende Teil:

Komponente D implementiert I, dekoriert / verwendet A, verwendet L für die Protokollierung

Wenn es so ausgedrückt wird, klingt es komplex, aber sehen Sie es so: Komponente D macht eine Sache: A und L zusammenbringen.

  • Komponente D wird nicht protokolliert. es delegiert das an L
  • Komponente D implementiert I selbst nicht; es delegiert das an A

Die einzige Verantwortung der Komponente D besteht darin, sicherzustellen, dass L benachrichtigt wird, wenn A verwendet wird. Die Implementierungen von A und L sind beide an anderer Stelle. Dies ist vollständig SRP-konform und gleichzeitig ein gutes Beispiel für OCP und eine weit verbreitete Verwendung von Dekorateuren.

Eine wichtige Einschränkung: Wenn D Ihre Protokollierungskomponente L verwendet, sollte dies so erfolgen, dass Sie die Art und Weise ändern können, in der Sie protokollieren. Der einfachste Weg, dies zu tun, besteht darin, eine Schnittstelle IL zu haben, die von L implementiert wird. Dann:

  • Komponente D verwendet eine AWL zum Protokollieren. Es wird eine Instanz von L bereitgestellt
  • Komponente D verwendet ein I, um Funktionalität bereitzustellen; Eine Instanz von A wird bereitgestellt
  • Komponente B verwendet ein I; eine Instanz von D wird bereitgestellt

Auf diese Weise hängt nichts direkt von irgendetwas anderem ab, was es einfach macht, sie auszutauschen. Dies erleichtert die Anpassung an Änderungen und das Verspotten von Teilen des Systems, sodass Sie einen Komponententest durchführen können.

Anaximander
quelle
Ich kenne eigentlich nur C #, das native Delegationsunterstützung hat. Deshalb habe ich geschrieben D implements I. Vielen Dank für Ihre Antwort.
Aitch
1

Natürlich ist es eine Verletzung von SRP, da Sie ein Querschnittsthema haben. Sie können jedoch eine Klasse erstellen, die für die Erstellung der Protokollierung bei Ausführung einer Aktion verantwortlich ist.

Beispiel:

class Logger {
   ActuallLogger logger;
   public Action ComposeLog(string msg, Action action) {
      return () => {
          logger.debug(msg);
          action();
      };
   }
}
Paul Nikonowicz
quelle
2
Abgestimmt. Protokollierung ist in der Tat ein Querschnittsthema. So ist die Sequenzierung von Methodenaufrufen in Ihrem Code. Das ist kein Grund genug, einen Verstoß gegen die SRP geltend zu machen. Das Protokollieren des Auftretens eines bestimmten Ereignisses in Ihrer Anwendung ist KEIN Querschnittsthema. Der WEG, auf dem diese Nachrichten an interessierte Benutzer weitergeleitet werden, ist in der Tat ein besonderes Problem, und die Beschreibung im Implementierungscode ist ein Verstoß gegen SRP.
Laurent LA RIZZA
"Sequenzierungsmethodenaufrufe" oder funktionale Zusammensetzung sind kein Querschnittsthema, sondern ein Implementierungsdetail. Die Aufgabe der von mir erstellten Funktion besteht darin, eine Protokollanweisung mit einer Aktion zu erstellen. Ich muss nicht das Wort "und" verwenden, um zu beschreiben, was diese Funktion tut.
Paul Nikonowicz
Es ist kein Implementierungsdetail. Dies hat tiefgreifende Auswirkungen auf die Form Ihres Codes.
Laurent LA RIZZA
Ich denke, dass ich SRP aus der Perspektive "WAS macht diese Funktion?" Betrachte, während Sie SRP aus der Perspektive "WIE macht diese Funktion?" Betrachte.
Paul Nikonowicz