Skip to main content
Summary

오류는 실력 부족의 증거가 아니다

코드를 쓰다 보면 오류가 난다. 괄호 하나가 빠져도 멈추고, 변수 이름 한 글자가 달라도 멈춘다. 처음에는 이 일이 답답하게 느껴진다. 하지만 자바스크립트를 잘한다는 것은 오류가 전혀 나지 않는다는 뜻이 아니다. 오류가 났을 때 어디서부터 봐야 하는지 알고, 문제를 조금씩 좁혀 갈 수 있다는 뜻이다.

이 과정을 디버깅이라고 한다. 디버깅은 고장 난 전등을 고치는 일과 비슷하다. 전구가 문제인지, 스위치가 문제인지, 전선이 문제인지 한 번에 알 수 없다. 그래서 하나씩 확인한다. 코드도 마찬가지다.

이번 장의 목표는 정답 코드를 많이 외우는 것이 아니다. 오류 메시지를 읽고, console.log()로 확인하고, 작은 조각부터 실행해 보면서 스스로 문제를 좁히는 습관을 만드는 것이다.

오류를 찾고 고치는 초보자의 디버깅 습관AI 생성 이미지

오류 메시지를 초보자 언어로 번역하기

브라우저 콘솔에 빨간 글씨가 보이면 먼저 겁이 난다. 하지만 오류 메시지는 컴퓨터가 보내는 힌트다. 문장이 딱딱할 뿐, 대부분은 이렇게 번역할 수 있다.

SyntaxError: 문법 모양이 깨졌다

SyntaxError는 자바스크립트가 코드를 읽다가 문장 모양을 이해하지 못했다는 뜻이다. 보통 괄호, 따옴표, 중괄호, 쉼표 같은 기호 문제에서 자주 나온다.

const message = '안녕하세요;
console.log(message);

위 코드는 문자열을 여는 따옴표는 있는데 닫는 따옴표가 없다. 사람은 대충 눈치챌 수 있지만, 컴퓨터는 추측하지 않는다.

이런 오류가 나면 먼저 확인할 것은 세 가지다.

  • 따옴표를 열고 닫았는가?

  • 괄호 (), 중괄호 {}, 대괄호 []의 짝이 맞는가?

  • 코드 한 줄이 이상한 곳에서 끊기지 않았는가?

참고로 문법 오류는 실제로 빠뜨린 곳보다 한 줄 뒤에서 표시될 때도 있다. 줄 번호가 가리키는 곳만 보지 말고, 바로 위아래 줄까지 함께 살펴보자.

ReferenceError: 그런 이름은 모른다

ReferenceError는 코드에서 사용한 이름을 자바스크립트가 찾지 못했다는 뜻이다. 변수 이름을 잘못 썼거나, 아직 만들지 않은 변수를 사용했을 때 자주 나온다.

const userName = '민지';
console.log(userNmae);

userName으로 만들었는데 출력할 때 userNmae라고 썼다. 글자 순서가 바뀌었다. 콘솔에는 보통 userNmae is not defined처럼 나온다. 이 말은 'userNmae라는 이름은 정의된 적이 없다'는 뜻이다.

이 오류가 보이면 변수 이름을 복사해서 비교해 보자. 자바스크립트는 대문자와 소문자도 구분한다. userName, username, UserName은 모두 다른 이름이다.

TypeError: 값의 종류나 상태가 맞지 않는다

TypeError는 어떤 값에 어울리지 않는 일을 시켰을 때 자주 나온다. 예를 들어 값이 null인데 그 안의 기능을 쓰려고 하거나, 함수가 아닌 값을 함수처럼 부르려고 할 때 발생할 수 있다.

const saveButton = document.querySelector('#saveButton');
saveButton.addEventListener('click', function () {
  console.log('저장했습니다.');
});

HTML에 id='saveButton'인 버튼이 없으면 saveButton에는 요소가 아니라 null이 들어간다. null은 '찾은 것이 없음'이라는 뜻이다. 그런데 그 상태에서 addEventListener()를 부르면 이런 오류가 날 수 있다.

Cannot read properties of null (reading 'addEventListener')

이 문장은 이렇게 읽으면 된다.

'지금 값이 null인데, 그 안에서 addEventListener라는 속성을 읽으려고 했다.'

해결은 보통 둘 중 하나다.

  • HTML의 id 이름과 자바스크립트 선택자가 같은지 확인한다.

  • 자바스크립트 코드가 HTML 요소가 만들어진 뒤에 실행되는지 확인한다.

console.log는 질문을 던지는 도구다

초보자가 가장 많이 쓰는 디버깅 도구는 console.log()다. 하지만 아무 곳에나 console.log('확인')만 많이 찍으면 오히려 헷갈린다. 중요한 것은 콘솔에 무엇을 물어볼지 정하는 일이다.

좋은 질문은 보통 세 가지다.

이 줄까지 실행됐나?

버튼을 클릭했는데 아무 일도 안 일어난다면, 이벤트 함수가 실행되는지부터 확인한다.

