Для тех из вас, кому посчастливилось не работать на языке с динамической областью действия, позвольте мне немного освежить в этом информацию. Представьте себе псевдо-язык, названный "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)
в bar()
не будет печатать ничего (в MUMPS, все имена указывают на пустую строку , если явно не установлено другое значение ). Но нет ничего, что могло бы предотвратить утечку переменных от вызывающей стороны к вызываемой стороне: если бы мы function p() { NEW x = 3; q(); print(x); }
, насколько нам известно, q()
могли бы мутировать x
, несмотря на то, что не принимали явно x
в качестве параметра. Это все еще плохая ситуация, но она не так плоха, как раньше.
Помня об этих опасностях, как мы можем безопасно реорганизовать код в MUMPS или любом другом языке с динамической областью видимости?
Существуют некоторые очевидные полезные практики для упрощения рефакторинга, например, никогда не используйте переменные в функции, отличной от тех, которые вы инициализируете ( NEW
) самостоятельно или передаете в качестве явного параметра, и явно документируйте любые параметры, которые неявно передаются из вызывающих функций. Но в десятилетней базе кодов ~ 10 8- LOC это роскошь, которой часто нет.
И, конечно, по существу, все хорошие практики для рефакторинга в языках с лексической областью видимости также применимы в языках с динамической областью видимости - тесты на запись и так далее. Тогда возникает вопрос: как мы можем снизить риски, связанные с повышенной уязвимостью динамически изменяемого кода при рефакторинге?
(Обратите внимание, что хотя « Как вы перемещаетесь и реорганизуете код, написанный на динамическом языке? », Заголовок похож на этот вопрос, он совершенно не связан.)