Warum ist der globale Staat so böse?


328

Lassen Sie mich zunächst sagen, dass mir die Konzepte der Abstraktion und der Abhängigkeitsinjektion bekannt sind. Ich muss meine Augen hier nicht öffnen.

Nun, die meisten von uns sagen (zu) oft, ohne wirklich zu verstehen: "Benutze keine globalen Variablen" oder "Singletons sind böse, weil sie global sind". Aber was wirklich ist so schlecht über den ominösen globalen Zustand?

Angenommen, ich benötige eine globale Konfiguration für meine Anwendung, z. B. Systemordnerpfade oder anwendungsweite Datenbankanmeldeinformationen.

In diesem Fall sehe ich keine andere gute Lösung als die Bereitstellung dieser Einstellungen in einem globalen Bereich, der normalerweise für die gesamte Anwendung verfügbar ist.

Ich weiß , dass es schlecht ist zu missbrauchen , aber ist der globale Raum wirklich DASS Übel? Und wenn ja, welche guten Alternativen gibt es?


5
Sie können eine Konfigurationsklasse verwenden, die den Zugriff auf diese Einstellungen verwaltet. Ich denke, es ist eine gute Praxis, einen einzigen Ort zu haben, an dem Konfigurationseinstellungen aus Konfigurationsdateien und / oder Datenbanken gelesen werden, während andere Objekte sie aus dieser Konfigurationssache abrufen. Dadurch wird Ihr Design sauberer und vorhersehbarer.
Hajo

25
Wo ist der Unterschied zu einem globalen? Dein "einzigartiger Ort" klingt sehr verdächtig wie ein Singleton.
Madara Uchiha

130
Das einzig wahre Übel sind Dogmen.
Pieter B

34
Wenn Sie mit Ihren Programmierkollegen den globalen Status verwenden, ist das so, als würden Sie mit Ihren Freunden dieselbe Zahnbürste verwenden - Sie können es, aber Sie wissen nie, wann sich jemand dazu entschließen wird, ihn in den Hintern zu schieben.
Zigi

5
Der Teufel ist böse. Der globale Staat ist weder böse noch gut. Es kann sehr nützlich oder schädlich sein, je nachdem, wie Sie es verwenden. Höre nicht auf andere und lerne einfach, wie man richtig programmiert.
ärgerlich_squid

Antworten:


282

Sehr kurz, es macht den Programmstatus unvorhersehbar.

Stellen Sie sich vor, Sie haben ein paar Objekte, die beide dieselbe globale Variable verwenden. Angenommen, Sie verwenden in keinem Modul eine Zufallsquelle, kann die Ausgabe einer bestimmten Methode vorhergesagt (und daher getestet) werden, wenn der Systemstatus bekannt ist, bevor Sie die Methode ausführen.

Wenn jedoch eine Methode in einem der Objekte einen Nebeneffekt auslöst, der den Wert des gemeinsam genutzten globalen Status ändert, wissen Sie nicht mehr, wie der Ausgangsstatus lautet, wenn Sie eine Methode im anderen Objekt ausführen. Sie können jetzt nicht mehr vorhersagen, welche Ausgabe Sie erhalten, wenn Sie die Methode ausführen, und können sie daher nicht testen.

Auf akademischer Ebene mag dies nicht sehr ernst klingen, aber die Fähigkeit, Code als Einheit zu testen, ist ein wichtiger Schritt bei der Überprüfung seiner Richtigkeit (oder zumindest seiner Eignung für den Zweck).

In der realen Welt kann dies schwerwiegende Folgen haben. Angenommen, Sie haben eine Klasse, die eine globale Datenstruktur auffüllt, und eine andere Klasse, die die Daten in dieser Datenstruktur verarbeitet, ihren Status ändert oder sie dabei zerstört. Wenn die Prozessorklasse eine Methode ausführt, bevor die Populatorklasse fertig ist, ist das Ergebnis, dass die Prozessorklasse wahrscheinlich unvollständige Daten zu verarbeiten hat und die Datenstruktur, an der die Populatorklasse gearbeitet hat, beschädigt oder zerstört werden könnte. Das Programmverhalten wird unter diesen Umständen völlig unvorhersehbar und wird wahrscheinlich zu einem epischen Verlust führen.

Darüber hinaus beeinträchtigt der globale Status die Lesbarkeit Ihres Codes. Wenn Ihr Code eine externe Abhängigkeit hat, die nicht explizit in den Code aufgenommen wurde, muss jeder, der die Pflege Ihres Codes übernimmt, danach suchen, um herauszufinden, woher er stammt.

Was Alternativen angeht, so ist es unmöglich, überhaupt keinen globalen Status zu haben, aber in der Praxis ist es normalerweise möglich, den globalen Status auf ein einzelnes Objekt zu beschränken, das alle anderen Objekte einschließt und auf das niemals unter Berufung auf die Gültigkeitsregeln verwiesen werden darf der Sprache, die Sie verwenden. Wenn ein bestimmtes Objekt einen bestimmten Zustand benötigt, sollte es explizit danach fragen, indem es als Argument an seinen Konstruktor oder durch eine Setter-Methode übergeben wird. Dies wird als Abhängigkeitsinjektion bezeichnet.

