Чому Глобальна держава така зла?


328

Перш ніж ми розпочнемо це, дозвольте сказати, що я добре знаю поняття абстракції та ін'єкції залежності. Мені тут не потрібно відкривати очі.

Що ж, більшість з нас багато разів говорить (занадто), не розуміючи: "Не використовуйте глобальні змінні" або "Синглтони - це зло, тому що вони глобальні". Але що на насправді це так погано про зловісний глобальному стані?

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

У такому випадку я не бачу іншого корисного рішення, крім надання цих налаштувань у якомусь глобальному просторі, який буде загальнодоступним для всієї програми.

Я знаю, що це погано зловживати цим, але хіба глобальний простір справді ТОГО зла? А якщо є, то які хороші альтернативи є?


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

25
Яка різниця у використанні глобальної? Ваше "єдине місце" звучить дуже підозріло, як синглтон.
Мадара Учіха

130
Єдине справжнє зло - догми.
Пітер Б

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

5
Диявол - зло. Глобальна держава не є ні злом, ні добрим. Це може бути дуже корисним або шкідливим, залежно від того, як ви його використовуєте. Не слухайте інших і просто навчіться правильно програмувати.
дратівливий_сквір

Відповіді:


282

Коротше кажучи, це робить стан програми непередбачуваним.

Для уточнення уявіть, що у вас є пара об'єктів, які обидва використовують одну і ту ж глобальну змінну. Якщо припустити, що ви не використовуєте джерела випадковості ніде в будь-якому модулі, то вихід конкретного методу можна передбачити (і, отже, протестувати), якщо стан системи відомий перед тим, як виконати метод.

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

На академічному рівні це може здатися не настільки серйозним, але можливість одиничного тестового коду є головним кроком у процесі доведення його коректності (або принаймні придатності за призначенням).

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

Крім того, глобальна держава шкодить читабельності вашого коду. Якщо ваш код має зовнішню залежність, яка явно не вводиться до коду, тоді, хто отримає завдання з обслуговування вашого коду, доведеться шукати його, щоб з’ясувати, звідки він узятий.

Щодо альтернатив, які існують, ну взагалі неможливо мати глобальну державу, але на практиці зазвичай можливо обмежити глобальну державу одним об'єктом, який охоплює всі інші, і на який ніколи не слід посилатися, покладаючись на правила обстеження мови, якою ви користуєтесь. Якщо певному об'єкту потрібен певний стан, він повинен явно запитувати його, передавши його як аргумент своєму конструктору або методом setter. Це відомо як ін'єкція залежності.

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

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


61
В основному, оскільки кожен може змінити стан, ви не можете покластися на нього.
Одід

21
@ Істина Альтернатива? Dependency Injection .
rdlowrey

12
Ваш головний аргумент насправді не стосується того, що є ефективним лише для читання, наприклад об'єкта, що представляє конфігурацію.
франк

53
Ніщо з цього не пояснює, чому глобальні змінні, що читаються лише погано ... про що явно просив ОП. Це інакше гарна відповідь, я просто здивований, що ОП відзначила це "прийнятим", коли він чітко звернувся до іншого питання.
Конрад Рудольф

20
being able to unit test code is a major step in the process of proving its correctness (or at least fitness for purpose). Ні, це не так. "Зараз уже два десятиліття було зазначено, що тестування програм може переконливо продемонструвати наявність помилок, але ніколи не може продемонструвати їх відсутність. Після цитування цього широко розрекламованого зауваження, програмний інженер повертається до порядку дня і продовжує вдосконалити його стратегії тестування, як алхімік з того часу, який продовжував удосконалювати свої хризокосмічні очищення ". - Djikstra, 1988. (Це робить це вже 4,5 десятиліття ...)
Мейсон Уілер

135

Змінна глобальна держава є злом з багатьох причин:

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

