Thursday, March 1, 2018

Refactor Ruby on Rails Code


Refactor Ruby on Rails Code

thỉnh thoảng, chúng ta không thích một đề nghị chức năng bởi cách dễ nhất để giải quyết vấn đề đó là viết bad code (mã xấu) và chúng ta không nghĩ ra được giải pháp nào khác trong đầu. Điều này có thể khiến các developer tìm thấy rất ít kết quả chuẩn y các trang Ruby Toolbox, github, developer blog, và StackOverflow kiêng kị một gem hoặc plugin hoặc code mẫu mà khiến chúng ta cảm thấy tốt hơn về chính mình.

Bạn hoàn toàn có thể viết bad code, Đôi khi code Rails xấu lại dễ dàng để refactor thành code đẹp hơn là một giải pháp tồi đã cài đặt trong thời kì gấp rút. Dưới đây là quy trình refactor Rails code tôi thích làm theo khi chỉnh sửa các vấn đề đối với các giải pháp nhất thời khủng khiếp của mình.

 

Views

Step 1. Bắt đầu với Views

ví thử chúng ta đang bắt đầu cài đặt 1 ticket cho 1 feature mới và khách hàng nói với chúng ta rằng: "Những vị khách truy cập hệ thống nên có thể xem được danh sách các project đang hoạt động trên trang chào mừng“.

Ticket này yêu cầu một sự đổi thay có thể nhìn thấy được, thành ra, nơi hợp lý để bắt đầu làm việc sẽ là trong phần Views. Vấn đề này rất đơn giản và là một vấn đề mà chúng ta đã được đào tạo để giải quyết nhiều lần. Tôi sẽ giải quyết nó theo cách được gọi là The Wrong Way và chứng minh làm thế nào để refactor giải pháp của tôi đối với các phần thích hợp. Giải quyết vấn đề The Wrong Way có thể giúp chúng ta vượt qua được cái bẫy của việc không biết giải pháp đúng đắn.

Để bắt đầu, giả tỉ chúng ta có một model được gọi là Project với một thuộc tính boolean là active. Chúng ta muốn có được một danh sách thảy các project mà có active bằng true, thành thử chúng ta có thể dùng Project.where (active: true), và lặp lại nó với mỗi block.app/views/pages/welcome.slim: ul.projects - Project.where(active: true).each do project li.project = link_to project_path(project), project.name 

Tôi biết bạn đang nói gì: "Điều đó sẽ không bao giờ pass một cuộc review code" hoặc "Khách hàng của tôi chắc chắn sẽ thải hồi tôi vì điều này". Đúng vậy, giải pháp này đã phá vỡ sự liên quan của mô hình Model-View-Controller, nó có thể dẫn đến việc gọi sai dữ liệu cái mà rất khó để theo dõi, và maintain trong tương lai. Nhưng hãy xem xét giá trị của việc thực hành nó theo The Wrong Way:
Bạn có thể đẩy sự đổi thay này lên staging trong vòng 15 phút.
ngoại giả, block này dễ nhớ.
Khắc phục sự cố Rails này là đơn giản (có thể được giao cho một junior developer).

Step 2. Partials

Sau khi thực hiện The Wrong Way, tôi cảm thấy tồi về bản thân mình và muốn tách biệt code xấu đó. Nếu sự thay đổi này rõ ràng chỉ là mối hệ trọng trong lớp Views, tôi có thể refactor đoạn code hổ hang của tôi thành một partial.app/views/pages/welcome.slim: = render :partial => 'shared/projects_list' app/views/shared/_projects_list.slim: ul.projects - Project.where(active: true).each do project li.project = link_to project_path(project), project.name 


Nó trông đã tốt hơn một chút. Rõ ràng, chúng ta vẫn đang mắc lỗi của một truy vấn Model trong View, nhưng ít ra khi một người bảo trì đến sau và thấy một partial khủng khiếp của tôi, họ sẽ có một cách giải quyết vấn đề mã Rails cụ thể. Sửa một phần nào đó không sáng dạ nhưng rõ ràng là xoành xoạch dễ dàng hơn so với việc sửa một giải pháp tồi đã cài đặt với nhiều lỗi trừu tượng.

Step 3. Helpers

Helpers trong Rails là một cách để tạo ra một DSL (Domain Specific Language) cho một section của Views. Bạn phải viết lại mã của mình bằng cách sử dụng content_tag thay vì slim hoặc HTML, nhưng bạn sẽ có được ích lợi khi được phép thao tác các cấu trúc dữ liệu mà không để các developer khác nhìn chằm chặp vào bạn trong 15 dòng mã views.

Nếu tôi được sử dụng helpers ở đây, tôi có thể refactor lại thẻ li:app/views/shared/_projects_list.slim: 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 

Controllers

Step 4. Chuyển nó tới Controllers

