Per quelli di voi che hanno la fortuna di non lavorare in una lingua con ambito dinamico, permettetemi di darvi un piccolo ripasso su come funziona. Immagina una pseudo-lingua, chiamata "RUBELLA", che si comporta in questo modo:
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"
Cioè, le variabili si propagano su e giù liberamente nello stack di chiamate - tutte le variabili definite in foo
sono visibili a (e mutabili da) il suo chiamante bar
, e anche il contrario è vero. Questo ha gravi implicazioni per il codice refactability. Immagina di avere il seguente codice:
function a() { // defined in file A
x = "qux";
b();
}
function b() { // defined in file B
c();
}
function c() { // defined in file C
print(x);
}
Ora, le chiamate a a()
stamperanno qux
. Ma poi, un giorno, decidi che devi cambiare b
un po '. Non conosci tutti i contesti di chiamata (alcuni dei quali potrebbero essere al di fuori della tua base di codice), ma dovrebbe essere tutto a posto - le tue modifiche saranno completamente interne a b
, giusto? Quindi lo riscrivi in questo modo:
function b() {
x = "oops";
c();
}
E potresti pensare di non aver cambiato nulla, dal momento che hai appena definito una variabile locale. Ma, in effetti, hai rotto a
! Ora, a
stampa oops
anziché qux
.
Tornando fuori dal regno delle pseudo-lingue, questo è esattamente il modo in cui MUMPS si comporta, anche se con una sintassi diversa.
Le versioni moderne ("moderne") di MUMPS includono la cosiddetta istruzione NEW
, che consente di impedire alle variabili di filtrare da un chiamato a un chiamante. Quindi nel primo esempio sopra, se avessimo fatto NEW y = "tetanus"
in foo()
, quindi print(y)
in bar()
non stamperebbe nulla (in MUMPS, tutti i nomi puntano alla stringa vuota a meno che non siano impostati esplicitamente su qualcos'altro). Ma non c'è nulla che possa impedire alle variabili di passare da un chiamante a un chiamato: se abbiamo function p() { NEW x = 3; q(); print(x); }
, per quanto ne sappiamo, q()
potrebbe mutare x
, nonostante non ricevesse esplicitamente x
come parametro. Questa è ancora una brutta situazione in cui ci si trova, ma non as come di solito.
Tenendo presente questi pericoli, in che modo possiamo eseguire il refactoring del codice in modo sicuro in MUMPS o in qualsiasi altra lingua con ambito dinamico?
Ci sono alcune ovvie buone pratiche per rendere più facile il refactoring, come non usare mai variabili in una funzione diversa da quelle che hai inizializzato ( NEW
) o che sei passato come parametro esplicito e documentando esplicitamente tutti i parametri che sono passato implicitamente dai chiamanti di una funzione. Ma in un codebase decennale, ~ 10 8 -LOC, questi sono lussi che spesso non si hanno.
E, naturalmente, essenzialmente tutte le buone pratiche per il refactoring in lingue con scope lessicale sono applicabili anche in linguaggi con scope dinamico - test di scrittura e così via. La domanda, quindi, è questa: come possiamo mitigare i rischi specificamente associati con l'aumento della fragilità del codice con scope dinamiche durante il refactoring?
(Si noti che, mentre come si naviga e il codice refactoring è scritto in un linguaggio dinamico? ha un titolo simile a questa domanda, è del tutto estraneo.)