Чем глобальные различия отличаются от базы данных?


250

Я только что натолкнулся на этот старый вопрос, спрашивающий, что же такого плохого в глобальном состоянии, и принятый верхний ответ утверждает, что вы не можете доверять ни одному коду, который работает с глобальными переменными, потому что какой-то другой код где-то еще может прийти и изменить значение, а затем вы не знаете, каким будет поведение вашего кода, потому что данные разные! Но когда я смотрю на это, я не могу не думать, что это действительно слабое объяснение, потому что чем это отличается от работы с данными, хранящимися в базе данных?

Когда ваша программа работает с данными из базы данных, вам все равно, изменяет ли ее другой код в вашей системе, или даже если в этом отношении ее изменяет совершенно другая программа. Вам все равно, что это за данные; вот и весь смысл. Все, что имеет значение, это то, что ваш код правильно обрабатывает данные, с которыми он сталкивается. (Очевидно, что я закрываю часто острую проблему кеширования здесь, но давайте пока проигнорируем это.)

Но если данные, с которыми вы работаете, поступают из внешнего источника, который не контролируется вашим кодом, например, из базы данных (или пользовательского ввода, или сетевого сокета, или файла и т. Д.), И в этом нет ничего плохого с этим, тогда как глобальные данные в самом коде - над которым ваша программа имеет гораздо большую степень контроля - каким-то образом плохая вещь, когда она, очевидно, намного менее плоха, чем совершенно нормальные вещи, которые никто не считает проблемой?


117
Приятно видеть, что ветераны-члены немного бросают вызов догмам ...
svidgen

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

45
Глобальное состояние похоже на наличие единой базы данных с одной таблицей с одной строкой с бесконечным числом столбцов, к которым одновременно обращается произвольное число приложений.
BevynQ

42
Базы данных тоже злые.
Стиг Хеммер

27
Интересно "перевернуть" аргумент, который вы приводите здесь, и пойти в другом направлении. Структура, которая имеет указатель на другую структуру, логически представляет собой просто внешний ключ в одной строке одной таблицы, который указывает на другую строку другой таблицы. Чем отличается работа с любым кодом, в том числе ходом связанных списков, от манипулирования данными в базе данных? Ответ: это не так. Вопрос: почему тогда мы манипулируем структурами данных в памяти и структурами данных в базе данных, используя такие разные инструменты? Ответ: я действительно не знаю! Похоже, случайность истории, а не хороший дизайн.
Эрик Липперт

Ответы:


118

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

Но углубившись в эту проблему, есть различия между глобальным состоянием в типичном объектно-ориентированном приложении и состоянием, которое хранится в базе данных. Вкратце, наиболее важными из них являются:

  • Объектно-ориентированные системы позволяют заменять объект другим классом объекта, если он является подтипом исходного типа. Это позволяет изменять поведение , а не только данные .

  • Глобальное состояние в приложении, как правило, не обеспечивает строгой согласованности, гарантирующей выполнение базы данных - нет транзакций, во время которых вы видите для нее согласованное состояние, нет атомарных обновлений и т. Д.

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


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

24
@DavidHammen Я фактически работал над симуляцией состояния мира для онлайн-игры, которая явно относится к той категории приложений, о которой вы говорите, и даже там я бы не стал (и не использовал) глобальное состояние для него. Даже если некоторое повышение эффективности может быть достигнуто с помощью глобального состояния, проблема заключается в том, что глобальное состояние не масштабируется . Его становится сложно использовать после перехода от однопоточной к многопоточной архитектуре. Это становится неэффективным, когда вы переходите на архитектуру NUMA. Это становится невозможным, когда вы переходите к распределенной архитектуре. Газета, на которую вы ссылаетесь, датируется ...
Жюль

24
1993. Тогда эти проблемы были меньшей проблемой. Авторы работали над однопроцессорной системой, моделирующей взаимодействия 1000 объектов. В современной системе вы, скорее всего, запустите симуляцию такого рода, по крайней мере, на двухъядерной системе, но вполне вероятно, что в одной системе может быть как минимум 6 ядер. Для более крупных проблем вы бы запустили его в кластере. Для такого рода изменений вы должны избегать глобального состояния, потому что глобальное состояние не может быть эффективно использовано совместно.
Жюль

