제어 흐름 예외 처리 (Control Flow with Exceptions)

제어 흐름 예외 처리 (Control Flow with Exceptions)

자바스크립트에서 예외 처리는 코드가 예상치 못한 상황에 부딪혔을 때 흐름을 조정하는 강력한 방법이다. try-catchthrow를 활용하면 에러를 잡아내고, 그에 맞춰 로직을 유연하게 다룰 수 있다. 이번에는 예외 처리를 통해 제어 흐름을 어떻게 관리할 수 있는지, 알아보려고 한다.


예외를 잘 다루면 코드가 더 안정적이고 읽기 쉽게 변한다. 단계별로 하나씩 살펴보자.


try-catch의 기본 구조

try-catch는 에러가 발생할 가능성이 있는 코드를 감싸고, 에러가 나면 그걸 잡아서 처리한다. 간단한 예시로 시작해보자:

try {
    let result = undefinedVariable;
    console.log(result);
} catch (error) {
    console.log("문제가 생겼다: " + error.message);
} // "문제가 생겼다: undefinedVariable is not defined"

try 안에서 정의되지 않은 변수를 호출하면 에러가 발생하고, catch가 그걸 잡아서 메시지를 출력한다. 이 기본 구조를 이해하면 흐름을 제어하는 첫걸음이 된다.


1. throw로 사용자 정의 에러 던지기

throw를 쓰면 특정 조건에서 의도적으로 에러를 발생시킬 수 있다. 이를 통해 흐름을 원하는 방향으로 바꿀 수 있다:

function divide(a, b) {
    if (b === 0) {
        throw new Error("0으로 나눌 수 없다!");
    }
    return a / b;
}

try {
    let result = divide(10, 0);
    console.log(result);
} catch (error) {
    console.log("에러: " + error.message);
} // "에러: 0으로 나눌 수 없다!"

0으로 나누려는 시도를 throw로 막고, catch에서 처리했다. 이렇게 하면 코드가 비정상적으로 끝나는 걸 막을 수 있다.


2. finally로 항상 실행되는 코드 추가

finally는 에러가 나든 안 나든 무조건 실행되는 블록이다. 정리 작업을 할 때 유용하다:

try {
    console.log("작업 시작");
    throw new Error("강제로 에러 발생");
} catch (error) {
    console.log("잡았다: " + error.message);
} finally {
    console.log("작업 끝, 정리 완료");
} 
// "작업 시작"
// "잡았다: 강제로 에러 발생"
// "작업 끝, 정리 완료"

finally는 에러 여부와 상관없이 마지막에 실행된다. 자원을 정리하거나 마무리 로직을 넣기에 딱 맞다.


3. 중첩된 try-catch로 세밀한 제어

try-catch를 중첩하면 에러를 더 세밀하게 다룰 수 있다:

try {
    try {
        throw new Error("내부 에러");
    } catch (innerError) {
        console.log("내부에서 잡음: " + innerError.message);
        throw new Error("외부로 전달");
    }
} catch (outerError) {
    console.log("외부에서 잡음: " + outerError.message);
} 
// "내부에서 잡음: 내부 에러"
// "외부에서 잡음: 외부로 전달"

안쪽에서 에러를 잡고, 필요하면 다시 던져서 바깥에서 처리했다. 이런 식으로 계층적으로 흐름을 조정할 수 있다.


4. 조건에 따른 흐름 분기

예외 처리를 조건문과 섞으면 흐름을 더 유연하게 만들 수 있다:

function checkValue(value) {
    try {
        if (value < 0) {
            throw new Error("음수는 안 된다");
        }
        console.log("값이 유효하다: " + value);
    } catch (error) {
        console.log("문제 발생: " + error.message);
    }
}

checkValue(5);  // "값이 유효하다: 5"
checkValue(-3); // "문제 발생: 음수는 안 된다"

조건에 따라 에러를 던지고, 그에 맞춰 흐름을 분기했다. 입력값을 검증할 때 특히 유용하다.


5. 비동기 코드에서의 예외 처리

비동기 작업에서는 try-catch를 약간 다르게 써야 한다. Promise와 함께 보자:

function fetchData() {
    return new Promise((resolve, reject) => {
        reject(new Error("데이터 가져오기 실패"));
    });
}

async function process() {
    try {
        await fetchData();
    } catch (error) {
        console.log("비동기 에러: " + error.message);
    }
}

process(); // "비동기 에러: 데이터 가져오기 실패"

async/await와 함께 쓰면 비동기 에러도 깔끔하게 잡아낸다. 비동기 흐름을 제어할 때 필수적인 방법이다.


6. 커스텀 에러 객체 활용

기본 Error 대신 커스텀 에러를 만들어 쓰면 더 구체적으로 흐름을 다룰 수 있다:

class ValidationError extends Error {
    constructor(message) {
        super(message);
        this.name = "ValidationError";
    }
}

function validate(input) {
    if (!input) {
        throw new ValidationError("입력값이 비었다");
    }
    console.log("입력값: " + input);
}

try {
    validate("");
} catch (error) {
    console.log(error.name + ": " + error.message);
} // "ValidationError: 입력값이 비었다"

커스텀 에러를 만들면 에러의 종류를 구분해서 처리하기 쉬워진다.


7. 에러를 활용한 복잡한 흐름 제어

예외를 적극적으로 활용하면 복잡한 로직도 깔끔하게 정리된다:

function processUser(user) {
    try {
        if (!user.name) {
            throw new Error("이름이 없다");
        }
        if (user.age < 18) {
            throw new Error("미성년자다");
        }
        console.log(`처리 완료: ${user.name}, ${user.age}세`);
    } catch (error) {
        console.log("처리 실패: " + error.message);
    }
}

processUser({ name: "철수", age: 20 }); // "처리 완료: 철수, 20세"
processUser({ name: "영희", age: 15 }); // "처리 실패: 미성년자다"

여러 조건을 검사하고, 문제가 생기면 바로 에러를 던져서 흐름을 중단했다. 복잡한 조건문 대신 예외로 처리하니 깔끔해졌다.


8. 성능과 가독성에 미치는 영향

예외 처리가 성능과 가독성에 어떤 영향을 주는지 보자:

- 성능: 예외 처리는 약간의 오버헤드가 있지만, 코드가 비정상적으로 끝나는 걸 막아 안정성을 높인다.

- 가독성: 긴 조건문 대신 에러를 던지고 잡으면 로직이 명확해진다.

throw로 의도적인 흐름 변경과 finally로 정리 작업을 보장하는 점이 예외 처리의 강점이다.


마무리

예외 처리는 단순히 에러를 잡는 데 그치지 않는다. try-catch, throw, finally를 통해 흐름을 세밀하게 조정하고, 비동기와 커스텀 에러까지 다루면 코드가 더 튼튼해진다. 이런 도구들을 잘 활용하면 안정적이고 깔끔한 코드를 만들 수 있다.


+ Recent posts