为什么全球国家如此邪恶?


328

在我们开始之前,让我说我非常了解抽象和依赖注入的概念。我不需要在这里睁开眼睛。

好吧,我们大多数人在没有真正理解的情况下说了太多次了,“不要使用全局变量”,或者“ Singletons之所以邪恶是因为它们是全局的”。但真正如此不好的不祥的全局状态?

假设我需要为应用程序进行全局配置,例如系统文件夹路径或应用程序范围的数据库凭据。

在那种情况下,除了在某种全局空间中提供这些设置之外,我看不到任何好的解决方案,这对于整个应用程序都是通用的。

我知道这是不好的滥用,但是全球空间真的THAT邪?如果是的话,那里有什么好的替代方案?


5
您可以使用配置类来处理对这些设置的访问。我认为最好的做法是只有一个地方可以从配置文件和/或数据库中读取配置设置,而其他对象则可以从该配置文件中获取它们。它使您的设计更加干净和可预测。
哈霍2012年

25
使用全局变量有何区别?您的“一个唯一的地方”听起来很像一个Singleton。
马杜拉·乌奇哈

130
唯一真正的邪恶是教条。
Pieter B

34
与其他程序员一起使用全局状态就像与您的朋友使用同一把牙刷一样,您可以,但是您永远不知道有人会决定何时将其推翻。
ziG

5
魔鬼是邪恶的。全球国家既不是邪恶也不是善良。根据您的使用方式,它可能非常有用或有害。不要听别人的话,而只是学习如何正确编程。
annoying_squid

Answers:


282

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

详细来说,假设您有两个都使用相同全局变量的对象。假设您没有在任何一个模块中的任何地方使用随机源,那么在执行方法之前,如果已知系统的状态,则可以预测(并因此测试)特定方法的输出。

但是,如果其中一个对象中的方法触发副作用,从而改变了共享全局状态的值,那么当您在另一个对象中执行方法时,您将不再知道起始状态是什么。现在,您不再能够预测执行该方法时将获得的输出,因此无法对其进行测试。

在学术层面上,这听起来可能并不那么严重,但是能够对代码进行单元测试是证明其正确性(或至少适合目的)的重要一步。

在现实世界中,这可能会产生一些非常严重的后果。假设您有一个填充全局数据结构的类,另一个有使用该数据结构中的数据,更改其状态或在此过程中销毁数据的类。如果处理器类在填充器类完成之前执行了一个方法,则结果是处理器类可能有不完整的数据要处理,并且填充器类正在处理的数据结构可能会损坏或破坏。在这种情况下,程序行为将变得完全不可预测,并可能导致史诗性的损失。

此外,全局状态会损害代码的可读性。如果您的代码具有未明确引入到代码中的外部依赖关系,那么无论谁负责维护您的代码,都必须去寻找它,以找出其来源。

至于存在哪些替代方案,完全没有全局状态是不可能的,但是在实践中,通常可以将全局状态限制为包装所有其他状态的单个对象,并且决不能依靠作用域规则来引用您使用的语言。如果特定对象需要特定状态,则应通过将其作为参数传递给其构造函数或使用setter方法来显式地请求它。这称为依赖注入。

由于您正在使用的任何语言的范围规则,传递一种您已经可以访问的状态似乎很愚蠢,但是好处是巨大的。现在,如果有人孤立地查看代码,则很清楚它需要什么状态以及它来自何处。在代码模块的灵活性方面,它也具有巨大的优势,因此有机会在不同的上下文中重用它。如果传递了状态,并且对状态的更改是代码块本地的,则可以传递您喜欢的任何状态(如果它是正确的数据类型),然后让您的代码对其进行处理。用这种风格编写的代码倾向于具有松散关联的组件的集合的外观,这些组件很容易互换。模块的代码不必关心状态从何而来,而只关心如何处理它。

还有许多其他原因可以说明,通过州的方式要比依赖全球状态的方式优越得多。这个答案绝不是全面的。您可能会写一本关于全球状态为何不好的整本书。


61
基本上,因为任何人都可以更改状态,所以您不能依赖它。
奥德

