对于那些有幸不使用动态范围内的语言的人,让我稍微介绍一下它的工作原理。想象一下一种伪语言,称为“ RUBELLA”,其行为如下:
function foo() {
print(x); // not defined locally => uses whatever value `x` has in the calling context
y = "tetanus";
}
function bar() {
x = "measles";
foo();
print(y); // not defined locally, but set by the call to `foo()`
}
bar(); // prints "measles" followed by "tetanus"
也就是说,变量可以自由地在调用堆栈上下传播-定义在其中的所有变量foo
对其调用者都是可见的(并且可以被其调用者更改)bar
,反之亦然。这对代码重构具有严重的影响。假设您有以下代码:
function a() { // defined in file A
x = "qux";
b();
}
function b() { // defined in file B
c();
}
function c() { // defined in file C
print(x);
}
现在,呼叫a()
将打印qux
。但是,有一天,您决定需要进行一些更改b
。您不知道所有调用上下文(其中某些实际上可能在代码库之外),但这应该没问题-您的更改将完全在内部进行b
,对吗?因此,您可以这样重写它:
function b() {
x = "oops";
c();
}
您可能会认为您没有做任何更改,因为您刚刚定义了局部变量。但是,实际上,您已经破产了a
!现在,a
打印oops
而不是qux
。
将其带出伪语言领域,这就是MUMPS的行为方式,尽管语法不同。
MUMPS的现代(“现代”)版本包括所谓的NEW
语句,它使您可以防止变量从被调用方泄漏给调用方。因此,在上面的第一个示例中,如果我们在中完成NEW y = "tetanus"
了操作foo()
,则print(y)
in bar()
将不打印任何内容(在MUMPS中,除非明确设置为其他名称,否则所有名称都指向空字符串)。但是,没有什么可以阻止变量从调用者泄漏到被调用者:就function p() { NEW x = 3; q(); print(x); }
我们所知,即使没有显式接收作为参数,如果我们拥有,它q()
也可能发生突变。这仍然是一个糟糕的情况,但并不像以前那样糟糕。x
x
考虑到这些危险,我们如何通过动态作用域安全地重构MUMPS或任何其他语言的代码?
要做出重构变得容易,从未像现在这样使用比初始化(其它函数变量一些明显的良好做法NEW
)自己或作为一个明确的参数传递,并明确记载的任何参数都隐式地从函数的调用者传递。但是在几十年前的〜10 8 -LOC代码库中,这些都是人们通常没有的奢侈品。
并且,当然,基本上所有在词汇范围内的语言进行重构的良好实践也都适用于动态范围内的语言-编写测试等。那么问题是:重构时,我们如何减轻与动态范围代码的脆弱性相关的风险?
(请注意,尽管您如何导航和重构以动态语言编写的代码?该问题的标题与此类似,但这是完全无关的。)