全局变量与数据库有何不同?


250

我只是碰到了这个老问题,问全局状态有什么弊端,最受好评的答案断言您不能信任任何与全局变量一起使用的代码,因为其他地方可能会出现其他代码并对其进行修改值,然后您将不知道代码的行为,因为数据不同! 但是,当我看到这一点时,我不禁会认为这是一个很弱的解释,因为与使用数据库中存储的数据有何不同?

当您的程序正在使用来自数据库的数据时,您不必担心系统中的其他代码是否正在更改它,或者即使是完全不同的程序正在更改它。您不在乎数据是什么。这就是重点。重要的是您的代码可以正确处理它遇到的数据。(显然,我正在解决这里经常遇到的棘手的缓存问题,但是暂时让我们忽略它。)

但是,如果您正在使用的数据来自您的代码无法控制的外部来源,例如数据库(或用户输入,网络套接字或文件等),则没有任何问题这样一来,那么代码本身内的全局数据(程序对它具有更大程度的控制)又如何呢?当它显然远比没有人认为是问题的完全正常的东西坏得多时,这又是一件坏事呢?


117
很高兴看到资深会员向教条稍加挑战...
svidgen

10
在应用程序中,通常会提供一种访问数据库的方法,该方法会传递给要访问数据库的函数。您不必使用全局变量来做到这一点,只需知道它们就在眼前。那是关键的区别。
安迪

45
全局状态就像拥有一个数据库,一个表,一个表,一个行,由任意数量的应用程序同时访问的无数列一样。
BevynQ '16

42
数据库也是邪恶的。
Stig Hemmer

27
“反转”您在此处所做的论点并朝另一个方向前进是很有趣的。从逻辑上讲,具有指向另一个结构的指针的结构只是一个表的一行中的外键,该外键指向另一个表的另一行。如何处理任何代码(包括步行链表)与处理数据库中的数据有什么不同?答:不是。问题:为什么我们要使用这种不同的工具来操纵内存中的数据结构和数据库中的数据结构?答:我真的不知道!似乎是历史的偶然,而不是好的设计。
埃里克·利珀特

Answers:


118

首先,我想说的是,您链接到的答案夸大了该特定问题,而全局状态的主要弊端是它以不可预测的方式引入了耦合,这将使将来很难更改系统的行为。

但是进一步研究这个问题,典型的面向对象应用程序中的全局状态与数据库中保存的状态之间存在差异。简要地说,其中最重要的是:

  • 面向对象的系统允许用不同类的对象替换对象,只要它是原始类型的子类型即可。这允许更改行为,而不仅仅是data

  • 应用程序中的全局状态通常不提供数据库所具有的强一致性保证-没有事务可以看到数据库的一致状态,也没有原子更新等。

此外,我们可以将数据库状态视为必然的弊端。从我们的系统中消除它是不可能的。但是,全局状态是不必要的。我们可以完全消除它。因此,即使数据库的问题同样严重,我们仍然可以消除一些潜在的问题,部分解决方案总比没有解决方案好。


44
我认为一致性的原因实际上是主要原因:在代码中使用全局变量时,通常不知道它们何时实际初始化。模块之间的依赖关系深深地隐藏在调用序列中,而诸如交换两个调用之类的简单操作可能会产生非常讨厌的错误,因为突然之间某些全局变量在首次使用时就不再正确地初始化了。至少那是我需要使用的遗留代码的问题,这使得重构成为噩梦。
cmaster

24
@DavidHammen我实际上已经为在线游戏进行了世界状态模拟,这显然属于您正在谈论的应用程序类别,即使在那儿我也不会(也没有)使用全局状态。即使使用全局状态可以提高效率,但问题是全局状态不可伸缩。从单线程体系结构过渡到多线程体系结构后,将变得难以使用。当您迁移到NUMA架构时,它变得效率低下。当您迁移到分布式体系结构时,它变得不可能。您引用的论文的日期为...
Jules

24
1993年。那时这些问题已不再是一个问题。作者正在一个单一处理器系统上工作,模拟1,000个对象的交互。在现代系统中,您可能至少在双核系统上运行了这种模拟,但很可能在单个系统中至少有6个核。对于更大的问题,您仍然可以在群集上运行它。对于这种更改,必须避免使用全局状态,因为不能有效共享全局状态。
Jules