21
@Truth替代?依赖注入
rdlowrey,2012年

12
您的主要论点实际上并不适用于有效的只读对象,例如表示配置的对象。
Frank

53
这些都没有解释为什么只读全局变量不好……这是OP明确询问的。这是一个很好的答案,当OP明确提出不同观点时,OP将该操作标记为“接受”,我感到惊讶。
Konrad Rudolph

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
我认为,如果上下文是可变的,则通过函数参数或依赖项注入传递上下文对象会引起问题,这与使用可变全局状态是同样的问题。
Alfredo Osorio

1
所有的好东西,阿们!但是问题是关于不变的全球状态
MarkJ

@Alfredo-非常正确。尽管它并不那么糟糕,因为至少可以对范围进行控制。但是总的来说,通过使上下文和依赖项不可变来解决问题更容易。
mikera

2
+1表示可变/不可变。不变的全局变量是可以的。即使是那些延迟加载但永不更改的变量。当然,不要公开全局变量,而要公开全局接口或API。
2014年

1
@giorgio这个问题清楚地表明,所涉及的变量在启动时会获取其值,并且在程序执行期间(系统文件夹,数据库凭据)在此之后永远不会更改。即不可变,一旦赋予其值就不会改变。就个人而言,我也使用“状态”一词,因为它在一个执行中可能与另一个执行不同,或者在另一台机器上可能不同。可能会有更好的话。
MarkJ 2014年

62
  1. 由于您整个该死的应用程序都可以使用它,因此很难再次将它们淘汰出局。如果您更改了与全局相关的任何内容,则所有代码都需要更改。这是一个令人头疼的麻烦-远远不仅仅能够grep为类型名称查找使用它的函数。
  2. 它们之所以不好,是因为它们引入了隐藏的依赖关系,从而破坏了多线程,这对于越来越多的应用程序而言越来越重要。
  3. 全局变量的状态始终是完全不可靠的,因为所有代码都可能对其执行任何操作。
  4. 他们真的很难测试。
  5. 它们使调用API变得很困难。“您必须记得在调用API之前先调用SET_MAGIC_VARIABLE()”只是乞求有人忘记调用它。它使使用API​​容易出错,从而导致难以发现的错误。通过将其用作常规参数,可以强制调用方正确提供一个值。

只需将引用传递给需要它的函数即可。没那么难。


3
好吧,您可以拥有一个封装锁定的全局配置类,并且IS旨在在任何可能的时间更改状态。我会选择这种方法,而不是从代码中的1000x位置实例化配置读取器。但是,是的,不可预测性对他们来说绝对是最糟糕的事情。
编码器2012年

6
@Coder:请注意,对全局变量的明智选择不是“从代码中1000x位置读取配置读取器”,而是一个配置读取器,该读取器创建了一个配置对象,该方法可以将其接受为参数(->依赖注入)。
sleske,2012年

2
细说:为什么grep for a type比全局更容易?问题是关于只读的全局变量,因此第2点和第3点
无关紧要

2
@MarkJ:我希望没有人遮住这个名字。
DeadMG

2
@ DeadMG,Re“ ..总是完全不可靠..”,对于依赖项注入也是如此。仅仅因为您将其设置为参数并不意味着obj保证具有固定状态。在该函数的某个位置,您可能调用了另一个函数,该函数在顶部修改了注入变量的状态。
Pacerier's

34

如果您说“状态”,通常指“可变状态”。全局可变状态是完全邪恶的,因为这意味着程序的任何部分都可以影响其他任何部分(通过更改全局状态)。

想象一下调试一个未知程序:发现函数A对于某些输入参数表现出某种方式,但是有时对于相同参数它的工作方式却不同。您发现它使用了全局变量x

您查找修改x的位置,发现有五个修改它的位置。现在祝您好运,在什么情况下函数A可以完成...


如此不变的全球国家不是那么邪恶吗?
FrustratedWithFormsDesigner 2012年

50
我要说的是,不变的全球状态是众所周知的称为“常量”的良好实践。
Telastyn 2012年

