Dlaczego państwo globalne jest tak złe?

328

Zanim zaczniemy, pozwól mi powiedzieć, że jestem w pełni świadomy koncepcji abstrakcji i wstrzykiwania zależności. Nie potrzebuję tutaj otwierać oczu.

Cóż, większość z nas mówi (zbyt) wiele razy, naprawdę nie rozumiejąc: „Nie używaj zmiennych globalnych” lub „Singletony są złe, ponieważ są globalne”. Ale co tak naprawdę jest złego w złowieszczym globalnym państwie?

Powiedzmy, że potrzebuję globalnej konfiguracji dla mojej aplikacji, na przykład ścieżek folderów systemowych lub poświadczeń bazy danych dla całej aplikacji.

W takim przypadku nie widzę żadnego dobrego rozwiązania poza zapewnieniem tych ustawień w jakiejś globalnej przestrzeni, która będzie powszechnie dostępna dla całej aplikacji.

Wiem, że nadużywanie go jest złe , ale czy globalna przestrzeń naprawdę jest TYM złem? A jeśli tak, jakie są dobre alternatywy?

Madara Uchiha
źródło
5
możesz użyć klasy config, która obsługuje dostęp do tych ustawień. Myślę, że dobrą praktyką jest posiadanie jednego i tylko jednego miejsca, w którym ustawienia konfiguracji są odczytywane z plików konfiguracyjnych i / lub baz danych, podczas gdy inne obiekty pobierają je z tej konfiguracji. sprawia, że ​​Twój projekt jest bardziej czysty i przewidywalny.
Hajo,
25
Jaka jest różnica w korzystaniu z globalnego? Twoje „jedno i tylko jedno miejsce” brzmi bardzo podejrzanie jak Singleton.
Madara Uchiha,
130
Jedynym prawdziwym złem są dogmaty.
Pieter B,
34
Używanie globalnego stanu z innymi programistami jest jak używanie tej samej szczoteczki do zębów ze znajomymi - możesz, ale nigdy nie wiadomo, kiedy ktoś zdecyduje się wrzucić go w tyłek.
ziGi
5
Diabeł jest zły. Państwo globalne nie jest ani złe, ani dobre. Może być bardzo przydatny lub szkodliwy w zależności od sposobu jego użycia. Nie słuchaj innych i po prostu naucz się, jak prawidłowo programować.
annoying_squid

Odpowiedzi:

282

Krótko mówiąc, sprawia, że ​​stan programu jest nieprzewidywalny.

Aby to rozwinąć, wyobraź sobie, że masz kilka obiektów, które wykorzystują tę samą zmienną globalną. Zakładając, że nie używasz źródła losowości w żadnym z modułów, to wynik konkretnej metody można przewidzieć (a zatem przetestować), jeśli stan systemu jest znany przed wykonaniem metody.

Jeśli jednak metoda w jednym z obiektów wyzwala efekt uboczny, który zmienia wartość wspólnego stanu globalnego, wówczas nie wiesz już, jaki jest stan początkowy, gdy wykonasz metodę w drugim obiekcie. Nie możesz już przewidywać, jakie wyniki uzyskasz po wykonaniu metody, a zatem nie możesz jej przetestować.

Na poziomie akademickim może to nie wydawać się aż tak poważne, ale możliwość testowania kodu jednostkowego jest ważnym krokiem w procesie dowodzenia jego poprawności (lub przynajmniej przydatności do określonego celu).

W prawdziwym świecie może to mieć bardzo poważne konsekwencje. Załóżmy, że masz jedną klasę wypełniającą globalną strukturę danych, a inną klasę, która zużywa dane w tej strukturze danych, zmieniając jej stan lub niszcząc je w trakcie procesu. Jeśli klasa procesora wykona metodę przed wykonaniem klasy populator, wynik będzie taki, że klasa procesorów prawdopodobnie będzie miała niekompletne dane do przetworzenia, a struktura danych, nad którą pracowała klasa populator, może zostać uszkodzona lub zniszczona. Zachowanie programu w takich okolicznościach staje się całkowicie nieprzewidywalne i prawdopodobnie doprowadzi do epickiej straty.

Ponadto stan globalny szkodzi czytelności twojego kodu. Jeśli Twój kod ma zewnętrzną zależność, która nie jest wyraźnie wprowadzona do kodu, to każdy, kto dostanie zadanie utrzymania kodu, będzie musiał go poszukać, aby dowiedzieć się, skąd on pochodzi.

Jeśli chodzi o istniejące alternatywy, to nie ma żadnego stanu globalnego, ale w praktyce zwykle można ograniczyć stan globalny do jednego obiektu, który otacza wszystkie pozostałe i do którego nigdy nie można się odwoływać, opierając się na regułach określania zakresu języka, którego używasz. Jeśli dany obiekt potrzebuje określonego stanu, powinien jawnie o to poprosić, przekazując go jako argument do swojego konstruktora lub metody ustawiającej. Jest to znane jako wstrzykiwanie zależności.

