返回介绍

缩小变量的作用域

发布于 2024-08-18 11:54:29 字数 5364 浏览 0 评论 0 收藏 0

我们都听过“避免全局变量”这条建议。这是一条好的建议,因为很难跟踪这些全局变量在哪里以及如何使用它们。并且通过“命名空间污染”(名字太多容易与局部变量冲突),代码可能会意外地改变全局变量的值,虽然本来的目的是使用局部变量,或者反过来也有同样的效果。

实际上,让所有的变量都“缩小作用域”是一个好主意,并非只是针对全局变量。

关键思想

让你的变量对尽量少的代码行可见。

很多编程语言提供了多重作用域/访问级别,包括模块、类、函数以及语句块作用域。通常越严格的访问控制越好,因为这意味着该变量对更少的代码行“可见”。

为什么要这么做?因为这样有效地减少了读者同时需要考虑的变量个数。如果你能把所有的变量作用域都减半,那么这就意味着同时需要思考的变量个数平均来讲是原来的一半。

例如,假设你有一个很大的类,其中有一个成员变量只由两个方法用到,使用方式如下:

从某种意义上来讲,类的成员变量就像是在该类的内部世界中的“小型全局变量”。尤其对大的类来讲,很难跟踪所有的成员变量以及哪个方法修改了哪个变量。这样的小型全局变量越少越好。

在本例中,最好把str_“降格”为局部变量:

另一个对类成员访问进行约束的方法是“尽量使方法变成静态的”。静态方法是让读者知道“这几行代码与那些变量无关”的好办法。

或者还有一种方式是“把大的类拆分成小一些的类”。这种方法只有在这些小一些的类事实上相互独立时才能发挥作用。如果你只是创建两个类来互相访问对方的成员,那你什么目的也没达到。

把大文件拆分成小文件,或者把大函数拆分成小函数也是同样的道理。这么做的一个重要的动机就是数据(即变量)分离。

但是不同的语言有不同的管理作用域的规则。我们接下来给出一些与变量作用域相关的更有趣规则。

C++中if语句的作用域

假设你有以下C++代码:

变量info在此函数的余下部分仍在作用域内,因此,读这段代码的人要始终记得它,猜测它是否或者怎样再次用到。

但是在本例中,info只有在if语句中才用到。在C++语言中,我们实际上可以把info定义在条件表达式中:

现在读者可以在info超出作用域后放心地忘掉它了。

在JavaScript中创建“私有”变量

假设你有一个长期存在的变量,只有一个函数会用到它:

像submitted这种全局变量会让读代码的人非常不安。看上去好像只有submit_form()使用submitted,但你就是没办法确定。实际上,另一个JavaScript文件可能也在用一个叫submitted的全局变量,却不是为了同一个目的!

你可以把submitted放在一个“闭包”中来避免这个问题:

请注意在最后一行上的圆括号——它会使外层的这个匿名函数立即执行,返回内层的函数。

如果你以前没见过这种技巧,可能一开始它看上去有些怪。它的效果是营造一个“私有”作用域,只有内层函数才能访问。现在读者不必再去猜“submitted还在什么地方用到了?”或者担心与其他同名的全局变量冲突。(这方面的更多技巧,参见《JavaScript:The Good Parts》,原作者Douglas Crockford[O'Reilly,2008])

JavaScript全局作用域

在JavaScript中,如果你在变量定义中省略var关键字(例如,写成x=1而非var x=1),这个变量会放在全局作用域中,所有的JavaScript文件和<script>块都可以访问它。下面是一个例子:

这段代码不慎把i放在了全局作用域中,那么以后的代码块也能看到它:

很多程序员没有注意到这个作用域规则,这个令人吃惊的行为可以产生奇怪的bug。这种bug的一个共同形式是,当两个函数都创建了有相同名字的局布变量时,忘记了使用var。这些函数会在背地里“交谈”,然后可怜的程序员可能会认为他的计算机疯了或者RAM坏了。

对于JavaScript通用的“最佳实践”是“总是用var关键字来定义变量”(例如:var x=1)。这个方法把变量的作用域约束在定义它的(最内层)函数之中。

在Python和JavaScript中没有嵌套的作用域

像C++和Java这样的语言有“语句块作用域”,定义在if、for、try或者类似结构中的变量被限制在这个语句块的嵌套作用域里。

但是在Python和JavaScript中,在语句块中定义的变量会“溢出”到整个函数。例如,请注意在下面这段完全正确的Python代码中对example_value的使用:

这条作用域规则让很多程序员感到意外,并且写成这样的代码也很难读。在其他语言中,可能更容易找到example_value最初是在哪里定义的——你只要沿着你所在的函数“左手边”一路找下去就可以了。

前面的例子同时也有错误:如果在代码的第一部分中没有设置example_value,那么第二部分会产生异常:"NameError:'example_value' is not defined"。我们可以改正它并让代码更可读,把example_value的定义移到它与使用点的“最近共同前辈”(就嵌套而言)处就可以了:

然而,在这个例子中其实example_value完全可以不要。example_value只保存一个中间结果,如第9章所述,这种变量可以通过“尽早完成任务”来消除。在这个例子中,这意味着在找到example_value时马上给它写日志。

下面是修改过的新代码:

把定义向下移

原来的C语言要求把所有的变量定义放在函数或语句块的顶端。这个要求很令人遗憾,因为对于有很多变量的函数,它强迫读者马上思考所有这些变量,即使是要到很久之后才会用到它们。(C99和C++去掉了这个要求。)

在下面的例子中,所有的变量都无辜地定义在函数的顶部:

这段示例代码的问题是它强迫读者同时考虑3个变量,并且在它们间不断切换。

因为读者在读到后面之前不需要知道所有变量,所以可以简单地把每个定义移到对它的使用之前:

你可能会想到底all_replies是不是个必要的变量,或者这么做是不是可以消除它:

在本例中,all_replies是一个相当好的解释,所以我们决定留下它。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文