Ist die Verwendung vieler statischer Methoden eine schlechte Sache?

97

Ich neige dazu, alle Methoden in einer Klasse als statisch zu deklarieren, wenn diese Klasse keine internen Zustände verfolgen muss. Wenn ich beispielsweise A in B umwandeln muss und mich nicht auf einen internen Zustand C verlasse, der variieren kann, erstelle ich eine statische Transformation. Wenn es einen internen Zustand C gibt, den ich anpassen möchte, füge ich einen Konstruktor hinzu, um C festzulegen, und verwende keine statische Transformation.

Ich habe verschiedene Empfehlungen (einschließlich StackOverflow) gelesen, um statische Methoden NICHT zu überbeanspruchen, aber ich verstehe immer noch nicht, was mit der obigen Faustregel falsch ist.

Ist das ein vernünftiger Ansatz oder nicht?

Lolo
quelle

Antworten:

152

Es gibt zwei Arten gängiger statischer Methoden:

  • Eine "sichere" statische Methode liefert immer die gleiche Ausgabe für die gleichen Eingaben. Es ändert keine Globals und ruft keine "unsicheren" statischen Methoden einer Klasse auf. Im Wesentlichen verwenden Sie eine eingeschränkte Art der funktionalen Programmierung - haben Sie keine Angst davor, sie sind in Ordnung.
  • Eine "unsichere" statische Methode mutiert den globalen Status oder Proxys für ein globales Objekt oder ein anderes nicht testbares Verhalten. Dies sind Rückschläge bei der prozeduralen Programmierung und sollten nach Möglichkeit überarbeitet werden.

Es gibt einige häufige Verwendungen von "unsicheren" Statiken - zum Beispiel im Singleton-Muster - aber beachten Sie, dass Sie trotz aller hübschen Namen, die Sie sie nennen, nur globale Variablen mutieren. Überlegen Sie sorgfältig, bevor Sie unsichere Statiken verwenden.

John Millikin
quelle
Dies war genau das Problem, das ich lösen musste - die Verwendung oder eher der Missbrauch von Singleton-Objekten.
Overslacked
Vielen Dank für diese hervorragende Antwort. Meine Frage ist, ob die Singletons als Parameter an die statischen Methoden übergeben werden. Ist die statische Methode dadurch unsicher?
Tony D
1
Die Begriffe "reine Funktion" und "unreine Funktion" sind Namen, die in der funktionalen Programmierung für die so genannte "sichere" und "unsichere" Statik verwendet werden.
Omnimike
19

Ein Objekt ohne internen Zustand ist verdächtig.

Normalerweise kapseln Objekte Status und Verhalten. Ein Objekt, das nur das Verhalten einschließt, ist ungerade. Manchmal ist es ein Beispiel für Leichtgewicht oder Fliegengewicht .

In anderen Fällen erfolgt die prozedurale Gestaltung in einer Objektsprache.

S.Lott
quelle
6
Ich höre, was Sie sagen, aber wie kann so etwas wie ein Math-Objekt alles andere als Verhalten einschließen?
JonoW
10
Er sagte nur misstrauisch, nicht falsch, und er hat absolut Recht.
Bill K
2
@ JonoW: Mathe ist ein ganz besonderer Fall, in dem es viele zustandslose Funktionen gibt. Wenn Sie funktionale Programmierung in Java durchführen, haben Sie natürlich viele zustandslose Funktionen.
S.Lott
13

Dies ist wirklich nur eine Fortsetzung von John Millikins großartiger Antwort.


Obwohl es sicher sein kann, zustandslose Methoden (die so ziemlich Funktionen sind) statisch zu machen, kann dies manchmal zu einer Kopplung führen, die schwer zu ändern ist. Angenommen, Sie haben eine statische Methode als solche:

public class StaticClassVersionOne {
    public static void doSomeFunkyThing(int arg);
}

Was du nennst als:

StaticClassVersionOne.doSomeFunkyThing(42);

Das ist alles schön und gut und sehr praktisch, bis Sie auf einen Fall stoßen, in dem Sie das Verhalten der statischen Methode ändern müssen und feststellen, dass Sie eng daran gebunden sind StaticClassVersionOne. Möglicherweise könnten Sie den Code ändern, und es wäre in Ordnung, aber wenn andere Anrufer vom alten Verhalten abhängig wären, müssten sie im Hauptteil der Methode berücksichtigt werden. In einigen Fällen kann dieser Methodenkörper ziemlich hässlich oder nicht mehr wartbar sein, wenn er versucht, all diese Verhaltensweisen auszugleichen. Wenn Sie die Methoden aufteilen, müssen Sie möglicherweise den Code an mehreren Stellen ändern, um dies zu berücksichtigen, oder neue Klassen aufrufen.

Wenn Sie jedoch eine Schnittstelle erstellt haben, um die Methode bereitzustellen, und diese an Aufrufer weitergegeben haben, kann jetzt, wenn sich das Verhalten ändern muss, eine neue Klasse erstellt werden, um die Schnittstelle zu implementieren, die sauberer, einfacher zu testen und wartbarer ist. und das wird stattdessen den Anrufern gegeben. In diesem Szenario müssen die aufrufenden Klassen nicht geändert oder sogar neu kompiliert werden, und die Änderungen werden lokalisiert.

Es kann eine wahrscheinliche Situation sein oder auch nicht, aber ich denke, es lohnt sich, darüber nachzudenken.

Grundlefleck
quelle
5
Ich behaupte, dass dies nicht nur ein wahrscheinliches Szenario ist, sondern dass die Statik ein letzter Ausweg ist. Statik macht TDD auch zu einem Albtraum. Wo immer Sie die Statik verwenden, können Sie nicht verspotten, Sie müssen wissen, was die Eingabe und Ausgabe ist, um eine nicht verwandte Klasse zu testen. Wenn Sie nun das Verhalten der Statik ändern, werden Ihre Tests für nicht verwandte Klassen, die diese Statik verwenden, unterbrochen. Außerdem wird es zu einer versteckten Abhängigkeit, die Sie nicht an den Konstruktor weitergeben können, um Entwickler über eine möglicherweise wichtige Abhängigkeit zu informieren.
DanCaveman
6

Die andere Option besteht darin, sie als nicht statische Methoden zum Ursprungsobjekt hinzuzufügen:

dh ändern:

public class BarUtil {
    public static Foo transform(Bar toFoo) { ... }
}

in

public class Bar {
    ...
    public Foo transform() { ...}
}

In vielen Situationen ist dies jedoch nicht möglich (z. B. bei der regulären Generierung von Klassencode aus XSD / WSDL / usw.), oder die Klasse wird sehr lang, und Transformationsmethoden können für komplexe Objekte oft ein echtes Problem sein, und Sie möchten sie nur in ihrer eigenen Klasse. Also ja, ich habe statische Methoden in Utility-Klassen.

JeeBee
quelle
5

Statische Klassen sind in Ordnung, solange sie an den richtigen Stellen verwendet werden.

Nämlich: Methoden, die 'Blatt'-Methoden sind (sie ändern den Status nicht, sie transformieren lediglich die Eingabe irgendwie). Gute Beispiele hierfür sind Dinge wie Path.Combine. Diese Art von Dingen sind nützlich und sorgen für eine engere Syntax.

Die Probleme, die ich mit der Statik habe, sind zahlreich:

Erstens, wenn Sie statische Klassen haben, werden Abhängigkeiten ausgeblendet. Folgendes berücksichtigen:

public static class ResourceLoader
{
    public static void Init(string _rootPath) { ... etc. }
    public static void GetResource(string _resourceName)  { ... etc. }
    public static void Quit() { ... etc. }
}

