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

Monkey-Patching อย่างปลอดภัย: 3 เทคนิคป้องกันความสับสน

Monkey Patching ตอนลอง Ruby ครั้งแรก מדהים คุณเพิ่มเมธอดเข้า core classes ได้เลย! ไม่ต้องเรียก Time.now.advance(days: -1) เขียน 1.day.ago แทนได้! ทำให้ Ruby อ่านเขียนสนุก จนกระทั่ง…

เจอบั๊กแปลกเพราะ patch เปลี่ยน Hash

สับสนว่าโค้ดไหนรันจริง ไม่ debug ได้ตอนพัง

และสุดท้ายค้นพบว่าปัญหาทั้งหมดมาจาก 6 เดือนก่อน ที่คุณ monkey-patched Enumerable เพื่อทำให้บรรทัดเดียวสั้นลง 5 ตัวอักษร

แต่ทางเลือกคืออะไร? ไม่มี convenience เลย? โค้ดแบบ GroupableArray.new([1, 2, 3, 4]).in_groups_of(2)? รอ blank? เข้า core Ruby ก่อนถึงได้จัดการ user input สวยๆ?

คุณไม่ว่าจะยอมแพ้ monkey patches แต่มันเขียน monkey patches ที่ ไม่ ทำให้คุณอยากไล่ตัวเองครั้งหน้าที่เห็นมันได้อย่างไร?

ใส่ใน Module

ตอน monkey patch คลาส อย่าแค่เปิดคลาสแล้ว塞 patch เข้าไป:

class DateTime def weekday? !sunday? && !saturday? end end 

ทำไมไม่?

  • ถ้า 2 libraries monkey-patch เมธอดเดียวกัน คุณจะแยกไม่ได้

    Patch ตัวแรกจะถูก overwrite หายไป

  • ถ้าเกิด error จะดูเหมือน error เกิดใน DateTime

    แม้จะจริงแต่ไม่ช่วยอะไร

  • ยากกว่าที่จะปิด monkey patches

    ต้อง comment ทั้ง patch หรือ skip require file

  • ถ้าลืม require 'date' ก่อนรัน patch คุณจะ redefine DateTime แทนที่จะ patch มัน

แทนนั้น ใส่ monkey patches ใน module:

module CoreExtensions module DateTime module BusinessDays def weekday? !sunday? && !saturday? end end end end 

วิธีนี้ จัดกลุ่ม monkey patches ที่เกี่ยวข้องรวมกัน ตอน error จะชัดเจนว่ามาจากไหน และคุณสามารถ include ทีละกลุ่ม:

# Monkey-patch DateTime จริงๆ DateTime.include CoreExtensions::DateTime::BusinessDays 

ถ้าไม่ต้องการ patch แล้ว comment บรรทัดนั้นออกไป

เก็บไว้ด้วยกัน

ตอน monkey patch core classes คุณเพิ่ม core Ruby APIs ทุกแอปที่มี core patches รู้สึกต่างกันหน่อย ดังนั้นคุณต้องมีวิธีเรียนรู้การเปลี่ยนแปลงเหล่านั้นอย่างรวดเร็วตอนเข้า codebase ใหม่ ต้องรู้ว่า monkey patches ของคุณอยู่ไหน

ผมฟอลโลว์ Rails convention ส่วนมาก Patches ไปใน lib/core_extensions/class_name/group.rb ดังนั้น patch นี้:

module CoreExtensions module DateTime module BusinessDays def weekday? !sunday? && !saturday? end end end end 

จะไปใน lib/core_extensions/date_time/business_days.rb

นักพัฒนาใหม่ใครก็ตาม browse ไฟล์ Ruby ใน lib/core_extensions ได้ และจะได้เรียนรู้ว่าคุณเพิ่มอะไรเข้า Ruby และพวกเขาจะ ใช้ เมธอดสะดวกใหม่ๆ ที่คุณเขียน แทนที่จะแค่รบกวน

คิดถึง Edge Cases

ผมไม่รู้ว่า Enumerable ไม่มี sum method บ่อยครั้งอยากเขียน [1, 2, 3].sum หรือ ["a", "b", "c"].sum หรือ [Article.new, Article.new, Article.new].sum… โอ้

ตอน monkey patch คลาส คุณมักคิดแค่อย่างหนึ่งที่อยากทำให้ง่าย อยากคำนวณ sum ของตัวเลข แต่ลืมว่า Arrays เก็บสิ่งอื่นได้

ตอนนี้มีเหตุผล คุณ ไม่ค่อยคำนวณ average ของ hashes แต่ตอน attach เมธอดเข้า object ที่ fail ตอนเรียกบางครั้ง คุณจะสับสนเองในอนาคต

จัดการได้หลายวิธี จากดีไปหาแย่:

  • จัดการ unexpected input อย่างสมเหตุสมผล

    ใช้ได้ดีถ้า patch ทำงานกับ strings เรียก to_s ก่อนได้ผลสมเหตุสมผลจากทุกอย่าง และ Confident Ruby สอนวิธีจัดการ input หลากหลายมาก

  • จัดการ error ให้ชัดเจนขึ้น

    อาจแค่ throw ArgumentError พร้อมข้อความดี ตอนเจอ input ที่ไม่คาดคิด อย่าพึ่งผู้อื่นเข้าใจ NoMethodError แบบสุ่ม

  • เขียนเอกสารประกอบ input ที่คาดหวังใน comment

    2 ตัวเลือกก่อนดีกว่า ถ้าไม่ตรวจสอบ edge cases ใน patch ได้ อย่างน้อยให้เขียนเอกสาร ไว้ให้ caller ที่ค้นพบว่า patch ของคุณทำปัญหา รู้ว่าคุณพยายามทำอะไร

Monkey Patch โปรดของผมตลอดกาล

สุดท้าย ผมอยากแบ่งปัน monkey patch โปรด: Hash#string_merge:

lib/core_extensions/hash/merging.rb
module CoreExtensions module Hash module Merging def string_merge(other_hash, separator = " ") merge(other_hash) {|key, old, new| old.to_s + separator + new.to_s} end end end end {}.string_merge({:class => "btn"}) # => {:class=>"btn"} h = {:class => "btn"} # => {:class=>"btn"} h.string_merge({:class => "btn-primary"}) # => {:class=>"btn btn-primary"} 

มันทำให้ attach CSS classes เข้า HTML elements สวยงามขึ้นมาก

Monkey Patching ที่มีเหตุผล

Monkey-patching core classes ไม่ได้แย่หมด ถ้าทำดี มันทำให้โค้ดรู้สึก יותר像 Ruby แต่นั่นเหมือนขอบคมของ Ruby ทุกอย่าง ต้องระวังเพิ่มเติมตอนใช้

ถ้าคุณเก็บ patches ไว้ด้วยกัน จัดกลุ่มเป็น modules และจัดการ unexpected cases monkey patches ของคุณจะปลอดภัยที่สุด

Monkey patch ที่ดีที่สุดที่ คุณ เคยเขียน (หรือเห็น) คืออะไร? คอมเมนต์บอกผมหน่อย!