Có lẽ đoạn code khủng khiếp của bạn không chỉ là mối liên hệ của riêng Views. Nếu code của bạn vẫn còn bốc mùi, hãy tìm các truy bạn có thể chuyển từ Views sang Controllers.app/views/shared/_projects_list.slim: ul.projects - @projects_list.each do project = project_list_item(project) app/controllers/pages_controller.rb: def welcome @projects = Project.where(active: true) end 


Step 5. Controller Filters

Lý do rõ ràng nhất để chuyển di code vào before_filter hoặc after_filter của Controller là dùng cho đoạn code mà bạn đã lặp lại trong nhiều action của Controller. Bạn cũng có thể di chuyển code vào filter của Controller nếu bạn muốn tách riêng mục đích của action của controller khỏi các đề nghị trên views của bạn.app/controllers/pages_controller.rb: before_filter :projects_list def welcome end def projects_list @projects = Project.where(active:true) end 


Step 6. Application Controller

ví thử rằng bạn cần đoạn code của bạn để hiển thị trên mỗi trang, hoặc bạn muốn tạo một hàm Controller helper để có thể gọi trong mọi controllers, bạn có thể chuyển di hàm đó vào ApplicationController. Nếu những thay đổi là chung cho tất các trang, bạn cũng có thể nên đổi thay cả layout application của bạn.app/controllers/pages_controller.rb: def welcome end app/views/layouts/application.slim: 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 

The Models

Step 7. Model

Như phương châm của mô hình MVC đã chỉ ra: Fat Model, Skinny Controllers, và Dumb Views. Chúng ta mong đợi sẽ refactor lại tuốt mọi thứ có thể vào trong Model, và đúng là hồ hết các chức năng phức tạp cuối cùng sẽ trở nên các quan hệ giữa các model và các phương thức trong model. Chúng ta nên tránh định nghĩa các hàm format hoặc views trong Model, nhưng việc chuyển đổi dữ liệu thành các loại dữ liệu khác là được phép.

Trong tỉ dụ của chúng ta, phần tốt nhất để refactor vào model sẽ là mệnh đề where(active: true), mà chúng ta có thể biến thành một scope. dùng scope không chỉ giúp cho việc gọi đến nó dễ dàng hơn mà còn giúp chúng ta trong trường hợp chúng ta muốn thêm một thuộc tính mới như deleted hay outdated, chúng ta chỉ cần sửa scope này mà không phải tìm tất các mệnh đề where để sửa.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) 


Step 8. Model Filters

Chúng ta không có mục đích sử dụng cụ thể cho các bộ lọc before_save hoặc after_save của Model trong thí dụ này, nhưng bước tiếp theo tôi thường thực hiện là chuyển các chức năng từ các hàm Controller và các hàm Model thành các bộ lọc Model.

giả thử chúng ta có một tính chất khác, num_views. Nếu num_views > 50, project sẽ không hoạt động. Chúng ta có thể giải quyết vấn đề này trong View, nhưng đổi thay cơ sở dữ liệu trong View là không hợp. Chúng ta có thể giải quyết nó trong Controller, nhưng Controller của chúng ta nên "thin" nhất có thể! Nhưng chúng ta có thể giải quyết nó một cách dễ dàng trong 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 


Note: Bạn nên tránh việc gọi self.save trong Model filter, vì điều này gây ra sự kiện lưu đệ quy, và tầng thao tác cơ sở dữ liệu của vận dụng của bạn nên là Controller.

Step 9. Libraries

thỉnh thoảng, chức năng của bạn đủ lớn để có thể chuyển nó thành một thư viện riêng. Bạn có thể muốn chuyển di nó vào một file thư viện vị nó được tái dùng ở rất nhiều nơi, hoặc nó đủ lớn mà bạn muốn phát triển nó một cách biệt lập.

Bạn nên lưu các file thư viện trong thư mục lib/, nhưng khi chúng phát triển thêm, bạn có thể chuyển chúng thành một RubyGem! Một lợi thế lớn của chuyển di code của bạn vào thư viện là bạn có thể test thư viện biệt lập với model của bạn.

Dù sao, trong trường hợp của một danh sách Project, chúng ta có thể biện minh cho việc di chuyển scope :activetừ model Project vào một file thư viện và gọi nó lại vào 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 

Note: phương thức self.included được gọi khi một lớp Rails Model được cung cấp và truyền vào trong lớp scope như là biến k.


Kết luận

Trong chỉ dẫn refactor Ruby on Rails code này, chúng ta đã thực hiện chưa đến 15 phút và triển khai một giải pháp và đưa nó lên staging để người dùng test, sẵn sàng để được hài lòng vào bộ chức năng hoặc gỡ bỏ. Khi kết thúc quá trình refactor, chúng ta có một đoạn code mà có thể vượt qua cả quá trình review nghiêm ngặt nhất.

Trong quá trình refactor Rails của riêng bạn, bạn có thể bỏ qua một đôi bước nếu bạn tin cẩn vào việc làm như vậy (thí dụ: nhảy từ View tới Controller, hoặc Controller tới Model). Chỉ cần lưu ý về luồng code từ View sang Model.

No comments:

Post a Comment