722 Stimmen

Warum werden statische Variablen als böse angesehen?

Ich bin ein Java-Programmierer, der neu in der Unternehmenswelt ist. Vor kurzem habe ich eine Anwendung entwickelt, die Groovy und Java. Der gesamte von mir geschriebene Code enthielt eine ganze Reihe von statischen Elementen. Ich wurde von den leitenden Technikern gebeten, die Anzahl der verwendeten statischen Elemente zu verringern. Ich habe darüber gegoogelt und festgestellt, dass viele Programmierer ziemlich gegen die Verwendung statischer Variablen sind.

Ich finde statische Variablen bequemer zu verwenden. Und ich nehme an, dass sie auch effizient sind (bitte korrigieren Sie mich, wenn ich falsch liege), denn wenn ich eine Funktion innerhalb einer Klasse 10.000 Mal aufrufen müsste, wäre ich froh, wenn ich die Methode statisch machen und ein einfaches Class.methodCall() zu verwenden, anstatt den Speicher mit 10.000 Instanzen der Klasse zu überladen, richtig?

Außerdem reduziert die Statik die Abhängigkeiten von anderen Teilen des Codes. Sie können als perfekte Zustandshalter fungieren. Darüber hinaus finde ich, dass Statik in einigen Sprachen wie Smalltalk y Scala . Warum also ist dieser Widerstand gegen die Statik unter Programmierern weit verbreitet (insbesondere in der Java-Welt)?

PS: Bitte korrigieren Sie mich, wenn meine Annahmen zur Statik falsch sind.

784voto

Jon Skeet Punkte 1325502

Statische Variablen stellen einen globalen Zustand dar. Das ist schwer nachzuvollziehen und schwer zu testen: Wenn ich eine neue Instanz eines Objekts erstelle, kann ich im Rahmen von Tests über seinen neuen Zustand nachdenken. Wenn ich Code verwende, der statische Variablen verwendet, könnte er sich in jedem beliebigen Zustand befinden - und alles könnte ihn verändern.

Ich könnte noch eine ganze Weile so weitermachen, aber das wichtigste Konzept, über das man nachdenken sollte, ist, dass etwas umso leichter zu verstehen ist, je enger der Rahmen ist, in dem es liegt. Wir sind gut darin, über kleine Dinge nachzudenken, aber es ist schwer, über den Zustand eines Millionen-Zeilen-Systems nachzudenken, wenn es keine Modularität gibt. Das gilt übrigens für alle möglichen Dinge - nicht nur für statische Variablen.

307voto

Jessica Brown Punkte 7992

Es ist nicht sehr objektorientiert: Ein Grund dafür, dass die Statik von manchen als "böse" angesehen wird, ist, dass sie im Gegensatz zur objektorientiertes Paradigma . Insbesondere verstößt es gegen den Grundsatz, dass Daten in Objekten gekapselt sind (die erweitert werden können, Information Hiding, etc.). Statik, so wie Sie sie beschreiben, bedeutet im Wesentlichen, sie als globale Variable zu verwenden, um Probleme wie den Geltungsbereich zu vermeiden. Globale Variablen sind jedoch eines der bestimmenden Merkmale des prozeduralen oder imperativen Programmierparadigmas und kein Merkmal "guten" objektorientierten Codes. Das soll nicht heißen, dass das prozedurale Paradigma schlecht ist, aber ich habe den Eindruck, dass Ihr Vorgesetzter von Ihnen erwartet, dass Sie "guten objektorientierten Code" schreiben, und Sie eigentlich "guten prozeduralen Code" schreiben wollen.

Es gibt viele Probleme in Java, wenn man anfängt, Statik zu verwenden, die nicht immer sofort offensichtlich sind. Wenn Sie zum Beispiel zwei Kopien Ihres Programms in derselben VM laufen lassen, werden diese den Wert der statischen Variablen teilen und den Zustand der jeweils anderen durcheinander bringen? Oder was passiert, wenn Sie die Klasse erweitern, können Sie das statische Mitglied überschreiben? Geht Ihrer VM der Speicher aus, weil Sie wahnsinnig viele statische Variablen haben und dieser Speicher nicht für andere benötigte Instanzobjekte zurückgewonnen werden kann?

