try/catch/finally 심화, Error 객체와 커스텀 에러 클래스, console 고급 메서드, Chrome DevTools 중단점 디버깅, 스택 트레이스 읽기, 자주 마주치는 오류 유형과 해결법까지 실무 디버깅 역량을 갖춥니다.
🎯 학습 목표
이 강을 마치면 다음을 할 수 있습니다.
- try/catch/finally로 예외를 체계적으로 처리할 수 있습니다.
- Error 객체의 속성을 이해하고 커스텀 에러를 만들 수 있습니다.
- console 메서드와 DevTools 중단점으로 버그를 빠르게 찾을 수 있습니다.
- TypeError, ReferenceError 등 흔한 오류를 보자마자 원인을 파악할 수 있습니다.
💡 에러란 무엇인가 — 에러의 종류
이 섹션에서는 자바스크립트 에러의 종류와 발생 시점을 살펴보겠습니다.
프로그래밍에서 에러(Error)는 피할 수 없는 동반자입니다. 중요한 것은 에러를 두려워하지 않고, 에러 메시지를 빠르게 읽고 원인을 찾는 능력을 기르는 것입니다. 에러는 발생 시점과 원인에 따라 크게 세 가지로 나뉩니다.
구문 오류(SyntaxError)는 코드 실행 전 파싱 단계에서 발생합니다. 괄호 미닫힘, 예약어 오타 등이 원인입니다. 코드 자체가 실행되지 않으므로 try/catch로 잡을 수 없습니다. IDE에서 빨간 밑줄로 즉시 표시됩니다.
런타임 오류(Runtime Error)는 코드 실행 중 발생합니다. null의 속성 접근, 정의되지 않은 변수 사용 등이 원인입니다. try/catch로 처리할 수 있습니다.
논리 오류(Logic Error)는 코드는 정상 실행되지만 결과가 의도와 다른 경우입니다. 조건문 방향이 반대이거나 인덱스 계산이 틀린 경우가 대표적입니다. 오류 메시지가 없으므로 디버깅 도구로 추적해야 합니다.
// SyntaxError — 파싱 시 발생, try/catch 불가
// if (x ← 이 줄 자체가 오류
// 주요 런타임 오류 유형
try {
null.property; // TypeError: Cannot read properties of null
} catch (e) { console.log(e.name, e.message); }
try {
undeclaredVar; // ReferenceError: undeclaredVar is not defined
} catch (e) { console.log(e.name, e.message); }
try {
JSON.parse('{ broken'); // SyntaxError: 실행 중 JSON 파싱 오류
} catch (e) { console.log(e.name, e.message); }
try {
decodeURIComponent('%'); // URIError: malformed URI sequence
} catch (e) { console.log(e.name, e.message); }
// RangeError — 허용 범위 초과
try {
new Array(-1); // RangeError: Invalid array length
} catch (e) { console.log(e.name, e.message); }
// 논리 오류 — 오류 메시지 없음
function sum(a, b) { return a - b; } // 뺄셈인데 덧셈 함수라고 이름 붙임
console.log(sum(3, 2)); // 1 (오류는 없지만 5가 나와야 함)
🛡️ try/catch/finally 심화
이 섹션에서는 예외를 체계적으로 처리하는 try/catch/finally 패턴을 깊이 있게 살펴보겠습니다.
25강과 26강에서 비동기 오류 처리에 try/catch를 사용했습니다. 이번 섹션에서는 동기 코드에서의 활용과 오류 유형별 분기 처리까지 다룹니다.
// 기본 구조와 Error 객체
try {
const result = JSON.parse('{ broken json }');
} catch (error) {
console.log(error.name); // 'SyntaxError'
console.log(error.message); // Unexpected token b in JSON at position 2
console.log(error.stack); // 스택 트레이스 (어디서 오류 났는지 경로)
}
// 오류 유형별 분기 처리
function processUserInput(input) {
try {
const data = JSON.parse(input);
if (!data.name) throw new TypeError('name 필드가 없습니다');
if (data.age < 0) throw new RangeError('나이는 0 이상이어야 합니다');
return data;
} catch (error) {
if (error instanceof SyntaxError) {
console.error('JSON 형식 오류:', error.message);
return null;
}
if (error instanceof TypeError) {
console.error('데이터 형식 오류:', error.message);
return null;
}
if (error instanceof RangeError) {
console.error('값 범위 오류:', error.message);
return null;
}
throw error; // 처리 못한 오류는 상위로 전파
} finally {
console.log('입력 처리 완료'); // 항상 실행
}
}
// 오류 재발생 (re-throw) 패턴
async function fetchData(url) {
try {
const res = await fetch(url);
if (!res.ok) throw new Error('HTTP ' + res.status);
return await res.json();
} catch (error) {
if (error.name === 'AbortError') return null; // 취소는 정상
// 다른 오류는 로깅 후 재발생
console.error('[fetchData]', error.message);
throw error; // 호출자에서 추가 처리 가능
}
}
catch 블록에서 무조건 오류를 삼키면 버그를 숨기게 됩니다. 처리할 수 있는 오류만 처리하고, 나머지는 다시 throw하는 패턴이 좋은 에러 처리입니다. 예를 들어 네트워크 요청 취소(AbortError)는 정상 동작이므로 null 반환으로 처리하고, 인증 만료(401)는 재로그인을 유도하고, 서버 오류(500)는 사용자에게 메시지를 보여주는 식으로 분기합니다.
🏗️ 커스텀 에러 클래스
이 섹션에서는 비즈니스 로직에 특화된 커스텀 에러를 만드는 방법을 살펴보겠습니다.
내장 Error 클래스를 상속해 도메인 전용 에러를 만들면 오류 종류를 instanceof로 명확히 구분할 수 있습니다.
// 커스텀 에러 클래스
class ValidationError extends Error {
constructor(message, field) {
super(message); // Error의 message 설정
this.name = 'ValidationError';
this.field = field; // 추가 속성
}
}
class NetworkError extends Error {
constructor(message, statusCode) {
super(message);
this.name = 'NetworkError';
this.statusCode = statusCode;
}
}
// 사용
function validateUser(user) {
if (!user.email) {
throw new ValidationError('이메일은 필수입니다', 'email');
}
if (!user.email.includes('@')) {
throw new ValidationError('이메일 형식이 올바르지 않습니다', 'email');
}
if (user.age < 14) {
throw new ValidationError('14세 미만은 가입할 수 없습니다', 'age');
}
}
// 호출 측에서 instanceof로 구분
try {
validateUser({ email: 'invalid', age: 10 });
} catch (error) {
if (error instanceof ValidationError) {
console.error(error.field + ' 필드 오류:', error.message);
highlightField(error.field); // 오류 필드 강조 표시
} else {
throw error;
}
}
🔍 console 고급 활용
이 섹션에서는 console.log 너머의 다양한 디버깅 메서드를 살펴보겠습니다.
대부분의 개발자가 console.log만 사용하지만, 브라우저 콘솔은 훨씬 다양한 도구를 제공합니다. 이를 활용하면 디버깅 시간을 크게 줄일 수 있습니다.
// 로그 레벨 구분
console.log('일반 정보');
console.info('정보 메시지'); // 파란 아이콘
console.warn('경고'); // 노란 아이콘
console.error('오류'); // 빨간 아이콘
// 객체를 표 형식으로 출력
const users = [
{ id: 1, name: '홍길동', age: 30 },
{ id: 2, name: '김영희', age: 25 },
];
console.table(users); // 스프레드시트처럼 보기 좋은 표
// 그룹으로 묶어서 출력
console.group('사용자 정보');
console.log('이름:', '홍길동');
console.log('나이:', 30);
console.groupEnd();
// 조건이 false일 때만 출력
console.assert(users.length > 0, '사용자가 없습니다'); // 통과
console.assert(users.length > 10, '사용자가 10명 미만'); // Assertion failed!
// 실행 시간 측정
console.time('데이터 처리');
const sorted = [...users].sort((a, b) => a.age - b.age);
console.timeEnd('데이터 처리'); // 데이터 처리: 0.1ms
// 호출 스택 추적
function a() { b(); }
function b() { c(); }
function c() { console.trace('여기서 호출됨'); }
a(); // 콜 스택: c → b → a → (anonymous)
// 조건부 중단점 대신 debugger 키워드
function processPayment(amount) {
if (amount > 100000) {
debugger; // 이 줄에서 DevTools가 자동 중단
}
return amount * 0.9;
}
🔬 Chrome DevTools 디버깅
이 섹션에서는 Chrome DevTools의 중단점(Breakpoint)을 사용해 코드 실행을 멈추고 상태를 검사하는 방법을 살펴보겠습니다.
console.log로 값을 찍어보는 방식은 원시적이지만 빠릅니다. 그러나 복잡한 버그를 추적할 때는 중단점 디버깅이 훨씬 강력합니다. 코드 실행을 특정 줄에서 멈추고, 그 시점의 변수 값을 모두 확인할 수 있습니다.
// DevTools 디버깅 단계별 가이드
// 1. Sources 패널 열기 (F12 → Sources 탭)
// 2. 파일 트리에서 자바스크립트 파일 선택
// 3. 줄 번호 클릭 → 파란 마커 = 중단점(Breakpoint)
// 4. 코드 실행 시 해당 줄에서 자동 멈춤
function calculateDiscount(price, userType) {
let discountRate = 0; // ← 이 줄에 중단점 설정
if (userType === 'vip') {
discountRate = 0.3;
} else if (userType === 'member') {
discountRate = 0.1;
}
const discount = price * discountRate;
return price - discount; // ← 또는 이 줄에 중단점
}
// 중단점에서 가능한 동작 (DevTools 오른쪽 패널):
// - Step Over (F10): 현재 줄 실행 후 다음 줄로
// - Step Into (F11): 함수 내부로 들어가기
// - Step Out (Shift+F11): 현재 함수에서 나오기
// - Resume (F8): 다음 중단점까지 실행 재개
// Scope 패널에서 현재 시점 변수 값 확인
// Watch 패널에서 특정 표현식 관찰: price * discountRate
중단점을 설정할 줄을 선택하는 요령은 버그가 의심되는 지점보다 한두 줄 앞에 설정하는 것입니다. 멈춘 시점의 변수 값을 확인하면서 어디서 값이 잘못됐는지 추적합니다. 조건부 중단점(줄 번호 우클릭 → Add conditional breakpoint)을 설정하면 특정 조건일 때만 멈추므로 반복문 디버깅에 유용합니다.
🐛 자주 마주치는 오류와 해결법
이 섹션에서는 입문자가 가장 많이 겪는 오류 유형과 즉각적인 해결법을 살펴보겠습니다.
// 1. Cannot read properties of null/undefined
// 가장 흔한 오류! null/undefined에 .을 사용할 때
const user = null;
// user.name ← TypeError: Cannot read properties of null
// 해결: 옵셔널 체이닝 ?.
console.log(user?.name); // undefined (오류 없음)
console.log(user?.address?.city); // undefined
// 또는 기본값
const name = user?.name ?? '익명';
// 2. X is not a function
const num = 42;
// num.forEach(...) ← TypeError: num.forEach is not a function
// 해결: 타입 확인 후 사용
if (Array.isArray(data)) data.forEach(...);
if (typeof fn === 'function') fn();
// 3. X is not defined
// console.log(myVar) ← ReferenceError: myVar is not defined
// 해결: 선언 확인, 스코프 확인
// 4. Maximum call stack exceeded (무한 재귀)
function infinite() { return infinite(); } // ← RangeError
// 해결: 재귀 종료 조건(base case) 확인
// 5. Unexpected token / JSON 파싱 오류
// JSON.parse('undefined') ← SyntaxError
// 해결: 파싱 전 null 체크 + try/catch
function safeParse(str) {
if (!str) return null;
try { return JSON.parse(str); } catch { return null; }
}
// 6. async 함수에서 await 누락
async function getData() {
const res = fetch('/api'); // await 없음!
const data = res.json(); // res는 Promise, .json() 없음
console.log(data.name); // undefined
}
// 해결: await 추가
async function getDataFixed() {
const res = await fetch('/api'); // await 필수
const data = await res.json();
console.log(data.name);
}
// 7. this 바인딩 문제 (일반 함수 콜백)
const obj = {
name: '홍길동',
greet: function() {
setTimeout(function() {
console.log(this.name); // undefined (this가 window)
}, 1000);
}
};
// 해결: 화살표 함수 사용
const objFixed = {
name: '홍길동',
greet: function() {
setTimeout(() => {
console.log(this.name); // '홍길동' (화살표 함수는 this 상속)
}, 1000);
}
};
📝 핵심 요약
28강에서 배운 내용을 정리해 보겠습니다.
| 오류 유형 | 주요 원인 | 해결법 |
|---|---|---|
| TypeError | null/undefined 접근, 타입 불일치 | 옵셔널 체이닝, 타입 확인 |
| ReferenceError | 정의되지 않은 변수 | 선언 여부, 스코프 확인 |
| SyntaxError | JSON 파싱, 문법 오류 | try/catch + null 체크 |
| RangeError | 무한 재귀, 배열 길이 오류 | 종료 조건 확인 |
| 도구 | 활용 |
|---|---|
console.table() | 배열·객체 보기 좋게 출력 |
console.trace() | 호출 스택 추적 |
debugger | 코드에서 직접 중단점 설정 |
| DevTools Sources | 중단점, 변수 검사, Step 실행 |
다음 강인 29강 — 미니 프로젝트: 할 일 앱에서는 1강부터 28강까지 배운 모든 내용을 통합합니다. DOM 조작, 이벤트 처리, localStorage 저장, async 처리까지 — 실제로 동작하는 완성된 앱을 처음부터 끝까지 만들어 봅니다!
관련 주제
- try catch finally
- Error 객체
- 커스텀 에러 클래스
- DevTools 중단점
- 스택 트레이스
- TypeError ReferenceError
- 개발·프로그래밍
- 개발·프로그래밍 강의
- 자바스크립트 기초 30강 — 웹을 움직이는 언어
- 무료강의
- 무료 온라인 강의
- NUGUNA
- 누구나
📚 시리즈 전체 공유
자바스크립트 기초 30강 — 웹을 움직이는 언어
이 강의가 속한 시리즈는 총 30강, 모두 무료입니다. 처음부터 배우려는 동료에게 시리즈 전체를 알려 주세요.
댓글
불러오는 중...