Może wydawać się głupie przejście w stan, do którego masz już dostęp ze względu na zasady określania zakresu w jakimkolwiek języku, którego używasz, ale zalety są ogromne. Teraz, gdy ktoś patrzy na kod w izolacji, jasne jest, jakiego stanu potrzebuje i skąd pochodzi. Ma również ogromne zalety w zakresie elastyczności modułu kodu, a tym samym możliwości ponownego użycia go w różnych kontekstach. Jeśli stan jest przekazywany, a zmiany stanu są lokalne dla bloku kodu, możesz przekazać dowolny stan (jeśli jest to poprawny typ danych) i zlecić jego przetworzenie. Kod napisany w tym stylu ma wygląd kolekcji luźno powiązanych komponentów, które można łatwo zamieniać. Kod modułu nie powinien dbać o to, skąd pochodzi stan, tylko jak go przetworzyć.

Istnieje wiele innych powodów, dla których przekazywanie stanu znacznie przewyższa poleganie na stanie globalnym. Ta odpowiedź w żadnym wypadku nie jest wyczerpująca. Prawdopodobnie mógłbyś napisać całą książkę o tym, dlaczego stan globalny jest zły.

GordonM
źródło
61
Zasadniczo, ponieważ każdy może zmienić stan, nie można na nim polegać.
Oded
21
@Truth Alternatywa? Wstrzykiwanie zależności .
rdlowrey
12
Twój główny argument tak naprawdę nie ma zastosowania do czegoś, co faktycznie jest tylko do odczytu, takiego jak obiekt reprezentujący konfigurację.
frankc
53
Nic z tego nie wyjaśnia, dlaczego zmienne globalne tylko do odczytu są złe… o co wyraźnie pytał PO. To skądinąd dobra odpowiedź, jestem po prostu zaskoczony, że PO oznaczył ją jako „zaakceptowaną”, kiedy wyraźnie odniósł się do innej kwestii.
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). Nie, nie jest. „Minęły już dwie dekady, odkąd zauważono, że testy programów mogą w przekonujący sposób wykazać obecność błędów, ale nigdy nie mogą wykazać ich braku. Po zacytowaniu tej dobrze nagłośnionej uwagi inżynier oprogramowania wraca do porządku dnia i kontynuuje dopracować swoje strategie testowe, tak jak dawny alchemik, który nadal udoskonalał swoje chryzokosmiczne oczyszczenia ”. - Djikstra, 1988. (To sprawia, że ​​teraz 4,5 dekady ...)
Mason Wheeler
135

Zmienne państwo globalne jest złe z wielu powodów:

  • Błędy ze zmiennego stanu globalnego - wiele trudnych błędów jest spowodowanych zmiennością. Błędy, które mogą być spowodowane mutacją z dowolnego miejsca w programie, są jeszcze bardziej skomplikowane, ponieważ często trudno jest znaleźć dokładną przyczynę
  • Słaba testowalność - jeśli masz zmienny stan globalny, musisz go skonfigurować dla wszystkich pisanych testów. To sprawia, że ​​testowanie jest trudniejsze (dlatego ludzie będący ludźmi są mniej skłonni do tego!). np. w przypadku poświadczeń bazy danych dla całej aplikacji, co jeśli jeden test musi uzyskać dostęp do konkretnej testowej bazy danych innej niż wszystko inne?
  • Brak elastyczności - co jeśli jedna część kodu wymaga jednej wartości w stanie globalnym, ale inna część wymaga innej wartości (np. Wartości tymczasowej podczas transakcji)? Nagle masz trochę nieprzyjemnego refaktoryzacji na rękach
  • Nieczystość funkcji - funkcje „czyste” (tj. Te, których wynik zależy tylko od parametrów wejściowych i nie wywołują żadnych skutków ubocznych) są znacznie łatwiejsze do przemyślenia i zbudowania większych programów. Funkcje, które odczytują zmienny stan globalny lub nim manipulują, są z natury nieczyste.
  • Kod zrozumieniem - zachowanie kod, który zależy od wielu zmiennych globalnych modyfikowalnych jest znacznie trudniejsze do zrozumienia - trzeba zrozumieć zakres możliwych interakcji ze zmienną globalną, zanim będzie można rozumować o zachowaniu kodu. W niektórych sytuacjach problem ten może stać się trudny do rozwiązania.
  • Problemy z współbieżnością - zmienny stan globalny zazwyczaj wymaga pewnej formy blokowania, gdy jest używany w sytuacji równoczesnej. Bardzo trudno jest to naprawić (jest przyczyną błędów) i znacznie komplikuje kod (trudny / drogi w utrzymaniu).
  • Wydajność - wiele wątków ciągle walących w ten sam stan globalny powoduje rywalizację z pamięcią podręczną i ogólnie spowalnia system.