Альтернативи змінній глобальній державі:

  • Параметри функції - часто не помічаються, але краще параметризувати свої функції - це найкращий спосіб уникнути глобального стану. Це змушує вас вирішити важливе концептуальне питання: яку інформацію потребує ця функція, щоб виконати свою роботу? Іноді має сенс мати структуру даних під назвою "Контекст", яку можна передавати вниз по ланцюгу функцій, яка містить всю відповідну інформацію.
  • Введення залежності - те саме, що і для параметрів функції, щойно зроблено трохи раніше (при побудові об'єкта, а не виклику функції). Будьте уважні, якщо ваші залежності є об'єктами, що змінюються, це може швидко викликати ті самі проблеми, що і глобальний стан, що змінюється .....
  • Незмінна глобальна держава здебільшого нешкідлива - вона фактично є постійною. Але переконайтесь, що це дійсно константа, і що ви не збираєтеся в подальшому перетворити це на змінне глобальне стан!
  • Незмінні одинаки - майже те саме, що і незмінний глобальний стан, за винятком того, що ви можете відкладати дані, поки вони не знадобляться. Корисно, наприклад, для великих фіксованих структур даних, які потребують дорогого разового попереднього розрахунку. Змінні одинаки, звичайно, еквівалентні глобальному стану, що змінюється, і тому є злими :-)
  • Динамічне прив'язування - доступне лише в деяких мовах, таких як Common Lisp / Clojure, але це ефективно дозволяє прив'язувати значення в контрольованому обсязі (як правило, на локальній основі), що не впливає на інші потоки. Певною мірою це "безпечний" спосіб отримати той же ефект, що і глобальна змінна, оскільки ви знаєте, що буде зачіпатися лише поточна нитка виконання. Це особливо корисно у випадку, коли у вас є декілька потоків, наприклад, кожен, що обробляє незалежні транзакції.

11
Я думаю, що передача об'єкта контексту або за параметром функції, або за допомогою введення залежності може спричинити проблеми, якщо контекст є змінним, таку ж проблему, як використання глобального стану, що змінюється.
Альфредо Озоріо

1
Всі добрі речі, амінь! Але питання стосується незмінного глобального стану
MarkJ

@Alfredo - дуже правда. Хоча це не так вже й погано, оскільки принаймні сферу можна дещо контролювати. Але загалом проблему простіше просто вирішити, зробивши контексти та залежності незмінними.
mikera

2
+1 для змінних / незмінних. Незмінні глобалі - це нормально. Навіть ті, які ледаче завантажені, але ніколи не змінюються. Звичайно, не виставляйте глобальних змінних, а глобальний інтерфейс чи API.
Джесс

1
@giorgio З питання дає зрозуміти, що ці змінні змінні значення отримують при запуску і ніколи не змінюються після цього під час виконання програми (системні папки, облікові дані бази даних). Тобто непорушний, він не змінюється, коли йому дано значення. Особисто я також вживаю слово "стан", оскільки воно може відрізнятися від одного виконання до іншого або на іншому верстаті. Можуть бути і кращі слова.
MarkJ

62
  1. Оскільки вся ваша проклята програма може користуватися нею, завжди неймовірно важко їх знову відмовити. Якщо ви коли-небудь зміните щось спільне з вашим глобальним, весь ваш код потребує змін. Це головний біль від технічного обслуговування - набагато більше, ніж просто можливість grepввести ім'я типу, щоб дізнатися, які функції використовують.
  2. Вони погані, оскільки вводять приховані залежності, які порушують багатопоточність, що стає все більш важливим для все більшої кількості застосувань.
  3. Стан глобальної змінної завжди повністю ненадійний, тому що весь ваш код може зробити для цього все, що завгодно.
  4. Їх справді важко перевірити.
  5. Вони ускладнюють виклик API. "Ви повинні пам’ятати, щоб зателефонувати SET_MAGIC_VARIABLE () перед тим, як зателефонувати в API", просто благаєте, щоб хтось забув його зателефонувати. Це робить використання схильних до помилок API, викликаючи помилки, які важко знайти. Використовуючи його як звичайний параметр, ви змушуєте абонента належним чином надати значення.

Просто передайте посилання на потрібні функції. Це не так складно.


3
Ну, у вас може бути глобальний клас конфігурацій, який інкапсулює блокування, і IS призначений для зміни стану в будь-який час. Я вибрав би такий підхід над інстанціюванням читачів конфігурацій з 1000x місць у коді. Але так, непередбачуваність - це абсолютно найгірше в них.
Кодер

6
@Coder: Зауважте, що розумною альтернативою глобальному є не "конфігурація зчитувачів з 1000x місць у коді", а один конфігураційний зчитувач, який створює конфігураційний об'єкт, який методи можуть приймати як параметр (-> введення залежності).
sleske

2
Нітпікінг: Чому легше схопитися за тип, ніж глобальний? І питання стосується глобальних глобальних мереж, тому пункти 2 і 3 не стосуються
MarkJ