19
我认为将数据库状态称为“必要的邪恶”有点困难。我的意思是,国家从什么时候开始成为邪恶的?状态是数据库的全部目的。状态就是信息。没有状态,您所拥有的只是运算符。没有什么可操作的操作员有什么好处?这个状态必须去某个地方。归根结底,函数式编程只是达到目的的一种手段,没有状态的改变,根本就没有做任何事情的意义。这有点像面包师称蛋糕为必不可少的邪恶-这不是邪恶的。这是事情的全部重点。
J ...

5
@DavidHammen“仍有一些物体至少了解游戏中的每个物体”不一定正确。现代分布式仿真中的一项主要技术是利用局部性并进行近似处理,以使遥远的对象不需要了解遥远的所有事物,只需知道那些遥远的对象的所有者向他们提供了什么数据。
JAB

75

首先,基于对链接问题的公认答案,全局变量有什么问题?

非常简短地讲,它使程序状态不可预测。

在大多数情况下,数据库都符合ACID。ACID专门解决了可能导致数据存储不可预测或不可靠的潜在问题。

此外,全局状态会损害代码的可读性。

这是因为全局变量存在于远离其用途的范围中,甚至可能存在于其他文件中。使用数据库时,您正在使用的记录集或ORM对象位于您正在读取(或应该位于)代码的本地。

数据库驱动程序通常提供一致,可理解的界面来访问相同的数据,而不管问题域如何。从数据库中获取数据时,程序将具有数据副本。更新是原子的。与全局变量相反,除非您自己添加同步,否则多个线程或方法可能在没有原子性的情况下对同一条数据进行操作。数据更新是不可预测的,难以跟踪。更新可能是交错的,从而导致多线程数据损坏的沼泽标准教科书示例(例如交错增量)。

首先,数据库通常对与全局变量不同的数据进行建模,但是撇开了片刻,数据库从头开始被设计为符合ACID的数据存储,从而减轻了对全局变量的许多担忧。


4
+1您要说的是数据库具有事务,因此可以原子地读取和写入多个全局状态。好点,只有对每个完全独立的信息使用全局变量才能避免这一点。
l0b0

1
@ l0b0事务是实现大多数ACID目标的机制,正确的。但是DB接口本身通过将数据带入更本地的范围来使代码更清晰。考虑使用带有try-with-resources块的JDBC RecordSet或使用单个函数调用来获取数据的ORM函数。与此相比,将数据管理在远离全局代码的地方。

1
因此,如果我在函数的开头将值复制到局部变量(带有互斥锁),修改局部变量,然后将值复制到全局变量的末尾,则可以使用全局变量。功能?(...他口口声声地问。)
RM

1
@RM他提到了两点。您抛出的内容可能会解决第一个问题(程序状态无法预测),但不能解决第二个问题(代码的可读性)。实际上,它可能会使您的程序的可读性甚至更差:P。
riwalk

1
@RM您的函数将始终运行,是的。但是您会遇到一个问题,即是否有其他事情同时修改了全局变量,而该修改比您编写的变量更重要。当然,数据库也可能有相同的问题。
格雷厄姆

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
的方式保证(因为我们不能假设)“我们被赋予并没有改变数据”在数据库将是一个交易。
l0b0

是的...应该被暗示为“相同的锁”。
svidgen '16

但是,一天结束时可能很难仔细考虑。

是的,数据库确实处于全局状态-这就是为什么它如此诱人使​​用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
是的,OP表示:“ 您不在乎数据是什么;这才是重点”,我认为这很有趣-如果我们不在乎,那为什么要存储它呢?这里有一个想法:让刚刚停止使用的变量和数据在所有。那应该使事情简单得多。“停止世界,我想下车!”

1
+1从同一数据库写入和读取的不同线程或应用程序可能会导致大量众所周知的问题,这就是为什么应该始终在数据库或应用程序级别上制定一种策略来解决此问题的原因,或者都。因此,您(应用程序开发人员)根本不在乎其他人正在从数据库中读取或写入数据,这绝对不是事实。
Andres F.

1
+1附带说明,这个答案几乎可以解释我最讨厌的依赖注入。它隐藏了这些依赖关系。
jpmc26年

@ jpmc26我可能正在标记单词,但是以上内容不是依赖注入(与全局查找相对)如何使依赖显式的一个很好的例子吗?在我看来,您似乎更喜欢某些API,例如JAX-RS和Spring使用的注释魔术。
埃米尔·伦德伯格

