Computer >> 컴퓨터 >  >> 프로그래밍 >> Ruby

Ruby 상수 완벽 가이드: 꼭 알아야 할 모든 것

Ruby에서 상수란 무엇일까요?

상수는 항상 대문자로 시작하는 변수의 한 종류입니다. 메타프로그래밍을 사용하지 않는 한, 메서드 외부에서만 정의할 수 있습니다.

상수는 원래 변경되지 않아야 하는 값을 담기 위해 사용되지만, 흥미롭게도 Ruby는 상수 변경 자체를 막지는 않습니다.

상수는 다음과 같이 생겼습니다:

FRUIT = "orange"

그렇다면 지금부터, 상수는 매우 중요한 주제이므로 이 글에서 하나씩 자세히 살펴보겠습니다!

상수 정의 방법

상수를 선언하는 데에는 특별한 기호나 복잡한 문법이 필요하지 않습니다. 단지 첫 글자를 대문자로 쓰기만 하면 됩니다.

다음은 모두 유효한 상수입니다:

ABC = 1
Goo = 2
Foo = 3

주목할 점은 메서드 내부에서는 상수를 정의할 수 없다는 것입니다.

메서드 안에 정의하면 다음과 같은 애매한 에러 메시지가 출력됩니다:

def the_method
  ABC = 1
end

# "dynamic constant assignment"

따라서 상수는 반드시 메서드 외부에 정의해야 합니다. 일반적으로는 클래스 최상단에 상수 정의를 모아두면 코드를 읽는 사람이 한눈에 파악할 수 있어 좋습니다.

class RubyBlog
  URL    = "rubyguides.com"
  AUTHOR = "Jesus Castello"
end

이렇게 정의한 상수는 클래스 메서드 내부에서 사용하거나, 클래스 외부에서는 아래와 같은 문법으로 접근할 수 있습니다:

p RubyBlog::AUTHOR

# "Jesus Castello"

상수의 스코프에 대해서는 뒤에서 더 자세히 다룰 예정이니 끝까지 읽어주세요!

Uninitialized Constant(초기화되지 않은 상수) 에러

Ruby 개발 중 자주 마주치게 되는 대표적인 에러가 바로 이것입니다:

puts Foo
# "uninitialized constant Foo (NameError)"

이 에러를 머릿속에서 "상수를 찾을 수 없음"이라고 번역하면 이해하기 쉬워집니다.

이 에러를 제대로 이해하기 위해 알아야 할 중요한 사실이 있습니다. 바로 Ruby의 클래스 자체가 상수라는 점입니다.

예시:

Array
String
Hash

이들은 모두 첫 글자가 대문자이기 때문에 상수입니다.

클래스를 정의한다는 것은 사실 Class 객체를 생성해서 상수에 할당하는 작업입니다. 그리고 그 상수가 곧 클래스 이름이 되는 것이죠.

왜 이것이 중요할까요?

"uninitialized constant" 에러가 발생하는 가장 흔한 원인은 해당 상수를 정의하고 있는 파일이나 gem을 require하는 것을 잊었기 때문입니다.

또는 단순히 상수 이름을 잘못 입력했을 가능성도 있습니다.

따라서 이 두 가지를 항상 유의하세요.

상수는 변경될 수 있다

앞서 언급했듯이, Ruby의 상수는 변경이 가능합니다.

예시:

ABC = 1
ABC = 2

다만 이 경우 다음과 같은 경고 메시지가 출력됩니다:

2: warning: already initialized constant ABC

프로그램은 여전히 정상적으로 동작하지만, 이런 경고는 피하는 것이 좋습니다.

상수 변경을 근본적으로 막을 방법은 없습니다. Ruby의 변수는 값을 담는 '컨테이너'가 아니라 객체를 가리키는 포인터, 즉 '레이블'에 불과하기 때문입니다.

가장 좋은 대안은 불변(immutable) 객체를 사용하는 것입니다.

예시:

AUTHOR = "Jesus Castello".freeze

AUTHOR << "o"
# RuntimeError: can't modify frozen String

관련 글: Ruby 가변성(mutability)과 freeze 메서드.

위 예시에서 AUTHOR 상수가 가리키는 대상 자체는 여전히 바꿀 수 있습니다. freeze가 보호해주는 것은 객체 자체의 수정뿐입니다.

상수 관련 메서드

상수를 다루기 위해 전용으로 제공되는 메서드들이 몇 가지 있습니다:

메서드 설명
constants 클래스에 정의된 상수들을 심볼 배열 형태로 반환합니다
const_get 상수의 값을 반환합니다. 심볼 또는 문자열을 인자로 받습니다
const_set 상수에 값을 설정합니다. 상수 이름(심볼)과 값, 두 개의 인자를 받습니다
const_missing method_missing의 상수 버전입니다
const_defined? 주어진 상수(심볼)가 정의되어 있으면 true를 반환합니다
remove_const 상수를 제거합니다
private_constant 상수를 비공개로 만들어, 클래스 외부에서 Class::ABC 문법으로 접근할 수 없게 합니다

이 메서드들을 활용하면 다양한 메타프로그래밍 트릭도 구현할 수 있습니다.

예시:

module Food
  class Bacon; end
  class Chocolate; end
end

puts "Classes defined inside #{Food}:"

puts Food.constants

또한 "Array" 같은 문자열을 사용해 실제 클래스를 가져올 수도 있습니다:

array_class = Object.const_get("Array")

다만 주의할 점이 있습니다. 문자열이 params 등 사용자 입력에서 온 것이라면 코드 인젝션 공격에 노출될 수 있습니다.

Rails에는 const_get을 대신 수행해주는 constantize 메서드가 있지만, 역시 별도의 보안 검사를 수행하지 않으니 유의해야 합니다.

치트 시트(Cheat Sheet)

Ruby 상수 완벽 가이드: 꼭 알아야 할 모든 것

Ruby 상수의 스코프

클래스 밖, 즉 코드의 최상위 레벨(top-level)에서 생성한 상수는 어디에서든 접근할 수 있습니다.

상수는 자식 클래스에서도 사용할 수 있습니다.

class A
  FOO = 1
end

class B < A
  def foo
    puts FOO
  end
end

B.constants

# [:FOO]

중첩된 모듈이나 클래스 바깥에서 정의된 상수 역시 중첩된 클래스 내부에서 접근 가능합니다.

module Food
  STORE_ADDRESS = "The moon"

  class Bacon
    def foo; puts STORE_ADDRESS; end
  end
end

fb = Food::Bacon.new
fb.foo

# "The moon"

모듈 믹스인(Mixin)

믹스인된 모듈의 상수 역시 사용할 수 있습니다:

module Mixin
  A = 123
end

class Product
  include Mixin

  puts A
end

# 123

주의할 점은 이 동작은 include할 때만 유효하며, extend를 사용하면 동작하지 않는다는 것입니다.

예시:

class Product
  extend Mixin

  puts A
end

#  uninitialized constant Product::A

추가로 알아둘 점:

모듈에서 include된 메서드를 호출하면, Ruby는 그 메서드가 정의된 위치를 기준으로 상수를 탐색합니다.

예시:

module Parent
  def print_value
    VALUE
  end
end

class Child
  include Parent

  VALUE = 1
end

# 동작함
p Child::VALUE

# uninitialized constant Parent::VALUE
p Child.new.print_value

꼭 기억해두세요!

모듈 중첩(Nesting)

마지막으로 중첩된 클래스(모듈도 동일)를 사용한 예시를 살펴보겠습니다.

class A
  FOO = 1
end

class A::B
  class C
    puts FOO
  end
end

여기서 A::B라는 표기를 일종의 축약형으로 사용했습니다. 하지만 문제는 클래스 CFOO에 직접 접근할 수 없다는 것입니다.

그렇기 때문에 다음과 같은 방식의 중첩을 사용하는 것이 좋습니다:

class A
  FOO = 1
end

class A
  class B
    class C
      puts FOO
    end
  end
end

첫 번째 예시에서도 ::A::FOO를 사용하면 상수에 접근할 수 있지만, 클래스 이름이 변경되면 에러가 발생합니다.

::A::FOO 문법이 동작하는 이유는 Ruby에게 최상위 스코프, 즉 ArrayString 같은 상수가 정의되어 있는 곳을 찾아보라고 지시하기 때문입니다.

요약

이번 글에서는 흥미로운 동작 방식을 가진 변수 타입인 Ruby 상수에 대해 배웠습니다. 상수의 값은 변경할 수 있지만, 그럴 경우 경고 메시지가 출력됩니다.

또한 클래스 이름 자체가 상수라는 사실, 그리고 사용자 입력과 함께 const_get을 사용하는 것은 피해야 한다는 점도 배웠습니다.

이 글이 도움이 되었다면 주변에 공유하는 것도 잊지 마세요. 더 많은 사람들이 상수의 동작 원리를 이해할 수 있을 것입니다.