본문으로 바로가기

throw & try/catch/finally(Exception Handling)

이 글에서는 예외 처리 방식에 대해 알아봅니다.

프로그램이 실행되는 동안 문제가 발생하면 프로그램이 자동으로 중단됩니다.

이럴 경우에 프로그램이 대처할 수 있도록 처리하는 것이 예외 처리(Exception Handling)라고 합니다.

프로그램 실행 중에 발생하는 오류를 예외(Exception)라고 하고, 프로그래밍 언어의 문법적인 오류를 에러(Error)라고 합니다.


예외가 발생하는 이유는 여러가지가 있겠지만 일반적으로 잘못된 코드를 작성했거나 사용자가 개발자가 원하지 않는 방향으로 프로그램을 사용했을 수도 있습니다.

예외는 기본 예외 처리와 고급 예외 처리 두 가지 방법으로 처리합니다.

예외가 발생하지 않게 사전에 해결하는 것을 기본 예외 처리라고 하며 대개 조건문으로 처리할 수 있습니다.

여기서는 예외 처리 방법 중에 고급 예외처리 방법에 대해 알아봅니다.




throw

"예외"란 무언가 예외적인 상황이나 에러가 발생했음을 가리키는 신호입니다.

예외를 '발생시키다(throw)'라는 것은 그런 에러나 예외 상황을 알린다는 뜻입니다.

한편 예외를 '잡아내다(catch)'라는 것은 그것을 처리한다는 뜻입니다.(즉 그 예외에서 회복하기 위해 무언가 필요하거나 적절한 행동을 취한다는 뜻이다.)

자바스크립트는 런타임 에러가 일어날 때마다 예외를 발생시킵니다. 또한 프로그램에서 throw문을 사용하여 명시적으로 예외를 발생시킬 때에도 마찬가지로 예외를 발생시킵니다.

이렇듯 예외를 강제로 발생시켜야 경우가 생길 때는 throw 키워드를 사용합니다.

그리고 예외를 잡아내는 데에는 try/catch/finally문을 사용합니다.

throw 문의 문법은 다음과 같습니다.

JavaScript
throw 표현식;

'표현식'의 결과값 타입은 무엇이든 될 수 있습니다. 하지만 대부분의 그 타입은 Error객체 또는 Error의 하위 클래스 중 하나의 인스턴스가 되곤 합니다. 

때로는 에러 메시지를 담고 있는 문자열이나 어떤 에러 코드를 나타내는 숫자 값도 유용할 수 있습니다.

다음의 예제 코드에서 예외를 발생시키는 throw 문이 어떻게 쓰이는지 살펴봅니다.

JavaScript
function factorial(x) {
	// 만약 전달인자가 유효하지 않으면 예외를 발생시킨다!!!
	if (x < 0) {
		throw new Error('x는 음수가 아니어야 합니다.')
	}
	// 유효하다면 값을 계산하여 정상적으로 반환환다.
	for (var f = 1; x > 1; f *= x, x--) /* 비어 있음 */
	return f;
}

예외가 발생하면 자바스크립트 인터프리터는 정상적인 프로그램 실행을 즉시 중단하고 가장 가까운 예외 처리기로 넘어갑니다.

예외 처리기는 앞으로 다룰 try/catch/finally 문 중에서 catch절을 사용하여 작성됩니다. 

예외를 발생시켰던 코드 블록이 catch절과 연결되어 있지 않으면면, 인터프리터는 바로 상위 단계를 감싸고 있는 코드 블록에 연결되어 있는지를 확인합니다. 처리기를 찾을 때까지 이 과정이 반복됩니다.

만일 예외를 처리할 try/catch/finally 문이 없는 함수 안에서 예외가 발생했다면 해당 함수를 호출했던 블록으로 그 예외가 전파되어 올라갑니다.

이 같은 방법으로 자바스크립트의 언어적인 구조를 따라서, 즉 호출 스택(call stack)을 따라서 예외가 전파되어 올라갑니다. 그래도 아무런 예외 처리기도 찾을 수 없으면 이 예외는 에러로 취급되어 사용자에게 보고되어 질 것입니다.


예외를 강제로 발생시키는 이유는 무엇일까요?