Objekt-Lebensdauer: Außerdem haben Statiken eine Lebensdauer, die der gesamten Laufzeit des Programms entspricht. Das bedeutet, dass der Speicher all dieser statischen Variablen nicht gelöscht werden kann, selbst wenn Sie Ihre Klasse nicht mehr verwenden. Wenn Sie stattdessen Ihre Variablen nicht statisch machen und in Ihrer main()-Funktion eine einzige Instanz Ihrer Klasse erzeugen und dann Ihre Klasse auffordern, eine bestimmte Funktion 10.000 Mal auszuführen, können nach diesen 10.000 Aufrufen und dem Löschen der Verweise auf die einzelne Instanz alle statischen Variablen entsorgt und wiederverwendet werden.

Verhindert eine bestimmte Wiederverwendung: Außerdem können statische Methoden nicht verwendet werden, um eine Schnittstelle zu implementieren, so dass statische Methoden verhindern können, dass bestimmte objektorientierte Funktionen genutzt werden können.

Andere Optionen: Wenn Effizienz Ihr Hauptanliegen ist, gibt es vielleicht andere Möglichkeiten, das Geschwindigkeitsproblem zu lösen, als nur den Vorteil zu berücksichtigen, dass der Aufruf in der Regel schneller ist als die Erstellung. Überlegen Sie, ob die transienten oder flüchtigen Modifikatoren irgendwo benötigt werden. Um die Möglichkeit des Inlinings zu erhalten, könnte eine Methode als final statt als statisch gekennzeichnet werden. Methodenparameter und andere Variablen können als final gekennzeichnet werden, um bestimmte Compiler-Optimierungen zu ermöglichen, die auf Annahmen darüber beruhen, was diese Variablen ändern kann. Ein Instanzobjekt könnte mehrfach wiederverwendet werden, anstatt jedes Mal eine neue Instanz zu erzeugen. Möglicherweise gibt es Compiler-Optimierungsschalter, die für die Anwendung im Allgemeinen aktiviert werden sollten. Vielleicht sollte das Design so eingerichtet werden, dass die 10.000 Durchläufe mit mehreren Threads durchgeführt werden können und die Vorteile von Multiprozessorkernen genutzt werden. Wenn die Portabilität keine Rolle spielt, würde eine native Methode vielleicht eine bessere Geschwindigkeit erzielen als die statische Methode.

Wenn Sie aus irgendeinem Grund nicht mehrere Kopien eines Objekts wünschen, kann die Singleton-Entwurfsmuster hat Vorteile gegenüber statischen Objekten, wie z.B. Thread-Sicherheit (vorausgesetzt, Ihr Singleton ist gut kodiert), ermöglicht Lazy-Initialisierung, garantiert, dass das Objekt richtig initialisiert wurde, wenn es verwendet wird, Sub-Classing, Vorteile beim Testen und Refactoring Ihres Codes, ganz zu schweigen davon, dass es VIEL einfacher ist, den Code zu entfernen, um doppelte Instanzen zu verhindern, als den gesamten Code Ihrer statischen Variablen zu überarbeiten, um Instanzvariablen zu verwenden, wenn Sie irgendwann Ihre Meinung ändern, dass Sie nur eine Instanz eines Objekts wollen. Ich musste das schon einmal machen, und es macht keinen Spaß, und am Ende muss man viel mehr Klassen bearbeiten, was das Risiko erhöht, neue Fehler einzuführen ... es ist also viel besser, die Dinge beim ersten Mal "richtig" einzurichten, auch wenn das scheinbar seine Nachteile hat. Für mich ist die Nacharbeit, die erforderlich ist, wenn man später mehrere Kopien von etwas braucht, wahrscheinlich einer der zwingendsten Gründe, Statik so selten wie möglich zu verwenden. Und deshalb würde ich auch Ihrer Aussage widersprechen, dass Statik die gegenseitigen Abhängigkeiten reduziert. Ich denke, dass man am Ende einen Code hat, der stärker gekoppelt ist, wenn man viele Statiken hat, auf die man direkt zugreifen kann, anstatt ein Objekt, das von sich aus "weiß, wie man etwas macht".

