null 떠나고 만난 undefined

Posted on 05 October, 2021

Image by Bongkarn Thanyakij from Pixabay

얼마 전 진행했던 코드리뷰에서 undefined의 사용에 대한 문제로 이야기가 나왔어요. 개인적으로는 너무도 오랫동안 당연하게 nullundefined를 나름 명확하게 구분했었고, 각 사용처에 맞게 사용하고 있었기에 사실 이 부분에 관해서는 더 알아볼 생각도 없었습니다. 아이러니하게도 이야기가 나왔던 문제는 오해로 생긴 다른 문제였기에 쉽게 해결됐어요. 하지만 관련해서 검색하다가 생각지 못한 내용을 만나게 됩니다. null을 사용하지 말고 undefined만을 사용하자는 주장이었죠. 타입스크립트는 컨벤션에도 명시되어 있었습니다. "Use undefined, do not use null." 이건 예전에 봤었지만 그냥 지나쳤던 것 같아요. 제 고정관념이 너무 확고했거든요. 운이 좋았는지 때마침 보고 있던 더글라스 크록포트 형의 신간에서도 관련된 내용이 있어서 조금 더 생각해볼 수 있었어요. 하지만 책에서는 너무 가볍게 다뤄서 아쉬웠습니다.

이렇게 새롭지 않아 뻔하고 너무 당연했던 것들도 시간이 흐르면 그 패러다임이 달라질 수 있는 것 같아요. 무엇 하나 변하지 않는 게 없네요.

결론적으로 앞으로의 프로젝트에서는 코드베이스에서 null은 직접 사용하지 않고 undefined 만을 사용하는 것을 고려해 보려고 합니다. 특히 토이프로젝트에서는 당분간 null을 사용하지 않을려고 합니다. 뭔가 경험에 기반한 저만의 정답이 나올 때까지요.

고정관념

undefined을 생각하면 떠오르는 몇가지 특징 있죠.

  1. 객체에 없는 프로퍼티에 접근하면 undefined를 얻는다.
  2. 변수에 값을 초기화하지 않으면 undefined를 갖는다.
  3. undefined는 아직 존재(초기화) 하지 않은 상태를 표현하는 값이다.
  4. 그래서 직접적인 할당은 모순을 만든다. 값이 아직 존재하지 않은 상태의 값이니까 말이다. 예를 들어 할당하는 순간 값이 있는 건데 값이 undefined면 아직 존재하지 않는 것이다.
  5. undefined를 키워드로 취급하지 않는 예전 브라우저들이 있다. 그래서 undefined를 변수처럼 사용할 수 있어 순수한 undefined가 아닐 수도 있다.

그 외에도 많지만 undefinednull을 구분해서 사용하는 관점에서의 특징을 적어봤어요.

처음 이쪽 세계의 커리어를 쌓고 지금까지 일하면서 보고 느낀 것들에 의해 고정관념이 생긴 것 일 수도 있어요. undefined는 정의되기 전의 일종의 상태 같은 값이기에 빈 값이라는 명확한 의미를 갖는 null을 사용하는 것이 당연하다고 여겼죠. 애초에 이런 생각을 하게 된 이유가 무엇인지 모르겠어요. 내용을 어디서 봤는지도 기억이 안 나고 이렇게 정확하게 정의한 것을 본적이 있는지조차 가물가물합니다. 아무튼 제 머릿속엔 그렇게 박혀있었습니다.

  • undefined 는 아직 초기화되지 않은 값, 존재하지 않는다.
  • null 은 빈 값을 표현하는 값, 존재는 한다. (마치 빈 스트링 같이)

인터넷에서 제가 이해하고 있는 내용을 제대로 설명하는 이미지를 찾았어요.

0 vs null vs undefined
출처: Stefan Baumgartner

사실 null을 잘 구분해서 사용하긴 했지만 단점이 없는 것은 아니에요. null을 사용했기 때문에 undefinednull을 둘 다 확인해야 하는 경우도 있고요. null 은 타입이 object로 구분되기 때문에 실수를 유발할 수도 있습니다. undefined보다 null이 타이핑하기 짧은 건 사실이지만 애초에 자동으로 초기화된 undefined라는 기본 값을 사용하지 않으니 오히려 코드가 더 길어질 때도 있어요.

