Đối với những người có may mắn không làm việc trong một ngôn ngữ có phạm vi năng động, hãy để tôi cung cấp cho bạn một chút bồi dưỡng về cách làm việc đó. Hãy tưởng tượng một ngôn ngữ giả, được gọi là "RUBELLA", hoạt động như thế này:
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"
Đó là, các biến truyền lên và xuống ngăn xếp cuộc gọi một cách tự do - tất cả các biến được xác định trong foo
đều hiển thị (và có thể thay đổi bởi) trình gọi của nó bar
và điều ngược lại cũng đúng. Điều này có ý nghĩa nghiêm trọng đối với khả năng tái cấu trúc mã. Hãy tưởng tượng rằng bạn có mã sau đây:
function a() { // defined in file A
x = "qux";
b();
}
function b() { // defined in file B
c();
}
function c() { // defined in file C
print(x);
}
Bây giờ, các cuộc gọi đến a()
sẽ in qux
. Nhưng sau đó, một ngày nào đó, bạn quyết định rằng bạn cần thay đổi b
một chút. Bạn không biết tất cả các bối cảnh cuộc gọi (một số trong đó thực tế có thể nằm ngoài cơ sở mã của bạn), nhưng điều đó sẽ ổn thôi - những thay đổi của bạn sẽ hoàn toàn bên trong b
, phải không? Vì vậy, bạn viết lại nó như thế này:
function b() {
x = "oops";
c();
}
Và bạn có thể nghĩ rằng bạn đã không thay đổi bất cứ điều gì, vì bạn vừa xác định một biến cục bộ. Nhưng, trên thực tế, bạn đã phá vỡ a
! Bây giờ, a
in oops
hơn là qux
.
Đưa điều này trở lại khỏi vương quốc của các ngôn ngữ giả, đây chính xác là cách MUMPS hành xử, mặc dù với các cú pháp khác nhau.
Các phiên bản hiện đại ("hiện đại") của MUMPS bao gồm NEW
câu lệnh được gọi là , cho phép bạn ngăn các biến bị rò rỉ từ một callee sang một người gọi. Vì vậy, trong ví dụ đầu tiên ở trên, nếu chúng ta đã làm NEW y = "tetanus"
trong foo()
, sau đó print(y)
trong bar()
sẽ in gì (trong quai bị, tất cả các tên trỏ đến chuỗi rỗng trừ khi rõ ràng thiết lập để cái gì khác). Nhưng không có gì có thể ngăn các biến bị rò rỉ từ một người gọi đến một callee: nếu function p() { NEW x = 3; q(); print(x); }
tất cả chúng ta biết, q()
có thể đột biến x
, mặc dù không nhận được x
một tham số rõ ràng . Đây vẫn là một tình huống xấu để được ở, nhưng không phải là xấu vì nó có thể sử dụng được.
Với những nguy hiểm này, làm thế nào chúng ta có thể cấu trúc lại mã một cách an toàn trong MUMPS hoặc bất kỳ ngôn ngữ nào khác có phạm vi động?
Có một số thực tiễn tốt rõ ràng để giúp tái cấu trúc dễ dàng hơn, như không bao giờ sử dụng các biến trong hàm ngoài các biến bạn tự khởi tạo ( NEW
) hoặc được truyền dưới dạng tham số rõ ràng và ghi lại rõ ràng bất kỳ tham số nào được truyền từ người gọi hàm. Nhưng trong một thập kỷ, ~ 10 8 -LOC codebase, đây là những thứ xa xỉ thường không có.
Và, tất nhiên, về cơ bản, tất cả các thực tiễn tốt để tái cấu trúc trong các ngôn ngữ có phạm vi từ vựng cũng được áp dụng trong các ngôn ngữ có phạm vi động - kiểm tra viết, v.v. Sau đó, câu hỏi đặt ra là: làm thế nào để chúng tôi giảm thiểu rủi ro liên quan cụ thể đến sự dễ vỡ của mã có phạm vi động khi tái cấu trúc?
(Lưu ý rằng trong khi Làm thế nào để bạn điều hướng và cấu trúc lại mã được viết bằng ngôn ngữ động? Có một tiêu đề tương tự với câu hỏi này, thì nó hoàn toàn không liên quan.)