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

루비에서 nil을 사용하는 이유, 제대로 이해하기

친구와 루비(Ruby) 코드에 대해 이야기를 나누다가 메서드의 반환값에 대한 주제가 나온 적이 있습니다.

그리고 당연히 그 대화에서 빠지지 않는 것이 바로 nil이죠.

그날 밤 잠자리에 들기 전 문득 이런 생각이 들었습니다. 우리는 왜 nil을 사용하는 걸까?

다음 날 이 주제로 글을 써보면 좋겠다고 생각했고, 지금 그 글을 읽고 계십니다. 재미있게 읽으시면서 새로운 것을 배워가시길 바랍니다!

반환값과 예상되는 동작

select 같은 메서드를 호출하면 항상 배열(Array)을 반환받게 됩니다.

선택할 요소가 하나도 없는 경우에도 마찬가지입니다.

예시:

[1,2,3].select {}
# []

[1,2,3].select(&:even?)
# [2]

그런데 왜 이것이 중요할까요?

좋은 질문입니다!

항상 배열을 반환받는다는 것이 보장된다면, 실제로 다루고 있는 객체가 배열인지 일일이 확인하지 않고도 배열의 메서드(size, empty?, push 등)를 자유롭게 호출할 수 있습니다.

이것이 좋은 이유는 클래스 검사는 높은 결합도(coupling)로 이어지기 때문입니다.

더 유연한 코드를 작성하고 싶다면 '이 객체의 클래스가 무엇인지' 확인하는 작업을 최대한 피하세요.

단일 값 반환하기

자, 그렇다면 하나의 값만 반환하는 find 같은 메서드는 어떨까요?

요소를 찾으면 아무 문제 없이 동작합니다.

[1,2,3].find { |n| n == 2 }
# 2

하지만 조건에 맞는 요소가 없다면 find는 뭔가를 반환해야 합니다. false를 반환할 수도 있지만, 반환값에 대해 어떤 메서드를 호출하려던 참이라면 예외가 발생하게 됩니다.

바로 여기서 nil이 등장합니다. 우리가 nil을 사용하는 이유는 일반적인 Null Object에 가장 가까운 존재이기 때문입니다.

이전에 nil에 관해 쓴 글에서 보여드렸듯이, nil은 사실 to_i, to_s, to_a 같은 메서드를 가진 하나의 클래스입니다.

덕분에 Array(), String() 같은 메서드를 사용해 객체를 해당 타입으로 변환할 수 있습니다.

예시:

String(123)
# "123"

String(nil)
# ""

Array(nil)
# []

문자열 보간(string interpolation)에서도 마찬가지입니다.

"abc#{nil}dfg"

문자열 보간은 중괄호 안의 표현식 평가 결과에 to_s를 호출합니다. 즉, #{1 + 1}이 있으면 내부적으로 2.to_s로 변환되는 것이죠.

nil이 문제가 되는 순간

nil문제예상치 못한 곳에서 나타날 때 발생합니다. 분명 foo 메서드에 응답하는 어떤 객체일 거라고 생각했는데 실제로는 nil이라면, 악명 높은 NoMethodError 예외와 마주하게 됩니다.

이런 상황에서는 별도의 글에서 소개한 Null Object 패턴을 구현하는 것이 좋습니다. 그 외의 해결책으로는 기본값을 지정할 수 있는 fetch 메서드를 활용하거나, 기본값을 설정하는 것이 말이 되지 않는 상황이라면 과감히 예외를 발생시키는 방법도 있습니다.

정리

이번 글에서 우리는 nil을 사용하는 이유, nil 값에 메서드를 호출할 때 발생하는 예외를 피하는 방법, 그리고 더 확신에 찬 코드를 작성하는 방법을 배웠습니다.

이제 여러분의 코드를 살펴보고 타입 검사 코드(is_a? / kind_of? / ===)가 있는지 찾아보세요. 오늘 배운 내용을 활용하면 그 코드를 어떻게 개선할 수 있을까요?