Welcome to the last part of my Ruby on Rails Patterns and Anti-Patterns series เป็นการเดินทางนานเขียนและ research topics เยอะ ในโพสต์นี้ จะครอบคลุม common problems ที่เจอบ่อยตอน build และ ship Rails applications ตลอดปี
Ideas ที่จะพูด apply ได้เกือบทุกที่ในโค้ด ถือเป็น general ideas ไม่เกี่ยว MVC pattern ถ้าสนใจ patterns/anti-patterns ใน Rails MVC อ่าน Model, View, Controller blog posts
Let's jump into general problems และ takeaways
Selfish Objects และ Law of Demeter
Law of Demeter = heuristic ได้ชื่อจาก Demeter Project Idea: objects OK ถ้า call one method at a time ไม่ chain multiple method calls ใน practice:
# Bad song.label.address # Good song.label_address ตอนนี้ song object ไม่ต้องรู้ address มาจากไหน — address เป็น responsibility ของ label object Encourage chain only one method call ทำ objects 'selfish' ไม่ share full info ตรงๆ แต่ผ่าน helper methods
Luckily ใน Rails ไม่ต้องเขียน helper method ใช้ delegate helper:
def Label < ApplicationModel belongs_to :song delegate :address, to: :song end เล่น options ของ delegate ใน docs แต่วิดีและ execution เรียบง่าย ด้วย Law of Demeter ลด structural coupling พร้อม delegate powerful ทำได้ในบรรทัดน้อย options มาก
Idea ใกล้เคียง: Single-responsibility Principle (SRP) module/class/function ควร responsible สำหรับ single part ของ system อีกมุม:
Gather things that change for the same reasons. Separate things that change for different reasons.
Folks เข้าใจ SRP ต่างกัน แต่ idea = keep building blocks responsible for single thing อาจท้าทาย achieve SRP ตอน Rails app expand แต่ตระหนักตอน refactor
ตอนเพิ่ม features และเพิ่ม LOC มัก reach out for quick solution มาดู grabbing the quick fix
I Know a Guy (ต้องการ Ruby Gem นั้นจริงไหม?)
สมัย Rails hot topic มี boom open-source collaboration gems ใหม่โผล่ทุกมุม (เหมือน JS libraries ตอนแต่น้อยกว่า)
👆 Info from Module Counts
Common approach: หา existing gem แก้ปัญหา
ไม่ผิด แต่ advice ก่อนตัดสินใจ install gem:
ถามตัวเอง:
- จะใช้ features ของ gem สัดส่วนเท่าไหร่?
- มี gem คล้ายที่ 'simpler' หรือ up-to-date กว่า?
- Implement feature ที่ต้องการได้ง่ายและมั่นใจไหม?
Evaluate ว่า worth implement ถ้าไม่ใช้ whole gem features หรือ gem implementation ซับซ้อนเกิน คุณทำ simpler ได้ → custom solution
Factor อื่น: gem repository active มั้ย? Active maintainers? Last release เมื่อไหร่?
Watch out gem dependencies อย่าลocked specific version ของ dependency check Gemfile.spec consult RubyGems version specifying
เรื่อง gems มี related idea: 'Not Invented Here' (NIH) phenomenon ใน Rails/Ruby world มาดู next section
Not Invented Here (อาจต้องการ Ruby Gem จริงๆ?)
ใน career หลายครั้งเจอ people (รวมผม) ตก 'Not Invented Here' syndrome คล้าย 'reinventing the wheel' Teams/orgs ไม่ trust libraries (gems) ที่ควบคุมไม่ได้ Lack of trust → reinvent gem ที่มีอยู่แล้ว
NIH บางครั้งดี: in-house solution great หาก improve over existing solutions ถ้า open-source solution → even better (ดู Rails หรือ React) แต่ถ้า reinvent wheel เพราะอยาก reinvent → อย่าทำ Wheel itself pretty great already
Topic นี้ tricky ถ้าเจอสถานการณ์ถามตัวเอง:
- มั่นใจว่าเราทำ solution ดีกว่าที่มีอยู่?
- Existing open-source solution ต่างจากที่เราต้องการ → contribute open-source improve ได้ไหม?
- กลายเป็น maintainers ของ open-source solution improve developers หลายคนได้ไหม?
แต่บางครั้งต้อง go your own way สร้าง library เอง Org ไม่ชอบ licensing open-source library forced to build your own แต่ whatever you do avoid reinventing the wheel
Lifeguard on Duty (Over-rescuing Exceptions)
People tend to rescue exceptions มากกว่าที่ aimed for
Topic นี้ code-related มากกว่า previous ones Common sense บ้าง แต่เห็นในโค้ดบ้าง เช่น:
begin song.upload_lyrics rescue puts 'Lyrics upload failed' end ไม่ specify exception ที่จะ rescue → catch exceptions ที่ไม่ได้ plan
กรณีนี้ problem อาจเป็น song object = nil ตอน exception reported to error tracker คุณคิดว่า upload process ผิด แต่จริงๆ อาจเป็น issue ต่างหาก
Safe side: rescue exceptions ได้ list ของ exceptions ที่อาจเกิด ถ้าไม่ได้ทุก exception under-rescue ดีกว่า over-rescue Rescue exceptions ที่รู้จัก handle อื่นๆ later stage
You Ask Too Much (Too Many SQL Queries)
Section นี้: web development, relational-database problem
Bomb webserver ด้วย too many SQL queries ใน 1 request ยังไง? Fetch multiple records จาก multiple tables ใน 1 request แต่มักเกิดจาก infamous N+1 query problem
Models:
class Song < ApplicationRecord belongs_to :artist end class Artist < ApplicationRecord has_many :songs end อยาก show songs ใน genre และ artists:
songs = Song.where(genre: genre).limit(10) songs.each do |song| puts "#{song.title} by #{song.artist.name}" end Code นี้ trigger 1 SQL query ได้ 10 songs แล้ว 1 extra SQL query ต่อ song fetch artist = 11 queries total
Imagine load more songs → heavier load on database
ทางเลือก: ใช้ includes จาก Rails:
songs = Song.includes(:artists).where(genre: genre).limit(10) songs.each do |song| puts "#{song.title} by #{song.artist.name}" end หลัง includes ได้ 2 SQL queries เท่านั้น ไม่ว่าจะกี่ songs neat!
Diagnose too many SQL queries ใน development: เห็น group ของ similar SQL queries fetch จาก table เดียว → something fishy เปิด SQL logging ใน development environment รวมถึง Rails verbose query logs show query เรียกจากไหนในโค้ด
ถ้า logs ไม่ใช่เรื่องคุณ หรืออยาก serious ใช้ AppSignal performance measuring และ N+1 query detection ได้ excellent indicator
Sum Up
Thanks for reading blog post series Glad you joined interesting ride: introduce patterns/anti-patterns → explore ใน Rails MVC → final post general problems
หวังว่าเรียนรู้เยอะ หรืออย่างน้อย revise/establish what you know ไม่ต้อง stress memorize ทุกอย่าง Consult series ตอน trouble
คุณจะเจอ patterns และ anti-patterns เพราะโลก (และ software engineering) ไม่ ideal อย่ากลัว
Mastering patterns/anti-patterns ทำให้คุณ great software engineer แต่ทำให้ better: knowing when to break patterns/molds เพราะไม่มี perfect solution
Thanks again for joining and reading See you next time — cheers!
P.S. อยากอ่าน Ruby Magic posts ทันทีเผยแพร่? สมัครรับ Ruby Magic newsletter!