public static class TextureManager
{
    private static Dictionary<string, Texture> m_textures;

    public static Init(IEnumerable<GraphicsFormat> _formats) 
    {
        m_textures = new Dictionary<string, Texture>();

        foreach(var graphicsFormat in _formats)
        {
              // do something to create loading classes for all 
              // supported formats or some other contrived example!
        }
    }

    public static Texture GetTexture(string _path) 
    {
        if(m_textures.ContainsKey(_path))
            return m_textures[_path];

        // How do we know that ResourceLoader is valid at this point?
        var texture = ResourceLoader.LoadResource(_path);
        m_textures.Add(_path, texture);
        return texture; 
    }

    public static Quit() { ... cleanup code }       
}

In TextureManager können Sie anhand eines Konstruktors nicht erkennen, welche Initialisierungsschritte ausgeführt werden müssen. Sie müssen sich mit der Klasse befassen, um ihre Abhängigkeiten zu finden und die Dinge in der richtigen Reihenfolge zu initialisieren. In diesem Fall muss der ResourceLoader vor der Ausführung initialisiert werden. Skalieren Sie nun diesen Abhängigkeitsalptraum und Sie können wahrscheinlich erraten, was passieren wird. Stellen Sie sich vor, Sie versuchen, Code zu verwalten, wenn keine explizite Reihenfolge für die Initialisierung vorliegt. Vergleichen Sie dies mit der Abhängigkeitsinjektion mit Instanzen - in diesem Fall wird der Code nicht einmal kompiliert, wenn die Abhängigkeiten nicht erfüllt sind!

Wenn Sie außerdem Statiken verwenden, die den Status ändern, ist dies wie ein Kartenhaus. Man weiß nie, wer Zugang zu was hat, und das Design ähnelt eher einem Spaghetti-Monster.

Schließlich und ebenso wichtig ist, dass die Verwendung von Statik ein Programm an eine bestimmte Implementierung bindet. Statischer Code ist das Gegenteil von Design für Testbarkeit. Das Testen von Code, der mit Statik durchsetzt ist, ist ein Albtraum. Ein statischer Aufruf kann niemals gegen ein Test-Double ausgetauscht werden (es sei denn, Sie verwenden Test-Frameworks, die speziell zum Verspotten statischer Typen entwickelt wurden). Ein statisches System bewirkt daher, dass alles, was ihn verwendet, ein sofortiger Integrationstest ist.

Kurz gesagt, Statik ist für einige Dinge in Ordnung und für kleine Werkzeuge oder Wegwerfcode würde ich ihre Verwendung nicht entmutigen. Darüber hinaus sind sie jedoch ein blutiger Albtraum für Wartbarkeit, gutes Design und einfache Tests.

Hier ist ein guter Artikel zu den Problemen: http://gamearchitect.net/2008/09/13/an-anatomy-of-despair-managers-and-contexts/

Mark Simpson
quelle
4

Der Grund, warum Sie vor statischen Methoden gewarnt werden, besteht darin, dass deren Verwendung einen der Vorteile von Objekten einbüßt. Objekte sind für die Datenkapselung vorgesehen. Dies verhindert das Auftreten unerwarteter Nebenwirkungen, wodurch Fehler vermieden werden. Statische Methoden haben keine gekapselten Daten * und bieten daher keinen Vorteil.

Wenn Sie jedoch keine internen Daten verwenden, sind diese in Ordnung und etwas schneller auszuführen. Stellen Sie jedoch sicher, dass Sie darin keine globalen Daten berühren.

  • Einige Sprachen haben auch Variablen auf Klassenebene, die die Kapselung von Daten und statischen Methoden ermöglichen würden.
Steve Rowe
quelle
4

Das scheint ein vernünftiger Ansatz zu sein. Der Grund, warum Sie nicht zu viele statische Klassen / Methoden verwenden möchten, ist, dass Sie sich von der objektorientierten Programmierung wegbewegen und sich mehr dem Bereich der strukturierten Programmierung zuwenden.

