Почему Глобальное Государство так Зло?


328

Прежде чем мы начнем, позвольте мне сказать, что я хорошо знаю концепции абстракции и внедрения зависимости. Мне не нужно, чтобы мои глаза открылись здесь.

Ну, большинство из нас (слишком) часто говорят, не понимая: «Не используйте глобальные переменные» или «Синглтоны - это зло, потому что они глобальны». Но что на самом деле это так плохо о зловещем глобальном состоянии?

Допустим, мне нужна глобальная конфигурация для моего приложения, например, пути к системным папкам или учетные данные базы данных всего приложения.

В этом случае я не вижу хорошего решения, кроме как предоставить эти настройки в каком-то глобальном пространстве, которое будет общедоступным для всего приложения.

Я знаю , что это плохо злоупотребляют , но это глобальное пространство действительно ЧТО зло? И если это так, какие есть хорошие альтернативы?


5
Вы можете использовать класс конфигурации, который обрабатывает доступ к этим настройкам. я думаю, что хорошей практикой является наличие единственного и единственного места, где параметры конфигурации считываются из файлов конфигурации и / или баз данных, в то время как другие объекты извлекают их из этой конфигурации. это делает ваш дизайн более чистым и предсказуемым.
Хаджо

25
В чем разница с использованием глобального? Ваше «одно-единственное место» звучит очень подозрительно, как синглтон.
Мадара Учиха

130
Единственное настоящее зло - это догмы.
Питер Б

34
Использование глобального состояния с вашими коллегами-программистами похоже на использование одной и той же зубной щетки с вашими друзьями - вы можете, но никогда не знаете, когда кто-то решит засунуть его в задницу.
ЗиГи

5
Дьявол это зло. Глобальное состояние не является ни злом, ни добром. Это может быть очень полезным или вредным в зависимости от того, как вы его используете. Не слушайте других и просто научитесь правильно программировать.
annoying_squid

Ответы:


282

Вкратце, это делает состояние программы непредсказуемым.

Чтобы уточнить, представьте, что у вас есть пара объектов, которые используют одну и ту же глобальную переменную. Предполагая, что вы не используете источник случайности ни в одном из модулей, выход конкретного метода можно предсказать (и, следовательно, протестировать), если состояние системы известно до выполнения метода.

Однако, если метод в одном из объектов вызывает побочный эффект, который изменяет значение общего глобального состояния, вы больше не знаете, что такое начальное состояние, когда выполняете метод в другом объекте. Теперь вы больше не можете предсказать, какой вывод вы получите при выполнении метода, и, следовательно, вы не можете его протестировать.

На академическом уровне это может показаться не таким уж серьезным, но возможность модульного тестирования кода является важным шагом в процессе доказательства его правильности (или, по крайней мере, соответствия цели).

В реальном мире это может иметь очень серьезные последствия. Предположим, у вас есть один класс, который заполняет глобальную структуру данных, и другой класс, который использует данные в этой структуре данных, изменяя их состояние или уничтожая их в процессе. Если класс процессора выполняет метод до того, как будет создан класс популяции, в результате класс процессора, вероятно, будет иметь неполные данные для обработки, а структура данных, над которой работал класс популяции, может быть повреждена или уничтожена. Поведение программы в этих условиях становится совершенно непредсказуемым и, вероятно, приведет к эпической потере.

Кроме того, глобальное состояние ухудшает читабельность вашего кода. Если у вашего кода есть внешняя зависимость, которая явно не введена в код, тогда любой, кто получит работу по поддержке вашего кода, должен будет искать его, чтобы выяснить, откуда он взялся.

Что касается того, какие альтернативы существуют, то вообще невозможно вообще не иметь глобального состояния, но на практике обычно можно ограничить глобальное состояние одним объектом, который охватывает все остальные, и на который никогда нельзя ссылаться, полагаясь на правила области видимости. языка, который вы используете. Если конкретному объекту необходимо определенное состояние, он должен явно запросить его, передав его в качестве аргумента своему конструктору или с помощью метода установки. Это известно как инъекция зависимости.

