[JAVA] 예외 처리
예외 처리(Exception Handling)란, 프로그램 실행 중 예기치 않은 상황이 발생했을 때, 그 상황을 감지하고 적절하게 대응함으로써 프로그램의 흐름을 안정적으로 유지하는 기술을 말한다. 예외는 정상적인 처리 흐름에서 벗어난 오류 상황을 나타내며, 이를 적절히 처리하지 않으면 프로그램은 비정상적으로 종료된다.
예외 처리는 단순한 오류 회피 기법이 아니라, 시스템의 복원력과 신뢰성을 보장하는 핵심적인 설계 요소다.
2. 예외 처리의 기원과 발전
프로그래밍 초기에는 예외 처리라는 개념 자체가 구조화되어 있지 않았다. 예컨대 C 언어와 같은 저수준 언어에서는 함수의 반환값으로 오류 상태를 전달했고, 개발자가 이를 직접 확인하여 분기 처리해야 했다.
하지만 이 방식은 다음과 같은 문제가 있었다.
- 리턴값 확인을 누락하면 오류를 놓치기 쉬움
- 정상 로직과 오류 처리 코드가 혼재되어 가독성이 떨어짐
- 복잡한 예외 흐름을 정교하게 표현하기 어려움
자바(Java)는 이러한 문제를 해결하기 위해, 예외를 객체로 표현하고, try-catch-finally
구문을 통해 정해진 방식으로 흐름을 분리하는 객체지향 예외 처리 모델을 도입하였다. 이로써 안정성, 명료성, 유지보수성을 동시에 확보할 수 있게 되었다.
3. 자바의 예외 처리 구조
자바에서 예외가 발생하면, 해당 상황을 나타내는 예외 객체(Exception Object)가 생성되며, 이 객체는 호출 스택을 따라 전파된다. 적절한 catch 블록에서 이를 처리하지 않으면 프로그램은 종료된다.
자바의 기본적인 예외 처리 구조는 다음과 같다.
try {
// 예외가 발생할 수 있는 코드
} catch (ExceptionType e) {
// 예외 발생 시 실행할 코드
} finally {
// 예외 발생 여부와 무관하게 항상 실행되는 코드
}
구성 요소
- try: 예외가 발생할 가능성이 있는 코드 블록
- catch: 특정 예외가 발생했을 때 실행되는 코드 블록
- finally: 예외 발생 여부와 관계없이 항상 실행되는 블록 (자원 해제에 주로 사용)
4. 예외 클래스의 계층 구조
자바의 예외는 Throwable
클래스를 루트로 하는 계층 구조를 갖는다. 이 구조는 크게 다음과 같이 나뉜다:
- Error: 시스템 수준의 심각한 오류 (예:
OutOfMemoryError
,StackOverflowError
)로, 개발자가 직접 처리하지 않는다. - Exception: 애플리케이션 수준에서 발생하는 오류로, 개발자가 명시적으로 처리해야 한다.
Exception
은 다시 체크 예외(Checked Exception)과 언체크 예외(Unchecked Exception)로 구분된다.
4.1 체크 예외
Checked Exception은 컴파일 타임에 반드시 예외 처리를 강제당하는 예외다. 주로 외부 환경에 의존하는 작업 중 발생하며, 반드시 try-catch
또는 throws
를 통해 명시적으로 처리해야 한다.
대표 유형
- 파일 입출력:
IOException
- 데이터베이스 연결:
SQLException
- 클래스 로딩:
ClassNotFoundException
예제
public void readFile(String path) throws IOException {
BufferedReader reader = new BufferedReader(new FileReader(path));
String line = reader.readLine();
reader.close();
}
주요 특징
- 컴파일러가 예외 처리 여부를 검사함
- 명시적 처리 또는 전파 필요
- 예측 가능한 예외 흐름 설계에 유리
4.2 언체크 예외
Unchecked Exception은 컴파일 타임에 처리 여부가 강제되지 않는 예외로, 주로 프로그래머의 실수나 잘못된 로직에서 기인한다. 조건 검사를 통해 사전에 예방하는 것이 바람직하다.
대표 유형
NullPointerException
ArrayIndexOutOfBoundsException
IllegalArgumentException
예제
public int divide(int a, int b) {
return a / b; // b가 0이면 ArithmeticException 발생
}
주요 특징
- 처리 여부는 개발자가 판단
- 런타임 오류의 주요 원인
- 배경 지식과 경험에 따라 예방 수준이 높아짐
5. 코드 품질을 높이기 위한 예외 원칙
예외 처리는 프로그램의 안정성을 보장하기 위한 중요한 기술이지만, 잘못된 사용은 오히려 코드 품질을 악화시킬 수 있다. 다음은 예외 처리 시 반드시 지켜야 할 핵심 원칙들이다.
5.1 의미 있는 예외 메시지 작성
예외 메시지는 문제의 원인과 위치를 파악하는 데 필수적인 정보다. 가능한 한 문맥을 포함한 구체적인 메시지를 작성해야 한다.
catch (IOException e) {
throw new RuntimeException("파일 읽기 실패: " + e.getMessage(), e);
}
5.2 빈 catch 블록 금지
빈 catch 블록은 예외를 무시하며, 예외를 무시하는 것은 문제 은폐로 이어지고, 디버깅을 어렵게 만든다.
catch (Exception e) {
// 금지: 아무 처리 없이 예외 무시
}
정말 무시해야 할 경우라도 명확한 주석과 로그 기록은 필수다.
catch (SocketTimeoutException e) {
logger.warn("일시적 네트워크 지연 발생: " + e.getMessage());
}
5.3 흐름 제어 수단으로 사용 금지
예외는 비정상적인 상황에서만 사용되어야 하며, 흐름 제어 상황에서는 이를 예외가 아닌 조건문으로 처리해야 한다.
// 중복 체크 시 예외 사용
try {
userRepository.save(new User(username)); // 중복되면 예외 발생
} catch (DataIntegrityViolationException e) {
System.out.println("이미 존재하는 사용자입니다.");
}
// 사용자 중복은 예측 가능한 상황으로 흐름 제어로 처리하는게 바람직하다.
if (userRepository.existsByUsername(username)) {
System.out.println("이미 존재하는 사용자입니다.");
} else {
userRepository.save(new User(username));
}
5.4 예외 처리는 구체적으로
가능하면 구체적인 예외 타입을 명시해야 한다. Exception
만으로 처리하면 의도치 않은 예외까지 모두 잡게 되어 문제 추적이 어려워진다.
try {
// ...
} catch (IOException e) {
// 입출력 처리
} catch (SQLException e) {
// DB 처리
}
6. 자바에서의 예외 처리 진화
자바는 실제 개발 환경에서 발생하는 문제들을 해결하기 위해 예외 처리 기능을 지속적으로 개선해왔다. 그중 대표적인 변화는 다음과 같다.
6.1 try-with-resources (Java 7)
AutoCloseable
인터페이스를 구현한 자원을 자동으로 해제해주는 문법. finally
없이도 안정적으로 자원을 닫을 수 있다.
try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
System.out.println(br.readLine());
} catch (IOException e) {
e.printStackTrace();
}
6.2 다중 예외 처리 (Java 7)
하나의 catch 블록에서 여러 예외 타입을 동시에 처리할 수 있어, 중복 코드를 줄이고 가독성을 높일 수 있다.
catch (IOException | SQLException e) {
logger.error("예외 발생: " + e.getMessage());
}
6.3 record를 활용한 예외 정보 구조화 (Java 14~)
record
를 활용해 예외와 함께 전달할 부가 정보를 구조화할 수 있다. 특히 API나 비즈니스 로직에서 유용하다.
public record ErrorDetail(String code, String message) {}
public class DetailedException extends RuntimeException {
private final ErrorDetail detail;
public DetailedException(ErrorDetail detail) {
super(detail.message());
this.detail = detail;
}
public ErrorDetail getDetail() {
return detail;
}
}
7. 사용자 정의 예외 클래스
복잡한 도메인 로직에서는 자체적인 예외 클래스 정의가 유지보수성과 가독성을 높이는 데 효과적이다.
public class InvalidUserInputException extends RuntimeException {
public InvalidUserInputException(String message) {
super(message);
}
}
if (!isValid(input)) {
throw new InvalidUserInputException("입력값이 유효하지 않습니다.");
}
8. 결론
자바의 예외 처리 시스템은 단순한 에러 캐치 수단이 아니라, 시스템의 회복력과 예측 가능성을 확보하기 위한 메커니즘이며, 예외를 구조적으로 설계하고, 적절하게 처리하는 것은 곧 코드 품질, 사용자 경험, 서비스 신뢰도 전반에 긍정적인 영향을 준다.
예외는 무조건 잡는 것이 목적이 아니며 잡아야 할 예외만 명확하게 처리하는 것이 좋은 코드라고 할 수 있다.
'백엔드 > JAVA' 카테고리의 다른 글
[JAVA] 흐름 제어 문법 (2) | 2024.12.17 |
---|---|
[JAVA] 자료형 (0) | 2024.12.11 |
[JAVA] JVM(Java Virtual Machine) (1) | 2024.11.21 |
[JAVA] 클래스 타입 및 사용 종류 (0) | 2024.11.13 |
[JAVA] 개념 정리 (4) | 2024.11.12 |