19
Я думаю, что называть состояние базы данных «необходимым злом» - это немного натянуто. Я имею в виду, с каких пор государство стало злом? Состояние - это целое назначение базы данных. Государство это информация. Без государства все, что у вас есть, это операторы. Что хорошего в операторах, которым нечем работать? Это состояние должно куда-то идти. В конце концов, функциональное программирование - это всего лишь средство для достижения цели, и без изменения состояния не было бы никакого смысла вообще что-либо делать. Это похоже на то, как пекарь называет торт необходимым злом - это не зло. В этом весь смысл.
J ...

5
@DavidHammen "есть еще какой-то объект, который знает хотя бы немного о каждом объекте в игре" Не обязательно так. Основной метод в современном распределенном моделировании заключается в использовании преимуществ локальности и приближений, при которых удаленные объекты не должны знать обо всем, что находится далеко, только о том, какие данные им предоставляют владельцы этих удаленных объектов.
JAB

75

Во-первых, каковы проблемы с глобальными переменными, основанные на принятом ответе на вопрос, который вы связали?

Вкратце, это делает состояние программы непредсказуемым.

Базы данных в большинстве случаев совместимы с ACID. ACID специально решает основные проблемы, которые делают хранилище данных непредсказуемым или ненадежным.

Кроме того, глобальное состояние ухудшает читабельность вашего кода.

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

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

Как правило, базы данных моделируют данные, отличные от глобальных переменных, но на мгновение оставим это в стороне, базы данных с самого начала разрабатываются как совместимое с ACID хранилище данных, которое устраняет многие проблемы, связанные с глобальными переменными.


4
+1 Вы говорите, что в базах данных есть транзакции, позволяющие атомарно читать и записывать несколько частей глобального состояния. Хороший момент, который можно обойти, только используя глобальные переменные для каждого абсолютно независимого фрагмента информации.
10

1
Транзакции @ l0b0 - это механизм, который достигает большинства целей ACID, верно. Но сам интерфейс БД делает код более понятным, перенося данные в более локальную область. Подумайте об использовании JDBC RecordSet с блоком try-with-resources или функции ORM, которая получает фрагмент данных с помощью одного вызова функции. Сравните это с управлением данными далеко от кода, который вы читаете в глобальном где-то.

1
Поэтому было бы хорошо использовать глобальные переменные, если я скопирую значение в локальную переменную (с мьютексом) в начале функции, изменим локальную переменную, а затем скопирую значение обратно в глобальную переменную в конце функция? (... риторически спросил он.)
РМ

1
@RM Он упомянул два момента. То, что вы выбросили, может касаться первого (состояние программы непредсказуемо), но не относится ко второму (читабельность вашего кода). Фактически, это может сделать читабельность вашей программы еще хуже: P.
riwalk

1
@RM Ваша функция будет работать последовательно, да. Но тогда у вас возникнет вопрос о том, изменило ли что-то еще глобальную переменную тем временем, и эта модификация была более важной, чем то, что вы пишете в нее. Конечно, у баз данных тоже может быть такая же проблема.
Graham

45

Я бы предложил несколько наблюдений:

Да, база данных - это глобальное состояние.

На самом деле, это суперглобальное состояние, как вы указали. Это универсально! Сфера его применение влечет за собой ничего или кого - либо , который подключается к базе данных. И я подозреваю, что многие люди с многолетним опытом могут рассказать вам ужасные истории о том, как «странные вещи» в данных привели к «неожиданному поведению» в одном или нескольких соответствующих приложениях ...

Одним из потенциальных последствий использования глобальной переменной является то, что два разных «модуля» будут использовать эту переменную для своих собственных разных целей. И в этом смысле таблица базы данных ничем не отличается. Это может стать жертвой той же проблемы.

Хм ... вот в чем дело:

Если модуль каким-то образом не работает внешне, он ничего не делает.

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

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

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

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

Если приложение не находится на уровне данных (в SQL или чем-то еще), объекты, с которыми работают наши модули, на самом деле являются копиями общего глобального состояния. Мы можем делать все, что захотим, без какого-либо влияния на фактическое общее состояние.

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