6
不可变的全球状态不是邪恶的,它只是很糟糕:-)。它仍然存在问题,因为它引入了耦合(使更改,重用和单元测试更加困难),但是产生的问题要少得多,因此在简单情况下通常是可以接受的。
sleske,2012年

2
IFF确实会使用全局变量,那么只有一段代码可以对其进行修改。其余的可以免费阅读。其他人更改它的问题不会随封装和访问功能而消失。这不是这些构造的目的。
phkahler

1
@Pacerier:是的,无论将其用作全局变量还是局部变量,更改广泛使用的接口都是困难的。但是,这与我的观点无关,那就是使用全局变量很难理解不同代码之间的交互
sleske

9

您回答了自己的问题。当“滥用”它们时,它们很难管理,但是如果知道如何包含它们,则在适当使用时它们可能是有用的且[可]可预测。维护和对全局变量的更改通常是一场噩梦,随着应用程序大小的增加,情况变得更糟。

有经验的程序员谁可以告诉全局是唯一的选择,并把它们作为简单的解决方法之间的差异,可以使用它们有最小的问题。但是,使用它们可能会导致无尽的问题,因此建议不要使用它们。

编辑:为了澄清我的意思,全局变量本质上是不可预测的。与任何不可预测的事物一样,您可以采取措施来控制不可预测性,但是可以做的事情总是存在局限性。除此之外,新开发人员加入该项目的麻烦是必须处理相对未知的变量,因此反对使用全局变量的建议应该是可以理解的。


9

Singletons有很多问题-这是我脑海中最大的两个问题。

  • 这使单元测试成为问题。从一个测试到下一个测试,全局状态可能会被污染

  • 它强制执行“唯一的一个”硬规则,即使它不可能更改,也突然发生。然后需要更改使用全局可访问对象的一整套实用程序代码。

话虽如此,大多数系统对大型全局对象都有一些需求。这些项目既庞大又昂贵(例如,数据库连接管理器),或者包含普遍的状态信息(例如,锁定信息)。

Singleton的替代方法是在启动时创建这些Big Global Objects,并将它们作为参数传递给所有需要访问此对象的类或方法。

这里的问题是,最终您会遇到一场“包裹通过”大游戏。您有一个组件及其依赖关系图,有些类创建了其他类,每个类都必须持有一堆依赖组件,因为它们产生的组件(或产生的组件的组件)需要它们。

您遇到新的维护问题。一个示例:突然,图形中的“ WidgetFactory”组件需要一个您要模拟的计时器对象。但是,“ WidgetFactory”是由“ WidgetCreationManager”的一部分“ WidgetBuilder”创建的,即使只有一个实际使用它,您也需要具有三个了解此计时器对象的类。您发现自己想要放弃并返回到Singletons,只是使此计时器对象可全局访问。

幸运的是,这正是依赖注入框架所解决的问题。您可以简单地告诉框架它需要创建哪些类,它使用反射为您找出依赖关系图,并在需要时自动构造每个对象。

因此,总而言之,单例很糟糕,替代方法是使用依赖注入框架。

我碰巧使用了温莎城堡,但您却无奈选择。请参阅2008年的本页,以获取可用框架的列表。


8

首先,要使依赖项注入是“有状态的”,您将需要使用单例,因此人们说这在某种程度上是一种错误。人们一直在使用全局上下文对象...例如,即使会话状态在本质上也是一个全局变量。不管是否通过依赖项注入传递所有信息,都不总是最好的解决方案。我目前在一个非常大的应用程序上工作,该应用程序使用许多全局上下文对象(通过IoC容器注入的单个子集),并且调试从来就不是问题。特别是对于事件驱动的体系结构,与使用全局上下文对象而不是随便传递任何更改相比,它可能是首选。取决于您问谁。

任何东西都可能被滥用,并且还取决于应用程序的类型。例如,在Web应用程序中使用静态变量与在桌面应用程序中完全不同。如果可以避免使用全局变量,则可以这样做,但是有时它们有其用途。至少要确保您的全局数据在明确的上下文对象中。就调试而言,没有任何调用堆栈和某些断点无法解决。