In Ihrem Fall, in dem Sie einfach A nach B transformieren, sagen wir, wir transformieren nur Text, von dem aus wir gehen

"hello" =>(transform)=> "<b>Hello!</b>"

Dann wäre eine statische Methode sinnvoll.

Wenn Sie diese statischen Methoden jedoch häufig für ein Objekt aufrufen und sie für viele Aufrufe eindeutig sind (z. B. hängt die Art und Weise, wie Sie sie verwenden, von der Eingabe ab) oder Teil des inhärenten Verhaltens des Objekts ist, ist dies der Fall Sei weise, es zu einem Teil des Objekts zu machen und einen Zustand davon aufrechtzuerhalten. Eine Möglichkeit, dies zu tun, besteht darin, es als Schnittstelle zu implementieren.

class Interface{
    method toHtml(){
        return transformed string (e.g. "<b>Hello!</b>")
    }

    method toConsole(){
        return transformed string (e.g. "printf Hello!")
    }
}


class Object implements Interface {
    mystring = "hello"

    //the implementations of the interface would yield the necessary 
    //functionality, and it is reusable across the board since it 
    //is an interface so... you can make it specific to the object

   method toHtml()
   method toConsole()
}

Bearbeiten: Ein gutes Beispiel für die gute Verwendung statischer Methoden sind HTML-Hilfsmethoden in Asp.Net MVC oder Ruby. Sie erstellen HTML-Elemente, die nicht an das Verhalten eines Objekts gebunden sind und daher statisch sind.

Edit 2: Die funktionale Programmierung wurde in strukturierte Programmierung geändert (aus irgendeinem Grund war ich verwirrt), und Torsten wurde darauf hingewiesen, dass er darauf hingewiesen hat.

MunkiPhD
quelle
2
Ich denke nicht, dass die Verwendung statischer Methoden als funktionale Programmierung qualifiziert ist, also denke ich, dass Sie strukturierte Programmierung meinen.
Torsten Marek
3

Ich habe kürzlich eine Anwendung überarbeitet, um einige Klassen zu entfernen / ändern, die ursprünglich als statische Klassen implementiert wurden. Im Laufe der Zeit haben diese Klassen so viel gewonnen und die Leute haben die neuen Funktionen immer wieder als statisch markiert, da nie eine Instanz herumschwebte.

Meine Antwort lautet also, dass statische Klassen nicht von Natur aus schlecht sind, es jedoch möglicherweise einfacher ist, jetzt mit dem Erstellen von Instanzen zu beginnen und sie später umzugestalten.

überschwemmt
quelle
3

Ich würde es als Designgeruch betrachten. Wenn Sie hauptsächlich statische Methoden verwenden, haben Sie wahrscheinlich kein sehr gutes OO-Design. Es ist nicht unbedingt schlecht, aber wie bei allen Gerüchen würde es mich dazu bringen, anzuhalten und neu zu bewerten. Es deutet darauf hin, dass Sie möglicherweise ein besseres OO-Design erstellen können oder dass Sie in die andere Richtung gehen und OO für dieses Problem vollständig vermeiden sollten.

Patros
quelle
2

Ich pendelte zwischen einer Klasse mit einer Reihe statischer Methoden und einem Singleton hin und her. Beide lösen das Problem, aber der Singleton kann viel einfacher durch mehr als einen ersetzt werden. (Programmierer scheinen immer so sicher zu sein, dass es nur eine von etwas geben wird, und ich habe mich oft genug geirrt, um statische Methoden vollständig aufzugeben, außer in einigen sehr begrenzten Fällen).

