Vá khỉ trong ngôn ngữ lập trình động: Một ví dụ về JavaScript

Javascript
Lập trình động
Vá khỉ trong ngôn ngữ lập trình động cover image

Giới thiệu

Bài viết này sẽ khám phá các khái niệm về ngôn ngữ lập trình Động và Tĩnh, sự khác biệt chính giữa hai ngôn ngữ này và những gì mỗi mô hình cung cấp về mặt ưu điểm và cạm bẫy. Việc khám phá này sẽ tập trung hơn nữa vào các ngôn ngữ lập trình động, đặc biệt là một trong những mẫu thiết yếu mà nó kích hoạt: Monkey Patch, mẫu này sẽ được trình bày với sự trợ giúp của một ví dụ trong JavaScript.

Ngôn ngữ lập trình động và tĩnh

Thuật ngữ

Để hiểu những gì tạo nên một ngôn ngữ động hay một ngôn ngữ tĩnh, chúng ta cần thiết lập sự hiểu biết về một số thuật ngữ chính thường được sử dụng trong ngữ cảnh này: Thời gian biên dịch, Thời gian chạy và *Kiểm tra kiểu *.

Biên dịch và Thời gian chạy là hai thuật ngữ tương ứng với các giai đoạn khác nhau trong vòng đời của chương trình máy tính, bắt đầu bằng Thời gian biên dịch.

Thời gian biên dịch

Thời gian biên dịch là bước đầu tiên trong vòng đời của một chương trình. Một nhà phát triển viết mã bằng một ngôn ngữ lập trình nhất định. Thông thường, máy không thể hiểu mã được viết bằng ngôn ngữ cấp cao nên một trình biên dịch chuyên dụng được sử dụng để dịch mã sang định dạng trung gian cấp thấp hơn để sẵn sàng thực thi.

Thời gian chạy

Thời gian chạy thường gói gọn hai bước: tải chương trình vào bộ nhớ bằng cách phân bổ các tài nguyên cần thiết để thực thi cùng với các lệnh của nó, sau đó thực thi chương trình theo thứ tự của các lệnh đó.

Sơ đồ sau đây minh họa quá trình này:

Kiểm tra loại

Kiểm tra kiểu là một tính năng tích hợp trong hầu hết các ngôn ngữ lập trình. Đó là khả năng kiểm tra xem giá trị được gán cho một biến nhất định có tương ứng với loại biến đó hay không. Mỗi ngôn ngữ lập trình có một cách khác nhau để biểu diễn giá trị của một loại nhất định trong bộ nhớ. Những cách biểu diễn khác nhau này giúp bạn có thể kiểm tra sự tương ứng giữa loại giá trị và loại biến mà bạn cố gắng gán giá trị đó.

Bây giờ chúng ta đã hiểu biết ở mức độ cao về vòng đời chương trình và kiểm tra loại, chúng ta có thể tiến hành khám phá các ngôn ngữ lập trình tĩnh.

Ngôn ngữ lập trình tĩnh

Ngôn ngữ lập trình tĩnh, còn được gọi là ngôn ngữ được gõ tĩnh là những ngôn ngữ áp dụng kiểm tra kiểu mà chúng tôi đã đề cập ở giai đoạn biên dịch. Điều này có nghĩa là một biến không được khai báo kiểu của nó và không có giá trị nào có thể được gán cho nó ngoài các giá trị từ kiểu khai báo của nó. Ngôn ngữ lập trình tĩnh mang lại sự an toàn cao hơn khi xử lý các loại nhưng có thể làm chậm quá trình phát triển trong một số trường hợp sử dụng nhất định khi điều này trở thành một hạn chế khắc nghiệt.

Ngôn ngữ lập trình động

Mặt khác, các ngôn ngữ lập trình động áp dụng kiểm tra kiểu khi chạy. Điều này có nghĩa là bất kỳ biến nào cũng có thể chứa bất kỳ giá trị nào tại bất kỳ điểm nào trong chương trình. Điều này có thể có lợi vì nó mang lại mức độ linh hoạt cho nhà phát triển mà ngôn ngữ tĩnh không có. Các ngôn ngữ động có xu hướng thực thi chậm hơn so với các ngôn ngữ tĩnh vì chúng bao gồm một bước bổ sung để tự động tìm ra cách gõ của từng biến.

Bản vá khỉ

Gõ tĩnh và gõ động là một đặc điểm cơ bản trong ngôn ngữ lập trình, việc sử dụng mô hình này với mô hình kia có thể tạo ra một loạt các mô hình và thực tiễn khác nhau có thể cải thiện đáng kể chất lượng và tốc độ phát triển. Nó cũng có thể mở ra nhiều hạn chế và phản khuôn mẫu nếu không có sự cân nhắc cẩn thận khi đưa ra quyết định thiết kế.