我想强调,盲目使用全局变量是一个坏主意。函数应该是可重用的,并且不在乎数据来自何处-引用全局变量会将函数与特定的数据输入耦合在一起。这就是为什么应该传递它,为什么依赖项注入会有所帮助的原因,尽管您仍在处理集中化的上下文存储(通过单例)。

顺便说一句,有人认为依赖注入是不好的,包括Linq的创建者,但这并不能阻止人们使用它,包括我自己。最终,经验将是您最好的老师。有的时候要遵守规则,有的时候要打破规则。


4

由于此处的其他一些答案可以区分可变状态和不可变全局状态,因此我想指出,即使不可变全局变量/设置也常常令人烦恼

考虑问题的:

...假设我需要为我的应用程序进行全局配置,例如系统文件夹路径或应用程序范围的数据库凭据。...

是的,对于一个小程序来说,这可能不是问题,但是一旦在稍大的系统中使用组件执行此操作,编写自动测试就会突然变得困难,因为所有测试(〜在同一个进程中运行)都必须可以使用相同的全局配置值。

如果显式传递了所有配置数据,则组件将变得更易于测试,并且您无需担心如何为多个测试引导全局配置值,甚至可以并行进行。


3

好吧,对于一个,您可以遇到与单例完全相同的问题。今天看起来像“我只需要其中的一项全局”的东西,突然变成了您将来需要更多的东西。

例如,今天您创建此全局配置系统是因为您想要整个系统的一个全局配置。几年后,您移植到另一个系统,有人说:“嘿,您知道,如果有一个通用的全局配置和一个平台特定的配置,这可能会更好。” 突然之间,您需要做所有这些工作来使全局结构不全局,因此可以有多个全局结构。

(这不是一个随机的例子……这是我当前所在项目中的配置系统发生的。)

考虑到制作非全局性对象的成本通常是微不足道的,这样做很愚蠢。您只是在制造未来的问题。


您的示例不是OP的意思。您显然是在多次实例化抽象配置(只是一个配置管理器数据结构,而没有任何特定于应用程序的首选项键),因为您希望将正在运行的程序实例中的所有键拆分为多个配置管理器类实例。(例如,每个这样的实例可以处理一个配置文件)。
乔·苏

3

奇怪的是,另一个问题是由于扩展不够“全局”,它们使应用程序难以扩展。全局变量的范围是过程。

如果要通过使用多个进程或在多个服务器上运行来扩展应用程序,则不能。至少要等到您排除所有全局变量并将其替换为其他某种机制后,才能进行。


3

为什么全球国家如此邪恶?

可变的全局状态是邪恶的,因为我们的大脑很难一次考虑多个参数,并且很难从时间和价值两个角度来综合考虑它们如何影响事物。

因此,在调试或测试对象的行为非常糟糕,该对象的行为在程序执行过程中有多个外部原因需要更改。更何况我们不得不推理大约数十个这些对象。


3

专为安全和健壮的系统设计而设计的语言通常会完全摆脱全局可变状态。(可以说这意味着没有全局变量,因为不可变对象在某种意义上并不是真正有状态的,因为它们从不具有状态转换。)

Joe-E是一个例子,David Wagner解释了这一决定:

当功能存储在全局变量中时,对谁有权访问对象的分析和最小特权原则都会被颠覆,因此程序的任何部分都可能可读。一旦对象在全球范围内可用,就不再可能限制分析范围:访问对象是一项特权,不能从程序中的任何代码中保留。Joe-E通过验证全局范围不包含任何功能,仅包含不可变的数据,避免了这些问题。

所以考虑的一种方法是

  1. 编程是一个分布式推理问题。大型项目的程序员需要将程序划分为多个部分,个人可以对此进行推理。
  2. 范围越小,就越容易推理。试图证明系统属性的个人和静态分析工具,以及需要测试系统属性的测试均是如此。
  3. 全局可用的重要授权来源使系统的属性难以推理。

因此,全局可变状态使得更难

  1. 设计强大的系统,
  2. 难以证明系统的性质,并且
  3. 很难确保测试在与生产环境类似的范围内进行测试。

