Для тих із вас, хто має щастя не працювати мовою з динамічним розмахом, дозвольте мені трохи оновити, як це працює. Уявіть псевдомову, яку називають "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-коди, таких розкошів, яких часто немає.
І, звичайно, по суті всі добрі практики рефакторингу в мовах з лексичною сферою застосовуються також у мовах з динамічною сферою - пишіть тести тощо. Питання, таким чином, таке: як ми пом’якшуємо ризики, специфічно пов’язані з підвищеною крихкістю динамічно-охопленого коду під час рефакторингу?
(Зауважте, що в той час, як код навігації та рефактора написаний динамічною мовою? Має подібний заголовок до цього питання, він повністю не пов'язаний.)