1.오류 메시지는 단서입니다
코드를 처음 짜면 빨간 오류가 자주 납니다. 전문 개발자도 마찬가지입니다. 차이는 오류를 보고 당황하느냐, 단서로 읽느냐에 있습니다. 오류를 찾아 고치는 일을 디버깅(debugging)이라고 하는데, 그 첫걸음은 메시지를 끝까지, 한 글자씩 읽는 것입니다. 메시지에는 대개 고칠 곳의 이름이 그대로 들어 있습니다.
JavaScript 오류 메시지는 오류이름: 설명 모양입니다. 예를 들어 TypeError: Cannot read properties of undefined (reading 'length')는 '종류는 TypeError(값의 종류가 맞지 않음), 내용은 undefined에서 length를 읽으려 했다'는 뜻입니다. 이 실습장은 실행 중에 난 오류라면 몇 번째 줄에서 났는지도 함께 보여 줍니다. 그 줄이 출발점입니다.
알아 둘 점이 두 가지 있습니다. 첫째, 설명 문구는 브라우저마다 조금씩 다릅니다. 이 강의의 메시지는 크롬 계열 브라우저가 쓰는 V8 엔진의 문구이고, 다른 브라우저에서는 같은 오류가 다른 말로 나올 수 있습니다. 오류 이름(TypeError 등)은 언어 규칙으로 정해져 있어서 대부분 어느 브라우저에서나 같습니다. 둘째, 오류가 난 줄과 실수한 줄이 다를 수 있습니다. 값이 잘못 만들어진 곳은 위쪽이고, 그 값을 쓰다가 터진 곳이 아래쪽인 경우가 흔합니다.
오류가 나면 실행은 그 줄에서 멈춥니다. 그래서 오류 앞의 console.log는 출력되고, 뒤의 것은 출력되지 않습니다. 이 성질은 '어디까지 제대로 돌았는가'를 알려 주는 단서가 됩니다. 아래 코드는 첫 줄이 찍힌 뒤 3행에서 멈춥니다.
words[2]는 없는 칸이라 undefinedconst words = ["사과", "배"];
console.log(words[0].length);
console.log(words[2].length);
console.log("여기는 실행되지 않음");2
TypeError: Cannot read properties of undefined (reading 'length')- 오류 이름 — 어떤 종류의 문제인가 (
SyntaxError,ReferenceError,TypeError,RangeError…) - 설명 — 무엇을 하다가 막혔는가 (어떤 이름, 어떤 속성)
- 줄 번호 — 어디서 멈췄는가 (실수한 곳은 그보다 위일 수 있다)
2.`SyntaxError` — 아예 실행되지 않는 오류
SyntaxError는 문법 오류입니다. 괄호를 닫지 않았거나, 쉼표를 빠뜨렸거나, 같은 이름을 두 번 선언한 경우처럼 코드가 JavaScript 문장으로 읽히지 않을 때 납니다. 다른 오류와 달리 코드를 실행하기 전, 읽는 단계에서 걸리기 때문에 한 줄도 실행되지 않습니다. 맨 위에 console.log를 넣어도 아무것도 찍히지 않는다면 문법 오류를 먼저 의심하세요.
이 실습장은 문법 오류일 때 줄 번호 없이 메시지만 보여 줍니다. 그래서 메시지 속 단서가 더 중요합니다. missing ) after argument list는 함수 호출의 닫는 괄호가 없다는 뜻이고, Unexpected identifier 'price'는 'price가 나올 자리가 아닌데 나왔다', 즉 그 바로 앞에 쉼표 같은 것이 빠졌다는 뜻입니다. Unexpected token이 보이면 그 글자 바로 앞부분을 살펴봅니다. 이름이 따옴표로 표시되어 있으면 그 이름으로 검색해서 위치를 찾으면 빠릅니다.
문법 오류는 대개 짝이 안 맞는 데서 생깁니다. 여는 괄호·중괄호·따옴표마다 닫는 짝이 있는지, 들여쓰기를 맞춰 보면 짝이 어긋난 곳이 눈에 띕니다. 편집기가 짝 괄호를 강조해 주는 기능도 큰 도움이 됩니다. 한 번에 긴 코드를 쓰지 말고 몇 줄씩 써서 실행해 보면, 문법 오류가 났을 때 방금 쓴 몇 줄만 보면 됩니다.
console.log("안녕";SyntaxError: missing ) after argument listprice 바로 앞, 곧 윗줄 끝의 쉼표가 빠졌다const item = {
name: "사과"
price: 1500
};SyntaxError: Unexpected identifier 'price'let count = 0;
let count = 1;SyntaxError: Identifier 'count' has already been declaredJSON.parse로 읽다가 나는 오류도 이름이 SyntaxError입니다. 다만 이것은 코드의 문법이 아니라 '데이터의 문법'이 틀린 것이고, 실행 중에 나므로 줄 번호도 나옵니다. JSON이 깨졌는지는 JSON 포맷터에 붙여 넣으면 어디가 틀렸는지 바로 보입니다.3.`ReferenceError` — 그런 이름은 없습니다
ReferenceError는 '그 이름을 찾을 수 없다'는 오류입니다. 가장 흔한 원인은 오타입니다. console을 Console로 쓰거나, total을 totla로 쓰면 JavaScript는 그런 이름의 변수가 없다고 알려 줍니다. 대소문자도 다른 이름으로 칩니다. 메시지의 is not defined 앞에 나온 이름을 그대로 복사해서 코드에서 찾아보면, 철자가 다른 곳이 금방 보입니다.
두 번째 원인은 '선언하기 전에 사용'입니다. let·const로 만든 변수는 선언한 줄보다 위에서 쓰면 Cannot access 'price' before initialization이 납니다. 세 번째는 범위(scope)입니다. 5강에서 본 것처럼 함수나 { } 블록 안에서 만든 변수는 그 밖에서 보이지 않습니다. 반복문 안에서 만든 변수를 반복문이 끝난 뒤에 쓰려 하면 is not defined가 납니다.
이 실습장처럼 엄격 모드로 도는 코드에서는 let이나 const 없이 처음 보는 이름에 값을 넣는 것도 ReferenceError입니다. 엄격 모드가 아니면 이런 실수가 조용히 새 전역 변수를 만들어 버려 더 찾기 어려운 버그가 되는데, 엄격 모드는 이를 오류로 바로 알려 줍니다.
const total = 100;
console.log(totla);ReferenceError: totla is not definedconsole.log(price);
const price = 1500;ReferenceError: Cannot access 'price' before initializationfor (let i = 0; i < 3; i++) {
const last = i;
}
console.log(last);ReferenceError: last is not defined4.`TypeError` — 그 값으로는 할 수 없는 일
TypeError는 값의 종류에 맞지 않는 일을 시켰다는 오류로, 초보 단계에서 가장 많이 만납니다. 대표가 Cannot read properties of undefined (reading '…')입니다. undefined나 null에는 속성이 없는데 점(.)을 찍어 무언가를 꺼내려 했다는 뜻입니다. 괄호 안의 이름이 꺼내려던 속성이고, 그 점 바로 왼쪽에 있는 값이 undefined였습니다.
중요한 것은 '왜 그 값이 undefined가 되었나'입니다. JavaScript는 없는 배열 칸(arr[5])이나 없는 속성(user.nmae)을 꺼낼 때 오류를 내지 않고 조용히 undefined를 돌려줍니다. 오류는 그 undefined를 한 번 더 쓰는 순간에야 납니다. 그래서 오류 줄의 점 왼쪽 값을 console.log로 찍어 보고, 그 값이 어디서 왔는지 위로 거슬러 올라가야 합니다. 범인은 대개 인덱스 범위, 속성 이름 오타, 함수가 return을 빠뜨려 undefined를 돌려준 경우입니다.
또 하나 자주 보는 것이 … is not a function입니다. 함수가 아닌 것을 괄호 ()로 부른 경우인데, 메서드 이름 오타(toUppercase)이거나, 다른 종류의 값에 그 메서드를 부른 경우입니다. 예를 들어 toFixed는 숫자의 메서드라서 문자열 "1500"에 부르면 이 오류가 납니다. 2강에서 본 형 변환 문제와 이어지는 지점입니다. const로 만든 변수에 다시 값을 넣는 Assignment to constant variable.도 TypeError입니다.
undefined — 오류는 그다음에 난다const user = { name: "민수" };
console.log(user.nmae);
console.log(user.nmae.length);undefined
TypeError: Cannot read properties of undefined (reading 'length')const s = "hello";
console.log(s.toUppercase());TypeError: s.toUppercase is not a functiontoFixed가 없다const price = "1500";
console.log(typeof price);
console.log(price.toFixed(0));string
TypeError: price.toFixed is not a function| 오류 이름 | 뜻 | 흔한 메시지 | 먼저 볼 곳 |
|---|---|---|---|
SyntaxError | 문장으로 읽을 수 없다 | missing ) after argument list | 괄호·쉼표·따옴표 짝 |
ReferenceError | 그런 이름이 없다 | x is not defined | 철자·대소문자·선언 위치·범위 |
TypeError | 그 값으로는 못 하는 일 | Cannot read properties of undefined | 점 왼쪽 값이 어디서 왔는지 |
RangeError | 값이 허용 범위를 벗어났다 | Maximum call stack size exceeded | 음수·너무 큰 수·끝나지 않는 재귀 |
5.`RangeError`, 그리고 오류가 나지 않는 버그
RangeError는 값의 종류는 맞는데 범위가 틀렸다는 오류입니다. "*".repeat(-1)처럼 음수 횟수를 주거나, new Array(-1)처럼 음수 길이를 주면 납니다. 함수가 자기 자신을 끝없이 부르면 Maximum call stack size exceeded가 나는데, 크롬 계열(V8)에서는 이것도 RangeError입니다(파이어폭스는 같은 상황을 InternalError: too much recursion으로 알립니다). 함수가 스스로를 부르는 재귀에는 반드시 멈추는 조건이 있어야 합니다.
더 까다로운 것은 오류가 나지 않는 버그입니다. 코드는 끝까지 돌았는데 결과만 틀린 경우입니다. 4강에서 본 off-by-one이 대표적입니다. i <= arr.length로 반복하면 마지막에 없는 칸을 꺼내 undefined를 더하게 되고, 숫자에 undefined를 더하면 오류 대신 NaN이 됩니다. 0으로 나누면 Infinity가, 0 / 0은 NaN이 나올 뿐 오류는 나지 않습니다. 결과에 NaN·undefined·Infinity가 보이면 오류 메시지가 없어도 버그 신호입니다.
끝나지 않는 반복도 오류 메시지를 내지 않습니다. while의 조건이 영원히 참이면 프로그램은 계속 돌기만 합니다. 이 실습장은 실행이 2초 안에 끝나지 않으면 강제로 멈추고 시간 제한에 걸렸다고 알려 줍니다. 그 안내가 보이면 반복문의 끝 조건을 의심하세요. 조건에 쓰는 변수가 반복 안에서 실제로 바뀌는지, 끝에 가까워지는 방향으로 바뀌는지를 봅니다. 실제 브라우저 탭에서 무한 반복을 돌리면 탭이 멈출 수 있으니, 반복문을 처음 쓸 때는 횟수가 작은 입력부터 시험합니다.
function stars(n) {
return "*".repeat(n);
}
console.log(stars(3));
console.log(stars(-1));***
RangeError: Invalid count value: -1NaN — off-by-oneconst nums = [10, 20, 30];
let sum = 0;
for (let i = 0; i <= nums.length; i++) {
sum += nums[i];
}
console.log(sum);
console.log(10 / 0, 0 / 0);NaN Infinity NaN
6.디버깅의 순서 — 찍어 보고, 가설 세우고, 줄이기
디버깅에는 정해진 순서가 있습니다. 먼저 '기대한 결과'와 '실제 결과'를 한 문장씩 적습니다. 그다음 원인에 대한 가설을 하나 세우고(예: 반복이 한 번 더 돈다), 그것을 확인할 수 있는 가장 작은 실험을 합니다. 가설이 맞으면 고치고, 틀리면 지우고 다음 가설로 넘어갑니다. 여러 곳을 한꺼번에 바꾸면 무엇이 고친 것인지 알 수 없으니 한 번에 하나씩만 바꿉니다.
가장 손쉬운 실험 도구는 console.log입니다. 의심 가는 곳의 값을 찍어 보면 '내 머릿속의 값'과 '실제 값'이 어디서부터 갈라지는지 보입니다. 반복문 안에서는 번호와 값을 함께 찍고, 값 옆에 이름표를 붙이면(console.log("i:", i)) 출력이 섞여도 헷갈리지 않습니다. typeof를 같이 찍으면 "1500"(문자열)과 1500(숫자)처럼 겉보기에 같은 값도 구분됩니다. 문제를 찾았으면 확인용 출력은 지웁니다.
코드가 길면 최소 재현(minimal reproduction)을 만듭니다. 버그가 그대로 나타나는 한도 안에서 관계없는 줄을 하나씩 지워, 문제를 일으키는 가장 짧은 코드를 남기는 것입니다. 줄이는 과정에서 원인이 보이는 경우가 많고, 남이나 AI에게 물을 때도 짧은 코드가 훨씬 정확한 답을 얻습니다. 설명하다 보면 답이 보이기도 해서, 코드를 한 줄씩 소리 내어 설명해 보는 방법(고무 오리 디버깅)도 널리 쓰입니다.
NaN을 출력합니다. 어떻게 원인을 찾을까요?const sales = [10, 20, 30];
let sum = 0;
for (let i = 0; i <= sales.length; i++) {
console.log("i:", i, "값:", sales[i]);
sum += sales[i];
}
console.log(sum / sales.length);i: 0 값: 10 i: 1 값: 20 i: 2 값: 30 i: 3 값: undefined NaN
- 1단계 기대와 실제를 적습니다. 기대: 20, 실제:
NaN. 오류 메시지는 없으니 결과만 틀린 버그입니다. - 2단계 가설: '더하는 값 중에 숫자가 아닌 것이 섞였다'. 반복 안에서
i와sales[i]를 찍어 봅니다. - 3단계 출력을 보면
i: 3일 때 값이undefined입니다. 배열은 0~2번뿐인데 3번까지 돌았습니다. - 4단계 조건
i <= sales.length를i < sales.length로 고치고, 빈 배열·한 칸 배열도 시험합니다.
undefined를 더했습니다. <로 고치면 20이 나옵니다.7.AI에게 오류를 물을 때 줄 것
AI 코딩 도우미는 오류 메시지를 설명하고 고칠 방향을 제시하는 데 꽤 쓸모가 있습니다. 다만 받은 정보만큼만 정확합니다. '안 돼요, 고쳐 주세요'보다 오류 메시지 전체, 문제가 나는 최소 코드, 기대한 결과와 실제 결과, 실행 환경(브라우저·엔진), 이미 해 본 것을 함께 주면 훨씬 정확한 답이 옵니다. 오류 메시지는 요약하지 말고 그대로 복사해 붙이세요. 번역하거나 줄이면 중요한 단서(이름·속성·줄 번호)가 사라집니다.
붙여 넣기 전에 반드시 걸러야 할 것도 있습니다. 비밀번호, API 키, 접속 토큰, 실제 고객의 이름·전화번호 같은 개인정보는 코드나 데이터에 섞여 있어도 지우고 가짜 값으로 바꿉니다. 회사나 학교의 코드라면 외부 AI에 붙여 넣어도 되는지 그곳의 정책을 먼저 확인합니다. 10강에서 이 부분을 더 자세히 다룹니다.
AI가 고친 코드를 받았다면 그대로 믿지 말고 확인합니다. 원래 오류가 사라졌는지, 기대한 결과가 나오는지, 빈 배열이나 0 같은 경계값에서도 맞는지 직접 실행해 봅니다. AI는 그럴듯하지만 틀린 설명을 할 때가 있고, 오류만 숨기는 방식(예: 문제 값을 무조건 0으로 바꾸기)으로 '고치는' 경우도 있습니다. 왜 그렇게 고쳤는지 설명을 요청하고, 그 설명이 오류 메시지와 맞는지 따져 보세요.
- 오류 메시지 전체(이름·설명·줄 번호) — 그대로 복사
- 문제가 나타나는 가장 짧은 코드(최소 재현)
- 기대한 결과와 실제 결과
- 실행 환경(어떤 브라우저, 이 실습장인지 등)과 이미 시도한 것
- 비밀번호·키·실제 개인정보는 빼고 가짜 값으로
📌 핵심 정리
- 오류 메시지는 '이름: 설명'과 줄 번호로 읽는다 — 설명 문구는 브라우저마다 조금 다르지만 이름은 같다
SyntaxError는 실행 전에 걸려 한 줄도 돌지 않고,ReferenceError는 이름 문제,TypeError는 값 종류 문제,RangeError는 범위 문제다undefined는 조용히 생기고 나중에 터진다 — 오류 줄의 점 왼쪽 값이 어디서 왔는지 거슬러 올라간다NaN·Infinity·끝나지 않는 반복은 오류 메시지 없이 오는 버그 신호다 — 실습장은 2초가 지나면 실행을 멈춘다- 기대·실제 적기 → 가설 하나 →
console.log로 확인 → 최소 재현, 그리고 AI에게는 메시지 전체와 짧은 코드를 비밀 정보 없이 준다
🧪 코드 실습
모든 처음 코드에는 버그가 하나씩 숨어 있습니다. 먼저 그대로 실행해 오류 메시지나 틀린 결과를 읽고, 원인을 찾아 한 곳만 고쳐 테스트를 통과시키세요.
코드는 이 브라우저 안의 격리된 공간에서만 실행되고 서버로 보내지 않습니다. 네트워크에는 접속할 수 없고, 2초가 넘으면 멈춥니다. 고친 코드는 이 브라우저에만 저장됩니다. Ctrl+Enter(⌘+Enter)로 실행하고, Tab 키는 공백 두 칸입니다(Esc 다음 Tab 은 다음 칸으로 이동).
자바스크립트가 꺼져 있어 실행할 수는 없지만, 문제와 처음 코드, 자동 검사 내용, 예시 답안은 그대로 읽을 수 있습니다.
1이름 오타 고치기
가격 price와 할인율 rate(예: 0.1은 10%)를 받아 할인된 가격을 돌려주는 함수 discount(price, rate)입니다. 실행하면 ReferenceError가 납니다. 메시지에 나온 이름을 보고 고치세요.
discount(10000, 0.1)기대값9000discount(5000, 0.5)기대값2500discount(3000, 0)기대값3000discount(0, 0.2)기대값0
💡 힌트
prise is not defined — 그 이름이 어디에 있는지 찾고, 매개변수 이름과 비교해 보세요.
예시 답안 보기
function discount(price, rate) {
const off = price * rate;
return price - off;
}2없는 메서드 고치기
문자열 s를 모두 대문자로 바꾸고 끝에 "!"를 붙여 돌려주는 함수 shout(s)입니다. 실행하면 TypeError: s.toUppercase is not a function이 납니다. 원인을 찾아 고치세요. 빈 문자열이면 "!"를 돌려줍니다.
shout("hi")기대값"HI!"shout("Hello World")기대값"HELLO WORLD!"shout("가나다")기대값"가나다!"shout("")기대값"!"
💡 힌트
JavaScript 이름은 대소문자를 구분합니다. 7강에서 쓴 메서드 이름의 철자를 한 글자씩 비교해 보세요.
예시 답안 보기
function shout(s) {
return s.toUpperCase() + "!";
}3NaN이 나오는 평균 고치기
숫자 배열 nums의 평균을 돌려주는 함수 average(nums)입니다. 빈 배열이면 0을 돌려줍니다. 오류는 나지 않는데 결과가 NaN입니다. 반복 안에서 i와 nums[i]를 찍어 보고 원인을 찾으세요.
average([10,20,30])기대값20average([7])기대값7average([1,2])기대값1.5average([-5,5])기대값0average([])기대값0
💡 힌트
console.log("i:", i, nums[i]);를 반복 안에 넣어 보세요. 마지막에 찍히는 값이 무엇인가요?
예시 답안 보기
function average(nums) {
if (nums.length === 0) return 0;
let sum = 0;
for (let i = 0; i < nums.length; i++) {
sum += nums[i];
}
return sum / nums.length;
}4끝나지 않는 반복 고치기
양의 정수 n을 받아 n부터 1까지 거꾸로 센 배열을 돌려주는 함수 countDown(n)입니다. 예: countDown(3) → [3, 2, 1], countDown(0) → []. 처음 코드는 끝나지 않아 실습장이 2초 뒤에 실행을 멈춥니다. 반복의 끝 조건을 살펴 고치세요.
countDown(3)기대값[3,2,1]countDown(1)기대값[1]countDown(5)기대값[5,4,3,2,1]countDown(0)기대값[]
💡 힌트
조건 n > 0이 언젠가 거짓이 되려면 반복 안에서 n이 어떻게 바뀌어야 할까요?
예시 답안 보기
function countDown(n) {
const out = [];
while (n > 0) {
out.push(n);
n--;
}
return out;
}🤖 AI에게 이렇게 물어보세요
복사해서 [ ] 부분을 내 상황으로 바꿔 쓰세요. 답을 그대로 믿지 말고 본문과 비교해 확인하세요.
오류 메시지의 뜻을 모를 때
JavaScript를 [브라우저/실습장]에서 실행했더니 다음 오류가 났어: [오류 메시지 전체를 그대로]. 문제가 나는 코드는 이거야: [최소 코드]. 이 오류가 무슨 뜻인지 쉬운 말로 설명하고, 내 코드에서 어느 줄의 어떤 값이 원인일 가능성이 큰지 순서대로 알려 줘. 바로 답을 주기 전에 내가 `console.log`로 확인해 볼 곳을 먼저 알려 줘.
오류는 없는데 결과가 틀릴 때
다음 함수는 오류 없이 돌지만 결과가 틀려. 입력: [입력], 기대한 결과: [기대], 실제 결과: [실제]. 코드: [최소 코드]. 원인 가설을 2~3개 세우고, 각 가설을 확인할 `console.log` 위치를 알려 줘. 고친 코드를 줄 때는 무엇을 왜 바꿨는지와 경계값(빈 배열, 0) 테스트도 함께 줘.
AI가 고쳐 준 코드를 검증할 때
네가 고쳐 준 코드가 원래 문제를 정말 해결했는지 확인하고 싶어. 원래 오류: [메시지], 고친 코드: [코드]. 바뀐 줄마다 이유를 설명하고, 오류를 숨기기만 하는 수정(예: 값을 무조건 0으로 바꾸기)은 아닌지 스스로 점검해 줘. 이 코드가 실패할 수 있는 입력도 3개 들어 줘.
🧰 관련 도구
이 강의에서 다룬 JSON·정규식·텍스트를 바로 확인하고 다듬어 볼 수 있는 도구입니다. 실제 개인정보나 비밀번호는 붙여 넣지 마세요.
- MDN Web Docs — JavaScript 오류 참고서(JavaScript error reference)
- MDN Web Docs — SyntaxError, ReferenceError, TypeError, RangeError
- MDN Web Docs — 엄격 모드(Strict mode)
- Chrome DevTools 문서 — JavaScript 디버깅 시작하기
학습 목표를 모두 이뤘다면 완료를 눌러 진도에 기록하세요.
이 브라우저에서는 저장할 수 없어 이 화면에서만 표시됩니다.