Alternatywy dla zmiennego stanu globalnego:

  • Parametry funkcji - często pomijane, ale lepsza parametryzacja funkcji jest często najlepszym sposobem na uniknięcie stanu globalnego. Zmusza cię do rozwiązania ważnego pytania koncepcyjnego: jakich informacji wymaga ta funkcja do wykonania swojej pracy? Czasami sensowne jest posiadanie struktury danych o nazwie „Kontekst”, która może być przekazywana łańcuchem funkcji, które zawijają wszystkie istotne informacje.
  • Wstrzykiwanie zależności - tak samo jak w przypadku parametrów funkcji, zrobione nieco wcześniej (przy budowie obiektu, a nie wywołanie funkcji). Bądź ostrożny, jeśli twoje zależności są zmiennymi obiektami, może to szybko spowodować takie same problemy jak zmienny stan globalny .....
  • Niezmienny stan globalny jest w większości nieszkodliwy - w rzeczywistości jest stały. Ale upewnij się, że to naprawdę jest stałe i że nie będziesz miał pokusy, aby zmienić go w zmienny stan globalny w późniejszym momencie!
  • Niezmienne singletony - prawie tak samo jak niezmienny stan globalny, z tym wyjątkiem, że możesz odroczyć tworzenie instancji, dopóki nie będą potrzebne. Przydatne np. Dla dużych stałych struktur danych, które wymagają kosztownych jednorazowych wstępnych obliczeń. Zmienne singletony są oczywiście równoznaczne ze zmiennym stanem globalnym i dlatego są złe :-)
  • Wiązanie dynamiczne - dostępne tylko w niektórych wersjach językowych, takich jak Common Lisp / Clojure, ale pozwala to skutecznie powiązać wartość w kontrolowanym zakresie (zwykle w oparciu o wątek lokalny), co nie wpływa na inne wątki. W pewnym stopniu jest to „bezpieczny” sposób na uzyskanie takiego samego efektu jak zmienna globalna, ponieważ wiadomo, że wpłynie to tylko na bieżący wątek wykonania. Jest to szczególnie przydatne w przypadku, gdy masz wiele wątków, na przykład obsługujących niezależne transakcje.
mikera
źródło
11
Myślę, że przekazanie obiektu kontekstu przez parametr funkcji lub wstrzyknięcie zależności spowodowałoby problemy, jeśli kontekst jest zmienny, taki sam problem jak użycie zmiennego stanu globalnego.
Alfredo Osorio
1
Wszystkie dobre rzeczy i amen! Ale pytanie dotyczy niezmiennego stanu globalnego
MarkJ
@Alfredo - bardzo prawdziwe. Chociaż nie jest tak źle, ponieważ przynajmniej zakres można nieco kontrolować. Ale ogólnie łatwiej jest po prostu rozwiązać problem, czyniąc konteksty i zależności niezmiennymi.
mikera
2
+1 za zmienne / niezmienne. Niezmienne globale są w porządku. Nawet te, które są leniwie załadowane, ale nigdy się nie zmieniają. Oczywiście nie ujawniaj zmiennych globalnych, ale globalny interfejs lub interfejs API.
Jess,
1
@giorgio Pytanie wyjaśnia, że ​​zmienne, o których mowa, otrzymują swoje wartości podczas uruchamiania i nigdy nie zmieniają się później podczas wykonywania programu (foldery systemowe, poświadczenia bazy danych). Tzn. Niezmienny, nie zmienia się po nadaniu wartości. Osobiście używam również słowa „stan”, ponieważ może ono różnić się od jednego wykonania do drugiego lub na innym komputerze. Mogą być lepsze słowa.
MarkJ
62
  1. Ponieważ cała ta cholerna aplikacja może z niej korzystać, zawsze niezwykle trudno jest je ponownie rozważyć. Jeśli kiedykolwiek zmienisz coś, co ma związek z globalnym, cały Twój kod wymaga zmiany. Jest to ból głowy związany z utrzymaniem - o wiele więcej niż zwykłe grepokreślenie nazwy typu, aby dowiedzieć się, które funkcje ją wykorzystują.
  2. Są złe, ponieważ wprowadzają ukryte zależności, które przerywają wielowątkowość, co jest coraz ważniejsze w coraz większej liczbie aplikacji.
  3. Stan zmiennej globalnej jest zawsze całkowicie zawodny, ponieważ cały kod może coś z tym zrobić.
  4. Naprawdę trudno je przetestować.
  5. Utrudniają wywoływanie interfejsu API. „Musisz pamiętać o wywołaniu SET_MAGIC_VARIABLE () przed wywołaniem interfejsu API”, po prostu błaga, aby ktoś zapomniał go wywołać. Sprawia, że ​​korzystanie z interfejsu API jest podatne na błędy, co powoduje trudne do znalezienia błędy. Używając go jako zwykłego parametru, zmuszasz rozmówcę do poprawnego podania wartości.

Wystarczy przekazać odwołanie do funkcji, które go potrzebują. To nie jest takie trudne.

DeadMG
źródło
3
Cóż, możesz mieć globalną klasę konfiguracji, która zawiera blokowanie, i JEST zaprojektowana w celu zmiany stanu w dowolnym momencie. Wybrałbym to podejście zamiast tworzenia czytników konfiguracji z 1000x miejsc w kodzie. Ale tak, nieprzewidywalność jest w nich absolutnie najgorsza.
Koder
6
@ Koder: Zauważ, że rozsądną alternatywą dla globalnej nie jest „czytniki konfiguracji z 1000x miejsc w kodzie”, ale jeden czytnik konfiguracji, który tworzy obiekt config, który metody mogą zaakceptować jako parametr (-> Wstrzyknięcie zależności).
sleske
2
Nitpicking: Dlaczego łatwiej jest grepować dla typu niż globalnego? Pytanie dotyczy globalnych danych tylko do odczytu, więc punkty 2 i 3 nie mają znaczenia
MarkJ
2
@ MarkJ: Mam nadzieję, że nikt nigdy, powiedzmy, nie zacieniał nazwy.
DeadMG
2
@DeadMG, Re „..jest zawsze całkowicie zawodny ..”, to samo dotyczy wstrzykiwania zależności. Tylko dlatego, że został ustawiony jako parametr, nie oznacza, że ​​obj ma zagwarantowany stały stan. Gdzieś w dół funkcji można było wywołać inną funkcję, która modyfikuje stan wstrzykniętej zmiennej u góry.
Pacerier
34

