我遇到了一个古老的问题,询问全局状态有什么弊端,最受好评的答案断言您不能信任任何与全局变量一起使用的代码,因为其他地方可能会出现其他代码并修改其值,然后您将不知道代码的行为,因为数据不同!但是当我看到它时,我不禁会认为这是一个很弱的解释,因为与使用数据库中存储的数据有何不同?
当您的程序正在使用时从数据库中获取数据,您不必担心系统中的其他代码是否正在更改它,或者即使完全不同的程序也在更改它。您不在乎数据是什么。这就是重点。重要的是您的代码正确处理了它遇到的数据。 (显然,我在这里解决了缓存这一经常棘手的问题,但是暂时让我们忽略它。)
但是,如果您正在使用的数据来自外部来源,代码没有控制权,例如数据库(或用户输入,网络套接字或文件等),这没什么不对,那么代码本身中的全局数据又如何呢?具有更大程度的控制权-一件坏事显然比没有人认为是问题的完全正常的事情要糟得多吗?
#1 楼
首先,我想说的是,您链接到的答案夸大了该特定问题,而全局状态的主要弊端在于,它以不可预测的方式引入了耦合,从而使将来很难更改系统的行为。但进一步研究这个问题,典型的面向对象应用程序中的全局状态与数据库中保存的状态之间存在差异。简要地说,其中最重要的是:面向对象的系统允许将对象替换为其他类的对象,只要它是原始类型的子类型即可。这允许更改行为,而不仅仅是数据。
应用程序中的全局状态通常不提供数据库所具有的强大一致性保证-没有事务可以看到数据库的一致状态,没有原子性更新等。
此外,我们可以将数据库状态视为必然的弊端。从我们的系统中消除它是不可能的。但是,全局状态是不必要的。我们可以完全消除它。因此,即使数据库存在同样严重的问题,我们仍然可以消除一些潜在的问题,部分解决方案总比没有解决方案要好。
评论
我认为一致性的原因实际上是主要原因:在代码中使用全局变量时,通常无法确定何时实际初始化它们。模块之间的依赖关系深深地隐藏在调用序列中,而诸如交换两个调用之类的简单操作却会产生非常讨厌的错误,因为突然之间某些全局变量在首次使用时就不再正确初始化了。至少那是我需要使用的遗留代码的问题,这使得重构成为噩梦。
–cmaster-恢复莫妮卡
16年5月24日在20:13
@DavidHammen我实际上已经为在线游戏进行了世界状态模拟,这显然属于您正在谈论的应用程序类别,即使在那儿,我也不会(也没有)使用全局状态。即使使用全局状态可以提高效率,但问题是全局状态不可伸缩。从单线程体系结构过渡到多线程体系结构后,将变得难以使用。当您迁移到NUMA架构时,它变得效率低下。当您迁移到分布式体系结构时,它变得不可能。您引用的论文可追溯至...
–法律
16年5月25日在10:47
1993年。那时这些问题已不再是一个问题。作者正在一个单一处理器系统上工作,模拟1,000个对象的交互。在现代系统中,您可能至少在双核系统上运行了这种模拟,但很可能在单个系统中至少有6个核。对于更大的问题,您仍然可以在群集上运行它。对于这种更改,必须避免使用全局状态,因为不能有效共享全局状态。
–法律
16年5月25日在10:56
我认为将数据库状态称为“必要的邪恶”有点困难。我的意思是,国家从什么时候开始成为邪恶的?状态是数据库的全部目的。状态就是信息。没有状态,您所拥有的只是运算符。没有什么要操作的操作员有什么好处?这个状态必须去某个地方。归根结底,函数式编程只是达到目的的一种手段,没有状态的改变,根本就没有做任何事情的意义。这有点像面包师称蛋糕为必不可少的邪恶-这不是邪恶的。这是事情的全部重点。
–... J ...
16年5月25日在12:25
@DavidHammen“仍有一些物体至少了解游戏中的每个物体”不一定正确。现代分布式仿真中的一项主要技术是利用局部性并进行近似处理,以使遥远的对象不需要了解遥远的所有事物,而只需知道那些遥远的对象的所有者向他们提供了什么数据。
– JAB
16年5月25日在14:16
#2 楼
首先,基于对链接的问题的公认答案,全局变量有什么问题?很简单,它使程序状态不可预测。
大多数时候,数据库都符合ACID标准。 ACID专门解决了可能导致数据存储无法预测或不可靠的潜在问题。
此外,全局状态会损害代码的可读性。
这是因为全局变量存在于远离其用途的范围内,甚至可能存在于其他文件中。使用数据库时,您正在使用的记录集或ORM对象位于正在读取(或应该位于)代码的本地。
数据库驱动程序通常提供一致,可理解的接口来访问数据不论问题域如何都相同。从数据库中获取数据时,程序将具有数据副本。更新是原子的。与全局变量相反,除非您自己添加同步,否则多个线程或方法可能没有原子性地对同一条数据进行操作。数据更新是不可预测的,很难跟踪。更新可能是交错式的,从而导致标准的教科书示例中出现多线程数据损坏(例如交错式增量)的情况。
数据库通常以与全局变量不同的模型来建模,但是暂时将数据库建模从头设计,是一个符合ACID的数据存储,可减轻全局变量带来的许多困扰。
评论
+1您要说的是数据库具有事务,因此可以原子地读取和写入多个全局状态。好点,只有对每个完全独立的信息使用全局变量才能避免这一点。
–l0b0
16年5月25日在7:40
@ l0b0事务是实现大多数ACID目标的机制,正确的。但是DB接口本身通过将数据带入更本地的范围来使代码更清晰。考虑将JDBC RecordSet与try-with-resources块一起使用,或者将ORM函数使用单个函数调用获取一条数据。与此相比,将数据管理在远离全局代码的地方。
–user22815
16年5月25日在10:03
因此,如果我在函数的开头将值复制到局部变量(带有互斥锁),修改局部变量,然后在结束时将值复制回全局变量,则可以使用全局变量。功能? (...他反问道。)
– R.M.
16年5月26日在18:03
@R M。他提到两点。您抛出的内容可能会解决第一个问题(程序状态不可预测),但不会解决第二个问题(代码的可读性)。实际上,它可能会使您的程序的可读性甚至更差:P。
–riwalk
16年5月26日在19:07
@R M。您的函数将始终运行,是的。但是您会遇到一个问题:与此同时,是否还有其他东西修改了全局变量,而这种修改比您编写的内容更重要。当然,数据库也可能有相同的问题。
–格雷厄姆
16年5月27日在13:11
#3 楼
我将提供一些观察结果:是的,数据库是全局状态。
实际上,正如您所指出的,它是一个超全局状态。通用!它的范围要求连接到数据库的任何东西。而且,我怀疑许多有多年经验的人会告诉您有关数据中“奇怪的事物”如何导致一个或多个相关应用程序中的“意外行为”的恐怖故事...
使用全局变量的潜在后果之一是,两个不同的“模块”将出于各自不同的目的而使用该变量。从这个意义上说,数据库表没有什么不同。它可能会成为同一问题的受害者。
嗯...这是要解决的问题:
如果模块不能以某种方式进行外部操作,则它什么也不做。 br />
可以给有用的模块数据,也可以找到它。并且,它可以返回数据或可以修改状态。但是,如果它不以某种方式与外部世界交互,那么它也可能什么也不做。
现在,我们的首选是接收数据并返回数据。如果大多数模块完全不用考虑外界在做什么,那么它们就更容易编写。但是最终,需要一些东西来查找数据并修改该外部全局状态。
此外,在实际应用程序中,数据存在,以便可以通过各种操作读取和更新数据。锁和事务可以防止某些问题。但是,原则上要防止这些操作在一天结束时彼此冲突,只需要仔细考虑即可。 (而且会犯错误...)
但是,通常,我们通常不直接使用全局状态。
除非应用程序位于数据层(在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.
}
}
我们根本不使用数据库来处理诸如此类的过程/操作中的东西。可能是数据库的缓慢特性和简单变量的相对便利性使我们望而却步:我们与数据库的呆滞,笨拙的交互作用使它们成为了我们过去使用变量犯下的许多错误的不佳之选。 >
评论
保证(因为我们不能假设)数据库中的“我们得到的数据没有更改”的方法将是事务。
–l0b0
16年5月25日在7:46
是的...应该被暗示为“相同的锁”。
– svidgen
16年5月25日在11:54
但是,一天结束时可能很难仔细考虑。
–user186205
16年5月26日在0:51
是的,数据库确实是全局状态的-这就是为什么它如此诱人使用git或ipfs之类的数据来共享数据的原因。
–威廉·佩恩(William Payne)
16年5月26日在12:19
#4 楼
我不同意以下基本说法:当您的程序正在使用数据库中的数据时,您不必关心系统中的其他代码是否正在更改它,或者即使完全不同的程序正在对此进行更改。
我最初的想法是“哇,就是哇”。为了避免这种情况,我们花费了大量的时间和精力-并权衡了每个应用程序的权衡和折衷方案。仅仅忽略它是灾难的根源。
但是我在体系结构层面上也是不同意的。全局变量不仅仅是全局状态。它是全局状态,可以从任何地方透明访问。
与使用数据库相反,您需要具有它的句柄-(除非您要存储而不是在全局变量中句柄...。)
例如使用全局变量可能看起来像这样
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);
...
}
所以我认为使用数据库的意义很大更像使用显式状态,而不是使用全局变量。
评论
是的,我以为OP会说:“您不在乎数据是什么;这就是重点”,这很有趣-如果我们不在乎,那为什么要存储它呢?这是一个想法:让我们完全停止使用变量和数据。那应该使事情简单得多。 “停止世界,我想下车!”
–user186205
16年5月26日,0:56
+1从同一数据库写入和读取的不同线程或应用程序可能会导致大量众所周知的问题,这就是为什么应该始终在数据库或应用程序级别上制定一种策略来解决此问题的原因,或者都。因此,您(应用程序开发人员)根本不在乎其他人正在从数据库中读取或写入数据,这绝对不是事实。
– Andres F.
16年5月27日在17:39
+1在一个旁注中,这个答案在很大程度上解释了我最讨厌的依赖注入。它隐藏了这类依赖。
– jpmc26
16年5月28日在1:04
@ jpmc26我可能正在标记单词,但是以上内容不是依赖注入(与全局查找相对)如何使依赖显式的一个很好的例子吗?在我看来,您似乎更喜欢某些API,例如JAX-RS和Spring使用的注释魔术。
–埃米尔·伦德伯格(Emil Lundberg)
16年5月31日在9:39
@EmilLundberg不,问题是当您具有层次结构时。依赖性注入将较低层的依赖性从较高层的代码中隐藏起来,从而使得难以跟踪哪些事物交互。例如,如果MakeNewThing依赖于MakeNewThingInDb,而我的控制器类使用MakeNewThing,则从我控制器的代码中还不清楚我正在修改数据库。那么,如果我使用另一个实际上将当前事务提交给数据库的类怎么办? DI使得很难控制对象的范围。
– jpmc26
16年5月31日在9:46
#5 楼
因为状态可以在其他地方更改,所以不能信任全局变量的唯一原因是,它本身并没有足够的理由不使用它们(这是一个很好的理由!)。答案可能主要是描述用法,将变量限制为仅访问与之相关的代码区域更有意义。数据库是另一回事,因为它们是为可以说是“全局”访问的目的。
例如:
数据库通常具有内置的类型和结构验证,它比语言访问更重要它们
数据库几乎都是基于事务进行一致更新的,这可以防止出现不一致的状态,在这种情况下,不能保证最终状态在全局对象中会是什么样(除非它隐藏在单例后面)。
数据库结构至少是基于表或对象结构的隐式文档,比起使用它的应用程序更重要。
最重要的是,数据库的用途不同于全局变量。数据库用于存储和搜索大量有组织的数据,其中全局变量用于特定的利基(在合理的情况下)。
评论
嗯在我写一个几乎相同的答案的过程中,您击败了我。 :)
–法律
16年5月24日在20:06
@Jules您的答案从应用程序方面提供了更多细节;留着它。
–杰弗里·斯威尼(Jeffrey Sweeney)
16年5月24日在20:10
但是,除非您完全依赖存储过程进行数据访问,否则所有这些结构仍将无法强制按预期使用表。或者以适当的顺序执行操作。或根据需要创建锁(事务)。
– svidgen
16年5月24日20:10
嗨,如果您使用的是Java等静态类型的语言,第1点和第3点是否仍然适用?
– Jesvin Jose
16年6月1日在10:09
@aitchnyu不一定。要指出的是,建立数据库是为了可靠地共享数据,而全局变量通常不是。与严格类型的NoSQL数据库相比,以严格的语言实现自文档接口的对象具有不同的目的。
–杰弗里·斯威尼(Jeffrey Sweeney)
16年6月1日在13:48
#6 楼
但是当我看到它时,我禁不住认为这是一个很弱的解释,因为与使用数据库中存储的数据有何不同?
或者与使用交互式设备,文件,共享内存等不同的东西。每次运行时执行完全相同的操作的程序是非常无聊且无用的程序。所以,是的,这是一个微不足道的论点。
对我来说,与全局变量有所不同的不同之处在于,它们形成了隐藏且不受保护的通信线路。从键盘读取非常明显并且受到保护。我必须进行某个函数调用,并且无法访问键盘驱动程序。这同样适用于文件访问,共享内存以及您的示例数据库。对于代码的读者来说,很明显,该功能是从键盘读取的,该功能访问文件,其他一些功能访问共享内存(最好对此进行保护),而其他一些功能访问数据库。 />
另一方面,对于全局变量,它一点也不明显。 API说要调用
foo(this_argument, that_argument)
。调用序列中没有任何内容表明应将全局变量g_DangerWillRobinson
设置为某个值,但应在调用foo
之前(或在调用foo
之后进行检查)。Google禁止使用非常量在C ++中引用参数,主要是因为对于代码的读者来说,
foo(x)
不会更改x
,因为foo
采用非恒定引用作为参数,这对代码读者而言并不明显。 (与C#相比,C#规定函数定义和调用站点都必须使用ref
关键字限定引用参数。)虽然我不同意Google的标准,但我确实理解它们的观点。代码只编写一次,修改几次,但是如果很好,它就会被读取很多遍。隐藏的通信线路是非常糟糕的业障。 C ++的非const引用代表少量的隐藏通信线。一个好的API或一个好的IDE会告诉我“哦!这是通过引用调用的”。全局变量是巨大的通信隐患。
评论
您的答案更有意义。
– Billal Begueradj
16年5月28日在14:12
#7 楼
我认为引用的解释将问题简化到了使推理变得荒谬的程度。当然,外部数据库的状态有助于全局状态。重要的问题是您的程序如何依赖(可变)全局状态。如果在空白处分割字符串的库函数将取决于存储在数据库中的中间结果,那么我将反对这种设计至少与反对用于相同目的的全局字符数组一样多。另一方面,如果您决定此时您的应用程序不需要完整的DBMS来存储业务数据,并且可以使用全局内存中的键值结构,则不一定表明设计不良。重要的是,无论您选择哪种存储数据的解决方案,此选择都隔离在系统的一小部分,因此大多数组件可以与为部署选择的解决方案无关,并可以进行隔离和已部署的单元测试解决方案可以在以后的工作中轻松更改。#8 楼
作为主要从事嵌入式固件工作的软件工程师,我几乎总是将全局变量用于模块之间的所有操作。实际上,这是嵌入式的最佳实践。它们是静态分配的,因此没有炸开堆/堆栈的风险,也没有多余的时间分配/清理函数入口/出口。缺点是我们这样做必须考虑如何使用这些变量,而其中很多归结于数据库处理中的相同思想。变量的任何异步读/写都必须是原子的。如果可以在一个位置上写入多个变量,则必须考虑确保它们始终写入有效数据,因此不会任意替换前一次写入(或者安全替换是任意操作)。如果多次读取同一变量,则必须考虑一下如果变量在两次读取之间更改值会发生什么,或者必须在开始时获取变量的副本,以便使用一致的值进行处理,即使在处理过程中,该值变得过时。
(对于最后一个,在我开发飞机对策系统的合同的第一天,与安全性高度相关,软件团队正在研究一个错误报告他们一直试图找出一个星期左右的时间。我有足够的时间下载开发工具和代码副本。我问“在读取之间是否会导致该变量更新并引起它? “但是并没有真正得到答案。嘿,毕竟新手知道什么?所以,当他们仍在讨论它时,我添加了保护性代码以原子方式读取变量,进行了本地构建,基本上说:“嘿伙计们,试试看”。证明我值得签约的方法。:)
es并不是明确的坏事,但是如果您不仔细考虑它们,它们的确会使您面临各种各样的问题。
#9 楼
根据您所判断的方面,全局变量和数据库访问可能是天壤之别,但是只要我们将它们视为依赖项,它们都是相同的。让我们考虑函数式编程的定义一个纯函数表示它必须完全取决于它作为输入的参数,从而产生确定的输出。也就是说,给定两次相同的参数集,它必须产生相同的结果。
当一个函数依赖于全局变量时,它不再被视为纯函数,因为对于相同的集合或参数,它可能会产生不同的输出,因为全局变量的值在两次调用之间可能已更改。
但是,如果我们将全局变量视为函数接口的一部分以及其他参数,那么该函数仍然可以被视为确定性函数,所以这不是问题。问题在于,直到我们对看似显而易见的函数的意外行为感到惊讶之前,它一直是隐藏的,然后继续阅读它们的实现以发现隐藏的依赖项。
这部分是全局变量的那一刻成为隐藏的依赖关系是我们程序员认为是邪恶的。它使代码更难以推理,难以预测其行为,难以重用,难以测试,尤其是在出现问题时增加调试和修复时间。
发生同样的事情当我们隐藏对数据库的依赖关系时。
我们可以使函数或对象直接调用数据库查询和命令,隐藏这些依赖关系并给我们带来与全局变量完全相同的麻烦;或者我们可以将它们明确化,事实证明,这是具有许多名称的最佳实践,例如存储库模式,数据存储,网关等。
PS:其他对于此比较而言很重要的方面,例如是否涉及并发,但是在这里其他答案涵盖了这一点。
评论
我喜欢您从依赖关系的角度出发。
–cbojar
16年5月27日在1:50
#10 楼
好的,让我们从历史的角度开始。我们在一个旧的应用程序中,用汇编语言和C语言的典型混合编写。没有函数,只有过程。当您要传递参数或从过程中返回值时,请使用全局变量。不用说,这很难跟踪,并且一般而言,每个过程都可以对每个全局变量执行所需的任何操作。毫不奇怪,人们在可行的情况下立即转向以不同的方式传递参数和返回值(除非对性能至关重要,否则不要这样做-例如,查看Build Engine(Duke 3D)源代码)。全局变量的恨就在这里诞生-您几乎不知道每个过程将读取和更改哪个全局状态,并且您不能真正安全地嵌套过程调用。
这是否意味着全局变仇恨已经成为过去了吗?不完全是。
首先,我不得不提一下,我已经看到了与我正在研究的项目中传递参数的方法完全相同的方法。在大约10年的项目中,要在C#中传递两个引用类型的实例。从字面上看,没有充分的理由这样做,并且很可能是出于对货物的了解,或者对C#的工作方式有完全的误解。
更大的一点是,通过添加全局变量,您正在扩展有权访问该全局变量的每段代码的范围。还记得所有这些建议,例如“使方法保持简短”吗?如果您有600个全局变量(同样是真实示例,则为:/),则所有方法范围都将被这600个全局变量隐式扩展,并且没有简单的方法来跟踪谁可以访问什么。
如果做错了(通常的方式:)),全局变量之间可能存在耦合。但是您不知道它们是如何耦合的,也没有机制可以确保全局状态始终保持一致。即使您引入关键部分来尝试使内容保持一致,您也会发现它与适当的ACID数据库相比差强人意:
没有办法回滚部分更新,除非您在“交易”之前保留旧值。不用说,到目前为止,将值作为参数传递已经是一个胜利:)
每个访问相同状态的人都必须遵循相同的同步过程。但是无法强制执行此操作-如果您忘记设置关键部分,则会很麻烦。
即使您正确同步了所有访问,也可能会有嵌套调用访问部分修改的状态。这意味着您要么陷入僵局(如果您的关键部分不重要),要么处理不一致的数据(如果它们不重要)。
是否可以解决这些问题?并不是的。您需要封装来处理此问题,或者是非常严格的纪律。做正确的事很难,而且通常不是成功开发软件的好方法:)
较小的作用域会使代码更容易推理。全局变量使最简单的代码段也包含了很大范围。
当然,这并不意味着全局范围定义是有害的。但这不应该是您追求的第一个解决方案-这是“易于实现,难以维护”的典型示例。
评论
听起来很像物理世界:很难回滚。
–user186205
16年5月26日,1:11
这是一个很好的答案,但是一开始它可以支持论文陈述(TL; DR部分)。
– jpmc26
16年5月31日在9:58
#11 楼
全局变量是一个工具,可以用于善与恶。数据库是一个工具,可以用于善与恶。
原始的张贴者注意到,差异并不是很大。
没有经验的学生经常认为bug是其他人会遇到的事情。教师使用“全局变量是邪恶的”作为惩罚不良设计的简化原因。学生通常不理解仅仅因为他们的100行程序没有错误,并不意味着可以将相同的方法用于10000行程序。
使用数据库时,您不能只是禁止全球状态,因为这就是程序的全部目的。相反,您会获得更多详细信息准则,例如ACID和范式等。
如果人们对全局变量使用ACID方法,那就不会太糟糕。
另一方面,如果您对数据库的设计不好,它们可能是噩梦。
评论
典型的学生对stackoverflow的主张:帮帮我!我的代码很完美,但是无法正常工作!
–David Hammen
16年5月25日在13:54
“ ACID全局变量方法”-请参阅Clojure中的参考。
–查尔斯·达菲(Charles Duffy)
16年5月25日在19:49
@DavidHammen,您认为专业人士的大脑与学生不同吗?
– Billal Begueradj
16年5月28日在12:40
@BillalBEGUERADJ-那是专业人士和学生之间的区别。我们知道,尽管有多年的经验,并且尽管在代码审查,测试等方面付出了最大的努力,但我们的代码并不完美。
–David Hammen
16-5-28在13:02
一些示例:jquery代码无法正常工作的代码是完美的不知道什么是错误的,我的代码是完美的,但是idont现在为什么我发现问题并且GUI完美,但是计算器代码却无法工作。清单一直在继续。
–David Hammen
16年5月28日在13:21
#12 楼
对我而言,主要的弊端是Globals无法防止并发问题。您可以添加用于处理Globals这类问题的机制,但是您会发现,解决的并发性问题越多,Globals就越会模仿数据库。次要的弊端是没有使用合同。评论
例如,C中的errno。
–David Hammen
16年5月25日在13:56
这正好解释了为什么全局变量和数据库不同的原因。可能还有其他差异,但是您的特定职位完全破坏了这一概念。如果您给出了一个快速的代码示例,那么我相信您会获得很多支持。例如MyFunc(){x = globalVar * 5; // ....其他一些处理; y = globalVar * 34; //糟糕,某些其他线程可能在执行某些其他操作期间更改了globalVar,并且x和y在其计算中对globalVar使用了不同的值,这几乎肯定不会给出令人满意的结果。
–扣篮
16年5月26日在17:28
#13 楼
其他一些答案试图解释为什么使用数据库是好的。他们错了!数据库是全局状态,因此就像单例或全局变量一样邪恶。当您可以轻松地仅使用本地Map或Array来代替数据库时,使用数据库是种错误!全局变量允许全局访问,这具有滥用的风险。全局变量也有上行空间。一般说来,全局变量是您应该避免的东西,而不是永远不要使用的东西。如果可以轻松避免它们,则应避免使用它们。但是,如果好处大于缺点,那么您当然应该使用它们!*
**同样适用于**,它们是全局状态的数据库-就像全局变量一样。如果您可以在不访问数据库的情况下完成工作,并且生成的逻辑可以满足您的所有需求,并且同样复杂,那么使用数据库会给您的项目增加更多的风险,而没有任何相应的好处。
在现实生活中,许多应用程序设计时都需要全局状态,有时甚至需要持久性全局状态-这就是为什么我们有文件,数据库等。
*这里是学生的例外。禁止学生使用全局变量是很有意义的,因此他们必须学习替代方法。
**一些答案错误地声称数据库在某种程度上比其他形式的全局状态受到更好的保护(问题是明确地关于全局状态,而不仅仅是全局变量)。那是胡扯。根据惯例,数据库方案中提供的主要保护与任何其他全局状态完全相同。大多数语言还以
const
的形式为全局状态提供了很多额外的保护,这些类根本不允许在构造函数中设置状态后更改其状态,或者可以将线程信息或程序状态考虑在内的getter和setter 。#14 楼
从某种意义上说,全局变量和数据库之间的区别类似于对象的私有成员与公共成员之间的区别(假定任何人仍然使用公共字段)。如果将整个程序视为一个对象,则全局变量是私有变量,而数据库是公共字段。这里的关键区别是假定的职责之一。
编写对象时,假定维护成员方法的任何人都将确保私有字段的行为良好。但是您已经放弃了关于公共领域状态的任何假设,并格外小心。
相同的假设在更广泛的程度上适用于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
}
}
}
#15 楼
数据库可以是全局状态,但不一定总是如此。我不同意您无法控制的假设。一种管理方法是锁定和安全性。这可以在记录,表或整个数据库中完成。另一种方法是具有某种版本字段,如果数据过时,该字段将防止更改记录。像全局变量一样,一旦数据库中的值被更改,就可以对其进行更改。是解锁的,但是有很多方法可以控制访问权限(不要将所有开发者的密码提供给允许更改数据的帐户。)。如果您具有访问受限的变量,则它不是全局变量。
#16 楼
有几个区别:可以即时修改数据库值。另一方面,除非重新部署应用程序并修改代码,否则无法更改在代码中设置的全局值。实际上,这是故意的。数据库用于可能随时间变化的值,但是全局变量仅应用于永不更改且不包含实际数据的事物。
数据库值(行,列)具有上下文和关系数据库中的映射。可以使用Jailer之类的工具轻松提取和分析此关系。另一方面,全局变量略有不同。您可以找到所有用法,但是您不可能告诉我变量与世界其他地方交互的所有方式。
全局变量更快。从数据库中获取内容需要建立数据库连接,运行对我的选择,然后必须关闭数据库连接。您可能还需要进行任何类型转换。将其与在您的代码中访问的全局对象进行比较。
这些是我现在唯一想到的,但是我敢肯定还有更多。简而言之,它们是两个不同的事物,应该用于不同的目标。
#17 楼
当然,全局变量并不总是不适当的。它们之所以存在是因为它们有合法用途。全局变量的主要问题以及避免全局变量的主要来源是,使用全局变量的代码仅附加到一个全局变量上。例如,考虑使用HTTP服务器存储服务器名称。
如果将服务器名称存储在全局名称中,则该进程无法同时运行两个不同服务器名称的逻辑。也许最初的设计从未考虑过一次运行一个以上的服务器实例,但是如果您以后决定要这样做,那么服务器名称就不能在全局范围内。
相反,如果服务器名称在数据库中,则没有问题。您可以简单地为HTTP服务器的每个实例创建该数据库的一个实例。因为服务器的每个实例都有其自己的数据库实例,所以它可以具有自己的服务器名称。
因此,对全局变量的主要反对意见是,访问该全局变量的所有代码只能有一个值,不适用于数据库条目。相同的代码可以轻松访问具有特定条目不同值的不同数据库实例。
#18 楼
我认为这是一个有趣的问题,但很难回答,因为在“全球国家”一词下有两个主要问题被混和在一起。首先是“全局耦合”的概念。证明是为全局状态提供的替代方法是依赖项注入。问题是DI并不一定消除全局状态。也就是说,注入对全局状态的依赖绝对是可能且常见的。 DI的作用是消除全局变量和常用的Singleton模式带来的耦合。除了不那么明显的设计外,消除这种耦合几乎没有什么弊端,消除耦合的好处随着对全局变量的依赖关系的数量呈指数增长。是共享状态。我不确定全局共享状态和总体共享状态之间是否存在真正明显的区别,但是成本和收益要细微得多。简而言之,有无数的软件系统需要共享状态才能有用。例如,比特币是一种非常聪明的以分散方式在全球范围内(字面意义上)共享状态的方式。在不造成巨大瓶颈的情况下正确共享可变状态是困难的,但很有用。因此,如果您真的不需要这样做,则可以通过最小化共享的可变状态来简化应用程序。因此,在这两个方面,关于数据库与全局变量的区别也是一个分歧。他们会引入耦合吗?是的,他们可以,但是在很大程度上取决于应用程序的设计方式和数据库的设计方式。对于数据库是否在没有设计细节的情况下引入全局耦合,有太多因素无法一一回答。至于是否引入状态共享,那正是数据库的重点。问题是他们是否做得好。同样,我认为如果没有很多其他信息(例如替代方案和许多其他折衷方案),那么回答这个问题就太复杂了。
#19 楼
我会稍作不同的思考:“行为”之类的“全局变量”是数据库管理员(DBA)付出的代价,因为这样做对他们的工作来说是必不可少的。全局变量的问题,例如其他几个人指出,这不是武断的。问题在于,使用它们会使程序的行为越来越难以预测,因为很难确定谁在使用变量以及以何种方式使用变量。对于现代软件来说,这是一个大问题,因为现代软件通常被要求做很多灵活的事情。在运行过程中,它可能进行数十亿甚至数万亿次复杂的状态操纵。能够证明有关该软件将在数十亿或数万亿次操作中将执行的操作的真实陈述的能力非常宝贵。
对于现代软件,我们所有的语言都提供了帮助实现此目的的工具,例如作为封装。不使用它的选择是不必要的,这导致了“全球化是邪恶的”心态。在软件开发领域的许多地区,唯一使用它们的人是不知道如何更好地编码的人。这意味着他们不仅直接遇到麻烦,而且间接表明开发人员不知道自己在做什么。在其他地区,您会发现全局变量是完全正常的(尤其是嵌入式软件喜欢全局变量,部分原因是它们与ISR配合良好)。但是,在众多软件开发人员中,他们只是少数派的声音,因此您听到的唯一声音是“全局元素是邪恶的”。
数据库开发是少数少数情况之一。完成DBA工作所需的工具非常强大,其理论也不是基于封装。为了从数据库中找出每一点性能,他们需要完全不受限制地访问一切,类似于全局变量。使用了他们庞大的1亿行(或更多!)数据库中的一个,您会明白为什么他们不让自己的数据库引擎承受任何压力。
他们为此付出了代价,亲爱的价钱。 DBA被迫对细节的关注几乎是病态的,因为它们的工具不能保护他们。他们最好的保护方式是ACID或外键。那些不是病态的人会发现自己的表完全是一团糟,完全无法使用甚至损坏。
拥有10万行软件包并不罕见。从理论上讲,软件中的任何行都可能在任何时间点影响任何全局变量。在DBA中,您永远不会找到100k个可以修改数据库的不同查询。保持必要的细节以保护自己不受保护,这将是不合理的。如果一个DBA有这么大的东西,他们将有意使用访问器封装数据库,避开“全局性”问题,然后通过“更安全”的机制尽其所能。因此,当推推推时,甚至数据库人员也避免使用全局变量。它们只会带来很大的危险,还有其他一些同样强大但又不那么危险的选择。
如果您要在其他碎玻璃上走动,或者在扫过的人行道上走动,是否愿意事情是否平等?是的,您可以在碎玻璃上行走。是的,有些人甚至以此为生。但还是让他们扫一下人行道继续前进吧!
#20 楼
我认为前提是错误的。没有理由数据库不需要是“全局状态”而不是(很大)上下文对象。如果要绑定到特定数据库,则代码是通过全局变量或固定的全局数据库连接参数使用的,它与任何其他全局状态没有什么不同,并且没有任何坏处。另一方面,如果为数据库连接正确传递上下文对象,则它只是较大的(广泛使用的)上下文状态,而不是全局状态。测量差异很容易:您可以运行您的程序逻辑的两个实例,每个实例在一个程序/进程中都使用自己的数据库,而无需对代码进行侵入式更改?如果是这样,则您的数据库不是真正的“全局状态”。
#21 楼
全球人不是邪恶的。他们只是一个工具。对全局变量的误用以及对任何其他编程功能的滥用都是有问题的。我的一般建议是,仅应将全局变量仅用于已被充分理解和考虑的情况,而其他解决方案都不是最优的。最重要的是,您要确保已充分记录了可以修改全局值的位置,并且如果您正在运行多线程,则要确保以事务性方式访问全局和任何依赖于全局的全局变量。 />
评论
一些拒绝投票者会介意解释您的拒绝投票吗?不加解释地拒绝投票似乎是不礼貌的。
–拜伦·琼斯(Byron Jones)
16年7月7日在22:16
#22 楼
只读模式,并假设您的数据在打印时不是最新的。队列写或以其他方式处理冲突。
欢迎来到地狱魔鬼,您正在使用全局db。
评论
很高兴看到资深会员对教条稍加挑战...在应用程序中,通常会提供一种访问数据库的方法,该方法会传递给要访问数据库的函数。您不必对全局变量执行此操作,只需知道它们就在眼前。那是关键的区别。
全局状态就像有一个数据库,一个表,一个表,一个行,一个无限的列并由任意数量的应用程序同时访问一样。
数据库也是邪恶的。
“反转”您在此处所做的论点并朝另一个方向发展很有趣。从逻辑上讲,具有指向另一个结构的指针的结构只是一个表的一行中的外键,它指向另一表的另一行。如何处理任何代码(包括步行链表)与处理数据库中的数据有什么不同?答:不是。问题:为什么我们要使用这种不同的工具来操纵内存中的数据结构和数据库中的数据结构?答:我真的不知道!似乎是历史的偶然,而不是好的设计。