Es mag albern erscheinen, einen Status zu übergeben, auf den Sie aufgrund der Gültigkeitsregeln der von Ihnen verwendeten Sprache bereits zugreifen können, aber die Vorteile sind enorm. Wenn jemand den Code isoliert betrachtet, ist klar, welchen Status er benötigt und woher er kommt. Es hat auch enorme Vorteile in Bezug auf die Flexibilität Ihres Codemoduls und damit die Möglichkeit, es in verschiedenen Kontexten wiederzuverwenden. Wenn der Status übergeben wird und Änderungen am Status für den Codeblock lokal sind, können Sie einen beliebigen Status übergeben (sofern es sich um den richtigen Datentyp handelt) und den Code von Ihrem Code verarbeiten lassen. Code, der in diesem Stil geschrieben wurde, sieht in der Regel wie eine Sammlung lose zugeordneter Komponenten aus, die leicht ausgetauscht werden können. Dem Code eines Moduls sollte es egal sein, woher der Status kommt, nur wie er verarbeitet wird.

Es gibt viele andere Gründe, warum die Weitergabe des Staates der Nutzung des globalen Staates weit überlegen ist. Diese Antwort ist keineswegs umfassend. Sie könnten wahrscheinlich ein ganzes Buch darüber schreiben, warum der globale Zustand schlecht ist.


61
Da jeder den Status ändern kann, können Sie sich grundsätzlich nicht darauf verlassen.
Oded

21
@Truth Die Alternative? Abhängigkeitsinjektion .
Rdlowrey

12
Ihr Hauptargument trifft nicht wirklich auf etwas zu, das effektiv schreibgeschützt ist, z. B. ein Objekt, das eine Konfiguration darstellt.
Frankc

53
Nichts davon erklärt, warum schreibgeschützte globale Variablen schlecht sind ... worüber das OP explizit gefragt hat. Es ist eine ansonsten gute Antwort, ich bin nur überrascht, dass das OP es als "akzeptiert" markiert hat, als er ausdrücklich einen anderen Punkt ansprach.
Konrad Rudolph

20
being able to unit test code is a major step in the process of proving its correctness (or at least fitness for purpose). Nein, ist es nicht. "Es ist nun zwei Jahrzehnte her, seit darauf hingewiesen wurde, dass Programmtests das Vorhandensein von Fehlern überzeugend nachweisen können, jedoch niemals deren Abwesenheit. Nachdem der Softwareentwickler diese gut publizierte Bemerkung mit Nachdruck zitiert hat, kehrt er zur Tagesordnung zurück und fährt fort seine Teststrategien zu verfeinern, genau wie der Alchemist von damals, der seine chrysokosmischen Reinigungen weiter verfeinerte. " - Djikstra, 1988. (Das sind jetzt 4,5 Jahrzehnte ...)
Mason Wheeler

135

Veränderlicher globaler Staat ist aus vielen Gründen böse:

  • Bugs aus veränderlichen globalen Zuständen - Viele knifflige Bugs werden durch die Veränderlichkeit verursacht. Fehler, die durch Mutationen von einer beliebigen Stelle im Programm verursacht werden können, sind noch schwerwiegender, da es oft schwierig ist, die genaue Ursache zu ermitteln
  • Schlechte Testbarkeit - Wenn Sie einen veränderlichen globalen Status haben, müssen Sie ihn für alle Tests konfigurieren, die Sie schreiben. Dies erschwert das Testen (und Menschen, die Menschen sind, tun dies mit geringerer Wahrscheinlichkeit!). Was ist beispielsweise bei anwendungsweiten Datenbankanmeldeinformationen, wenn ein Test auf eine bestimmte Testdatenbank zugreifen muss, die sich von allen anderen unterscheidet?
  • Inflexibilität - Was ist, wenn ein Teil des Codes einen Wert im globalen Status erfordert, ein anderer Teil jedoch einen anderen Wert (z. B. einen temporären Wert während einer Transaktion)? Sie haben plötzlich ein böses Stück Refactoring in der Hand
  • Funktionsverunreinigung - "reine" Funktionen (dh Funktionen, bei denen das Ergebnis nur von den Eingabeparametern abhängt und keine Nebenwirkungen hat) sind viel einfacher zu überlegen und zu komponieren, um größere Programme zu erstellen. Funktionen, die den veränderlichen globalen Zustand lesen oder manipulieren, sind von Natur aus unrein.
  • Codeverständnis - Codeverhalten, das von vielen veränderlichen globalen Variablen abhängt, ist viel schwieriger zu verstehen - Sie müssen den Bereich möglicher Interaktionen mit der globalen Variablen verstehen, bevor Sie über das Verhalten des Codes nachdenken können. In einigen Situationen kann dieses Problem unlösbar werden.
  • Parallelitätsprobleme - Der veränderbare globale Status erfordert normalerweise eine Art Sperrung, wenn er in einer gleichzeitigen Situation verwendet wird. Dies ist sehr schwer zu beheben (ist eine Ursache von Fehlern) und erhöht die Komplexität Ihres Codes erheblich (schwer / teuer zu warten).
  • Leistung - Mehrere Threads, die sich ständig auf denselben globalen Status stützen, verursachen Cache-Konflikte und verlangsamen Ihr System insgesamt.

