Lập trình siêu âm nghe có vẻ là một từ rất lạ, nhưng liệu nó có tốt không?
Nó có thể hữu ích, nhưng nhiều người không nhận ra rằng việc sử dụng lập trình siêu hình có một số chi phí.
Chỉ để chúng ta ở trên cùng một trang…
siêu lập trình là gì chính xác?
Tôi định nghĩa lập trình siêu hình là sử dụng bất kỳ phương pháp nào:
- Thay đổi cấu trúc mã của bạn (như
define_method
) - Chạy một chuỗi như thể nó là một phần của mã Ruby thực của bạn (như
instance_eval
) - Làm điều gì đó như một phản ứng đối với một số sự kiện (như
method_missing
)
Vì vậy, chi phí của lập trình siêu hình là gì? Tôi phân loại chúng thành 3 nhóm:
- Tốc độ
- Khả năng đọc
- Khả năng tìm kiếm
Lưu ý :Bạn cũng có thể nói rằng có một nhóm thứ tư: Bảo mật . Lý do cho điều đó là
eval
các phương pháp này không thực hiện bất kỳ loại kiểm tra bảo mật nào đối với những gì đang được chuyển vào. Bạn phải tự mình thực hiện điều đó.
Hãy cùng khám phá chi tiết hơn từng điều đó!
Tốc độ
Cái giá phải trả đầu tiên là tốc độ vì hầu hết các phương pháp lập trình siêu hình đều chậm hơn các phương pháp thông thường.
Đây là một số mã đo điểm chuẩn:
require 'benchmark/ips' class Thing def method_missing(name, *args) end def normal_method end define_method(:speak) {} end t = Thing.new Benchmark.ips do |x| x.report("normal method") { t.normal_method } x.report("missing method") { t.abc } x.report("defined method") { t.speak } x.compare! end
Kết quả (Ruby 2.2.4) :
normal method: 7344529.4 i/s defined method: 5766584.9 i/s - 1.34x slower missing method: 4777911.7 i/s - 1.54x slower
Như bạn có thể thấy cả hai phương pháp lập trình siêu thị (define_method
&method_missing
) chậm hơn một chút so với phương pháp bình thường.
Đây là điều thú vị mà tôi đã khám phá ra…
Kết quả trên là từ Ruby 2.2.4
, nhưng nếu bạn chạy các điểm chuẩn này trên Ruby 2.3
hoặc Ruby 2.4
có vẻ như các phương pháp này ngày càng chậm hơn!
Kết quả điểm chuẩn của Ruby 2.4 :
normal method: 8252851.6 i/s defined method: 6153202.9 i/s - 1.39x slower missing method: 4557376.3 i/s - 1.87x slower
Tôi đã chạy điểm chuẩn này nhiều lần để đảm bảo rằng nó không phải là một điểm chuẩn.
Nhưng nếu bạn chú ý và nhìn vào số lần lặp lại mỗi giây (i/s
) Có vẻ như các phương thức thông thường đã nhanh hơn kể từ Ruby 2.3. Đó là lý do method_missing
trông chậm hơn rất nhiều 🙂
Khả năng đọc
Thông báo lỗi có thể ít hữu ích hơn khi sử dụng instance_eval
/ class_eval
phương pháp.
Hãy xem đoạn mã sau:
class Thing class_eval("def self.foo; raise 'something went wrong'; end") end Thing.foo
Điều này sẽ dẫn đến lỗi sau:
(eval):1:in 'foo': 'something went wrong...' (RuntimeError)
Lưu ý rằng chúng tôi thiếu tên tệp (nó cho biết eval
thay vào đó) &số dòng chính xác. Tin tốt là có một bản sửa lỗi cho điều này, eval
này phương thức có thêm hai tham số:
- tên tệp
- một số dòng
Sử dụng các hằng số tích hợp sẵn __FILE__
&__LINE__
làm tham số cho class_eval
bạn sẽ nhận được thông tin chính xác trong thông báo lỗi.
Ví dụ :
class Thing class_eval( "def foo; raise 'something went right'; end", __FILE__, __LINE__ ) end
Tại sao đây không phải là mặc định?
Tôi không biết, nhưng điều cần lưu ý nếu bạn định sử dụng các phương pháp này 🙂
Khả năng tìm kiếm
Phương pháp siêu lập trình làm cho mã của bạn ít tìm kiếm hơn, ít truy cập hơn (thông qua tài liệu kém hơn) và khó gỡ lỗi hơn.
Nếu bạn đang tìm kiếm một định nghĩa phương pháp, bạn sẽ không thể thực hiện CTRL + F (hoặc bất kỳ phím tắt nào bạn sử dụng) để tìm một phương thức được xác định thông qua lập trình siêu mẫu, đặc biệt nếu tên của phương pháp được tạo tại thời điểm chạy.
Ví dụ sau xác định 3 phương pháp sử dụng lập trình siêu hình:
class RubyBlog def create_post_tags types = ['computer_science', 'tools', 'advanced_ruby'] types.each do |type| define_singleton_method(type + "_tag") { puts "This post is about #{type}" } end end end rb = RubyBlog.new rb.create_post_tags rb.computer_science_tag
Các công cụ tạo tài liệu (như Yard
hoặc RDoc
) không thể tìm thấy các phương pháp này và liệt kê chúng.
Các công cụ này sử dụng một kỹ thuật được gọi là “Phân tích tĩnh” để tìm các lớp và phương thức. Kỹ thuật này chỉ có thể tìm thấy các phương thức được xác định trực tiếp (sử dụng def
cú pháp).
Thử chạy yard doc
với ví dụ cuối cùng, bạn sẽ thấy rằng phương pháp duy nhất được tìm thấy là create_post_tags
.
Có vẻ như thế này :
Có một cách để nói với yard
để ghi lại các phương pháp bổ sung, sử dụng @method
, nhưng điều đó không phải lúc nào cũng thực tế.
Ví dụ :
class Thing # @method build_report define_method(:build_report) end
Ngoài ra, nếu bạn định sử dụng một công cụ như grep
, ack
hoặc trình chỉnh sửa của bạn để tìm kiếm định nghĩa phương pháp, sẽ khó hơn khi tìm phương pháp lập trình siêu thị hơn các phương pháp thông thường.
“Tôi không nghĩ Sidekiq sử dụng bất kỳ lập trình siêu hình nào bởi vì tôi thấy nó che khuất mã nhiều hơn là nó giúp ích cho 95% thời gian. ” - Mike Perham, Người tạo ra Sidekiq
Kết luận
Không phải mọi thứ đều xấu về lập trình siêu hình. Nó có thể hữu ích trong những trường hợp thích hợp để làm cho mã của bạn linh hoạt hơn.
Chỉ cần lưu ý về các chi phí phụ để bạn có thể đưa ra quyết định tốt hơn.
Đừng quên chia sẻ bài đăng này nếu bạn thấy nó hữu ích 🙂