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

  Юрій  | 

  Оновл. 10 Тра 2020  | 

 243

Якщо ви запитаєте ветерана-програміста дати одну слушну пораду про програмуванню, то, після певних роздумів, він відповість: «Уникайте використання глобальних змінних!». І, частково, він матиме рацію. Глобальні змінні є одними з найбільш зловживаних об’єктів в 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 Зірок (6 оцінок, середня: 5,00 з 5)
Loading...

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

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