Đặc biệt, các ngôn ngữ lập trình kiểu động được biết là mang lại mức độ linh hoạt cao hơn vì chúng không giới hạn một biến ở một kiểu duy nhất. Tính linh hoạt này đi kèm với chi phí trách nhiệm bổ sung đối với nhà phát triển khi triển khai và gỡ lỗi chương trình để đảm bảo không có hành vi không thể đoán trước nào xảy ra. Mẫu vá khỉ xuất phát từ triết lý này.

Monkey Patch đề cập đến quá trình mở rộng/thay đổi hoạt động của một thành phần trong thời gian chạy. Thành phần được đề cập có thể là thư viện, lớp, phương thức hoặc thậm chí là mô-đun. Ý tưởng là như nhau: một đoạn mã được tạo ra để hoàn thành một nhiệm vụ nhất định và mục tiêu của việc vá khỉ là thay đổi hoặc mở rộng hành vi của đoạn mã đó để nó hoàn thành một nhiệm vụ mới mà không cần thay đổi chính mã đó .

Điều này có thể thực hiện được bằng ngôn ngữ lập trình động vì cho dù chúng ta đang xử lý loại thành phần nào, nó vẫn có cùng cấu trúc của một đối tượng với các thuộc tính khác nhau, các thuộc tính có thể chứa các phương thức có thể được gán lại để đạt được hành vi mới trong đối tượng mà không đi sâu vào nội bộ của nó và chi tiết thực hiện. Điều này trở nên đặc biệt hữu ích trong trường hợp các thư viện và mô-đun của bên thứ ba vì chúng có xu hướng khó điều chỉnh hơn.

Ví dụ sau đây sẽ giới thiệu một trường hợp sử dụng phổ biến có thể hưởng lợi từ việc sử dụng kỹ thuật vá khỉ. Javascript đã được sử dụng để triển khai ở đây nhưng điều này vẫn được áp dụng rộng rãi cho bất kỳ ngôn ngữ lập trình động nào khác.

Ví dụ

Triển khai Khung thử nghiệm tối thiểu với Mô-đun HTTP gốc của Node

Thử nghiệm đơn vị và tích hợp có thể thuộc các trường hợp sử dụng của bản vá Monkey. Chúng thường liên quan đến các trường hợp thử nghiệm trải rộng trên nhiều dịch vụ để thử nghiệm tích hợp hoặc các phần phụ thuộc API và/hoặc cơ sở dữ liệu để thử nghiệm đơn vị. Trong hai trường hợp này và để hoàn thành các mục tiêu thử nghiệm ngay từ đầu, chúng tôi muốn các thử nghiệm của mình độc lập với các tài nguyên bên ngoài này. Cách để đạt được điều này là thông qua chế nhạo. Mocking là mô phỏng hành vi của các dịch vụ bên ngoài để quá trình kiểm tra có thể tập trung vào logic thực tế của mã. Việc vá lỗi khỉ có thể hữu ích ở đây vì nó có thể sửa đổi các phương thức của dịch vụ bên ngoài bằng cách thay thế chúng bằng các phương thức giữ chỗ mà chúng tôi gọi là “sơ khai”. Các phương pháp này trả về kết quả mong đợi trong các trường hợp thử nghiệm để chúng tôi có thể tránh bắt đầu yêu cầu các dịch vụ sản xuất chỉ vì mục đích thử nghiệm.

Ví dụ sau đây là cách triển khai đơn giản bản vá Monkey trên mô-đun http gốc của NodeJ. Mô-đun http là giao diện triển khai các phương thức giao thức http cho NodeJ. Nó chủ yếu được sử dụng để tạo các máy chủ http cơ bản và liên lạc với các dịch vụ bên ngoài bằng giao thức http.

Trong ví dụ bên dưới, chúng tôi có một trường hợp thử nghiệm đơn giản trong đó chúng tôi gọi một dịch vụ bên ngoài để tìm nạp danh sách id người dùng. Thay vì gọi dịch vụ thực tế, chúng tôi vá phương thức http get để nó chỉ trả về kết quả mong đợi là một mảng id người dùng ngẫu nhiên. Điều này có vẻ không quan trọng lắm vì chúng tôi chỉ đang tìm nạp dữ liệu nhưng nếu chúng tôi triển khai một trường hợp thử nghiệm khác liên quan đến việc thay đổi một loại dữ liệu nào đó, chúng tôi có thể vô tình thay đổi dữ liệu trong quá trình sản xuất khi chạy thử nghiệm.