103voto

Preet Sangha Punkte 62622

Das Böse ist ein subjektiver Begriff.

Sie haben keine Kontrolle über die Statik in Bezug auf die Erstellung und Zerstörung. Sie lebt auf Geheiß des Programms, das geladen und entladen wird.

Da die statischen Daten in einem Raum leben, müssen alle Threads, die sie nutzen wollen, eine Zugangskontrolle durchlaufen, die Sie verwalten müssen. Das bedeutet, dass die Programme stärker gekoppelt sind, und diese Veränderung ist schwieriger vorhersehbar und zu verwalten (wie J. Skeet sagt). Dies führt zu Problemen bei der Isolierung der Auswirkungen von Änderungen und wirkt sich somit auf die Verwaltung von Tests aus.

Dies sind die beiden Hauptprobleme, die ich mit ihnen habe.

65voto

irreputable Punkte 43667

Nein, globale Staaten sind nicht per se böse. Aber wir müssen sehen Ihr Code, um zu sehen, ob Sie ihn richtig verwendet haben. Es ist durchaus möglich, dass ein Neuling globale Zustände missbraucht; genau wie er jede Sprachfunktion missbrauchen würde.

Globale Staaten sind eine absolute Notwendigkeit. Wir können globale Staaten nicht vermeiden. Wir können nicht vermeiden, über globale Zustände nachzudenken. - Wenn wir unsere Anwendungssemantik verstehen wollen.

Menschen, die versuchen, globale Zustände um ihrer selbst willen loszuwerden, enden unweigerlich mit einem viel komplexeren System - und die globalen Zustände sind immer noch da, geschickt/idiotisch getarnt unter vielen Schichten von Indirektionen; und wir müssen immer noch über globale Zustände nachdenken, nachdem wir alle Indirektionen ausgepackt haben.

Wie die Spring-Leute, die in xml großzügig globale Zustände deklarieren und meinen, das sei irgendwie besser.

@Jon Skeet if I create a new instance of an object haben Sie nun zwei Dinge, über die Sie nachdenken müssen - den Zustand innerhalb des Objekts und den Zustand der Umgebung, in der sich das Objekt befindet.

37voto

JBCP Punkte 12445

Wenn Sie das Schlüsselwort "static" ohne das Schlüsselwort "final" verwenden, sollte dies ein Zeichen dafür sein, dass Sie Ihren Entwurf sorgfältig überdenken sollten. Selbst das Vorhandensein eines "final" ist kein Freifahrtschein, da ein veränderbares statisches finales Objekt ebenso gefährlich sein kann.

Ich würde schätzen, dass in etwa 85 % der Fälle, in denen ich ein "Static" ohne ein "Final" sehe, es FALSCH ist. Oft finde ich seltsame Umgehungslösungen, um diese Probleme zu verschleiern oder zu verbergen.

Bitte erstellen Sie keine statischen Mutables. Insbesondere Collections. Im Allgemeinen sollten Collections initialisiert werden, wenn ihr enthaltendes Objekt initialisiert wird, und sie sollten so entworfen werden, dass sie zurückgesetzt oder vergessen werden, wenn ihr enthaltendes Objekt vergessen wird.

Die Verwendung von Statik kann zu sehr subtilen Fehlern führen, die den Ingenieuren tagelang zu schaffen machen. Ich weiß das, denn ich habe diese Fehler sowohl erzeugt als auch gejagt.

Wenn Sie weitere Einzelheiten erfahren möchten, lesen Sie bitte weiter

Warum nicht Statik verwenden?

Es gibt viele Probleme mit der Statik, einschließlich des Schreibens und Ausführens von Tests, sowie subtile Fehler, die nicht sofort offensichtlich sind.