Может показаться глупым переходить в состояние, к которому вы уже можете получить доступ из-за правил области видимости любого языка, который вы используете, но преимущества огромны. Теперь, если кто-то смотрит на код изолированно, становится ясно, в каком состоянии он нуждается и откуда он поступает. Он также имеет огромные преимущества в отношении гибкости вашего модуля кода и, следовательно, возможности его повторного использования в различных контекстах. Если состояние передается и изменения состояния являются локальными для блока кода, то вы можете передать любое состояние, которое вам нравится (если это правильный тип данных), и ваш код его обработает. Код, написанный в этом стиле, имеет тенденцию выглядеть как набор свободно связанных компонентов, которые можно легко заменить. Код модуля не должен заботиться о том, откуда приходит состояние, а только о том, как его обрабатывать.

Существует множество других причин, по которым переходящее состояние намного превосходит доверие к глобальному состоянию. Этот ответ ни в коем случае не является исчерпывающим. Возможно, вы могли бы написать целую книгу о том, почему глобальное состояние плохо.


61
По сути, потому что любой может изменить состояние, вы не можете на него положиться.
Одед

21
@ Правда альтернатива? Инъекция зависимости .
rdlowrey

12
Ваш основной аргумент не относится к чему-то, что эффективно доступно только для чтения, например к объекту, представляющему конфигурацию.
Франк

53
Ничто из этого не объясняет, почему глобальные переменные, доступные только для чтения , плохи… о чем явно спрашивал ОП. В остальном это хороший ответ, я просто удивлен, что ОП пометил его как «принятый», когда он явно обратился к другому вопросу.
Конрад Рудольф

20
being able to unit test code is a major step in the process of proving its correctness (or at least fitness for purpose), Нет, это не так. «Прошло уже два десятилетия с тех пор, как было отмечено, что тестирование программы может убедительно продемонстрировать наличие ошибок, но никогда не сможет продемонстрировать их отсутствие. После того, как это ревностно процитировано это широко разрекламированное замечание, инженер-программист возвращается к порядку дня и продолжает совершенствовать свои стратегии тестирования, точно так же, как алхимик прошлого, который продолжал совершенствовать свои хризокосмические очищения ". - Джикстра, 1988. (Это делает это 4,5 десятилетия сейчас ...)
Мейсон Уилер

135

Изменчивое глобальное государство является злом по многим причинам:

  • Ошибки из изменяемого глобального состояния - многие хитрые ошибки вызваны изменчивостью. Ошибки, которые могут быть вызваны мутацией из любой точки программы, даже хитрые, так как часто бывает трудно найти точную причину
  • Плохая тестируемость - если у вас изменяемое глобальное состояние, вам нужно будет настроить его для любых тестов, которые вы пишете. Это усложняет тестирование (и поэтому люди, являющиеся людьми, имеют меньше шансов сделать это!). например, в случае учетных данных базы данных всего приложения, что если одному тесту необходим доступ к конкретной тестовой базе данных, отличной от всего остального?
  • Негибкость - что, если одна часть кода требует одно значение в глобальном состоянии, а другая часть требует другого значения (например, временное значение во время транзакции)? У вас вдруг появляется неприятный рефакторинг
  • Примесь функций - «чистые» функции (т. Е. Те, в которых результат зависит только от входных параметров и не имеют побочных эффектов), гораздо проще рассуждать и составлять для создания более крупных программ. Функции, которые читают или манипулируют изменчивым глобальным состоянием, по своей природе нечисты.
  • Код понимания - поведение кода , который зависит от многих изменчивых глобальных переменных гораздо сложнее понять - вы должны понять диапазон возможных взаимодействий с глобальной переменной , прежде чем рассуждать о поведении кода. В некоторых ситуациях эта проблема может стать неразрешимой.
  • Проблемы параллелизма - изменяемое глобальное состояние обычно требует некоторой формы блокировки при использовании в параллельной ситуации. Это очень трудно понять (это является причиной ошибок) и значительно усложняет ваш код (сложно / дорого поддерживать).
  • Производительность - несколько потоков, непрерывно создающих ошибки в одном и том же глобальном состоянии, вызывают конфликт в кэше и замедляют работу всей системы.