И , наконец, мы обычно делаем разные вещи с базами данных , чем мы могли бы с нехорошими глобал.

Непослушный, разбитый глобал выглядит так:

Int32 counter = 0;

public someMethod() {
  for (counter = 0; counter < whatever; counter++) {
    // do other stuff.
  }
}

public otherMethod() {
  for (counter = 100; counter < whatever; counter--) {
    // do other stuff.
  }
}

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


3
Способ гарантировать (поскольку мы не можем предположить), что «данные, которые мы получили, не изменились» в базе данных, будет транзакцией.
10

Да ... это должно было подразумеваться с "таким же замком".
svidgen

Но в конце дня может быть трудно тщательно обдумать.

Да, базы данных действительно являются глобальным состоянием, поэтому так заманчиво делиться данными, используя что-то вроде git или ipfs.
Уильям Пейн

21

Я не согласен с фундаментальным утверждением, что:

Когда ваша программа работает с данными из базы данных, вам все равно, изменяет ли ее другой код в вашей системе, или даже если в этом отношении ее изменяет совершенно другая программа.

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

Но я также диаспра на архитектурном уровне. Глобальная переменная - это не просто глобальное состояние. Это глобальное состояние, которое доступно из любой точки мира прозрачно. В отличие от использования базы данных, вы должны иметь дескриптор к ней - (если вы не храните, чем обрабатываете в глобальной переменной ....)

Например, использование глобальной переменной может выглядеть так

int looks_ok_but_isnt() {
  return global_int++;
}

int somewhere_else() {
  ...
  int v = looks_ok_but_isnt();
  ...
}

Но делать то же самое с базой данных нужно было бы более четко о том, что она делает

int looks_like_its_using_a_database( MyDB * db ) {
   return db->get_and_increment("v");
}

int somewhere_else( MyBD * db ) { 
   ...
   v = looks_like_its_using_a_database(db);
   ...
}

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

int looks_like_it_uses_explicit_state( MyState * state ) {
   return state->v++;
}


int somewhere_else( MyState * state ) { 
   ...
   v = looks_like_it_uses_explicit_state(state);
   ...
}

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


2
Да, я подумал, что это было интересно, когда ОП сказал: « Тебе все равно, что это за данные; в этом весь смысл » - если нам все равно, зачем их хранить? Вот мысль: давайте просто перестанем использовать переменные и данные вообще . Это должно сделать вещи намного проще. "Останови мир, я хочу выйти!"

1
+1 Различные потоки или приложения, пишущие и читающие из одной и той же базы данных, являются потенциальным источником большого числа известных проблем, поэтому всегда должна быть стратегия для решения этой проблемы, либо на уровне базы данных или приложения, либо обе. Так что это определенно НЕ верно, что вы (разработчик приложения) не заботитесь о том, кто еще читает или пишет из базы данных.
Андрес Ф.

1
+1 Кстати, этот ответ в значительной степени объясняет то, что я больше всего ненавижу в внедрении зависимостей. Это скрывает эти виды зависимостей.
jpmc26

@ jpmc26 Я мог бы отмечать слова, но разве приведенный выше не является хорошим примером того, как внедрение зависимостей (в отличие от глобального поиска) помогает сделать зависимости явными? Мне кажется, что вы, скорее, не согласны с определенными API-интерфейсами, например с магией аннотаций, используемой JAX-RS и Spring.
Эмиль Лундберг

2
@ EmilLundberg Нет, проблема в том, что у вас есть иерархия. Внедрение зависимостей скрывает зависимости более низких уровней от кода на более высоких уровнях, что затрудняет отслеживание того, какие вещи взаимодействуют. Например, если MakeNewThingзависит от того , использует ли MakeNewThingInDbмой класс контроллера MakeNewThing, то из кода в моем контроллере не ясно, что я изменяю базу данных. И что тогда, если я использую другой класс, который фактически передает мою текущую транзакцию в БД? DI делает очень трудным контролировать область объекта.
jpmc26

18

То, что единственная причина, по которой глобальным переменным нельзя доверять, поскольку состояние может быть изменено где-то еще, само по себе не является достаточной причиной, чтобы не использовать их, согласился (хотя это довольно веская причина!). Вероятно, ответ в основном описывал использование, где ограничение доступа переменной только к тем областям кода, к которым она имеет отношение, имело бы больше смысла.

