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

Ruby 변수 스코프와 바인딩(Binding) 객체 완벽 이해하기

스코프(scope)는 모든 Ruby 개발자가 반드시 이해해야 하는 핵심 개념입니다.

왜 그럴까요?

수많은 에러 메시지와 혼란이 바로 스코프에서 비롯되기 때문입니다.

그렇다면 스코프란 무엇일까요?

스코프란 특정 시점에 어떤 변수에 접근할 수 있는지를 의미합니다.

변수의 종류에 따라 스코프의 범위도 달라집니다.

스코프는 매우 좁을 수도 있고(지역 변수), 매우 넓을 수도 있습니다(전역 변수).

상태 변경(state mutation)과 이름 충돌 문제를 피하려면 가능한 한 가장 좁은 스코프를 사용하는 것이 좋습니다.

아래에서는 스코프가 Ruby 코드에 어떤 영향을 미치는지 구체적인 예제를 통해 살펴보겠습니다.

지역 변수 스코프

지역 변수는 가장 좁은 스코프를 가집니다.

특히 메서드 내부에서 정의된 지역 변수는 메서드가 반환되는 순간 더 이상 존재하지 않습니다.

예제를 보겠습니다:

a = 50

def apple
  a = 100

  puts a
end

apple을 호출하면 무엇이 출력될 것 같으신가요?

정답은 다음과 같습니다:

apple을 호출하면 항상 100이 출력됩니다.

메서드 외부에서 a50으로 정의되어 있다고 해도, 메서드 내부a 변수에는 아무런 영향을 주지 못합니다.

둘은 서로 완전히 다른 변수입니다.

스코프를 비눗방울이라고 상상해 보세요

첫 번째 a = 50은 하나의 비눗방울 안에 들어 있고, 메서드를 호출하는 순간—어떤 메서드든—새로운 텅 빈 비눗방울 속으로 들어가게 됩니다.

기존의 지역 변수는 새 비눗방울로 가져갈 수 없습니다.

그리고 메서드가 끝에 도달하면…

비눗방울이 터집니다.

방울 안에 있던 변수들은 사라지고 더 이상 접근할 수 없습니다.

지역 변수가 동작하는 방식이 바로 이것입니다.

인스턴스 변수 스코프

인스턴스 변수는 더 넓은 스코프를 가집니다.

구체적으로 말하면, 인스턴스 변수는 하나의 Ruby 객체 내부에서 데이터를 공유하기 위해 사용됩니다.

예제:

class Fruit
  def more_juice
    @quantity = 100
  end

  def less_juice
    @quantity = 50
  end
end

이 예제에서 @quantitymore_juiceless_juice 두 메서드에서 동일한 변수입니다.

즉, 메서드 간에 공유되는 값입니다.

하지만 클래스 외부에서, 그리고 서로 다른 Fruit 객체에서는 @quantity의 값이 각각 다릅니다.

예제:

orange = Fruit.new
apple  = Fruit.new

orange.more_juice
apple.less_juice

모든 객체는 자신만의 인스턴스 변수 집합을 가집니다.

따라서 이 예제에서 orange@quantity는 100이 되고, apple@quantity는 50이 됩니다.

사람마다 이름, 나이, 국적이 다른 것과 같은 이치입니다.

블록에서의 스코프 작동 방식

블록(block)은 스코프 관점에서 매우 흥미로운 특징을 가집니다.

비눗방울 비유를 다시 사용하자면, 블록은 현재 비눗방울의 지역 변수들을 새로운 방울로 가져옵니다.

그리고 그 변수들에 접근하거나 값을 변경할 수 있습니다.

예제:

a = []

3.times { a << 1 }

p a
# [1, 1, 1]

하지만…

비눗방울은 여전히 터지기 때문에, 블록 내부에서 새로 생성된 지역 변수들은 함께 사라집니다.

꽤 흥미롭지 않나요?

무슨 뜻인지 예제로 확인해 보겠습니다:

1.times { b = [1,2,3] }

b
# NameError

여기에 더해, 블록은 자신이 생성된 시점의 비눗방울까지 함께 가지고 다닙니다.

이러한 현상을 "클로저(closure)"라고 부릅니다.

기억하세요, 이 "비눗방울"이란 소스 코드의 특정 지점에서 접근 가능한 모든 변수들의 집합입니다.

즉, 스코프 자체를 하나의 객체로 감싼 것입니다.

Ruby에서는 이것을 Binding이라고 부릅니다.

Ruby 바인딩 활용법

이 글에서 마지막으로 소개할 개념은 바인딩(binding)입니다.

지금까지 이야기한 비눗방울들을 Ruby 객체, 즉 Binding 클래스의 객체 형태로 저장할 수 있습니다.

예제:

def banana
  a = 100

  binding
end

banana.class
# Binding

이 바인딩 객체가 바로 그 비눗방울입니다.

심지어 방울 안을 들여다보며 무엇이 들어 있는지 확인할 수도 있습니다.

예제:

banana.send(:local_variables)
# [:a]

Rails의 scope는 어떨까?

Rails에서의 scope는 Ruby의 스코프와는 전혀 다른 개념입니다.

그렇다면 Rails의 scope란 무엇일까요?

ActiveRecord 메서드들로 구성된 커스텀 데이터베이스 쿼리에 이름을 붙이는 기능입니다.

예제:

class User < ApplicationRecord
  scope(:with_email) { where.not(email: nil) }
end

User.with_email

이것을 Ruby의 스코프 개념과 혼동하지 않도록 주의하세요 🙂

마무리

지금까지 Ruby의 스코프와 바인딩 객체에 대해 알아보았습니다!

스코프는 특정 시점에 어떤 변수에 접근할 수 있는지를 결정한다는 점을 꼭 기억하세요.

비유하자면 비눗방울처럼, 지역 변수는 각자 자신만의 방울을 가지고, 객체는 인스턴스 변수를 담기 위한 또 다른 방울을 공유합니다.

읽어주셔서 감사합니다.