예외 처리(Exception Handling) Part 1: [예외의 개념과 종류]

Frog Lemon·2025년 10월 13일
0

Java

목록 보기
4/6
post-thumbnail

1. 서론

이 글은 우아한테크코스의 테크니컬 라이팅을 목적으로, 프로그래밍을 어느 정도 경험했지만 예외(Exception) 에 대한 이해가 부족하거나 궁금한 사람들을 위해 작성되었다.
관련 코드는 Java 로 작성되었으며, 자바와 스프링 영역에서의 예외의 개념부터 시작하여 다양한 예외 처리 전략 까지 단계적으로 살펴본다.

대부분의 사람들은 프로그래밍을 하다 보면 다음과 같은 문제 상황을 한 번쯤 경험하게 된다.

  • 코드에 오타가 났다던가
  • 함수에 잘못된 값을 전달했다던가
  • 무한 루프를 만들고 실행했다던가

이러한 문제들은 프로그램의 정상적인 실행을 방해한다.
그렇다면 프로그래머들은 이런 상황을 어떻게 해결할까?
이 질문에 대한 답이 바로 예외 처리(Exception Handling) 이다.


2. 예외와 에러의 차이

예외(Exception)를 이해하기 위해서는 먼저 에러(Error) 와의 차이를 명확히 구분해야 한다. 결론부터 말하자면 차이점은 아래와 같다.

에러(Error) : 시스템의 고장으로 인한 복구 불가능한 문제
예외(Exception) : 프로그램 실행 중 발생하는 복구 가능한 문제

추가적으로 오류와 에러를 혼동하시는 분들이 있는데 프로그래밍 세계에서 일반적으로 ‘에러’와 ‘오류’는 같은 의미로 사용된다.
즉, 일상적으로 “오류가 났다”라고 말하는 것은 대부분 시스템 수준의 에러(Error) 를 의미한다.

2-1. 자바의 예외 계층 구조

자바에서는 에러와 예외를 Throwable 클래스를 최상위로 두고 시스템 수준의 에러는 Error,
프로그램 실행 중 발생하는 문제는 Exception으로 나누어 관리하고있다.

Throwable
├── Error
└── Exception
     ├── RuntimeException (Unchecked)
     └── Checked Exception

다음으로는 에러와 예외에 대한 개념을 알아보자.

2-2. 에러(Error)란 무엇인가

오류(Error)프로그램이 제어할 수 없는 심각한 문제 상황을 뜻한다.
주로 시스템 자원 부족, 가상머신(VM)의 비정상 동작, 하드웨어 장애 등의 원인으로 발생하며, 다음과 같이 세 가지 유형으로 나눌 수 있다.

🔹 컴파일 에러 (Compile-time Error)

코드의 문법이나 구조가 언어의 규칙(Java, Python 등)에 맞지 않아 프로그램이 실행조차 되지 않는 오류이다.
이러한 에러는 컴파일러나 IDE에서 자동으로 탐지되며, 메시지를 통해 비교적 쉽게 해결할 수 있다.

예시:

  • 문법 에러(Syntax Error)
  • 타입 불일치(Type Error)

🔹 런타임 에러 (Runtime Error)

프로그램 실행 도중 발생하는 예외적 상황으로, 프로그램이 중단되거나 비정상 동작하게 된다.
이번 글의 주제인 예외(Exception) 는 바로 이 런타임 시점에서 발생한다.
이러한 에러는 로그 분석이나 스택 트레이스(Stack Trace) 를 통해 원인을 파악할 수 있으며, 예외 처리 전략으로 대응이 가능하다.

예시:

  • 0으로 나누기
  • Null 참조
  • 파일 없음

🔹 논리 에러 (Logical Error)

문법적으로나 실행적으로는 문제가 없지만 프로그램의 결과가 의도와 다르게 나오는 오류이다.
예를 들어, 잔고가 0원인데 결제가 되는 상황을 생각할 수 있다.
이런 오류는 테스트와 디버깅을 통해서만 탐지할 수 있다.

예시:

  • 잘못된 알고리즘
  • 조건식 오류
  • 계산 실수

2-3. 예외(Exception)란 무엇인가

사전적으로 예외는 어떤 일에서 제외되거나 특별한 경우를 의미하는 단어이다. 그러나 프로그래밍 세계에서의 예외(Exception)는 다른 의미를 가진다.

프로그래밍에서의 예외는 프로그램 실행 중(런타임) 발생하는 오류 상황을 말한다. 예외 처리는 이러한 오류가 프로그램의 비정상 종료로 이어지지 않도록 오류를 감지하고, 이에 대한 적절한 대응 로직을 수행하는 과정이다.
즉, 예외는 “복구 가능한 오류”이며, 코드 레벨에서 처리할 수 있는 문제이다.

예외 처리의 예시

예를 들어, 파일을 열 때 해당 파일이 존재하지 않는다면 예외가 발생한다.
이 상황에서 예외를 처리하지 않으면 프로그램이 즉시 종료된다.
하지만 예외 처리를 통해 다음과 같이 대응할 수 있다.

try {
    FileReader reader = new FileReader("data.txt");
} catch (FileNotFoundException e) {
    System.out.println("파일이 없습니다. 기본 설정으로 진행합니다.");
}

위의 설명으로 사전 설명은 끝났다고 생각한다. 이제 본격적으로 예외에 대해 알아보자.