全局可变状态类似于DLL hell。随着时间的流逝,大型系统的不同部分将需要与共享的可变状态部分稍有不同的行为。解决DLL地狱和共享的可变状态不一致问题需要在不同团队之间进行大规模协调。如果适当地确定全局状态的范围,则不会发生这些问题。


2

全球人还不错。如其他一些答案所述,它们的真正问题是,今天,明天,您的全局文件夹路径可能是几个甚至数百个之一。如果您要编写一个快速的一次性程序,请使用全局变量(如果更简单)。通常,尽管如此,即使您只认为自己需要一个倍数,也要走多路。必须重组一个突然需要与两个数据库通信的大型复杂程序,这并不令人愉快。

但是它们不会损害可靠性。如果程序意外更改,那么从程序中许多地方引用的任何数据都可能导致问题。枚举器在枚举过程中更改枚举时,会使它们感到窒息。事件队列事件可以彼此玩弄。线程总是会发疯。任何不是局部变量或不可更改字段的问题。全局变量是这类问题,但您不会通过使其不具有全局变量来解决此问题。

如果要写入文件,并且文件夹路径更改,则更改和写入需要同步。(作为一千种可能出错的事情之一,例如,您抓住路径,然后将该目录删除,然后将文件夹路径更改为一个好的目录,然后尝试写入已删除的目录。)是否存在问题文件夹路径是全局路径,或者是程序当前正在使用的千路径之一。

可以通过队列中的不同事件,不同级别的递归或不同线程访问的字段确实存在问题。简单地说(和简单化):局部变量是好的而字段是坏的。但前者的全局变量仍然会是场,所以这(但是非常重要)的问题也不能适用于全球领域的正义或邪恶的状态。

另外:多线程问题:

(请注意,事件队列或递归调用可能会遇到类似的问题,但是多线程是最糟糕的。)请考虑以下代码:

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

如果filePath是局部变量或某种常量,则您的程序在运行时不会失败,因为它filePath为null。支票始终有效。没有其他线程可以更改其值。 否则,没有任何保证。当我开始用Java编写多线程程序时,我总是在这样的行上看到NullPointerExceptions。 任何其他线程可以随时更改该值,而且它们经常这样做。正如其他几个答案所指出的那样,这给测试带来了严重的问题。上面的陈述可以工作十亿次,经过广泛而全面的测试,然后在生产中就被炸毁。用户将无法重现该问题,并且直到他们确信自己看到并忘记了该问题之后,该问题才会再次发生。

全局变量肯定存在此问题,如果您可以完全消除它们或将它们替换为常量或局部变量,那将是一件非常好的事情。如果您在Web服务器上运行无状态代码,则可能可以。通常,您的所有多线程问题都可以由数据库解决。

但是,如果您的程序必须记住从一个用户操作到下一个用户操作的所有内容,那么您将拥有可由任何正在运行的线程访问的字段。将全局字段切换到此类非全局字段将无助于可靠性。


1
您能否弄清这是什么意思?:“不是局部变量或不可更改字段的任何问题都是问题。全局变量是此类问题,但您不会通过使它们变为非全局变量来解决此问题。”
安德列斯·F

1
@AndresF .:我扩展了答案。我认为我采用的是台式机方法,该页面上的大多数人都使用数据库服务器代码。在这些情况下,“全局”可能意味着不同的含义。
拉尔夫·查平(RalphChapin)2012年

2

在任何实际应用中,状态都是不可避免的。您可以按自己喜欢的方式包装它,但是电子表格必须在单元格中包含数据。您可以使单元格对象仅具有用作接口的功能,但这并不限制可以在单元格上调用方法并更改数据的位置。您构建整个对象层次结构以尝试隐藏接口,以便代码的其他部分无法默认情况下更改数据。这不会阻止对包含对象的引用被任意传递。这些都不能单独消除并发问题。它确实使增加对数据的访问的难度增加了,但实际上并没有消除全局变量的感知问题。如果有人要修改状态,则将使用全局状态或通过复杂的API进行修改(后者只会阻止而不是阻止)。

