Para aquellos de ustedes que tienen la suerte de no trabajar en un idioma con alcance dinámico, permítanme darles un pequeño repaso sobre cómo funciona. Imagine un pseudo-lenguaje, llamado "RUBELLA", que se comporta así:
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"
Es decir, las variables se propagan hacia arriba y hacia abajo en la pila de llamadas libremente: todas las variables definidas en foo
son visibles para (y mutables por) su llamador bar
, y lo contrario también es cierto. Esto tiene serias implicaciones para la refactorización del código. Imagine que tiene el siguiente 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);
}
Ahora, las llamadas a a()
se imprimirán qux
. Pero luego, algún día, decides que necesitas cambiar b
un poco. No conoce todos los contextos de llamadas (algunos de los cuales pueden estar fuera de su base de código), pero eso debería estar bien: sus cambios serán completamente internos b
, ¿verdad? Entonces lo reescribes así:
function b() {
x = "oops";
c();
}
Y puede pensar que no ha cambiado nada, ya que acaba de definir una variable local. Pero, de hecho, ¡te has roto a
! Ahora, a
imprime en oops
lugar de qux
.
Sacando esto del ámbito de los pseudo-idiomas, así es exactamente como se comporta MUMPS, aunque con una sintaxis diferente.
Las versiones modernas ("modernas") de MUMPS incluyen la llamada NEW
declaración, que le permite evitar que las variables se filtren de una persona que llama a una persona que llama. Así que en el primer ejemplo anterior, si hubiéramos hecho NEW y = "tetanus"
en foo()
, a continuación, print(y)
en bar()
imprimiría nada (en las paperas, todos los nombres apuntan a la cadena vacía a menos que establecer explícitamente a otra cosa). Pero no hay nada que pueda evitar que las variables se filtren de una persona que llama a una persona que llama: si tenemos function p() { NEW x = 3; q(); print(x); }
, por lo que sabemos, q()
podría mutar x
, a pesar de no recibirlo explícitamente x
como parámetro. Todavía es una mala situación, pero no tan mala como probablemente solía ser.
Con estos peligros en mente, ¿cómo podemos refactorizar de manera segura el código en MUMPS o en cualquier otro idioma con alcance dinámico?
Existen algunas buenas prácticas obvias para facilitar la refactorización, como nunca usar variables en una función que no sean las que usted mismo inicializa ( NEW
) o se pasan como un parámetro explícito, y documentar explícitamente cualquier parámetro que se pase implícitamente desde los llamadores de una función. Pero en una base de código de ~ 10 8 -LOC de décadas, estos son lujos que a menudo no se tienen.
Y, por supuesto, esencialmente todas las buenas prácticas para refactorizar en idiomas con alcance léxico también son aplicables en idiomas con alcance dinámico: pruebas de escritura, etc. La pregunta, entonces, es esta: ¿cómo mitigamos los riesgos específicamente asociados con la mayor fragilidad del código de alcance dinámico cuando se refactoriza?
(Tenga en cuenta que si bien ¿Cómo navega y refactoriza el código escrito en un lenguaje dinámico? Tiene un título similar a esta pregunta, no tiene ninguna relación).