Однако базы данных - это другое дело, потому что они предназначены для того, чтобы к ним обращались, так сказать, «глобально».

Например:

  • Базы данных обычно имеют встроенную проверку типа и структуры, которая выходит за рамки языка, к которому они обращаются.
  • Базы данных почти единогласно обновляют на основе транзакций, что предотвращает несогласованные состояния, где нет гарантий того, как конечное состояние будет выглядеть в глобальном объекте (если оно не спрятано за единичным узлом)
  • Структура базы данных, по крайней мере, неявно документирована на основе структуры таблицы или объекта, в большей степени, чем приложение, использующее ее.

Самое главное, что базы данных служат не для глобальной цели, а для другой цели. Базы данных предназначены для хранения и поиска больших объемов организованных данных, где глобальные переменные служат определенным нишам (когда это оправдано).


1
Да. Вы опередили меня, когда я был на полпути к написанию почти идентичного ответа. :)
Жюль

@Jules ваш ответ предоставляет более подробную информацию со стороны приложения вещей; Оставь это.
Джеффри Суини

Но, если вы полностью не зависите от хранимых процедур доступа к данным, вся эта структура все равно не сможет обеспечить использование таблиц по назначению. Или что операции выполняются в соответствующем порядке. Или что блокировки (транзакции) создаются по мере необходимости.
svidgen

Привет, пункты 1 и 3 все еще применимы, если вы используете статический тип языка, такой как Java?
Джесвин Хосе

@aitchnyu Не обязательно. Смысл в том, что базы данных создаются с целью надежного обмена данными, а глобальные переменные, как правило, нет. Объект, реализующий самодокументирующийся интерфейс на строгом языке, служит иным целям, чем даже свободно типизированная база данных NoSQL.
Джеффри Суини

10

Но когда я смотрю на это, я не могу не думать, что это действительно слабое объяснение, потому что чем это отличается от работы с данными, хранящимися в базе данных?

Или отличается от работы с интерактивным устройством, с файлом, с общей памятью и т. Д. Программа, которая делает то же самое при каждом запуске, является очень скучной и довольно бесполезной программой. Так что да, это слабый аргумент.

Для меня различие, которое имеет значение в отношении глобальных переменных, состоит в том, что они образуют скрытые и незащищенные линии связи. Чтение с клавиатуры очень очевидно и защищено. Я должен сделать определенный вызов функции, и я не могу получить доступ к драйверу клавиатуры. То же самое относится к доступу к файлам, общей памяти и вашему примеру, базам данных. Для читателя кода очевидно, что эта функция читает с клавиатуры, что эта функция обращается к файлу, какая-то другая функция обращается к разделяемой памяти (и вокруг этого должна быть защита), а какая-то другая функция обращается к базе данных.

С глобальными переменными, с другой стороны, это совсем не очевидно. API говорит, чтобы позвонить foo(this_argument, that_argument). В последовательности вызова нет ничего, что говорило бы, что глобальная переменная g_DangerWillRobinsonдолжна быть установлена ​​в какое-то значение, но перед вызовом foo(или проверено после вызова foo).


