예외 처리는 프로그램을 작성할 때 필수적인 부분이지만, 어떻게 예외를 처리할지에 대한 기준을 명확히 해야 합니다.
잘못된 예외 처리 방식은 오히려 코드의 가독성을 떨어뜨리고 성능을 저하시킬 수 있다.
이 글에서는 예외를 잘못 사용했을 때 발생할 수 있는 문제점과 올바른 예외 처리 방법을 정리해보겠습니다.
예외는 예외적인 상황에서만 사용하자
예외를 활용한 분기 처리는 절대로 사용하지 말아야 합니다. 예외는 예측 불가능한 문제를 처리하는데 사용해야 하며, 일상적인 제어 흐름에는 사용되지 않아야 합니다.
예외를 활용한 조건문 대체 (잘못된 반복문 종료 방식)
try {
int[] arr = {1, 2, 3};
int i = 0;
while (true) {
System.out.println(arr[i]); // 정상적으로 배열을 순회하는 듯 보이지만
i++;
}
} catch (ArrayIndexOutOfBoundsException e) {
// 예외가 발생하면 반복문을 종료하는 방식 (잘못된 방식)
System.out.println("반복문 종료");
}
- 문제점
- 예외를 정상적인 종료 조건으로 사용: 배열의 끝을 초과하면 예외가 발생하는데, 이는 정상적인 흐름에서 사용할 방법이 아닙니다.
- 버그와 정상 흐름 구분 불가: 예외가 발생하더라도, 이를 버그로 인식하지 못하고 정상적인 흐름처럼 처리할 수 있습니다.
- 성능 저하: 예외 발생 시 스택 트레이스를 생성하는 비용이 크기 때문에, 성능에 영향을 미칠 수 있습니다.
좋은 예제: if-else로 정상 흐름 제어
// 잘못된 입력값을 예외로 처리하는 대신, 사전 검사로 방지
public void process(int value) {
if (value < 0) {
System.out.println("음수는 허용되지 않습니다.");
return;
}
// 정상적인 로직 처리
}
나쁜 예제: 예외를 이용한 흐름 제어
try {
int result = divide(10, 0); // 0으로 나누면 예외 발생
} catch (ArithmeticException e) {
System.out.println("잘못된 입력입니다."); // 예외를 분기처리로 활용
}
예외는 진짜 예외적인 상황에서만 사용하라!
예외는 파일이 없는 경우, 네트워크 오류, 배열 인덱스 초과 등의 정상적인 흐름에서 예측하기 어려운 문제를 처리할 때 사용해야 합니다.
정상적인 조건이라면 if-else 문을 사용하는 것이 더 바람직합니다.
- 예외는 예상치 못한 상황에서, 즉 파일이 없거나, 네트워크 오류가 발생하거나, 배열 인덱스를 초과한 경우와 같은 비정상적인 흐름을 처리하는 데 사용해야 합니다.
- 정상적인 흐름에서는 if-else문을 사용하는 것이 훨씬 가독성이 좋고, 성능에도 유리합니다.
애플리케이션 실패 시 발생하는 예외의 두 가지 유형
- 애플리케이션에서 실패가 발생했을 때 두 가지 유형의 예외가 발생할 수 있다
- 폴백(fallback) 로직이 정의된 경우와 애플리케이션에서 처리할 수 없는 예외입니다.
폴백에 대한 정의가 되어 있는 경우
- 예를 들어 내가 현재 진행하고 있는 주식 조회 애플리케이션의 경우 거기에서 실패했으면 가장 최근에 서버에 기록된 가장 최근 가격을 그냥 반환한다라는 케이스에서는 실패했을 경우에 대한 실행 흐름이 정의가 되어 있다.
- 실패했을 경우 실행 흐름이 정의가 되어 있는 경우 캐치로 예외를 잡고 로그를 남긴 다음 캐시나 DB 에 저장된 가장 최신 데이터를 읽어온다는 로직을 수행한다.
폴백 로직이 없을 경우
- DB 자체에 문제가 생겨 쿼리가 안되는 상황은 애플리케이션 자체에서 처리할 수 없는 예외이다.
- 해당 예외를 상위 레이어로 전파시켜 API reponse 를 500 으로 응답하여야 한다.
예외의 종류와 사용 기준
- Object : 자바에서 기본형을 제외한 모든 것을 객체다. 예외도 객체이다. 모든 객체의 최상위 부모는 Object 이므로 예외의 최상위 부모도 Object 이다.
- Throwable : 최상위 예외이다. 하위는 Exception 과 Error 과 있다.
- 프로그램 실행시 발생할 수 있는 프로그램 오류는 Exception 과 Error 가 있다.
- Error : 자바 어플리케이션 관점에서 봤을 때 JVM 이 핸들링 할 수 없는 심각한 시스템 수준의 예외로 애플리케이션 복구 할 수 없는 상태를 의미한다. 애플리케이션 개발자는 이 예외를 잡으려고 해서는 안된다.
- ex) OS 쓰레드 고갈, OS 의 네트워크 인터페이스 자체에 생긴 문제 , OutOfMemoryError
- Exception : JVM 이 핸들링 할 수 있는 애플리케이션 예외로 개발자가 적절히 처리할 수 있는 오류 상황을 의미한다.
- 예외 케이스에 대한 비즈니스 flow 를 정의해 다룰 수 있다.
- 체크 예외
- 애플리케이션 로직에서 사용할 수 있는 최상위 예외이다.
- Exception 과 그 하위 예외는 모두 컴파일러가 체크하는 체크예외이다. 단 RuntimeException 은 예외로 한다.
- RuntimeException : 언체크예외, 런타임 예외
- 컴파일러가 체크하지 않는 언체크 예외이다.
- RuntimeException 과 그 자식 예외는 모두 언체크 예외이다.
- RuntimeException 이름을 따라서 RuntimeException 과 그 하위 언체크 예외를 런타임예외라고 많이 부른다.
- ChekcedException 과 UnCheckedException 차이
- 체크예외와 런타임 예외의 차이는 예외를 처리할 수 없을때 밖으로 던지는 부분에 있다. 이 부분을 필수로 선언해야 하는가 생략할 수 있는가 차이다.
복구 할 수 있는 상황에서는 검사 예외를 프로그래밍 오류에는 런타임 예외를 사용하라.
기본적으로 런타임예외를 사용하자!
가능하면 각 메서드나 함수에서 던지는 예외들을 문서화해두는게 바람직하다.
체크예외는 비즈니스 로직상 너무 중요해서 의도적으로 던지는 예외메만 사용한다. (checked exception 은 호불호의 영역이라 여러 커뮤니티에서도 다양한 의견이 있다.)
이 경우 해당 예외를 잡아서 반드시 처리해야하는 문제일때만 체크예외를 사용해야 한다. 예를 들어 다음과 같은 경우가 있다.
- 체크 예외 예)
- 계좌 이체 실패 예외
- 결제시 포인트 부족 예외
- 로그인 ID, PW 불일치 예외
- 물론 이 경우에도 100% 체크 예외를 만들지 않고 runtime 에러로 만들고 문서화해두는것도 더 좋은 방법이 될수 있다.
- 다만 계좌이체 실패처럼 매우 중요한 문제는 개발자가 실수로 예외를 놓치면 안된다고 판단할 수 있다.
- 이 경우는 체크예외로 만들어 두면 컴파일러를 통해 놓친 예외를 인지할 수 있다.
호출하는 쪽에서 복구하리라 여겨지는 상황이라면 검사 예외를 사용해야한다. (이펙티브 자바)
- 일반적으로 협업에서는 런타임 예외를 사용함
- 대부분의 예외는 상대 네트워크 서버 문제 발생으로 통신불가, db 서버 문제로 접속 불가와 같이 대부분 예외를 잡아 해결할 수 없음.
예외를 잡아서 재호출시 같은 오류 반복됨. 이런 오류는 오류페이지로 대체 및 개발자를 위해 로그남기기.
왜냐하면 체크예외의 경우 예외를 던지거나 처리를 무조건 해줘야하기 때문에 불필요한 코드가 생겨남.
- 대부분의 예외는 상대 네트워크 서버 문제 발생으로 통신불가, db 서버 문제로 접속 불가와 같이 대부분 예외를 잡아 해결할 수 없음.
- 체크 예외를 사용하는 경우
- 해당 예외를 복구할 수 있는 경우가 있는 경우 사용.
- fallback 로직이 정의되어 있는 경우는 체크 예외를 주어 API 호출자에게 그 상황을 회복해내라고 강제할 수도 있다. (비즈니즈 표현의 일부)
- 비즈니스적으로 너무 중요해서 예외를 반드시 잡아야 하는 경우
- 계좌 이체 실패 시
- 계좌 이체 실패 시
- 해당 예외를 복구할 수 있는 경우가 있는 경우 사용.
결론
- 예외는 진짜 예외적인 상황에서만 사용해야 합니다.
- 정상적인 조건문 제어는 if-else 문으로 처리하고, 예외는 예측 불가능한 문제 상황에서만 활용해야 합니다.
- 체크 예외와 런타임 예외의 차이를 명확히 이해하고, 상황에 맞는 예외 처리를 설계하는 것이 중요합니다.
- 비즈니스에서 처리할 수 없는 예외는 에러라고 보고 fallback 로직들을 묶을 수도 있겠지만, 예외는 각 비즈니스 맥락을 담고 있기 때문에 로직에서 처리하는 게 더 좋다. (이 문장에서 예외와 에러는 같은 예외 타입이더라도 맥락상의 논리적 분류입니다)
이 글이 여러분의 코드 작성 시, 예외 처리에 대한 올바른 접근법을 선택하는 데 도움이 되길 바랍니다.
https://newfangled.tistory.com/51
[JAVA] Error 와 Exception
예외 클래스의 계층 구조자바에서는 실행 시 발생할 수 있는 오류 (Excepton과 Error)를 클래스로 정의하였다.모든 클래스의 조상인 Object 클래스가 최상단에 있고 Exception과 Error 클래스의 자손이
newfangled.tistory.com
이펙티즈 자바 예외 챕터 참고
'JAVA' 카테고리의 다른 글
바이트코드 조작 (0) | 2025.03.24 |
---|---|
클래스 로더란? (0) | 2025.03.24 |
JVM, JDK, JRE 의 차이, JVM의 동작방식 (0) | 2025.03.23 |
네트워크 - 프로그램1 (1) | 2025.01.29 |
자바 - IO 기본 (buffer) (1) | 2025.01.28 |