2
@EmilLundberg不,问题是当您具有层次结构时。依赖性注入将较低层的依赖性从较高层的代码中隐藏起来,从而使得难以跟踪哪些事物交互。例如,如果MakeNewThing依靠MakeNewThingInDb并且我的控制器类使用MakeNewThing,那么从我控制器的代码中还不清楚我正在修改数据库。那么,如果我使用另一个实际上当前事务提交给数据库的类怎么办?DI使得很难控制对象的范围。
jpmc26,2013年

18

因为状态可以在其他地方更改,所以不能信任全局变量的唯一原因是,它本身并没有足够的理由不使用它们(这是一个很好的理由!)。答案可能主要是描述用法,在这种用法中,将变量的访问仅限于与其相关的更有意义的代码区域。

但是,数据库是另一回事,因为可以说它们是为“全局”访问而设计的。

例如:

  • 数据库通常具有内置的类型和结构验证,其功能远胜于访问它们的语言
  • 数据库几乎会根据事务一致地进行更新,这可以防止出现不一致的状态,在这种情况下,不能保证最终状态在全局对象中会是什么样(除非隐藏在单例后面)
  • 至少基于表或对象结构隐式地记录数据库结构,这比利用它的应用程序更重要

不过,最重要的是,数据库的用途不同于全局变量。数据库用于存储和搜索大量有组织的数据,其中全局变量用作特定的利基(在合理的情况下)。


1
嗯 在我写一个几乎完全相同的答案的过程中,您击败了我。:)
Jules

@Jules您的答案从应用程序方面提供了更多细节;收下。
杰弗里·斯威尼

但是,除非您完全依赖存储过程进行数据访问,否则所有这些结构仍将无法强制按预期使用表。或者以适当的顺序执行操作。或根据需要创建锁(事务)。
svidgen '16

嗨,如果您使用的是Java等静态类型的语言,第1点和第3点是否仍然适用?
杰斯文·何塞

@aitchnyu不一定。要指出的是,建立数据库是为了可靠地共享数据,而全局变量通常不是。与严格类型的NoSQL数据库相比,以严格的语言实现自文档接口的对象具有不同的目的。
杰弗里·斯威尼

10

但是,当我看到这一点时,我不禁会认为这是一个很弱的解释,因为与使用数据库中存储的数据有何不同?

或与交互设备,文件,共享内存等不同的程序。每次运行时执行完全相同的操作的程序是一个非常无聊且无用的程序。是的,这是一个很弱的论点。

对我而言,在全局变量方面有所不同的不同之处在于,它们形成了隐藏且不受保护的通信线路。从键盘读取非常明显并且受到保护。我必须进行某个函数调用,并且无法访问键盘驱动程序。这同样适用于文件访问,共享内存以及您的示例数据库。对于代码的阅读者来说,很明显,该功能是从键盘读取的,该功能访问文件,其他一些功能访问共享内存(最好对此进行保护),而其他一些功能访问数据库。

另一方面,对于全局变量,它一点也不明显。API说要调用foo(this_argument, that_argument)。调用序列中没有任何内容表明全局变量g_DangerWillRobinson应设置为某个值,但应在调用之前foo(或在调用之后进行检查foo)。


