2014년 6월 10일 화요일

10억 달러 짜리 실수

만약 여러분이 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억 달러 짜리 실수를 만회할 해결책은 다음편에 계속…


  1. null 체크를 해서 ArgumentNullException을 던지는 것이나 체크 안해서 NullReferenceException이 저절로 발생하는 것이나 같은 예외인데 결과적으로 무슨 차이냐고 하실 분이 있을 것 같다. 왜 반드시 이렇게 짜야 하는지는 다른 글에서 설명하겠다.

댓글 2개:

  1. simply because it was so easy to implement.
    너무 재밌네요 :-)

    답글삭제
    답글
    1. 요새 만들었더라면 전세계 개발자들로부터 엄청난 비난과 악플에 시달렸을 것 같아요. ^^

      삭제

댓글을 입력하세요. 링크를 걸려면 <a href="">..</a> 태그를 쓰면 됩니다. <b>와 <i> 태그도 사용 가능합니다.

게시한지 14일이 지난 글에는 댓글이 등록되지 않습니다. 날짜를 반드시 확인해 주세요.