Альтернативы изменчивому глобальному состоянию:

  • Параметры функций - часто упускаются из виду, но более точная параметризация ваших функций часто является лучшим способом избежать глобального состояния. Это заставляет вас решить важный концептуальный вопрос: какая информация требуется этой функции для выполнения своей работы? Иногда имеет смысл иметь структуру данных, называемую «Контекст», которая может передаваться по цепочке функций, которая объединяет всю соответствующую информацию.
  • Внедрение зависимостей - то же, что и для параметров функции, только что сделано немного раньше (при создании объекта, а не при вызове функции). Будьте осторожны, если ваши зависимости являются изменяемыми объектами, это может быстро вызвать те же проблемы, что и изменяемое глобальное состояние .....
  • Неизменное глобальное состояние в основном безвредно - оно фактически является константой. Но убедитесь, что это действительно константа, и что у вас не будет соблазна превратить его в изменяемое глобальное состояние на более позднем этапе!
  • Неизменяемые синглтоны - почти такие же, как неизменное глобальное состояние, за исключением того, что вы можете отложить создание экземпляров до тех пор, пока они не понадобятся. Полезно, например, для больших фиксированных структур данных, которые требуют дорогого разового предварительного расчета. Изменчивые синглтоны, конечно, эквивалентны изменчивому глобальному состоянию и поэтому являются злыми :-)
  • Динамическое связывание - доступно только в некоторых языках, таких как Common Lisp / Clojure, но это позволяет эффективно связывать значение в контролируемой области (обычно на основе локального потока), которое не влияет на другие потоки. В некоторой степени это «безопасный» способ получить тот же эффект, что и глобальная переменная, поскольку вы знаете, что будет затронут только текущий поток выполнения. Это особенно полезно в случае, когда у вас есть несколько потоков, каждый из которых обрабатывает, например, независимые транзакции.

11
Я думаю, что передача объекта контекста либо по параметру функции, либо по внедрению зависимости вызовет проблемы, если контекст изменчив, такая же проблема, как при использовании изменяемого глобального состояния.
Альфредо Осорио

1
Все хорошие вещи, и аминь! Но вопрос об неизменном глобальном состоянии
MarkJ

@ Альфредо - очень верно. Хотя это не так плохо, так как, по крайней мере, объем можно контролировать. Но в целом проще решить проблему, сделав неизменными контексты и зависимости.
Микера

2
+1 для изменяемого / неизменяемого. Неизменные глобалы в порядке. Даже те, которые лениво загружены, но никогда не меняются. Конечно, не выставляйте глобальные переменные, а глобальный интерфейс или API.
Джесс

1
@giorgio Этот вопрос проясняет, что рассматриваемые переменные получают свои значения при запуске и никогда не изменяются впоследствии во время выполнения программы (системные папки, учетные данные базы данных). Т.е. неизменяемый, он не изменяется, если ему присвоено значение. Лично я также использую слово «состояние», потому что оно может отличаться от одного исполнения к другому или на другой машине. Там могут быть лучшие слова.
MarkJ

62
  1. Поскольку все ваше чертово приложение может его использовать, всегда невероятно сложно снова их вернуть. Если вы когда-нибудь измените что-либо, связанное с вашим глобальным, весь ваш код нуждается в изменении. Это головная боль обслуживания - гораздо больше, чем просто возможность grepдля имени типа выяснить, какие функции его используют.
  2. Они плохие, потому что вводят скрытые зависимости, которые нарушают многопоточность, что становится все более важным для все большего числа приложений.
  3. Состояние глобальной переменной всегда совершенно ненадежно, потому что весь ваш код может с ней что-то делать.
  4. Их действительно сложно проверить.
  5. Они затрудняют вызов API. «Вы должны не забыть вызвать SET_MAGIC_VARIABLE () перед вызовом API» - это просто умолять кого-то забыть вызвать его. Это делает использование API подверженным ошибкам, что приводит к трудностям поиска ошибок. Используя его как обычный параметр, вы заставляете вызывающую сторону правильно указывать значение.

Просто передайте ссылку на функции, которые в этом нуждаются. Это не так сложно.


3
Ну, у вас может быть глобальный класс конфигурации, который инкапсулирует блокировку, и IS предназначен для изменения состояния в любое время. Я бы выбрал этот подход вместо создания экземпляров программ чтения конфигураций из 1000-кратных мест в коде. Но да, непредсказуемость - это самое страшное в них.
Кодер

6
@ Кодер: Обратите внимание, что разумной альтернативой глобальному является не «считыватели конфигурации из 1000-кратных мест в коде», а один считыватель конфигурации, который создает объект конфигурации, методы которого могут быть приняты в качестве параметра (-> внедрение зависимости).
слеске

2
Nitpicking: Почему проще использовать grep для типа, чем для глобального? И вопрос касается глобальных переменных только для чтения, поэтому пункты 2 и 3 не актуальны
MarkJ

2
@MarkJ: Я надеюсь, что никто, скажем, никогда не скрывал это имя.
DeadMG

