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

   ⁄ 

 Обновлено 9 Апр 2017

  ⁄   

⁄  2

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

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

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

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

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

Обратите внимание, программист присвоил g_mode значение 1, а затем вызвал doSomething(). Если бы он не знал наверняка, что doSomething() изменит g_mode, то он, вероятно, не ожидал бы дальнейшего развития событий (g_mode = 2)! Следовательно, остальной код в main() будет работать по-другому (а затем уничтожится весь мир…).

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

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

Например, нередко можно найти фрагмент кода, который выглядит так:

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

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

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

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

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

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

Так в чем же плюсы использования неконстантных глобальных переменных?

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

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

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

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

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

Например, вместо:

Сделайте так:

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

Например, вместо:

Сделайте так:

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

Вместо:

Сделайте так:

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

Шутка

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

Ответ: //.

Где лучшие шутки? Здесь! Где? Здесь! Где? В C++!
(если кто не понял, // — это символ однострочного комментария)

Итого

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

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

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (9 оценок, среднее: 5,00 из 5)
Загрузка...
Поделиться в:
Подписаться на обновления:

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

  1. STM32_ Den_Od:

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

    1. Li4ik:

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

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

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

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

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