全局变量(Global Variables)是指程序中任意地方都能访问的变量。有时,那些作用域比局部变量(local variables)更广的变量也可比作是全局变量:例如类中可以任意访问的变量。
有经验的程序员会尽可能避免使用全局变量。为何不建议使用全局变量?基于代码大全中的“13.3全局数据”和网上的一些相关资料,这里做一些简单的整理和小结。
常见的与全局变量有关的问题
- 无意间的修改
- 命名冲突
- 奇异的别名(Aliasing)
- 并发(Concurrency)的问题
- 初始化的顺序
- 阻碍了代码的复用
- 干扰了模块化
无意间修改了全局变量
个人比较喜欢的说法是,你不知道全局变量什么时候被修改了。其产生的后果有:
- 难以理解
- 产生歧义
- 破坏了代码封装
- 难以测试
int
乍一看认为sum的值应该是6,但结果是12,因为函数inc_global_variable中修改了global_variable的值。
int
在不查看函数inc_global_variable内部代码的情况下,我们是很难理解到底发生了什么。当一个函数总是需要查看它的内部代码,这就破坏了创建函数的初衷:封装代码隐藏复杂度。
最后,因为我们不知道全局变量什么时候被修改了,这大大增加了代码测试难度。作为对比,类似sin,cos这类的三角函数,我们不需要知道内部是如何实现的,只要清楚给定输入参数就能得到对应的输出结果。而函数inc_global_variable给定输入参数后,是仍然无法确定输出结果的,除非事先确定global_variable的值。换句话说,在不了解代码内部实现的情况下,几乎无法写测试代码。
命名冲突
命名冲突这点应该比较好理解,我们在创建新的变量的时候会尽量避免与之前变量的名称冲突。一个局部函数内部变量的数目也就3~4个的时候,不大会有命名冲突的麻烦。但有了全局变量之后,情况可能就不一样了,特别是,没有限制全局变量的使用,没有规范全局变量的命名,这时候很可能需要我们把之前的代码都翻一遍,才能确保避免命名冲突。
有人会说局部变量是可以覆盖全局变量,但这只会产生更多的麻烦。首先特别容易给代码阅读者带来不必要的歧义。其次对于后来写代码的人,可能错误地把全局变量当做局部变量使用,或者反过来,把局部变量当做全局变量使用。
奇异的别名(Aliasing)
“别名(Aliasing)”指的是两个或多个名称指向同一个变量。
#include
第一次显示的结果是:
2
global variable is:
但第二次却是:
input variable is: 7
global variable is: 7
这是因为,第二次的时候,指针input_variable指向的正是变量global_variable。
并发(Concurrency)的问题
在多线程的情况下,在修改同一个全局变量之前,需要先将其锁定(locking)。如何锁定,不同的编程语言语法各有不同,有的简单有的复杂。数据是何时什么情况下被修改的变得更加复杂,确保全局变量前后意义的一致性,显得特别有挑战。
初始化的顺序
一个文件的全局变量的初始化需要依赖另一个文件中的全局变量的初始化。无法确保初始化的顺序,就无法确保后续的全局变量的值。
阻碍了代码的复用
大家应该有类似的经历,其他程序中的一段代码正好用得着,ctrl-c + ctrl-v之后,当前程序的代码就能正常运行了,此时我们就可以愉快的偷个懒了。
但是如果那段代码中用了全局变量,那就比较头痛了。有些时候,这需要把代码重新研究一遍,和再写一遍无异。
干扰了模块化
为何要模块化?这是我们处理复杂的大型程序最有效的方法,把它拆解成多个相对简单小型且互不干扰的模块。互不干扰是保证我们在同一时间只处理其中一个模块。
全局变量干扰了模块化,我们不但要关注当前的模块,还要关注所有使用了相同全局变量的其他模块。
什么情况下应该使用全局变量?
完全不使用全局变量,这并不怎么现实。如果将全局变量推广至全局数据,类似数据库、配置文件及命名常量(Named constants),这些在大多程序应用中都能见到。
书中列出的适合使用全局变量的情况如下:
- 保存全局数值(Preservation of global values)
- 模拟命名常量(Emulation of named constants)
- 模拟枚举类型(Emulation of enumerated types)
- 简化极其常用数据的使用(Streamlining use of extremely common data)
- 消除流浪数据(Eliminating tramp data)
如何正确地使用全局变量
- 若非万不得已不使用全局变量
- 制定明确的全局变量规范
- 为全局变量制作一个清单
- 不要用全局变量存放中间结果
- 不要为了避免使用全局变量,把所有的数据放在一个大对象中到处传递
- 用访问器(Access routines)代替全局变量
若非万不得已不使用全局变量
先使用局部变量(local variables),然后是类变量(class variables),最后再尝试使用全局变量(global variable)。
制定明确的全局变量规范
比方说,所有全局变量名以“g_”打头,g_const_表示全局命名常量(named constants),g_enum_全局枚举类型(enumerated types)变量。当然具体情况具体分析,但一定要达成共识。
为全局变量制作一个清单
简单的说,就是在文档里把所有的全局变量在一个地方列清楚。
不要用全局变量存放中间结果
这一点比较好理解,说不准,一个不小心就把中间变量当做全局变量去使用了。
不要为了避免使用全局变量,把所有的数据发在一个大对象中到处传递
不建议使用全局变量,不是说不要使用全局变量,过犹不及。
用访问器(Access routines)代替全局变量
使用访问器(Access routines)统一管理全局变量,是代码大全极力推荐的方式。
简单的说就是将数据(data)隐藏到类(class)中。使用类似关键词static的方式确保数据唯一,然后通过routines「1」统一查看和修改数据。
注解
- routine到底是应该翻译成子程序、函数或是方法一直搞不清楚,好像都没啥差别。