Alternativen zum veränderlichen globalen Staat:

  • Funktionsparameter - oft übersehen, aber eine bessere Parametrisierung Ihrer Funktionen ist oft der beste Weg, um einen globalen Zustand zu vermeiden. Es zwingt Sie, die wichtige konzeptionelle Frage zu lösen: Welche Informationen benötigt diese Funktion, um ihre Aufgabe zu erfüllen? Manchmal ist es sinnvoll, eine Datenstruktur mit dem Namen "Kontext" zu haben, die über eine Funktionskette weitergegeben werden kann, in der alle relevanten Informationen zusammengefasst sind.
  • Abhängigkeitsinjektion - wie bei Funktionsparametern, nur etwas früher ausgeführt (bei der Objektkonstruktion und nicht beim Funktionsaufruf). Seien Sie vorsichtig, wenn es sich bei Ihren Abhängigkeiten um veränderbare Objekte handelt. Dies kann schnell dieselben Probleme verursachen wie der veränderbare globale Status.
  • Unveränderlicher globaler Zustand ist meist harmlos - es ist praktisch eine Konstante. Aber stellen Sie sicher, dass es sich wirklich um eine Konstante handelt und dass Sie nicht versucht sind, sie zu einem späteren Zeitpunkt in einen veränderlichen globalen Zustand zu verwandeln!
  • Unveränderliche Singletons - so ziemlich das Gleiche wie ein unveränderlicher globaler Zustand, außer dass Sie die Instanziierung verschieben können, bis sie benötigt werden. Nützlich für z. B. große feste Datenstrukturen, die eine teure einmalige Vorberechnung erfordern. Mutable Singletons sind natürlich gleichbedeutend mit mutable global state und daher böse :-)
  • Dynamische Bindung - nur in einigen Sprachen wie Common Lisp / Clojure verfügbar. Auf diese Weise können Sie jedoch effektiv einen Wert innerhalb eines kontrollierten Bereichs binden (normalerweise auf Thread-lokaler Basis), der andere Threads nicht beeinflusst. In gewissem Maße ist dies eine "sichere" Methode, um den gleichen Effekt wie eine globale Variable zu erzielen, da Sie wissen, dass nur der aktuelle Ausführungsthread betroffen ist. Dies ist besonders nützlich, wenn Sie beispielsweise mehrere Threads haben, die jeweils unabhängige Transaktionen verarbeiten.

11
Ich denke, dass die Übergabe eines Kontextobjekts entweder als Funktionsparameter oder als Abhängigkeitsinjektion Probleme verursachen würde, wenn der Kontext veränderlich ist, dasselbe Problem wie die Verwendung eines veränderlichen globalen Zustands.
Alfredo Osorio

1
Alles gute Zeug und Amen! Aber die Frage ist über unveränderlichen globalen Zustand
MarkJ

@ Alfredo - sehr wahr. Obwohl es nicht so schlimm ist, da zumindest der Umfang etwas kontrolliert werden kann. Im Allgemeinen ist es jedoch einfacher, das Problem zu lösen, indem Kontexte und Abhängigkeiten unveränderlich gemacht werden.
mikera

2
+1 für veränderlich / unveränderlich. Unveränderliche Globals sind in Ordnung. Sogar diejenigen, die faul geladen sind, sich aber nie ändern. Natürlich sollten Sie keine globalen Variablen verfügbar machen, sondern eine globale Schnittstelle oder API.
Jess

1
@giorgio Die Frage macht deutlich, dass die fraglichen Variablen ihre Werte beim Start erhalten und sich während der Programmausführung nie mehr ändern (Systemordner, Datenbankanmeldeinformationen). Dh unveränderlich, es ändert sich nicht, sobald ihm ein Wert zugewiesen wurde. Ich persönlich verwende auch das Wort "state", weil es von Ausführung zu Ausführung unterschiedlich sein kann oder auf einer anderen Maschine. Es kann bessere Wörter geben.
MarkJ

62
  1. Da Ihre ganze verdammte App es verwenden kann, ist es immer unglaublich schwierig, sie wieder zu entfernen. Wenn Sie jemals Änderungen an Ihrem globalen Code vornehmen, müssen Sie den gesamten Code ändern. Dies ist ein Wartungsproblem, das weit mehr ist, als nur grepherauszufinden, welche Funktionen den Typnamen verwenden.
  2. Sie sind schlecht, weil sie versteckte Abhängigkeiten einführen, die das Multithreading unterbrechen, was für immer mehr Anwendungen von entscheidender Bedeutung ist.
  3. Der Status der globalen Variablen ist immer völlig unzuverlässig, da Ihr gesamter Code möglicherweise irgendetwas damit zu tun hat.
  4. Sie sind wirklich schwer zu testen.
  5. Sie erschweren den Aufruf der API. "Sie müssen daran denken, SET_MAGIC_VARIABLE () aufzurufen, bevor Sie API aufrufen" bittet nur darum, dass jemand vergisst, es aufzurufen. Die Verwendung der API ist fehleranfällig und führt zu schwer auffindbaren Fehlern. Indem Sie ihn als regulären Parameter verwenden, erzwingen Sie, dass der Aufrufer einen Wert ordnungsgemäß bereitstellt.

Übergeben Sie einfach eine Referenz an Funktionen, die sie benötigen. Es ist nicht so schwer.


3
Nun, Sie können eine globale Konfigurationsklasse haben, die das Sperren kapselt und so konzipiert ist, dass sie den Status zu jeder Zeit ändert. Ich würde diesen Ansatz dem Instantiieren von Konfigurationslesern von 1000x Stellen im Code vorziehen. Aber ja, Unvorhersehbarkeit ist das absolut Schlimmste an ihnen.
Coder

6
@Coder: Beachten Sie, dass die vernünftige Alternative zu einem globalen nicht "Konfigurationsleser von 1000x Stellen im Code" ist, sondern ein Konfigurationsleser, der ein Konfigurationsobjekt erstellt, das von den Methoden als Parameter akzeptiert werden kann (-> Dependency Injection).
sleske

2
Nitpicking: Warum ist es einfacher, nach einem Typ zu suchen als nach einem globalen? Und die Frage bezieht sich auf schreibgeschützte globale Zeichen, sodass die Punkte 2 und 3 nicht relevant sind
MarkJ

2
@MarkJ: Ich hoffe, dass noch nie jemand den Namen beschattet hat.
DeadMG

