Dla tych z was, którzy mają szczęście nie pracować w języku z dynamicznym zakresem, pozwólcie, że trochę odświeżę, jak to działa. Wyobraź sobie pseudo-język o nazwie „RUBELLA”, który zachowuje się tak:
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"
Oznacza to, że zmienne swobodnie propagują się w górę i w dół stosu wywołań - wszystkie zmienne zdefiniowane w foo
są widoczne dla jego wywołującego (i mogą być przez niego mutowane) bar
, a odwrotność jest również prawdą. Ma to poważne konsekwencje dla refaktowalności kodu. Wyobraź sobie, że masz następujący kod:
function a() { // defined in file A
x = "qux";
b();
}
function b() { // defined in file B
c();
}
function c() { // defined in file C
print(x);
}
Teraz wywołania a()
zostaną wydrukowane qux
. Ale pewnego dnia zdecydujesz, że musisz b
się trochę zmienić . Nie znasz wszystkich kontekstów wywoływania (niektóre z nich mogą faktycznie znajdować się poza bazą kodu), ale to powinno być w porządku - twoje zmiany będą całkowicie wewnętrzne b
, prawda? Więc przepisujesz to w ten sposób:
function b() {
x = "oops";
c();
}
I możesz myśleć, że nic nie zmieniłeś, ponieważ właśnie zdefiniowałeś zmienną lokalną. Ale tak naprawdę złamałeś a
! Teraz a
drukuje oops
zamiast qux
.
Wydobywając to z królestwa pseudojęzyków, dokładnie tak zachowuje się MUMPS, aczkolwiek z inną składnią.
Nowoczesne („nowoczesne”) wersje MUMPS zawierają tak zwaną NEW
instrukcję, która pozwala zapobiegać wyciekaniu zmiennych z odbiorcy do dzwoniącego. Tak więc w pierwszym przykładzie powyżej, gdybyśmy zrobili NEW y = "tetanus"
w foo()
, a następnie print(y)
w bar()
wydrukuje nic (w śwince, wszystkie nazwy wskazują na pusty ciąg, chyba że wyraźnie ustawiony na coś innego). Ale nic nie stoi na przeszkodzie, aby zmienne wyciekły z dzwoniącego do odbierającego: gdybyśmy function p() { NEW x = 3; q(); print(x); }
, o ile wiemy, q()
mogliby mutować x
, mimo że nie otrzymywali jawnie x
jako parametru. Wciąż jest to zła sytuacja, ale nie tak zła, jak kiedyś.
Biorąc pod uwagę te niebezpieczeństwa, w jaki sposób możemy bezpiecznie refaktoryzować kod w MUMPS lub innym języku z dynamicznym określaniem zakresu?
Istnieją pewne oczywiste dobre praktyki ułatwiające refaktoryzację, takie jak nigdy nie używanie zmiennych w funkcji innej niż te, które inicjujesz ( NEW
) samodzielnie lub są przekazywane jako jawny parametr, i jawne dokumentowanie wszelkich parametrów, które są niejawnie przekazywane przez funkcje wywołujące funkcję. Ale w dziesięcioletniej bazie kodu ~ 10 8- LOC są to luksusy, których często nie ma.
I, oczywiście, zasadniczo wszystkie dobre praktyki dotyczące refaktoryzacji w językach o zakresie leksykalnym mają również zastosowanie w językach o zakresie dynamicznym - testy zapisu i tak dalej. Pytanie zatem brzmi: w jaki sposób ograniczamy ryzyko związane ze zwiększoną niestabilnością dynamicznie zakodowanego kodu podczas refaktoryzacji?
(Pamiętaj, że chociaż sposób nawigacji i zmiany kodu napisanego w dynamicznym języku? Ma podobny tytuł do tego pytania, jest całkowicie niezwiązany).
źródło
Odpowiedzi:
Łał.
Nie znam MUMPS jako języka, więc nie wiem, czy mój komentarz dotyczy tutaj. Ogólnie rzecz biorąc - Musisz refaktoryzować od wewnątrz. Tych konsumentów (czytników) stanu globalnego (zmienne globalne) należy przekształcić w metody / funkcje / procedury wykorzystujące parametry. Metoda c powinna wyglądać tak po refaktoryzacji:
wszystkie zastosowania c należy przepisać na (co jest zadaniem mechanicznym)
służy to odizolowaniu „wewnętrznego” kodu od stanu globalnego przy użyciu stanu lokalnego. Kiedy to zrobisz, będziesz musiał przepisać b na:
przypisanie x = „ups” służy do zachowania efektów ubocznych. Teraz musimy uznać b za zanieczyszczenie państwa globalnego. Jeśli masz tylko jeden zanieczyszczony element, rozważ to refaktoryzacja:
koniec przepisuj każde użycie b za pomocą x = b (). Funkcja b musi używać tylko metod, które zostały już wyczyszczone (możesz chcieć zmienić nazwę ro, aby to wyjaśnić) podczas wykonywania tego refaktoryzacji. Następnie powinieneś refaktoryzować b, aby nie zanieczyszczać środowiska globalnego.
zmień nazwę b na b_cleaned. Myślę, że będziesz musiał trochę z tym pograć, aby przyzwyczaić się do tego refaktoryzacji. Oczywiście nie każda metoda może zostać zrefaktoryzowana, ale będziesz musiał zacząć od wewnętrznych części. Wypróbuj to za pomocą Eclipse i java (metody wyodrębniania) i „klasy globalnej”, czyli członków klasy, aby uzyskać pomysł.
hth.
Pytanie: Mając na uwadze te niebezpieczeństwa, w jaki sposób możemy bezpiecznie refaktoryzować kod w MUMPS lub innym języku z dynamicznym określaniem zakresu?
Pytanie: W jaki sposób ograniczamy ryzyko związane ze zwiększoną niestabilnością kodu o dynamicznym zakresie podczas refaktoryzacji?
źródło
EXECUTE
), czasami nawet na oczyszczonych danych wejściowych użytkownika - co oznacza, że niemożliwe będzie statyczne znalezienie i przepisanie wszystkich zastosowań funkcji.Wydaje mi się, że najlepszym rozwiązaniem jest oddanie pełnej kontroli nad kodem i upewnienie się, że masz przegląd modułów i ich zależności.
Przynajmniej masz szansę na wyszukiwanie globalne i możesz dodać testy regresji dla części systemu, w których spodziewany jest wpływ zmiany kodu.
Jeśli nie widzisz szansy na osiągnięcie pierwszego, moja najlepsza rada jest następująca: nie refaktoryzuj modułów, które są ponownie używane przez inne moduły lub dla których nie wiesz, że inni na nich polegają . W każdej bazie kodu o rozsądnych rozmiarach są duże szanse na znalezienie modułów, od których nie zależy żaden inny moduł. Więc jeśli masz mod A zależny od B, ale nie odwrotnie, i żaden inny moduł nie zależy od A, nawet w języku dynamicznie skalowanym, możesz wprowadzić zmiany w A bez rozbijania B lub innych modułów.
Daje to szansę na zastąpienie zależności A do B zależnością A do B2, gdzie B2 to zdezynfekowana, przepisana wersja B. B2 powinien być nowo napisany z uwzględnieniem reguł, o których wspomniałeś powyżej, aby utworzyć kod bardziej ewolucyjny i łatwiejszy do refaktoryzacji.
źródło
Stwierdzić oczywiste: jak zrobić tutaj refaktoryzację? Postępuj bardzo ostrożnie.
(Jak już to opisałeś, opracowanie i utrzymanie istniejącej bazy kodu powinno być wystarczająco trudne, nie mówiąc już o próbie jej refaktoryzacji).
Wierzę, że zastosowałbym tutaj podejście testowe z mocą wsteczną. Wymagałoby to napisania zestawu testów, aby upewnić się, że bieżąca funkcjonalność będzie działać po rozpoczęciu refaktoryzacji, po pierwsze, aby ułatwić testowanie. (Tak, spodziewam się tutaj problemu z kurczakiem i jajkami, chyba że kod jest już wystarczająco modułowy, aby go przetestować bez zmiany).
Następnie możesz przystąpić do innego refaktoryzacji, sprawdzając, czy nie przełamałeś żadnych testów.
Wreszcie możesz rozpocząć pisanie testów, które oczekują nowej funkcjonalności, a następnie napisać kod, aby testy te działały.
źródło