Каждый, кто учился программировать, наверняка слышал, что глобальные переменные — это зло: они ведут к запутанному состоянию программы, могут изменяться из любого места кода и в целом осложняют отладку. Однако статья «Global Variables Are Not the Problem» на сайте Code Style & Taste убедительно показывает, что всё гораздо тоньше. Иногда упорное избегание глобальных переменных может привести к не меньшим проблемам — от неочевидных багов до перегруженного дизайна. Попробуем разобраться, в чём тут дело и почему глобальные переменные не всегда так плохи, как принято считать.
Что пошло не так без глобальных переменных?
🪄 Неожиданные изменения в копиях
Автор приводит пример на JavaScript, где счётчик для функции объявляется как часть объекта obj — и при клонировании объекта (через structuredClone) внезапно «сбивается» логика инкрементов. В итоге программа печатает «3» там, где мы ожидали «5». Интуитивно мы могли подумать: «Хм, всё из-за того, что мы поделили состояние на разные копии». Вариант с глобальной переменной count работает, потому что он не размножается через копирование.
🧩 Опасное дублирование
Когда мы стремимся «инкапсулировать» всё, передавая данные аргументами, велик риск где-то незаметно продублировать или «размножить» информацию. Это и приводит к ошибке, известной как действие на расстоянии (action at a distance): меняем «копию», а она в итоге влияет или не влияет на общий счётчик. Парадоксальным образом, глобальный счётчик работает надёжнее, ведь всегда существует в единственном экземпляре.
Определение глобальных переменных
🚩 Что такое «глобальная» переменная?
По словам автора, любой объект или переменная, которые не передаются в функцию явно, могут считаться «глобальными». Есть несколько градаций:
- 🛡️ Приватный (Private)/ Статический (Static): глобальные внутри одного файла, недоступные извне (например, в C/C++ это static-переменная на уровне файла).
- 🧵 Локальные данные потока (Thread Local): уникальные глобальные переменные на поток (в многопоточных программах).
- ⚙️ Статический член класса (Static Member): «статик»-член класса в некоторых ООП-языках (например, MyClass::sharedResource).
Автор также поднимает вопрос, считать ли глобальным использование функции, которая модифицирует некую «скрытую» переменную. Например, inc() в коде, «печать» в консоль или логирование.
Почему же глобальные переменные (иногда) уместны?
🔥 Наглядность и простота
У нас всегда есть единая точка, где живёт состояние. Это полезно, если нужна некая «мировая» сущность вроде счётчика вызовов или лога событий.
✨ Отсутствие «клонирования»
Вместо того чтобы протягивать объект контекста через сотни функций, можно хранить информацию в статической области видимости. Код может стать гораздо короче и чище, особенно если узлы дерева вызывают виртуальные методы и не хочется везде таскать дополнительный аргумент.
🎯 Безопасность через инкапсуляцию
Глобальная переменная не обязательно доступна «всем и каждому». Мы можем «спрятать» её за методом, который, например, только инкрементирует счётчик или только добавляет новые события в очереди. Тогда внешний код не сможет случайно её переопределить.
Когда глобальные переменные действительно опасны?
⚠️ Несколько потоков
В многопоточном окружении глобальная переменная может стать причиной гонок и непредсказуемых состояний. Но здесь зачастую помогают thread_local или грамотные примитивы синхронизации.
⚠️ Сложная логика
Если глобальная переменная меняется в разных местах и в разном порядке, это может привести к непростому для понимания коду. Особенно опасно, когда она хранит «случайное» состояние, которое нужно аккуратно сбрасывать.
⚠️ Лень и «быстрые костыли»
Часто новички, столкнувшись с десятком функций, решают «просто объявить всё глобально», чтобы не усложнять сигнатуры. Такой подход нередко заканчивается трудностями при расширении программы.
Примеры использования
🦆 Счётчик вызовов
Нужен, чтобы знать, сколько раз мы зашли в функцию, или поставить «breakpoint» в 123-й раз — глобальный count может быть идеальным решением.
🐧 Уникальные ID
Если нужно быстро «метить» объекты уникальным серийным номером, проще иметь глобальный или статический счётчик, чем тащить объект «генератора ID» через все вызовы.
🧵 Пулы ресурсов
Использование глобальных пулов (например, коннектов к базе) может быть оправдано, если мы прячем это внутри функций и контролируем доступ (например, только «взять ресурс» и «вернуть»).
Мнение автора (и моё)
Статья подчёркивает, что глобальные переменные сами по себе не «зло». Проблемы возникают, когда программисты небрежно подходят к дизайну — будь то глобальная переменная или «умно передаваемый» объект. На практике глобальные переменные хороши для вещей, которые действительно являются «общеизвестными» (логирование, конфигурации, счётчики). Но если состояние сложное, изменчивое и требует жёсткого контроля, лучше избегать глобальной видимости или хотя бы скрывать её за функциями и классами.
Как человек, писавший код на C++ и JavaScript, я видел, как заведомо хороший подход «нет глобальных переменных» оборачивается хаотичным протягиванием «context» во все методы. Глобальная переменная иногда помогает сделать код проще и даже безопаснее, если пользоваться ею аккуратно — как молотком, а не отбойным молотком, где надо и не надо.
Дополнительно
Итог: Глобальные переменные — это инструмент, который может быть вреден в неумелых руках, но отнюдь не «абсолютное зло». Главное — понимать, зачем и как вы их используете, и уметь инкапсулировать доступ, чтобы не плодить «прихотливые» зависимости и не превращать проект в неразборчивый клубок.