Làm thế nào để bạn tái cấu trúc một cách an toàn trong một ngôn ngữ với phạm vi động?


13

Đố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ó barvà đ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 bmộ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ờ, ain oopshơ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 NEWcâ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 xmộ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 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.)



@gnat Tôi không thấy câu hỏi đó / câu trả lời của nó có liên quan đến câu hỏi này như thế nào.
Senshin

1
@gnat Bạn có nói rằng câu trả lời là "sử dụng các quy trình khác nhau và các công cụ nặng khác" không? Ý tôi là, điều đó có lẽ không sai, nhưng nó cũng quá chung chung đến mức không đặc biệt hữu ích.
Senshin

2
Thành thật mà nói, tôi không nghĩ có câu trả lời nào khác ngoài "chuyển sang ngôn ngữ mà các biến thực sự có quy tắc phạm vi" hoặc "sử dụng con ghẻ khốn của ký hiệu Hungary trong đó mọi biến được đặt trước bởi tệp và / hoặc tên phương thức hơn loại hoặc loại ". Vấn đề bạn mô tả thật khủng khiếp tôi không thể tưởng tượng được một giải pháp tốt .
Ixrec

4
Ít nhất bạn không thể buộc tội MUMPS quảng cáo sai vì được đặt tên theo một căn bệnh khó chịu.
Carson63000

Câu trả lời:


4

Tôi không biết MUMPS là ngôn ngữ, vì vậy tôi không biết liệu nhận xét của mình có áp dụng ở đây không. Nói chung - Bạn phải cấu trúc lại từ trong ra ngoài. Những người tiêu dùng (độc giả) của trạng thái toàn cầu (biến toàn cục) phải được tái cấu trúc thành các phương thức / hàm / thủ tục sử dụng tham số. Phương thức c sẽ trông như thế này sau khi tái cấu trúc:

function c(c_scope_x) {
   print c(c_scope_x);
}

tất cả các công dụng của c phải được viết lại thành (đây là một nhiệm vụ cơ học)

c(x)

điều này là để cách ly mã "bên trong" khỏi trạng thái toàn cầu bằng cách sử dụng trạng thái cục bộ. Khi bạn hoàn thành việc đó, bạn sẽ phải viết lại b thành:

function b() {
   x="oops"
   print c(x);
}

việc gán x = "oops" là có để giữ các tác dụng phụ. Bây giờ chúng ta phải coi b là gây ô nhiễm nhà nước toàn cầu. Nếu bạn chỉ có một yếu tố ô nhiễm, hãy xem xét việc tái cấu trúc này:

function b() {
   x="oops"
   print c(x);
   return x;
}

kết thúc viết lại mỗi lần sử dụng b với x = b (). Hàm b phải chỉ sử dụng các phương thức đã được dọn sạch (bạn có thể muốn ro đổi tên cho rõ ràng) khi thực hiện tái cấu trúc này. Sau đó, bạn nên tái cấu trúc b để không gây ô nhiễm môi trường toàn cầu.

function b() {
   newvardefinition b_scoped_x="oops"
   print c_cleaned(b_scoped_x);
   return b_scoped_x;
}

đổi tên b thành b_cleaned. Tôi đoán bạn sẽ phải chơi một chút với điều đó để được bồi dưỡng cho việc tái cấu trúc đó. Chắc chắn không phải mọi phương pháp đều có thể được tái cấu trúc bằng cách này nhưng bạn sẽ phải bắt đầu từ các phần bên trong. Hãy thử điều đó với Eclipse và java (các phương thức trích xuất) và "trạng thái toàn cầu" hay còn gọi là các thành viên lớp để có ý tưởng.

function x() {
  fifth_to_refactor();
  {
    forth_to_refactor()
    ....
    {
      second_to_refactor();
    }
    ...
    third_to_refactor();
  }
  first_to_refactor()
}

hth

Câu hỏi: 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ó lẽ người khác có thể đưa ra một gợi ý.

Câu hỏi: 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ã phạm vi động khi tái cấu trúc?

  • Viết một chương trình, trong đó tái cấu trúc an toàn cho bạn.
  • Viết một chương trình, trong đó xác định các ứng cử viên an toàn / ứng cử viên đầu tiên.

À, có một trở ngại cụ thể của MUMPS khi cố gắng tự động hóa quá trình tái cấu trúc: MUMPS không có chức năng hạng nhất, cũng không có con trỏ chức năng hoặc bất kỳ khái niệm tương tự nào. Điều đó có nghĩa là bất kỳ cơ sở mã MUMPS lớn nào chắc chắn sẽ có rất nhiều cách sử dụng eval (trong MUMPS, được gọi EXECUTE), đôi khi ngay cả trên đầu vào của người dùng được khử trùng - điều đó có nghĩa là không thể tìm thấy và viết lại một cách tĩnh tất cả các cách sử dụng hàm.
Senshin

