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ụ:
1 2 3 4 5 6 7 8 9 | @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:
1 2 3 4 5 6 7 8 9 10 11 | 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é:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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.
refactoring-conditionals-1">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?
1 2 3 4 5 6 | 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é:
1 2 3 4 5 6 7 8 9 10 11 | 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 lai.
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
.
Hãy xem một ví dụ:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | require 'socket' class MailSender def initialize @sent_messages = [] end def send_message(msg, recipient = "sample@email.com") 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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
Techtalk via viblo