Gedanken und Best Practices zu statischen Klassen und Mitgliedern [geschlossen]

11

Ich bin sehr neugierig auf Gedanken und Best Practices der Branche in Bezug auf statische Mitglieder oder ganze statische Klassen. Gibt es irgendwelche Nachteile oder nimmt es an irgendwelchen Anti-Mustern teil?

Ich sehe diese Entitäten als "Dienstprogrammklassen / Mitglieder" in dem Sinne, dass sie keine Instanziierung der Klasse erfordern oder verlangen, um die Funktionalität bereitzustellen.

Was sind die allgemeinen Gedanken und Best Practices der Branche dazu?

In den folgenden Beispielklassen und Mitgliedern wird veranschaulicht, worauf ich mich beziehe.

// non-static class with static members
//
public class Class1
{
    // ... other non-static members ...

    public static string GetSomeString()
    {
        // do something
    }
}

// static class
//
public static class Class2
{
    // ... other static members ...

    public static string GetSomeString()
    {
        // do something
    }
}

Danke im Voraus!

Thomas Stringer
quelle
1
Der MSDN-Artikel über statische Klassen und statische Methoden bietet eine ziemlich gute Behandlung.
Robert Harvey
1
@ Robert Harvey: Obwohl der Artikel, auf den Sie verwiesen haben, nützlich ist, bietet er nicht viel über Best Practices und einige der Fallstricke bei der Verwendung statischer Klassen.
Bernard

Antworten:

18

Vermeiden Sie im Allgemeinen statische Aufladungen - insbesondere statische Zustände.

Warum?

  1. Statik verursacht Probleme mit der Parallelität. Da es nur eine Instanz gibt, wird diese natürlich von der gleichzeitigen Ausführung gemeinsam genutzt. Diese gemeinsam genutzten Ressourcen sind der Fluch der gleichzeitigen Programmierung und oft nicht müssen geteilt werden.

  2. Statik verursacht Probleme beim Testen von Einheiten. Jedes Unit-Test-Framework, das es wert ist, wird gleichzeitig getestet. Dann stößt du auf # 1. Schlimmer noch, Sie komplizieren Ihre Tests mit all dem Setup / Teardown-Zeug und dem Hackery, in das Sie geraten, um zu versuchen, Setup-Code zu "teilen".

Es gibt auch viele kleine Dinge. Statik ist in der Regel unflexibel. Sie können sie nicht verbinden, Sie können sie nicht überschreiben, Sie können das Timing ihrer Konstruktion nicht steuern, Sie können sie in Generika nicht gut verwenden. Sie können sie nicht wirklich versionieren.

Statik wird sicherlich verwendet: Konstante Werte funktionieren hervorragend. Reine Methoden, die nicht in eine einzelne Klasse passen, können hier hervorragend funktionieren.

Aber im Allgemeinen vermeiden Sie sie.

Telastyn
quelle
Ich bin neugierig auf das Ausmaß der Unfähigkeit zur Parallelität, die Sie meinen. Können Sie das erweitern? Wenn Sie meinen, dass auf die Mitglieder ohne Trennung zugegriffen werden kann, ist dies absolut sinnvoll. Ihre Anwendungsfälle für die Statik sind das, wofür ich sie normalerweise verwende: konstante Werte und reine / nützliche Methoden. Wenn das der beste und 99% ige Anwendungsfall für Statik ist, dann gibt mir das ein gewisses Maß an Komfort. Auch +1 für Ihre Antwort. Tolle Informationen.
Thomas Stringer
@ThomasStringer - Nur dass mehr Kontaktpunkte zwischen Threads / Aufgaben mehr Möglichkeiten für Parallelitätsprobleme und / oder mehr Leistungsverlust bei der Synchronisierung bedeuten.
Telastyn
Wenn also ein Thread gerade auf ein statisches Mitglied zugreift, müssen andere Threads warten, bis der besitzende Thread die Ressource freigibt?
Thomas Stringer
@ ThomasStringer - vielleicht, vielleicht nicht. Die Statik unterscheidet sich (fast in den meisten Sprachen) in dieser Hinsicht nicht von anderen gemeinsam genutzten Ressourcen.
Telastyn
@ThomasStringer: Leider ist es tatsächlich schlimmer als das, es sei denn, Sie markieren das Mitglied volatile. Sie erhalten nur keine Zusicherungen aus dem Speichermodell ohne flüchtige Daten, sodass das Ändern einer Variablen in einem Thread möglicherweise nicht sofort oder überhaupt nicht berücksichtigt wird.
Phoshi
17

