Pour ceux d'entre vous qui ont la chance de ne pas travailler dans une langue à portée dynamique, permettez-moi de vous donner un petit rappel sur la façon dont cela fonctionne. Imaginez un pseudo-langage, appelé "RUBELLA", qui se comporte comme ceci:
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"
Autrement dit, les variables se propagent librement de haut en bas dans la pile des appels - toutes les variables définies dans foo
sont visibles (et modifiables par) son appelant bar
, et l'inverse est également vrai. Cela a de sérieuses implications pour la refactorisation du code. Imaginez que vous disposez du code suivant:
function a() { // defined in file A
x = "qux";
b();
}
function b() { // defined in file B
c();
}
function c() { // defined in file C
print(x);
}
Maintenant, les appels à a()
seront imprimés qux
. Mais un jour, vous décidez que vous devez changer b
un peu. Vous ne connaissez pas tous les contextes d'appel (dont certains peuvent en fait être en dehors de votre base de code), mais cela devrait être correct - vos modifications vont être complètement internes à b
, non? Donc vous le réécrivez comme ceci:
function b() {
x = "oops";
c();
}
Et vous pourriez penser que vous n'avez rien changé, puisque vous venez de définir une variable locale. Mais en fait, vous avez cassé a
! Maintenant, a
imprime oops
plutôt que qux
.
En ramenant cela hors du domaine des pseudo-langages, c'est exactement comment MUMPS se comporte, bien qu'avec une syntaxe différente.
Les versions modernes ("modernes") de MUMPS incluent la soi-disant NEW
instruction, qui vous permet d'empêcher les variables de fuir d'un appelé vers un appelant. Donc, dans le premier exemple ci-dessus, si nous avions fait NEW y = "tetanus"
in foo()
, alors print(y)
in bar()
n'imprimerait rien (dans MUMPS, tous les noms pointent vers la chaîne vide sauf s'ils sont explicitement définis sur autre chose). Mais rien ne peut empêcher les variables de fuir d'un appelant à un appelé: si nous en avons function p() { NEW x = 3; q(); print(x); }
, pour autant que nous le sachions, elles q()
pourraient muter x
, bien qu'elles ne reçoivent pas explicitement x
comme paramètre. C'est toujours une mauvaise situation, mais pas aussi mauvaise qu'auparavant.
Compte tenu de ces dangers, comment pouvons-nous refactoriser le code en toute sécurité dans MUMPS ou dans tout autre langage avec une portée dynamique?
Il existe des bonnes pratiques évidentes pour faciliter le refactoring, comme ne jamais utiliser de variables dans une fonction autre que celles que vous initialisez ( NEW
) vous-même ou qui sont passées en tant que paramètre explicite, et documenter explicitement tous les paramètres qui sont implicitement transmis par les appelants d'une fonction. Mais dans une base de code vieille de 10 décennies ~ 10 8 -LOC, ce sont des luxes que l'on n'a souvent pas.
Et, bien sûr, pratiquement toutes les bonnes pratiques de refactorisation dans les langues à portée lexicale sont également applicables dans les langues à portée dynamique - tests d'écriture, etc. La question est donc la suivante: comment atténuer les risques spécifiquement associés à la fragilité accrue du code à portée dynamique lors de la refactorisation?
(Notez que même si Comment naviguer et refactoriser du code écrit dans un langage dynamique? A un titre similaire à cette question, il n'a aucun rapport.)