2
@DeadMG, Re "..ist immer völlig unzuverlässig ..", das gleiche gilt für die Abhängigkeitsinjektion. Nur weil Sie es zu einem Parameter gemacht haben, heißt das noch lange nicht, dass das Objekt garantiert einen festen Zustand hat. Irgendwo in der Funktion können Sie eine andere Funktion aufrufen, die den Status der injizierten Variablen oben ändert.
Pacerier

34

Wenn Sie "Zustand" sagen, bedeutet dies normalerweise "veränderlicher Zustand". Und ein globaler veränderlicher Zustand ist völlig böse, weil dies bedeutet, dass jeder Teil des Programms jeden anderen Teil beeinflussen kann (indem er den globalen Zustand ändert).

Stellen Sie sich das Debuggen eines unbekannten Programms vor: Sie finden, dass sich die Funktion A für bestimmte Eingabeparameter auf eine bestimmte Weise verhält, aber manchmal für dieselben Parameter anders funktioniert. Sie stellen fest, dass die globale Variable x verwendet wird .

Sie suchen nach Stellen, die x ändern , und stellen fest, dass es fünf Stellen gibt, die x ändern. Nun viel Glück beim Herausfinden, in welchen Fällen Funktion A was tut ...


So unveränderlicher globaler Staat ist nicht so schlecht?
FrustratedWithFormsDesigner

50
Ich würde sagen, dass unveränderlicher globaler Zustand die bekannte gute Praxis ist, die als "Konstanten" bekannt ist.
Telastyn

6
Unveränderlicher globaler Zustand ist nicht böse, es ist nur schlecht :-). Es ist immer noch problematisch, da es die Kopplung einführt (Änderungen, Wiederverwendung und Komponententests erschwert), aber es verursacht viel weniger Probleme, so dass es in einfachen Fällen normalerweise akzeptabel ist.
sleske

2
Wenn man globale Variablen verwendet, sollte nur ein Teil des Codes diese ändern. Der Rest ist frei, es zu lesen. Die Probleme anderer, die es ändern, verschwinden nicht mit Kapselungs- und Zugriffsfunktionen. Es ist nicht das, wofür diese Konstrukte sind.
Phkahler

1
@Pacerier: Ja, das Ändern einer häufig verwendeten Schnittstelle ist schwierig, unabhängig davon, ob sie als globale oder lokale Variable verwendet wird. Das ist jedoch unabhängig von meinem Standpunkt, nämlich dass die Interaktion verschiedener Codeteile mit globalen Variablen schwer zu verstehen ist.
sleske

9

Sie haben sozusagen Ihre eigene Frage beantwortet. Sie sind schwierig zu handhaben, wenn sie "missbraucht" werden, können aber nützlich und [etwas] vorhersehbar sein, wenn sie richtig verwendet werden, von jemandem, der weiß, wie man sie unterdrückt. Wartung und Änderungen an / auf den Globals sind in der Regel ein Albtraum, der sich mit zunehmender Größe der Anwendung verschlimmert.

Erfahrene Programmierer, die den Unterschied zwischen Globals als einzige Option und der einfachen Lösung erkennen können, haben möglicherweise nur minimale Probleme bei der Verwendung. Die endlosen möglichen Probleme, die bei ihrer Verwendung auftreten können, erfordern jedoch den Rat, sie nicht zu verwenden.

Bearbeiten: Um zu verdeutlichen, was ich meine, sind Globals von Natur aus nicht vorhersehbar. Wie bei allem Unvorhersehbaren können Sie Schritte unternehmen, um die Unvorhersehbarkeit einzudämmen, aber es gibt immer Grenzen, was getan werden kann. Hinzu kommt der Ärger neuer Entwickler, die sich dem Projekt anschließen und sich mit relativ unbekannten Variablen auseinandersetzen müssen. Die Empfehlungen gegen die Verwendung globaler Variablen sollten verständlich sein.


9

Es gibt viele Probleme mit Singletons - hier sind die zwei größten Probleme in meinem Kopf.

  • Dies macht das Testen von Einheiten problematisch. Der globale Zustand kann von einem Test zum nächsten kontaminiert werden

  • Es erzwingt die "One-and-only-one" -Hard-Regel, die sich zwar unmöglich ändern kann, aber plötzlich erfüllt. Eine ganze Reihe von Utility-Code, der das global zugängliche Objekt verwendet, muss dann geändert werden.

Allerdings benötigen die meisten Systeme Big Global Objects. Hierbei handelt es sich um umfangreiche und kostenintensive Elemente (z. B. Database Connection Manager) oder um Informationen zum allgegenwärtigen Status (z. B. Sperrinformationen).

Die Alternative zu einem Singleton besteht darin, diese großen globalen Objekte beim Start zu erstellen und als Parameter an alle Klassen oder Methoden zu übergeben, die Zugriff auf dieses Objekt benötigen.

Das Problem hierbei ist, dass Sie am Ende ein großes Spiel "Pass-the-Parcel" haben. Sie haben ein Diagramm der Komponenten und ihrer Abhängigkeiten, und einige Klassen erstellen andere Klassen, und jede muss eine Reihe von Abhängigkeitskomponenten enthalten, nur weil ihre gespawnten Komponenten (oder die Komponenten der gespawnten Komponenten) sie benötigen.

Sie haben neue Wartungsprobleme. Ein Beispiel: Plötzlich benötigt Ihre "WidgetFactory" -Komponente tief im Diagramm ein Timer-Objekt, das Sie verspotten möchten. "WidgetFactory" wird jedoch von "WidgetBuilder" erstellt, das Teil von "WidgetCreationManager" ist, und Sie müssen drei Klassen haben, die über dieses Timer-Objekt Bescheid wissen, obwohl nur eine es tatsächlich verwendet. Sie möchten aufgeben und zu Singletons zurückkehren und dieses Timer-Objekt einfach global zugänglich machen.