Jeśli powiesz „stan”, zwykle oznacza to „stan zmienny”. A globalny stan zmienny jest całkowicie zły, ponieważ oznacza, że jakakolwiek część programu może wpływać na dowolną inną część (poprzez zmianę stanu globalnego).

Wyobraź sobie debugowanie nieznanego programu: okazuje się, że funkcja A zachowuje się w określony sposób dla niektórych parametrów wejściowych, ale czasami działa inaczej dla tych samych parametrów. Okazuje się, że używa zmiennej globalnej x .

Poszukujesz miejsc, które modyfikują x , i okazuje się, że istnieje pięć miejsc, które go modyfikują. Teraz powodzenia w ustaleniu, w jakich przypadkach funkcja A robi to, co ...

Śleske
źródło
Czy niezmienne globalne państwo nie jest takie złe?
FrustratedWithFormsDesigner
50
Powiedziałbym, że niezmienne państwo globalne jest dobrze znaną dobrą praktyką znaną jako „stałe”.
Telastyn
6
Niezmienny stan globalny nie jest zły, jest po prostu zły :-). Nadal jest problematyczny ze względu na sprzężenie, które wprowadza (utrudnia zmiany, ponowne użycie i testowanie jednostkowe), ale stwarza znacznie mniej problemów, więc w prostych przypadkach jest to zwykle do przyjęcia.
śleske,
2
IFF używa zmiennych globalnych, wówczas tylko jeden fragment kodu powinien go modyfikować. Reszta może go przeczytać. Kwestie innych osób, które to zmieniają, nie ustępują dzięki funkcjom enkapsulacji i dostępu. Nie po to są te konstrukty.
phkahler
1
@Pacerier: Tak, zmiana powszechnie używanego interfejsu jest trudna, bez względu na to, czy jest używana jako zmienna globalna czy lokalna. Jest to jednak niezależne od mojego punktu widzenia, a mianowicie, że interakcja różnych fragmentów kodu jest trudna do zrozumienia w przypadku globalnych zmiennych.
sleske,
9

W pewnym sensie odpowiedziałeś na własne pytanie. Trudno nimi zarządzać, gdy są „nadużywane”, ale mogą być przydatne i [nieco] przewidywalne, gdy są właściwie stosowane przez kogoś, kto wie, jak je powstrzymać. Utrzymanie i zmiany w / na globals są zwykle koszmarem, pogarszającym się wraz ze wzrostem wielkości aplikacji.

Doświadczeni programiści, którzy potrafią odróżnić globale jako jedyną opcję od łatwości naprawy, mogą mieć minimalne problemy z ich używaniem. Ale niekończące się możliwe problemy, które mogą pojawić się przy ich użyciu, wymagają odradzania ich używania.

edytuj: Aby wyjaśnić, co mam na myśli, globale są nieprzewidywalne z natury. Podobnie jak w przypadku wszystkiego, co nieprzewidywalne, możesz podjąć kroki w celu ograniczenia nieprzewidywalności, ale zawsze istnieją ograniczenia co do tego, co można zrobić. Dodajmy do tego kłopoty nowych programistów dołączających do projektu, którzy muszą radzić sobie ze stosunkowo nieznanymi zmiennymi, zalecenia dotyczące używania globałów powinny być zrozumiałe.

orourkek
źródło
9

Jest wiele problemów z Singletonami - oto dwa największe problemy w mojej głowie.

  • To sprawia, że ​​testowanie jednostkowe jest problematyczne. Stan globalny może zostać zanieczyszczony z jednego testu na drugi

  • Wymusza twardą zasadę „jeden i tylko jeden”, która, choć nie mogła się zmienić, nagle tak się dzieje. Cały pakiet kodu narzędziowego, który używał globalnie dostępnego obiektu, musi zostać zmieniony.

Mimo to większość systemów potrzebuje dużych obiektów globalnych. Są to przedmioty, które są duże i drogie (np. Menedżerowie połączeń z bazą danych) lub zawierają rozległe informacje o stanie (na przykład informacje o blokowaniu).

Alternatywą dla Singletona jest tworzenie tych dużych globalnych obiektów podczas uruchamiania i przekazywanie ich jako parametrów do wszystkich klas lub metod, które potrzebują dostępu do tego obiektu.

Problem polega na tym, że kończy się duża gra „pass-the-paczkę”. Masz wykres składników i ich zależności, a niektóre klasy tworzą inne klasy, a każda z nich musi przechowywać kilka składników zależności tylko dlatego, że ich składniki spawnowane (lub komponenty spawnowane) ich potrzebują.

Występują nowe problemy z konserwacją. Przykład: Nagle komponent „WidgetFactory” znajdujący się głęboko na wykresie potrzebuje obiektu czasowego, który chcesz wyśmiewać. Jednak „WidgetFactory” jest tworzony przez „WidgetBuilder”, który jest częścią „WidgetCreationManager”, i musisz mieć trzy klasy, które wiedzą o tym obiekcie timera, chociaż tylko jedna z niego faktycznie korzysta. Czujesz, że chcesz zrezygnować i powrócić do Singletonów, a po prostu sprawić, by ten obiekt czasowy był globalnie dostępny.