Bằng cách này, chúng tôi có thể triển khai các chức năng của mình và viết bài kiểm tra cho từng chức năng đồng thời đảm bảo sự an toàn cho các dịch vụ sản xuất của mình.

// import the http module
let http = require("http");

// patch the get method of the http module
http.get = async function(url) {
  return {
    data: ["1234", "1235", "1236", "1236"]
  };
}

// example test suite, call new patched get method for testing
test('get array of user ids from users api', async () => {
  const res = await http.get("https://users.api.com/ids");
  const userIds = res.data;
  expect(userIds).toBeDefined();
  expect(userIds.length).toBe(4);
  expect(userIds[0]).toBe("1234");
});

Đoạn mã trên rất đơn giản, chúng tôi nhập mô-đun http, gán lại phương thức http.get bằng một phương thức mới chỉ trả về một mảng id. Bây giờ chúng tôi gọi phương thức được vá mới bên trong trường hợp thử nghiệm và chúng tôi nhận được kết quả mới như mong đợi.

~/SphericalTartWorker$ npm test

> nodejs@1.0.0 test
> jest

PASS  ./index.test.js
  ✓ get array of user ids from users api (25 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.977 s, estimated 2 s
Ran all test suites.

Những cạm bẫy và hạn chế thường gặp

Không có gì ngạc nhiên khi việc vá khỉ có những sai sót và hạn chế riêng. Trong bối cảnh các mô-đun trong hệ thống mô-đun nút, việc vá một mô-đun toàn cầu như http được coi là một hoạt động có tác dụng phụ, điều này là do http có thể truy cập được từ bất kỳ điểm nào trong cơ sở mã và bất kỳ thực thể nào khác có thể phụ thuộc vào nó. Các thực thể này mong muốn mô-đun http hoạt động theo cách hoạt động thông thường của nó, bằng cách thay đổi một trong các phương thức http, chúng tôi sẽ phá vỡ tất cả các phần phụ thuộc http khác bên trong cơ sở mã một cách hiệu quả.

Vì chúng ta đang vận hành trong một ngôn ngữ được gõ động nên mọi thứ có thể không bị lỗi ngay lập tức và sẽ mặc định có hành vi không thể đoán trước, khiến việc gỡ lỗi trở thành một nhiệm vụ cực kỳ phức tạp. Trong các trường hợp sử dụng khác, có thể có hai bản vá khác nhau của cùng một thành phần trên cùng một thuộc tính, trong trường hợp đó, chúng tôi thực sự không thể dự đoán bản vá nào sẽ được ưu tiên hơn bản vá kia, dẫn đến mã thậm chí còn khó đoán hơn.

Điều quan trọng cần đề cập là việc vá khỉ có thể có những khác biệt nhỏ về hành vi giữa các ngôn ngữ lập trình khác nhau. Tất cả phụ thuộc vào thiết kế ngôn ngữ và lựa chọn thực hiện. Ví dụ: trong python, không phải tất cả các phiên bản sử dụng phương thức được vá sẽ bị ảnh hưởng bởi bản vá. Nếu một phiên bản gọi rõ ràng phương thức đã vá thì nó sẽ nhận được phiên bản cập nhật mới, ngược lại, các phiên bản khác có thể chỉ có thuộc tính trỏ đến phương thức đã vá và không gọi nó một cách rõ ràng sẽ nhận được phiên bản gốc, điều này là do cách python ràng buộc trong các lớp hoạt động.

Phần kết luận

Trong bài viết này, chúng tôi đã khám phá sự khác biệt cấp cao giữa các ngôn ngữ lập trình tĩnh và động, chúng tôi đã thấy các ngôn ngữ lập trình động có thể hưởng lợi như thế nào từ các mô hình và mẫu mới tận dụng tính linh hoạt vốn có mà các ngôn ngữ này mang lại. Ví dụ chúng tôi trình bày có liên quan đến việc vá lỗi Monkey, một kỹ thuật được sử dụng để mở rộng hành vi của mã mà không thay đổi nó từ nguồn. Chúng tôi đã thấy một trường hợp trong đó việc sử dụng kỹ thuật này sẽ mang lại lợi ích cùng với những hạn chế tiềm ẩn của nó. Phát triển phần mềm là sự đánh đổi và việc sử dụng giải pháp phù hợp cho vấn đề đòi hỏi nhà phát triển phải cân nhắc kỹ lưỡng cũng như hiểu biết tốt về các nguyên tắc và nguyên tắc cơ bản của kiến ​​trúc.


Career Services background pattern

Dịch vụ nghề nghiệp

Contact Section background image

Hãy giữ liên lạc

Code Labs Academy © 2024 Đã đăng ký Bản quyền.