Code, der sich auf statische Objekte stützt, kann nicht einfach unitgetestet werden, und statische Objekte können (in der Regel) nicht einfach gemockt werden.

Wenn Sie Statik verwenden, ist es nicht möglich, die Implementierung der Klasse auszulagern, um Komponenten höherer Ebenen zu testen. Stellen Sie sich zum Beispiel eine statische CustomerDAO vor, die Kundenobjekte zurückgibt, die sie aus der Datenbank lädt. Nun habe ich eine Klasse CustomerFilter, die auf einige Kundenobjekte zugreifen muss. Wenn CustomerDAO statisch ist, kann ich keinen Test für CustomerFilter schreiben, ohne zuerst meine Datenbank zu initialisieren und nützliche Informationen einzupflegen.

Und die Population und Initialisierung der Datenbank nimmt viel Zeit in Anspruch. Und meiner Erfahrung nach wird sich Ihr DB-Initialisierungs-Framework im Laufe der Zeit ändern, was bedeutet, dass sich die Daten verändern und die Tests möglicherweise nicht funktionieren. IE, stellen Sie sich vor, Kunde 1 verwendet werden, um ein VIP, aber die DB-Initialisierung Rahmen geändert, und jetzt Kunde 1 ist nicht mehr VIP, aber Ihr Test wurde hart-codiert, um Kunden 1 zu laden