2
@DeadMG, Re "... всегда абсолютно ненадежен ..", то же самое касается внедрения зависимостей. То, что вы сделали его параметром, не означает, что объект гарантированно имеет фиксированное состояние. Где-то внизу функции вы могли бы сделать вызов другой функции, которая изменяет состояние введенной переменной вверху.
Pacerier

34

Если вы говорите «состояние», это обычно означает «изменяемое состояние». И глобальное изменяемое состояние является абсолютно злым, потому что это означает, что любая часть программы может влиять на любую другую часть (путем изменения глобального состояния).

Представьте себе отладку неизвестной программы: вы обнаружите, что функция A ведет себя определенным образом для определенных входных параметров, но иногда она работает по-разному для тех же параметров. Вы обнаружите, что он использует глобальную переменную x .

Вы ищете места, которые изменяют x , и обнаруживаете, что есть пять мест, которые изменяют его. Теперь удачи, чтобы выяснить, в каких случаях функция A делает то, что ...


Так что неизменное глобальное государство не так уж и плохо?
FrustratedWithFormsDesigner

50
Я бы сказал, что неизменное глобальное состояние - это хорошо известная хорошая практика, известная как «константы».
Теластин

6
Неизменное глобальное государство не зло, это просто плохо :-). Это по-прежнему проблематично из-за вводимой связи (усложняет изменения, повторное использование и модульное тестирование), но создает гораздо меньше проблем, поэтому в простых случаях это обычно приемлемо.
слеске

2
Если кто-то использует глобальные переменные, то только один фрагмент кода должен его изменить. Остальные могут свободно читать. Проблемы других, меняющих его, не уходят с функциями инкапсуляции и доступа. Это не то, для чего эти конструкции.
phkahler

1
@Pacerier: Да, изменить широко используемый интерфейс сложно, независимо от того, используется ли он как глобальная или локальная переменная. Однако это не зависит от моей точки зрения, заключающейся в том, что взаимодействие различных частей кода трудно понять с помощью глобальных переменных.
Слёске

9

Вы как бы ответили на свой вопрос. С ними трудно справляться, когда они «злоупотребляют», но могут быть полезными и [несколько] предсказуемыми при правильном использовании кем-то, кто знает, как их содержать. Обслуживание и внесение изменений в / на глобалы обычно является кошмаром, усугубляемым при увеличении размера приложения.

Опытные программисты, которые могут определить разницу между глобальными переменными, являющимися единственным вариантом, и их легко исправить, могут иметь минимальные проблемы при их использовании. Но бесконечные возможные проблемы, которые могут возникнуть при их использовании, требуют совета против их использования.

редактировать: чтобы уточнить, что я имею в виду, глобалы непредсказуемы по своей природе. Как и в случае с любым непредсказуемым, вы можете предпринять шаги для сдерживания непредсказуемости, но всегда есть пределы тому, что можно сделать. Добавьте к этому хлопот новых разработчиков, присоединяющихся к проекту, которому приходится иметь дело с относительно неизвестными переменными, рекомендации против использования глобальных переменных должны быть понятными.


9

Есть много проблем с Singletons - вот две самые большие проблемы в моей голове.

  • Это делает модульное тестирование проблематичным. Глобальное состояние может быть загрязнено от одного теста к другому

  • Он применяет жесткое правило «один-единственный-один», которое, хотя оно и не могло измениться, внезапно меняется. Целый набор утилитарного кода, который использовал глобально доступный объект, должен быть изменен.

Сказав это, большинство систем нуждаются в больших глобальных объектах. Это большие и дорогие элементы (например, менеджеры соединений с базой данных) или содержащие всеобъемлющую информацию о состоянии (например, информацию о блокировке).

Альтернативой Singleton является создание этих больших глобальных объектов при запуске и передача их в качестве параметров всем классам или методам, которым необходим доступ к этому объекту.

Проблема здесь в том, что вы в конечном итоге играете в «пас-посылку». У вас есть график компонентов и их зависимостей, и некоторые классы создают другие классы, и каждый из них должен содержать несколько компонентов зависимостей только потому, что они нужны их порожденным компонентам (или компонентам порожденных компонентов).

Вы сталкиваетесь с новыми проблемами обслуживания. Пример: Внезапно ваш "WidgetFactory", компонент глубоко в графике, нуждается в объекте таймера, который вы хотите смоделировать. Однако «WidgetFactory» создается с помощью «WidgetBuilder», который является частью «WidgetCreationManager», и вам необходимо иметь три класса, которые знают об этом объекте таймера, даже если его использует только один. Вы обнаружите, что хотите отказаться и вернуться к Singletons, и просто сделать этот объект таймера глобально доступным.

