Урок №50. Почему глобальные переменные – зло?

  Юрий  | 

  |

  Обновл. 11 Сен 2020  | 

 77184

 ǀ   17 

Если вы попросите ветерана-программиста дать один дельный совет о программировании, то после некоторого раздумья он ответит: «Избегайте использования глобальных переменных!». И, частично, он будет прав. Глобальные переменные являются одними из самых злоупотребляемых объектов в языке C++. Хоть они и выглядят безвредными в небольших программах, использование их в крупных проектах зачастую чрезвычайно проблематично.

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

Но прежде, чем мы разберемся с вопросом «Почему?», нужно кое-что уточнить. Когда разработчики говорят, что глобальные переменные — это зло, они НЕ подразумевают полностью ВСЕ глобальные переменные. Они говорят о неконстантных глобальных переменных.

Почему (неконстантные) глобальные переменные — это зло?

Безусловно, причина №1, почему неконстантные глобальные переменные являются опасными, — это то, что их значения могут изменять любые вызываемые функции, при этом вы можете этого и не знать. Например, рассмотрим следующую программу:

Результат выполнения программы:

Launching nuclear missiles...

Сначала мы присваиваем переменной g_mode значение 1, а затем вызываем функцию doSomething(). Если бы мы не знали заранее, что doSomething() изменит значение g_mode, то, вероятно, не ожидали бы дальнейшего развития событий (g_mode = 2 => Launching nuclear missiles...)!

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

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

Предположим, что g_mode равно 3, а не 4 — наша программа выдаст неверные результаты. Как это исправить? Нужно будет отыскать все места, где предположительно могло измениться значение переменной g_mode, а затем проследить ход выполнения кода в каждом потенциально опасном участке. Возможно, изменение глобальной переменной вы обнаружите вообще в другом коде, который, как вам казалось на первый взгляд, никак не связан с фрагментом, приведенным выше.

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

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

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

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

Правило: Вместо глобальных переменных используйте локальные (когда это целесообразно).

В чём плюсы использования (неконстантных) глобальных переменных?


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

Например, если ваша программа использует базу данных для чтения и записи данных, то имеет смысл определить базу данных глобально, поскольку доступ к ней может понадобиться с любого места. Аналогично, если в вашей программе есть журнал ошибок (или журнал отладки), в котором вы можете читать/записывать информацию об ошибках (или об отладке), то имеет смысл определить его глобально. Звуковая библиотека может быть еще одним хорошим примером: вам, вероятно, не захочется открывать доступ к ней для каждой функции, которая делает запрос. Поскольку у вас будет только одна звуковая библиотека, управляющая всеми звуками, логично будет объявить её глобально, инициализировать при запуске программы, а затем использовать только в режиме чтения.

Как защититься от «глобального разрушения»?

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

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

Например, вместо следующего:

Сделайте следующее:

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

Например, вместо следующего:

Сделайте следующее:

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

Вместо следующего:

Сделайте следующее:

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

Шутка


Какой наилучший префикс для глобальных переменных?

Ответ: //.

Заключение

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


Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (501 оценок, среднее: 4,94 из 5)
Загрузка...

Комментариев: 17

  1. Codder:

    Вот щас посмеялся. Это не программист, а ребенок, если не понимает как работает элементарный синтаксис.

    Я даже не знаю на чем может базироваться его ожидание.

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

    1. Александр:

      Это же пример, брат.
      Реальный код больше и в нем проще запутаться.

    2. Максим:

      … а теперь представьте, что программист include-ит c десяток нужных для проекта сторонних библиотек,
      те, в свою очередь, инклудят ещё с дюжину требуемых для работы библиотек, и вот, одна из функций, в одной из криво сляпаных под-под-библиотек использует g_mode = -1; …

  2. Kinonik:

    Я бы не сказал, что глобалки прям таки зло. Я программирую не много, около 7-9 лет на С++. Единственной потенциальной угрозой я считаю потокобезопасность. К глобалкам доступ быстрее для методов, т.к. им не нужно ничего возвращать, они напрямую могут работать с файлами.
    Самое нелепое, т.ч. способы, которые дают возможность обойтись без глобалок делают локалки глобальными в каком-то роде 🙂
    Вы передаёте в функцию какую-то переменную и он, представляете, МОЖЕТ ЕЁ МЕНЯТЬ! А если функция в другом потоке… ух…

  3. ФывФыв:

    А в чём смысл шутки? Что-то я не догнал…

    1. Фото аватара Юрий:

      Для шутки нужно степень учёную иметь. Не для вас походу)

    2. Слава:

      // — превращает строку в комментарий.

    3. Юрец:

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

  4. Алексей:

    Вердикт — осторожно использовать глобальность, или наломать дров.

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

  5. Евгений:

    Хотел спросить совета. Я вот хочу потренироваться путём написания текстового квеста (я раньше в Космических рейнджерах любил их проходить). Допустим, такие понятия как здоровье или количество денег объявить как глобальные переменные, или всё же лучше как статические, например в той же main? Так же предполагается, что квест не будет линейным: можно будет пойти из точки А в точку B, из неё в C, затем в D, а из неё обратно в B. При этом здоровье или деньги будут меняться.

    1. Александр:

      на том количестве знаний, что Вы уже успели прочитать в этих статьях, лучше сделать static переменные и функции доступа к ним (например — getHealth() возвращает здоровье, setHealth(int h) устанавливает здоровье на фиксированное значение, charWounded(int c) — "ранит" персонажа по заданным правилам, зависящим от c)

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

      Ну и самым хорошим вариантом будет создание класса "персонаж" 🙂

      1. Вячеслав:

        Александр хотел задать вопрос собираюсь писать бота на криптобиржу что лучше сделать написать функцию или создать переменную, которая будет принимать с биржи данные через API ключи?

    2. Codder:

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

  6. STM32_ Den_Od:

    Добрый день. Спасибо за ваши статьи много нового узнаю. Программирую на Си под микроконтроллеры. А как же быть с флагами?
    Или семафорами которые должны быть глобальными, потому что они поднимаются в разных местах программы.

    1. Фото аватара Юрий:

      Привет. Не буду врать, не особо знаком с низкоуровневым программированием, могу ошибаться, но вот, что думаю. Во-первых, набор флагов ведь и так объявляется в качестве константных значений (урок 46). С семафорами, скорее всего, есть два варианта:

      1. Либо объявить данные локально и вызывать их через функцию (используя структуры и классы (еще не рассматривали) для хранения и передавая эти значения по константной ссылке (урок 89) — тогда вы сможете гарантировать то, что эти значения не изменяться в программе).
      2. Либо всё равно использовать глобальную переменную (и попробовать её енкапсулировать, т.е. защитить от возможных изменений в программе).

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

    2. AleksandrM:

      Никак. Ну или почти никак. Самый простой вариант: объявить глобально. Только надо не просто объявлять(int Flag;), а объявлять и инициализировать(int Flag=0;), иначе есть шанс "выстрелить себе в ногу" при обращении к данной переменной.

    3. Андрей:

      Бро давай свяжемся с тобой т.к. я тоже програмирую микроконьроллеры но на с++

Добавить комментарий для Юрий Отменить ответ

Ваш E-mail не будет опубликован. Обязательные поля помечены *