Für diejenigen von Ihnen, die das Glück haben, nicht in einer Sprache mit dynamischem Umfang zu arbeiten, möchte ich Ihnen eine kleine Auffrischung darüber geben, wie das funktioniert. Stellen Sie sich eine Pseudosprache namens "RUBELLA" vor, die sich wie folgt verhält:
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"
Das heißt, Variablen breiten sich frei im Aufrufstapel auf und ab aus - alle in definierten Variablen foo
sind für ihren Aufrufer sichtbar (und für ihn veränderbar) bar
, und das Gegenteil ist auch der Fall. Dies hat schwerwiegende Auswirkungen auf die Code-Refactorability. Stellen Sie sich vor, Sie haben folgenden Code:
function a() { // defined in file A
x = "qux";
b();
}
function b() { // defined in file B
c();
}
function c() { // defined in file C
print(x);
}
Jetzt werden Anrufe an a()
gedruckt qux
. Aber eines Tages entscheiden Sie, dass Sie b
ein wenig ändern müssen . Sie kennen nicht alle aufrufenden Kontexte (von denen einige tatsächlich außerhalb Ihrer Codebasis liegen können), aber das sollte in Ordnung sein - Ihre Änderungen werden vollständig intern sein b
, oder? Also schreiben Sie es wie folgt um:
function b() {
x = "oops";
c();
}
Und Sie denken vielleicht, dass Sie nichts geändert haben, da Sie gerade eine lokale Variable definiert haben. Aber in der Tat, du bist kaputt a
! Jetzt a
druckt oops
eher als qux
.
Dies wird aus dem Bereich der Pseudosprachen zurückgebracht, und genau so verhält sich MUMPS, wenn auch mit unterschiedlicher Syntax.
Moderne ("moderne") Versionen von MUMPS enthalten die sogenannte NEW
Anweisung, mit der Sie verhindern können, dass Variablen von einem Angerufenen zu einem Aufrufer gelangen. So im ersten Beispiel oben, wenn wir getan hatten NEW y = "tetanus"
in foo()
, dann print(y)
in bar()
würde nichts drucken (in Mumps, alle Namen auf die leere Zeichenfolge verweisen , sofern nicht ausdrücklich auf etwas anderes). Es gibt jedoch nichts, was verhindern könnte, dass Variablen von einem Aufrufer zu einem Angerufenen gelangen: Wenn wir function p() { NEW x = 3; q(); print(x); }
, soweit wir wissen, q()
mutieren könnten x
, obwohl wir nicht explizit x
als Parameter empfangen . Dies ist immer noch eine schlechte Situation, aber nicht so schlimm, wie es wahrscheinlich früher war.
Wie können wir unter Berücksichtigung dieser Gefahren Code in MUMPS oder einer anderen Sprache mit dynamischem Gültigkeitsbereich sicher umgestalten?
Es gibt einige gute Methoden, um das Refactoring zu vereinfachen, z. B. die Verwendung von Variablen in einer Funktion, die nicht von Ihnen selbst initialisiert ( NEW
) oder als expliziter Parameter übergeben wurde, und die explizite Dokumentation von Parametern, die implizit von Funktionsaufrufern übergeben wurden. Aber in einer jahrzehntealten Codebasis von ~ 10 8 -LOC handelt es sich um Luxus, den man oft nicht hat.
Und natürlich sind im Wesentlichen alle bewährten Methoden für die Umgestaltung in Sprachen mit lexikalischem Geltungsbereich auch in Sprachen mit dynamischem Geltungsbereich anwendbar - Schreibtests und so weiter. Die Frage lautet also: Wie verringern wir die Risiken, die speziell mit der erhöhten Fragilität von Code mit dynamischem Gültigkeitsbereich beim Refactoring verbunden sind?
(Beachten Sie, dass in einer dynamischen Sprache verfasster Code zum Navigieren und Umgestalten zwar einen ähnlichen Titel wie diese Frage hat, aber in keiner Beziehung steht.)