К счастью, именно эту проблему решает инфраструктура внедрения зависимостей. Вы можете просто указать каркасу, какие классы ему нужно создать, и он использует отражение, чтобы выяснить для вас граф зависимостей, и автоматически создает каждый объект, когда он необходим.

Итак, в общем, Singletons плохи, и альтернатива состоит в том, чтобы использовать платформу Dependency Injection.

Я использую Замок Виндзор, но вы избалованы выбором. См. Эту страницу еще в 2008 году для списка доступных структур.


8

Прежде всего, чтобы внедрение зависимостей было «отслеживающим состояние», вам необходимо использовать синглтоны, поэтому люди, которые говорят, что это как-то альтернатива, ошибаются. Люди постоянно используют объекты глобального контекста ... Даже, например, состояние сеанса по сути является глобальной переменной. Передача всего, будь то путем внедрения зависимости или нет, не всегда является лучшим решением. В настоящее время я работаю над очень большим приложением, которое использует много объектов глобального контекста (синглеты, внедренные через контейнер IoC), и это никогда не было проблемой для отладки. Особенно в архитектуре, управляемой событиями, может быть предпочтительнее использовать объекты глобального контекста, чем передавать то, что изменилось. Зависит от того, кого вы спрашиваете.

Любым может быть злоупотреблено, и это также зависит от типа приложения. Использование статических переменных, например, в веб-приложении, совершенно отличается от настольного приложения. Если вы можете избежать глобальных переменных, то сделайте это, но иногда они имеют свое применение. По крайней мере, убедитесь, что ваши глобальные данные находятся в понятном контекстном объекте. Что касается отладки, ничего не может решить стек вызовов и некоторые точки останова.

Я хочу подчеркнуть, что слепое использование глобальных переменных - плохая идея. Функции должны быть многоразовыми и не должны заботиться о том, откуда поступают данные - обращение к глобальным переменным связывает функцию с определенным вводом данных. Вот почему это должно быть передано и почему внедрение зависимостей может быть полезным, хотя вы все еще работаете с централизованным хранилищем контекста (через синглтоны).

Кстати ... Некоторые люди думают, что внедрение зависимостей плохо, в том числе создатель Linq, но это не остановит людей от его использования, в том числе и меня. В конечном итоге опыт будет вашим лучшим учителем. Есть времена, чтобы следовать правилам и времена, чтобы нарушать их.


4

Поскольку некоторые другие ответы здесь делают различие между изменяемым и неизменным глобальным состоянием, я хотел бы предположить, что даже неизменяемые глобальные переменные / настройки часто раздражают .

Рассмотрим вопрос:

... Допустим, мне нужна глобальная конфигурация для моего приложения, например, пути к системным папкам или учетные данные базы данных всего приложения. ...

Да, для небольшой программы это, вероятно, не проблема, но как только вы сделаете это с компонентами в немного большей системе, написание автоматизированных тестов внезапно станет сложнее, потому что все тесты (выполняющиеся в одном и том же процессе) должны работать с то же значение глобальной конфигурации.

Если все данные конфигурации передаются в явном виде, тестирование компонентов становится намного проще, и вам не нужно беспокоиться о том, как загрузить глобальное значение конфигурации для нескольких тестов, возможно, даже параллельно.


3

Ну, например, вы можете столкнуться с той же проблемой, что и с одиночками. То, что сегодня выглядит как «глобальная вещь, из которой мне нужен только один», внезапно превратится в то, что вам нужно в будущем.

Например, сегодня вы создаете эту глобальную систему конфигурации, потому что вам нужна одна глобальная конфигурация для всей системы. Через несколько лет вы портируете на другую систему, и кто-то говорит: «Эй, вы знаете, это могло бы работать лучше, если бы была одна общая глобальная конфигурация и одна конфигурация, специфичная для платформы». Внезапно у вас есть вся эта работа, чтобы сделать ваши глобальные структуры не глобальными, так что вы можете иметь несколько.

(Это не случайный пример ... это произошло с нашей системой конфигурации в проекте, в котором я сейчас нахожусь.)

Учитывая, что стоимость создания чего-то неглобального обычно тривиальна, делать это глупо. Вы просто создаете будущие проблемы.


