Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

ต้นทุนจริงของ Metaprogramming: สิ่งที่คุณต้องรู้

Metaprogramming 聽起來很花哨,但它好嗎?

它可能有用,但許多人沒意識到使用 metaprogramming 是有代價的。

先讓我們在同一頁面上……

Metaprogramming 到底是什麼?

我將 metaprogramming 定義為使用任何方法:

  • 改變代碼結構(如 define_method)
  • 將字符串作為實際 Ruby 代碼運行(如 instance_eval)
  • 對某些事件做出反應(如 method_missing)

那麼 metaprogramming 的代價是什麼?我將它們分為 3 組:

  • 速度
  • 可讀性
  • 可搜尋性

注意: 你也可以說有第四組:安全性。原因是 eval 方法,它們不會對傳入的內容進行任何安全檢查。你必須自己做。

讓我們更詳細地探索每一個!

速度

第一個代價是速度,因為大多數 metaprogramming 方法比常規方法慢。

這裡有一些基準測試代碼:

 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 

結果 (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 

如你所見,兩種 metaprogramming 方法(define_method 和 method_missing) 都比普通方法慢不少。

這裡有個有趣的發現……

上面的結果來自 Ruby 2.2.4,但如果你在 Ruby 2.3 或 Ruby 2.4 上運行這些基準測試,似乎這些方法變得更慢了!

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 

我運行了這個基準測試好幾次以確保這不是偶然。

但如果你注意並看看每秒迭代次數 (i/s),似乎常規方法從 Ruby 2.3 開始變快了。這就是 method_missing 看起來慢得多的原因 🙂

可讀性

使用 instance_eval / class_eval 方法時,錯誤信息可能不太有幫助。

看看下面的代碼:

 class Thing class_eval("def self.foo; raise 'something went wrong'; end") end Thing.foo 

這將導致以下錯誤:

 (eval):1:in 'foo': 'something went wrong...' (RuntimeError) 

注意我們缺少文件名(它說 eval 代替)和正確的行號。好消息是有修復方法,這些 eval 方法接受兩個額外參數:

  • 文件名
  • 行號

使用內建常量 __FILE__ 和 __LINE__ 作為 class_eval 的參數,你將在錯誤信息中獲得正確信息。

例子:

 class Thing class_eval( "def foo; raise 'something went right'; end", __FILE__, __LINE__ ) end 

為什麼這不是預設值?

我不知道,但如果你打算使用這些方法,這是值得記住的 🙂

可搜尋性

Metaprogramming 方法使你的代碼搜尋性較差、文檔較難訪問(通過較差的文檔)且更難調試。

如果你在尋找方法定義,你無法用 CTRL+F(或你使用的任何快捷鍵)找到通過 metaprogramming 定義的方法,特別是如果方法名是在運行時構建的。

下面的例子使用 metaprogramming 定義了 3 個方法:

 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 

生成文檔的工具(如 Yard 或 RDoc) 找不到這些方法並列出它們。

這些工具使用稱為"靜態分析"的技術來尋找類和方法。這項技術只能找到直接定義的方法(使用 def 語法)。

試著用最後一個例子運行 yard doc,你會看到找到的唯一方法是 create_post_tags。

它看起來像這樣:

ต้นทุนจริงของ Metaprogramming: สิ่งที่คุณต้องรู้

有一種方法告訴 yard 記錄額外的方法,使用 @method 標籤,但這並不總是實用。

例子:

 class Thing # @method build_report define_method(:build_report) end 

另外如果你要用 grep、ack 或編輯器搜尋方法定義,找 metaprogramming 方法會比常規方法難得多。

「我不認為 Sidekiq 使用任何 metaprogramming,因為我發現它在 95% 的情況下比幫助更能模糊代碼。」 — Mike Perham, Sidekiq 創始人

結論

Metaprogramming 不全是壞事。在正確的情況下它可能有用,使你的代碼更靈活。

只要意識到額外的代價,這樣你就能做出更好的決策。

如果你覺得有用別忘了 分享這篇文章 🙂