Найти в Дзене
Цифровая Переплавка

Глобальные переменные: когда они не враги, а союзники

Оглавление

Каждый, кто учился программировать, наверняка слышал, что глобальные переменные — это зло: они ведут к запутанному состоянию программы, могут изменяться из любого места кода и в целом осложняют отладку. Однако статья «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» во все методы. Глобальная переменная иногда помогает сделать код проще и даже безопаснее, если пользоваться ею аккуратно — как молотком, а не отбойным молотком, где надо и не надо.

Дополнительно

Итог: Глобальные переменные — это инструмент, который может быть вреден в неумелых руках, но отнюдь не «абсолютное зло». Главное — понимать, зачем и как вы их используете, и уметь инкапсулировать доступ, чтобы не плодить «прихотливые» зависимости и не превращать проект в неразборчивый клубок.