Na szczęście jest to dokładnie problem rozwiązany przez strukturę Dependency Injection. Możesz po prostu powiedzieć frameworkowi, jakie klasy musi utworzyć, a on używa refleksji, aby obliczyć dla ciebie wykres zależności, i automatycznie konstruuje każdy obiekt, gdy jest potrzebny.

Podsumowując, Singletony są złe, a alternatywą jest użycie frakcji Dependency Injection.

Zdarza mi się używać Castle Windsor, ale jesteś rozpieszczany wyborem. Zobacz tę stronę z 2008 roku, aby uzyskać listę dostępnych ram.

Andrew Shepherd
źródło
8

Przede wszystkim, aby zastrzyk zależności był „stanowy”, należy użyć singletonów, więc ludzie twierdzący, że jest to jakaś alternatywa, są w błędzie. Ludzie cały czas używają globalnych obiektów kontekstu ... Na przykład nawet stan sesji jest w zasadzie zmienną globalną. Przekazywanie wszystkiego dookoła, czy to przez wstrzykiwanie zależności, czy nie, nie zawsze jest najlepszym rozwiązaniem. Pracuję obecnie nad bardzo dużą aplikacją, która wykorzystuje wiele globalnych obiektów kontekstowych (singletony wstrzykiwane przez kontener IoC) i nigdy nie było problemu z debugowaniem. Zwłaszcza w przypadku architektury opartej na zdarzeniach można preferować używanie globalnych obiektów kontekstowych zamiast przekazywania tego, co się zmieniło. Zależy, kogo zapytasz.

Wszystko może być nadużywane i zależy to również od rodzaju aplikacji. Używanie zmiennych statycznych na przykład w aplikacji internetowej jest zupełnie inne niż aplikacja komputerowa. Jeśli możesz uniknąć zmiennych globalnych, zrób to, ale czasami mają one swoje zastosowania. Przynajmniej upewnij się, że twoje dane globalne są w wyraźnym obiekcie kontekstowym. Jeśli chodzi o debugowanie, nic nie może się równać ze stosem wywołań i niektórymi punktami przerwania.

Chcę podkreślić, że ślepe używanie zmiennych globalnych jest złym pomysłem. Funkcje powinny nadawać się do wielokrotnego użytku i nie powinny obchodzić się, skąd pochodzą dane - odwoływanie się do zmiennych globalnych łączy funkcję z określonym wejściem danych. Dlatego należy to przekazać i dlaczego wstrzyknięcie zależności może być pomocne, chociaż nadal masz do czynienia ze scentralizowanym sklepem kontekstowym (za pośrednictwem singletonów).

Btw ... Niektórzy ludzie uważają, że zastrzyk uzależnienia jest zły, w tym twórca Linq, ale to nie powstrzyma ludzi przed używaniem uzależnienia, w tym ode mnie. Ostatecznie doświadczenie będzie twoim najlepszym nauczycielem. Są czasy, aby przestrzegać zasad i czasy, aby je złamać.

KingOfHypocrites
źródło
4

Ponieważ niektóre inne odpowiedzi tutaj rozróżniają między zmiennym i niezmiennym stanem globalnym, chciałbym stwierdzić, że nawet niezmienne zmienne / ustawienia globalne są często denerwujące .

Rozważ pytanie:

... Powiedzmy, że potrzebuję globalnej konfiguracji dla mojej aplikacji, na przykład ścieżek folderów systemowych lub poświadczeń bazy danych dla całej aplikacji. ...

Racja, w przypadku małego programu prawdopodobnie nie stanowi to problemu, ale jak tylko zrobisz to z komponentami w nieco większym systemie, pisanie automatycznych testów nagle staje się trudniejsze, ponieważ wszystkie testy (~ w ramach tego samego procesu) muszą działać z ta sama globalna wartość konfiguracji.

Jeśli wszystkie dane konfiguracyjne zostaną przekazane jawnie, składniki stają się znacznie łatwiejsze do przetestowania i nigdy nie musisz się martwić, jak uruchomić globalną wartość konfiguracji dla wielu testów, może nawet równolegle.

Martin Ba
źródło
3

Po pierwsze, możesz napotkać dokładnie ten sam problem, co w singletonach. To, co dziś wygląda na „globalną rzecz, której potrzebuję tylko jednego”, nagle zmieni się w coś, czego potrzebujesz bardziej w przyszłości.

Na przykład dzisiaj tworzysz ten globalny system konfiguracji, ponieważ potrzebujesz jednej globalnej konfiguracji dla całego systemu. Kilka lat później przenosisz się do innego systemu i ktoś mówi: „hej, wiesz, to może działać lepiej, jeśli istnieje jedna ogólna konfiguracja globalna i jedna konfiguracja specyficzna dla platformy”. Nagle masz całą tę pracę, aby twoje globalne struktury nie stały się globalne, dzięki czemu możesz mieć wiele.

(To nie jest przypadkowy przykład ... tak się stało z naszym systemem konfiguracji w projekcie, w którym aktualnie jestem.)

Biorąc pod uwagę, że koszt zrobienia czegoś nieglobalnego jest zwykle trywialny, głupotą jest to zrobić. Tworzysz tylko przyszłe problemy.

