Nil…
대체 무엇일까요?
nil은 루비(Ruby)에서 "빈 값" 또는 "기본값"을 나타내는 특별한 객체입니다. 또한 false처럼 동작하는 "falsy(거짓 같은)" 값이기 때문에, 조건문에서 사용하면 거짓으로 평가됩니다.
중요한 점:
nil 객체는 루비 전체에서 단 하나만 존재하며, object_id는 4입니다(64비트 루비에서는 8). 이것이 nil이 특별하게 취급되는 이유 중 하나입니다.
nil.object_id # 4
이제 더 깊이 들여다보겠습니다!
Nil 값은 어디에서 오는 걸까?
많은 메서드가 결과로 nil을 반환할 수 있습니다.
이는 값을 요청했지만 그 값이 존재하지 않을 때 발생합니다.
예시:
- 상품을 찾지 못한 경우
- 배열 인덱스가 범위를 벗어난 경우
- 해시(Hash)에 해당 키가 존재하지 않는 경우
이를 보여주는 코드입니다:
arr = [1,2,3] arr[5] # nil
우리는 호출한 메서드가 유효한 값을 반환하기를 기대하는데 nil이 반환되면 다양한 문제가 발생할 수 있습니다.
예를 들어 다음과 같은 NoMethodError 예외가 그렇습니다:
arr[5].size # NoMethodError: undefined method 'size' for nil:NilClass
이런 오류를 피하려면 메서드를 호출하기 전에 값이 nil인지 먼저 확인해야 합니다.
루비에서 Nil 검사하기
"undefined method for nil:NilClass" 오류를 만나고 싶지 않다면 아래 방법들을 활용해 보세요.
nil이 아닐 때만 메서드 호출하기:
if array[5] && array[5].size # do something... end
바로 이런 불편함 때문에 루비 2.3에서는 "안전 탐색(safe navigator)" 연산자(&.)가 도입되었습니다.
예시:
if arr[5]&.size # do something... end
nil을 검사하는 또 다른 방법은 nil? 메서드를 사용하는 것입니다:
if @bacon.nil? # ... end
또한 blank? 메서드를 사용할 수도 있습니다(레일즈에서만 제공).
다루는 객체에 따라서는 nil 검사 자체를 완전히 피할 수 있는 몇 가지 기법도 있습니다.
예를 들어 Hash와 Array 객체에는 fetch 메서드를 사용할 수 있습니다.
또는 직접 작성한 클래스에서 nil 대신 기본값을 반환하고 싶다면 "Null Object 패턴"을 활용할 수 있습니다.
Nil이 숨어 있는 다른 곳들
정의되지 않은 인스턴스 변수는 nil을 반환한다는 점을 기억해야 합니다.
@foo # nil
인스턴스 변수 이름을 잘못 입력했거나 초기화를 잊었을 때 이런 상황이 발생할 수 있으니 주의하세요!
nil을 발견할 수 있는 또 다른 곳은 puts와 print 메서드입니다.
이 두 메서드는 항상 nil을 반환합니다.
이 내용을 언급하는 이유는, 실제로 학생들이 다음과 같은 코드를 작성하는 것을 여러 번 보았기 때문입니다:
numbers = [1,2,3].map { |n| puts n * 2 }
그 결과 numbers는 [nil, nil, nil]이 됩니다. 이 경우 해결책은 간단히 블록 안에서 puts를 제거하는 것입니다.
NilClass 이해하기
다른 모든 루비 객체와 마찬가지로 nil도 고유한 메서드 집합을 가지고 있습니다.
Rubinius 소스 코드의 클래스 정의는 다음과 같습니다:
class NilClass
def to_s
""
end
def inspect
"nil"
end
def nil?
true
end
def to_a
[]
end
def to_f
0.0
end
def to_i
0
end
def to_c
Complex(0)
end
def to_h
{}
end
end
모든 to_something 계열 메서드가 빈 값(숫자의 경우 0)을 반환한다는 점에 주목하세요.
빈 해시, 빈 문자열, 빈 배열…
그 이유는 nil이 "아무것도 없음"을 나타내기 위해서입니다.
Null Object 패턴
nil에 대한 이야기에서 Null Object 패턴을 빼놓을 수 없습니다.
Sandi Metz는 훌륭한 강연 "Nothing is Something"에서, nil이 "아무것도 없음"을 의미하지만 더 구체적인 의미를 가질 수 있다고 지적합니다.
예를 들어, 상품을 찾는 메서드를 작성하면서 상품이 없을 때 기본 상품을 반환하고 싶다고 가정해 보겠습니다.
이럴 때 다음과 같은 null 상품을 반환할 수 있습니다:
class MissingProduct
def name
"Product not found"
end
end
이제 실제 상품 객체와 null 상품이 섞인 배열이 있더라도, 모든 상품은 name 메서드를 가지게 됩니다. 따라서 NoMethodError 예외가 발생하지 않습니다.
팁: 객체의 타입을 확인하지 않고도 모든 객체를 동일한 방식으로 다룰 수 있다면, 그것이 바로 다형성(Polymorphism)을 활용하는 것입니다.
이런 종류의 객체는 프레젠테이션 레이어(뷰)에서는 매우 유용하지만, 컨트롤러에서는 상황이 다릅니다. 컨트롤러에서는 원하는 데이터가 없을 때 경고 메시지 표시나 리디렉션 등 다른 동작을 수행해야 하기 때문입니다.
Truthiness(참/거짓 판정)
nil에 대해 알아야 할 또 하나의 사실은, false(역시 객체입니다)와 함께 오직 이 두 값만이 "falsy"로 평가된다는 점입니다.
루비에서 그 외의 모든 값은 불리언 문맥에서 true로 간주됩니다.
일부 언어에서는 숫자 0을 false로 취급하지만, 루비에서는 그렇지 않습니다:
if 0 puts 123 end # 123
이 코드는 "만약 0이 참이라면 123을 출력하라"는 의미이며, 실제로 123이 출력됩니다.
요약
이 글에서 nil은 "아무것도 없음"을 나타내는 하나의 루비 객체라는 것을 배웠습니다.
또한 nil과 false만이 루비에서 "falsy"한 두 가지 값이며, 그 외의 모든 것은 "truthy"하다는 것도 배웠습니다.
이 글이 도움이 되었다면 좋아하는 소셜 네트워크에 공유하는 것을 잊지 마세요!