Tránh Sử Dụng &:to_s Trong Ruby
Trong Ruby, việc sử dụng cú pháp rút gọn với các symbol làm đối số cho block, như &:to_s, rất phổ biến. Mặc dù cú pháp này trông ngắn gọn và thanh lịch, nhưng nó có thể gây khó hiểu cho người mới bắt đầu và không phải lúc nào cũng được hỗ trợ tốt bởi các IDE hay công cụ refactoring.
Gần đây, những tùy chọn trực quan hơn như it và _1 đã được giới thiệu, điều này đặt ra câu hỏi: liệu các nhóm có nên bắt đầu thống nhất phong cách của họ xung quanh những tùy chọn này không?
Bài viết này sẽ khám phá ý tưởng về việc cố tình tránh cú pháp block-pass với symbol và trình bày một cách tiếp cận thực tiễn bằng cách sử dụng RuboCop.
Sự Phát Triển Của Các Tham Số Block
Ruby đã trải qua nhiều giai đoạn phát triển liên quan đến tham số block.
ruby
# Tất cả các cách dưới đây đều trả về ["1", "2", "3"]
# Cách cơ bản nhất
[1, 2, 3].map { |i| i.to_s }
# Kể từ Ruby 1.9
[1, 2, 3].map(&:to_s)
# Kể từ Ruby 2.7 (Tham số Đánh số)
[1, 2, 3].map { _1.to_s }
# Kể từ Ruby 3.4 (Tham số `it`)
[1, 2, 3].map { it.to_s }
Cách Cơ Bản
Cách cổ điển yêu cầu phải chỉ định tên cho tham số block:
ruby
[1, 2, 3].map { |i| i.to_s }
Mặc dù tên biến không quan trọng, bạn vẫn cần phải cung cấp một tên.
Cách Sử Dụng Symbol#to_proc
Ruby 1.9 đã giới thiệu cú pháp rút gọn bằng cách sử dụng Symbol#to_proc:
ruby
[1, 2, 3].map(&:to_s)
Về mặt nội bộ, :to_s.to_proc hoạt động như sau:
ruby
sym = :to_s
blk = sym.to_proc
# Tương đương với:
# ->(obj, *args, **kwargs, &block) { obj.public_send(:to_s, *args, **kwargs, &block) }
blk.call(1) # => "1"
Khi được truyền như một block (&:to_s), Ruby sẽ tự động gọi to_proc và thực thi phương thức.
Tham Số Đánh Số và it
Sau đó, đã có Tham số Đánh số (_1) trong Ruby 2.7 và cú pháp it trong Ruby 3.4, cả hai đều thể hiện ý tưởng rằng “tên không quan trọng”.
ruby
[1, 2, 3].map { _1.to_s }
[1, 2, 3].map { it.to_s }
Tại Sao Nên Tránh Sử Dụng Symbol Block-Pass?
Động lực đứng sau các cú pháp mới này là để làm cho sự “không có tên” của tham số trở nên rõ ràng. Cả _1 và it đều giải quyết vấn đề này, và cá nhân tôi thích it — nó đọc tự nhiên, là bổ sung mới nhất và phù hợp với các xu hướng hiện tại của Ruby.
Vấn đề với Symbol#to_proc là các lập trình viên không sử dụng Ruby thường gặp khó khăn trong việc hiểu nó ngay lập tức. Ruby thú vị bởi vì nó cung cấp nhiều cách để viết cùng một thứ, nhưng nếu chúng ta muốn Ruby giữ được tính thân thiện với người mới và hấp dẫn, khả năng đọc hiểu là rất quan trọng.
Hơn nữa, _1 và it tích hợp tốt hơn với các IDE và công cụ refactoring.
Đó là lý do tại sao tôi tin rằng Symbol#to_proc nên được giới hạn ở các trường hợp mã golf hoặc niche và chúng ta nên ngừng sử dụng nó trong mã sản xuất hàng ngày.
Một Custom RuboCop Cop
Để thực thi ý tưởng này, dưới đây là một custom RuboCop cop. Nó sẽ đánh dấu các cách sử dụng như array.map(&:to_s) và đề xuất thay thế chúng bằng it (hoặc _1).
Nó hoạt động như một phiên bản ngược của Style::SymbolProc.
ruby
# Để kích hoạt custom cop này, thêm vào `.rubocop.yml`:
#
# require:
# - path/to/custom/cop/avoid_symbol_block_pass.rb
#
# Custom/AvoidSymbolBlockPass:
# Enabled: true
#
# --- Ví dụ vi phạm (NG) ---
# array.map(&:to_s)
# users.each(&:destroy)
#
# --- Được phép (OK) ---
# array.map { it.to_s }
# users.each { it.destroy }
class RuboCop::Cop::Custom::AvoidSymbolBlockPass < RuboCop::Cop::Base
MSG = "Tránh sử dụng Symbol#to_proc (`&:to_s`). Hãy cân nhắc sử dụng `it` hoặc `_1` thay thế."
def on_block_pass(node)
return unless node.children.first&.sym_type?
add_offense(node)
end
end
Kết Luận
- Ruby cung cấp nhiều cú pháp block, mỗi cú pháp đều có bối cảnh lịch sử riêng.
Symbol#to_proc(&:to_s) rất ngắn gọn, nhưng không thân thiện với người mới và không thân thiện với IDE.- Nên ưu tiên các lựa chọn rõ ràng hơn như
it(hoặc_1). - Sử dụng custom cop RuboCop để thực thi phong cách này trong đội của bạn.
Bằng cách chuyển hướng khỏi cú pháp symbol block-pass, chúng ta có thể làm cho các mã nguồn Ruby trở nên tiếp cận hơn, nhất quán hơn và dễ bảo trì hơn.