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

  Юрий  | 

    | 

  Обновл. 17 Апр 2019  | 

 15797

 ǀ   11 

Если вы спросите ветерана-программиста дать один дельный совет о программировании, то, после некоторого раздумья, он ответит: «Избегайте использования глобальных переменных!». И, частично, он будет прав. Глобальные переменные являются одними из самых злоупотребляемых объектов в 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 (131 оценок, среднее: 4,92 из 5)
Загрузка...

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

  1. Аватар ФывФыв:

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

    1. Юрий Юрий:

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

    2. Аватар Слава:

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

  2. Аватар Алексей:

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

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

  3. Аватар Евгений:

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

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

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

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

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

      1. Аватар Вячеслав:

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

  4. Аватар STM32_ Den_Od:

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

    1. Юрий Юрий:

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

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

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

    2. Аватар AleksandrM:

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

    3. Аватар Андрей:

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

Добавить комментарий

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