我在这个论坛上多次听到使用全局变量是一种死罪,而实现单例是一种犯罪。
我突然想到,旧的良好常量具有这些不光彩的做法的所有特征:它们是全球可访问的,毫无疑问它们引入了有史以来最全球化的状态。
所以,问题是:我们不应该也向常量宣布圣战,并一直使用所有现代事物,如 DI、IoC 或其他时髦的词吗?
我在这个论坛上多次听到使用全局变量是一种死罪,而实现单例是一种犯罪。
我突然想到,旧的良好常量具有这些不光彩的做法的所有特征:它们是全球可访问的,毫无疑问它们引入了有史以来最全球化的状态。
所以,问题是:我们不应该也向常量宣布圣战,并一直使用所有现代事物,如 DI、IoC 或其他时髦的词吗?
一般来说是的,避免使用常量。它们引入了从消费者到全局范围的耦合。也就是说,消费者依赖外部的东西。这是不明显的,例如
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。这只是一种选择。单个常量不会使您的代码无法使用,但在很大程度上依赖常量会产生不利影响,因此根据经验,尽量将它们保持在最低限度。
在相关的旁注中,您可能还对以下内容感兴趣:
全局变量被认为是不好的做法的主要原因是因为它们可以在系统的一部分中修改并在另一部分中使用,而这两段代码之间没有直接联系。
这会导致潜在的错误,因为可以编写使用全局变量的代码而不知道(或考虑)它的所有使用位置以及可以更改它的方式。反之亦然,编写对全局进行更改的代码,而没有意识到更改可能对代码的其他不相关部分产生的影响。
常量不存在这个问题,因为它们是……嗯,常量。一旦它们被定义,它们就不能被改变,因此上述段落中描述的发布不会发生。
因此,它们可以在全球范围内使用。
也就是说,我看到一些编写不佳的 PHP 代码用于define
创建常量,但在不同的情况下以不同的方式声明常量。这是对常量的误用:常量应该是一个绝对固定的值;它应该只是一个单一的值。如果您在程序的不同运行中可能有不同的值,则不应将其定义为常量。那种东西确实应该是一个变量,然后应该遵循和其他变量一样的规则。
这种误用只能发生在像 PHP 这样的脚本语言中;它不可能在编译语言中发生,因为您只能在一个地方定义一个常量并定义一个固定值。
全局变量和全局常量之间有很大的区别。
避免使用全局变量的主要原因是它可以随时被任何东西修改。它可以在调用/执行顺序上引入各种隐藏的依赖关系,并且可以导致相同的代码有时工作而不是其他代码,这取决于全局是否以及如何更改。显然,如果您正在处理并发性或并行性,那么糟糕的魔力可能会更加严重。
全局常量在整个代码中始终(或应该)完全相同。一旦您的代码开始执行,就可以保证查看它的每一位代码每次都会看到相同的内容。这意味着没有引入意外依赖的危险。使用常量实际上可以很好地提高可靠性,因为这意味着如果您需要更改代码,则无需更新多个位置的值。(永远不要低估人为错误!)
单身人士是另一个问题。这是一种经常被滥用的设计模式,基本上可以最终成为全局变量的面向对象版本。在某些语言(例如 C++)中,如果您不注意初始化顺序,它也可能会出错。然而,有时它可能是一种有用的模式,尽管通常有更好的替代方案(尽管有时需要稍微多做些工作)。
编辑:为了简要扩展,您在问题中提到全局常量引入了“有史以来最全局的状态”。这不是很准确,因为全局常量是(或应该)以与固定源代码相同的方式固定的。它定义了程序的静态性质,而“状态”通常被理解为动态运行时概念(即可以改变的东西)。