谷歌禁止使用的非const引用参数在C ++中,主要是因为它是不明显的代码的读者foo(x)会改变x,因为这foo需要一个非常引用作为参数。(与C#相比,C#规定函数定义和调用站点都必须使用ref关键字对引用参数进行限定。)虽然我不同意Google的标准,但我确实理解它们的观点。

代码只编写一次,修改几次,但是如果很好,它就会被读取很多次。隐藏的通信线路是非常糟糕的业障。C ++的非const引用代表次要的隐藏通信线。一个好的API或一个好的IDE会告诉我“哦!这是通过引用调用的”。全局变量是巨大的通信隐患。


您的答案更有意义。
Billal Begueradj,

8

我认为引用的解释将问题简化到推理变得荒谬的程度。当然,外部数据库的状态有助于全局状态。重要的问题是如何您的程序取决于(可变)全局状态。如果在空白处分割字符串的库函数将取决于存储在数据库中的中间结果,那么我将反对这种设计至少与反对用于相同目的的全局字符数组一样多。另一方面,如果您决定此时您的应用程序不需要完整的DBMS来存储业务数据,并且可以使用全局内存中的键值结构,则不一定表明设计不良。重要的是,无论您选择哪种存储数据的解决方案,此选择都隔离在系统的一小部分,因此大多数组件可以与为部署选择的解决方案无关,并可以进行隔离和已部署的单元测试解决方案可以在稍后的时间轻松更改。


8

作为主要从事嵌入式固件工作的软件工程师,我几乎总是将全局变量用于模块之间的所有操作。实际上,这是嵌入式的最佳实践。它们是静态分配的,因此没有炸开堆/堆栈的风险,也不需要花费额外的时间来分配/清理函数入口/出口。

这种方法的缺点是,我们必须考虑如何将这些变量的使用,以及很多的归结为同一种思想是进入数据库扯皮。变量的任何异步读/写都必须是原子的。如果可以在一个位置上写入多个变量,则必须考虑确保它们始终写入有效数据,因此不会任意替换前一次写入(或者安全地进行任意替换)。如果多次读取同一变量,则必须考虑一下如果变量在两次读取之间更改值会发生什么,或者必须在开始时获取变量的副本,以便即使使用一致的值进行处理,即使该值在处理过程中变得过时。

(对于最后一个,在我从事与航空安全系统高度相关的飞机对策系统合同的第一天,软件团队正在查看他们试图找出一个星期左右的错误报告。我有足够的时间下载开发工具和代码副本。我问“难道在读取之间就不会更新该变量并引起它吗?”但并没有真正得到答案。毕竟,新手知道吗?所以,当他们仍在讨论它时,我添加了保护性代码以原子方式读取该变量,进行了本地构建,并基本上说“嘿,请尝试一下”。 ::)

因此,全局变量并不是一个明确的坏事,但是如果您不仔细考虑它们,它们的确会使您面临许多问题。


7

根据您要判断的方面,全局变量和数据库访问可能是天壤之别,但是只要我们将它们视为依赖项,它们就相同。

让我们考虑函数式编程对纯函数状态的定义,即它必须仅取决于其作为输入的参数,从而产生确定性的输出。也就是说,给定两次相同的参数集,它必须产生相同的结果。

当函数依赖于全局变量时,就不能再将其视为纯函数了,因为对于相同的集合或参数,由于全局变量的值在两次调用之间可能已更改,因此它可能会产生不同的输出。

但是,如果我们将全局变量视为函数接口的一部分,而不考虑其其他参数,则该函数仍然可以被视为确定性函数,所以这不是问题。问题在于,直到我们看似显而易见的函数的意外行为感到惊讶之前,它都是隐藏的,然后阅读其实现以发现隐藏的依赖项

在这一部分中,全局变量成为隐藏的依赖项的那一刻被美国程序员视为邪恶。它使代码更难以推理,难以预测其行为,难以重用,难以测试,尤其是在出现问题时增加了调试和修复时间。

当我们隐藏对数据库的依赖关系时,也会发生同样的事情。我们可以有直接调用数据库查询和命令的函数或对象,隐藏这些依赖关系并给我们带来与全局变量完全相同的麻烦。或者我们可以将它们明确化,事实证明,这是最佳实践,它有很多名称,例如存储库模式,数据存储,网关等。

PS:还有其他方面对于此比较也很重要,例如是否涉及并发,但是这里的其他答案也涵盖了这一点。


我喜欢您从依赖关系的角度出发。
cbojar '16

6

好的,让我们从历史的角度开始。

我们在一个旧的应用程序中,用您的汇编语言和C语言的典型组合编写。没有函数,只有过程。当您要传递参数或从过程中返回值时,可以使用全局变量。不用说,这很难跟踪,一般而言,每个过程都可以对每个全局变量执行所需的操作。毫不奇怪,人们会在可行的情况下尽快以不同的方式传递参数和返回值(除非这样做对性能至关重要,例如查看Build Engine(Duke 3D)源代码)。全局变量的恨就在这里诞生-您几乎不知道每个过程将读取和更改的全局状态是什么,并且您不能真正地安全嵌套过程调用。

这是否意味着全局变量仇恨已成为过去?不完全的。

首先,我必须提到的是,在我现在正在研究的项目中,我已经看到了传递参数的完全相同的方法。在大约10年的项目中,要在C#中传递两个引用类型的实例。从字面上看,没有足够的理由这样做,并且很可能是出于对货物的了解,或者对C#的工作方式有完全的误解。

更大的一点是,通过添加全局变量,您正在扩展有权访问该全局变量的每段代码的范围。还记得所有这些建议,例如“使方法保持简短”吗?如果您有600个全局变量(同样是真实示例,则为:/),则所有方法范围都将被这600个全局变量隐式扩展,并且没有简单的方法来跟踪谁可以访问什么。

如果做错了(通常的方法:)),全局变量之间可能会相互耦合。但是您不知道它们是如何耦合的,也没有机制可以确保全局状态始终保持一致。即使您引入关键部分来尝试使内容保持一致,您也会发现它与适当的ACID数据库相比差强人意:

  • 除非您在“事务”之前保留旧值,否则无法回滚部分更新。不用说,到目前为止,将值作为参数传递已经是一个胜利:)
  • 访问相同状态的每个人都必须遵守相同的同步过程。但是没有办法强制执行此操作-如果您忘记设置关键部分,那就太麻烦了。
  • 即使您正确同步了所有访问,也可能会有嵌套调用访问部分修改的状态。这意味着您要么陷入僵局(如果您的关键部分不重要),要么处理不一致的数据(如果它们不重要)。

