Build Dumb, Refactor Smart: วิธีนวดปัญหาจาก Ruby on Rails Code
เผยแพร่แล้ว: 2022-03-11บางครั้ง ลูกค้าส่งคำขอคุณสมบัติที่เราไม่ชอบจริงๆ ไม่ใช่ว่าเราไม่ชอบลูกค้าของเรา แต่เรารักลูกค้าของเรา ไม่ใช่ว่าเราไม่ชอบคุณลักษณะนี้ คุณลักษณะที่ลูกค้าร้องขอส่วนใหญ่มีความสอดคล้องอย่างสมบูรณ์แบบกับเป้าหมายทางธุรกิจและรายได้ของพวกเขา
บางครั้ง เราไม่ชอบคำขอคุณลักษณะเพราะวิธีที่ง่ายที่สุดในการแก้ปัญหาคือการเขียนโค้ดที่ไม่ถูกต้อง และเราไม่มีโซลูชันที่สวยงามอยู่ในหัวของเรา สิ่งนี้จะส่งนักพัฒนา Rails หลายคนไปสู่การค้นหาที่ไร้ผลผ่าน RubyToolbox, GitHub, บล็อกของนักพัฒนา และ StackOverflow เพื่อค้นหาอัญมณีหรือปลั๊กอินหรือโค้ดตัวอย่างที่จะทำให้เรารู้สึกดีขึ้นเกี่ยวกับตัวเอง
Refactor Ruby บน Rails Code
ฉันมาที่นี่เพื่อบอกคุณ: ไม่เป็นไรที่จะเขียนโค้ดที่ไม่ถูกต้อง ในบางครั้ง โค้ด Rails ที่ไม่ดีนั้นง่ายต่อการจัดโครงสร้างใหม่เป็นโค้ดที่สวยงาม มากกว่าโซลูชันที่คิดไม่ดีซึ่งดำเนินการภายใต้วิกฤตเวลา
นี่คือกระบวนการรีแฟคเตอร์ของ Rails ที่ฉันชอบทำเมื่อต้องนวดปัญหาโดยใช้แถบคาดศีรษะอันน่ากลัวของฉัน:
สำหรับมุมมองอื่น นี่คือบันทึกการคอมมิต Git สำหรับคุณลักษณะที่ได้รับการปรับโครงสร้างใหม่ทีละขั้นตอน:
และนี่คือบทความที่น่าสนใจอีกบทความหนึ่งเกี่ยวกับการรีแฟคเตอร์ขนาดใหญ่จากเพื่อนร่วมงานในเครือข่าย Toptal
เรามาดูกันว่ามันทำอย่างไร
มุมมอง
ขั้นตอนที่ 1. เริ่มใน Views
สมมติว่าเรากำลังเริ่มต้นตั๋วสำหรับคุณลักษณะใหม่ ลูกค้าบอกเราว่า: "ผู้เยี่ยมชมควรสามารถดูรายการโครงการที่ใช้งานอยู่ได้ในหน้าต้อนรับ"
ตั๋วนี้ต้องมีการเปลี่ยนแปลงที่มองเห็นได้ ดังนั้นสถานที่ที่เหมาะสมในการเริ่มงานจะอยู่ใน Views ปัญหาตรงไปตรงมาและเป็นสิ่งที่เราทุกคนได้รับการฝึกอบรมเพื่อแก้ปัญหาหลายครั้ง ฉันจะแก้มัน The Wrong Way และสาธิตวิธีการจัดองค์ประกอบโซลูชันของฉันใหม่ในพื้นที่ที่เหมาะสม การแก้ปัญหา The Wrong Way สามารถช่วยให้เราเอาชนะการไม่รู้วิธีแก้ปัญหาที่ถูกต้อง
ในการเริ่มต้น สมมติว่าเรามีโมเดลชื่อ Project
ซึ่งมีแอตทริบิวต์บูลีนที่เรียกว่า active
เราต้องการรับรายการ Projects
ทั้งหมดที่ active
เท่ากับ true
ดังนั้นเราจึงสามารถใช้ Project.where(active: true)
และวนซ้ำกับ each
บล็อก
app/views/pages/welcome.haml: %ul.projects - Project.where(active: true).each do |project| %li.project= link_to project_path(project), project.name
ฉันรู้ว่าคุณกำลังพูดอะไร: "นั่นจะไม่มีวันผ่านการตรวจสอบโค้ด" หรือ "ลูกค้าของฉันคงจะไล่ฉันออกแน่สำหรับเรื่องนี้" ใช่ โซลูชันนี้ช่วยแยกข้อกังวลของ Model-View-Controller ออก ซึ่งอาจส่งผลให้เกิดการเรียกฐานข้อมูลที่หลงทางซึ่งติดตามได้ยาก และอาจกลายเป็นเรื่องยากที่จะรักษาไว้ในอนาคต แต่พิจารณาถึงคุณค่าของการทำ The Wrong Way :
- คุณสามารถรับการเปลี่ยนแปลงนี้ในการแสดงละครได้ภายใน 15 นาที
- หากปล่อยทิ้งไว้ บล็อกนี้จะแคชได้ง่าย
- การแก้ไขปัญหา Rails นี้ตรงไปตรงมา (สามารถมอบให้กับนักพัฒนารุ่นเยาว์)
ขั้นตอนที่ 2 บางส่วน
หลังจากทำ The Wrong Way ฉันรู้สึกแย่กับตัวเองและต้องการแยกรหัสที่ไม่ดีออก หากการเปลี่ยนแปลงนี้เป็นเพียงความกังวลของเลเยอร์ View อย่างชัดเจน ฉันก็สามารถปรับความอัปยศของฉันให้เหลือเพียงบางส่วนได้
app/views/pages/welcome.haml: = render :partial => 'shared/projects_list' app/views/shared/projects_list.haml: %ul.projects - Project.where(active: true).each do |project| %li.project= link_to project_path(project), project.name
ดีขึ้นนิดหน่อย เห็นได้ชัดว่าเรายังคงทำผิดพลาดในการสืบค้นแบบจำลองในมุมมอง แต่อย่างน้อยเมื่อผู้ดูแลเข้ามาในภายหลังและเห็นบางส่วนที่น่ากลัวของฉัน พวกเขาจะมีวิธีตรงไปตรงมาในการแก้ปัญหารหัส Rails โดยเฉพาะ การแก้ไขสิ่งที่เป็นนามธรรมที่โง่เขลานั้นง่ายกว่าการแก้ไขสิ่งที่เป็นนามธรรมที่บกพร่องและมีข้อบกพร่องเสมอ
ขั้นตอนที่ 3 ผู้ช่วย
Helpers in Rails เป็นวิธีสร้าง DSL (Domain Specific Language) สำหรับส่วนของ Views ของคุณ คุณต้องเขียนโค้ดใหม่โดยใช้ content_tag's แทน haml หรือ HTML แต่คุณได้รับประโยชน์จากการได้รับอนุญาตให้จัดการโครงสร้างข้อมูลโดยไม่ต้องให้นักพัฒนารายอื่นจ้องมองคุณด้วยโค้ดการดูที่ไม่ได้พิมพ์ 15 บรรทัด
ถ้าฉันจะใช้ตัวช่วยที่นี่ ฉันอาจจะปรับโครงสร้างแท็ก li
ใหม่
app/views/shared/projects_list.haml: %ul.projects - Project.where(active: true).each do |project| = project_list_item(project) app/helpers/projects_helper.rb: def project_list_item(project) content_tag(:li, :class => 'project') do link_to project_path(project), project.name end end
ผู้ควบคุม
ขั้นตอนที่ 4 ย้ายไปยังตัวควบคุม
บางทีรหัสที่น่ากลัวของคุณไม่ได้เป็นเพียงข้อกังวลในการดู หากโค้ดของคุณยังมีกลิ่นอยู่ ให้มองหาคำค้นหาที่คุณสามารถเปลี่ยนจาก Views เป็น Controllers ได้

