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.)
la source
Réponses:
Sensationnel.
Je ne connais pas MUMPS comme langue, donc je ne sais pas si mon commentaire s'applique ici. De manière générale - Vous devez refactoriser de l'intérieur vers l'extérieur. Ces consommateurs (lecteurs) d'état global (variables globales) doivent être refactorisés en méthodes / fonctions / procédures utilisant des paramètres. La méthode c devrait ressembler à ceci après refactoring:
tous les usages de c doivent être réécrits (ce qui est une tâche mécanique)
il s'agit d'isoler le code "interne" de l'état global en utilisant l'état local. Lorsque vous aurez terminé, vous devrez réécrire b en:
l'affectation x = "oops" est là pour garder les effets secondaires. Nous devons maintenant considérer b comme polluant l'état global. Si vous n'avez qu'un seul élément pollué, pensez à ce refactoring:
réécrire chaque utilisation de b avec x = b (). La fonction b doit utiliser uniquement des méthodes déjà nettoyées (vous pouvez vouloir que ro rename co le précise) lors de cette refactorisation. Après cela, vous devez refactoriser b pour ne pas polluer l'environnement mondial.
renommez b en b_cleaned. Je suppose que vous devrez jouer un peu avec cela pour vous familiariser avec ce refactoring. Bien sûr, toutes les méthodes ne peuvent pas être refactorisées par cela, mais vous devrez commencer par les parties internes. Essayez cela avec Eclipse et java (méthodes d'extraction) et «état global», alias les membres de la classe, pour vous faire une idée.
hth.
Question: Compte tenu de ces dangers, comment pouvons-nous refactoriser en toute sécurité le code dans MUMPS ou dans tout autre langage avec une portée dynamique?
Question: Comment atténuer les risques spécifiquement associés à la fragilité accrue du code à portée dynamique lors de la refactorisation?
la source
EXECUTE
), parfois même sur une entrée utilisateur aseptisée - ce qui signifie qu'il peut être impossible de trouver et de réécrire statiquement toutes les utilisations d'une fonction.Je suppose que votre meilleur coup est de mettre la base de code complète sous votre contrôle et de vous assurer d'avoir une vue d'ensemble des modules et de leurs dépendances.
Vous avez donc au moins la possibilité de faire des recherches globales et d'ajouter des tests de régression pour les parties du système où vous vous attendez à un impact par un changement de code.
Si vous ne voyez aucune chance d'accomplir le premier, mon meilleur conseil est: ne refactorisez pas les modules qui sont réutilisés par d'autres modules, ou pour lesquels vous ne savez pas que d'autres comptent sur eux . Dans toute base de code d'une taille raisonnable, les chances sont élevées que vous puissiez trouver des modules dont aucun autre module ne dépend. Donc, si vous avez un mod A dépendant de B, mais pas l'inverse, et qu'aucun autre module ne dépend de A, même dans un langage à portée dynamique, vous pouvez apporter des modifications à A sans interrompre B ou tout autre module.
Cela vous donne une chance de remplacer la dépendance de A à B par une dépendance de A à B2, où B2 est une version réécrite et nettoyée de B. B2 devrait être une nouvelle écriture avec les règles à l'esprit que vous avez mentionnées ci-dessus pour créer le code plus évolutif et plus facile à refactoriser.
la source
Pour énoncer l'évidence: comment faire du refactoring ici? Procédez très soigneusement.
(Comme vous l'avez décrit, développer et maintenir la base de code existante devrait être assez difficile, et encore moins tenter de la refactoriser.)
Je crois que j'appliquerais rétroactivement ici une approche axée sur les tests. Cela impliquerait d'écrire une suite de tests pour vous assurer que la fonctionnalité actuelle continue de fonctionner pendant que vous commencez la refactorisation, tout d'abord pour faciliter les tests. (Oui, je m'attends à un problème de poulet et d'oeuf ici, à moins que votre code soit déjà suffisamment modulaire pour le tester sans le changer du tout.)
Ensuite, vous pouvez procéder à d'autres refactorisations, en vérifiant que vous n'avez pas réussi les tests au fur et à mesure.
Enfin, vous pouvez commencer à écrire des tests qui attendent de nouvelles fonctionnalités, puis écrire le code pour faire fonctionner ces tests.
la source