Was soll die Position des Loggers in der Parameterliste sein [geschlossen]

12

In meinem Code füge ich einen Logger über die Parameterliste ihres Konstruktors in viele meiner Klassen ein

Mir ist aufgefallen, dass ich es zufällig geschrieben habe: Manchmal ist es das erste auf der Liste, manchmal das letzte und manchmal das dazwischen

Hast du eine Vorliebe? Meiner Intuition nach ist Konsistenz in diesem Fall hilfreich, und ich persönlich bevorzuge es, sie an die erste Stelle zu setzen, damit es leichter ist, bemerkt zu werden, wenn sie fehlt, und leichter zu überspringen, wenn sie vorhanden ist.

Esra
quelle

Antworten:

31

Logger sind das, was wir als "Querschnittsthema" bezeichnen. Sie geben Techniken wie aspektorientiertes Programmieren nach; Wenn Sie eine Möglichkeit haben, Ihre Klassen mit einem Attribut zu dekorieren oder Code zu weben, ist dies eine gute Möglichkeit, Protokollierungsfunktionen zu erhalten und gleichzeitig Ihre Objekte und Parameterlisten "rein" zu halten.

Der einzige Grund, warum Sie einen Logger möglicherweise übergeben möchten, ist, wenn Sie verschiedene Logging-Implementierungen angeben möchten, die meisten Logging-Frameworks jedoch so flexibel sind, dass Sie sie beispielsweise für verschiedene Logging-Ziele konfigurieren können. (Protokolldatei, Windows Event Manager usw.)

Aus diesen Gründen ziehe ich es vor, die Protokollierung zu einem natürlichen Teil des Systems zu machen, anstatt einen Protokollierer zu Protokollierungszwecken an jede Klasse zu übergeben. Im Allgemeinen verweise ich also auf den entsprechenden Protokollierungsnamespace und verwende einfach den Logger in meinen Klassen.

Wenn Sie dennoch einen Logger übergeben möchten, ist es meine Präferenz, dass Sie ihn zum letzten Parameter in der Parameterliste machen (machen Sie ihn, wenn möglich, zu einem optionalen Parameter). Es macht wenig Sinn, wenn es der erste Parameter ist. Der erste Parameter sollte der wichtigste sein, der für die Operation der Klasse am relevantesten ist.

Robert Harvey
quelle
11
+! Halten Sie den Logger von Konstruktoren fern. Ich bevorzuge einen statischen Logger, damit ich weiß, dass jeder das gleiche verwendet
Steven A. Lowe
1
@ StevenA.Lowe Beachten Sie jedoch, dass die Verwendung von statischen Loggern später zu anderen Problemen führen kann, wenn Sie nicht vorsichtig sind (z. B. Fiasko der Reihenfolge der statischen Initialisierung in C ++). Ich bin damit einverstanden, dass der Logger als global zugängliche Einheit seinen Reiz hat, aber es sollte sorgfältig geprüft werden, ob und wie ein solches Design in die Gesamtarchitektur passt.
ComicSansMS
@ComicSansMS: natürlich plus Threading-Probleme usw. Nur eine persönliche Vorliebe - "so einfach wie möglich, aber nicht einfacher";)
Steven A. Lowe
Ein statischer Logger kann ein Problem sein. Dies erschwert die Abhängigkeitsinjektion (es sei denn, Sie meinen einen von Ihrem DI-Container instanziierten Singleton-Logger), und wenn Sie jemals Ihre Architektur ändern möchten, kann dies schmerzhaft sein. Im Moment verwende ich zum Beispiel Azure-Funktionen, die als Parameter für jede Funktionsausführung einen Logger übergeben. Daher bereue ich, dass ich meinen Logger durch den Konstruktor geleitet habe.
Slothario
@ Slothario: Das ist das Schöne an einem statischen Logger. Eine Injektion jeglicher Art ist nicht erforderlich.
Robert Harvey
7

In Sprachen mit Funktionsüberladung würde ich argumentieren, dass je wahrscheinlicher ein Argument optional ist, desto genauer sollte es sein. Dies schafft Konsistenz, wenn Sie Überladungen dort erstellen, wo sie fehlen:

foo(mandatory);
foo(mandatory, optional);
foo(mandatory, optional, evenMoreOptional);

In funktionalen Sprachen ist das Umgekehrte sinnvoller - je wahrscheinlicher Sie eine Standardeinstellung wählen, desto weiter links sollte sie sein. Dies erleichtert die Spezialisierung der Funktion durch einfaches Anwenden von Argumenten:

addThreeToList = map (+3)

Wie in den anderen Antworten erwähnt, möchten Sie den Logger jedoch wahrscheinlich nicht explizit in der Argumentliste jeder Klasse im System übergeben.

