자바스크립트로 코드를 짜다보면 예상하지 못한 동작으로 당황할 때가 있다.
마치 설계자가 지뢰를 깔아놓은 것처럼.
언젠가 서비스에 장애가 난 적이 있었는데, iOS에서 api 응답을 파싱하지 못했다. 사용자의 실시간 리뷰를 나타날 때 문자열을 처리하는 부분이 문제였다.
자바스크립트는 문자열을 나타낼 때 UTF-16 방식을 채택했다. 문자열 객체의 길이값 length는 그 안에 있는 16비트 코드 유닛값의 갯수를 그대로 나타낸다. 문자열 연산에서도 마찬가지다. 그러면 문제가 발생하는데, 대표적인게 이모지가 포함된 문자열이다.
"👩💻".length
> 5
"👩💻".split("")
> (5) ['\uD83D', '\uDC69', '', '\uD83D', '\uDCBB']
"👩💻".slice(0,1)
> '\uD83D'
"👩💻".slice(0,2)
> '👩'
"👩💻".slice(0,3)
> '👩'
"👩💻".slice(0,4)
> '👩\uD83D'
"👩💻".slice(0,5)
> '👩💻'
['\uD83D', '\uDC69'] === 👩
['\uD83D', '\uDC69', '', '\uD83D', '\uDCBB'] === 👩💻
라는 것을 알 수 있다. 그렇다면 뒤의 두 코드인
['\uD83D', '\uDCBB'] 는 맥북일까?
"👩💻".slice(3,5)
> '💻'
그렇다. 맥북을 하고 있는 소녀 이모지는 2개의 16비트 코드 소녀 이모지와 2개의 16비트 코드 맥북 이모지의 조합인 것이다. 가운데 공백을 ZWJ(ZERO WIDTH JOINER)라고 하는데 아래 페이지에서 이런 이모티콘(zwj sequences)에 대해 확인할 수 있다.
문제가 되는 코드는 실시간 리뷰를 특정 길이만큼 잘라 '...'을 붙혀주는 함수였는데 중간에 이모티콘이 애매하게 잘려 UTF-16 코드 유닛이 붙어버렸고 클라이언트에서 파싱에러를 낸 케이스였다.
문자열을 배열로 변환하면 유니코드를 온전히 인식한다고 해서 변경해주니 해결되었다.
["👩💻"].slice(0,1).join()
> '👩💻'
손이 많이 가는 언어이다.
자바스크립트에서 유니코드에 대해 중요한 개념은 문자열을 코드 단위의 시퀀스로 처리한다는 것이다. 이는 개발자들에게 혼란을 줄 수 있는데, 자바스크립트가 설계될 때부터 섬세하게 처리되지 못한 부분이 아닐까 생각한다. 열흘만에 만들어진 언어다보니 그럴수도 있겠다 싶지만.
자바가 그리워지는 날이었다.