Як ви безпечно переробляєте мовою з динамічним масштабом?


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)в bar()нічого б не надрукували (у MUMPS всі імена вказують на порожню рядок, якщо явно не встановлено щось інше). Але ніщо не може перешкоджати просоченню змінних від абонента до виклику: якщо ми function p() { NEW x = 3; q(); print(x); }, наскільки ми знаємо, q()могли б мутувати x, незважаючи на те, що явно не отримували xпараметр. Це все ще погана ситуація, але не така погана, як це, мабуть, раніше.

Маючи на увазі ці небезпеки, як можна безпечно перетворювати код на MUMPS або будь-яку іншу мову з динамічним визначенням масштабу?

Існує кілька очевидних добрих практик для полегшення рефакторингу, наприклад, ніколи не використовуйте змінних у функції, відмінній від тих, яку ви ініціалізуєте ( NEW) самі або передаєте як явний параметр, і явно документуйте будь-які параметри, які неявно передаються від абонентів функції. Але в десятиліттях, ~ 10 8 -LOC-коди, таких розкошів, яких часто немає.

І, звичайно, по суті всі добрі практики рефакторингу в мовах з лексичною сферою застосовуються також у мовах з динамічною сферою - пишіть тести тощо. Питання, таким чином, таке: як ми пом’якшуємо ризики, специфічно пов’язані з підвищеною крихкістю динамічно-охопленого коду під час рефакторингу?

(Зауважте, що в той час, як код навігації та рефактора написаний динамічною мовою? Має подібний заголовок до цього питання, він повністю не пов'язаний.)



@gnat Я не бачу, наскільки це питання / його відповіді стосуються цього питання.
сеншин

1
@gnat Ви хочете сказати, що у відповідь "використовувати різні процеси та інші важкі речі"? Я маю на увазі, це, мабуть, не помиляється, але це також загальноприйнято до того, що це не особливо корисно.
сеншин

2
Чесно кажучи, я не думаю, що є відповідь на це, крім "переходу на мову, де змінні фактично мають правила скопіювання" або "використання пасинка-пасинка угорської нотації, де кожна змінна префіксована своїм файлом та / або ім'ям методу. ніж тип або вид ". Питання, яке ви описуєте, просто таке жахливе, що я не можу уявити собі хорошого рішення.
Іксрек

4
Принаймні, ви не можете звинувачувати МУМПС у неправдивій рекламі за те, що вона названа на честь неприємної хвороби.
Carson63000

Відповіді:


4

Ого.

Я не знаю МУМПИ як мови, тому не знаю, чи стосується тут мій коментар. Взагалі кажучи - ви повинні рефактор зсередини. Ці споживачі (читачі) глобального стану (глобальні змінні) повинні бути перероблені на методи / функції / процедури з використанням параметрів. Метод 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;
}

кінець перепишіть кожне вживання b з x = b (). Функція b повинна використовувати лише очищені методи (можливо, ви хочете, щоб ro rename co зробила це зрозумілим) під час цього рефакторингу. Після цього вам слід рефактор b, щоб не забруднювати глобальне середовище.

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

перейменуйте b на b_cleaned. Я думаю, вам доведеться трохи пограти з цим, щоб звикнути до цього рефакторингу. Звичайно, не кожен метод може бути відремонтований цим, але вам доведеться починати з внутрішніх частин. Спробуйте це з Eclipse та java (методи вилучення) та "глобальний стан", також члени класу, щоб отримати уявлення.

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

чт.

Запитання: Маючи на увазі ці небезпеки, як можна безпечно переробити код у MUMPS або будь-якій іншій мові з динамічним визначенням масштабу?

  • Можливо, хтось ще може дати підказку.

Запитання: Як ми пом’якшуємо ризики, специфічно пов’язані з підвищеною крихкістю динамічно-охопленого коду під час рефакторингу?

  • Напишіть програму, яка робить безпечні для вас рефактори.
  • Напишіть програму, яка визначить безпечних кандидатів / перших кандидатів.

Так, є одна специфічна перешкода для спроб автоматизації процесу рефакторингу: MUMPS не має функцій першого класу, а також не має функціональних покажчиків чи подібних понять. Це означає, що будь-яка велика база коду MUMPS неминуче матиме багато застосувань eval (у MUMPS, що називається EXECUTE), іноді навіть на санітарному введенні користувача - це означає, що статично неможливо знайти та переписати всі звичаї функції.
сеншин

Гаразд, вважай мою відповідь неадекватною. Відео на YouTube, я думаю, що рефакторинг @ google шкала зробив дуже унікальний підхід. Вони використовували кланг для розбору AST, а потім використовували власну пошукову систему, щоб знайти будь-яке (навіть приховане використання) для рефакторації свого коду. Це може бути способом пошуку кожного використання. Я маю на увазі розбір і пошук підходу на паротитному коді.
thepacker

2

Я думаю, ваш найкращий результат - взяти під свій контроль повну базу коду та переконатися, що ви маєте огляд модулів та їх залежностей.

Отже, принаймні, у вас є шанс зробити глобальний пошук, і ви зможете додати тести регресії для частин системи, де ви очікуєте впливу від зміни коду.

Якщо ви не бачите шансу виконати перший, моя найкраща порада: не переробляйте жодних модулів, які повторно використовуються іншими модулями, або для яких ви не знаєте, що інші на них покладаються . У будь-якій кодовій базі розумного розміру шанси високі, ви можете знайти модулі, від яких не залежить жоден інший модуль. Отже, якщо у вас є модуль A залежно від B, але не навпаки, і жоден інший модуль не залежить від A, навіть у мові, що динамічно визначається, ви можете внести зміни до A, не порушуючи B або будь-які інші модулі.

Це дає вам шанс замінити залежність від A до B на залежність від A до B2, де B2 - це санітована, переписана версія B. B2 повинна бути нещодавно написаною із зазначеними вище правилами, щоб зробити код більш еластичний і легший для рефактора.


Це хороша порада, хоча я додам, що це по суті важко в MUMPS, оскільки немає поняття специфікаторів доступу та будь-якого іншого механізму інкапсуляції, що означає, що API, які ми вказуємо в нашій кодовій базі, є фактично лише пропозиціями для споживачів код про те, які функції вони повинні викликати. (Звичайно, ця особлива складність не пов’язана з динамічним оцінюванням; я просто зазначаю це як цікавий
предмет

Прочитавши цю статтю , я впевнений, що не заздрю ​​вам за ваше завдання.
Doc Brown

0

Стверджувати очевидне: як тут зробити рефакторинг? Дійте дуже обережно.

(Як ви вже описали це, розробка та підтримка існуючої бази коду повинна бути досить складною, не кажучи вже про спробу її рефакторингу.)

Я вважаю, що я застосував би заднім числом тут тестовий підхід. Це передбачає написання набору тестів, щоб забезпечити те, що поточна функціональність продовжує працювати, коли ви починаєте рефакторинг, спочатку просто для полегшення тестування. (Так, тут я очікую проблеми з куркою та яйцями, якщо тільки ваш код не є досить модульним, щоб перевірити, не змінюючи його взагалі.)

Потім ви можете приступити до іншого рефакторингу, перевіривши, чи не пройшли жодних тестів під час проходження.

Нарешті, ви можете почати писати тести, які очікують нової функціональності, а потім написати код, щоб ці тести спрацювали.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.