Ein besserer Ansatz ist es, ein CustomerDAO zu instanziieren und es an den CustomerFilter zu übergeben, wenn er erstellt wird. (Ein noch besserer Ansatz wäre die Verwendung von Spring oder einem anderen Framework für die Inversion der Kontrolle.

Sobald Sie dies getan haben, können Sie schnell eine alternative DAO in Ihrem CustomerFilterTest mocken oder stub out, so dass Sie mehr Kontrolle über den Test haben,

Ohne die statische DAO ist der Test schneller (keine Datenbankinitialisierung) und zuverlässiger (da er nicht fehlschlägt, wenn sich der Code der Datenbankinitialisierung ändert). In diesem Fall wird zum Beispiel sichergestellt, dass Kunde 1 immer ein VIP ist und bleiben wird, soweit der Test betroffen ist.

Ausführen von Tests

Statik ist ein echtes Problem, wenn Suiten von Unit-Tests zusammen ausgeführt werden (zum Beispiel mit Ihrem Continuous Integration Server). Stellen Sie sich eine statische Zuordnung von Netzwerk-Socket-Objekten vor, die von einem Test zum anderen offen bleibt. Der erste Test öffnet vielleicht einen Socket am Port 8080, aber Sie haben vergessen, die Map zu löschen, wenn der Test abgebrochen wird. Wenn nun ein zweiter Test gestartet wird, stürzt er wahrscheinlich ab, wenn er versucht, einen neuen Socket für Port 8080 zu erstellen, da der Port noch belegt ist. Stellen Sie sich auch vor, dass die Socket-Referenzen in Ihrer statischen Collection nicht entfernt werden und (mit Ausnahme der WeakHashMap) nie für Garbage Collect in Frage kommen, was zu einem Speicherleck führt.

Dies ist ein übergeneralisiertes Beispiel, aber in großen Systemen tritt dieses Problem IMMER auf. Viele Leute denken nicht daran, dass Unit-Tests ihre Software wiederholt in derselben JVM starten und stoppen, aber es ist ein guter Test für Ihr Softwaredesign, und wenn Sie eine hohe Verfügbarkeit anstreben, müssen Sie sich dessen bewusst sein.

Diese Probleme treten häufig bei Framework-Objekten auf, z. B. beim DB-Zugriff, beim Caching, beim Messaging und bei den Protokollierungsschichten. Wenn Sie Java EE oder einige Best-of-Breed-Frameworks verwenden, verwalten diese wahrscheinlich vieles davon für Sie, aber wenn Sie wie ich mit einem Altsystem zu tun haben, haben Sie möglicherweise viele benutzerdefinierte Frameworks für den Zugriff auf diese Schichten.

Wenn sich die Systemkonfiguration, die für diese Framework-Komponenten gilt, zwischen den Unit-Tests ändert und das Unit-Test-Framework die Komponenten nicht abbaut und neu erstellt, können diese Änderungen nicht wirksam werden, und wenn sich ein Test auf diese Änderungen stützt, schlägt er fehl.

Auch Komponenten, die nicht zu einem Framework gehören, sind von diesem Problem betroffen. Stellen Sie sich eine statische Map namens OpenOrders vor. Sie schreiben einen Test, der ein paar offene Aufträge erstellt und überprüft, ob sie alle im richtigen Zustand sind, dann endet der Test. Ein anderer Entwickler schreibt einen zweiten Test, der die benötigten Aufträge in die OpenOrders-Map einträgt und dann überprüft, ob die Anzahl der Aufträge korrekt ist. Einzeln ausgeführt, würden beide Tests erfolgreich sein, aber wenn sie zusammen in einer Suite ausgeführt werden, schlagen sie fehl.

Schlimmer noch, das Scheitern könnte auf der Reihenfolge der Durchführung der Tests beruhen.

In diesem Fall vermeiden Sie durch den Verzicht auf statische Daten das Risiko, dass Daten über mehrere Testinstanzen hinweg bestehen bleiben, was eine höhere Zuverlässigkeit der Tests gewährleistet.

Subtile Wanzen

Wenn Sie in einer Hochverfügbarkeitsumgebung arbeiten oder überall dort, wo Threads gestartet und gestoppt werden können, können dieselben Bedenken wie bei den Unit-Test-Suites auch dann gelten, wenn Ihr Code in der Produktion ausgeführt wird.

Bei der Arbeit mit Threads ist es besser, ein Objekt zu verwenden, das während der Startphase des Threads initialisiert wird, als ein statisches Objekt zum Speichern von Daten zu verwenden. Auf diese Weise wird jedes Mal, wenn der Thread gestartet wird, eine neue Instanz des Objekts (mit einer potenziell neuen Konfiguration) erstellt, und Sie vermeiden, dass Daten aus einer Instanz des Threads in die nächste Instanz übergehen.

Wenn ein Thread stirbt, wird ein statisches Objekt nicht zurückgesetzt oder garbage collected. Stellen Sie sich vor, Sie haben einen Thread mit dem Namen "EmailCustomers", der beim Start eine statische String-Sammlung mit einer Liste von E-Mail-Adressen auffüllt und dann beginnt, E-Mails an jede dieser Adressen zu senden. Angenommen, der Thread wird irgendwie unterbrochen oder abgebrochen, so dass Ihr Hochverfügbarkeits-Framework den Thread neu startet. Wenn der Thread dann startet, wird die Liste der Kunden neu geladen. Da die Sammlung jedoch statisch ist, könnte die Liste der E-Mail-Adressen aus der vorherigen Sammlung beibehalten werden. Nun erhalten einige Kunden möglicherweise doppelte E-Mails.

Eine Randbemerkung: Statisches Finale

Die Verwendung von "static final" ist praktisch das Java-Äquivalent eines C #define, obwohl es technische Implementierungsunterschiede gibt. Ein C/C++ #define wird vom Präprozessor vor der Kompilierung aus dem Code ausgelagert. Ein "static final" in Java landet im Klassenspeicher der JVM und ist damit (normalerweise) dauerhaft im Arbeitsspeicher gespeichert. In dieser Hinsicht ähnelt sie eher einer "static const"-Variablen in C++ als einer #define.

Zusammenfassung

Ich hoffe, dass dies dazu beiträgt, ein paar grundlegende Gründe zu erklären, warum die Statik problematisch ist. Wenn Sie ein modernes Java-Framework wie Java EE oder Spring usw. verwenden, werden Sie vielleicht nicht auf viele dieser Situationen stoßen, aber wenn Sie mit einem großen Bestand an Legacy-Code arbeiten, können sie viel häufiger auftreten.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X