Ваш пример не то, что имел в виду OP. Вы четко говорите о создании абстрактных конфигураций (просто структура данных менеджера конфигурации, без каких-либо ключей предпочтений для конкретного приложения) несколько раз, потому что вы хотите разделить все ключи в работающем экземпляре вашей программы на несколько экземпляров вашего класса менеджера конфигурации. (Скажем, каждый такой экземпляр может обрабатывать один файл конфигурации, например).
Джо Со

3

Любопытно, что другая проблема заключается в том, что они затрудняют масштабирование приложения, поскольку они не являются «глобальными». Область действия глобальной переменной - это процесс.

Если вы хотите масштабировать свое приложение, используя несколько процессов или работая на нескольких серверах, вы не можете. По крайней мере, пока вы не выделите все глобалы и не замените их каким-либо другим механизмом.


3

Почему Глобальное Государство так Зло?

Изменчивое глобальное состояние является злом, потому что нашему мозгу очень трудно учитывать более нескольких параметров одновременно и выяснять, как они сочетаются как с точки зрения времени, так и с точки зрения ценности, чтобы что-то повлиять.

Поэтому мы очень плохо отлаживаем или тестируем объект, поведение которого имеет более чем несколько внешних причин, которые должны быть изменены во время выполнения программы. Не говоря уже о том, когда мы должны рассуждать о десятках этих объектов, взятых вместе.


3

Языки, разработанные для безопасного и надежного проектирования систем, часто полностью избавляются от изменяемого состояния. (Возможно, это означает отсутствие глобальных переменных, поскольку неизменяемые объекты в некотором смысле не имеют состояния с сохранением состояния, так как они никогда не имеют переходов между состояниями.)

Джо-Э является одним из примеров, и Дэвид Вагнер объясняет решение следующим образом:

Анализ того, кто имеет доступ к объекту, и принцип наименьших привилегий оба подрываются, когда возможности хранятся в глобальных переменных, и, следовательно, потенциально могут быть прочитаны любой частью программы. Как только объект становится глобально доступным, больше невозможно ограничивать область анализа: доступ к объекту - это привилегия, которую нельзя удерживать ни в одном коде программы. Joe-E избегает этих проблем, проверяя, что глобальная область не содержит никаких возможностей, только неизменяемые данные.

Так что один из способов думать об этом

  1. Программирование - это проблема распределенных рассуждений. Программисты в больших проектах должны разделить программу на части, которые могут быть рассуждены людьми.
  2. Чем меньше объем, тем легче рассуждать. Это относится как к отдельным лицам, так и к инструментам статического анализа, которые пытаются доказать свойства системы, а также к тестам, которые должны проверять свойства системы.
  3. Существенные источники авторитета, доступные по всему миру, затрудняют рассмотрение свойств системы.

Следовательно, глобально изменяемое состояние затрудняет

  1. разработка надежных систем,
  2. сложнее доказать свойства системы, и
  3. Сложнее быть уверенным, что ваши тесты тестируются в объеме, аналогичном вашей производственной среде.

Глобальное изменяемое состояние похоже на DLL ад . Со временем для разных частей большой системы потребуется несколько отличное поведение от общих частей изменяемого состояния. Устранение несоответствий DLL ада и общего изменчивого состояния требует крупномасштабной координации между разрозненными группами. Эти проблемы не возникли бы, если бы глобальное состояние было правильно определено с самого начала.


2

Глобалы не так уж и плохи. Как указывалось в нескольких других ответах, реальная проблема с ними заключается в том, что сегодня ваш глобальный путь к папке может быть одним из нескольких или даже сотен. Если вы пишете быструю разовую программу, используйте глобальные переменные, если это проще. Однако, как правило, учитывается кратность, даже если вы думаете, что она вам нужна. Нелегко реструктурировать большую сложную программу, которая вдруг должна общаться с двумя базами данных.

Но они не вредят надежности. Любые данные, на которые ссылаются во многих местах вашей программы, могут вызвать проблемы, если они неожиданно изменятся. Перечислители задыхаются, когда перечисляемая коллекция изменяется в середине перечисления. События очереди событий могут подшутить друг над другом. Темы всегда могут нанести вред. Все, что не является локальной переменной или неизменным полем, является проблемой. Глобальные переменные - это проблема такого рода, но вы не сможете ее исправить, сделав их неглобальными.