let value;

// vs

let value = null;

그래도 null대신 undefined를 사용한다는 건 생각을 해보지도 않았죠. 용도가 다르다고 생각했으니까요. 바꿔 말하면 코드에서 null을 사용하지 않는 것을 생각해본적이 없었죠. 용도가 있으니까요.

자바스크립트는 두 개네?

대부분의 프로그래밍 언어는 없음을 표현하는 값 혹은 타입을 갖고 있어요. 보통은 그게 하나죠. 자바나 c, c++, c# 등은 null을 사용하고요. (c는 \0도 사용하지만 null하고 같은 의미니까요.) lisp, objectiv-c, swift는 nil을 사용해요. 그밖에 다른 언어들도 많지만 그나마 조금 알고 있는 언어들만 생각해 봤어요. 네 아무튼 모두 하나에요.

rust 같이 null이 없는 경우도 있긴 하죠. 하스켈도 없어요. 함수형 프로그래밍을 적용한 언어일수록 null을 안 쓰려는 것 같아요. 함수형 언어의 특성상 null 을 두지 않는 방향이 더 맞긴 하겠네요. 불변성을 중시하니 나중에 변경될 수 있는 값 따위는 쓰고 싶지 않을 테니까요. 있거나 혹은 없거나 둘 중 하나겠죠. (함수형 프로그래밍은 잘 모르지만요...)

없음을 이용하는 언어에서는 대부분 한 가지 값을 사용하고 있고 nullundefined를 별도로 구분하지 않죠. 사실 이 부분도 전혀 인식하지 않고 있다가 이번에 인지하게 된 것 같아요. "아 자바스크립트는 두 개네?" 그게 문제라고 생각해 봤던 적은 없었습니다. 부정적인 상태를 뜻하는 값이 한 개 이상이라는 점은 Truthy와 Falsy한 값으로 뭉뚱그려진 암묵적인 형 변환이라는 편리함과 모호함을 동시에 선물했고 "잘" 사용하면 됨 아니면 네놈 탓이라는 무서운 압박을 가하는 양날의 검이었죠. 원인을 너무 과대하게 생각한 것 일 수도 있겠네요.

undefined 만을 사용해야하는 이유

그러면 왜 undefined 만을 사용해야 할까요? nullundefined 보다 나아서 일까요? undefined만 사용하자는 주장은 사실 둘 중 하나를 사용하자는 주장에 가깝습니다. 그래서 이 문제는 무엇을 사용하지 않는 것이 좋을까 있을까로 해석될 것 같습니다.

null은 개발자의 의도로 사용됩니다. 반면 undefined는 자바스크립트 엔진에 의해서 사용되죠

let something;

이렇게 변수를 선언할 때 초깃값을 주지 않는다면 something 이란 변수에는 undefined가 할당됩니다. 굳이 개발자가 명시적으로 할당하지 않아도 그렇게 되지요. 코드상에서 암묵적으로 값이 할당됐다는 설명보다는 이 코드를 실행하기 전에 실행 컨텍스트인 활성 객체(AO)가 새로 생성되고 함수 내부의 변수들을 수집하는 과정에서 코드에서 명시적으로 값을 할당하기 전까진 undefined값을 갖도록 하는 자바스크립트 엔진 내부 작업의 결과라고 설명하는 것이 더 정확합니다. 아무리 코드상에서 변수를 선언함과 동시에 바로 값을 할당했다 치더라도 동작은 우선 undefined값을 갖은 후에 값이 변경되는 형태니까요. 아무튼 일단 'undefined'가 무조건 사용됩니다.

엔진이 null이 아니고 undefined를 사용합니다. 적어도 이 과정에서는 엔진에게 undefined 대신 null을 사용하게 할 방법이 전혀 없습니다. undefined가 디폴트이자 대체 불가능한 값입니다. 함수에서 특별히 값을 리턴하지 않았을 때 undefined를 리턴한다는 점도 마찬가지 입니다.

자 여기서 첫번째 이유

"엔진이 null을 사용하지 않고 undefined를 사용하고 있습니다."

