Для тех из вас, кому посчастливилось не работать на языке с динамической областью действия, позвольте мне немного освежить в этом информацию. Представьте себе псевдо-язык, названный "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 это роскошь, которой часто нет.
И, конечно, по существу, все хорошие практики для рефакторинга в языках с лексической областью видимости также применимы в языках с динамической областью видимости - тесты на запись и так далее. Тогда возникает вопрос: как мы можем снизить риски, связанные с повышенной уязвимостью динамически изменяемого кода при рефакторинге?
(Обратите внимание, что хотя « Как вы перемещаетесь и реорганизуете код, написанный на динамическом языке? », Заголовок похож на этот вопрос, он совершенно не связан.)
источник
Ответы:
Вау.
Я не знаю MUMPS как язык, поэтому я не знаю, применим ли мой комментарий здесь. Вообще говоря - Вы должны рефакторинг изнутри. Эти потребители (читатели) глобального состояния (глобальные переменные) должны быть преобразованы в методы / функции / процедуры с использованием параметров. Метод c должен выглядеть следующим образом после рефакторинга:
все случаи использования c должны быть переписаны (это механическая задача)
это должно изолировать «внутренний» код от глобального состояния, используя локальное состояние. Когда вы закончите с этим, вам придется переписать b в:
Назначение x = "oops" предназначено для сохранения побочных эффектов. Теперь мы должны рассматривать b как загрязняющее глобальное состояние. Если у вас есть только один загрязненный элемент, рассмотрите этот рефакторинг:
end переписать каждое использование b с x = b (). При выполнении этого рефакторинга функция b должна использовать только методы, уже очищенные (вы можете захотеть, чтобы ro rename co прояснил это). После этого вам следует провести рефакторинг b, чтобы не загрязнять окружающую среду.
переименуйте b в b_cleaned. Думаю, вам придется немного поиграть с этим, чтобы привыкнуть к этому рефакторингу. Конечно, не каждый метод может быть реорганизован этим, но вам придется начинать с внутренних частей. Попробуйте это с помощью Eclipse и java (извлекайте методы) и "global state", или членов класса, чтобы получить представление.
НТН.
Вопрос: Имея в виду эти опасности, как мы можем безопасно реорганизовать код в MUMPS или любом другом языке с динамической областью видимости?
Вопрос: Как мы можем снизить риски, связанные с повышенной уязвимостью динамически изменяемого кода при рефакторинге?
источник
EXECUTE
), иногда даже для санированного пользовательского ввода - это означает, что может быть невозможно статически найти и переписать все случаи использования функции.Я думаю, что ваш лучший способ - это взять под свой контроль полную базу кода и убедиться, что у вас есть обзор модулей и их зависимостей.
Так что, по крайней мере, у вас есть шанс выполнить глобальный поиск и добавить регрессионные тесты для тех частей системы, на которые вы рассчитываете влияние изменения кода.
Если вы не видите возможности выполнить первое, мой лучший совет: не проводите рефакторинг модулей, которые повторно используются другими модулями или для которых вы не знаете, что другие полагаются на них . В любой кодовой базе разумного размера высоки шансы найти модули, от которых не зависит ни один другой модуль. Таким образом, если у вас есть мод A, зависящий от B, но не наоборот, и никакой другой модуль не зависит от A, даже на языке с динамической областью действия, вы можете вносить изменения в A, не нарушая B или любые другие модули.
Это дает вам возможность заменить зависимость от A до B зависимостью от A до B2, где B2 - очищенная, переписанная версия B. B2 должна быть заново написана с учетом упомянутых выше правил, чтобы сделать код более эволюционируемый и простой в рефакторинге.
источник
Чтобы заявить очевидное: как сделать рефакторинг здесь? Действуйте очень осторожно.
(Как вы уже описали, разработка и поддержка существующей кодовой базы должны быть достаточно сложными, не говоря уже о попытке ее реорганизовать.)
Я считаю, что я бы задним числом применил подход, основанный на тестировании. Это потребует написания набора тестов, чтобы убедиться, что текущая функциональность продолжает работать, как только вы начнете рефакторинг, во-первых, просто чтобы облегчить тестирование. (Да, я ожидаю здесь проблемы с яйцами, если только ваш код не является достаточно модульным, чтобы тестировать его, не меняя его вообще.)
Затем вы можете приступить к другому рефакторингу, проверяя, что вы не нарушили ни одного теста.
Наконец, вы можете начать писать тесты, которые ожидают новых функциональных возможностей, а затем написать код, чтобы эти тесты работали.
источник