Glücklicherweise ist dies genau das Problem, das durch ein Dependency Injection-Framework gelöst wird. Sie können dem Framework einfach mitteilen, welche Klassen es erstellen muss, und es verwendet Reflection, um das Abhängigkeitsdiagramm für Sie zu ermitteln, und erstellt jedes Objekt automatisch, wenn sie benötigt werden.

Zusammenfassend sind Singletons also schlecht, und die Alternative ist die Verwendung eines Abhängigkeitsinjektions-Frameworks.

Ich benutze zufällig Castle Windsor, aber Sie haben die Qual der Wahl. Auf dieser Seite aus dem Jahr 2008 finden Sie eine Liste der verfügbaren Frameworks.


8

Zunächst einmal müssten Sie Singletons verwenden, damit die Abhängigkeitsinjektion "stateful" ist. Leute, die sagen, dies sei irgendwie eine Alternative, irren sich. Menschen verwenden ständig globale Kontextobjekte ... Auch der Sitzungsstatus ist im Wesentlichen eine globale Variable. Es ist nicht immer die beste Lösung, alles durch Abhängigkeitsinjektion weiterzugeben. Ich arbeite derzeit an einer sehr großen Anwendung, die viele globale Kontextobjekte verwendet (Singletons, die über einen IoC-Container injiziert werden), und das Debuggen war noch nie ein Problem. Insbesondere bei einer ereignisgesteuerten Architektur kann es vorzuziehen sein, globale Kontextobjekte zu verwenden, anstatt Änderungen weiterzugeben. Kommt darauf an, wen du fragst.

Alles kann missbraucht werden und es hängt auch von der Art der Anwendung ab. Die Verwendung statischer Variablen zum Beispiel in einer Web-App unterscheidet sich grundlegend von einer Desktop-App. Wenn Sie globale Variablen vermeiden können, tun Sie dies, aber manchmal haben sie ihre Verwendung. Stellen Sie zumindest sicher, dass sich Ihre globalen Daten in einem klaren Kontextobjekt befinden. Was das Debuggen betrifft, kann nichts, was ein Aufrufstapel und einige Haltepunkte nicht lösen.

Ich möchte betonen, dass die blinde Verwendung globaler Variablen eine schlechte Idee ist. Funktionen sollten wiederverwendbar sein und es sollte ihnen egal sein, woher die Daten kommen - bei Bezug auf globale Variablen wird die Funktion mit einer bestimmten Dateneingabe gekoppelt. Dies ist der Grund, warum es weitergegeben werden sollte und warum die Abhängigkeitsinjektion hilfreich sein kann, obwohl es sich immer noch um einen zentralisierten Kontextspeicher handelt (über Singletons).

Übrigens ... Einige Leute denken, dass die Abhängigkeitsinjektion schlecht ist, einschließlich des Erstellers von Linq. Letztendlich wird Erfahrung Ihr bester Lehrer sein. Es gibt Zeiten, um Regeln zu befolgen und Zeiten, um sie zu brechen.


4

Da hier einige andere Antworten die Unterscheidung zwischen veränderlichem und unveränderlichem globalem Zustand treffen, möchte ich meinen, dass selbst unveränderliche globale Variablen / Einstellungen oft ärgerlich sind .

Betrachten Sie die Frage:

... Angenommen, ich benötige eine globale Konfiguration für meine Anwendung, z. B. Systemordnerpfade oder anwendungsweite Datenbankanmeldeinformationen. ...

Richtig, für ein kleines Programm ist dies wahrscheinlich kein Problem, aber sobald Sie dies mit Komponenten in einem etwas größeren System tun , wird das Schreiben automatisierter Tests plötzlich schwieriger, da alle Tests (die innerhalb desselben Prozesses ablaufen) funktionieren müssen der gleiche globale Konfigurationswert.

Wenn alle Konfigurationsdaten explizit übergeben werden, lassen sich die Komponenten viel einfacher testen und Sie müssen sich keine Gedanken mehr darüber machen, wie Sie einen globalen Konfigurationswert für mehrere Tests booten, möglicherweise sogar parallel.


3

Zum einen können Sie genau das Problem angehen, das Sie mit Singletons haben. Was heute wie eine "globale Sache aussieht, von der ich nur eine brauche", wird sich plötzlich in etwas verwandeln, von dem Sie später mehr brauchen.

Zum Beispiel erstellen Sie heute dieses globale Konfigurationssystem, weil Sie eine globale Konfiguration für das gesamte System wünschen. Ein paar Jahre später portieren Sie auf ein anderes System und jemand sagt: "Hey, wissen Sie, das könnte besser funktionieren, wenn es eine allgemeine globale Konfiguration und eine plattformspezifische Konfiguration gäbe." Plötzlich haben Sie diese ganze Arbeit, um Ihre globalen Strukturen nicht global zu machen, so dass Sie mehrere haben können.

(Dies ist kein zufälliges Beispiel ... dies geschah mit unserem Konfigurationssystem in dem Projekt, in dem ich mich gerade befinde.)

In Anbetracht der Tatsache, dass die Kosten für die Erstellung eines nicht-globalen Objekts in der Regel gering sind, ist es dumm, dies zu tun. Sie schaffen nur zukünftige Probleme.