2
@MarkJ: Я сподіваюся, що ніхто ніколи, скажімо, не затіняв ім'я.
DeadMG

2
@DeadMG, Re ".. завжди є абсолютно ненадійним ..", те ж саме стосується ін'єкції залежності. Тільки тому, що ви зробили його параметром, не означає, що obj гарантовано має фіксований стан. Десь внизу функції ви могли б зателефонувати до іншої функції, яка змінює стан введеної змінної вгорі.
Pacerier

34

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

Уявіть налагодження невідомої програми: Ви виявите, що функція A поводиться певним чином для певних вхідних параметрів, але іноді вона працює по-іншому для тих же параметрів. Ви виявляєте, що він використовує глобальну змінну x .

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


Тож незмінна глобальна держава не така вже й зла?
FrustratedWithFormsDesigner

50
Я б сказав, що незмінна глобальна держава - це добре відома добра практика, відома як "константи".
Теластин

6
Незмінна глобальна держава - це не зло, це просто погано :-). Це все ще проблематично через з'єднання, яке воно вводить (ускладнює зміни, повторне використання та тестування модулів), але створює набагато менше проблем, тому в простих випадках це зазвичай прийнятно.
sleske

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

1
@Pacerier: Так, змінити широко використовуваний інтерфейс важко, незалежно від того, використовується він як глобальна чи локальна змінна. Однак це не залежно від моєї точки зору, що полягає в тому, що взаємодію різних фрагментів коду важко зрозуміти з глобальними змінами.
sleske

9

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

У досвідчених програмістів, які можуть сказати різницю між глобальними, що є єдиним варіантом, і їх легкістю виправити, можуть виникнути мінімальні проблеми з їх використанням. Але нескінченні можливі проблеми, які можуть виникнути при їх використанні, потребують порад проти їх використання.

редагувати: Щоб уточнити, що я маю на увазі, глобалісти від природи непередбачувані. Як і у будь-якому непередбачуваному, ви можете вжити заходів, щоб стримувати непередбачуваність, але завжди є обмеження у тому, що можна зробити. Додайте до цього клопоту нових розробників, які приєднуються до проекту, що мають справу з відносно невідомими змінними, рекомендації щодо використання глобальних програм повинні бути зрозумілими.


9

У Сінглтона багато проблем - ось дві найбільші проблеми в моєму розумі.

  • Це робить тестування приладів проблематичним. Глобальна держава може забруднитися від одного випробування до іншого

  • Він застосовує жорстке правило "Єдиний-єдиний", яке, хоч і не могло змінитися, раптом. Цілу купу утилітних кодів, які використовували глобально доступний об'єкт, потрібно змінити.