const button = document.querySelector('#saveButton');

button.addEventListener('click', function () {
  console.log('버튼 클릭 함수 실행됨');
});

이 문장이 콘솔에 나오면 버튼과 이벤트 연결은 된 것이다. 나오지 않으면 버튼을 잘 찾았는지, 이벤트 이름을 잘 썼는지부터 봐야 한다. 만약 이 코드에서 바로 Cannot read properties of null 같은 오류가 난다면, 클릭 문제를 보기 전에 #saveButton으로 버튼을 제대로 찾고 있는지 먼저 확인해야 한다.

지금 값이 무엇인가?

입력값을 사용하기 전에는 실제로 어떤 값이 들어왔는지 확인한다.

const priceText = priceInput.value;
console.log('입력된 값:', priceText);

라벨을 함께 쓰면 나중에 콘솔을 읽기 쉽다. console.log(priceText)만 여러 개 찍으면 어떤 값인지 헷갈린다.

값의 종류가 맞나?

웹페이지 입력칸에서 가져온 값은 대부분 문자열이다. 숫자처럼 보여도 처음에는 글자다.

const priceText = priceInput.value;
console.log('값:', priceText);
console.log('값의 종류:', typeof priceText);

typeof는 값의 종류를 알려 준다. typeof priceText가 string으로 나오면 계산 전에 Number()로 바꿔야 할 수 있다.

const price = Number(priceText);
console.log('숫자로 바꾼 값:', price);
console.log('바꾼 뒤 종류:', typeof price);

console.log()는 정답을 알려 주는 마법이 아니다. 대신 '여기까지 왔는가', '값은 무엇인가', '종류는 맞는가'를 확인하게 해 준다.

한 번에 많이 쓰지 말고 작게 확인하기

오류가 많은 코드는 보통 한 번에 너무 많이 쓴 코드에서 나온다. HTML 요소 찾기, 이벤트 연결하기, 입력값 가져오기, 숫자로 바꾸기, 조건문 처리하기, 화면 바꾸기를 한꺼번에 쓰면 문제가 생겼을 때 어디가 원인인지 알기 어렵다.

작게 나누면 훨씬 쉽다. 예를 들어 배송비 안내 기능을 만든다고 하자.

먼저 계산만 함수로 떼어 낸다.

function getDeliveryMessage(priceText) {
  if (priceText === '') {
    return '금액을 입력하세요.';
  }

  const price = Number(priceText);

  if (Number.isNaN(price)) {
    return '숫자만 입력하세요.';
  }

  if (price >= 50000) {
    return '무료 배송입니다.';
  }

  return 50000 - price + '원이 더 필요합니다.';
}

아직 버튼도, 입력칸도 필요 없다. 콘솔에서 함수만 테스트한다.

console.log(getDeliveryMessage(''));
console.log(getDeliveryMessage('abc'));
console.log(getDeliveryMessage('60000'));
console.log(getDeliveryMessage('30000'));

이렇게 하면 계산 로직이 맞는지 먼저 확인할 수 있다. 그다음에 DOM과 이벤트를 붙인다.

button.addEventListener('click', function () {
  const message = getDeliveryMessage(priceInput.value);
  result.textContent = message;
});

문제가 생기면 범위가 좁아진다. 함수 테스트는 맞는데 화면이 안 바뀐다면 DOM이나 이벤트 문제일 가능성이 높다. 함수 테스트부터 틀린다면 HTML을 보기 전에 함수 안의 조건문과 값 변환을 보면 된다.

방금 바꾼 부분부터 의심하기

오류를 만났을 때 처음부터 전체 코드를 다시 읽으면 지친다. 더 좋은 방법은 '방금 바꾼 부분'을 먼저 보는 것이다.

코드는 방금 수정한 곳에서 깨지는 경우가 많다.

  • 변수 이름을 바꿨는가?

  • HTML의 id나 class 이름을 바꿨는가?

  • 조건문을 추가했는가?

  • 문자열을 숫자로 바꾸는 코드를 옮겼는가?

  • async, await, fetch() 주변을 수정했는가?

수정 전에는 잘 됐는데 수정 후 안 된다면, 가장 강한 단서는 수정한 부분이다. 여러 줄을 한꺼번에 바꿨다면 잠시 주석 처리하거나 원래대로 되돌려서 어느 줄에서 문제가 생겼는지 좁혀 간다.

복잡한 문제를 만났을 때는 절반씩 끊어 보는 방법도 도움이 된다. 코드의 앞부분에 console.log('A 지점'), 중간에 console.log('B 지점'), 끝부분에 console.log('C 지점')을 넣는다. A는 나오고 B가 안 나오면 A와 B 사이를 보면 된다.

초보자가 자주 만나는 실수 체크리스트