Exception 클래스

자바 프로그래밍을 경험한 사람이라면 Exception 클래스를 한 번쯤은 마주쳤을 것이다.
Exception 클래스가 어떻게 구성되어 있는지, 그리고 어떤 역할을 하는지를 자세히 살펴보자.

💡Exception.class docs 공식문서를 보는 것도 추천한다.

1. Exception 클래스의 정의

Exceptionjava.lang 패키지에 포함된 클래스이며,Throwable 클래스를 상속 받고 있고, 자바의 모든 체크 예외(Checked Exception) 의 기본 클래스이다.

package java.lang;

public class Exception extends Throwable {
    ...
}

2. Exception 클래스의 주요 생성자

Exception 클래스는 다양한 상황에서 예외를 생성할 수 있도록 여러 생성자 오버로딩을 제공한다.

생성자설명
Exception()기본 생성자. 메시지와 원인 없음
Exception(String message)예외 메시지 지정
Exception(String message, Throwable cause)메시지와 원인 예외를 함께 지정
Exception(Throwable cause)원인 예외만 지정 (메시지는 cause.toString()으로 설정됨)
Exception(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace)예외 억제 및 스택 트레이스 기록 여부 제어 (Java 7+)

3. Checked 예외 ,Unchecked 예외

위에서 Checked 예외를 언급했다. 모두가 예상했겠지만 Unchekced 예외 또한 존재하고, 두 예외의 차이점을 알아보자.

3-1. checked 예외

  • 컴파일러가 명시적 처리(try-catch 또는 throws 선언) 를 강제하는 예외이다.

  • Exception의 하위 클래스 중 RuntimeException을 상속하지 않은 것들이 이에 해당한다.

  • 예시) IOException, SQLException, FileNotFoundException

public void readFile() throws IOException {
    FileReader reader = new FileReader("data.txt"); // FileReader 생성 시 IOException 발생 가능
    // 반드시 예외 선언 필요: checked 예외는 메서드에서 직접 처리하거나 선언해야 함
}

위 코드에서 FileReader 생성 시, 생성자의 매개변수로 지정된 "data.txt" 파일이 존재하지 않거나 경로가 잘못되었다면
IOException(checked 예외)이 발생한다. 이러한 경우를 대비하기 위해 checked 예외는 메서드 내부에서 반드시 처리되어야 한다.
처리 방법은 두 가지가 있다:

  1. 메서드 내부에서 try-catch 블록으로 직접 처리
  2. 메서드 선언부에 throws IOException을 선언하여 상위 호출자에게 전달

이 선언이나 처리가 이루어지지 않으면 컴파일 오류가 발생한다.
즉, Checked 예외는 코드 어디선가 반드시 해결해야 하며, 처리하지 않고 넘어갈 수 없다.

3-1. Unchecked 예외

  • RuntimeException을 상속하는 예외이다.

  • 명시적 예외 처리 없이 발생 가능하다 (컴파일러가 강제하지 않음)

  • 주로 프로그래밍 오류로 발생한다.

  • 예시) NullPointerException, IllegalArgumentException, ArrayIndexOutOfBoundsException

public void divide(int a, int b) {
    // b가 0이면 ArithmeticException 발생 (Unchecked 예외)
    int result = a / b;
    System.out.println("결과: " + result);
}

위 코드에서 정수를 0으로 나누면 ArithmeticException이 발생한다.
하지만 예외가 발생해도 애플리케이션이 자동으로 종료되지는 않으며, 컴파일러도 이를 체크하지 않기 때문에 try-catch 선언이 필수는 아니다.

즉, Unchecked 예외는 발생 가능성이 있어도 처리해야 한다는 강제성은 없다.
다만 필요하다면 try-catch로 처리할 수 있으며, 코드 설계 시 예외가 발생하지 않도록 사전에 오류를 방지하는 것이 권장된다.

❓왜 예외를 두가지 상황으로 나누었을까?

그에 대한 해답은 자바의 아버지라 불리는 James Gosling 과의 대화에서 알 수 있다.

💡제임스 고슬링과의 인터뷰

James Gosling이 체크 예외 설계 의도를 설명한 인터뷰가 있으며, 그 중 일부가 다음과 같다:

“In Java you can ignore exceptions, but you have to willfully do it. You can’t accidentally say, ‘I don’t care.’ You have to explicitly say, ‘I don’t care.’”
— Artima, “Failure and Exceptions — A Conversation with James Gosling, Part II”
artima.com

이 발언은 “예외 처리를 무시하려면 명시적으로 그렇게 해야 한다”는 의미다.
즉, 자바는 컴파일러 수준에서 예외 처리를 강제함으로써, 개발자가 단순히 “귀찮아서” 예외를 무시하는 일을 방지한다.
이것이 바로 Gosling이 추구한 안정성과 명시성 중심의 설계 철학이다.

또한, 같은 인터뷰에서 Gosling은 C 언어에서 오류 코드를 무시하는 관습을 지적하면서 Java가 예외 처리를 강제한 이유를 설명한다.

결국 자바는 이러한 철학을 기반으로,예외를 두 가지로 구분했다.
컴파일러가 강제하는 “체크 예외(Checked Exception)”
개발자 선택에 맡기는 “언체크 예외(Unchecked Exception)” 로 말이다.

profile
도전하며 굴러가는 돌멩이, 인생 마라톤 중 😎

0개의 댓글