Wenn die Funktion "rein" ist, sehe ich keine Probleme. Eine reine Funktion arbeitet nur in den Eingabeparametern und liefert darauf basierend ein Ergebnis. Es hängt nicht von einem globalen Zustand oder einem externen Kontext ab.

Wenn ich mir Ihr eigenes Codebeispiel ansehe:

public class Class1
{
    public static string GetSomeString()
    {
        // do something
    }
}

Diese Funktion akzeptiert keine Parameter. Daher ist es wahrscheinlich nicht rein (die einzige reine Implementierung dieser Funktion wäre die Rückgabe einer Konstanten). Ich gehe davon aus, dass dieses Beispiel nicht repräsentativ für Ihr eigentliches Problem ist. Ich weise lediglich darauf hin, dass dies wahrscheinlich keine reine Funktion ist.

Nehmen wir ein anderes Beispiel:

public static bool IsOdd(int number) { return (number % 2) == 1; }

Es ist nichts Falsches daran, dass diese Funktion statisch ist. Wir könnten dies sogar zu einer Erweiterungsfunktion machen, damit der Client-Code noch besser lesbar wird. Erweiterungsfunktionen sind im Grunde nur eine spezielle Art von statischen Funktionen.

Telastyn erwähnt die Parallelität korrekt als potenzielles Problem mit statischen Mitgliedern. Da diese Funktion jedoch keinen gemeinsam genutzten Status verwendet, treten hier keine Parallelitätsprobleme auf. Tausend Threads können diese Funktion gleichzeitig ohne Parallelitätsprobleme aufrufen.

Im .NET Framework gibt es seit geraumer Zeit Erweiterungsmethoden. LINQ enthält viele Erweiterungsfunktionen (z. B. Enumerable.Where () , Enumerable.First () , Enumerable.Single () usw.). Wir sehen das nicht als schlecht an, oder?

Unit-Tests können häufig von Vorteil sein, wenn der Code austauschbare Abstraktionen verwendet, sodass der Unit-Test den Systemcode durch ein Test-Double ersetzen kann. Statische Funktionen verbieten diese Flexibilität, dies ist jedoch vor allem an den Grenzen der Architekturschicht wichtig, wo wir beispielsweise eine tatsächliche Datenzugriffsschicht durch eine gefälschte Datenzugriffsschicht ersetzen möchten .

Wenn wir jedoch einen Test für ein Objekt schreiben, das sich anders verhält, je nachdem, ob eine Zahl ungerade oder gerade ist, müssen wir die Funktion nicht wirklich IsOdd()durch eine alternative Implementierung ersetzen können . Ebenso sehe ich nicht, wann wir zum Enumerable.Where()Testen eine andere Implementierung bereitstellen müssen .

Untersuchen wir also die Lesbarkeit des Client-Codes für diese Funktion:

Option a (mit der als Erweiterungsmethode deklarierten Funktion):

public void Execute(int number) {
    if (number.IsOdd())
        // Do something
}

Option b:

public void Execute(int number) {
    var helper = new NumberHelper();
    if (helper.IsOdd(number))
        // Do something
}

Die statische (Erweiterungs-) Funktion macht den ersten Code besser lesbar, und die Lesbarkeit ist sehr wichtig. Verwenden Sie daher gegebenenfalls statische Funktionen.

Pete
quelle