app/views/shared/projects_list.haml: %ul.projects - @projects_list.each do |project| = project_list_item(project) app/controllers/pages_controller.rb: def welcome @projects = Project.where(active: true) end
ขั้นตอนที่ 5. ตัวกรองคอนโทรลเลอร์
เหตุผลที่ชัดเจนที่สุดในการย้ายโค้ดไปยังคอนโทรลเลอร์ before_filter
หรือ after_filter
คือสำหรับโค้ดที่คุณได้ทำซ้ำในการดำเนินการของคอนโทรลเลอร์หลายรายการ คุณยังสามารถย้ายโค้ดไปยังตัวกรอง Controller ได้หากต้องการแยกวัตถุประสงค์ของการดำเนินการ Controller ออกจากข้อกำหนดของมุมมองของคุณ
app/controllers/pages_controller.rb: before_filter :projects_list def welcome end def projects_list @projects = Project.where(active:true) end
ขั้นตอนที่ 6. ตัวควบคุมแอปพลิเคชัน
สมมติว่าคุณต้องการให้รหัสของคุณแสดงบนทุกหน้า หรือคุณต้องการให้ฟังก์ชันตัวช่วยตัวควบคุมพร้อมใช้งานสำหรับตัวควบคุมทั้งหมด คุณสามารถย้ายฟังก์ชันของคุณไปที่ ApplicationController หากการเปลี่ยนแปลงเป็นสากล คุณอาจต้องการแก้ไขเค้าโครงแอปพลิเคชันของคุณด้วย
app/controllers/pages_controller.rb: def welcome end app/views/layouts/application.haml: %ul.projects - projects_list.each do |project| = project_list_item(project) app/controllers/application_controller.rb: before_filter :projects_list def projects_list @projects = Project.where(active: true) end
นางแบบ
ขั้นตอนที่ 7 การ Refactoring ไปยัง Model
ตามคติของ MVC: Fat Model, Skinny Controllers และ Dumb Views เราคาดหวังให้ปรับโครงสร้างทุกอย่างที่เราสามารถทำได้ในแบบจำลอง และเป็นความจริงที่ฟังก์ชันที่ซับซ้อนที่สุดจะกลายเป็นการเชื่อมโยงแบบจำลองและวิธีการของแบบจำลองในที่สุด เราควรหลีกเลี่ยงการจัดรูปแบบ/ดูสิ่งต่าง ๆ ในโมเดลเสมอ แต่อนุญาตให้แปลงข้อมูลเป็นข้อมูลประเภทอื่นได้ ในกรณีนี้ สิ่งที่ดีที่สุดในการปรับโครงสร้างโมเดลคือส่วนคำสั่ง where where(active: true)
ซึ่งเราสามารถเปลี่ยนเป็นขอบเขตได้ การใช้ขอบเขตนั้นมีค่าไม่เพียงเพราะมันทำให้การเรียกดูสวยงามขึ้นเท่านั้น แต่ถ้าเราตัดสินใจที่จะเพิ่มแอตทริบิวต์เช่น delete
หรือ outdated
เราสามารถแก้ไขขอบเขตนี้แทนที่จะไล่ตามส่วนคำสั่ง where
ทั้งหมดของเรา
app/controllers/application_controller.rb: before_filter :projects_list def projects_list @projects = Project.active end app/models/project.rb: scope :active, where(active: true)
ขั้นตอนที่ 8 ตัวกรองแบบจำลอง
เราไม่มีการใช้งานเฉพาะสำหรับ after_save filters
before_save
ของ Model ในกรณีนี้ แต่ขั้นตอนต่อไปที่ฉันมักจะทำคือการย้ายฟังก์ชันการทำงานจาก Controllers และเมธอดของ Model ไปยังตัวกรอง Model
สมมติว่าเรามีแอตทริบิวต์อื่น num_views
หาก num_views > 50
โปรเจ็กต์จะไม่ทำงาน เราสามารถแก้ปัญหานี้ได้ใน View แต่การเปลี่ยนแปลงฐานข้อมูลใน View นั้นไม่เหมาะสม เราสามารถแก้ไขได้ในคอนโทรลเลอร์ แต่คอนโทรลเลอร์ของเราควรจะบางที่สุด! เราสามารถแก้ได้ง่ายๆใน Model
app/models/project.rb: before_save :deactivate_if_over_num_views def deactivate_if_over_num_views if num_views > 50 self.active = false fi end
หมายเหตุ: คุณควรหลีกเลี่ยงการเรียก self.save
ในตัวกรอง Model เนื่องจากสิ่งนี้ทำให้เกิดเหตุการณ์การบันทึกแบบเรียกซ้ำ และเลเยอร์การจัดการฐานข้อมูลของแอปพลิเคชันของคุณควรเป็น Controller อยู่ดี
ขั้นตอนที่ 9 ห้องสมุด
ในบางครั้ง คุณลักษณะของคุณมีขนาดใหญ่พอที่จะรับประกันว่าเป็นห้องสมุดของตัวเอง คุณอาจต้องการย้ายไปยังไฟล์ไลบรารีเนื่องจากมีการใช้ซ้ำในหลายๆ ที่ หรือมีขนาดใหญ่พอที่คุณจะต้องการพัฒนาแยกจากกัน
การจัดเก็บไฟล์ไลบรารีใน ไดเร็กทอรี lib/ เป็นเรื่องปกติ แต่เมื่อเติบโตขึ้น คุณสามารถถ่ายโอนไฟล์เหล่านั้นไปยัง RubyGem จริงได้! ข้อได้เปรียบที่สำคัญของการย้ายรหัสของคุณไปยังไลบรารีคือ คุณสามารถทดสอบไลบรารีแยกจากโมเดลของคุณ
อย่างไรก็ตาม ในกรณีของ Project List เราสามารถปรับย้าย scope :active
call จาก Project
model ไปเป็นไฟล์ไลบรารี่ และนำมันกลับมาที่ Ruby:
app/models/project.rb: class Project < ActiveRecord::Base include Activeable before_filter :deactivate_if_over_num_views end lib/activeable.rb: module Activeable def self.included(k) k.scope :active, k.where(active: true) end def deactivate_if_over_num_views if num_views > 50 self.active = false end end end
หมายเหตุ: เมธอด self.included
จะถูกเรียกเมื่อมีการโหลดคลาส Rails Model และส่งผ่านในขอบเขตคลาสเป็นตัวแปร k
บทสรุป
ในบทช่วยสอนการปรับโครงสร้าง Ruby on Rails นี้ เราใช้เวลาไม่ถึง 15 นาทีและนำโซลูชันไปใช้งานและจัดเตรียมไว้สำหรับการทดสอบผู้ใช้ พร้อมที่จะได้รับการยอมรับในชุดคุณลักษณะหรือนำออก เมื่อสิ้นสุดกระบวนการปรับโครงสร้างใหม่ เรามีโค้ดบางส่วนที่วางกรอบงานสำหรับการนำรายการที่เปิดใช้งานได้และแสดงรายการได้ไปใช้ในหลายโมเดล ซึ่งจะผ่านกระบวนการตรวจสอบที่เข้มงวดที่สุด
ในกระบวนการรีแฟคเตอร์ Rails ของคุณเอง อย่าลังเลที่จะข้ามขั้นตอนด้านล่างไปป์ไลน์หากคุณมั่นใจในการดำเนินการดังกล่าว (เช่น ข้ามจาก View to Controller หรือ Controller to Model) เพียงจำไว้ว่าการไหลของโค้ดมาจาก View to Model
อย่ากลัวที่จะดูโง่ สิ่งที่แยกภาษาสมัยใหม่ออกจากแอปพลิเคชันการแสดงเทมเพลต CGI แบบเก่าไม่ใช่ว่าเราทำทุกอย่าง ด้วยวิธีที่ถูกต้อง ทุกครั้ง—แต่คือการที่เราใช้เวลาในการจัดองค์ประกอบใหม่ นำกลับมาใช้ใหม่ และแบ่งปันความพยายามของเรา