자바스크립트에서 중괄호와 대괄호를 더했는데 0이 나왔다는 예를 보면, 계산 규칙부터 의심하기 쉽다. 그런데 같은 모양을 괄호로 감싸면 이번에는 문자열이 나온다. 이런 장면에서 먼저 적어 둘 것은 정답 하나가 아니라 실행한 자리다. 콘솔에 넣었는지, 파일의 맨 앞에 썼는지, 함수 인수로 전달했는지에 따라 파서가 읽는 문맥이 달라질 수 있다.

Sylwia Laskowska는 10월 29일 DEV 글에서 이 예를 문법 해석과 형 변환으로 나누어 설명했다. 핵심은 문장 위치의 {}가 빈 블록으로 읽힐 수 있다는 점이다. 여기서는 그 설명을 출발점으로 삼아, 이상한 출력 하나를 재현 가능한 기록으로 바꾸는 방법을 살펴본다. 개발 도구의 입력창마다 동작이 같다고 가정하지 않는 것이 첫 조건이다.
참고 원문: Sylwia Laskowska의 관련 원문
같은 글자도 같은 프로그램은 아니다
이 글을 준비하며 로컬 Node.js v26.0.0의 vm.runInNewContext로 짧은 예제 다섯 개를 실행했다. 각 문자열을 별도의 일반 Script 문맥에서 평가했다. 그 조건에서 {} + []는 숫자 0, ({} + [])는 문자열 [object Object]를 반환했다. [] + []는 빈 문자열, [] + {}는 문자열 [object Object]였다. 실제 결과와 실행 버전은 별도 확인 기록으로 남겼다.
첫 번째 예의 앞 중괄호는 빈 객체를 만드는 식이 아니라 빈 블록으로 해석된다. 뒤에 남은 +[]에서 더하기 기호는 두 값을 잇는 연산자가 아니라 하나의 값을 숫자로 바꾸는 단항 연산자다. 빈 배열은 원시 값 변환 과정에서 빈 문자열이 되고, 빈 문자열을 숫자로 바꾸면 0이 된다. 겉으로 보이는 더하기 한 번을 양쪽 값의 덧셈으로만 읽으면 이 경로를 놓친다.
반면 괄호 안의 {}는 객체를 나타내는 식이다. 객체와 배열을 더하는 과정에서 기본 객체 표현과 빈 문자열이 이어져 [object Object]라는 문자열이 된다. 중요한 차이는 객체가 갑자기 다른 수학 법칙을 따른다는 데 있지 않다. 앞의 기호가 어떤 문법 단위로 읽혔는지부터 달랐다. 출력만 캡처해 두면 이 차이를 다시 확인하기 어렵다.
오류 기록에 실행 자리를 붙이기
가령 팀 문서에 ‘빈 데이터를 더하면 0이 된다’는 문장만 남겼다고 해 보자. 다음 사람은 데이터 배열을 더하는 코드를 찾아 나설 수 있다. 하지만 실제 문제는 파일 맨 앞에서 시험한 문장 하나였을 수 있다. 입력 문자열, 실행 환경과 버전, 파일인지 식 입력창인지, 출력의 값과 자료형을 함께 남기면 조사할 범위가 좁아진다. 이 예에서 0과 문자열 '0'의 구분도 빠질 수 없다.
내가 고를 기록 형식은 길지 않다. ‘Node 일반 Script, 입력 {} + [], 결과 0, typeof는 number’처럼 조건과 결과를 한 줄에 둔다. 브라우저 콘솔에서 본 결과라면 해당 브라우저 버전과 실제 입력도 따로 적는다. 이번 로컬 확인을 다른 콘솔이나 모듈의 동작까지 검증한 근거로 넓히지 않는다. 도구가 입력을 식으로 감싸거나 특별하게 처리할 가능성은 그 자리에서 다시 확인할 일이다.
형 변환을 피하려고 무조건 괄호부터 더하는 습관도 조심스럽다. 괄호가 문맥을 바꾸어 원래 현상을 감출 수 있기 때문이다. 원래 입력은 그대로 보존하고, 괄호를 추가한 변형을 두 번째 사례로 비교하는 편이 낫다. 그렇게 해야 수정 전의 조건과 수정 후의 결과 사이에 무엇이 달라졌는지 말할 수 있다. 문제를 설명하는 코드와 실제 업무 코드도 구분해야 한다.
표시하려는 값과 계산하려는 값을 나누기
실무에서 더 자주 만나는 것은 이 짧은 퍼즐보다 로그 메시지다. 예를 들어 결과 객체를 문자열에 바로 더하면 구조를 읽기 어려운 기본 표현이 나타날 수 있다. 같은 로컬 확인에서 JSON.stringify({items: []})는 {"items":[]}라는 문자열을 반환했다. 이는 이 작은 예제의 구조를 보여 주는 방법이지, 모든 객체를 안전하게 직렬화하는 만능 해법은 아니다. 순환 참조나 직렬화되지 않는 값은 별도 검토가 필요하다.
업무 코드에서는 의도를 먼저 정하는 편이 도움이 된다. 숫자 합계라면 입력이 숫자로 인정되는 조건과 실패 처리를 정하고, 문자열 표시라면 표시 형식을 정한다. 여러 자료형을 한 연산자에 맡긴 뒤 우연히 원하는 결과가 나오는지 보는 방식은 입력이 조금만 바뀌어도 설명하기 어렵다. 테스트 역시 결과 한 개보다 허용하는 입력과 거부하는 입력을 함께 보여 주어야 한다.
가상의 장바구니 합계를 생각해 보자. 빈 배열을 더했을 때의 언어 규칙을 알아도 가격 필드가 누락됐을 때 무엇을 해야 하는지는 정해지지 않는다. 누락을 0으로 볼지, 계산을 중단할지, 사용자에게 다시 입력받을지는 제품의 판단이다. 언어의 자동 변환이 업무 규칙을 대신하게 두면 정상처럼 보이는 결과가 오류를 숨길 수 있다. 이 사례는 설계를 설명하기 위한 예시이며 실제 결제 시스템을 실행한 결과가 아니다.
퍼즐의 결론을 디버깅 습관으로 옮기기
이런 예제는 언어가 망가졌다는 증거로도, 규칙을 외우면 문제가 없다는 증거로도 쓰기 쉽다. 내게 더 유용한 부분은 값을 계산하기 전에 문법을 읽는 단계가 있다는 사실이다. 재현 기록에 그 단계를 남기면 낯선 출력이 나왔을 때 동료와 같은 프로그램을 보고 있는지부터 확인할 수 있다. 설명을 외우는 일보다 서로 다른 실행 조건을 나누는 일이 먼저다.
다음에 뜻밖의 덧셈 결과를 만나면 코드를 곧바로 정리하기 전에 원래 입력을 보존하고 싶다. 그다음 실행 자리, 값, 자료형을 적고 한 조건씩 바꿔 본다. 결과가 달라진 지점을 찾은 뒤에야 실제 코드의 의도를 분명하게 고친다. 지금 남겨 둔 오류 기록에는 출력뿐 아니라, 그 출력을 만든 실행 자리까지 들어 있는가?
함께 읽기: TypeScript 컴파일러 전환: 속도보다 먼저 비교할 호환성
공개 원문을 참고하고 AI의 도움으로 구성한 글입니다. 자체 예시는 본문에 구분했으며, 원문의 제품이나 실험을 직접 실행한 후기가 아닙니다.