有可能解决这些问题吗?并不是的。您需要封装来处理这个问题,或者说是严格的纪律。做正确的事很难,而且通常不是成功开发软件的好方法:)

范围越小,代码就越容易推理。全局变量使即使最简单的代码段也包含了巨大的范围。

当然,这并不意味着全球范围界定是邪恶的。它不应该是您追求的第一个解决方案-它是“易于实现,难以维护”的典型示例。


听起来很像物理世界:很难回滚。

这是一个很好的答案,但是一开始它可以支持论文陈述(TL; DR部分)。
jpmc26,2016年

6

全局变量是一种工具,它可以用于善与恶。

数据库是一种工具,它可以用于善与恶。

正如原始海报所指出的,差异并不大。

没有经验的学生经常认为错误是其他人会遇到的事情。教师使用“全局变量是邪恶的”作为惩罚不良设计的简化原因。学生通常不理解仅仅因为他们的100行程序没有错误,并不意味着相同的方法可以用于10000行程序。

当使用数据库时,您不能仅仅禁止全局状态,因为这就是程序的全部目的。相反,您会获得更多详细信息准则,例如ACID和范式等。

如果人们对全局变量使用ACID方法,那还不错。

另一方面,如果您对数据库的设计不好,则它们可能是噩梦。


3
典型的学生对stackoverflow的主张:帮帮我!我的代码很完美,但是效果不佳!
大卫·哈曼

“ ACID全局变量方法”-请参阅Clojure中的参考。
查尔斯·达菲

@DavidHammen,您认为专业人士的大脑与学生不同吗?
Billal Begueradj '16

@BillalBEGUERADJ-那是专业人士和学生之间的区别。我们知道,尽管有多年的经验,并且尽管在代码审查,测试等方面付出了最大的努力,但我们的代码并不完美。
大卫·哈曼


5

对我而言,主要的弊端是Globals无法防止并发问题。您可以添加用于处理Globals这类问题的机制,但是您会发现,解决的并发性问题越多,Globals就越会模仿数据库。次要的弊端是没有使用合同。


3
例如,errno在C.
David Hammen