Auf jeden Fall gibt Ihnen der Singleton die Möglichkeit, später etwas an die Factory zu übergeben, um eine andere Instanz zu erhalten, und dies ändert das Verhalten Ihres gesamten Programms ohne Refactoring. Das Ändern einer globalen Klasse statischer Methoden in etwas mit anderen "Hintergrund" -Daten oder einem etwas anderen Verhalten (untergeordnete Klasse) ist ein großes Problem.

Und statische Methoden haben keinen ähnlichen Vorteil.

Also ja, sie sind schlecht.

Bill K.
quelle
1

Solange kein interner Zustand ins Spiel kommt, ist dies in Ordnung. Beachten Sie, dass normalerweise erwartet wird, dass statische Methoden threadsicher sind. Wenn Sie also Hilfsdatenstrukturen verwenden, verwenden Sie diese threadsicher.

Lucero
quelle
1

Wenn Sie wissen, dass Sie den internen Status von C niemals verwenden müssen, ist dies in Ordnung. Sollte sich dies in Zukunft jemals ändern, müssten Sie die Methode nicht statisch machen. Wenn es zunächst nicht statisch ist, können Sie den internen Status einfach ignorieren, wenn Sie ihn nicht benötigen.

Adam Crume
quelle
1

Wenn es sich um eine Dienstprogrammmethode handelt, ist es schön, sie statisch zu machen. Guava und Apache Commons bauen auf diesem Prinzip auf.

Meine Meinung dazu ist rein pragmatisch. Wenn es sich um Ihren App-Code handelt, sind statische Methoden im Allgemeinen nicht das Beste. Statische Methoden unterliegen schwerwiegenden Einschränkungen beim Testen von Einheiten - sie können nicht einfach verspottet werden: Sie können keine verspottete statische Funktionalität in einen anderen Test einfügen. Normalerweise können Sie einer statischen Methode auch keine Funktionalität hinzufügen.

Daher habe ich in meiner App-Logik normalerweise kleine statische Dienstprogramm-ähnliche Methodenaufrufe. Dh

static cutNotNull(String s, int length){
  return s == null ? null : s.substring(0, length);
}

Einer der Vorteile ist, dass ich solche Methoden nicht teste :-)

Andrey Chaschev
quelle
1

Natürlich gibt es keine Silberkugel. Statische Klassen sind für kleine Dienstprogramme / Helfer in Ordnung. Aber die Verwendung statischer Methoden für die Programmierung von Geschäftslogik ist sicherlich böse. Betrachten Sie den folgenden Code

   public class BusinessService
   {

        public Guid CreateItem(Item newItem, Guid userID, Guid ownerID)
        {
            var newItemId = itemsRepository.Create(createItem, userID, ownerID);
            **var searchItem = ItemsProcessor.SplitItem(newItem);**
            searchRepository.Add(searchItem);
            return newItemId;
        }
    }

Sie sehen einen statischen Methodenaufruf für ItemsProcessor.SplitItem(newItem);Es riecht Ursache

  • Sie haben keine explizite Abhängigkeit deklariert und wenn Sie sich nicht mit Code befassen, übersehen Sie möglicherweise die Kopplung zwischen Ihrer Klasse und dem statischen Methodencontainer
  • Sie können das BusinessServiceIsolieren nicht testen ItemsProcessor(die meisten Testtools verspotten keine statischen Klassen) und es macht Unit-Tests unmöglich. Keine Unit Tests == geringe Qualität
Konstantin Chernov
quelle
0

Statische Methoden sind im Allgemeinen eine schlechte Wahl, selbst für zustandslosen Code. Erstellen Sie stattdessen eine Singleton-Klasse mit diesen Methoden, die einmal instanziiert und in die Klassen eingefügt wird, die die Methoden verwenden möchten. Solche Klassen sind leichter zu verspotten und zu testen. Sie sind viel objektorientierter. Sie können sie bei Bedarf mit einem Proxy umschließen. Statik macht OO viel schwieriger und ich sehe keinen Grund, sie in fast allen Fällen zu verwenden. Nicht 100%, aber fast alle.

John
quelle