Ihr Beispiel ist nicht das, was OP bedeutete. Sie sprechen eindeutig davon, abstrakte Konfigurationen (nur eine Configuration Manager-Datenstruktur, ohne anwendungsspezifische Einstellungsschlüssel) mehrmals zu instanziieren, da Sie alle Schlüssel in einer ausgeführten Instanz Ihres Programms auf mehrere Instanzen Ihrer Configuration Manager-Klasse aufteilen möchten. (Angenommen, jede dieser Instanzen kann beispielsweise eine Konfigurationsdatei verarbeiten.)
Jo So

3

Das andere Problem ist merkwürdigerweise, dass sie die Skalierbarkeit einer Anwendung erschweren, weil sie nicht "global" genug sind. Der Gültigkeitsbereich einer globalen Variablen ist der Prozess.

Wenn Sie Ihre Anwendung mithilfe mehrerer Prozesse oder auf mehreren Servern skalieren möchten, ist dies nicht möglich. Zumindest erst, wenn Sie alle Globalen herausgerechnet und durch einen anderen Mechanismus ersetzt haben.


3

Warum ist der globale Staat so böse?

Ein veränderlicher globaler Zustand ist böse, weil es für unser Gehirn sehr schwierig ist, mehr als ein paar Parameter gleichzeitig zu berücksichtigen und herauszufinden, wie sie sich aus einer Timing-Perspektive und einer Werteperspektive kombinieren, um etwas zu bewirken.

Daher sind wir sehr schlecht darin, ein Objekt zu debuggen oder zu testen, dessen Verhalten mehr als ein paar externe Gründe hat, die während der Ausführung eines Programms geändert werden müssen. Geschweige denn, wenn wir über Dutzende dieser Objekte zusammen denken müssen.


3

Sprachen, die für ein sicheres und robustes Systemdesign entwickelt wurden, entfernen häufig den globalen veränderlichen Status insgesamt. (Vermutlich bedeutet dies, dass es keine globalen Objekte gibt, da unveränderliche Objekte in gewissem Sinne nicht wirklich zustandsbehaftet sind, da sie niemals einen Zustandsübergang haben.)

Joe-E ist ein Beispiel, und David Wagner erklärt die Entscheidung folgendermaßen:

Die Analyse, wer Zugriff auf ein Objekt hat, und das Prinzip der geringsten Berechtigungen werden beide untergraben, wenn Funktionen in globalen Variablen gespeichert werden und daher von jedem Teil des Programms möglicherweise gelesen werden können. Sobald ein Objekt global verfügbar ist, kann der Analyseumfang nicht mehr eingeschränkt werden: Der Zugriff auf das Objekt ist ein Privileg, das keinem Code im Programm vorenthalten werden kann. Joe-E vermeidet diese Probleme, indem sichergestellt wird, dass der globale Bereich keine Funktionen, sondern nur unveränderliche Daten enthält.

Eine Möglichkeit, darüber nachzudenken, ist

  1. Die Programmierung ist ein Problem der verteilten Argumentation. Programmierer großer Projekte müssen das Programm in Teile aufteilen, die von Einzelpersonen begründet werden können.
  2. Je kleiner der Umfang, desto einfacher ist es, darüber nachzudenken. Dies gilt sowohl für Einzelpersonen als auch für statische Analysewerkzeuge, die versuchen, die Eigenschaften eines Systems zu prüfen, und für Tests, die die Eigenschaften eines Systems prüfen müssen.
  3. Aufgrund der weltweit verfügbaren maßgeblichen Autoritätsquellen ist es schwierig, über die Eigenschaften des Systems nachzudenken.

Ein global veränderlicher Zustand erschwert es daher

  1. robuste Systeme entwerfen,
  2. schwerer zu beweisen, Eigenschaften eines Systems, und
  3. Es ist schwieriger, sicherzustellen, dass Ihre Tests in einem ähnlichen Umfang wie Ihre Produktionsumgebung durchgeführt werden.

Der globale veränderbare Zustand ähnelt der DLL-Hölle . Im Laufe der Zeit erfordern verschiedene Teile eines großen Systems ein subtil anderes Verhalten als gemeinsame Teile eines veränderlichen Zustands. Das Lösen von Inkonsistenzen zwischen DLL-Höllen und gemeinsam genutzten veränderlichen Zuständen erfordert eine umfassende Koordination zwischen verschiedenen Teams. Diese Probleme würden nicht auftreten, wenn der globale Staat von Anfang an richtig eingeschätzt worden wäre.


2

Globals sind nicht so schlimm. Wie in mehreren anderen Antworten angegeben, besteht das eigentliche Problem darin, dass Ihr globaler Ordnerpfad heute morgen einer von mehreren oder sogar Hunderten sein kann. Wenn Sie ein schnelles, einmaliges Programm schreiben, verwenden Sie Globals, wenn es einfacher ist. Im Allgemeinen ist es jedoch der richtige Weg, ein Vielfaches zuzulassen, auch wenn Sie nur glauben, dass Sie eines brauchen. Es ist nicht angenehm, ein großes komplexes Programm neu strukturieren zu müssen, das plötzlich mit zwei Datenbanken kommunizieren muss .

Aber sie schaden nicht der Zuverlässigkeit. Alle Daten, auf die von vielen Stellen in Ihrem Programm verwiesen wird, können Probleme verursachen, wenn sie sich unerwartet ändern. Enumeratoren ersticken, wenn die Auflistung, die sie auflisten, in der Mitte der Aufzählung geändert wird. Event Queue Events können sich gegenseitig Streiche spielen. Threads können immer Chaos anrichten. Alles, was keine lokale Variable oder kein unveränderliches Feld ist, ist ein Problem. Globals sind solche Probleme, aber Sie werden das nicht beheben, indem Sie sie nicht global machen.