Если вы собираетесь записывать в файл, а путь к папке меняется, изменения и запись должны быть синхронизированы. (Как одна из тысячи вещей, которые могут пойти не так, скажем, вы берете путь, затем этот каталог удаляется, затем путь к папке меняется на хороший каталог, затем вы пытаетесь записать в удаленный каталог.) Существует проблема, существует ли путь к папке является глобальным или является одним из тысячи, используемых программой в настоящее время.

Существует реальная проблема с полями, к которым могут обращаться различные события в очереди, разные уровни рекурсии или разные потоки. Чтобы сделать это простым (и упрощенным): локальные переменные - это хорошо, а поля - плохо. Но прежние глобальные поля все еще будут полями, поэтому эта (хотя и критически важная) проблема не относится к статусу «Хорошо» или «Зло» глобальных полей.

Дополнение: Многопоточность Проблемы:

(Обратите внимание, что у вас могут быть похожие проблемы с очередью событий или рекурсивными вызовами, но многопоточность является наихудшим.) Рассмотрим следующий код:

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

Если filePathэто локальная переменная или какая-то константа, ваша программа не выйдет из строя при запуске, потому что filePathимеет значение null. Проверка всегда работает. Никакой другой поток не может изменить свое значение. В противном случае , нет никаких гарантий. Когда я начал писать многопоточные программы на Java, я постоянно получал исключения NullPointerException в таких строках. Любыедругой поток может изменить значение в любое время, и они часто это делают. Как указывают несколько других ответов, это создает серьезные проблемы для тестирования. Вышеупомянутое утверждение может сработать миллиард раз, пройдя всестороннее и всестороннее тестирование, а затем взорваться один раз в производстве. Пользователи не смогут воспроизвести проблему, и это больше не повторится, пока они сами не убедят себя в том, что видят вещи, и забыли об этом.

У глобалов определенно есть эта проблема, и если вы можете устранить их полностью или заменить их константами или локальными переменными, это очень хорошая вещь. Если у вас есть код без сохранения состояния, работающий на веб-сервере, вы, вероятно, можете. Как правило, все ваши проблемы с многопоточностью могут быть приняты базой данных.

Но если ваша программа должна запоминать вещи от одного действия пользователя к другому, у вас будут поля, доступные для любых работающих потоков. Переключение глобального на такое неглобальное поле не поможет надежности.


1
Можете ли вы уточнить, что вы подразумеваете под этим ?: «Все, что не является локальной переменной или неизменяемым полем, является проблемой. Глобальные проблемы - это проблема такого рода, но вы не собираетесь ее решать, делая их неглобальными».
Андрес Ф.

1
@AndresF .: Я расширил свой ответ. Я думаю, что я использую настольный подход, где большинство людей на этой странице больше используют серверный код с базой данных. «Глобальный» может означать разные вещи в этих случаях.
RalphChapin

2

Государство неизбежно в любом реальном приложении. Вы можете обернуть его любым удобным для вас способом, но электронная таблица должна содержать данные в ячейках. Вы можете создавать объекты ячеек только с функциями в качестве интерфейса, но это не ограничивает количество мест, которые могут вызывать метод в ячейке и изменять данные. Вы строите целые иерархии объектов, пытаясь скрыть интерфейсы, чтобы другие части кода не моглиизменить данные по умолчанию. Это не препятствует произвольной передаче ссылки на содержащий объект. Ничто из этого не устраняет проблемы параллелизма само по себе. Это затрудняет распространение доступа к данным, но на самом деле не устраняет проблемы, возникающие в глобальных масштабах. Если кто-то хочет изменить часть состояния, он собирается сделать это, если он будет глобальным или с помощью сложного API (позднее это только обескуражит, а не предотвратит).

Истинная причина не использовать глобальное хранилище - избегать конфликтов имен. Если вы загружаете несколько модулей, которые объявляют одно и то же глобальное имя, у вас либо неопределенное поведение (очень трудно отлаживать, потому что юнит-тесты пройдут), либо ошибка компоновщика (я думаю, C - предупреждает ли ваш компоновщик об этом или нет?).

Если вы хотите повторно использовать код, вы должны иметь возможность захватить модуль из другого места и не допустить его случайного вмешательства в ваш глобальный объект, потому что они использовали один с тем же именем. Или, если вам повезет и вы получите ошибку, вам не нужно менять все ссылки в одном разделе кода, чтобы предотвратить столкновение.


1