不使用全局存储的真正原因是避免名称冲突。如果加载声明相同全局名称的多个模块,则您可能具有不确定的行为(很难通过调试,因为单元测试将通过)或链接器错误(我在想C-链接器是否对此发出警告或失败?)。

如果要重用代码,则必须能够从另一个位置获取模块,并且不要使其意外进入全局,因为它们使用了相同的名称。或者,如果您很幸运并且遇到错误,则不想更改代码的一个部分中的所有引用以防止发生冲突。


1

当很容易看到和访问所有全局状态时,程序员总是会这样做。您所获得的内容是直言不讳的,而且很难跟踪依赖关系(int blahblah意味着数组foo在任何情况下都是有效的)。从本质上讲,它几乎不可能维护程序不变式,因为所有内容都可以独立地旋转。someInt与otherInt之间有关系,很难管理,也很难证明您是否可以随时直接更改。

话虽如此,它是可以做到的(可以追溯到某些系统中唯一的方法),但是那些技能却丢失了。它们主要围绕编码和命名约定展开,这是有充分理由的。与依赖人类遵循总体规划和阅读源代码相比,您的编译器和链接器在检查类/模块的受保护/私有数据中的不变量方面做得更好。


1
“但是那些技能已经丧失了”……还不完全。我最近在一家软件公司工作,该公司被“ Clarion”(一个叫Clarion)的代码生成器工具所使用,它具有自己的类似于语言的语言,缺乏诸如将参数传递给子例程的功能。 “改变”或“现代化”,终于对我的言论感到厌烦,并把我描绘成缺乏能力和无能。我不得不离开...
路易·萨默斯

1

我不会说全局变量是好是坏,但我要添加到讨论中的事实是,如果您不使用全局状态,那么您可能会浪费大量内存,尤其是当您使用类将其依赖项存储在字段中时。

对于全球国家而言,没有这样的问题,一切都是全球性的。

例如:设想以下情形:您有一个10x10的网格,该网格由类“ Board”和“ Tile”组成。

如果要以OOP方式进行操作,则可能会将“ Board”对象传递给每个“ Tile”。现在说“ Tile”有2个“ byte”类型字段,用于存储其坐标。对于一个图块,在32位计算机上占用的总内存为(1 +1 + 4 = 6)字节:x坐标为1,y坐标为1,板子指针为4。对于10x10的图块设置,这总共提供了600个字节

现在,对于委员会位于全局范围内的情况,从每个图块访问的单个对象只需要为每个图块获取2个字节的内存,即x和y坐标字节。这只会提供200个字节。

因此,在这种情况下,如果仅使用全局状态,则将获得1/3的内存使用量。

除其他外,我想这是全局范围仍然保留在(相对)低级语言(例如C ++)中的原因


1
这在很大程度上取决于语言。在程序持续运行的语言中,确实可以通过使用全局空间和单例而不是实例化许多类来节省内存,但即使是这样的论点也是不稳定的。这都是关于优先事项的。在PHP之类的语言(每个请求运行一次,并且对象不会持久)中,即使该参数也没有意义。
Madara Uchiha 2014年

1
@MadaraUchiha不,这个论点绝不动摇。这是客观事实,除非某些VM或其他编译语言针对此类问题进行了一些硬性代码优化。否则它仍然是相关的。即使使用曾经是“一次性”的服务器端程序,在执行期间也会保留内存。对于高负载服务器,这可能是关键点。
luke1985 2014年

0

对于全局状态,有几个因素需要考虑:

  1. 程序存储器空间
  2. 不变的全局变量/一次写入全局变量。
  3. 可变全局变量
  4. 对全局变量的依赖。

您拥有的全局变量越多,引入重复项的机会就越大,从而在重复项不同步时破坏事物。将所有全局变量保存在虚假的人类记忆中既是必要的,也是痛苦的。

通常,一次不可变/写一次就可以,但是要注意初始化序列错误。

可变全局变量通常被误认为是恒定变量…

有效使用全局变量的函数具有额外的“隐藏”参数,从而使其难以重构。

全球状态不是邪恶的,但它确实要付出一定的代价-当收益大于成本时就使用它。

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.