Wenn Sie in eine Datei schreiben möchten und sich der Ordnerpfad ändert, müssen die Änderungen und der Schreibvorgang synchronisiert werden. (Als eine von tausend möglichen Fehlern können Sie sagen, Sie greifen auf den Pfad zu, dann wird dieses Verzeichnis gelöscht, dann wird der Ordnerpfad in ein gutes Verzeichnis geändert, und Sie versuchen, in das gelöschte Verzeichnis zu schreiben.) Das Problem besteht darin, ob Der Ordnerpfad ist global oder einer von Tausenden, die das Programm derzeit verwendet.

Es gibt ein echtes Problem mit Feldern, auf die von verschiedenen Ereignissen in einer Warteschlange, verschiedenen Rekursionsebenen oder verschiedenen Threads zugegriffen werden kann. Einfach ausgedrückt: Lokale Variablen sind gut und Felder sind schlecht. Ehemalige globale Felder bleiben jedoch weiterhin Felder, sodass dieses (jedoch äußerst wichtige) Problem nicht für den Status "Gut" oder "Böse" globaler Felder gilt.

Zusatz: Multithreading-Probleme:

(Beachten Sie, dass bei einer Ereigniswarteschlange oder bei rekursiven Aufrufen ähnliche Probleme auftreten können, Multithreading jedoch bei weitem das schlechteste ist.) Betrachten Sie den folgenden Code:

if (filePath != null)  text = filePath.getName();

Wenn filePathes sich um eine lokale Variable oder eine Konstante handelt, wird Ihr Programm bei der Ausführung nicht fehlschlagen, da filePathnull ist. Die Prüfung funktioniert immer. Kein anderer Thread kann seinen Wert ändern. Ansonsten gibt es keine Garantien. Als ich anfing, Multithread-Programme in Java zu schreiben, bekam ich die ganze Zeit NullPointerExceptions in solchen Zeilen. IrgendeinAndere Threads können den Wert jederzeit ändern, und dies ist häufig der Fall. Wie mehrere andere Antworten zeigen, führt dies zu ernsthaften Problemen beim Testen. Die obige Aussage kann milliardenfach funktionieren, indem sie umfangreiche und umfassende Tests durchläuft und dann einmal in der Produktion in die Luft gejagt wird. Die Benutzer werden das Problem nicht reproduzieren können und es wird nicht wieder vorkommen, bis sie sich davon überzeugt haben, dass sie Dinge gesehen und vergessen haben.

Globals haben definitiv dieses Problem, und wenn Sie sie vollständig beseitigen oder durch Konstanten oder lokale Variablen ersetzen können, ist das eine sehr gute Sache. Wenn auf einem Webserver statusfreier Code ausgeführt wird, ist dies wahrscheinlich möglich. In der Regel können alle Ihre Multithreading-Probleme von der Datenbank übernommen werden.

Wenn sich Ihr Programm jedoch die Daten von einer Benutzeraktion zur nächsten merken muss, haben Sie Felder, auf die alle laufenden Threads zugreifen können. Ein globales Feld in ein solches nicht-globales Feld umzuwandeln, trägt nicht zur Zuverlässigkeit bei.


1
Können Sie klarstellen, was Sie damit meinen ?: "Alles, was keine lokale Variable oder ein unveränderliches Feld ist, ist ein Problem. Globale Variablen sind solche Probleme, aber Sie werden das nicht beheben, indem Sie sie nicht global machen."
Andres F.

1
@AndresF .: Ich habe meine Antwort erweitert. Ich glaube, ich verfolge einen Desktop-Ansatz, bei dem die meisten Leute auf dieser Seite mehr Server-Code-mit-einer-Datenbank sind. "Global" kann in diesen Fällen unterschiedliche Bedeutungen haben.
Ralph Chapin

2

Zustand ist in jeder realen Anwendung unvermeidlich. Sie können es nach Belieben zusammenfassen, eine Tabelle muss jedoch Daten in Zellen enthalten. Sie können Zellobjekte nur mit Funktionen als Schnittstelle erstellen, aber das schränkt nicht ein, an wie vielen Stellen eine Methode in der Zelle aufgerufen und die Daten geändert werden können. Sie erstellen ganze Objekthierarchien, um zu versuchen, Schnittstellen auszublenden, damit andere Teile des Codes dies nicht könnenStandardmäßig ändern Sie die Daten. Dies verhindert nicht, dass ein Verweis auf das enthaltene Objekt willkürlich weitergegeben wird. All dies beseitigt auch nicht die Probleme der Parallelität von selbst. Es erschwert zwar die Verbreitung des Zugriffs auf Daten, beseitigt jedoch nicht die wahrgenommenen Probleme mit globalen Unternehmen. Wenn jemand einen Status ändern möchte, wird er dies tun, egal ob global oder über eine komplexe API (letztere wird nur entmutigen, nicht verhindern).

Der wahre Grund, keinen globalen Speicher zu verwenden, ist die Vermeidung von Namenskollisionen. Wenn Sie mehrere Module laden, die denselben globalen Namen deklarieren, haben Sie entweder undefiniertes Verhalten (sehr schwer zu debuggen, da Komponententests bestanden werden) oder einen Linkerfehler (ich denke C - Warnt Ihr Linker oder schlägt er fehl?).

Wenn Sie Code wiederverwenden möchten, müssen Sie in der Lage sein, ein Modul von einem anderen Ort aus abzurufen, und müssen es nicht versehentlich auf Ihrem globalen Server ablegen, da dort ein Modul mit demselben Namen verwendet wird. Wenn Sie Glück haben und eine Fehlermeldung erhalten, müssen Sie nicht alle Verweise in einem Codeabschnitt ändern, um die Kollision zu verhindern.


1