Steven Burnap
źródło
Twój przykład nie jest tym, co miał na myśli OP. Mówisz wyraźnie o tworzeniu abstrakcyjnych konfiguracji (tylko struktura danych menedżera konfiguracji, bez żadnych kluczy preferencji specyficznych dla aplikacji) wiele razy, ponieważ chcesz podzielić wszystkie klucze w uruchomionej instancji programu na wiele instancji klasy menedżera konfiguracji. (Powiedzmy, że każda taka instancja może na przykład obsługiwać jeden plik konfiguracyjny).
Jo So
3

Inny ciekawy problem polega na tym, że utrudniają skalowanie aplikacji, ponieważ nie są wystarczająco „globalne”. Zakres zmiennej globalnej to proces.

Jeśli chcesz skalować aplikację za pomocą wielu procesów lub na wielu serwerach, nie możesz. Przynajmniej dopóki nie uwzględnisz wszystkich globali i nie zastąpisz ich innym mechanizmem.

James Anderson
źródło
3

Dlaczego państwo globalne jest tak złe?

Zmienny stan globalny jest zły, ponieważ bardzo trudno jest naszemu mózgowi wziąć pod uwagę więcej niż kilka parametrów jednocześnie i dowiedzieć się, w jaki sposób łączą one zarówno z perspektywy czasowej, jak i wartościowej, aby wpłynąć na coś.

Dlatego bardzo źle radzimy sobie z debugowaniem lub testowaniem obiektu, którego zachowanie ma więcej niż kilka przyczyn zewnętrznych, które należy zmienić podczas wykonywania programu. Nie mówiąc już o tym, kiedy musimy uzasadnić dziesiątki tych obiektów razem wziętych.

guillaume31
źródło
3

Języki zaprojektowane do bezpiecznego i solidnego projektowania systemów często całkowicie pozbywają się globalnego stanu zmiennego . (Prawdopodobnie oznacza to brak globali, ponieważ niezmienne obiekty w pewnym sensie nie są tak naprawdę stanowe, ponieważ nigdy nie mają przejścia stanu).

Joe-E jest jednym z przykładów, a David Wagner wyjaśnia tę decyzję w ten sposób:

Analiza tego, kto ma dostęp do obiektu i zasada najmniejszego uprzywilejowania są obalone, gdy możliwości są przechowywane w zmiennych globalnych, a zatem są potencjalnie czytelne dla dowolnej części programu. Gdy obiekt jest ogólnie dostępny, nie można już ograniczyć zakresu analizy: dostęp do obiektu jest przywilejem, którego nie można odmówić żadnemu kodowi w programie. Joe-E unika tych problemów, sprawdzając, czy globalny zasięg nie zawiera żadnych możliwości, a jedynie niezmienne dane.

Jednym ze sposobów myślenia o tym jest

  1. Programowanie to rozproszony problem z rozumowaniem. Programiści realizujący duże projekty muszą podzielić program na części, które mogą być uzasadnione przez poszczególne osoby.
  2. Im mniejszy zakres, tym łatwiej jest o tym myśleć. Dotyczy to zarówno osób, jak i narzędzi analizy statycznej, które próbują udowodnić właściwości systemu, oraz testów, które muszą przetestować właściwości systemu.
  3. Znaczące źródła uprawnień, które są dostępne na całym świecie, sprawiają, że trudno jest uzasadnić właściwości systemu.

Dlatego globalnie zmienny stan utrudnia

  1. zaprojektuj solidne systemy,
  2. trudniejsze do udowodnienia właściwości systemu, oraz
  3. trudniej jest mieć pewność, że testy są testowane w zakresie zbliżonym do środowiska produkcyjnego.

Globalny stan zmienny jest podobny do piekła DLL . Z czasem różne elementy dużego systemu będą wymagały subtelnie innego zachowania niż wspólne elementy o zmiennym stanie. Rozwiązanie piekła DLL i wspólnych niespójności stanów zmiennych wymaga koordynacji na dużą skalę między różnymi zespołami. Te problemy nie wystąpiłyby, gdyby stan globalny był odpowiednio zakrojony na początek.

Mike Samuel
źródło
2

Globale nie są takie złe. Jak stwierdzono w kilku innych odpowiedziach, prawdziwym problemem z nimi jest to, że dzisiejsza ścieżka do folderów globalnych może być jutro jedną z kilku, a nawet setek. Jeśli piszesz szybki, jednorazowy program, użyj globals, jeśli jest to łatwiejsze. Generalnie jednak dopuszczenie wielokrotności, nawet jeśli uważasz, że potrzebujesz jednej, jest właściwą drogą. Restrukturyzacja dużego, złożonego programu, który nagle musi rozmawiać z dwiema bazami danych , nie jest przyjemna .

Ale nie szkodzą niezawodności. Wszelkie dane przywoływane z wielu miejsc w twoim programie mogą powodować problemy, jeśli nieoczekiwanie się zmienią. Wyliczacze dławią się, gdy kolekcja, którą wyliczają, jest zmieniana podczas wyliczania w połowie. Zdarzenia w kolejce zdarzeń mogą na siebie nawzajem grać. Wątki zawsze mogą siać spustoszenie. Wszystko, co nie jest zmienną lokalną lub niezmiennym polem, stanowi problem. Globale są tego rodzaju problemem, ale nie naprawisz tego, czyniąc je nieglobalnymi.