개발자의 의도로 null을 사용하게 된다면 falsy한 값을 확인해야 하는 상황에서 undefinednull 두 가지를 모두 명시적으로 확인해야 합니다. 예를 들어 number 변수의 경우 숫자 0 값과 null 값과 undefined를 모두 확인해야 하는 거죠.

두번째 이유

"null의 사용은 falsy한 값이 더 추가되는 것이다."

그리고 마지막으로 세번째 이유가 있습니다. 유명한 내용이죠.

"typeof 키워드를 사용하면 null의 타입을 object로 판별합니다."

이건 스펙이 의도한 것이 아닌 자바스크립트 상의 명백한 버그입니다. 버그임에도 바꿀 수 없는 것은 너무 오랜 기간 마치 표준인 것처럼 사용됐고 그래서 이것을 변경했을 때 기존 코드에 미치는 영향이 크기 때문에 울며 겨자 먹기로 그대로 둘 수밖에 없는 것이지요. 이 문제를 해결하기 위해선 typeof 와 함께 또 다른 타입 체크 키워드가 생겨야 할 판입니다. 즉 해결될 가능성이 적습니다.

object를 값으로 사용하는 변수에서 object의 부재로 null을 사용한다면 typeof로 변수를 판별해서 특정 작업을 할 때는 각별히 더 신경 쓰고 조심해야 합니다. 보통 이런 타입 체크 코드는 뭔가 더 조심하려고 만드는 코드인데 그 조심하려고 만드는 코드를 사용할 때 더 조심해야 하고 더 신경이 쓰인다는 것 자체가 참 아이러니한 거죠. null을 사용하지 않으면 이런 문제가 없습니다.

undefined만 사용했을 때의 문제점

첫 번째 문제undefined에 대한 문제입니다. undefined는 키워드임에도 불구하고 키워드로 취급하지 않아 식별자로 사용될 수 있었습니다.

function doSomething1(undefined) {
  console.log(undefined + 1);
}


function doSomething2() {
  var undefined = 0;
  console.log(undefined + 1);
}

요즘의 브라우저들은 이 문제가 해결돼서 undefined에 값을 할당할 수 없게 됐죠. 그 이전 브라우저를 지원해야 하는 경우라면 여전히 문제가 발생할 여지가 있습니다. 이게 문제인 것을 몰랐던 개발자도 ESLint 같은 정적 분석 도구의 도움으로 코드 베이스에 이런 말도 안 되는 코드를 만들진 않겠지만 런타임에 외부에서 장난질을 하는 경우는 막을 방법이 없겠죠.

두 번째 문제는 DOM API가 null을 사용한다는 점입니다. 문제라기보다는 자바스크립트 엔진이 undefined를 사용한다는 것을 핑계로 여기까지 왔는데 엥? DOM API에서는 null을 쓰잖아?의 뉘앙쓰입니다. querySelectorgetElementById 같이 DOM에서 엘리먼트를 탐색하는 API들은 찾는 대상을 발견하지 못했을 때 null을 리턴합니다.

세 번째 문제는 JSON에서는 undefined를 사용하지 못한다는 점입니다. 일반적으로 JSON에서는 옵셔널한 값이 없는 경우 null을 사용합니다. undefined은 JSON 스펙에서 사용할 수 없는 값입니다. 뭘 JSON까지 빡빡하게 고려하냐고 생각할 수 있겠지만 어쨌거나 코드에서 사용되니까요. 큰 문제는 없지만 찝찝했던 모호함을 없애고자 여기까지 왔어요. 시작부터 고려되는 예외는 없어야 할 것 같습니다.

undefined만 사용하기

undefined만 사용했을 때의 문제점이 세 가지가 있었습니다.

  1. undefined의 순수성
  2. DOM API의 null 사용
  3. JSON에서의 null

각 문제에 대한 대안을 생각해 볼게요.

undefined의 순수성

이 부분은 앞서 언급했듯 시간이 흐를수록 사라질 수 있는 문제입니다. 모던 브라우저에서는 undefinedundefined로 순수하게 보장되니까요. 사실 IE6 초창기 시절부터 개발해왔던 저도 undefined의 값이 변경되서 생겼던 문제는 겪어보지 못했던 것 같습니다. 악의적으로 의도로 문제가 될 순 있지만 어디까지나 그저 가능성만 있었던 거죠.