Doval
quelle
3

Sie können definitiv viel Zeit damit verbringen, dieses Problem zu überarbeiten.

Für Sprachen mit kanonischen Protokollierungsimplementierungen müssen Sie den kanonischen Protokollierer nur direkt in jeder Klasse instanziieren.

Versuchen Sie bei Sprachen ohne kanonische Implementierung, ein Protokollierungs-Fassaden-Framework zu finden und sich daran zu halten. slf4j ist eine gute Wahl in Java.

Persönlich würde ich mich lieber an eine einzelne konkrete Protokollierungsimplementierung halten und alles an syslog senden. Alle guten Tools zur Protokollanalyse können Sysout-Protokolle von mehreren App-Servern zu einem umfassenden Bericht zusammenfassen.

Wenn eine Funktionssignatur einen oder zwei Abhängigkeitsdienste sowie einige "echte" Argumente enthält, platziere ich die Abhängigkeiten zuletzt:

int calculateFooBarSum(int foo, int bar, IntegerSummationService svc)

Da meine Systeme in der Regel nur fünf oder weniger solcher Dienste enthalten, stelle ich immer sicher, dass die Dienste über alle Funktionssignaturen hinweg in derselben Reihenfolge enthalten sind. Die alphabetische Reihenfolge ist so gut wie jede andere. (Abgesehen davon: Wenn Sie diesen methodischen Ansatz für den Umgang mit Mutex beibehalten, verringern Sie auch die Wahrscheinlichkeit, dass Deadlocks auftreten.)

Wenn Sie feststellen, dass Sie mehr als ein Dutzend Abhängigkeiten in Ihre App einschleusen, muss das System wahrscheinlich in separate Subsysteme aufgeteilt werden (kann ich sagen, Microservices?).

Christian Willman
quelle
Es erscheint mir unangenehm, die Eigenschaftsinjektion zum Aufrufen von berechneFooBarSumme zu verwenden.
ein phu
2

Logger sind etwas Besonderes, weil sie buchstäblich überall verfügbar sein müssen.

Wenn Sie entschieden haben, dass Sie einen Logger an den Konstruktor jeder Klasse übergeben möchten, sollten Sie auf jeden Fall eine einheitliche Konvention festlegen (z. B. immer der erste Parameter, der immer als Referenz übergeben wird, die Konstruktorinitialisierungsliste beginnt immer mit m_logger (theLogger), etc). Alles, was in Ihrer gesamten Codebasis verwendet wird, wird eines Tages von Konsistenz profitieren.

Alternativ könnte jede Klasse ihr eigenes Logger-Objekt instanziieren, ohne dass etwas übergeben werden muss. Der Logger muss möglicherweise ein paar Dinge "magisch" wissen, damit dies funktioniert, aber das Hardcodieren eines Dateipfads in der Klassendefinition ist möglicherweise eine viel wartbarer und weniger mühsam, als es richtig an Hunderte verschiedener Klassen weiterzugeben, und viel weniger schlimm, als eine globale Variable zu verwenden, um diese Langeweile zu umgehen. (Zugegeben, Logger gehören zu den wenigen legitimen Anwendungsfällen für globale Variablen.)

Ixrec
quelle
1

Ich stimme denen zu, die vorschlagen, dass auf den Logger statisch zugegriffen und nicht in Klassen weitergegeben werden soll. Allerdings , wenn es ein starker Grund dafür ist , wollen Sie sie passieren in (vielleicht verschiedene Instanzen zu verschiedenen Orten oder etwas protokollieren wollen) , dann würde ich vorschlagen , Sie es nicht passieren den Konstruktor , sondern über einen separaten Anruf , dies zu tun machen, zum Beispiel Class* C = new C(); C->SetLogger(logger);eher alsClass* C = new C(logger);

Der Grund für den Vorzug dieser Methode liegt darin, dass der Logger kein wesentlicher Bestandteil der Klasse ist, sondern vielmehr ein injiziertes Feature, das für einen anderen Zweck verwendet wird. Wenn Sie es in die Konstruktorliste aufnehmen, wird es zu einer Anforderung der Klasse und impliziert, dass es Teil des tatsächlichen logischen Status der Klasse ist. Es ist vernünftig zu erwarten, zum Beispiel (mit dem meisten Klassen , obwohl nicht alle), dass , wenn X != Ydann C(X) != C(Y)aber es ist unwahrscheinlich , dass Sie Logger Ungleichheit testen würden , wenn Sie sind zu Instanzen derselben Klasse zu vergleichen.