Сказавши це, у більшості систем є певна потреба у великих глобальних об'єктах. Це об'єкти, які є великими і дорогими (наприклад, Менеджери з'єднань з базою даних) або містять розповсюджену інформацію про стан (наприклад, інформацію про блокування).

Альтернативою Singleton є створення цих великих глобальних об'єктів під час запуску і передавання в якості параметрів усім класам або методам, які потребують доступу до цього об'єкта.

Проблема тут полягає в тому, що ви закінчитеся з великою грою "пройти посилку". У вас є графік компонентів та їх залежностей, а деякі класи створюють інші класи, і кожен повинен містити купу компонентів залежності лише тому, що їх потребують породжені компоненти (або компоненти породжених компонентів).

У вас виникають нові проблеми з технічним обслуговуванням. Приклад: раптом вашому "WidgetFactory", компоненту в глибині графіка, потрібен об'єкт таймера, який ви хочете вималювати. Однак "WidgetFactory" створюється "WidgetBuilder", який є частиною "WidgetCreationManager", і вам потрібно мати три класи, які знають про цей об'єкт таймера, хоча лише один фактично ним користується. Ви виявляєте, що хочете відмовитися і повернутися до Singletons, і просто зробіть цей об'єкт таймера глобально доступним.

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

Отже, підсумовуючи, Singletons є поганим, а альтернативою є використання рамки введення залежностей.

Я випадково використовую Замок Віндзор, але ви зіпсовані на вибір. Перегляньте цю сторінку з 2008 року, щоб отримати список доступних рамок.


8

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

Зловживати будь-чим, це також залежить від типу програми. Використання статичних змінних, наприклад, у веб-програмі, зовсім інше, ніж у настільному додатку. Якщо ви можете уникнути глобальних змінних, тоді робіть це, але іноді вони мають своє використання. Принаймні переконайтеся, що ваші глобальні дані знаходяться у чіткому контекстному об’єкті. Що стосується налагодження, нічого стек викликів та деякі точки перерви не можуть вирішити.

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

До речі ... Деякі люди вважають, що ін'єкція залежності є поганою, в тому числі і автор Linq, але це не зупинить людей від її використання, в тому числі і я. Зрештою, досвід стане найкращим учителем. Існують часи дотримуватися правил та часи їх порушення.


4

Оскільки деякі інші відповіді тут розрізняють мінливий та незмінний глобальний стан, я хотів би зауважити, що навіть незмінні глобальні змінні / налаштування часто викликають роздратування .

Розглянемо питання:

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

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

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


3

Ну, для одного, ви можете зіткнутися з точно такою ж проблемою, що і з однотонними. Те, що сьогодні виглядає як "глобальна річ, яку мені потрібен лише один", раптом перетвориться на щось, що вам потрібно більше в дорозі.

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

(Це не якийсь випадковий приклад ... це сталося з нашою системою конфігурації в проекті, в якому я зараз перебуваю.)

Враховуючи, що вартість створення чогось неглобального зазвичай тривіальна, робити це нерозумно. Ви просто створюєте майбутні проблеми.


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

3

Інша проблема цікаво те, що вони ускладнюють додаток для масштабування, оскільки вони недостатньо "глобальні". Сфера глобальної змінної - це процес.

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


3

Чому Глобальна держава така зла?

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

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


3

Мови, які призначені для розробки безпечних і надійних систем часто позбутися глобального змінюваного стану в цілому. (Імовірно, це означає, що немає глобалістів, оскільки незмінні об'єкти в певному сенсі не є справжніми, бо вони ніколи не мають переходу стану.)

Джо-Е - один із прикладів, і Девід Вагнер пояснює рішення таким чином:

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

Тож один із способів подумати над цим - це

  1. Програмування - це проблема розподіленого міркування. Програмістам великих проектів потрібно розділити програму на шматки, про які люди можуть міркувати.
  2. Чим менший обсяг, тим простіше міркувати. Це стосується як людей, так і інструментів статичного аналізу, які намагаються довести властивості системи, і тестів, які потребують тестування властивостей системи.
  3. Значні джерела повноважень, які є у всьому світі, роблять властивості системи важкими для міркування.

Тому стан, що змінюється в усьому світі, ускладнює це

  1. проектувати надійні системи,
  2. важче довести властивості системи, і
  3. важче бути впевненим, що ваші тести тестуються в обсязі, подібному до виробничого середовища.

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


2

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

Але вони не шкодять надійності. Будь-які дані, на які посилається багато місць у вашій програмі, можуть спричинити проблеми, якщо вони несподівано зміниться. Переписувачі задихаються, коли колекція, яку вони перераховують, змінюється в середині перерахування. Події в черзі подій можуть відтворювати трюки один на одного. Нитки завжди можуть нанести хаос. Все, що не є локальною змінною або незмінним полем, є проблемою. Глобали - це така проблема, але ви не збираєтеся це виправляти, роблячи їх не глобальними.

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

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

Додавання: Багатопотокові проблеми:

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

if (filePath != null)  text = filePath.getName();

Якщо filePathце локальна змінна або якась константа, програма не вийде з ладу під час роботи, оскільки вона filePathє нульовою. Перевірка завжди працює. Жоден інший потік не може змінити його значення. Інакше гарантій немає. Коли я почав писати багатопотокові програми на Java, я постійно отримував NullPointerExceptions на таких лініях. Будь-якийінший потік може змінити значення в будь-який час, і це часто робиться. Як зазначають кілька інших відповідей, це створює серйозні проблеми для тестування. Вищезгадане твердження може спрацювати мільярд разів, отримуючи це шляхом обширного та всебічного тестування, а потім підірвати один раз у виробництві. Користувачі не зможуть відтворити проблему, і це не повториться, поки вони не переконають себе, що бачать речі та забули її.

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

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


1
Чи можете ви пояснити, що ви маєте на увазі під цим ?: "Все, що не є локальною змінною чи незмінним полем, є проблемою. Глобали - це така проблема, але ви не збираєтеся це виправляти, роблячи їх неглобальними".
Андрес Ф.

1
@AndresF.: Я продовжив свою відповідь. Я думаю, що я використовую настільний підхід, де більшість людей на цій сторінці мають більше серверного коду з базою даних. "Глобальне" може означати різні речі в цих випадках.
РальфЧапін

2

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

Справжня причина не використовувати глобальний сховище - уникати зіткнень імен. Якщо ви завантажуєте кілька модулів, які оголошують одне і те саме глобальне ім'я, у вас є або невизначена поведінка (дуже важко налагоджувати, оскільки пройдуть одиничні тести), або помилка лінкера (я думаю, C - чи попереджає ваш провідник або не працює на це?).

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


1

Коли легко побачити та отримати доступ до всього глобального стану, програмісти незмінно закінчують це. Те, що ви отримуєте, є невимовленим і важко відстежувати залежності (int blahblah означає, що масив foo дійсний у будь-якому випадку). По суті, це майже неможливо підтримувати інваріанти програми, оскільки все можна згорнути незалежно. someInt має зв’язок між otherInt, це важко керувати і важче довести, чи можна безпосередньо змінити будь-який час.

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


1
"але ці навички втрачені" ... ще не зовсім. Нещодавно я працював у будинку програмного забезпечення, який клянеться «Clarion», інструментом генератора коду, який має власну основну мову, яка не має таких функцій, як передача аргументів підпрограмм ... Розробники, що сидять, не були задоволені будь-якими пропозиціями щодо "змінити" або "модернізувати", нарешті надоїли моїми зауваженнями і зобразили мене як дефіцитного і некомпетентного. Мені довелося виїхати ...
Луї Сомерс

1

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

Для глобальної держави такого питання немає, все є глобальним.

Наприклад: уявіть собі такий сценарій: у вас є сітка 10х10, яка виготовлена ​​з класів "Дошка" та "Плитка".

Якщо ви хочете це зробити OOP так, ви, ймовірно, передасте об'єкт "Board" кожному "Плитку". Скажімо тепер, що "Плитка" має 2 "байтові" поля типу, що зберігають її координату. Загальна пам'ять, яку вона потребує 32-бітній машині для однієї плитки, складе (1 + 1 + 4 = 6) байт: 1 для x координати, 1 для координати y і 4 для вказівника на плату. Це дає загалом 600 байт для налаштування плиток 10х10

Тепер для випадку, коли Плата знаходиться в глобальному масштабі, з одного об'єкта, доступного з кожної плитки, вам потрібно було б отримати лише 2 байти пам'яті на кожну плитку, це байти координат x і y. Це дало б лише 200 байт.

Так що в цьому випадку ви отримуєте 1/3 використання пам'яті, якщо використовуєте лише глобальний стан.

Думаю, це, крім інших речей, є причиною того, що глобальний обсяг все ще залишається в (відносно) мовах низького рівня, таких як C ++


1
Це сильно залежить від мови. У мовах, де програми наполегливо працюють, можливо, ви можете зберегти пам'ять, використовуючи глобальний простір та одиночні кнопки, а не інстанціювати багато класів, але навіть цей аргумент хиткий. Вся справа в пріоритетах. У таких мовах, як PHP (який запускається один раз на запит, а об'єкти не зберігаються), навіть цей аргумент є суперечливим.
Мадара Учіха

1
@MadaraUchiha Ні, цей аргумент ніяк не хиткий. Це об'єктивні факти, якщо деякі відеомагнітофони чи інша мова, що складена, не проводить оптимізацію жорсткого коду з таким видом проблеми. Інакше це все ще актуально. Навіть із програмами на стороні сервера, які використовуються як "один кадр", пам'ять зберігається протягом часу виконання. Для серверів з високим навантаженням це може бути критичним моментом.
luke1985

0

З глобальним станом слід враховувати кілька факторів:

  1. Простір пам'яті програміста
  2. Незмінні глобали / пишіть один раз глобали.
  3. Змінні глобалі
  4. Залежність від глобальних.

Чим більше у вас глобалів, тим більше шансів ввести дублікати, і тим самим порушити речі, коли дублікати вийдуть із синхронізації. Зберігати все глобальне у вашій недостовірній людській пам’яті стає і необхідним, і болем.

Immutables / write один раз, як правило, добре, але слідкуйте за помилками послідовності ініціалізації.

Змінні глобалі часто приймають за незмінні глобалі ...

Функція, яка ефективно використовує глобали, має додаткові "приховані" параметри, що ускладнює рефакторинг.

Глобальна держава не є злом, але вона приходить з певною вартістю - використовуйте її, коли користь перевищує вартість.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.