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 foo
sã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 b
um 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, a
imprime em oops
vez 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 NEW
instruçã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 x
como 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.)