您如何安全地重构具有动态范围的语言?


13

对于那些有幸不使用动态范围内的语言的人,让我稍微介绍一下它的工作原理。想象一下一种伪语言,称为“ 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()也可能发生突变。这仍然是一个糟糕的情况,但并不像以前那样糟糕。xx

考虑到这些危险,我们如何通过动态作用域安全地重构MUMPS或任何其他语言的代码?

要做出重构变得容易,从未像现在这样使用比初始化(其它函数变量一些明显的良好做法NEW)自己或作为一个明确的参数传递,并明确记载的任何参数隐式地从函数的调用者传递。但是在几十年前的〜10 8 -LOC代码库中,这些都是人们通常没有的奢侈品。

并且,当然,基本上所有在词汇范围内的语言进行重构的良好实践也都适用于动态范围内的语言-编写测试等。那么问题是:重构时,我们如何减轻与动态范围代码的脆弱性相关的风险?

(请注意,尽管您如何导航和重构以动态语言编写的代码?该问题的标题与此类似,但这是完全无关的。)



@gnat我看不到该问题/答案与该问题的相关性。
senshin 2015年

1
@gnat您是在说答案是“使用不同的流程和其他重量级的东西”吗?我的意思是,这可能没错,但这过于笼统,以至于没有特别用处。
senshin 2015年

2
老实说,除了“切换到变量实际上具有作用域规则的语言”或“使用匈牙利表示法的混蛋继子”(每个变量都以文件名和/或方法名作为前缀)之外,我没有其他答案。而不是类型或种类”。您描述的问题是如此可怕,我无法想象有一个好的解决方案。
Ixrec

4
至少您不能指责MUMPS以一种令人讨厌的疾病命名而虚假广告。
Carson63000 2015年

Answers:


4

哇。

我不知道MUMPS是一种语言,所以不知道我的评论在这里是否适用。一般来说-您必须从内而外重构。必须使用参数将那些全局状态(全局变量)的使用者(读取器)重构为方法/函数/过程。重构后,方法c应该如下所示:

function c(c_scope_x) {
   print c(c_scope_x);
}

c的所有用法都必须重写(这是一项机械任务)

c(x)

这是通过使用局部状态将“内部”代码与全局状态隔离。完成后,您将不得不将b重写为:

function b() {
   x="oops"
   print c(x);
}

x =“ oops”分配在那里保留了副作用。现在我们必须考虑b污染全局状态。如果只有一个受污染的元素,请考虑以下重构:

function b() {
   x="oops"
   print c(x);
   return x;
}

最后用x = b()重写b的每次使用。进行此重构时,函数b必须仅使用已经清理的方法(您可能希望ro重命名以使其清楚)。之后,您应该重构b而不污染全局环境。

function b() {
   newvardefinition b_scoped_x="oops"
   print c_cleaned(b_scoped_x);
   return b_scoped_x;
}

将b重命名为b_cleaned。我想您将不得不对此稍作尝试以适应这种重构。当然,并不是每种方法都可以重构,但是您必须从内部开始。尝试使用Eclipse和Java(提取方法)和“全局状态” aka类成员来获得想法。

function x() {
  fifth_to_refactor();
  {
    forth_to_refactor()
    ....
    {
      second_to_refactor();
    }
    ...
    third_to_refactor();
  }
  first_to_refactor()
}

hth。

问题:考虑到这些危险,我们如何安全地以动态范围将MUMPS或任何其他语言的代码重构?

  • 也许其他人可以给出提示。

问题:我们如何减轻重构时动态范围代码的脆弱性带来的风险?

  • 编写一个程序,为您执行安全重构。
  • 编写一个程序,以识别安全的候选人/第一个候选人。

嗯,尝试自动化重构过程存在一个MUMPS特定的障碍:MUMPS没有一流的功能,也没有功能指针或任何类似的概念。这意味着任何大流行性腮腺炎代码库必然会有大量的eval用途(腮腺炎,叫EXECUTE),有时甚至在消毒用户输入-这意味着它几乎是不可能的静态查找和重写功能的所有用途。
senshin 2015年

好吧,认为我的答案不足够。我认为refactoring @ google规模的youtube视频是一种非常独特的方法。他们使用clang解析AST,然后使用自己的搜索引擎查找任何(甚至隐藏的用法)重构其代码。这可能是找到每种用法的一种方式。我的意思是针对腮腺炎代码的解析和搜索方法。
thepacker 2015年

2

我想您最好的选择是将完整的代码库置于您的控制之下,并确保您对模块及其依赖性有一个总体了解。

因此,至少您有机会进行全局搜索,并且有机会为您希望代码更改带来影响的系统部分添加回归测试。

如果您看不到完成第一件事的机会,那么我的最佳建议是: 不要重构任何被其他模块复用的模块,或者您不知道其他模块是否依赖于这些模块。在任何大小合适的代码库中,您找到其他模块都不依赖的模块的可能性很高。因此,如果您有一个依赖于B的mod A,反之亦然,而没有其他模块依赖于A,即使使用动态范围语言,您也可以对A进行更改而不会破坏B或任何其他模块。

这使您有机会用A对B2的依赖关系来替换A对B的依赖关系,其中B2是经过消毒的重写版本的B。B2应该是新编写的,并牢记您上面提到的规则以编写代码更具发展性和重构性。


这是一个很好的建议,尽管我要补充一点,这是MUMPS固有的困难,因为没有访问说明符或任何其他封装机制的概念,这意味着我们在代码库中指定的API实际上只是对用户的建议。有关调用哪些函数的代码。(当然,这个特殊的困难与动态范围无关;我只是将其记为兴趣点。)
senshin 2015年

阅读本文之后,我确定我不会羡慕您的任务。
布朗

0

显而易见:在这里如何进行重构? 进行非常仔细。

(正如您所描述的那样,开发和维护现有代码库应该足够困难,更不用说试图对其进行重构了。)

我相信我会在这里追溯应用测试驱动的方法。这将涉及编写一套测试,以确保当前的功能在您开始重构时仍能正常工作,首先只是为了简化测试。(是的,除非您的代码已经足够模块化,可以进行测试而不进行任何更改,否则我希望在这里遇到鸡肉和鸡蛋问题。)

然后,您可以继续进行其他重构,检查是否没有破坏任何测试。

最后,您可以开始编写需要新功能的测试,然后编写代码以使这些测试正常工作。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.