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

ค่าใช้จ่ายที่ซ่อนอยู่ของ Metaprogramming

การเขียนโปรแกรม Metaprogramming ดูเหมือนเป็นคำที่แปลกมาก แต่มันดีไหม

อาจมีประโยชน์ แต่หลายคนไม่ทราบว่าการใช้โปรแกรมเมตามีต้นทุนบางอย่าง

เพียงเพื่อให้เราอยู่ในหน้าเดียวกัน…

metaprogrammingคืออะไร ว่า?

ฉันกำหนด metaprogramming ว่าใช้วิธีใด ๆ ที่:

  • เปลี่ยนโครงสร้างของโค้ดของคุณ (เช่น define_method )
  • เรียกใช้สตริงราวกับว่าเป็นส่วนหนึ่งของโค้ด Ruby จริงของคุณ (เช่น instance_eval )
  • ทำบางสิ่งเพื่อตอบสนองต่อเหตุการณ์บางอย่าง (เช่น method_missing )

ดังนั้นค่าใช้จ่ายของ metaprogramming คืออะไร? ฉันแบ่งพวกเขาออกเป็น 3 กลุ่ม:

  • ความเร็ว
  • ความสามารถในการอ่าน
  • ความสามารถในการค้นหา

หมายเหตุ :คุณสามารถพูดได้ว่ามีกลุ่มที่สี่:ความปลอดภัย . เหตุผลก็คือ eval ซึ่งไม่ได้ทำการตรวจสอบความปลอดภัยใดๆ กับสิ่งที่ถูกส่งผ่าน คุณต้องดำเนินการเอง

มาสำรวจกันอย่างละเอียดกันเลย!

ความเร็ว

ต้นทุนแรกคือความเร็วเนื่องจากวิธีการเขียนโปรแกรมเมตาส่วนใหญ่ช้ากว่าวิธีปกติ

นี่คือโค้ดการเปรียบเทียบบางส่วน:

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

ผลลัพธ์ (ทับทิม 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

เหตุใดจึงไม่เป็นค่าเริ่มต้น

ฉันไม่รู้ แต่หากคุณจะใช้วิธีการเหล่านี้ โปรดจำไว้เสมอ 🙂

ความสามารถในการค้นหา

วิธีการเขียนโปรแกรมเมตาทำให้โค้ดของคุณค้นหาได้น้อยลง เข้าถึงได้น้อยลง (ผ่านเอกสารที่แย่กว่านั้น) และแก้ปัญหาได้ยากขึ้น

หากคุณกำลังมองหาคำจำกัดความของเมธอด คุณจะไม่สามารถทำ CTRL+F (หรือช็อตคัทที่คุณใช้) เพื่อค้นหาเมธอดที่กำหนดผ่านเมตาโปรแกรมมิงได้ โดยเฉพาะอย่างยิ่งถ้าชื่อเมธอดถูกสร้างขึ้นในขณะใช้งานจริง

ตัวอย่างต่อไปนี้กำหนด 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 ใช้เมตาโปรแกรมมิง เลยเพราะฉันพบว่ามันบดบังรหัสมากกว่าที่จะช่วย 95% ของเวลาทั้งหมด” – Mike Perham ผู้สร้าง Sidekiq

บทสรุป

ไม่ใช่ทุกอย่างที่ไม่ดีเกี่ยวกับ metaprogramming อาจมีประโยชน์ในสถานการณ์ที่เหมาะสมในการทำให้โค้ดของคุณมีความยืดหยุ่นมากขึ้น

เพียงตระหนักถึงค่าใช้จ่ายเพิ่มเติมเพื่อให้คุณสามารถตัดสินใจได้ดีขึ้น

อย่าลืม แชร์โพสต์นี้ ถ้าเห็นว่ามีประโยชน์ 🙂