예외(Exception)는 프로그램 실행 중에 예기치 않은 상황에서 발생하는 오류를 말한다. 이러한 예외는 프로그램의 정상적인 흐름을 방해하고, 처리되지 않으면 프로그램이 강제로 종료될 수 있다.
프로그래머가 의도하지 아니한 사태를 미연에 방지하는 역할을 수행한다.개발자의 처리가능 여부를 두고 통상 구분한다.
에러(Error): 주로 시스템 수준에서 발생하며, 개발자가 복구할 수 없는 문제를 말한다. 예를 들어, 메모리 부족이나 하드웨어 손상 등이 에러에 해당한다.
예외(Exception): 애플리케이션 수준에서 발생하며, 개발자가 처리하고 복구할 수 있는 문제를 말한다. 예를 들어, 잘못된 사용자 입력이나 파일을 찾을 수 없는 상황 등이 있다.
런타임 예외 (Runtime Exception):
실행 중에 발생하며, 주로 프로그래머의 실수로 인해 발생한다. 예:
NullPointerException,ArrayIndexOutOfBoundsException.
체크 예외 (Checked Exception):
컴파일 시점에 예외 발생 가능성을 검사받아야 한다. 예:
IOException,SQLException.
단, JS/TS의 경우 Checked Exception가 없다는 사실을 알아두자.
사용자 정의 예외 (Custom Exception):
특정 상황에 맞는 예외를 개발자가 정의하여 처리할 수 있다.
본 글에서는 사용자 정의 예외가 주 구문이나, try-catch구문의 경우 일반적으로 모든 예외에 가능하므로 알아두자.
언급되는 방법은 아래와 같다.
throw new Error("에러"); // -- 예외 전파
try-catch를 사용하면 발생한 에러를 잡을 수 있다.function aa() {
throw 1;
}
function bb() {
throw new Error("에러임");
}
try {
aa();
} catch (a) {
console.log(a); // 1 -- 예외 무시
}
try {
bb();
} catch (e) {
console.log(e);
/*
Error: 에러임
at bb (<anonymous>:5:8)
at <anonymous>:15:3
*/
}
Error 객체를 사용하지 않아도 예외 처리를 다른 방식으로도 할 수 있다. 예를 들어, type이나 object를 사용해서 처리할 수 있다.TypeScript에서는 Error 클래스를 확장하여 사용자 정의 예외를 만들 수 있다.
class CustomError extends Error {
constructor(message: string) {
super(message);
this.name = "CustomError";
}
}
function validateInput(input: string) {
if (input.trim() === "") {
throw new CustomError("입력 값이 비어 있습니다.");
}
}
try {
validateInput("");
} catch (e) {
if (e instanceof CustomError) {
console.log(`사용자 정의 예외 발생: ${e.message}`); // 사용자 정의 예외 발생: 입력 값이 비어 있습니다.
}
}
unknown 타입을 활용한 안전한 예외 처리function doSomething() {
throw new Error("예외 발생");
}
try {
doSomething();
} catch (e: unknown) {
if (e instanceof Error) {
console.log(`Error 메시지: ${e.message}`);
} else {
console.log("알 수 없는 예외 발생");
}
}
function throwCustomError() {
throw { message: "커스텀 에러 발생", code: 400 };
}
try {
throwCustomError();
} catch (e) {
console.log(e.message); // 커스텀 에러 발생
console.log(e.code); // 400
}
function divide(a, b) {
if (b === 0) {
throw "0으로 나눌 수 없습니다!";
}
return a / b;
}
try {
console.log(divide(10, 0));
} catch (e) {
console.log(e); // 0으로 나눌 수 없습니다!
}
Fail-safe는 실패 하더라도 안전한 이라는 의미를 가지고 있다.
DB Connection과 같이 자원을 할당하면 중간에 예외 혹은 에러가 발휘하는 경우, 그 자원이 회수되지 않아 문제가 되곤 한다.
그러한 경우를 미연에 방어하기 위해서, 가끔 언어들에게는 finally, 혹은using과 같은 try-with-resources Pattern을 지원한다.
가끔이라 기술했지만, 기본적으로 mordern하다면 대부분 지원해야만 한다고 본다.
js에서는 아래와 같은 방법이 있다.
const db = await pool.connect();
try{
...
}catch(e){
...
}finally{
db.relese();
}
C#(using)이나 Java(try), Kotlin(use)과 같은 경우 IDispose와 같이 자원해제 interface가 구현된 경우 사용후 끝나는 경우 자동으로 실행해준다.
그 지점은 using/try/use의 (realm이라 지칭되는) braket 이 종결되는 지점이며, 실행되는 함수는 자원 해제 함수이다. (비동기 포함.)
ts라면, using구문을 사용할 수 있다. 이때 [Symbol.dispose]를 구현하여야 하는데, 사용예는 아래와 같다.
{
using db = new DatabaseConnection(); // 블록 끝나면 알아서 dispose 됨
db.query("SELECT...");
} // <--- 여기서 자동 해제 (Symbol.dispose)
Type을 사용하던, 동적언어의 특성을 사용하던, Excetption을 사용하여 개발자나 유저에게 불원하는 사태가 일어남을 알리는 일은 매우 중요하고, 그것은 KPI의 요소중 하나로 작용함을 알아두어야 한다.
예외로부터 안전한프로그래밍을 실현하기 위한 Try-Catch-Finally의 사용을 자주 할 필요가 있음을 알아두도록 하자.