요즘은 #freeze
를 보는 것이 일반적입니다. 루비 코드에서 사용됩니다. 그러나 왜 동결이 사용되는지 완전히 명확하지 않은 경우가 많습니다. 이 게시물에서는 개발자가 변수를 고정할 수 있는 가장 일반적인 이유를 살펴보겠습니다. 각 이유를 설명하기 위해 Rails 코드베이스 및 기타 인기 있는 오픈 소스 프로젝트에서 예제 코드를 발췌했습니다.
불변 상수 만들기
Ruby에서 상수는 변경 가능합니다. 조금 복잡하지만 코드는 이해하기 쉽습니다. 여기에서 문자열 상수를 만들고 여기에 다른 문자열을 추가합니다.
MY_CONSTANT = "foo"
MY_CONSTANT << "bar"
puts MY_CONSTANT.inspect # => "foobar"
#freeze를 사용하여 실제로 일정한 상수를 만들 수 있습니다. 이번에는 문자열을 수정하려고 하면 RuntimeError가 발생합니다.
MY_CONSTANT = "foo".freeze
MY_CONSTANT << "bar" # => RuntimeError: can't modify frozen string
다음은 ActionDispatch 코드베이스의 실제 예입니다. Rails는 민감한 데이터를 "[FILTERED]" 텍스트로 대체하여 로그에서 숨깁니다. 이 텍스트는 고정된 상수에 저장됩니다.
module ActionDispatch
module Http
class ParameterFilter
FILTERED = '[FILTERED]'.freeze
...
객체 할당 줄이기
Ruby 앱의 속도를 높이기 위해 할 수 있는 가장 좋은 방법 중 하나는 생성되는 객체의 수를 줄이는 것입니다. 객체 할당의 성가신 소스 중 하나는 대부분의 앱에 흩어져 있는 문자열 리터럴에서 비롯됩니다.
log("foobar")와 같은 메소드 호출을 수행할 때마다 새로운 String 객체를 생성합니다. 코드가 이와 같은 메서드를 초당 수천 번 호출하면 초당 수천 개의 문자열을 생성(및 가비지 수집)한다는 의미입니다. 많은 오버헤드가 발생합니다!
다행히 Ruby는 우리에게 탈출구를 제공합니다. 문자열 리터럴을 고정하면 Ruby 인터프리터는 하나의 String 객체만 생성하고 나중에 사용할 수 있도록 캐시합니다. 고정된 문자열 인수와 고정되지 않은 문자열 인수의 성능을 보여주는 빠른 벤치마크를 작성했습니다. 약 50%의 성능 향상을 보여줍니다.
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
Rails 라우터를 보면 작동하는 것을 볼 수 있습니다. 라우터는 모든 웹 페이지 요청에 사용되므로 속도가 빨라야 합니다. 이는 고정된 문자열 리터럴이 많다는 것을 의미합니다.
# excerpted from https://github.com/rails/rails/blob/f91439d848b305a9d8f83c10905e5012180ffa28/actionpack/lib/action_dispatch/journey/router/utils.rb#L15
def self.normalize_path(path)
path = "/#{path}"
path.squeeze!('/'.freeze)
path.sub!(%r{/+\Z}, ''.freeze)
path.gsub!(/(%[a-f0-9]{2})/) { $1.upcase }
path = '/' if path == ''.freeze
path
end
Ruby의 내장 최적화>=2.2
Ruby 2.2 이상(MRI)은 해시 키로 사용되는 문자열 리터럴을 자동으로 고정합니다.
user = {"name" => "george"}
# In Ruby >= 2.2
user["name"]
# ...is equivalent to this, in Ruby <= 2.1
user["name".freeze]
그리고 Matz에 따르면 모든 문자열 리터럴은 Ruby 3에서 자동으로 고정됩니다.
링크:DevelopersMeeting20150820Japan - 문자열 리터럴은 Ruby 3.0에서 기본적으로 고정(불변)됩니다. https://t.co/4XcelftmSa
— 마츠모토 유키히로(@yukihiro_matz) 2015년 8월 20일
가치 개체 및 기능 프로그래밍
Ruby는 함수형 프로그래밍 언어가 아니지만 많은 Rubyists는 함수형 스타일로 작업하는 것의 가치를 보기 시작했습니다. 이 스타일의 주요 교리 중 하나는 부작용을 피해야 한다는 것입니다. 개체는 초기화된 후에 변경되어서는 안 됩니다.
생성자 내에서 freeze 메서드를 호출하면 객체가 절대 변경되지 않을 것임을 보장할 수 있습니다. 의도하지 않은 부작용으로 인해 예외가 발생합니다.
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