[ JS / TS ] 예외를 여러 방법으로 처리해보자

Lutica_·2024년 12월 25일

예외처리는?

예외(Exception)는 프로그램 실행 중에 예기치 않은 상황에서 발생하는 오류를 말한다. 이러한 예외는 프로그램의 정상적인 흐름을 방해하고, 처리되지 않으면 프로그램이 강제로 종료될 수 있다.

  • 즉, 프로그래머가 의도하지 아니한 사태를 미연에 방지하는 역할을 수행한다.
  • 예외처리를 잘하면 의도치 않은 입력,사건(Event)등을 막는 것이 예외의 역할이다.

예외/에러

개발자의 처리가능 여부를 두고 통상 구분한다.

  • 에러(Error): 주로 시스템 수준에서 발생하며, 개발자가 복구할 수 없는 문제를 말한다. 예를 들어, 메모리 부족이나 하드웨어 손상 등이 에러에 해당한다.

  • 예외(Exception): 애플리케이션 수준에서 발생하며, 개발자가 처리하고 복구할 수 있는 문제를 말한다. 예를 들어, 잘못된 사용자 입력이나 파일을 찾을 수 없는 상황 등이 있다.

에외의 종류

  • 런타임 예외 (Runtime Exception):

    실행 중에 발생하며, 주로 프로그래머의 실수로 인해 발생한다. 예: NullPointerException, ArrayIndexOutOfBoundsException.

  • 체크 예외 (Checked Exception):

    컴파일 시점에 예외 발생 가능성을 검사받아야 한다. 예: IOException, SQLException.
    단, JS/TS의 경우 Checked Exception가 없다는 사실을 알아두자.

  • 사용자 정의 예외 (Custom Exception):

    특정 상황에 맞는 예외를 개발자가 정의하여 처리할 수 있다.

  • 본 글에서는 사용자 정의 예외가 주 구문이나, try-catch구문의 경우 일반적으로 모든 예외에 가능하므로 알아두자.

예외 Handling...

이론

언급되는 방법은 아래와 같다.

  • 예외 무시: 예외를 처리하지 않고 프로그램을 계속 진행(비추천).
  • 예외 복구: 문제를 해결하고, 프로그램을 정상 상태로 되돌림.
  • 예외 전파: 예외를 호출자에게 전달하여 상위 레벨에서 처리.
  • 사용자 알림: 예외 정보를 사용자가 이해하기 쉬운 메시지로 변환.

실전

기본 예외처리

  • 아래 코드를 사용하면 에러가 발생하고 코드 실행이 중단된다.
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적 해결법

1. 사용자 정의 예외

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}`); // 사용자 정의 예외 발생: 입력 값이 비어 있습니다.
  }
}

2. unknown 타입을 활용한 안전한 예외 처리

function doSomething() {
  throw new Error("예외 발생");
}

try {
  doSomething();
} catch (e: unknown) {
  if (e instanceof Error) {
    console.log(`Error 메시지: ${e.message}`);
  } else {
    console.log("알 수 없는 예외 발생");
  }
}

JavaScript적 해결법

1. 객체 기반 예외 처리

function throwCustomError() {
  throw { message: "커스텀 에러 발생", code: 400 };
}

try {
  throwCustomError();
} catch (e) {
  console.log(e.message); // 커스텀 에러 발생
  console.log(e.code); // 400
}

2. 간단한 예외 처리

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

  • 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의 사용을 자주 할 필요가 있음을 알아두도록 하자.

profile
해보고 싶고, 하고 싶은 걸 하는 사람

0개의 댓글