+2

Refractoring bằng AntiPatterns trong Rails

Refractoring bằng AntiPatterns trong Rails

1. Giới thiệu

  • Trong quá trình phát triển ứng dụng, refractor luôn là công đoạn được ưu tiên hàng đầu để tối ưu các đoạn code, tăng khả năng maintain, dễ đọc, giảm độ phức tạp và tăng tính mở rộng cho hệ thống sau này

  • AntiPatterns là các cách tiếp cận phổ biến để giải quyết một vấn đề tuy nhiên lại dẫn đến không thực sự hiệu quả, nhưng AntiPatterns không phải là một thói quen tồi, một ý kiến tồi hay là một thực hành không tốt, mà đó là một khuôn mẫu lặp lại cho hành động, xử lý hay cấu trúc mà ban đầu thì hiệu quả nhưng lại sinh ra nhiều hậu quả tồi tệ hơn là các kết quả mong muốn.

  • Cuốn sách Rails AntiPatterns là một trong những cuốn sách rất hữu ích và nên đọc nhất cho tất cả những ai sử dụng Rails là nền tảng để phát triển các ứng dụng Web.

2. AntiPatterns trong Model

Trong Rails, Model là nơi chứa các logic chức năng của hệ thống, do đó là nơi nhận được sự quan tâm lớn nhất của đa số developer. Dưới đây là một vài vấn đề chung trong lớp Model.

Voyeuristic Models

Cả Ruby và Ruby on Rails đều sử dụng lập trình hướng đối tượng để xây dựng nên. Tuy nhiên trong thực tế phát triển, một lập trình viên có thể tạo một ứng dụng mà phá vỡ các nguyên tắc của một ngôn ngữ hướng đối tượng vì nhiều lý do. Để giải quyết vấn đề này, nâng cao khả năng đọc code và bảo trì code thì một vài giải pháp được đưa ra

Giải pháp 1: Law of Demeter

Law of Demeter là một đối tượng có thể gọi các hàm của một đối tượng khác nhưng không nên động đến một đối tượng liên quan thứ ba. Hay có thể gọi là chỉ sử dụng một dấu chấm.

Ta có các quan hệ:

class Address < ActiveRecord::Base
belongs_to :customer
end
class Customer < ActiveRecord::Base
has_one :address
has_many :invoices
end
class Invoice < ActiveRecord::Base
belongs_to :customer
end

Khi muốn hiển thị các thuộc tính của invoice:

<%= @invoice.customer.name %>
<%= @invoice.customer.address.street %>
<%= @invoice.customer.address.city %>,
<%= @invoice.customer.address.state %>
<%= @invoice.customer.address.zip_code %>

Thông qua quan hệ trong Ruby on Rails, ta dễ dàng làm được việc đó, tuy nhiên ở đây lại tiềm ẩn một vài vấn đề.

Theo tính chất đóng gói, thì invoice không nên động đến đối tượng customer để lấy ra các thuộc tính của đối tượng address. Bởi vì, ứng dụng có thể thay đổi, ví dụ như một customer có cả billing address và shipping address, khi đó mọi đoạn code mà có liên hệ với customer đều phải sửa lại.

Theo như giải pháp Law of Demeter, ta sẽ chỉ sử dụng một dấu chấm, tức là gọi trực tiếp đến method xử lý, trong Rails cung cấp một method là delegate, cung cấp một shortcut cho một hay nhiều method sẽ được tạo trong đối tượng đang sử dụng nhưng được xây dựng nên trong đối tượng khác.

class Customer < ActiveRecord::Base
has_one :address
has_many :invoices
delegate :street, :city, :state, :zip_code, :to => :address
end
class Invoice < ActiveRecord::Base
belongs_to :customer
delegate :name, :street, :city, :state, :zip_code, :to => :customer, :prefix => true
end

Khi đó ta viết lại

<%= @invoice.customer_name %>
<%= @invoice.customer_street %>
<%= @invoice.customer_city %>,
<%= @invoice.customer_state %>
<%= @invoice.customer_zip_code %>

AntiPattern: Fat Models

Để hình dụng một model sẽ phình to như thế nào, ta sẽ kiểm tra trong một ví dụ.

Model Order có các class method để tìm kiếm các order dựa theo trạng thái hay các điều kiện search phức tạp khác. Ngoài ra, nó còn bổ sung chức năng xuất một order bằng XML, JSON và PDF

# app/models/order.rb
class Order < ActiveRecord::Base
  def self.find_purchased
   # ...
  end
  def self.find_waiting_for_review
    # ...
  end
  def self.find_waiting_for_sign_off
    # ...
  end
  def self.find_waiting_for_sign_off
    # ...
  end
  def self.advanced_search(fields, options = {})
    # ...
  end
  def self.simple_search(terms)
    # ...
  end
  def to_xml
    # ...
  end
  def to_json
    # ...
  end
  def to_csv
    # ...
  end
  def to_pdf
    # ...
  end
end

Với tốc độ bổ sung chức năng như vậy, thì model Order sẽ nhanh chóng đạt 1000 dòng code Giải pháp: Tạo ra các Class mới Đây là phương pháp mà đa số các developer lạm dụng.

class Order < ActiveRecord::Base
  def to_xml
    # ...
  end
  def to_json
    # ...
  end
  def to_csv
    # ...
  end
  def to_pdf
    # ...
  end
end

Tuy nhiên, điều này dẫn đến một vi phạm nguyên tắc SRP(The single responsible priciple) hay còn gọi là không bao giờ có hơn một lý do cho một class để tồn tại.

Chúng ta có thể tách thành các class có mục đích duy nhất, thực hiện các chức năng duy nhất:

# app/models/order.rb
class Order < ActiveRecord::Base
def converter
OrderConverter.new(self)
end
end
# app/models/order_converter.rb
class OrderConverter
  attr_reader :order
  def initialize(order)
    @order = order
  end
  def to_xml
    # ...
  end
  def to_json
    # ...
  end
  def to_csv
    # ...
  end
  def to_pdf
    # ...
  end
end

Đây được gọi là tính chất composition.

3. Tổng kết


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.