Wenn es einfach ist, den gesamten globalen Zustand zu sehen und darauf zuzugreifen, werden Programmierer dies immer tun. Was Sie bekommen, ist unausgesprochen und es ist schwierig, Abhängigkeiten zu verfolgen (int blahblah bedeutet, dass array foo in jedem Fall gültig ist). Im Grunde genommen ist es so gut wie unmöglich, Programminvarianten zu pflegen, da alles unabhängig voneinander geändert werden kann. someInt hat eine Beziehung zwischen otherInt, die schwierig zu verwalten und zu beweisen ist, wenn Sie beide jederzeit direkt ändern können.

Das heißt, es kann getan werden (vor langer Zeit, als es in manchen Systemen der einzige Weg war), aber diese Fähigkeiten gehen verloren. Sie drehen sich hauptsächlich um Kodierungs- und Namenskonventionen - das Feld hat sich aus gutem Grund weiterentwickelt. Ihr Compiler und Linker können Invarianten in geschützten / privaten Daten von Klassen / Modulen besser überprüfen, als sich darauf zu verlassen, dass Menschen einem Masterplan folgen und die Quelle lesen.


1
"aber diese Fähigkeiten sind verloren" ... noch nicht ganz. Ich habe vor kurzem bei einem Softwarehaus gearbeitet, das auf "Clarion" schwört, ein Codegenerator-Tool mit einer eigenen Sprache, der es an Funktionen mangelt, z. B. Argumente an Unterprogramme zu übergeben "Veränderung" oder "Modernisierung", hatte endlich die Nase voll von meinen Äußerungen und stellte mich als mangelhaft und inkompetent dar. Ich musste gehen ...
Louis Somers

1

Ich werde nicht sagen, ob globale Variablen entweder gut oder schlecht sind, aber ich werde der Diskussion hinzufügen, dass Sie wahrscheinlich viel Speicher verschwenden, wenn Sie keinen globalen Status verwenden. Besonders, wenn Sie classess verwenden, um ihre Abhängigkeiten in Feldern zu speichern.

Für den globalen Staat gibt es kein solches Problem, alles ist global.

Beispiel: Stellen Sie sich folgendes Szenario vor: Sie haben ein 10x10-Raster, das aus den Klassen "Board" und "Tile" besteht.

Wenn Sie es auf die OOP-Weise tun möchten, werden Sie wahrscheinlich das "Board" -Objekt an jedes "Tile" übergeben. Angenommen, "Tile" enthält 2 Felder vom Typ "Byte", in denen die Koordinate gespeichert ist. Der Gesamtspeicher, der auf einer 32-Bit-Maschine für eine Kachel benötigt wird, beträgt (1 + 1 + 4 = 6) Bytes: 1 für x-Koordinate, 1 für y-Koordinate und 4 für einen Zeiger auf die Karte. Dies ergibt insgesamt 600 Bytes für die Einrichtung von 10 x 10 Kacheln

Für den Fall, dass sich die Karte im globalen Bereich befindet, müsste für ein einzelnes Objekt, auf das von jeder Kachel aus zugegriffen werden kann, nur 2 Byte Speicher pro Kachel abgerufen werden. Dies sind die x- und y-Koordinatenbytes. Dies würde nur 200 Bytes ergeben.

In diesem Fall erhalten Sie also 1/3 des Speicherbedarfs, wenn Sie nur den globalen Status verwenden.

Dies ist, neben anderen Dingen, vermutlich ein Grund, warum der globale Geltungsbereich immer noch in (relativ) einfachen Sprachen wie C ++ besteht


1
Das hängt stark von der Sprache ab. In Sprachen, in denen Programme dauerhaft ausgeführt werden, kann es sein, dass Sie Speicherplatz sparen können, indem Sie globalen Speicherplatz und Singletons verwenden, anstatt viele Klassen zu instanziieren, aber selbst dieses Argument ist unsicher. Es geht um Prioritäten. In Sprachen wie PHP (die einmal pro Anfrage ausgeführt werden und Objekte nicht beibehalten werden) ist auch dieses Argument umstritten.
Madara Uchiha

1
@ MadaraUchiha Nein, dieses Argument ist in keiner Weise wackelig. Das sind objektive Fakten, es sei denn, eine VM oder eine andere kompilierte Sprache führt eine Hardcore-Code-Optimierung mit dieser Art von Problem durch. Ansonsten ist es immer noch relevant. Selbst bei serverseitigen Programmen, bei denen es sich um "One-Shot" -Programme handelt, bleibt der Speicher bis zur Ausführung reserviert. Bei Servern mit hoher Auslastung kann dies ein kritischer Punkt sein.
Luke1985

0

Beim globalen Status sind mehrere Faktoren zu berücksichtigen:

  1. Speicherplatz für Programmierer
  2. Unveränderliche Globals / schreibe einmal Globals.
  3. Veränderbare Globale
  4. Abhängigkeit von Globalen.

Je mehr Globals du hast, desto größer ist die Chance, Duplikate einzuführen und damit Dinge zu beschädigen, wenn Duplikate nicht mehr synchron sind. Es ist sowohl notwendig als auch schmerzhaft, alle Globalen in deinem falschen menschlichen Gedächtnis zu behalten.

Immutables / write once sind im Allgemeinen in Ordnung, achten Sie jedoch auf Fehler in der Initialisierungssequenz.

Mutable Globals werden oft mit unveränderlichen Globals verwechselt ...

Eine Funktion, die Globals effektiv verwendet, verfügt über zusätzliche "versteckte" Parameter, die das Refactoring erschweren.

Der globale Staat ist nicht böse, aber er hat einen bestimmten Preis - nutzen Sie ihn, wenn der Nutzen die Kosten überwiegt.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.