Jeśli masz zamiar zapisać do pliku, a ścieżka do folderu się zmieni, zmiana i zapis muszą zostać zsynchronizowane. (Jako jedna z tysiąca rzeczy, które mogą pójść nie tak, powiedzmy, że pobierasz ścieżkę, a następnie ten katalog jest usuwany, następnie ścieżka folderu jest zmieniana na dobry katalog, a następnie próbujesz zapisać w usuniętym katalogu). Problem istnieje, czy ścieżka folderu jest globalna lub jest jedną z tysiąca, których program aktualnie używa.

Istnieje prawdziwy problem z polami, do których mogą uzyskać dostęp różne zdarzenia w kolejce, różne poziomy rekurencji lub różne wątki. Upraszczając (i upraszczając): zmienne lokalne są dobre, a pola złe. Ale dawne globale nadal będą polami, więc ta (choć niezwykle ważna) kwestia nie dotyczy statusu dobrych lub złych pól globalnych.

Dodatek: problemy z wielowątkowością:

(Pamiętaj, że możesz mieć podobne problemy z kolejką zdarzeń lub wywołaniami rekurencyjnymi, ale wielowątkowość jest zdecydowanie najgorsza.) Rozważ następujący kod:

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

Jeśli filePathjest zmienną lokalną lub jakąś stałą, twój program nie zawiedzie podczas działania, ponieważ filePathma wartość NULL. Kontrola zawsze działa. Żaden inny wątek nie może zmienić swojej wartości. W przeciwnym razie nie ma gwarancji. Kiedy zacząłem pisać programy wielowątkowe w Javie, cały czas otrzymywałem wyjątki NullPointerExceptions na takich liniach. Każdyinny wątek może zmienić wartość w dowolnym momencie i często tak jest. Jak wskazuje kilka innych odpowiedzi, stwarza to poważne problemy podczas testowania. Powyższe stwierdzenie może działać miliard razy, przechodząc przez obszerne i kompleksowe testy, a następnie raz wysadzić w powietrze podczas produkcji. Użytkownicy nie będą w stanie odtworzyć problemu i to się nie powtórzy, dopóki nie przekonają się, że widzieli rzeczy i zapomnieli o tym.

Globale z pewnością mają ten problem i jeśli możesz je całkowicie wyeliminować lub zastąpić stałymi lub lokalnymi zmiennymi, to bardzo dobrze. Jeśli na serwerze internetowym działa kod bezstanowy, prawdopodobnie możesz. Zazwyczaj wszystkie problemy z wielowątkowością mogą zostać przejęte przez bazę danych.

Ale jeśli twój program musi pamiętać rzeczy od jednej akcji użytkownika do drugiej, będziesz mieć pola dostępne dla dowolnego działającego wątku. Zmiana globalnej na tak nieglobalną dziedzinę nie pomoże niezawodności.

RalphChapin
źródło
1
Czy możesz wyjaśnić, co masz na myśli ?: „Cokolwiek, co nie jest zmienną lokalną lub niezmiennym polem, stanowi problem. Globale są tego rodzaju problemem, ale nie naprawisz tego, czyniąc je nieglobalnymi”.
Andres F.
1
@AndresF .: Rozszerzyłem swoją odpowiedź. Wydaje mi się, że stosuję podejście komputerowe, w którym większość osób na tej stronie ma więcej kodu serwera z bazą danych. „Globalny” może w tych przypadkach oznaczać różne rzeczy.
RalphChapin
2

Stan jest nieunikniony w żadnej rzeczywistej aplikacji. Możesz go zawinąć w dowolny sposób, ale arkusz kalkulacyjny musi zawierać dane w komórkach. Możesz tworzyć obiekty komórkowe z funkcjami tylko jako interfejs, ale to nie ogranicza liczby miejsc, które mogą wywołać metodę w komórce i zmienić dane. Budujesz hierarchie całych obiektów, próbując ukryć interfejsy, aby inne części kodu nie mogłyzmień dane domyślnie. Nie uniemożliwia to arbitralnego przekazywania odniesienia do obiektu zawierającego. Żadne z tych działań nie eliminuje samodzielnie problemów z współbieżnością. Utrudnia to rozprzestrzenianie dostępu do danych, ale tak naprawdę nie eliminuje dostrzeganych problemów z globalnymi. Jeśli ktoś chce zmodyfikować jakiś stan, zrobi to, niezależnie od tego, czy będzie to globalne, czy poprzez złożony interfejs API (później tylko zniechęci, a nie zapobiegnie).

Prawdziwym powodem nieużywania globalnej pamięci masowej jest unikanie kolizji nazw. Jeśli załadujesz wiele modułów, które deklarują tę samą globalną nazwę, albo masz niezdefiniowane zachowanie (bardzo trudne do debugowania, ponieważ testy jednostkowe przejdą) lub błąd linkera (myślę, że C - czy twój linker ostrzega, czy zawodzi?).

Jeśli chcesz ponownie użyć kodu, musisz być w stanie pobrać moduł z innego miejsca i nie pozwolić mu przypadkowo nadepnąć na globalny, ponieważ użyli go o tej samej nazwie. Lub jeśli masz szczęście i pojawi się błąd, nie musisz zmieniać wszystkich odniesień w jednej sekcji kodu, aby zapobiec kolizji.

phkahler
źródło
1

