Para aqueles que têm a sorte de não trabalhar em uma linguagem com escopo dinâmico, deixe-me fazer uma pequena revisão sobre como isso funciona. Imagine uma pseudo-linguagem, chamada "RUBELLA", que se comporta assim:
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"
Ou seja, as variáveis se propagam para cima e para baixo na pilha de chamadas livremente - todas as variáveis definidas em foosão visíveis (e mutáveis por) ao seu chamador bar, e o inverso também é verdadeiro. Isso tem sérias implicações para a refatorabilidade do código. Imagine que você tenha o seguinte código:
function a() { // defined in file A
x = "qux";
b();
}
function b() { // defined in file B
c();
}
function c() { // defined in file C
print(x);
}
Agora, as chamadas para a()serão impressas qux. Mas então, um dia, você decide que precisa mudar bum pouco. Você não conhece todos os contextos de chamada (alguns dos quais podem, de fato, estar fora da sua base de código), mas isso deve ser bom - suas alterações serão completamente internas b, certo? Então você reescreve assim:
function b() {
x = "oops";
c();
}
E você pode pensar que não mudou nada, pois acabou de definir uma variável local. Mas, de fato, você quebrou a! Agora, aimprime em oopsvez de qux.
Trazendo isso de volta para o mundo das pseudo-línguas, é exatamente assim que o MUMPS se comporta, embora com sintaxe diferente.
As versões modernas ("modernas") do MUMPS incluem a chamada NEWinstrução, que permite impedir que variáveis vazem de um chamado para um chamador. Assim, no primeiro exemplo acima, se tivéssemos feito NEW y = "tetanus"em foo(), em seguida, print(y)em bar()imprimiria nada (em MUMPS, todos os nomes apontam para a cadeia vazia a menos que explicitamente definido para algo mais). Mas não há nada que possa impedir que variáveis vazem de um chamador para um chamado: se tivermos function p() { NEW x = 3; q(); print(x); }, pelo que sabemos, q()poderia sofrer mutação x, apesar de não recebermos explicitamente xcomo parâmetro. Essa ainda é uma situação ruim, mas não tão ruim quanto costumava ser.
Com esses perigos em mente, como podemos refatorar com segurança o código no MUMPS ou em qualquer outro idioma com escopo dinâmico?
Existem algumas boas práticas óbvias para facilitar a refatoração, como nunca usar variáveis em uma função diferente daquelas que você inicializa ( NEW) ou são passadas como um parâmetro explícito e documentar explicitamente quaisquer parâmetros que são implicitamente passados pelos chamadores de uma função. Mas em uma base de código de ~ 10 8- LOC de décadas, esses são luxos que geralmente não se tem.
E, é claro, essencialmente todas as boas práticas para refatoração em idiomas com escopo lexical também são aplicáveis em idiomas com escopo dinâmico - testes de gravação e assim por diante. A questão, então, é a seguinte: como mitigamos os riscos especificamente associados ao aumento da fragilidade do código de escopo dinâmico ao refatorar?
(Observe que, embora Como você navegue e refatore o código escrito em um idioma dinâmico? Tenha um título semelhante a esta pergunta, ele não tem nenhuma relação.)