[博客翻译]全局变量不是问题


原文地址:https://codestyleandtaste.com/globals-are-not-the-problem.html


代码风格与品味
这是一个分享我对代码和实践的思考的博客
在这篇文章中,我将展示一个例子,说明避免使用全局变量如何导致了一个错误,我将定义什么是全局变量,解释问题所在,然后给出我成功使用它们的例子。

全局变量不是问题

我们都被告知全局变量是不好的。它们可以在任何地方被修改,有时会强制函数以特定顺序调用,如果程序足够大或状态足够随机,可能无法调试。我们通常在编程的第一年就被教导不要使用它们,但许多人从未弄清楚什么时候应该使用。
首先,我们来看没有全局变量的代码。在这里,我们想看看在抛出异常(未显示)之前,“simple”函数被调用了多少次,这样我们就可以在函数的顶部设置一个断点。我们的计数器逻辑中有一个错误。如果问题不明显,你可能会感到沮丧。

let counter = { count:0 }
let obj = { counter:counter };
function simple(obj) { 
	console.log(++obj.counter.count) 
	if (obj.counter.count == 123) {
		//让我们在异常之前设置一个断点
	}
	/* 剩下的函数逻辑有错误 */
}
function complex(obj) {
	let temp = structuredClone(obj)
	simple(temp)
	simple(temp)		
}
simple(obj)
simple(obj)
complex(obj)
simple(obj)

如果你运行这段代码,你会看到1 2 3 4 3被打印出来,而不是5。在我告诉你问题之前,让我们看看使用全局变量的版本,它运行正确。

let count = 0 //全局
let obj = { };
function simple(obj) { 
	console.log(++count) 
	if (count == 123) {
		//让我们在异常之前设置一个断点
	}
	/* 剩下的函数逻辑有错误 */
}
function complex(obj) {
	let temp = structuredClone(obj)
	simple(temp)
	simple(temp)		
}
simple(obj)
simple(obj)
complex(obj)
simple(obj)

问题是structuredClone对我们的对象进行了深拷贝。当complex函数执行simple函数时,错误的计数器被修改了,导致我们看到重复的数字,并让我们不确定正确的断点时间。
现在我们可以看到避免全局变量仍然可能有问题,让我们定义什么是全局变量以及什么算是使用全局变量。

定义全局变量

我对全局变量的定义是任何不作为参数传递或在函数内定义的变量,以下是一些不同语言中的变量类型。

  • 全局:这些是在类和函数外部定义的,并且对其他文件可见。我很少使用这些。
  • 私有/静态:这些是全局变量,但在文件外部不可见。我主要使用这种。
  • 线程局部:这些是全局变量(可能是静态的),在每个线程基础上有一个唯一的实例。当我处理线程时,我会避免所有类型的全局变量,而使用线程局部变量。
  • 静态成员:一个你只有一个的变量。在某些语言中,你可以使用它来拥有一个只读的“空”、“最小”、“最大”和其他变量。我通常尽可能避免这种情况。
  • 静态函数变量:在C语言中,在函数内部,你可以声明一个静态变量。你可以认为这是一个局部变量,但我不这么认为,因为它在堆上,并且可以在函数外部返回和修改。我绝对避免这种情况,除非作为一个我从不返回其地址的计数器。

你会认为以下情况是使用全局变量吗?

  • 调用一个内部使用全局变量的函数。例如,在我们上面的代码中,我们可以调用inc()来递增计数器并返回值,而不是++count。
  • 调用一个具有我们不容易看到的副作用的函数。例如,打