Как безопасно выполнить рефакторинг на языке с динамической областью действия?

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 это роскошь, которой часто нет.

И, конечно, по существу, все хорошие практики для рефакторинга в языках с лексической областью видимости также применимы в языках с динамической областью видимости - тесты на запись и так далее. Тогда возникает вопрос: как мы можем снизить риски, связанные с повышенной уязвимостью динамически изменяемого кода при рефакторинге?

(Обратите внимание, что хотя « Как вы перемещаетесь и реорганизуете код, написанный на динамическом языке? », Заголовок похож на этот вопрос, он совершенно не связан.)

senshin
источник
связанный (возможно, дубликат): есть ли связь между масштабом проекта и строгостью языка?
Комнат
@gnat Я не вижу, насколько этот вопрос / его ответы имеют отношение к этому вопросу.
Сеншин
1
@gnat Вы говорите, что ответ «использовать разные процессы и другие тяжелые вещи»? Я имею в виду, что это, вероятно, не так, но это также слишком общее, что не особенно полезно.
Сеншин
2
Честно говоря, я не думаю, что есть ответ на этот вопрос, кроме как «переключиться на язык, где переменные на самом деле имеют правила области видимости» или «использовать ублюдочного пасынка венгерской нотации, где каждая переменная имеет префикс по имени файла и / или метода, а чем тип или вид ". Проблема, которую вы описываете, настолько ужасна, что я не могу представить себе хорошее решение.
Ixrec
4
По крайней мере, вы не можете обвинить MUMPS в ложной рекламе за то, что ее назвали в честь противной болезни.
Carson63000

Ответы:

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;
}

end переписать каждое использование 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 (извлекайте методы) и "global state", или членов класса, чтобы получить представление.

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

НТН.

Вопрос: Имея в виду эти опасности, как мы можем безопасно реорганизовать код в MUMPS или любом другом языке с динамической областью видимости?

  • Может быть, кто-то еще может дать подсказку.

Вопрос: Как мы можем снизить риски, связанные с повышенной уязвимостью динамически изменяемого кода при рефакторинге?

  • Напишите программу, которая сделает безопасный рефакторинг для вас.
  • Напишите программу, которая идентифицирует безопасных кандидатов / первых кандидатов.
thepacker
источник
Ах, есть одно специфическое препятствие для MUMPS при попытке автоматизировать процесс рефакторинга: MUMPS не имеет первоклассных функций и не имеет указателей на функции или каких-либо подобных понятий. Это означает, что любая большая кодовая база MUMPS неизбежно будет иметь много применений eval (в MUMPS, называемом EXECUTE), иногда даже для санированного пользовательского ввода - это означает, что может быть невозможно статически найти и переписать все случаи использования функции.
Сеншин
Хорошо, считай мой ответ неадекватным. Видео на YouTube, я думаю, рефакторинг @ google scale сделал очень уникальный подход. Они использовали clang для анализа AST, а затем использовали свою собственную поисковую систему, чтобы найти любое (даже скрытое использование) для рефакторинга своего кода. Это может быть способ найти любое использование. Я имею в виду синтаксический анализ и поиск по коду паротита.
упаковщик
2

Я думаю, что ваш лучший способ - это взять под свой контроль полную базу кода и убедиться, что у вас есть обзор модулей и их зависимостей.

Так что, по крайней мере, у вас есть шанс выполнить глобальный поиск и добавить регрессионные тесты для тех частей системы, на которые вы рассчитываете влияние изменения кода.

Если вы не видите возможности выполнить первое, мой лучший совет: не проводите рефакторинг модулей, которые повторно используются другими модулями или для которых вы не знаете, что другие полагаются на них . В любой кодовой базе разумного размера высоки шансы найти модули, от которых не зависит ни один другой модуль. Таким образом, если у вас есть мод A, зависящий от B, но не наоборот, и никакой другой модуль не зависит от A, даже на языке с динамической областью действия, вы можете вносить изменения в A, не нарушая B или любые другие модули.

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

Док Браун
источник
Это хороший совет, хотя я добавлю в сторону, что это сложно по своей природе в MUMPS, так как здесь нет понятия спецификаторов доступа или какого-либо другого механизма инкапсуляции, а это означает, что API, которые мы указываем в нашей кодовой базе, фактически являются просто предложениями для потребителей код о том, какие функции они должны вызывать. (Конечно, эта конкретная трудность не связана с динамической областью видимости; я просто отмечаю это как интересную
вещь
Прочитав эту статью , я уверен, что не завидую вам за вашу задачу.
Док Браун
0

Чтобы заявить очевидное: как сделать рефакторинг здесь? Действуйте очень осторожно.

(Как вы уже описали, разработка и поддержка существующей кодовой базы должны быть достаточно сложными, не говоря уже о попытке ее реорганизовать.)

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

Затем вы можете приступить к другому рефакторингу, проверяя, что вы не нарушили ни одного теста.

Наконец, вы можете начать писать тесты, которые ожидают новых функциональных возможностей, а затем написать код, чтобы эти тесты работали.

Марк Херд
источник