Một số kỹ thuật Refactoring phổ biến

Trong quá trình code mình nhận thấy với những hàm được viết quá nhiều dòng, nhất là trong hàm đồng thời thực hiện nhiều chức năng thường gây ra nhiều vấn đề như:

  • Khó kiểm soát, khó tái sử dụng và dễ gây ra bug vì xử lý quá nhiều logic trong hàm
  • Mất nhiều thời gian để review làm ảnh hưởng đến tiến độ của dự án


Ngoài ra còn một số vấn đề nữa như bị comment, bị chê code ngu các kiểu 😂. Và để hạn chế được phần nào những vấn đề trên mình xin giới thiệu tới các bạn 3 kỹ thuật phổ biến thường được sử dụng để tái cấu trúc hàm mà không làm thay đổi hành vi của nó. Hãy cùng theo dõi và vận dụng các bạn nhé. Let's go!

Kỹ thuật 1: Extract Method

Hiểu nôm na kỹ thuật này là tách các đoạn mã trong một hàm cồng kềnh thành các hàm nhỏ hơn chỉ thực hiện một chức năng các bạn nhé 😀


Hãy xem một ví dụ:

@sold_items = %w(onions garlic potatoes)

def print_report
  puts "*** Sales Report for #{Time.new.strftime('%d/%m/%Y')} ***"
  @sold_items.each { |item| puts item }
  puts "*** End of Sales Report ***"
end

Chúng ta có thể tách phần xử lý ngày hiện tại thành một hàm riêng như sau:

def print_report
  puts "*** Sales Report for #{current_date} ***"
  @sold_items.each { |i| puts i }
  puts "*** End of Sales Report ***"
end

def current_date
  Time.new.strftime("%d/%m/%Y")
end

Bây giờ có vẻ đã dễ đọc hơn, nhưng chúng ta có thể tách thêm một số hàm nữa thử xem nhé:

def print_report
  print_header
  print_items
  print_footer
end

def print_header
  puts "*** Sales Report for #{current_date} ***"
end

def current_date
  Time.new.strftime("%d/%m/%Y")
end

def print_items
  @sold_items.each { |i| puts i }
end

def print_footer
  puts "*** End of Sales Report ***"
end

Có vẻ là dài hơn, nhưng mà các hàm nó tường minh nhìn dễ đọc hơn đúng không các bạn 😆

Don’t be afraid of small methods, they are good for your code.

Kỹ thuật 2: Refactoring Conditionals

Bạn cũng có thể cấu trúc lại các điều kiện phức tạp thành các phương thức để làm cho chúng dễ đọc hơn.


Hãy xem một ví dụ:

Bạn có thấy quen với cách đặt điều kiện thế này không 😅

def check_temperature
   temperature > 30 && (Time.now.hour >= 9 && Time.now.how <= 17)
   air_conditioner.enable!
end

Dễ thấy phần thứ 2 của điều kiện if không phải là dễ đọc, vì vậy chúng ta nên tách nó thành một phương thức nhé:

def check_temperature
  if temperature > 30 && working_hours
    air_conditioner.enable!
  end
end

def working_hours
  Time.now.hour >= 9 && Time.now.how <= 17
end

Dễ dàng để đọc hơn rồi đúng không. Điều này làm cho mọi thứ dễ dàng hơn nhiều đối với những người đọc mã này trong tương lại (bao gồm cả bạn 😆)

Kỹ thuật 3: Replace Method with Method Object

Đôi khi bạn có một phương thức lớn vượt khỏi tầm kiểm soát. Trong trường hợp này có thể khó tái cấu trúc vì các phương thức lớn có xu hướng có nhiều biến cục bộ (local variables). Một giải pháp là sử dụng kỹ thuật tái cấu trúc Method Object.

"Big methods are where classes go to hide." - Uncle Bob


Hãy xem một ví dụ:

require 'socket'

class MailSender
  def initialize
    @sent_messages = []
  end
  
  def send_message(msg, recipient = "[email protected]")
    raise ArgumentError, "message too small" if msg.size < 5
    
    formatted_msg = "[New Message] #{msg}"
    
    TCPSocket.open(recipient, 80) do |socket|
      socket.write(formatted_msg)
    end
    
    @send_messages << [msg, recipient]
    
    puts "Message sent."
  end
end

sender = MailSender.new
sender.send_message("testing")

Để thực hiện tái cấu trúc, chúng ta có thể tạo một lớp mới và chuyển các biến cục bộ (local variables) thành các biến đối tượng (instance variables). Điều này giúp chúng ta tái cấu trúc thêm mã này mà không phải lo lắng về việc truyền dữ liệu.


Đây là lớp MailSender sau khi được tái cấu trúc:

class MailSender
  def initialize
    @sent_messages = []
  end
  
  def deliver_message(message)
    send(message)
    @send_messages << message
    puts "Message sent."
  end
  
  def send(msg)
    TCPSocket.open(msg.recipient, 80) { |socket| socket.write(msg.formatted_msg) }
  end
end

Và đây là lớp mới mà chúng ta định nghĩa:

class Message
  attr_reader :msg, :recipient
  
  def initialize
    raise ArgumentError, "message too small" if msg.size < 5
    
    @msg = msg
    @recipient = recipient
  end
  
  def formatted_msg
    "[New Message] #{msg}"
  end
end

sender = MailSender.new
msg = Message.new("testing")

sender.deliver_message(msg)

Nhìn clear hẵn đúng không các bạn 😀

Phần kết luận

Trên đây chỉ là 3 trong số rất nhiều kỹ thuật để tái cấu trúc mã. Sử dụng các kỹ thuật này sẽ giúp bạn tuân thủ nguyên tắc Single Responibility Principle và giữ cho các lớp và phương thức của bạn luôn trong tầm kiểm soát.


Nếu bạn thích bài viết này, hãy chia sẻ nó với bạn bè của bạn để họ cũng có thể thưởng thức nó nhé 😀


Nguồn bài viết: Introduction to Refactoring


Bạn có thể xem thêm một số kỹ thuật khác ở đây:
https://refactoring.com/catalog
https://sourcemaking.com/refactoring


Happy Coding!