Jack Aidley
quelle
1
Dies hat natürlich den Nachteil, dass der Logger für den Konstruktor nicht verfügbar ist.
Ben Voigt
1
Ich mag diese Antwort wirklich. Dadurch wird die Protokollierung zu einem Nebenproblem der Klasse, das Sie separat von der Verwendung beachten müssen. Wenn Sie den Logger zum Konstruktor einer großen Anzahl von Klassen hinzufügen, verwenden Sie wahrscheinlich die Abhängigkeitsinjektion. Ich kann nicht für alle Sprachen sprechen, aber ich weiß, dass einige DI-Implementierungen in C # auch direkt in Eigenschaften (Getter / Setter) eingefügt werden.
jpmc26
@BenVoigt: Das ist wahr, und es mag ein vernichtender Grund sein, es nicht auf diese Weise zu tun, aber normalerweise können Sie die Protokollierung durchführen, die Sie sonst im Konstruktor als Antwort auf das Setzen eines Protokollierers durchführen würden.
Jack Aidley
0

Erwähnenswert ist, dass ich bisher noch nicht gesehen habe, wie die anderen Antworten hier angesprochen wurden: Indem der Logger über eine Eigenschaft oder statisch injiziert wird, wird es schwierig, die Klasse einem Komponententest zu unterziehen. Wenn Sie Ihren Logger beispielsweise über die Eigenschaft injizieren, müssen Sie diesen Logger jetzt jedes Mal injizieren, wenn Sie eine Methode testen, die den Logger verwendet. Dies bedeutet, dass Sie es genauso gut als Konstruktorabhängigkeit festlegen können, da die Klasse es erfordert.

Static bietet sich für das gleiche Problem an; Wenn der Logger nicht funktioniert, schlägt Ihre gesamte Klasse fehl (wenn Ihre Klasse den Logger verwendet) - obwohl der Logger nicht unbedingt Teil der Verantwortung der Klasse ist - obwohl dies nicht annähernd so schlimm ist wie die Eigenschaftsinjektion, weil Sie Zumindest wissen, dass der Logger in gewissem Sinne immer "da" ist.

Nur ein paar Denkanstöße, besonders wenn Sie TDD einsetzen. Meiner Meinung nach sollte ein Logger wirklich nicht Teil eines prüfbaren Teils einer Klasse sein (wenn Sie die Klasse testen, sollten Sie auch nicht Ihre Protokollierung testen).

Dan Pantry
quelle
1
hmmm ... Sie möchten also, dass Ihre Klasse die Protokollierung durchführt (die Protokollierung sollte in der Spezifikation enthalten sein), aber Sie möchten nicht mit einem Protokollierer testen. Ist das möglich? Ich denke, Ihr Punkt ist ein Non-Go. Wenn Ihre Testwerkzeuge kaputt sind, können Sie sie nicht testen. Es ist ein wenig übertriebenes Engineering, IMHO
Hogan
1
Mein Punkt ist, dass, wenn ich einen Konstruktor verwende und eine Methode für eine Klasse aufrufe und sie immer noch fehlschlägt, weil ich keine Eigenschaft festgelegt habe, der Konstrukteur der Klasse das Konzept hinter einem Konstruktor falsch verstanden hat. Wenn die Klasse einen Logger benötigt, sollte dieser in den Konstruktor eingefügt werden - dafür ist der Konstruktor da.
Dan Pantry
ähm .. nein nicht wirklich. Wenn Sie das Protokollierungssystem als Teil des "Frameworks" betrachten, ist es als Teil des Konstruktors nicht sinnvoll. Dies wurde jedoch in anderen Antworten klar angegeben.
Hogan
1
Ich argumentiere gegen die Immobilieninjektion. Ich befürworte nicht unbedingt die Verwendung des Injizierens im Konstruktor. Ich sage nur, dass das meiner Meinung nach der Immobilieninjektion vorzuziehen ist.
Dan Pantry
"Ist das möglich?" Ja, auch, IL-Weben ist eine Sache, die existiert und in der Top-Antwort erwähnt wurde ... mono-project.com/docs/tools+libraries/libraries/Mono.Cecil
Dan Pantry
0

Ich bin zu faul, um ein Logger-Objekt an jede Klasseninstanz weiterzugeben. In meinem Code befinden sich diese Dinge entweder in einem statischen Feld oder in einer threadlokalen Variablen in einem statischen Feld. Letzteres ist ziemlich cool und ermöglicht die Verwendung eines anderen Protokollierers für jeden Thread sowie das Hinzufügen von Methoden zum Ein- und Ausschalten der Protokollierung, die in einer Multithread-Anwendung eine sinnvolle und erwartete Funktion haben.

Atsby
quelle