오류가 날 때마다 모든 가능성을 떠올릴 필요는 없다. 아래 목록부터 차례대로 확인하면 많은 문제가 해결된다.

  • 변수 이름을 선언할 때와 사용할 때 똑같이 썼는가?

  • 대문자와 소문자가 달라지지 않았는가?

  • 따옴표, 괄호, 중괄호, 대괄호의 짝이 맞는가?

  • =와 ===를 혼동하지 않았는가?

  • 숫자 계산 전에 입력값을 Number()로 바꿨는가?

  • 빈 입력값 ''을 따로 처리했는가?

  • document.querySelector() 결과가 null은 아닌가?

  • 배열의 첫 번째 인덱스가 0이라는 점을 기억했는가?

  • fetch()로 받은 응답이나 JSON을 사용하기 전에 필요한 await를 빠뜨리지 않았는가?

  • 오류가 난 줄 번호 주변을 실제로 읽어 보았는가?

특히 빈 입력값은 자주 놓친다. Number('')는 0이 된다. 사용자가 아무것도 입력하지 않았는데 0원으로 계산될 수 있다. 그래서 숫자로 바꾸기 전에 빈 문자열인지 먼저 확인하는 습관이 좋다.

검색은 붙여넣기 전에 비교하는 과정이다

오류 메시지를 검색하는 것도 중요한 실력이다. 검색할 때는 전체 코드를 복사하기보다 오류 메시지의 핵심 부분과 사용 중인 기능을 함께 넣는다.

예를 들어 이런 오류가 났다고 하자.

Cannot read properties of null (reading 'addEventListener')

검색어는 이렇게 만들 수 있다.

Cannot read properties of null reading addEventListener javascript

또는 상황을 조금 붙인다.

javascript querySelector returns null addEventListener

검색 결과에서 찾은 코드를 바로 붙여 넣지는 말자. 먼저 자기 코드와 다른 점을 비교한다.

  • HTML의 id 이름이 같은가?

  • 자바스크립트가 실행되는 위치가 다른가?

  • 예제는 버튼 하나인데 내 코드는 여러 개인가?

  • 예제는 문자열을 다루는데 내 코드는 숫자 계산인가?

  • 예제에는 async가 있는데 내 코드에는 없는가?

해결책은 보통 '복사할 코드'가 아니라 '확인해야 할 관점'을 준다. 검색 결과를 읽고 내 코드에 필요한 한두 줄만 적용하는 연습을 하자.

오늘의 작은 실습: 일부러 고치며 배우기

아래 코드는 할인 가격을 계산하려는 코드다. 일부러 문제가 생기기 쉬운 부분이 들어 있다.

const priceText = '10000';
const discountText = '';

const price = Number(priceText);
const discount = Number(discountText);

const finalPrice = price - discount;
console.log('최종 가격:', finalPrice);

이 코드는 오류가 나지는 않는다. 하지만 discountText가 빈 문자열인데 Number('')가 0이 되어 그냥 계산된다. 사용자가 할인 금액을 입력하지 않았다는 사실을 놓친다.

조금 더 안전하게 고치면 이렇게 된다.

const priceText = '10000';
const discountText = '';

if (discountText === '') {
  console.log('할인 금액을 입력하세요.');
} else {
  const price = Number(priceText);
  const discount = Number(discountText);
  const finalPrice = price - discount;

  console.log('최종 가격:', finalPrice);
}

여기서 더 나아가려면 priceText도 빈 값인지, 숫자로 바꿀 수 없는 값인지 확인하면 된다. 좋은 코드는 처음부터 완벽한 코드가 아니라, 문제를 발견할 때마다 조금씩 안전해지는 코드다.

디버깅의 순서

오류가 났을 때 다음 순서로 움직여 보자.

  1. 빨간 오류 메시지를 끝까지 읽는다.

  2. 오류 종류가 SyntaxError, ReferenceError, TypeError 중 무엇인지 본다.

  3. 줄 번호가 있다면 그 줄과 바로 위아래 줄을 확인한다.

  4. console.log()로 실행 위치, 값, 타입을 확인한다.

  5. 방금 바꾼 부분부터 의심한다.

  6. 코드를 작은 조각으로 나눠 따로 실행해 본다.

  7. 검색할 때는 오류 메시지의 핵심 문장과 javascript를 함께 넣는다.

  8. 찾은 해결책을 붙여 넣기 전에 내 코드와 다른 점을 비교한다.

이 순서가 익숙해지면 오류는 막막한 벽이 아니라 따라갈 수 있는 흔적이 된다. 다음 장에서는 지금까지 배운 변수, 조건문, 반복문, 함수, DOM, 이벤트, 저장소, fetch()를 모아 작은 프로젝트를 완성해 볼 것이다. 그때도 오류는 날 수 있다. 괜찮다. 이제는 멈춘 지점에서 다시 출발하는 방법을 알고 있다.

책 목록

비개발자를 위한 JavaScript: 설치 없이 시작해 작은 프로젝트까지

전체 목차 보기

이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.

틸노트는 사용자가 AI를 활용해 노트를 쉽게 작성하거나 편집할 수 있는 서비스입니다.

AI 스퀘어 단톡방에 함께해요

AI 활용과 틸노트 사용 팁을 나누는 오픈채팅방입니다. 입장 비밀번호는 tilnote입니다.

참여하기