객체를 잘못 사용하는 사용자에게 예외를 강제로 발생시켜서 사용자에게 주의를 줄 수도 있고 예외와 관련된 처리를 해달라고 부탁할 수도 있습니다.



try/catch/finally

try/catch/finally 문은 자바스크립트의 예외 처리 기법입니다.

이 문장에서 try 절은 그저 처리할 예외가 발생할지도 모를 코드 블록을 정의하는 역할을 합니다.  try 블록 다음에는 catch절이 이어집니다.

catch절은 try 블록 내부에서 예외가 발생할 경우 호출되는 문장 블록입니다. 

catch 절 다음에는 finally 블록이 이어지는데, 여기에는 앞서 try 블록에서 일어난 일에 관계없이 항상 실행이 보장되어야 할 뒷정리용 코드가 포함됩니다.

catch나 finally 블록은 생략할 수 있습니다. 하지만 try 블록은 catch나 finally 중 적어도 하나 이상의 블록과 함께 사용되어야만 합니다.

try, catch, finally 블록은 모두 중괄호로 시작하여 중괄호로 끝납니다. 이들 중괄호는 필수로 요구되는 문법의 일부로서 생략할 수 없습니다.

설사 해당절에 단 하나의 문장만 있다 하더라도 마찬가지입니다. 

다음의 코드에서 try/catch/finally 문의 문법과 용도를 살펴보도록 합니다.  

특히 catch 키워드에 뒤따르는 괄호와 그 속의 식별자를 주의깊게 살펴보길 바랍니다.

이 식별자는 함수의 전달 인자와 닮았으며 이것은 오직 catch 블록의 몸체 내부에서만 존재하는 지역 변수를 명명합니다. 

앞서 던져진 예외 객체나 값이 무엇이었던 간에 그것이 이 변수에 할당될 것입니다.

JavaScript
try {
	/**
	 * 정상이라면 이 코드는 아무런 문제없이 블록의 시작부터 끝까지 실행된다.
	 * 하지만 경우에 따라 예외가 발생할 수 있다.
	 * 예외는 throw 문에 의해 직접적으로 발생할 수도 있고,
	 * 또는 예외를 발생시키는 메서드의 호출에 의해 발생할 수도 있다.
	 */
} catch (e) {
	/**
	 * 이 블록 내부의 문장들은 오직 try 블록에서 예외가 발생할 경우에만 실행된다.
	 * 이 문장들에선 지역 변수 e를 사용하여 Error 객체 또는 앞에서 던진 다른 값을 참조할 수 있다.
	 * 이 블록에서는 어떻게든 그 예외를 처리할 수도 있고,
	 * 그냥 아무것도 하지 않고 예외를 무시할 수도 있고,
	 * 아니면 throw 를 사용해서 예외를 다시 발생시킬 수도 있다.
	 */
} finally {
	/**
	 * 이 블록에는 try 블록에서 일어난 일에 관계없이 무조건 실행될 코드가 위치한다.
	 * 이 코드는 try 블록이 어떻게든 종료되면 실행된다.
	 * try 블록이 종료되는 상황은 다음과 같다.
	 *   1) 정상적으로 블록의 끝에 도달했을 때
	 *   2) break, continue 또는 return 문에 의해서
	 *   3) 예외가 발생했지만 catch 절에서 처리했을 때
	 *   4) 예외가 발생했고 그것이 잡히지 않은 채 퍼져나갈 때
	 */
}


다음에는 try/catch 문에 대한 좀 더 실제적인 예제가 있습니다. 여기서는 앞서 사용했던 factorial() 메서드를 사용합니다.

JavaScript
try {
	// 사용자에게 번호 입력을 요청
	var n = prompt("정수를 입력해 주세요.");
	// 사용자의 입력이 유효하다고 가정하고 그 숫자의 계승(factorial)을 계산한다.
	var f = factorial(n);
	// 결과를 표시한다.
	console.log(n + "! = " + f);
} catch (ex) { // 만약 사용자의 입력이 유효하지 않다면 이곳에 도달한다.
	// 사용자에게 에러가 무엇인지 알린다.
	alert(ex);
}

위 코드는 finally 절이 없은 try/catch 문의 예입니다. finally는 catch 만큼 자주 쓰이는 편은 아니지만 종종 유용할 때가 있습니다.

