만약 여러분이 C/C++/C#/자바 프로그래머라면 이 질문의 답을 한번 구해 보자: “null
이 필요한 이유가 뭐지?” 너무 당연한 질문인가? 정답은 아래 있으나 일단 30초간 생각할 시간.
.
.
.
.
.
.
.
.
.
.
null
이 필요한 첫번째 이유는 변수가 아직 초기화되지 않았음을 표시하기 위해서다. 예를 들어
Object obj;
라는 문장이 있을 때 우리는 obj
가 아직 초기화되지 않았다는 사실을 바로 알 수 있다. 그런데 이 문장 만으로는 우리가 일부러 초기화를 안하기로 한 건지 아니면 실수로 빼먹은 건지 헷갈릴 경우가 있다. 그래서
Object obj = null;
처럼 null
을 대입해 주면 일부러 초기화를 안했다는 점을 확실히 할 수 있다.
그리고 null
을 쓰면 얻을 수 있는 또 다른 장점은
Object obj;
DoSomethingWith(obj);
처럼 컴파일 에러가 나는 코드를 아래와 같이 고쳐서 컴파일되게 만들 수 있다는 점이다:
Object obj = null;
DoSomethingWith(obj);
그래서 이 두가지를 종합해 보면 오 이거 꽤 쓸모가 있구나 싶은데…
…사실은 정반대다. 왜냐 하면 초기화되지 않은 변수는 의도적으로 그랬건 실수로 그랬건 어디에도 쓸모가 없기 때문이다. 쓸모 없는 상태의 변수를 쓸모 없다고 표시하는 것 조차 쓸모없는 행동이기는 마찬가지다. 두번째는—이게 치명적인 문제인데—컴파일이 되지 말아야 할 엉터리 코드를 억지로 컴파일되게 만들 수 있다! 덕분에 우리는 레퍼런스를 파라미터로 받는 모든 메쏘드에 대해 아래처럼 일일히 null
체크를 해주어야만 하게 되었다:
public TextMatch Match(string text, string pattern) {
if (text == null)
throw new ArgumentNullException("text");
if (pattern == null)
throw new ArgumentNullException("pattern");
또는 내부 메쏘드의 경우:
private TextRange TryGetTextRange(string text, int hintLength, ...) {
Debug.Assert(text != null);
실제로 C/C++/C#/자바로 프로그램을 만들면 이런 단순 null
체크를 적게는 수십에서 많게는 수천개씩 도배해 주어야 한다1.
F# 언어의 창시자인 Don Syme이 소개한 사례에 의하면
one C# project had 3036 explicit null checks, where a functionally similar F# project had 27, a reduction of 112x in the total number of null checks.
기능적으로 비슷한 프로젝트였는데 C#으로 짠 쪽이 null
체크가 무려 112배나 많았다고 한다. 반면 버그는 F#으로 짠 쪽이 압도적으로 적었다.
이번에는 null
을 리턴하는 메쏘드에 관해 생각해 보자:
Object GetObject() {
...
return null;
}
완벽하게 컴파일되는 위의 메쏘드가 null
을 리턴했는지 안했는지를 확인하기 위해 우리는 GetObject()
를 호출하고 나서 반드시 리턴값을 체크해주어야 한다:
Object obj = GetObject();
if (obj == null) {
// 에러 처리
}
...
여기서 null
체크를 깜빡 잊으면—실제로 아주 흔한 실수다—프로그램은 어느 순간 NullReferenceException
을 토해 내면서 사망하고 말 것이다.
이러한 난처한 상황을 두고 1965년 null
의 개념을 최초로 만들어 낸 Tony Hoare 할아버지는 이렇게 말했다:
I call it my billion-dollar mistake.
나는 그걸 나의 십억 달러 짜리 실수라고 부른다.
왜냐하면
This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.
이것은 셀 수 없는 에러, 취약점, 시스템 크래시로 이어졌고, 그 결과 지난 사십 년간 십억 달러에 달하는 고통과 손해를 초래했다.
그럼 애시당초 왜 이런 쓸데없고 위험한 기능을 만들었냐 하면
simply because it was so easy to implement.
그냥 구현하기가 너무 쉬워서.
...네?!
이 10억 달러 짜리 실수를 만회할 해결책은 다음편에 계속…
null
체크를 해서ArgumentNullException
을 던지는 것이나 체크 안해서NullReferenceException
이 저절로 발생하는 것이나 같은 예외인데 결과적으로 무슨 차이냐고 하실 분이 있을 것 같다. 왜 반드시 이렇게 짜야 하는지는 다른 글에서 설명하겠다. ↩
simply because it was so easy to implement.
답글삭제너무 재밌네요 :-)
요새 만들었더라면 전세계 개발자들로부터 엄청난 비난과 악플에 시달렸을 것 같아요. ^^
삭제