Per quelli di voi che hanno la fortuna di non lavorare in una lingua con portata dinamica, lasciate che vi dia un piccolo aggiornamento su come funziona. Immagina uno pseudo-linguaggio, chiamato "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ù per lo stack di chiamate liberamente - tutte le variabili definite in foo
sono visibili (e mutabili da) al suo chiamante bar
, e anche il contrario è vero. Ciò ha serie implicazioni per la rifattabilità del codice. 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()
verranno stampate qux
. Ma poi, un giorno, deciderai che devi cambiare b
un po '. Non conosci tutti i contesti chiamanti (alcuni dei quali potrebbero in effetti essere al di fuori della tua base di codice), ma ciò dovrebbe andare bene - le tue modifiche saranno completamente interne 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
piuttosto che qux
.
Riportandolo fuori dal regno degli pseudo-linguaggi, questo è esattamente come si comporta MUMPS, sebbene con una sintassi diversa.
Le versioni moderne ("moderne") di MUMPS includono la cosiddetta NEW
istruzione, che consente di evitare che le variabili passino da una chiamata a un chiamante. Quindi, nel primo esempio di cui sopra, se avessimo fatto NEW y = "tetanus"
in foo()
, poi print(y)
in bar()
sarebbe stampare nulla (in MUMPS, tutti i nomi indicano la stringa vuota se non esplicitamente impostato a qualcos'altro). Ma non c'è nulla che possa impedire la fuoriuscita di variabili da un chiamante a una chiamata: se function p() { NEW x = 3; q(); print(x); }
, per quanto ne sappiamo, q()
potremmo mutare x
, nonostante non ricevessimo esplicitamente x
come parametro. Questa è ancora una brutta situazione, ma non così grave come probabilmente una volta.
Tenendo conto di questi pericoli, come possiamo eseguire il refactoring sicuro del codice in MUMPS o in qualsiasi altra lingua con ambito dinamico?
Esistono alcune buone pratiche ovvie per rendere più semplice il refactoring, come non usare mai variabili in una funzione diversa da quelle che si inizializza ( NEW
) o vengono passate come parametro esplicito e documentare esplicitamente tutti i parametri che sono implicitamente passati dai chiamanti di una funzione. Ma in una base di codice ~ 10 8 -LOC vecchia di decenni , questi sono lussi che spesso non si hanno.
E, naturalmente, essenzialmente tutte le buone pratiche per il refactoring in lingue con ambito lessicale sono applicabili anche in lingue con ambito dinamico: test di scrittura e così via. La domanda, quindi, è questa: come possiamo mitigare i rischi specificamente associati alla maggiore fragilità del codice con ambito dinamico durante il refactoring?
(Si noti che mentre come si naviga e si refactifica il codice scritto in un linguaggio dinamico? Ha un titolo simile a questa domanda, è del tutto indipendente.)