Gdy łatwo jest zobaczyć i uzyskać dostęp do całego stanu globalnego, programiści niezmiennie to robią. To, co dostajesz, jest niewypowiedziane i trudne do śledzenia zależności (int blahblah oznacza, że ​​tablica foo jest ważna w czymkolwiek). Zasadniczo sprawia, że ​​utrzymanie niezmienników programu jest prawie niemożliwe, ponieważ wszystko można zmienić osobno. someInt ma związek między innymiInt, trudny do zarządzania i trudniejszy do udowodnienia, czy możesz zmienić bezpośrednio w dowolnym momencie.

To powiedziawszy, można to zrobić (dawno temu, gdy był to jedyny sposób w niektórych systemach), ale umiejętności te zostały utracone. Koncentrują się one głównie na konwencjach kodowania i nazewnictwa - dziedzina zmieniła się nie bez powodu. Twój kompilator i linker lepiej sprawdzają niezmienniki w chronionych / prywatnych danych klas / modułów niż polegają na ludziach, aby postępowali zgodnie z głównym planem i czytali źródła.

zaraz
źródło
1
„ale te umiejętności zostały utracone” ... jeszcze nie do końca. Niedawno pracowałem w domu oprogramowania, który przysięga na „Clarion”, narzędzie do generowania kodu, które ma swój własny język podobny do podstawowego, pozbawiony funkcji takich jak przekazywanie argumentów do podprogramów ... Siedzący programiści nie byli zadowoleni z żadnych sugestii na temat „zmiana” lub „modernizacja”, w końcu miałem dość moich uwag i przedstawiłem mnie jako niedostatecznego i niekompetentnego. Musiałem odejść ...
Louis Somers
1

Nie powiem, czy zmienne globalne są dobre czy złe, ale dodam do dyskusji to, że jeśli nie używasz stanu globalnego, prawdopodobnie marnujesz dużo pamięci, szczególnie gdy używasz klas do przechowywania ich zależności w polach.

W przypadku stanu globalnego nie ma takiego problemu, wszystko ma charakter globalny.

Na przykład: wyobraź sobie następujący scenariusz: masz siatkę 10x10, która składa się z klas „Plansza” i „Płytka”.

Jeśli chcesz to zrobić w sposób OOP, prawdopodobnie przekażesz obiekt „Board” do każdego „Tile”. Powiedzmy teraz, że „Kafelek” ma 2 pola typu „bajt”, w których zachowuje swoją współrzędną. Całkowita pamięć, jaką zajmie na 32-bitowej maszynie dla jednego kafelka, wynosiłaby (1 + 1 + 4 = 6) bajtów: 1 dla współrzędnej x, 1 dla współrzędnej y i 4 dla wskaźnika do tablicy. Daje to w sumie 600 bajtów na ustawienie 10x10 płytek

Teraz w przypadku, gdy tablica ma zasięg globalny, pojedynczy obiekt dostępny z każdego kafelka musiałby uzyskać tylko 2 bajty pamięci na każdy kafelek, czyli bajty współrzędnych xiy. Dałoby to tylko 200 bajtów.

W takim przypadku uzyskujesz 1/3 zużycia pamięci, jeśli używasz tylko stanu globalnego.

To, oprócz innych rzeczy, wydaje się być powodem, dla którego zasięg globalny nadal pozostaje w (względnie) językach niskiego poziomu, takich jak C ++

luke1985
źródło
1
To bardzo zależy od języka. W językach, w których programy działają stale, może być prawdą, że możesz zaoszczędzić pamięć, używając globalnej przestrzeni i singletonów zamiast tworzenia wielu klas, ale nawet ten argument jest niepewny. Chodzi o priorytety. W językach takich jak PHP (uruchamiany raz na żądanie, a obiekty nie trwają), nawet ten argument jest dyskusyjny.
Madara Uchiha
1
@MadaraUchiha Nie, ten argument w żaden sposób nie jest niepewny. Są to obiektywne fakty, chyba że jakaś maszyna wirtualna lub inny skompilowany język dokonuje jakiejś trudnej optymalizacji kodu z tego rodzaju problemem. W przeciwnym razie jest to nadal aktualne. Nawet w przypadku programów po stronie serwera, które używają „jednego strzału”, pamięć jest zarezerwowana na czas wykonywania. W przypadku serwerów o wysokim obciążeniu może to być punkt krytyczny.
luke1985
0

Ze stanem globalnym należy wziąć pod uwagę kilka czynników:

  1. Miejsce w pamięci programisty
  2. Niezmienne globale / pisz raz globały.
  3. Zmienne globale
  4. Zależność od globałów.

Im więcej globali masz, tym większa szansa na wprowadzenie duplikatów, a tym samym zerwanie rzeczy, gdy duplikaty się zsynchronizują. Utrzymywanie wszystkich globali w twojej kruchej ludzkiej pamięci staje się konieczne i bolesne.

Niezmienne / jednokrotne zapisywanie jest ogólnie OK, ale uważaj na błędy sekwencji inicjalizacji.

Zmienne globale są często mylone z niezmiennymi globalami…

Funkcja, która skutecznie wykorzystuje globały, ma dodatkowe „ukryte” parametry, co utrudnia refaktoryzację.

Stan globalny nie jest zły, ale wiąże się z określonym kosztem - wykorzystaj go, gdy korzyść przewyższy koszt.

jmoreno
źródło