Được rồi xem xét câu trả lời của tôi là không đầy đủ. Một video youtube tôi nghĩ rằng tái cấu trúc quy mô @ google đã làm một cách tiếp cận rất độc đáo. Họ đã sử dụng clang để phân tích AST và sau đó sử dụng công cụ tìm kiếm của riêng họ để tìm bất kỳ (thậm chí sử dụng ẩn) để cấu trúc lại mã của họ. Đây có thể là một cách để tìm mọi cách sử dụng. Tôi có nghĩa là một cách tiếp cận phân tích và tìm kiếm trên mã quai bị.
đóng gói

2

Tôi đoán rằng cách tốt nhất của bạn là mang cơ sở mã đầy đủ dưới sự kiểm soát của bạn và đảm bảo bạn có cái nhìn tổng quan về các mô-đun và các phụ thuộc của chúng.

Vì vậy, ít nhất bạn có cơ hội thực hiện các tìm kiếm toàn cầu và có cơ hội thêm các bài kiểm tra hồi quy cho các bộ phận của hệ thống nơi bạn mong đợi tác động của thay đổi mã.

Nếu bạn không thấy cơ hội hoàn thành điều đầu tiên, lời khuyên tốt nhất của tôi là: không cấu trúc lại bất kỳ mô-đun nào được sử dụng lại bởi các mô-đun khác hoặc bạn không biết rằng những người khác dựa vào chúng . Trong bất kỳ cơ sở mã hóa nào có kích thước hợp lý, khả năng rất cao bạn có thể tìm thấy các mô-đun mà không có mô-đun nào khác phụ thuộc. Vì vậy, nếu bạn có một mod A phụ thuộc vào B, nhưng không phải ngược lại và không có mô-đun nào khác phụ thuộc vào A, ngay cả trong ngôn ngữ có phạm vi động, bạn có thể thay đổi A mà không phá vỡ B hoặc bất kỳ mô-đun nào khác.

Điều này cho bạn cơ hội thay thế sự phụ thuộc của A thành B bằng sự phụ thuộc của A thành B2, trong đó B2 là phiên bản được viết lại, được viết lại của B. B2 phải là một quy tắc mới được viết với các quy tắc mà bạn đã đề cập ở trên để tạo mã dễ phát triển hơn và dễ dàng hơn để tái cấu trúc.


Đây là một lời khuyên tốt, mặc dù tôi sẽ nói thêm rằng điều này vốn đã khó khăn trong MUMPS vì không có khái niệm về các chỉ định truy cập cũng như bất kỳ cơ chế đóng gói nào khác, có nghĩa là các API mà chúng tôi chỉ định trong cơ sở mã của chúng tôi thực sự chỉ là đề xuất cho người tiêu dùng về mã về chức năng mà họ nên gọi. (Tất nhiên, khó khăn đặc biệt này không liên quan đến phạm vi năng động; tôi chỉ ghi chú điều này như một điểm quan tâm.)
Senshin

Sau khi đọc bài viết này , tôi chắc chắn rằng tôi không ghen tị với bạn về nhiệm vụ của bạn.
Doc Brown

0

Để nói rõ: Làm thế nào để tái cấu trúc ở đây? Tiến hành rất cẩn thận.

(Như bạn đã mô tả về nó, việc phát triển và duy trì cơ sở mã hiện tại là đủ khó, chứ đừng nói đến việc cố gắng cấu trúc lại nó.)

Tôi tin rằng tôi sẽ áp dụng hồi tố một cách tiếp cận dựa trên thử nghiệm ở đây. Điều này sẽ liên quan đến việc viết một bộ thử nghiệm để đảm bảo chức năng hiện tại vẫn hoạt động khi bạn bắt đầu tái cấu trúc, trước tiên chỉ để làm cho thử nghiệm dễ dàng hơn. (Vâng, tôi mong đợi một vấn đề về gà và trứng ở đây, trừ khi mã của bạn đã đủ mô-đun để kiểm tra mà không thay đổi gì cả.)

Sau đó, bạn có thể tiến hành tái cấu trúc khác, kiểm tra xem bạn đã không phá vỡ bất kỳ bài kiểm tra nào khi bạn đi.

Cuối cùng, bạn có thể bắt đầu viết các bài kiểm tra mong đợi chức năng mới và sau đó viết mã để làm cho các bài kiểm tra đó hoạt động.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.