1
这正好解释了为什么全局变量和数据库不同的原因。可能还有其他差异,但是您的特定职位完全破坏了这一概念。如果您提供了一个简短的代码示例,那么我相信您会获得很多支持。例如MyFunc(){x = globalVar * 5; // ....其他一些处理;y = globalVar * 34; //糟糕,某些其他线程可能在执行某些其他操作期间更改了globalVar,并且x和y在其计算中对globalVar使用了不同的值,这几乎肯定不会给出令人满意的结果。
邓肯

5

其他一些答案试图解释为什么使用数据库是好的。他们错了!数据库是全局状态,因此就像单例或全局变量一样邪恶。当您可以轻松地使用本地Map或Array代替数据库时,使用数据库是种错误!

全局变量允许全局访问,这带来了滥用风险。全局变量也有上行空间。一般说来,全局变量是您应该避免的事情,而不是永远不要使用的事情。如果可以轻松避免它们,则应避免使用它们。但是,如果好处大于缺点,那么您当然应该使用它们!*

完全相同的**适用于全局状态的数据库,就像全局变量一样。如果您可以在不访问数据库的情况下完成工作,并且生成的逻辑可以满足您的所有需求,并且同样复杂,那么使用数据库会给您的项目增加更多的风险,而没有任何相应的好处。

在现实生活中,许多应用程序需要通过设计来实现全局状态,有时甚至需要持久的全局状态-这就是我们拥有文件,数据库等的原因。


*这里是学生的例外。禁止学生使用全局变量是很有意义的,因此他们必须学习替代方法。

**一些答案错误地声称,数据库在某种程度上比其他形式的全局状态受到更好的保护(问题明确地关于全局状态,而不仅仅是全局变量)。那是胡扯。按照惯例,数据库方案中提供的主要保护功能与其他任何全局状态完全相同。大多数语言还为全局状态提供了很多额外的保护,例如const,类的形式,这些类根本不允许在构造函数中设置状态后更改其状态,或者允许将线程信息或程序状态考虑在内的getter和setter。


2

从某种意义上说,全局变量与数据库之间的区别类似于对象的私有成员与公共成员之间的区别(假定任何人仍然使用公共字段)。如果将整个程序视为一个对象,则全局变量是私有变量,而数据库是公共字段。

他们在这里的主要区别是假定的责任之一。

编写对象时,假定维护成员方法的任何人都将确保私有字段的行为良好。但是您已经放弃了对公共领域状况的任何假设,并格外小心。

相同的假设在更广泛的程度上适用于globals v / s数据库。同样,编程语言/生态系统保证了对私有v / s公共的访问限制,就像在(非共享内存)全局v / s数据库上实施私有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之类的工具轻松提取和分析此关系。另一方面,全局变量略有不同。您可以找到所有用法,但是您不可能告诉我变量与世界其他地方交互的所有方式。

  • 全局变量更快。从数据库中获取内容需要建立数据库连接,运行对我的选择,然后必须关闭数据库连接。您可能还需要进行任何类型转换。将其与在代码中访问的全局变量进行比较。

这些是我目前唯一能想到的,但是我敢肯定还有更多。简而言之,它们是两个不同的事物,应该用于不同的目标


0

当然,全局变量并不总是不适当的。它们之所以存在是因为它们有合法用途。全局变量的主要问题以及避免使用全局变量的主要来源是,使用全局变量的代码仅附加到一个全局变量上。

例如,考虑一个存储服务器名称的HTTP服务器。

如果将服务器名称存储在全局名称中,则该进程无法同时运行两个不同服务器名称的逻辑。也许最初的设计从来没有考虑过一次运行一个以上的服务器实例,但是如果您以后决定要这样做,那么服务器名称就不能在全局范围内。

相反,如果服务器名称在数据库中,则没有问题。您可以简单地为HTTP服务器的每个实例创建该数据库的一个实例。因为服务器的每个实例都有其自己的数据库实例,所以它可以具有自己的服务器名称。

因此,对全局变量的主要反对意见是,访问该全局变量的所有代码只能有一个值,不适用于数据库条目。相同的代码可以轻松访问具有特定条目不同值的不同数据库实例。


0

我认为这是一个有趣的问题,但很难回答,因为在“全球国家”一词下有两个主要问题被混和在一起。首先是“全局耦合”的概念。证明是为全局状态提供的替代方法是依赖项注入。问题是DI并不一定消除全局状态。也就是说,注入对全局状态的依赖绝对是可能且普遍的。DI的工作是消除全局变量和常用的Singleton模式带来的耦合。除了不那么明显的设计外,消除这种耦合几乎没有什么弊端,消除耦合的好处随着对这些全局变量的依赖性数量的增加而成倍增加。

另一个方面是共享状态。我不确定全局共享状态和总体共享状态之间是否存在真正明显的区别,但是成本和收益要细微得多。简而言之,有无数个需要共享状态才有用的软件系统。例如,比特币是一种以分散的方式在全球(字面上)共享状态的非常聪明的方法。在不造成巨大瓶颈的情况下正确共享可变状态是困难但有用的。因此,如果您真的不需要这样做,则可以通过最小化共享可变状态来简化应用程序。

因此,在这两个方面,关于数据库与全局变量的区别也被分为两类。他们会引入耦合吗?是的,他们可以,但是在很大程度上取决于应用程序的设计方式和数据库的设计方式。对于数据库是否在没有设计细节的情况下引入全局耦合,有太多因素无法一一回答。至于它们是否引入了状态共享,那正是数据库的重点。问题是他们是否做得好。再次,我认为如果没有很多其他信息(例如替代方案和许多其他折衷方案),那么回答就太复杂了。


0

我会略有不同的思考:“行为”之类的“全局变量”是数据库管理员(DBA)付出的代价,因为这样做是完成工作的必要手段。

正如其他几个人指出的那样,全局变量的问题并不是一个任意的问题。问题在于,使用它们会使程序的行为越来越难以预测,因为很难确定谁在使用变量以及以何种方式使用变量。对于现代软件来说,这是一个大问题,因为现代软件通常被要求做很多灵活的事情。在运行过程中,它可能会进行数十亿甚至数万亿次的复杂状态操纵。能够证明有关该软件在数十亿或数万亿次操作中将执行的操作的真实陈述的能力非常有价值。

对于现代软件,我们所有的语言都提供了诸如封装之类的辅助工具。不使用它的选择是不必要的,这导致了“全球化是邪恶的”心态。在软件开发领域的许多地区,唯一使用它们的人是不知道如何更好地编码的人。这意味着他们不仅直接遇到麻烦,而且间接表明开发人员不知道自己在做什么。在其他地区,您会发现全局变量是完全正常的(尤其是嵌入式软件喜欢全局变量,部分原因是它们与ISR配合良好)。但是,在众多软件开发人员中,他们只是少数派的声音,因此您听到的唯一声音是“全球人士是邪恶的”。

数据库开发是少数少数情况之一。完成DBA工作所需的工具非常强大,并且其理论并不扎根于封装。为了从数据库中找出每一点性能,他们需要完全不受限制地访问一切,类似于全局变量。运用了他们庞大的1亿行(或更多!)数据库之一,您将欣赏为什么他们不让自己的数据库引擎承受任何压力。

他们为此付出了昂贵的代价。DBA被迫对细节的关注几乎是病态的,因为它们的工具不能保护他们。他们最好的保护方式是ACID或外键。那些不是病态的人会发现自己陷入了完全无法使用甚至损坏的混乱局面。

拥有10万行软件包的情况并不少见。从理论上讲,软件中的任何行都可能在任何时间点影响任何全局变量。在DBA中,您永远找不到100k可以修改数据库的不同查询。保持必要的细节来保护自己不受保护,这是不合理的。如果一个DBA有这么大的东西,他们将有意使用访问器封装数据库,避开“全局性”问题,然后通过“更安全”的机制尽其所能。因此,当推推推时,甚至数据库人员也避免使用全局变量。他们干脆来了很多的危险,并有替代品在强,但不是很危险。

如果其他所有条件都一样,您是愿意在碎玻璃上还是在人行横道上走来走去?是的,您可以在碎玻璃上行走。是的,有些人甚至以此为生。但还是让他们扫一下人行道继续前进!


0

我认为前提是错误的。没有理由数据库不需要是“全局状态”而不是(很大)上下文对象。如果要绑定到特定数据库,则代码是通过全局变量或固定的全局数据库连接参数使用的,这与任何其他全局状态没有什么不同,并且也没有什么坏处。另一方面,如果为数据库连接正确传递了上下文对象,则它只是较大的(且已广泛使用)上下文状态,而不是全局状态。

测量差异很容易:您是否可以在一个程序/进程中运行两个程序逻辑实例,每个实例使用其自己的数据库,而无需对代码进行侵入式更改?如果是这样,则您的数据库不是真正的“全局状态”。


-2

全球人不是邪恶的。它们只是一个工具。全局变量的误用以及任何其他编程功能的误用都是有问题的。

我的一般建议是,仅应在已充分理解和考虑的情况下使用全局变量,而其他解决方案都不是最佳方案。最重要的是,您要确保已妥善记录可在何处修改该全局值,并且如果您正在运行多线程,则要确保以事务性方式访问全局和任何依赖于全局的全局变量。


某些拒绝投票者会介意解释您的拒绝投票吗?不加解释地拒绝投票似乎是不礼貌的。
拜伦·琼斯

-2

只读模式,并假设您的数据在打印时不是最新的。队列写或处理冲突的另一种方式。欢迎来到地狱魔鬼,您正在使用全局数据库。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.