Я много раз слышал на этом форуме, что использование глобальной переменной – это мертвый грех, и внедрение синглтона – преступление.
Мне пришло в голову, что старые добрые константы несут в себе все черты этих бесчестных практик: они доступны по всему миру и, без сомнения, представляют собой глобальное состояние.
Итак, вопрос в том, должен ли мы также объявлять джихад константам и использовать все современные вещи, такие как DI, IoC или другие стильные слова?
Основная причина, по которой глобальные переменные считаются плохой практикой, состоит в том, что они могут быть изменены в одной части системы и использованы в другой части, без прямой связи между этими двумя частями кода.
Это приводит к потенциальным ошибкам, потому что можно написать код, который использует глобальную переменную, не зная (или не рассматривая) все места, где она используется, и способы ее изменения. Или наоборот, напишите код, который вносит изменения в глобальный, не понимая, какое влияние это изменение может иметь в других несвязанных частях вашего кода.
Константы не разделяют эту проблему, потому что они … ну, постоянные. После того, как они определены, они не могут быть изменены, и, таким образом, выданное, описанное в предыдущем абзаце, не может произойти.
Поэтому они могут использоваться в глобальном масштабе.
Тем не менее, я видел немного плохо написанный PHP-код, который использует define
для создания констант, но декларирует константы по-разному в разных обстоятельствах. Это неправильное использование констант: константа должна быть абсолютно фиксированной величиной; это должно быть когда-либо единственной ценностью. Если у вас есть что-то, что потенциально может быть разными значениями при разных прогонах программы, то это не должно определяться как константа. Подобная вещь действительно должна быть переменной, а затем должна следовать тем же правилам, что и другие переменные.
Такое неправильное использование может происходить только на языке сценариев, таком как PHP; это не могло произойти на скомпилированном языке, потому что вы можете определять константу только один раз, в одном месте и фиксированное значение.
В общем, да, избегайте констант. Они вводят связь между потребителями и глобальными масштабами. То есть потребители полагаются на что-то снаружи. Это неочевидно, например
class Foo { public function doSomething() { if (ENV === ENV_DEV) { // do something this way } else { // do something that way } } }
Не зная внутренности doSomething
, вы не будете знать, что существует зависимость от глобальной области с такой константой. Поэтому, помимо того, что ваш код несколько сложнее понять, вы также ограничиваете, как его можно использовать повторно.
Вышеприведенное также верно для констант, которые имеют только одно значение, например
public function log($message) { fwrite(LOGFILE, $message); }
Здесь константа будет указывать на файловый ресурс, определенный где-то снаружи, как
define('LOGFILE', fopen('/path/to/logfile'));
И это так же неочевидно, как использование ENV
. Это зависимость, которая требует, чтобы что-то вне класса существовало. И я должен знать, что для работы с этим объектом. Поскольку класс, использующий эту константу, скрывает эту деталь, я могу попытаться записать что-то, не убедившись, что константа существует, и тогда я задаюсь вопросом, почему она не работает. Он даже не должен быть ресурсом, LOGFILE
может просто содержать путь в виде строки. Тот же результат.
Опираясь на глобальные константы в ваших потребителях, вам также потребуется настроить глобальное состояние в своих модульных тестах. Это то, чего вы, как правило, хотите избежать, даже если константы являются фиксированным значением, потому что точка единичного теста заключается в том, чтобы протестировать устройство отдельно и вынудить среду в определенное состояние мешать этому.
Более того, использование глобальных констант всегда создает угрозу постоянных конфликтов между различными библиотеками. Как правило, не помещайте ничего в глобальную область. Используйте пространства имен для констант кластера, если вам нужно их использовать.
Тем не менее, обратите внимание, что константы с именами по-прежнему имеют те же проблемы в отношении связи, что и константы класса. Пока эта связь находится в одном и том же пространстве имен, она менее критична, но как только вы начинаете связываться с константами из разных пространств имен, вы снова препятствуете повторному использованию. В этом отношении рассмотрим любой открытый API.
Альтернативой использованию констант было бы использование неизменяемых объектов Value, например:
class Environment { private $value; public function __construct($value) { $this->assertValueIsAllowedValue($value); $this->value = $value; } public function getValue() { // …
Таким образом, вы можете передать эти значения объектам, которые им нужны, в дополнение к тому, чтобы убедиться, что значения действительны. Как всегда, YMMV. Это просто вариант. Единственная константа не сделает ваш код непригодным для использования, но в значительной степени опираясь на константы, это будет иметь пагубный эффект, так как эмпирическое правило попытается свести их к минимуму.
На соответствующем примечании, вы также можете быть заинтересованы в:
Существует большая разница между глобальной переменной и глобальной константой .
Основная причина, по которой глобальная переменная игнорируется, заключается в том, что она может быть изменена любым способом в любое время. Он может вводить все виды скрытых зависимостей в порядке выполнения вызовов / выполнения и может приводить к тому, что идентичный код работает иногда, а не другие, в зависимости от того, как и как глобальное изменение было изменено. Очевидно, что плохое моджо можно увеличить, если вы имеете дело с параллелизмом или параллелизмом.
Глобальная константа (или должна быть) одинакова во всем вашем коде. Как только ваш код начинает выполняться, гарантируется, что каждый бит просмотра кода будет видеть одно и то же каждый раз. Это означает, что нет никакой опасности вводить случайные зависимости. Использование констант может быть очень хорошим для повышения надежности, поскольку это означает, что вам не нужно обновлять значение в нескольких местах, если вам нужно изменить свой код. (Никогда не недооценивайте человеческую ошибку!)
Синглтоны – это еще одна проблема. Это часто злоупотребляемый шаблон дизайна, который может в конечном итоге стать объектно-ориентированной версией глобальных переменных. На некоторых языках (например, на C ++) это также может оказаться очень неправильным, если вы не будете осторожны с порядком инициализации. Однако иногда это может быть полезно, хотя обычно есть лучшие альтернативы (хотя иногда требуется немного больше работы).
РЕДАКТИРОВАТЬ: Чтобы кратко остановиться, вы упомянули в своем вопросе, что глобальные константы вводят «глобальное состояние когда-либо». Это не очень точно, потому что глобальная константа (или должна быть) исправлена так же, как исходный код исправлен. Он определяет статический характер программы, тогда как «состояние» обычно понимается как динамическая концепция времени выполнения (т. Е. Материал, который может измениться).