ยินดีต้อนรับสู่ส่วนสุดท้ายของซีรี่ส์ Ruby on Rails Patterns และ Anti-Patterns เป็นการเขียนและค้นคว้าหัวข้อเหล่านี้ค่อนข้างมาก ในบล็อกโพสต์นี้ เราจะพูดถึงปัญหาที่พบบ่อยที่สุดที่ฉันพบเมื่อสร้างและจัดส่งแอปพลิเคชัน Ruby on Rails ตลอดหลายปีที่ผ่านมา
แนวคิดที่ฉันจะพูดถึงในที่นี้ใช้ได้กับเกือบทุกที่ในโค้ด ดังนั้นให้พิจารณาว่าเป็นแนวคิดทั่วไป ไม่ใช่สิ่งที่เกี่ยวข้องกับรูปแบบ Model-View-Controller หากคุณสนใจรูปแบบและการต่อต้านรูปแบบที่เกี่ยวข้องกับ Rails MVC คุณสามารถดูบล็อกโพสต์ Model, View และ Controller ได้
มาดูปัญหาทั่วไปและประเด็นสำคัญกันดีกว่า
วัตถุเห็นแก่ตัวและกฎแห่งความเสื่อม
The Law of Demeter เป็นฮิวริสติกที่มีชื่อมาจากกลุ่มคนทำงานในโครงการ Demeter แนวคิดก็คือว่าอ็อบเจ็กต์ของคุณใช้ได้ตราบใดที่พวกมันเรียกเมธอดทีละรายการและไม่โยงการเรียกเมธอดหลายรายการ ความหมายในทางปฏิบัติมีดังต่อไปนี้:
# Bad
song.label.address
# Good
song.label_address
ตอนนี้ song
วัตถุไม่จำเป็นต้องรู้ว่าที่อยู่มาจากไหนอีกต่อไป — ที่อยู่เป็นความรับผิดชอบของ label
วัตถุ. ขอแนะนำให้คุณโยงการเรียกเมธอดเพียงวิธีเดียว และทำให้ออบเจ็กต์ของคุณ 'เห็นแก่ตัว' เพื่อที่พวกเขาจะได้ไม่เปิดเผยข้อมูลทั้งหมดโดยตรงแต่ผ่านเมธอดของตัวช่วย
โชคดีที่ใน Rails คุณไม่จำเป็นต้องเขียนวิธีการช่วยเหลือด้วยตัวเอง คุณสามารถใช้ delegate
ผู้ช่วย:
def Label < ApplicationModel
belongs_to :song
delegate :address, to: :song
end
คุณสามารถลองใช้ตัวเลือกต่างๆ ที่ผู้รับมอบสิทธิ์ยอมรับได้ในเอกสารของผู้รับมอบสิทธิ์ แต่แนวคิดและการดำเนินการนั้นค่อนข้างง่าย เมื่อใช้กฎ Demeter คุณจะลดการมีเพศสัมพันธ์ทางโครงสร้าง ร่วมกับ delegate
. อันทรงพลัง คุณทำได้ในไม่กี่บรรทัดและมีตัวเลือกที่ยอดเยี่ยมรวมอยู่ด้วย
แนวคิดอื่นที่คล้ายกับกฎแห่งดีมิเตอร์มากคือหลักการความรับผิดชอบเดียว (หรือ SRP เรียกสั้นๆ ว่า SRP) มันระบุว่าโมดูล คลาส หรือฟังก์ชันควรรับผิดชอบส่วนเดียวของระบบ หรือนำเสนอในลักษณะอื่น:
รวบรวมสิ่งที่เปลี่ยนแปลงไปด้วยเหตุผลเดียวกัน แยกสิ่งที่เปลี่ยนแปลงด้วยเหตุผลที่แตกต่างกันออกไป
ผู้คนมักจะมีความเข้าใจเกี่ยวกับ SRP ที่แตกต่างกัน แต่แนวคิดก็คือการทำให้หน่วยการสร้างของคุณรับผิดชอบในสิ่งเดียว อาจเป็นเรื่องยากที่จะบรรลุ SRP เนื่องจากแอป Rails ของคุณมีการขยาย แต่ต้องระวังเมื่อทำการรีแฟคเตอร์
เมื่อเพิ่มคุณสมบัติและเพิ่ม LOC ฉันพบว่าผู้คนมักจะพยายามหาวิธีแก้ปัญหาอย่างรวดเร็ว มาดูวิธีแก้ไขด่วนกันดีกว่า
ฉันรู้จักผู้ชาย (คุณต้องการอัญมณีทับทิมนั่นไหม)
ย้อนกลับไปในวันที่ Rails เป็นประเด็นร้อน การทำงานร่วมกันแบบโอเพ่นซอร์สได้รับความนิยมอย่างมาก โดยมี Ruby gem ใหม่ปรากฏขึ้นทุกมุม (เช่นทุกวันนี้กับไลบรารี JavaScript ที่เกิดขึ้นใหม่ทั้งหมด แต่มีขนาดเล็กกว่ามาก):
👆 ข้อมูลจากจำนวนโมดูล
อย่างไรก็ตาม วิธีการทั่วไปก็คือการหาอัญมณีที่มีอยู่เพื่อแก้ปัญหาของคุณ
ไม่มีอะไรผิดปกติ แต่ฉันต้องการแบ่งปันคำแนะนำเล็กน้อยก่อนที่คุณจะตัดสินใจติดตั้งอัญมณี
ก่อนอื่น ให้ถามตัวเองด้วยคำถามเหล่านี้:
- คุณจะใช้คุณสมบัติของอัญมณีส่วนใด
- มีอัญมณีที่คล้ายกันที่ 'เรียบง่ายกว่า' หรือทันสมัยกว่านี้ไหม
- คุณสามารถใช้คุณลักษณะที่คุณต้องการได้อย่างง่ายดายและมั่นใจหรือไม่
ประเมินว่าการดำเนินการนี้คุ้มค่าหรือไม่ หากคุณไม่ได้วางแผนที่จะใช้คุณลักษณะอัญมณีทั้งหมด หรือหากการติดตั้ง Gem ซับซ้อนเกินไป และคุณเชื่อว่าคุณทำได้ง่ายกว่านี้ ให้เลือกโซลูชันที่กำหนดเอง
อีกปัจจัยหนึ่งที่ฉันพิจารณาคือพื้นที่เก็บข้อมูลของอัญมณีมีการใช้งานอย่างไร — มีผู้ดูแลที่ทำงานอยู่หรือไม่? ครั้งล่าสุดที่เผยแพร่เกิดขึ้นเมื่อใด
คุณควรระวังการขึ้นต่อกันของอัญมณีด้วย คุณไม่ต้องการถูกล็อกเป็นเวอร์ชันเฉพาะของการขึ้นต่อกัน ดังนั้นให้ตรวจสอบ Gemfile.spec
เสมอ ไฟล์. ศึกษาวิธีการระบุรุ่นอัญมณีของ RubyGems
ในขณะที่เรากำลังพูดถึงอัญมณี แต่ก็มีแนวคิดที่เกี่ยวข้องซึ่งฉันได้พบ:ปรากฏการณ์ 'ไม่ได้ประดิษฐ์ขึ้นที่นี่' (หรือ NIH) ที่ใช้กับโลก Rails/Ruby มาดูกันว่ามีอะไรบ้างในหัวข้อถัดไป
ไม่ได้ประดิษฐ์ขึ้นที่นี่ (คุณอาจต้องการอัญมณีทับทิมนั้นหรือไม่)
สองสามเหตุการณ์ในอาชีพการงานของฉัน ฉันมีโอกาสได้สัมผัสกับผู้คน (รวมถึงฉันด้วย) ตกหลุมรักกลุ่มอาการ 'ไม่ได้ประดิษฐ์ขึ้นที่นี่' แนวคิดนี้คล้ายกับ 'การคิดค้นล้อใหม่' บางครั้ง ทีมและองค์กรไม่เชื่อถือห้องสมุด (อัญมณี) ที่พวกเขาไม่สามารถควบคุมได้ การขาดความไว้วางใจอาจเป็นตัวกระตุ้นให้พวกเขาสร้างอัญมณีที่มีอยู่แล้วขึ้นใหม่
บางครั้งการได้สัมผัสกับ NIH อาจเป็นสิ่งที่ดี การสร้างโซลูชันภายในองค์กรนั้นยอดเยี่ยม โดยเฉพาะอย่างยิ่งหากคุณปรับปรุงให้ดีขึ้นเหนือโซลูชันอื่นๆ หากคุณตัดสินใจที่จะโอเพ่นซอร์สโซลูชัน นั่นอาจจะดียิ่งขึ้นไปอีก (ดู Ruby on Rails หรือ React) แต่ถ้าคุณต้องการที่จะคิดค้นวงล้อใหม่เพื่อประโยชน์ของมันอย่าทำอย่างนั้น ตัวล้อเองก็สวยดีอยู่แล้ว
หัวข้อนี้ค่อนข้างยุ่งยาก และหากคุณเคยตกอยู่ในสถานการณ์เช่นนี้ ให้ถามตัวเองด้วยคำถามเหล่านี้:
- เรามั่นใจหรือไม่ว่าเราสามารถสร้างโซลูชันที่ดีกว่าที่มีอยู่ได้
- หากโซลูชันโอเพนซอร์สที่มีอยู่แตกต่างไปจากที่เราต้องการ เราจะทำการสนับสนุนโอเพนซอร์สและปรับปรุงได้หรือไม่
- นอกจากนี้ เราสามารถเป็นผู้ดูแลโซลูชันโอเพนซอร์ซและอาจปรับปรุงชีวิตของนักพัฒนาจำนวนมากได้หรือไม่
แต่บางครั้ง คุณก็ต้องไปตามทางของตัวเองและสร้างห้องสมุดด้วยตัวเอง บางทีองค์กรของคุณอาจไม่ชอบให้สิทธิ์การใช้งานไลบรารีโอเพนซอร์ซ ดังนั้นคุณจึงถูกบังคับให้สร้างไลบรารีของคุณเอง แต่ไม่ว่าคุณจะทำอะไร ฉันขอบอกว่าอย่าสร้างวงล้อขึ้นมาใหม่
ไลฟ์การ์ดประจำการ (ยกเว้นการช่วยเหลือมากเกินไป)
ผู้คนมักจะช่วยเหลือข้อยกเว้นมากกว่าที่พวกเขาตั้งเป้าไว้
หัวข้อนี้เกี่ยวข้องกับโค้ดมากกว่าหัวข้อก่อนหน้าเล็กน้อย อาจเป็นเรื่องธรรมดาสำหรับบางคน แต่สามารถเห็นได้ในโค้ดเป็นครั้งคราว ตัวอย่างเช่น:
begin
song.upload_lyrics
rescue
puts 'Lyrics upload failed'
end
หากเราไม่ระบุข้อยกเว้นที่ต้องการจะช่วยเหลือ เราจะตรวจพบข้อยกเว้นที่เราไม่ได้วางแผนไว้
ในกรณีนี้ ปัญหาอาจจะอยู่ที่ song
วัตถุ nil
. เมื่อข้อยกเว้นนั้นถูกรายงานไปยังตัวติดตามข้อผิดพลาด คุณอาจคิดว่ามีบางอย่างผิดปกติในกระบวนการอัปโหลด ในขณะที่จริงๆ แล้ว คุณอาจประสบกับสิ่งที่แตกต่างไปจากเดิมอย่างสิ้นเชิง
ดังนั้น เพื่อความปลอดภัย เมื่อต้องช่วยเหลือข้อยกเว้น ตรวจสอบให้แน่ใจว่าคุณได้รับรายการข้อยกเว้นทั้งหมดที่อาจเกิดขึ้น หากคุณไม่สามารถรับข้อยกเว้นทั้งหมดได้ด้วยเหตุผลบางประการ การช่วยเหลือน้อยไปก็ดีกว่าการช่วยเหลือมากไป ช่วยเหลือข้อยกเว้นที่คุณรู้จักและจัดการกับผู้อื่นในภายหลัง
คุณถามมากเกินไป (แบบสอบถาม SQL มากเกินไป)
ในส่วนนี้ เราจะพูดถึงปัญหาด้านฐานข้อมูลและความสัมพันธ์ระหว่างการพัฒนาเว็บอื่น
คุณทิ้งระเบิดเว็บเซิร์ฟเวอร์ด้วยการสืบค้น SQL มากเกินไปในคำขอเดียว ปัญหานั้นเกิดขึ้นได้อย่างไร? อาจเกิดขึ้นได้หากคุณพยายามดึงข้อมูลหลายรายการจากหลายตารางในคำขอเดียว แต่สิ่งที่เกิดขึ้นบ่อยที่สุดคือปัญหาการสืบค้น N+1 ที่น่าอับอาย
ลองนึกภาพโมเดลต่อไปนี้:
class Song < ApplicationRecord
belongs_to :artist
end
class Artist < ApplicationRecord
has_many :songs
end
หากเราต้องการแสดงเพลงสองสามเพลงในแนวเพลงและศิลปินของพวกเขา:
songs = Song.where(genre: genre).limit(10)
songs.each do |song|
puts "#{song.title} by #{song.artist.name}"
end
โค้ดชิ้นนี้จะทริกเกอร์การสืบค้น SQL หนึ่งครั้งเพื่อรับสิบเพลง หลังจากนั้น แบบสอบถาม SQL เพิ่มเติมหนึ่งรายการจะถูกดำเนินการเพื่อดึงข้อมูลศิลปินสำหรับแต่ละเพลง นั่นคือทั้งหมดสิบเอ็ด (11) ข้อความค้นหา
ลองนึกภาพสถานการณ์ถ้าเราโหลดเพลงมากขึ้น - เราจะใส่ฐานข้อมูลไว้ภายใต้ภาระที่หนักกว่าเพื่อพยายามหาศิลปินทั้งหมด
หรือใช้ includes
จาก Rails:
songs = Song.includes(:artists).where(genre: genre).limit(10)
songs.each do |song|
puts "#{song.title} by #{song.artist.name}"
end
หลังจาก includes
ตอนนี้เราได้รับคำสั่ง SQL สองคำเท่านั้น ไม่ว่าเราจะแสดงกี่เพลงก็ตาม เรียบร้อยแค่ไหน
วิธีหนึ่งที่คุณสามารถวินิจฉัยการสืบค้น SQL มากเกินไปคืออยู่ในระหว่างการพัฒนา หากคุณเห็นกลุ่มของแบบสอบถาม SQL ที่คล้ายกันดึงข้อมูลจากตารางเดียวกัน แสดงว่ามีบางอย่างคาวเกิดขึ้นที่นั่น นั่นเป็นเหตุผลที่ฉันขอแนะนำอย่างยิ่งให้คุณเปิดการบันทึก SQL สำหรับสภาพแวดล้อมการพัฒนาของคุณ นอกจากนี้ Rails ยังรองรับบันทึกการสืบค้นแบบละเอียดซึ่งแสดงว่ามีการเรียกคิวรีจากที่ใดในโค้ด
หากการดูบันทึกไม่ใช่เรื่องของคุณ หรือคุณต้องการอะไรที่จริงจังกว่านี้ ให้ลองใช้การวัดประสิทธิภาพของ AppSignal และการตรวจจับการสืบค้น N+1 ที่นั่น คุณจะได้รับตัวบ่งชี้ที่ยอดเยี่ยมว่าปัญหาของคุณมาจากข้อความค้นหา N+1 หรือไม่ นี่คือลักษณะที่ปรากฏด้านล่าง:
สรุป
ขอบคุณที่อ่านชุดโพสต์บล็อกนี้ ฉันดีใจที่คุณเข้าร่วมกับฉันในการขับขี่ที่น่าสนใจนี้ โดยเราได้เปลี่ยนจากการแนะนำรูปแบบและการต่อต้านรูปแบบใน Rails ไปจนถึงการสำรวจว่ามีอะไรอยู่ในรูปแบบ Rails MVC ก่อนโพสต์บล็อกสุดท้ายนี้เกี่ยวกับปัญหาทั่วไป
ฉันหวังว่าคุณได้เรียนรู้มากหรืออย่างน้อยก็แก้ไขและสร้างสิ่งที่คุณรู้อยู่แล้ว อย่าเครียดกับการท่องจำทั้งหมด คุณสามารถปรึกษาซีรีส์ได้เสมอหากคุณมีปัญหาในพื้นที่ใด ๆ
คุณจะพบทั้งรูปแบบและการต่อต้านรูปแบบอย่างแน่นอนเพราะโลกนี้ (และโดยเฉพาะวิศวกรรมซอฟต์แวร์) ไม่เหมาะ ที่ไม่ควรทำให้คุณกังวลเช่นกัน
การเรียนรู้รูปแบบและการป้องกันรูปแบบจะทำให้คุณเป็นวิศวกรซอฟต์แวร์ที่ยอดเยี่ยม แต่สิ่งที่ทำให้คุณดียิ่งขึ้นไปอีกคือการรู้ว่าเมื่อใดควรทำลายรูปแบบและแม่พิมพ์เหล่านั้น เพราะไม่มีวิธีแก้ปัญหาที่สมบูรณ์แบบ
ขอขอบคุณอีกครั้งที่เข้าร่วมและอ่าน เจอกันใหม่ตอนหน้า — และเชียร์!
ป.ล. หากคุณต้องการอ่านโพสต์ Ruby Magic ทันทีที่ออกจากสื่อ สมัครรับจดหมายข่าว Ruby Magic ของเราและไม่พลาดแม้แต่โพสต์เดียว!