Урок №53. Чому глобальні змінні – це зло?

  Юрій  | 

  Оновл. 18 Сер 2021  | 

 297

Якщо ви запитаєте ветерана-програміста дати одну слушну пораду по програмуванню, то після певних роздумів він відповість: «Уникайте використання глобальних змінних!». І, частково, він матиме рацію. Глобальні змінні є одними з найбільш зловживаних об’єктів в мові C++. Хоч вони і не виглядають проблемними в невеликих програмах, у великих проектах зазвичай все навпаки.

Початківці часто використовують величезну кількість глобальних змінних, тому що з ними легко працювати, особливо коли задіяно багато функцій. Це погана ідея. Багато розробників вважають, що неконстантні глобальні змінні взагалі не слід використовувати!

Але перш ніж ми розберемося з питанням «Чому?», потрібно дещо уточнити. Коли розробники кажуть, що глобальні змінні — це зло, вони не мають на увазі повністю ВСІ глобальні змінні. Вони говорять про неконстантні глобальні змінні.

Чому (неконстантні) глобальні змінні — це зло?

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

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

Launching nuclear missiles...

Спочатку ми присвоюємо змінній g_mode значення 1, а потім викликаємо функцію doSomething(). Якби ми не знали заздалегідь, що doSomething() змінить значення g_mode, то, ймовірно, не очікували б подальшого розвитку подій (g_mode = 2 => Запуск ядерних боєголовок...)!

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

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

Наприклад, нерідко можна зустріти приблизно наступне:

Припустимо, що g_mode дорівнює 3, а не 4 — наша програма видасть неправильні результати. Як це виправити? Потрібно буде відшукати усі місця, де, ймовірно, могло змінитися значення змінної g_mode, а потім простежити хід виконання коду в кожній потенційно небезпечній ділянці. Можливо, зміну глобальної змінної ви виявите взагалі в іншому коді, який, як вам здалося на перший погляд, ніяк не був пов’язаний з фрагментом, наведеним вище.

Однією з причин оголошення локальних змінних максимально близько до місця їх першого використання є зменшення кількості коду, який потрібно буде переглянути, щоб зрозуміти, що робить (навіщо потрібна) змінна. З глобальними змінними справи йдуть трохи інакше — оскільки їх можна використовувати в будь-якому місці програми, то вам доведеться переглянути мало не весь код, щоб простежити логіку виконання і зміни значень змінних у вашій програмі.

Наприклад, ви можете виявити, що на g_mode посилаються 442 рази в вашій програмі. Якщо використання змінної g_mode не підкріплене коментарями, то вам доведеться переглянути кожну згадку g_mode, щоб зрозуміти, як вона використовується в різних випадках.

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

Зокрема, не використовуйте глобальні змінні в якості важливих змінних, які виконують головні або вирішальні функції в програмі (наприклад, змінні, які використовуються в умовних стейтментах, як g_mode вище). Ваша програма навряд чи зламається, якщо в ній буде глобальна змінна з інформаційним значенням, яке може змінюватися (наприклад, ім’я користувача). Набагато гірше, якщо зміниться значення глобальної змінної, яка впливає безпосередньо на результати виконання самої програми або на її роботу.

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

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

Їх не багато. В основному можна обійтися без використання неконстантних глобальних змінних. Але в деяких випадках їх розумне використання може зменшити складність програми, і, іноді, може бути навіть кращим, ніж альтернативні варіанти вирішення проблеми.

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

Як захиститися від “глобального руйнування”?

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

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

Наприклад, замість наступного:

Зробіть наступне:

По-друге, замість дозволу прямого доступу до глобальних змінних, краще їх інкапсулювати. Спочатку додайте ключове слово static, щоб доступ до них був можливий тільки з файлу, в якому вони оголошені. Потім напишіть зовнішні глобальні «функції доступу» для роботи зі змінними. Ці функції допоможуть забезпечити належне використання змінних (наприклад, при перевірці користувацького вводу чи допустимого діапазону значень тощо). Крім того, якщо ви коли-небудь вирішите змінити початкову реалізацію програми (наприклад, перейти з однієї бази даних в іншу), то вам потрібно буде оновити тільки функції доступу замість кожного фрагменту коду, який безпосередньо використовує глобальні змінні.

Наприклад, замість наступного:

Зробіть наступне:

По-третє, при написанні автономної функції, яка використовує глобальні змінні, не використовуйте їх безпосередньо в тілі функції. Передавайте їх в якості параметрів. Таким чином, якщо у вашій функції потрібно буде коли-небудь використати інше значення, то ви зможете просто змінити параметр. Це підвищить модульність вашої програми.

Наприклад, замість наступного:

Зробіть наступне:

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

Жарт

Який найкращий префікс для глобальних змінних?

Відповідь: //.

Висновки

Уникайте використання неконстантних глобальних змінних, наскільки це можливо! Якщо ж використовуєте, то використовуйте їх максимально розумно і обережно.

Оцінити статтю:

1 Зірка2 Зірки3 Зірки4 Зірки5 Зірок (8 оцінок, середня: 5,00 з 5)
Loading...

Залишити відповідь

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