Google запретил использование неконстантных ссылочных аргументов в C ++ прежде всего потому, что читателю кода не очевидно, что foo(x)он изменится, xпотому что он fooпринимает непостоянную ссылку в качестве аргумента. (Сравните с C #, который требует, чтобы и определение функции, и сайт вызова должны квалифицировать ссылочный параметр с refключевым словом.) Хотя я не согласен со стандартом Google по этому вопросу, я понимаю их точку зрения.

Код пишется один раз и изменяется несколько раз, но если он вообще хорош, его читают много-много раз. Скрытые линии связи - очень плохая карма. Неконстантная ссылка C ++ представляет собой небольшую скрытую линию связи. Хороший API или хорошая IDE покажет мне: «О! Это вызов по ссылке». Глобальные переменные - это огромная скрытая линия связи.


Ваш ответ имеет больше смысла.
Биллаль Бегерадж

8

Я думаю, что приведенное объяснение упрощает проблему до такой степени, что рассуждения становятся нелепыми. Конечно, состояние внешней базы данных способствует глобальному состоянию. Важный вопрос, какВаша программа зависит от (изменяемого) глобального состояния. Если библиотечная функция для разделения строк на пустое пространство будет зависеть от промежуточных результатов, хранящихся в базе данных, я бы возражал против этого проекта по крайней мере так же, как и против глобального массива символов, используемого для той же цели. С другой стороны, если вы решите, что вашему приложению не требуется полноценная СУБД для хранения бизнес-данных на данном этапе, и глобальная структура ключ-значение в памяти подойдет, это не обязательно является признаком плохого дизайна. Важно то, что - независимо от того, какое решение вы выберете для хранения своих данных - этот выбор изолирован для очень небольшой части системы, поэтому большинство компонентов могут не зависеть от решения, выбранного для развертывания, и подвергаться модульному тестированию в отдельности и в развертывании. решение может быть изменено позже без особых усилий.


8

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

Недостатком этого является то, что мы действительно должны рассмотреть , как используются эти переменные, и много , что сводится к тому же виду , что мысль идет в БД-пререканий. Любое асинхронное чтение / запись переменных ДОЛЖНО быть атомарным. Если переменная может быть записана более чем в одном месте, необходимо подумать о том, чтобы они всегда записывали действительные данные, чтобы предыдущая запись не была произвольно заменена (или что произвольная замена - безопасная вещь). Если одна и та же переменная читается более одного раза, необходимо подумать над тем, что произойдет, если переменная изменяет значение между чтениями, или копия переменной должна быть взята в начале, чтобы обработка выполнялась с использованием согласованного значения, даже если это значение становится устаревшим во время обработки.

(Что касается последнего, в мой самый первый день контракта, работавшего над системой контрмер самолета, настолько связанной с безопасностью, команда разработчиков смотрела на сообщение об ошибке, которое они пытались выяснить в течение недели или около того. У меня было достаточно времени, чтобы загрузить инструменты разработки и копию кода. Я спросил: «Не может ли эта переменная быть обновлена ​​между чтениями и вызвать ее?», Но на самом деле не получил ответа. Эй, что делает В конце концов, новый парень знает? Итак, пока они еще обсуждали это, я добавил защитный код, чтобы атомарно прочитать переменную, сделал локальную сборку и в основном сказал: «Эй, ребята, попробуйте это». Способ доказать, что я стою своего контракта :)

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


7

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

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

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

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

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

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

PS: Есть и другие аспекты, которые важны для этого сравнения, например, задействован ли параллелизм, но этот момент освещается здесь другими ответами.


Мне нравится, что вы взяли это с точки зрения зависимостей.
cbojar

6

Хорошо, начнем с исторической точки.

Мы находимся в старом приложении, написанном на вашем типичном соединении ассемблера и C. Там нет функций, только процедуры . Когда вы хотите передать аргумент или возвращаемое значение из процедуры, вы используете глобальную переменную. Излишне говорить, что это довольно сложно отследить, и в целом каждая процедура может делать с любой глобальной переменной все, что захочет. Неудивительно, что люди обратились к передаче аргументов и возвращению значений по-другому, как только это стало возможным (если только это не было критично для производительности - например, посмотрите на исходный код Build Engine (Duke 3D)). Здесь родилась ненависть к глобальным переменным - вы очень мало представляли, какую часть глобального состояния каждая процедура будет читать и изменять, и вы не могли действительно безопасно вкладывать вызовы процедур.

Означает ли это, что глобальная переменная ненависть ушла в прошлое? Не совсем.

Во-первых, я должен отметить, что я видел точно такой же подход к передаче аргументов в проекте, над которым я сейчас работаю. Для передачи двух экземпляров ссылочного типа в C # в проекте, которому около 10 лет. Буквально нет веской причины делать это так, и, скорее всего, он был рожден либо из-за груза, либо из-за полного недопонимания того, как работает C #.

Еще важнее то, что, добавляя глобальные переменные, вы расширяете область действия каждого отдельного фрагмента кода, который имеет доступ к этой глобальной переменной. Помните все эти рекомендации, как «держать ваши методы короткими»? Если у вас есть 600 глобальных переменных (опять же, пример из реального мира: /), все области ваших методов неявно расширяются этими 600 глобальными переменными, и нет простого способа отследить, кто к чему имеет доступ.

Если все сделано неправильно (обычным способом :)), глобальные переменные могут иметь связь между собой. Но вы понятия не имеете, как они связаны, и нет механизма, который бы гарантировал постоянство глобального состояния. Даже если вы введете критические разделы, чтобы попытаться сохранить последовательность, вы обнаружите, что она плохо сравнивается с правильной базой данных ACID:

  • Отменить частичное обновление невозможно, если только вы не сохраните старые значения перед «транзакцией». Излишне говорить, что к этому моменту передача значения в качестве аргумента уже является победой :)
  • Каждый, имеющий доступ к одному и тому же состоянию, должен придерживаться одного и того же процесса синхронизации. Но нет способа обеспечить это - если вы забудете настроить критическую секцию, вы облажались.
  • Даже если вы правильно синхронизируете весь доступ, могут существовать вложенные вызовы, которые обращаются к частично измененному состоянию. Это означает, что вы либо зашли в тупик (если ваши критические разделы не возвращаются), либо имеете дело с противоречивыми данными (если они повторно вводятся).

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

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

