Для тех из вас, кому посчастливилось не работать на языке с динамической областью действия, позвольте мне немного освежить в этом информацию. Представьте себе псевдо-язык, названный "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 это роскошь, которой часто нет.
И, конечно, по существу, все хорошие практики для рефакторинга в языках с лексической областью видимости также применимы в языках с динамической областью видимости - тесты на запись и так далее. Тогда возникает вопрос: как мы можем снизить риски, связанные с повышенной уязвимостью динамически изменяемого кода при рефакторинге?
(Обратите внимание, что хотя « Как вы перемещаетесь и реорганизуете код, написанный на динамическом языке? », Заголовок похож на этот вопрос, он совершенно не связан.)