Когда легко увидеть и получить доступ ко всему глобальному состоянию, программисты неизменно делают это. То, что вы получаете, является невысказанным и трудно отслеживать зависимости (int blahblah означает, что массив foo действителен во всем). По сути, это делает практически невозможным поддержание программных инвариантов, поскольку все может быть изменено независимо. У someInt есть отношения между otherInt, с которыми сложно управлять, и сложнее доказать, можете ли вы изменить это напрямую в любое время.

Тем не менее, это может быть сделано (еще тогда, когда это было единственным способом в некоторых системах), но эти навыки теряются. Они вращаются главным образом вокруг соглашений о кодировании и наименовании - поле движется по уважительной причине. Ваш компилятор и компоновщик лучше справляются с проверкой инвариантов в защищенных / личных данных классов / модулей, чем полагаясь на то, что люди следуют генеральному плану и читают исходный код.


1
«но эти навыки утеряны» ... еще не совсем. Недавно я работал в доме программного обеспечения, который клянется "Clarion", инструментом для генерации кода, который имеет свой собственный базовый язык, в котором отсутствуют такие функции, как передача аргументов подпрограммам. Сидящие разработчики не были удовлетворены какими-либо предложениями о «изменить» или «модернизировать», наконец, сыт по горло моими замечаниями и изобразил меня неполноценным и некомпетентным. Я должен был уйти ...
Луи Сомерс

1

Я не собираюсь говорить, являются ли глобальные переменные хорошими или плохими, но я собираюсь добавить к обсуждению тот факт, что если вы не используете глобальное состояние, то вы, вероятно, тратите много памяти, особенно когда вы используете classess для хранения их зависимостей в полях.

Для глобального государства такой проблемы нет, все глобально.

Например: представьте себе следующий сценарий: у вас есть сетка 10x10, которая состоит из классов "Board" и "Tile".

Если вы хотите сделать это ООП способом, вы, вероятно, передадите объект "Board" в каждую "плитку". Предположим теперь, что «Tile» имеет 2 «байтовых» поля типа, хранящих его координаты. Общее количество памяти, которое потребуется 32-битной машине для одного тайла, составит (1 + 1 + 4 = 6) байтов: 1 для координаты x, 1 для координаты y и 4 для указателя на доску. Это дает в общей сложности 600 байтов для установки плитки 10x10

Теперь для случая, когда доска находится в глобальной области видимости, для одного объекта, доступного из каждой плитки, вам потребуется всего лишь 2 байта памяти на каждую плитку, то есть байты координат x и y. Это дало бы только 200 байтов.

Таким образом, в этом случае вы получаете 1/3 использования памяти, если вы используете только глобальное состояние.

Это, помимо всего прочего, я полагаю, является причиной того, что глобальная область все еще остается в (относительно) языках низкого уровня, таких как C ++


1
Это сильно зависит от языка. В языках, где программы работают постоянно, может быть верно, что вы можете сэкономить память, используя глобальное пространство и одиночные коды вместо создания множества классов, но даже этот аргумент шаток. Это все о приоритетах. В таких языках, как PHP (который запускается один раз для каждого запроса, а объекты не сохраняются), даже этот аргумент является спорным.
Мадара Учиха

1
@MadaraUchiha Нет, этот аргумент не шаткий ни в коем случае. Это объективные факты, если только некоторая виртуальная машина или другой скомпилированный язык не выполнит некоторую жесткую оптимизацию кода с этим типом проблемы. В противном случае это все еще актуально. Даже с серверными программами, которые используют «один выстрел», память резервируется на время выполнения. С серверами с высокой нагрузкой это может быть критической точкой.
luke1985

0

Есть несколько факторов, которые следует учитывать в глобальном состоянии:

  1. Пространство памяти программиста
  2. Неизменные глобалы / написать один раз глобалы.
  3. Изменчивые глобалы
  4. Зависимость от глобалов.

Чем больше глобальных значений у вас есть, тем больше вероятность введения дубликатов и, следовательно, разрушения, когда дубликаты не синхронизируются. Хранение всех глобалов в вашей неверной человеческой памяти становится и необходимостью, и болью.

С неизменяемыми значениями / записью, как правило, все в порядке, но следите за ошибками последовательности инициализации.

Изменчивые глобалы часто принимают за неизменные глобалы ...

Функция, которая эффективно использует глобальные переменные, имеет дополнительные «скрытые» параметры, что усложняет рефакторинг.

Глобальное государство не зло, но оно имеет определенную стоимость - используйте его, когда выгода превышает стоимость.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.