12/08/2018, 14:38

Khi nào sử dụng freeze trong Ruby

Gần đây việc sử dụng #freeze trong Ruby khá phổ biến, nhưng taị sao lại thế thì điều này chưa được giải thích rõ. Bài viết này sẽ xem xét những lý do phổ biến nhất mà những developer dùng biến freeze. Tạo hằng số Trong Ruby, hằng số có thể thay đổi. Khá lạ đúng không, nhưng ta có thể kiếm chứng ...

Gần đây việc sử dụng #freeze trong Ruby khá phổ biến, nhưng taị sao lại thế thì điều này chưa được giải thích rõ. Bài viết này sẽ xem xét những lý do phổ biến nhất mà những developer dùng biến freeze.

Tạo hằng số

Trong Ruby, hằng số có thể thay đổi. Khá lạ đúng không, nhưng ta có thể kiếm chứng với đoạn code sau.

MY_CONSTANT = "foo"
MY_CONSTANT << "bar"
puts MY_CONSTANT.inspect # => "foobar"

Nhưng bằng cách sử dụng freeze, ta sẽ tạo được một hằng số thực sự, đó là hằng số không thể thay đổi, và khi cố gắng thay đổi nó ta sẽ nhận được thông báo lỗi như thế này.

MY_CONSTANT = "foo".freeze
MY_CONSTANT << "bar" # => RuntimeError: can't modify frozen string

Giảm việc cấp phát đối tượng

Một trong những cách tốt nhất để tăng tốc ứng dụng Ruby đó là giảm số lượng các object được tạo ra. Một trong những nguồn gây ra việc cấp phát đối tượng này đến từ các string được rải khắp trong ứng dụng của bạn.

Mỗi lần bạn sử dụng method log("foobar"), bạn sẽ tạo một đối tượng string. Nếu code của bạn gọi nó hàng nghìn lần 1 giây, có nghĩa là bạn đang tạo hàng nghìn string 1 giây.

May mắn thay, nếu chúng ta dùng freeze với các string này, Ruby sẽ chỉ tạo ra một đối tượng string và cache chúng lại cho lần sử dụng sau. Chỉ số benchmark cho thấy hiệu suất khi dùng freeze tăng khoảng 50% so với không dùng.

require 'benchmark/ips'

def noop(arg)
end

Benchmark.ips do |x|
  x.report("normal") { noop("foo") }
  x.report("frozen") { noop("foo".freeze)  }
end

# Results with MRI 2.2.2:
# Calculating -------------------------------------
#               normal   152.123k i/100ms
#               frozen   167.474k i/100ms
# -------------------------------------------------
#               normal      6.158M (± 3.3%) i/s -     30.881M
#               frozen      9.312M (± 3.5%) i/s -     46.558M

Sự tối ưu hóa trong Ruby >= 2.2

Ruby 2.2 hoặc hơn sẽ tự động freeze string khi sử dụng string làm key của hash

user = {"name" => "george"}

# In Ruby >= 2.2
user["name"]

# ...is equivalent to this, in Ruby <= 2.1
user["name".freeze]

Và trong Ruby 3 thì rất có thể tất cả string sẽ đều được tự động freeze. https://bugs.ruby-lang.org/issues/11473

Value objects & functional programming

Ruby không phải ngôn ngữ lập trình hàm, nhưng nhiều Rubyists bắt đầu viết theo phong cách của lập trình hàm. Nguyên lý chính của phong cách này là nên tránh các tác động bên ngoài, tức là đối tượng sẽ không thay đổi sau khi được khởi tạo. Bằng cách sử dụng freeze bên trong constructor, ta có thể đảm bảo đối tượng sẽ không thể thay đổi, và mọi tác động bên ngoài sẽ đều báo lỗi.

class Point
  attr_accessor :x, :y
  def initialize(x, y)
    @x = x
    @y = y
    freeze
  end

  def change
    @x = 3
  end
end

point = Point.new(1,2)
point.change # RuntimeError: can't modify frozen Point

Tham khảo http://blog.honeybadger.io/when-to-use-freeze-and-frozen-in-ruby/

0