【问题标题】:Are constants as evil as global variables and singletons?常量与全局变量和单例一样邪恶吗?
【发布时间】:2013-11-16 06:36:34
【问题描述】:

我在这个论坛上多次听到使用全局变量是一种死罪,而实现单例是一种犯罪。

我突然想到,旧的良好常量具有这些不受欢迎的做法的所有特征:它们是全局访问的,毫无疑问,它们引入了最全局的状态。

那么,问题是:我们不应该也向常量声明圣战,并一直使用所有现代事物,如 DI、IoC 或其他时髦的词吗?

【问题讨论】:

  • 常量有一个其他全局变量没有的特性。他们不会改变。这有很大的不同。
  • 单例也没有改变。而且有全局变量也不会改变。
  • 为什么要在值不变的情况下使用变量?
  • 但是复杂的东西不是常数……如果它们是常数,它们就不会复杂。但无论如何,问题是:我有一个(标量)固定值,为什么要为它使用变量?
  • 有趣的问题,但现在我想起来了,我认为我使用的唯一常量是 PHP_EOL。你真正使用哪个常量?

标签: php dependency-injection singleton global-variables constants


【解决方案1】:

全局变量被认为是不好的做法的主要原因是因为它们可以在系统的一个部分进行修改并在另一部分使用,而这两段代码之间没有直接联系。

这会导致潜在的错误,因为在不知道(或考虑)使用全局变量的所有位置以及可以更改它的方式的情况下编写使用全局变量的代码是可能的。或者反之亦然,编写对全局进行更改的代码,而没有意识到更改可能对代码的其他不相关部分产生的影响。

常量不共享这个问题,因为它们是……嗯,常量。一旦它们被定义,它们就不能被改变,因此上述段落中描述的问题就不会发生。

因此,它们可以在全球范围内使用。

也就是说,我见过一些写得很糟糕的 PHP 代码,它们使用define 创建常量,但在不同情况下声明常量的方式不同。这是对常量的误用:常量应该是一个绝对固定的值;它应该只是一个单一的值。如果您在程序的不同运行中可能有不同的值,则不应将其定义为常量。那种东西确实应该是一个变量,然后应该和其他变量一样遵循同样的规则。

这种误用只能发生在像 PHP 这样的脚本语言中;它不可能在编译语言中发生,因为您只能在一个地方定义一次常量并定义一个固定值。

【讨论】:

    【解决方案2】:

    全局变量和全局常量有很大区别。

    避免使用全局变量的主要原因是它可以随时被任何东西修改。它可以在调用/执行顺序上引入各种隐藏的依赖关系,并且可能导致相同的代码有时工作而不是其他代码,这取决于全局是否以及如何更改。显然,如果您正在处理并发性或并行性,那么糟糕的魔力可能会更加严重。

    全局常量在整个代码中始终(或应该)完全相同。一旦您的代码开始执行,就可以保证查看它的每一位代码每次都会看到相同的内容。这意味着没有引入意外依赖的危险。使用常量实际上可以很好地提高可靠性,因为这意味着如果您需要更改代码,则无需更新多个位置的值。 (永远不要低估人为错误!)

    单身人士完全是另一个问题。这是一种经常被滥用的设计模式,基本上可以最终成为全局变量的面向对象版本。在某些语言(例如 C++)中,如果您不注意初始化顺序,它也可能会出错。然而,它有时可能是一种有用的模式,尽管通常有更好的替代方案(尽管有时需要稍微多做些工作)。

    编辑:只是为了简要扩展,您在问题中提到全局常量引入了“有史以来最全局的状态”。这不是很准确,因为全局常量是(或应该)以与固定源代码相同的方式固定的。它定义了程序的静态性质,而“状态”通常被理解为动态运行时概念(即可以改变的东西)。

    【讨论】:

      【解决方案3】:

      一般来说是的,避免使用常量。它们引入了从消费者到全局范围的耦合。也就是说,消费者依赖外部的东西。这是不明显的,例如

      class Foo
      {
          public function doSomething()
          {
              if (ENV === ENV_DEV) {
                  // do something this way
              } else {
                  // do something that way
              }
          }
      }
      

      如果不了解doSomething 的内部结构,您将不会知道具有该常量的全局范围存在依赖关系。因此,除了使您的代码更难理解之外,您还限制了它的重用方式。

      对于只有一个值的常量也是如此,例如

      public function log($message)
      {
          fwrite(LOGFILE, $message);
      }
      

      这里的常量将指向外部某处定义为的文件资源

      define('LOGFILE', fopen('/path/to/logfile'));
      

      这与使用ENV 一样不明显。这是一种依赖,需要类之外的东西存在。为了使用该对象,我必须知道这一点。由于使用这个常量的类隐藏了这个细节,我可能会尝试在不确保常量存在的情况下记录一些东西,然后我想知道为什么它不起作用。它甚至不必是资源,LOGFILE 可以简单地将路径包含为字符串。结果一样。

      依赖消费者中的全局常量还需要您在单元测试中设置全局状态。这是您通常希望避免的事情,即使常量是固定值,因为单元测试的重点是单独测试单元,并且必须将环境置于特定状态会阻碍这一点。

      此外,使用全局常量总是会带来不同库的常量冲突的威胁。根据经验,不要将任何内容放入全局范围。如果必须使用命名空间来聚集常量。

      但是,请注意命名空间常量在耦合方面仍然存在相同的问题,类常量也是如此。只要这种耦合在同一个命名空间内,它就不太重要,但是一旦你开始耦合到来自不同命名空间的常量,你就会再次阻碍重用。为此,请考虑任何常量公共 API。

      使用常量的替代方法是使用不可变的值对象,例如:

      class Environment
      {
          private $value;
      
          public function __construct($value)
          {
              $this->assertValueIsAllowedValue($value);
              $this->value = $value;
          }
      
          public function getValue() {
      // …
      

      这样,除了确保这些值有效之外,您还可以将这些值传递给需要它们的对象。像往常一样,YMMV。这只是一种选择。单个常量不会使您的代码无法使用,但在很大程度上依赖常量会产生不利影响,因此根据经验,尽量将它们保持在最低限度。

      在相关的旁注中,您可能还对以下内容感兴趣:

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-10-10
        • 2011-10-15
        • 1970-01-01
        • 2019-04-18
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多