가장 이상적인 해결책은 이 문제가 발생하지 않도록 브라우저의 지원 범위를 높이는 것입니다. 브라우저의 범위를 정확히 특정하기 조금 애매한데 대충 ES6를 지원하기 전에 대부분 대응됐을 겁니다. 확실한 건 ES6를 지원하는 브라우저라면 문제없다는 점입니다. 그럼에도 불구하고 브라우저를 제한할 수 없는 상황이거나 조금이라도 더 조심하고 싶다면 순수한 undefined를 만들어 사용합니다.

const undef = (() => {})();

함수가 아무것도 리턴하지 않으면 사이드 이팩트로 undefined를 리턴한다는 점을 이용해서 순수한 undefined를 만들 수 있습니다. 이보다 더 순수한 undefined는 없죠 :)

DOM API의 null 사용

이점이 조금 귀찮습니다. 이쯤 와서 생각해 보면 왜 이것들은 null을 굳이 사용해야 했나라는 불만까지 생깁니다. 일례로 비교적 최근에 생긴 WeakRef에서 참조 객체가 존재하는지 확인하는 deref()메서드도 참조 객체가 존재하지 않을 때 undefined를 리턴합니다. null을 사용하지 않아요. 근데 왜 DOM API에서만 굳이 사용했냐는 거죠. 불평해서 뭐합니까. 아무튼 사용합니다. querySelector API 에서라도 바뀌었다면 더 좋았지 않았을까 생각해 보지만 또 그렇게 되면 그 나름대로 최악의 일관성이겠네요.

그래서 nullundefined로 변경해 주는 작업을 매번 할 수밖에 없습니다.

 const element = document.querySelector('#something') || undefined;

이건 딱히 대안이라고 볼 수 없겠지만 요즘은 래액트나 뷰같은 프레임웍을 사용하는 것이 보편화됐기 때문에 직접 DOM API에 접근할 일이 거의 없기도 합니다. 라이브러리나 범용 모듈 수준에서만 조금 거슬리겠네요.

JSON에서의 null

보통 API의 데이터에서 옵셔널한 값이 없는 것을 표현하는 값으로 null을 사용하도록 설계하는데요. JSON 스펙도 스펙이지만 없는 값은 아예 전달을 하지 않는 것이 좋습니다. 말 그대로 "없는" 것이니까요. 없는 값을 표현하기 위해 프로퍼티로 굳이 키와 함께 null을 사용하는 것보다는 아예 해당 프로퍼티를 전달하지 않는 것이 장점이 더 많습니다. 이건 명확한 장점이네요. 적어도 페이로드 용량이라도 줄일 수 있으니까요.

const badJsonPayload = {
 name: 'shiren',
 ownedHouse: null
}

const goodJsonPayload = {
 name: 'shiren'
}

그리고 어차피 실제로 저는 ownedHouse가 없으니까요. ㅜㅜ

마무리

새로운 기술을 공부하는 것도 중요하지만 확고하다고 생각한 것들에 다른 의견을 수용하고 새로운 시각으로 바라보며 좀 더 깊이 있고 풍성한 생각을 갖도록 노력하는 것도 중요한 것 같습니다. 이런 융통성을 갖기란 정말 힘들죠. 더글라스 크록포드의 신작 말미에는 이런 내용이 나옵니다. "과학은 장례식 한 번에 하나씩 발전한다 ." 새로운 패러다임이 나오고 바뀌는 속도에 비해 사람들이 패러다임을 받아들이고 수용하는 능력은 부족하기 때문에 사람이 죽어 세대가 바뀌어야 발전한다는 의미입니다. 개인적으로 이 책에서 제일 인상 깊었던(?) 부분이기도 합니다. 변하지 않는 확고한 생각들이 쌓이면 쌓일수록 꼰대가 되는 것이겠죠. 이 바닥에서 변하지 않은 것은 변하지 않는 것 따윈 없다는 사실인 것 같습니다.

Buy Me A Coffee

크리에이티브 커먼즈 라이선스이 저작물은 크리에이티브 커먼즈 저작자표시-비영리-변경금지 4.0 국제 라이선스에 따라 이용할 수 있습니다.

© Sungho Kim2021