Конечно, это не значит, что глобальная сфера действия - это зло. Это просто не должно быть первое решение, к которому вы стремитесь - это типичный пример «простой в реализации, сложный в обслуживании».


Похоже на физический мир: очень трудно откатить вещи назад.

Это хороший ответ, но с самого начала он может выдержать тезис (раздел TL; DR).
jpmc26

6

Глобальная переменная - это инструмент, его можно использовать для добра и зла.

База данных - это инструмент, его можно использовать во благо и во зло.

Как отмечает оригинальный автор, разница не так уж велика.

Неопытные студенты часто думают, что ошибки - это то, что случается с другими людьми. Учителя используют «Глобальные переменные - зло» как упрощенную причину наказания за плохой дизайн. Студенты, как правило, не понимают, что их программа из 100 строк не содержит ошибок, но это не означает, что те же методы можно использовать для программ из 10000 строк.

Когда вы работаете с базами данных, вы не можете просто запретить глобальное состояние, поскольку именно в этом и заключается программа. Вместо этого вы получите более подробные рекомендации, такие как ACID и Normal Forms и так далее.

Если бы люди использовали подход ACID к глобальным переменным, они не были бы такими плохими.

С другой стороны, если вы плохо проектируете базы данных, это может стать кошмаром.


3
Типичная заявка студента на stackoverflow: Помоги мне! Мой код идеален, но он не работает правильно!
Дэвид Хаммен

«Подход ACID к глобальным переменным» - см. Ссылки в Clojure.
Чарльз Даффи

@DavidHammen, а вы думаете, у профессионалов есть мозги в отличие от студентов?
Биллаль Бегерадж

@BillalBEGUERADJ - В этом разница между профессионалами и студентами. Мы знаем, что, несмотря на многолетний опыт и усилия по проверке кода, тестированию и т. Д., Наш код не идеален.
Дэвид Хаммен


5

Для меня главное зло в том, что у глобалов нет защиты от проблем параллелизма. Вы можете добавить механизмы для решения таких проблем с Globals, но вы обнаружите, что чем больше проблем с параллелизмом вы решаете, тем больше ваши Globals начинают имитировать базу данных. Вторичное зло - это не контракт на использование.


3
Например, errnoв C.
Дэвид Хаммен

