[Java] 예외(Exception)의 개념과 적절한 예외 처리

thezz9·2025년 3월 23일
1

서론: 예외 처리가 부족할 때 생기는 문제

최근 마트 카운터 아르바이트를 시작하면서, 적절한 예외 처리가 되지 않아 시스템이 멈추는 문제를 경험했다.

편의점이 아닌 일반 마트의 포스(POS) 프로그램은 오래전에 개발된 경우가 많고, 이후 추가된 기능이 기존 시스템에 억지로 덧붙여지는 경우도 많다.

예를 들어, 물품을 스캔한 후 취소하면 수량이 0개가 되고, 결제 금액도 0원으로 변경된다.
이 상태에서 카카오페이, 네이버페이, 앱카드 등 "기타결제" 메뉴를 누르면 모든 버튼이 먹통이 되고 프로그램을 재시작해야 하는 문제가 발생한다.

이 문제를 보고 바로 떠올랐던 것은 예외 처리가 부족하다는 점이었다.
포스 프로그램이 개발될 당시에는 지금처럼 다양한 결제 방식이 없었을 것이고, 이후 기능이 추가되면서 적절한 예외 처리가 되지 않아 이런 문제가 발생한 것으로 보인다.

이처럼 예외 처리가 제대로 이루어지지 않으면 프로그램이 비정상적으로 종료되거나, 기능이 멈추는 치명적인 문제가 발생할 수 있다.

이번 글에서는 Java에서 예외(Exception)의 개념과, 적절한 예외 처리가 왜 중요한지 살펴보겠다.


1. 예외(Exception)란?

예외(Exception)는 프로그램 실행 중 예상하지 못한 상황이 발생하여 정상적인 흐름을 방해하는 사건을 의미한다.
일반적으로 프로그램은 개발자가 의도한 대로 순차적으로 실행되지만, 실행 중 다양한 원인으로 인해 오류가 발생할 수 있다.

예를 들어, 사용자가 입력해야 할 값을 입력하지 않거나, 존재하지 않는 파일을 열려고 하거나, 배열의 범위를 초과하는 인덱스에 접근하는 등의 상황이 있을 수 있다.
이러한 문제가 발생하면 프로그램이 갑자기 종료되거나, 예상치 못한 동작을 수행할 가능성이 높아진다.

Java에서는 이러한 예외를 자동으로 감지하고 개발자가 적절히 처리할 수 있도록 지원하는 예외 처리(Exception Handling) 기능을 제공한다.
예외 처리를 올바르게 하면, 프로그램이 강제 종료되지 않고 원하는 대로 동작하도록 유도할 수 있다.

즉, 예외 처리는 단순히 오류를 피하는 것이 아니라, 프로그램의 안정성을 높이고, 사용자 경험을 개선하는 중요한 요소라고 할 수 있다.


2. 예외의 종류

Java에서 예외는 크게 Checked ExceptionUnchecked Exception 두 가지로 나뉜다.
이 둘을 구분하는 핵심 기준은 예외 처리가 필수인지 여부다.

예외 유형상속 구조필수 처리 여부예시
Checked ExceptionThrowableException예외 처리가 필수 (try-catch 또는 throws)IOException, SQLException
Unchecked ExceptionThrowableExceptionRuntimeException예외 처리가 필수가 아님NullPointerException, IllegalArgumentException
RuntimeExceptionUnchecked Exception의 하위 개념RuntimeException을 직접 상속받은 예외ArithmeticException, IndexOutOfBoundsException

3. Checked Exception vs Unchecked Exception 차이점

Checked Exception (확인된 예외)

  • 예외 처리가 필수
  • Exception을 상속받지만, RuntimeException을 제외한 예외
  • 주로 외부 리소스(파일, 네트워크, DB 등) 사용 시 발생
  • try-catch 또는 throws로 반드시 처리해야 함

파일 읽기 (IOException)

import java.io.*;

public class CheckedExceptionExample {
    public static void main(String[] args) {
        try {
            FileReader file = new FileReader("test.txt"); // 파일이 없으면 FileNotFoundException 발생
            file.read();
            file.close();
        } catch (IOException e) { // 반드시 예외 처리해야 함
            System.out.println("파일을 읽는 중 오류 발생: " + e.getMessage());
        }
    }
}

💡 IOException컴파일 타임에 체크되는 예외라서 예외 처리를 강제한다.
(파일이 없을 가능성이 있으므로 대비가 필요함)


Unchecked Exception (확인되지 않은 예외)

  • 예외 처리가 선택 사항
  • RuntimeException을 상속받은 예외
  • 주로 개발자의 코드 실수로 인해 발생
  • try-catch로 잡을 수도 있지만, 근본적으로 코드 수정이 필요함

NullPointerException

public class UncheckedExceptionExample {
    public static void main(String[] args) {
        String text = null;
        System.out.println(text.length()); // NullPointerException 발생
    }
}

💡 NullPointerExceptionRuntimeException이므로 예외 처리가 필수가 아님
(try-catch로 잡는 것이 아니라, NPE 발생을 예방하는 것이 중요)


RuntimeException (런타임 예외)

  • Unchecked Exception의 하위 개념
  • RuntimeException을 직접 상속받은 예외들
  • 대표적인 예시:
    • ArithmeticException (0으로 나누기)
    • NullPointerException (null 접근)
    • ArrayIndexOutOfBoundsException (배열 인덱스 초과)

ArrayIndexOutOfBoundsException

public class RuntimeExceptionExample {
    public static void main(String[] args) {
        int[] numbers = {1, 2, 3};
        System.out.println(numbers[5]); // ArrayIndexOutOfBoundsException 발생
    }
}

