Para aquellos de ustedes que tienen la suerte de no trabajar en un lenguaje con alcance dinámico, permítanme informarles un poco sobre cómo funciona esto. Imagina 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 (y se pueden mata) de su interlocutor bar
, y lo contrario también es cierto. Esto tiene serias implicaciones para la refactorabilidad del código. Imagina que tienes 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()
imprimirán qux
. Pero entonces, algún día, decides que necesitas cambiar un poco de b
. No conoces todos los contextos de llamada (algunos de los cuales pueden estar fuera de tu base de código), pero eso debería estar bien: tus cambios serán completamente internos a b
, ¿verdad? Así que lo reescribes así:
function b() {
x = "oops";
c();
}
Y podría pensar que no ha cambiado nada, ya que acaba de definir una variable local. Pero, de hecho, has roto a
! Ahora, a
imprime oops
en lugar de qux
.
Sacando esto de nuevo del reino de los pseudo-lenguajes, esto es exactamente cómo se comporta MUMPS, aunque con una sintaxis diferente.
Las versiones modernas ("modernas") de MUMPS incluyen la llamada declaración NEW
, que le permite evitar que las variables se filtren de una persona a otra. Entonces, en el primer ejemplo anterior, si hubiéramos hecho NEW y = "tetanus"
en foo()
, entonces print(y)
en bar()
no imprimiría nada (en MUMPS, todos los nombres apuntan a la cadena vacía a menos que se establezca explícitamente en otra cosa). Pero no hay nada que pueda evitar que las variables se filtren de la persona que llama a un destinatario: si tenemos function p() { NEW x = 3; q(); print(x); }
, por lo que sabemos, q()
podría mutar x
, a pesar de no recibir explícitamente x
como parámetro. Esta es todavía una mala situación en la que estar, pero no es tan tan malo como probablemente solía ser.
Teniendo en cuenta estos peligros, ¿cómo podemos refactorizar de forma segura el código en MUMPS o en cualquier otro idioma con un 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 inicializa usted mismo ( NEW
) o se pasan como un parámetro explícito, y documentar explícitamente cualquier parámetro que sea pasó implícitamente de los llamadores de una función. Pero en una base de códigos de ~ 10 8 -LOC de hace décadas, estos son lujos que a menudo no se tienen.
Y, por supuesto, esencialmente todas las buenas prácticas para refactorizar en lenguajes con alcance léxico también son aplicables en lenguajes con alcance dinámico: pruebas de escritura, etc. La pregunta, entonces, es la siguiente: ¿Cómo mitigamos los riesgos específicamente asociados con la mayor fragilidad del código de alcance dinámico al refactorizar?
(Tenga en cuenta que mientras ¿Cómo navega y refruta el código escrito en un lenguaje dinámico? tiene un título similar a esta pregunta, no tiene ninguna relación.)