하지만 finally의 작동을 이해하려면 추가 설명이 필요합니다.  일단 try 블록이 일부라도 실행되면  finally 절의 실행은 보장됩니다.

이때 try 블록의 코드가 어떻게 끝나는지는 상관없습니다. finally 절은 보통 try 절 코드의 뒷정리를 위해 사용되는 것이 일반적입니다.

정상적인 경우 try 블록의 끝까지 프로그램 제어가 도달하고 나면 finally 블록으로 제어가 진행하여 무언가 필요한 뒷정리를 수행합니다. 

만일 return, continue, break 문 등으로 인해 try 블록에서 제어가 빠져 나왔다면 이들 문장이 인도하는 곳으로 제어가 이동하기에 앞서 finally 블록이 실행됩니다.

만일 try 블록에서 예외가 발생했는데 거기에 이 예외를 처리하기 위한 catch 블록이 함께 있다면, 제어는 일단 catch 블록으로 이동한 후에 finally 블록으로 넘어오게 될 것입니다.

만일 일어난 예외를 처리할 catch 블록이 현 단계에 없다면 일단 finally 블록으로 제어가 이동했다가 그 다음에 상위 단계로 전파되어 올라가서 해당 예외를 처리할 수 있는 가장 가까운 catch 절까지 이동할 것입니다.


finally 블록 자체에서 return, continue, break 또는 throw 문을 사용해 제어를 이동시키거나 또는 예외를 발생시키는 메서드를 호출하여 제어를 이동시킬 수도 있습니다.

이런 경우, finally 블록이 실행된 후에 있을 예정이었던 제어 이동은 취소됩니다. 그 대신 finally 에서 가리키는 새로운 제어 이동이 일어납니다. 

예를 들어 finally 절에서 예외를 발생시키면 그 예외가 기존에 발생해서 처리 중이던 예외를 대신하게 됩니다.

만일 finally 절에서 return 문을 실행했다면, 설사 기존에 예외가 발생했고 아직 미처 처리되지 않았다 하더라도 이 메서드는 정상적으로 반환될 것입니다.


try와 finally는 catch가 없어도 함께 쓰일 수 있습니다.

이러한 경우 try 절에서 try, continue, return 문 등이 실행된 것과 관계없이, finally 블록은 그저 무조건 실행이 보장되는 뒷정리 코드의 역할을 하게 됩니다.

예를 들어 다음의 코드에서는 루프 카운터 변수가 정상적으로 증가될 수 있게 try/finally 문을 사용하고 있습니다.

설사 continue 문에 의해 루프 반복이 갑자기 끝났다 하더라도 루프 카운터 변수는 증가되는 코드입니다.

JavaScript
var i = 0, total = 0;
while (i < a.length) {
	try {
		if(typeof a[i] != 'number' || isNaN(a[i])) { // 만일 이것이 숫자가 아니라면
			continue; // 이 루프의 다음 반복으로 넘어간다.
		}
		total += a[i]; // 숫자라면 total에 이 숫자를 더한다.
	} finally {
		i++; // 위에서 continue 를 사용했지만 무조건 i를 증가시키도록 한다.
	}
}



또 다른 코드를 살펴봅니다.

JavaScript
// try ~ catch ~ finally 절 : 자바스크립트의 예외 처리 구문(C#/Java와 비슷)
// fake(); // 정의되지 않은 함수를 호출하여 에러가 발생
try {
	// 예외(에러)가 발생할 만한 코드를 입력
	fake(); // 정의되지 않은 함수 호출
} catch(e) {
	// 예외가 발생했을 때 실행되는 영역
	alert(e.name + ", " + e.message);
} finally {
	// 예외가 발생하던 발생하지 않던 무조건 실행되는 영역
	alert('무조건 실행되는 영역')
}


JavaScript
// 예외를 무조건 발생시킴
// throw {name : '예외 이름', message : '예외 내용'}
try {
	var a = 3;
	var b = 0;
	if(b === 0 ) {
		throw {name : 'divideByZeroException', message: '0으로 나눌수가 없습니다.'}
	}
} catch(e) {
	alert(e.name + ", " + e.message); // 에외 처리구문 (예 : 비정상 종료를 정상종료하도록 하기위함)
} finally {
	alert('프로그램을 정상종료합니다.')
}



Jaehee's WebClub