💡 런타임 예외는 try-catch로 잡는 것이 아니라, 올바른 코드 작성이 우선이다.
(배열 범위를 초과하지 않도록 미리 체크해야 함)

(⭐+추가) Unchecked ExceptionRuntimeException의 차이를 잘 모르겠다?

  • Unchecked Exception: RuntimeException과 그 자식 예외들 + Error까지 포함하는 개념이다.
    즉, 프로그램 실행 중 개발자가 예측하지 못한 오류들이 발생할 수 있는 모든 예외를 포함한다.
  • RuntimeException: Unchecked Exception의 일부로, 개발자의 실수로 인해 발생하는 논리적인 오류를 뜻한다.
    (NullPointerException, IndexOutOfBoundsException 등)

쉽게 말해, 모든 RuntimeExceptionUnchecked Exception이지만, 모든 Unchecked ExceptionRuntimeException은 아니다.
(Error 같은 시스템적 오류도 Unchecked Exception에 포함되기 때문)


4. 예외 처리 방법 (try-catch, throws, if문 활용)

try-catch 문으로 예외 처리

예외가 발생할 가능성이 있는 코드를 try 블록 안에 넣고, catch에서 처리한다.

try {
    int result = 10 / 0; // ArithmeticException 발생
} catch (ArithmeticException e) {
    System.out.println("0으로 나눌 수 없습니다.");
    e.printStackTrace();  // 예외 발생 위치와 세부 정보 출력
}

💡 예외를 잡아서 프로그램이 강제 종료되지 않도록 할 수 있다.


throws 키워드로 예외 전가

메서드에서 예외를 직접 처리하지 않고, 호출한 쪽에서 처리하도록 넘길 수 있음

public void readFile() throws IOException {
    FileReader file = new FileReader("test.txt"); // FileNotFoundException 발생 가능
    file.read();
}

💡 이 메서드를 호출하는 쪽에서 try-catch를 사용해야 한다.

try {
    readFile();
} catch (IOException e) {
    System.out.println("파일을 읽는 중 오류 발생");
    e.printStackTrace();  // 예외 발생 위치와 세부 정보 출력
}

(⭐+추가) e.printStackTrace()는 예외가 발생한 위치와 세부 정보를 출력하여 디버깅에 유용하다. 하지만 운영 환경에서는 불필요하게 예외 세부 정보를 노출하지 않도록 주의해야 한다. 예외 로그는 개발 환경에서 디버깅 목적으로 사용하고, 운영 환경에서는 적절한 예외 메시지만 출력하는 것이 좋다.


if 문으로 예외 예방 (사전 방어 코드)

예외가 발생하기 전에 조건문을 이용해 사전 방어할 수 있다.

public void divide(int a, int b) {
    if (b == 0) {
        System.out.println("0으로 나눌 수 없습니다.");
        return;
    }
    System.out.println(a / b);
}

💡 if 문으로 예외 발생을 막으면 불필요한 try-catch를 줄일 수 있다.


5. RuntimeException이 필수 처리가 아닌 이유

RuntimeException은 개발자의 실수로 발생

  • NullPointerException, ArrayIndexOutOfBoundsException 같은 예외는 버그로 인한 것이므로,try-catch로 잡기보다는 코드를 수정해서 해결해야 한다.

잘못된 예시

try {
    String text = null;
    System.out.println(text.length()); // NullPointerException 발생
} catch (NullPointerException e) {
    System.out.println("NPE 발생!");
}

올바른 예시

if (text != null) {
    System.out.println(text.length());
} else {
    System.out.println("문자열이 null입니다.");
}

CheckedException은 외부 리소스 문제이므로 대비가 필요

  • IOException, SQLException 등은 외부 환경 문제(DB, 파일, 네트워크 등)로 인해 발생할 수 있으므로, 개발자가 대비해야 한다.
try {
    FileReader file = new FileReader("test.txt");
} catch (FileNotFoundException e) {
    System.out.println("파일이 존재하지 않습니다.");
}

💡 외부 리소스는 항상 존재 여부를 체크해야 한다!


6. 결론: 언제 try-catch를 사용할까?

Checked Exception (예외 처리 필수)

  • IOException, SQLException 등 외부 환경과 관련된 예외

Unchecked Exception (예외 처리 선택)

  • NullPointerException, ArrayIndexOutOfBoundsException 등은 코드를 수정하는 것이 우선

try-catch보다는 사전 방어 코드(if문) 활용이 더 중요

  • 예외가 발생하기 전에 조건문을 활용해 방어하면 더 안전한 코드 작성 가능

즉, RuntimeException은 try-catch로 해결하는 것이 아니라, 발생하지 않도록 예방하는 것이 핵심이다.


느낀점

예외 처리에 대해 알아보면서, IDE의 자동 완성이나 경고 메시지에만 너무 의존했던 것은 아닐까 하는 생각이 들었다.

특히 Checked Exception의 경우, 컴파일러가 try-catch문을 강제하기 때문에 별 생각 없이 사용했던 경향이 있었다.

또한, try-catchthrows를 피하려는 마음에 조건문을 활용한 사전 방어 코드를 우선적으로 작성해 왔는데, 결과적으로 최악의 선택은 아니었다는 점도 깨달았다.

하지만 예외 처리는 단순히 피하는 것이 아니라, 적절한 방식으로 다루는 것이 더 중요한 요소라는 생각이 든다.

앞으로는 try-catchthrows도 사용해 보면서, 상황에 맞는 최적의 예외 처리 방법을 익히도록 노력해봐야겠다.

profile
개발 취준생

0개의 댓글