Build Dumb, Refactor Smart: วิธีนวดปัญหาจาก Ruby on Rails Code

เผยแพร่แล้ว: 2022-03-11
Daniel Lewis เป็นนักพัฒนา Ruby on Rails มืออาชีพมากว่า 4 ปี โดยทำงานเกี่ยวกับเว็บแอปพลิเคชั่นที่มีปริมาณการใช้งานสูงหลายสิบรายการ ซึ่งส่วนใหญ่ผ่าน Toptal

บางครั้ง ลูกค้าส่งคำขอคุณสมบัติที่เราไม่ชอบจริงๆ ไม่ใช่ว่าเราไม่ชอบลูกค้าของเรา แต่เรารักลูกค้าของเรา ไม่ใช่ว่าเราไม่ชอบคุณลักษณะนี้ คุณลักษณะที่ลูกค้าร้องขอส่วนใหญ่มีความสอดคล้องอย่างสมบูรณ์แบบกับเป้าหมายทางธุรกิจและรายได้ของพวกเขา

บางครั้ง เราไม่ชอบคำขอคุณลักษณะเพราะวิธีที่ง่ายที่สุดในการแก้ปัญหาคือการเขียนโค้ดที่ไม่ถูกต้อง และเราไม่มีโซลูชันที่สวยงามอยู่ในหัวของเรา สิ่งนี้จะส่งนักพัฒนา Rails หลายคนไปสู่การค้นหาที่ไร้ผลผ่าน RubyToolbox, GitHub, บล็อกของนักพัฒนา และ StackOverflow เพื่อค้นหาอัญมณีหรือปลั๊กอินหรือโค้ดตัวอย่างที่จะทำให้เรารู้สึกดีขึ้นเกี่ยวกับตัวเอง

บางครั้ง เราไม่ชอบคำขอคุณลักษณะเพราะวิธีที่ง่ายที่สุดในการแก้ปัญหาคือการเขียนโค้ดที่ไม่ถูกต้อง
ทวีต

Refactor Ruby บน Rails Code

ฉันมาที่นี่เพื่อบอกคุณ: ไม่เป็นไรที่จะเขียนโค้ดที่ไม่ถูกต้อง ในบางครั้ง โค้ด Rails ที่ไม่ดีนั้นง่ายต่อการจัดโครงสร้างใหม่เป็นโค้ดที่สวยงาม มากกว่าโซลูชันที่คิดไม่ดีซึ่งดำเนินการภายใต้วิกฤตเวลา

นี่คือกระบวนการรีแฟคเตอร์ของ Rails ที่ฉันชอบทำเมื่อต้องนวดปัญหาโดยใช้แถบคาดศีรษะอันน่ากลัวของฉัน:

กระบวนการรีแฟคเตอร์ Ruby on Rails นี้เป็นกระบวนการที่ฉันปฏิบัติตามเพื่อแก้ไขปัญหา Rails

สำหรับมุมมองอื่น นี่คือบันทึกการคอมมิต Git สำหรับคุณลักษณะที่ได้รับการปรับโครงสร้างใหม่ทีละขั้นตอน:

บันทึกนี้สาธิตการปรับโครงสร้าง Rails ที่ทำทีละขั้นตอน

และนี่คือบทความที่น่าสนใจอีกบทความหนึ่งเกี่ยวกับการรีแฟคเตอร์ขนาดใหญ่จากเพื่อนร่วมงานในเครือข่าย 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 :

  1. คุณสามารถรับการเปลี่ยนแปลงนี้ในการแสดงละครได้ภายใน 15 นาที
  2. หากปล่อยทิ้งไว้ บล็อกนี้จะแคชได้ง่าย
  3. การแก้ไขปัญหา 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 แบบเก่าไม่ใช่ว่าเราทำทุกอย่าง ด้วยวิธีที่ถูกต้อง ทุกครั้ง—แต่คือการที่เราใช้เวลาในการจัดองค์ประกอบใหม่ นำกลับมาใช้ใหม่ และแบ่งปันความพยายามของเรา

ที่เกี่ยวข้อง: การตัดทอนเวลา: A Ruby on Rails ActiveRecord Tale