Прежде чем мы начнем, позвольте мне сказать, что я хорошо знаю концепции абстракции и внедрения зависимости. Мне не нужно, чтобы мои глаза открылись здесь.
Ну, большинство из нас (слишком) часто говорят, не понимая: «Не используйте глобальные переменные» или «Синглтоны - это зло, потому что они глобальны». Но что на самом деле это так плохо о зловещем глобальном состоянии?
Допустим, мне нужна глобальная конфигурация для моего приложения, например, пути к системным папкам или учетные данные базы данных всего приложения.
В этом случае я не вижу хорошего решения, кроме как предоставить эти настройки в каком-то глобальном пространстве, которое будет общедоступным для всего приложения.
Я знаю , что это плохо злоупотребляют , но это глобальное пространство действительно ЧТО зло? И если это так, какие есть хорошие альтернативы?
источник
Ответы:
Вкратце, это делает состояние программы непредсказуемым.
Чтобы уточнить, представьте, что у вас есть пара объектов, которые используют одну и ту же глобальную переменную. Предполагая, что вы не используете источник случайности ни в одном из модулей, выход конкретного метода можно предсказать (и, следовательно, протестировать), если состояние системы известно до выполнения метода.
Однако, если метод в одном из объектов вызывает побочный эффект, который изменяет значение общего глобального состояния, вы больше не знаете, что такое начальное состояние, когда выполняете метод в другом объекте. Теперь вы больше не можете предсказать, какой вывод вы получите при выполнении метода, и, следовательно, вы не можете его протестировать.
На академическом уровне это может показаться не таким уж серьезным, но возможность модульного тестирования кода является важным шагом в процессе доказательства его правильности (или, по крайней мере, соответствия цели).
В реальном мире это может иметь очень серьезные последствия. Предположим, у вас есть один класс, который заполняет глобальную структуру данных, и другой класс, который использует данные в этой структуре данных, изменяя их состояние или уничтожая их в процессе. Если класс процессора выполняет метод до того, как будет создан класс популяции, в результате класс процессора, вероятно, будет иметь неполные данные для обработки, а структура данных, над которой работал класс популяции, может быть повреждена или уничтожена. Поведение программы в этих условиях становится совершенно непредсказуемым и, вероятно, приведет к эпической потере.
Кроме того, глобальное состояние ухудшает читабельность вашего кода. Если у вашего кода есть внешняя зависимость, которая явно не введена в код, тогда любой, кто получит работу по поддержке вашего кода, должен будет искать его, чтобы выяснить, откуда он взялся.
Что касается того, какие альтернативы существуют, то вообще невозможно вообще не иметь глобального состояния, но на практике обычно можно ограничить глобальное состояние одним объектом, который охватывает все остальные, и на который никогда нельзя ссылаться, полагаясь на правила области видимости. языка, который вы используете. Если конкретному объекту необходимо определенное состояние, он должен явно запросить его, передав его в качестве аргумента своему конструктору или с помощью метода установки. Это известно как инъекция зависимости.
Может показаться глупым переходить в состояние, к которому вы уже можете получить доступ из-за правил области видимости любого языка, который вы используете, но преимущества огромны. Теперь, если кто-то смотрит на код изолированно, становится ясно, в каком состоянии он нуждается и откуда он поступает. Он также имеет огромные преимущества в отношении гибкости вашего модуля кода и, следовательно, возможности его повторного использования в различных контекстах. Если состояние передается и изменения состояния являются локальными для блока кода, то вы можете передать любое состояние, которое вам нравится (если это правильный тип данных), и ваш код его обработает. Код, написанный в этом стиле, имеет тенденцию выглядеть как набор свободно связанных компонентов, которые можно легко заменить. Код модуля не должен заботиться о том, откуда приходит состояние, а только о том, как его обрабатывать.
Существует множество других причин, по которым переходящее состояние намного превосходит доверие к глобальному состоянию. Этот ответ ни в коем случае не является исчерпывающим. Возможно, вы могли бы написать целую книгу о том, почему глобальное состояние плохо.
источник
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 десятилетия сейчас ...)Изменчивое глобальное государство является злом по многим причинам:
Альтернативы изменчивому глобальному состоянию:
источник
grep
для имени типа выяснить, какие функции его используют.Просто передайте ссылку на функции, которые в этом нуждаются. Это не так сложно.
источник
Если вы говорите «состояние», это обычно означает «изменяемое состояние». И глобальное изменяемое состояние является абсолютно злым, потому что это означает, что любая часть программы может влиять на любую другую часть (путем изменения глобального состояния).
Представьте себе отладку неизвестной программы: вы обнаружите, что функция A ведет себя определенным образом для определенных входных параметров, но иногда она работает по-разному для тех же параметров. Вы обнаружите, что он использует глобальную переменную x .
Вы ищете места, которые изменяют x , и обнаруживаете, что есть пять мест, которые изменяют его. Теперь удачи, чтобы выяснить, в каких случаях функция A делает то, что ...
источник
Вы как бы ответили на свой вопрос. С ними трудно справляться, когда они «злоупотребляют», но могут быть полезными и [несколько] предсказуемыми при правильном использовании кем-то, кто знает, как их содержать. Обслуживание и внесение изменений в / на глобалы обычно является кошмаром, усугубляемым при увеличении размера приложения.
Опытные программисты, которые могут определить разницу между глобальными переменными, являющимися единственным вариантом, и их легко исправить, могут иметь минимальные проблемы при их использовании. Но бесконечные возможные проблемы, которые могут возникнуть при их использовании, требуют совета против их использования.
редактировать: чтобы уточнить, что я имею в виду, глобалы непредсказуемы по своей природе. Как и в случае с любым непредсказуемым, вы можете предпринять шаги для сдерживания непредсказуемости, но всегда есть пределы тому, что можно сделать. Добавьте к этому хлопот новых разработчиков, присоединяющихся к проекту, которому приходится иметь дело с относительно неизвестными переменными, рекомендации против использования глобальных переменных должны быть понятными.
источник
Есть много проблем с Singletons - вот две самые большие проблемы в моей голове.
Это делает модульное тестирование проблематичным. Глобальное состояние может быть загрязнено от одного теста к другому
Он применяет жесткое правило «один-единственный-один», которое, хотя оно и не могло измениться, внезапно меняется. Целый набор утилитарного кода, который использовал глобально доступный объект, должен быть изменен.
Сказав это, большинство систем нуждаются в больших глобальных объектах. Это большие и дорогие элементы (например, менеджеры соединений с базой данных) или содержащие всеобъемлющую информацию о состоянии (например, информацию о блокировке).
Альтернативой Singleton является создание этих больших глобальных объектов при запуске и передача их в качестве параметров всем классам или методам, которым необходим доступ к этому объекту.
Проблема здесь в том, что вы в конечном итоге играете в «пас-посылку». У вас есть график компонентов и их зависимостей, и некоторые классы создают другие классы, и каждый из них должен содержать несколько компонентов зависимостей только потому, что они нужны их порожденным компонентам (или компонентам порожденных компонентов).
Вы сталкиваетесь с новыми проблемами обслуживания. Пример: Внезапно ваш "WidgetFactory", компонент глубоко в графике, нуждается в объекте таймера, который вы хотите смоделировать. Однако «WidgetFactory» создается с помощью «WidgetBuilder», который является частью «WidgetCreationManager», и вам необходимо иметь три класса, которые знают об этом объекте таймера, даже если его использует только один. Вы обнаружите, что хотите отказаться и вернуться к Singletons, и просто сделать этот объект таймера глобально доступным.
К счастью, именно эту проблему решает инфраструктура внедрения зависимостей. Вы можете просто указать каркасу, какие классы ему нужно создать, и он использует отражение, чтобы выяснить для вас граф зависимостей, и автоматически создает каждый объект, когда он необходим.
Итак, в общем, Singletons плохи, и альтернатива состоит в том, чтобы использовать платформу Dependency Injection.
Я использую Замок Виндзор, но вы избалованы выбором. См. Эту страницу еще в 2008 году для списка доступных структур.
источник
Прежде всего, чтобы внедрение зависимостей было «отслеживающим состояние», вам необходимо использовать синглтоны, поэтому люди, которые говорят, что это как-то альтернатива, ошибаются. Люди постоянно используют объекты глобального контекста ... Даже, например, состояние сеанса по сути является глобальной переменной. Передача всего, будь то путем внедрения зависимости или нет, не всегда является лучшим решением. В настоящее время я работаю над очень большим приложением, которое использует много объектов глобального контекста (синглеты, внедренные через контейнер IoC), и это никогда не было проблемой для отладки. Особенно в архитектуре, управляемой событиями, может быть предпочтительнее использовать объекты глобального контекста, чем передавать то, что изменилось. Зависит от того, кого вы спрашиваете.
Любым может быть злоупотреблено, и это также зависит от типа приложения. Использование статических переменных, например, в веб-приложении, совершенно отличается от настольного приложения. Если вы можете избежать глобальных переменных, то сделайте это, но иногда они имеют свое применение. По крайней мере, убедитесь, что ваши глобальные данные находятся в понятном контекстном объекте. Что касается отладки, ничего не может решить стек вызовов и некоторые точки останова.
Я хочу подчеркнуть, что слепое использование глобальных переменных - плохая идея. Функции должны быть многоразовыми и не должны заботиться о том, откуда поступают данные - обращение к глобальным переменным связывает функцию с определенным вводом данных. Вот почему это должно быть передано и почему внедрение зависимостей может быть полезным, хотя вы все еще работаете с централизованным хранилищем контекста (через синглтоны).
Кстати ... Некоторые люди думают, что внедрение зависимостей плохо, в том числе создатель Linq, но это не остановит людей от его использования, в том числе и меня. В конечном итоге опыт будет вашим лучшим учителем. Есть времена, чтобы следовать правилам и времена, чтобы нарушать их.
источник
Поскольку некоторые другие ответы здесь делают различие между изменяемым и неизменным глобальным состоянием, я хотел бы предположить, что даже неизменяемые глобальные переменные / настройки часто раздражают .
Рассмотрим вопрос:
Да, для небольшой программы это, вероятно, не проблема, но как только вы сделаете это с компонентами в немного большей системе, написание автоматизированных тестов внезапно станет сложнее, потому что все тесты (выполняющиеся в одном и том же процессе) должны работать с то же значение глобальной конфигурации.
Если все данные конфигурации передаются в явном виде, тестирование компонентов становится намного проще, и вам не нужно беспокоиться о том, как загрузить глобальное значение конфигурации для нескольких тестов, возможно, даже параллельно.
источник
Ну, например, вы можете столкнуться с той же проблемой, что и с одиночками. То, что сегодня выглядит как «глобальная вещь, из которой мне нужен только один», внезапно превратится в то, что вам нужно в будущем.
Например, сегодня вы создаете эту глобальную систему конфигурации, потому что вам нужна одна глобальная конфигурация для всей системы. Через несколько лет вы портируете на другую систему, и кто-то говорит: «Эй, вы знаете, это могло бы работать лучше, если бы была одна общая глобальная конфигурация и одна конфигурация, специфичная для платформы». Внезапно у вас есть вся эта работа, чтобы сделать ваши глобальные структуры не глобальными, так что вы можете иметь несколько.
(Это не случайный пример ... это произошло с нашей системой конфигурации в проекте, в котором я сейчас нахожусь.)
Учитывая, что стоимость создания чего-то неглобального обычно тривиальна, делать это глупо. Вы просто создаете будущие проблемы.
источник
Любопытно, что другая проблема заключается в том, что они затрудняют масштабирование приложения, поскольку они не являются «глобальными». Область действия глобальной переменной - это процесс.
Если вы хотите масштабировать свое приложение, используя несколько процессов или работая на нескольких серверах, вы не можете. По крайней мере, пока вы не выделите все глобалы и не замените их каким-либо другим механизмом.
источник
Изменчивое глобальное состояние является злом, потому что нашему мозгу очень трудно учитывать более нескольких параметров одновременно и выяснять, как они сочетаются как с точки зрения времени, так и с точки зрения ценности, чтобы что-то повлиять.
Поэтому мы очень плохо отлаживаем или тестируем объект, поведение которого имеет более чем несколько внешних причин, которые должны быть изменены во время выполнения программы. Не говоря уже о том, когда мы должны рассуждать о десятках этих объектов, взятых вместе.
источник
Языки, разработанные для безопасного и надежного проектирования систем, часто полностью избавляются от изменяемого состояния. (Возможно, это означает отсутствие глобальных переменных, поскольку неизменяемые объекты в некотором смысле не имеют состояния с сохранением состояния, так как они никогда не имеют переходов между состояниями.)
Джо-Э является одним из примеров, и Дэвид Вагнер объясняет решение следующим образом:
Так что один из способов думать об этом
Следовательно, глобально изменяемое состояние затрудняет
Глобальное изменяемое состояние похоже на DLL ад . Со временем для разных частей большой системы потребуется несколько отличное поведение от общих частей изменяемого состояния. Устранение несоответствий DLL ада и общего изменчивого состояния требует крупномасштабной координации между разрозненными группами. Эти проблемы не возникли бы, если бы глобальное состояние было правильно определено с самого начала.
источник
Глобалы не так уж и плохи. Как указывалось в нескольких других ответах, реальная проблема с ними заключается в том, что сегодня ваш глобальный путь к папке может быть одним из нескольких или даже сотен. Если вы пишете быструю разовую программу, используйте глобальные переменные, если это проще. Однако, как правило, учитывается кратность, даже если вы думаете, что она вам нужна. Нелегко реструктурировать большую сложную программу, которая вдруг должна общаться с двумя базами данных.
Но они не вредят надежности. Любые данные, на которые ссылаются во многих местах вашей программы, могут вызвать проблемы, если они неожиданно изменятся. Перечислители задыхаются, когда перечисляемая коллекция изменяется в середине перечисления. События очереди событий могут подшутить друг над другом. Темы всегда могут нанести вред. Все, что не является локальной переменной или неизменным полем, является проблемой. Глобальные переменные - это проблема такого рода, но вы не сможете ее исправить, сделав их неглобальными.
Если вы собираетесь записывать в файл, а путь к папке меняется, изменения и запись должны быть синхронизированы. (Как одна из тысячи вещей, которые могут пойти не так, скажем, вы берете путь, затем этот каталог удаляется, затем путь к папке меняется на хороший каталог, затем вы пытаетесь записать в удаленный каталог.) Существует проблема, существует ли путь к папке является глобальным или является одним из тысячи, используемых программой в настоящее время.
Существует реальная проблема с полями, к которым могут обращаться различные события в очереди, разные уровни рекурсии или разные потоки. Чтобы сделать это простым (и упрощенным): локальные переменные - это хорошо, а поля - плохо. Но прежние глобальные поля все еще будут полями, поэтому эта (хотя и критически важная) проблема не относится к статусу «Хорошо» или «Зло» глобальных полей.
Дополнение: Многопоточность Проблемы:
(Обратите внимание, что у вас могут быть похожие проблемы с очередью событий или рекурсивными вызовами, но многопоточность является наихудшим.) Рассмотрим следующий код:
Если
filePath
это локальная переменная или какая-то константа, ваша программа не выйдет из строя при запуске, потому чтоfilePath
имеет значение null. Проверка всегда работает. Никакой другой поток не может изменить свое значение. В противном случае , нет никаких гарантий. Когда я начал писать многопоточные программы на Java, я постоянно получал исключения NullPointerException в таких строках. Любыедругой поток может изменить значение в любое время, и они часто это делают. Как указывают несколько других ответов, это создает серьезные проблемы для тестирования. Вышеупомянутое утверждение может сработать миллиард раз, пройдя всестороннее и всестороннее тестирование, а затем взорваться один раз в производстве. Пользователи не смогут воспроизвести проблему, и это больше не повторится, пока они сами не убедят себя в том, что видят вещи, и забыли об этом.У глобалов определенно есть эта проблема, и если вы можете устранить их полностью или заменить их константами или локальными переменными, это очень хорошая вещь. Если у вас есть код без сохранения состояния, работающий на веб-сервере, вы, вероятно, можете. Как правило, все ваши проблемы с многопоточностью могут быть приняты базой данных.
Но если ваша программа должна запоминать вещи от одного действия пользователя к другому, у вас будут поля, доступные для любых работающих потоков. Переключение глобального на такое неглобальное поле не поможет надежности.
источник
Государство неизбежно в любом реальном приложении. Вы можете обернуть его любым удобным для вас способом, но электронная таблица должна содержать данные в ячейках. Вы можете создавать объекты ячеек только с функциями в качестве интерфейса, но это не ограничивает количество мест, которые могут вызывать метод в ячейке и изменять данные. Вы строите целые иерархии объектов, пытаясь скрыть интерфейсы, чтобы другие части кода не моглиизменить данные по умолчанию. Это не препятствует произвольной передаче ссылки на содержащий объект. Ничто из этого не устраняет проблемы параллелизма само по себе. Это затрудняет распространение доступа к данным, но на самом деле не устраняет проблемы, возникающие в глобальных масштабах. Если кто-то хочет изменить часть состояния, он собирается сделать это, если он будет глобальным или с помощью сложного API (позднее это только обескуражит, а не предотвратит).
Истинная причина не использовать глобальное хранилище - избегать конфликтов имен. Если вы загружаете несколько модулей, которые объявляют одно и то же глобальное имя, у вас либо неопределенное поведение (очень трудно отлаживать, потому что юнит-тесты пройдут), либо ошибка компоновщика (я думаю, C - предупреждает ли ваш компоновщик об этом или нет?).
Если вы хотите повторно использовать код, вы должны иметь возможность захватить модуль из другого места и не допустить его случайного вмешательства в ваш глобальный объект, потому что они использовали один с тем же именем. Или, если вам повезет и вы получите ошибку, вам не нужно менять все ссылки в одном разделе кода, чтобы предотвратить столкновение.
источник
Когда легко увидеть и получить доступ ко всему глобальному состоянию, программисты неизменно делают это. То, что вы получаете, является невысказанным и трудно отслеживать зависимости (int blahblah означает, что массив foo действителен во всем). По сути, это делает практически невозможным поддержание программных инвариантов, поскольку все может быть изменено независимо. У someInt есть отношения между otherInt, с которыми сложно управлять, и сложнее доказать, можете ли вы изменить это напрямую в любое время.
Тем не менее, это может быть сделано (еще тогда, когда это было единственным способом в некоторых системах), но эти навыки теряются. Они вращаются главным образом вокруг соглашений о кодировании и наименовании - поле движется по уважительной причине. Ваш компилятор и компоновщик лучше справляются с проверкой инвариантов в защищенных / личных данных классов / модулей, чем полагаясь на то, что люди следуют генеральному плану и читают исходный код.
источник
Я не собираюсь говорить, являются ли глобальные переменные хорошими или плохими, но я собираюсь добавить к обсуждению тот факт, что если вы не используете глобальное состояние, то вы, вероятно, тратите много памяти, особенно когда вы используете 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 ++
источник
Есть несколько факторов, которые следует учитывать в глобальном состоянии:
Чем больше глобальных значений у вас есть, тем больше вероятность введения дубликатов и, следовательно, разрушения, когда дубликаты не синхронизируются. Хранение всех глобалов в вашей неверной человеческой памяти становится и необходимостью, и болью.
С неизменяемыми значениями / записью, как правило, все в порядке, но следите за ошибками последовательности инициализации.
Изменчивые глобалы часто принимают за неизменные глобалы ...
Функция, которая эффективно использует глобальные переменные, имеет дополнительные «скрытые» параметры, что усложняет рефакторинг.
Глобальное государство не зло, но оно имеет определенную стоимость - используйте его, когда выгода превышает стоимость.
источник