1
Это объясняет, почему глобальные и базы данных не совпадают. Могут быть и другие различия, но ваш конкретный пост полностью разрушает концепцию. Если бы вы дали пример быстрого кода, то я уверен, что вы получите много голосов. например, MyFunc () {x = globalVar * 5; // .... Некоторая другая обработка; y = globalVar * 34; // Ой, какой-то другой поток мог бы изменить globalVar во время некоторой другой обработки, и x и y используют различные значения для globalVar в своих вычислениях, что почти наверняка не дало бы желаемых результатов.
Данк

5

Некоторые другие ответы пытаются объяснить, почему использование базы данных хорошо. Они не правы! База данных является глобальным состоянием и, как таковая, является таким же злом, как синглтон или глобальная переменная. Использование базы данных очень неправильно, если вместо этого вы можете просто использовать локальную карту или массив!

Глобальные переменные допускают глобальный доступ, что сопряжено с риском злоупотребления. Глобальные переменные также имеют свои плюсы. Обычно говорят, что глобальные переменные - это то, чего вам следует избегать, а не то , что вы никогда не должны использовать. Если вы можете легко избежать их, вы должны избегать их. Но если преимущества перевешивают недостатки, конечно, вы должны их использовать! *

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

В реальной жизни многим приложениям требуется глобальное состояние, иногда даже постоянное глобальное состояние, поэтому у нас есть файлы, базы данных и т. Д.


* Исключение составляют студенты. Имеет смысл запретить учащимся использовать глобальные переменные, чтобы они могли узнать, какие есть альтернативы.

** Некоторые ответы неверно утверждают, что базы данных как-то лучше защищены, чем другие формы глобального состояния (вопрос явно о глобальном состоянии , а не только о глобальных переменных). Это чепуха. Основная защита, предлагаемая в сценарии базы данных, по соглашению, которая точно такая же для любого другого глобального состояния. Большинство языков также предоставляют много дополнительной защиты для глобального состояния в форме constклассов, которые просто не позволяют изменять свое состояние после того, как оно было установлено в конструкторе, или методов получения и установки, которые могут принимать во внимание информацию о потоке или состояние программы.


2

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

Ключевым отличием здесь является взятие на себя ответственности.

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

То же самое предположение применяется на более широком уровне к базе данных globals v / s. Кроме того, язык / экосистема программирования гарантирует ограничения доступа к частным v / s public так же, как он применяет их к (не разделяемой памяти) глобальным базам данных v / s.

Когда многопоточность вступает в игру, концепция частной v / s публичной v / s глобальной v / s базы данных - это просто различия по спектру.

static int global; // within process memory space
static int dbvar; // mirrors/caches data outside process memory space

class Cls {
    public: static int class_public; // essentially the same as global
    private: static int class_private; // but public to all methods in class

    private: static void method() {
        static int method_private; // but public to all scopes in method
        // ...
        {
            static int scope1_private; // mutex guarded
            int the_only_truly_private_data;
        }
        // ...
        {
            static int scope2_private; // mutex guarded
        }
    }
}

1

База данных может быть глобальным состоянием, но это не обязательно должно быть постоянно. Я не согласен с предположением, что у вас нет контроля. Одним из способов управления этим является блокировка и безопасность. Это может быть сделано в записи, таблице или всей базе данных. Другой подход заключается в том, чтобы иметь какое-то поле версии, которое предотвратит изменение записи, если данные устарели.

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


0

Есть несколько отличий:

  • Значение базы данных может быть изменено на лету. С другой стороны, значение глобальной переменной, заданной в коде, не может быть изменено, если вы повторно не развернете свое приложение и не измените свой код. На самом деле это намеренно. База данных предназначена для значений, которые могут изменяться с течением времени, но глобальные переменные должны использоваться только для вещей, которые никогда не изменятся, и когда они не содержат фактических данных.

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

  • Глобальные переменные быстрее . Чтобы получить что-то из базы данных, необходимо установить соединение с базой данных, выполнить команду select, а затем соединение с базой данных должно быть закрыто. Любые преобразования типов, которые вам могут понадобиться, идут поверх этого. Сравните это с глобальным доступом в вашем коде.

Это единственное, о чем я могу думать прямо сейчас, но я уверен, что есть и другие. Проще говоря, это две разные вещи и должны использоваться для разных целей .


0

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

Например, рассмотрим HTTP-сервер, хранящий имя сервера.

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

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

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


0

Я думаю, что это интересный вопрос, но на него немного сложно ответить, потому что есть два основных вопроса, которые соотносятся с термином «глобальное состояние». Первое - это понятие «глобальная связь». Доказательством этого является то, что альтернатива для глобального состояния - это внедрение зависимостей. Дело в том, что DI не обязательно устраняет глобальное состояние. То есть абсолютно возможно и обычно вводить зависимости от глобального состояния. Что делает DI, так это удаляет связь, которая идет с глобальными переменными и обычно используемым шаблоном Singleton. Помимо немного менее очевидного дизайна, есть очень мало недостатков в устранении этого вида связи, и выгоды от устранения связи возрастают экспоненциально с числом зависимостей от этих глобальных переменных.

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

Таким образом, вопрос о том, чем базы данных отличаются от глобальных, также раздваивается между этими двумя аспектами. Они вводят связь? Да, они могут, но это во многом зависит от того, как разрабатывается приложение и как создается база данных. Существует слишком много факторов, чтобы однозначно ответить на вопрос, вводят ли базы данных глобальную связь без подробных сведений о структуре. Что касается того, вводят ли они совместное использование состояния, ну, это своего рода основной пункт базы данных. Вопрос в том, хорошо ли они это делают. Опять же, я думаю, что это слишком сложно, чтобы ответить без большого количества другой информации, такой как альтернативы и многие другие компромиссы.


0

Я бы подумал об этом немного по-другому: поведение, подобное «глобальной переменной», - это цена, которую платят администраторы баз данных (DBA), потому что это необходимое зло для выполнения их работы.

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

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

Разработка баз данных - одна из тех голосовых ситуаций меньшинства. Инструменты, необходимые для работы DBA, очень мощные, и их теория не основана на инкапсуляции. Чтобы извлекать из своих баз данных каждый скачок производительности, им нужен полный беспрепятственный доступ ко всему, подобно глобальным. Используйте одну из их огромных 100 миллионов строк (или больше!) Баз данных, и вы поймете, почему они не позволяют своему движку БД выдерживать любые удары.

Они платят за это дорогую цену. Администраторы баз данных вынуждены быть почти патологичными с их вниманием к деталям, потому что их инструменты не защищают их. Лучшее, что у них есть в способе защиты - это ACID или, возможно, внешние ключи. Те, кто не патологичен, оказываются в полном беспорядке таблиц, которые совершенно непригодны или даже повреждены.

Это не редкость, когда у вас есть 100 000 линейных программных пакетов. Теоретически, любая строка в программном обеспечении может влиять на любой глобал в любой момент времени. В администраторах баз данных вы никогда не найдете 100 000 различных запросов, которые могут изменить базу данных. Это было бы неразумно поддерживать с вниманием к деталям, необходимым для защиты вас от себя. Если у администратора базы данных есть что-то подобное, он намеренно инкапсулирует свою базу данных, используя средства доступа, обходя проблемы «глобального подобия», и затем выполняет столько работы, сколько возможно, с помощью этого «более безопасного» механизма. Таким образом, когда дело доходит до толчка, даже пользователи базы данных избегают глобальных изменений. Они просто приходят с большой опасностью, и есть альтернативы, которые такие же сильные, но не такие опасные.

Вы бы предпочли прогуляться по битому стеклу или по красиво подметенным тротуарам, если все остальные вещи равны? Да, вы можете ходить по битому стеклу. Да, некоторые люди даже зарабатывают на жизнь этим. Но все же, просто позвольте им подметать тротуар и двигаться дальше!


0

Я думаю, что предпосылка ложна. Нет причин, по которым база данных должна быть «глобальным состоянием», а не (очень большим) контекстным объектом. Если вы привязываетесь к конкретной базе данных, которую ваш код использует через глобальные переменные или фиксированные глобальные параметры подключения к базе данных, это ничем не отличается и не менее вредно, чем любое другое глобальное состояние. С другой стороны, если вы правильно передаете объект контекста для соединения с базой данных, это просто большое (и широко используемое) контекстное состояние, а не глобальное состояние.

Измерение разницы легко: вы могли бы запустить два экземпляра логики вашей программы, каждый из которых использует свою собственную базу данных, в одной программе / процессе без внесения инвазивных изменений в код? Если это так, ваша база данных не является «глобальным состоянием».


-2

Глобалы не являются злом; они просто инструмент. НЕДОСТАТОЧНО глобальных проблем, как и злоупотребление любой другой функцией программирования.

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


Могут ли некоторые из противников объяснить ваши отрицательные голоса? Кажется грубым понижать голос без объяснения причин.
Байрон Джонс

-2

Шаблон только для чтения, и предположите, что ваши данные не обновлены при печати. Очередь пишет или обрабатывает конфликты другим способом. Добро пожаловать в ад, дьявол, вы используете глобальный дБ.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.