예외 처리
📋 문서 버전
이 문서는 2개의 버전이 있습니다. 현재 최신 버전을 보고 있습니다.
예외 처리 (Exception Handling)
개요
예외 처리(Exception Handling)는 프로그램 실행 중 발생할 수 있는 비정상적인 상황(예외, Exception)을 적절히 관리하고 대응하는 프로그래밍 기법입니다. 일반적으로 예외는 논리적 오류나 시스템 자원 부족, 네트워크 연결 실패 등 예측 가능한 오류뿐만 아니라, 코드상에서 예상치 못한 상황에서도 프로그램이 갑자기 종료되는 것을 방지하기 위해 사용됩니다.
현대적인 객체 지향 프로그래밍 언어(Java, C++, Python, C# 등)는 대부분 예외 처리 메커니즘을 내장하고 있으며, 이를 통해 개발자는 정상적인 비즈니스 로직과 오류 처리 로직을 분리하여 코드의 가독성과 유지보수성을 높일 수 있습니다.
예외 처리의 필요성
프로그램이 실행되는 동안 다양한 오류가 발생할 수 있습니다. 예를 들어, 파일 읽기 시도 시 파일이 존재하지 않거나, 네트워크 요청 시 서버가 응답하지 않는 경우 등이 있습니다. 이러한 상황을 처리하지 않으면 프로그램은 크래시(Crash)되거나 원치 않는 동작을 수행할 수 있습니다.
예외 처리의 주요 목적은 다음과 같습니다:
- 프로그램의 안정성 유지: 오류 발생 시 프로그램이 강제 종료되지 않고 안전하게 복구하거나 종료되도록 합니다.
- 오류 정보의 명확한 전달: 오류의 원인과 위치를 구조화된 방식으로 제공하여 디버깅을 용이하게 합니다.
- 자원 관리: 데이터베이스 연결, 파일 핸들 등 제한된 자원을 올바르게 해제하여 메모리 누수나 자원 고갈을 방지합니다.
주요 구성 요소 및 동작 원리
대부분의 프로그래밍 언어는 try, catch, finally 키워드를 사용하여 예외 처리 블록을 구성합니다.
1. Try 블록 (시도 블록)
예외가 발생할 가능성이 있는 코드를 포함하는 영역입니다. 이 블록 내부에서 예외가 발생하면 실행 흐름이 즉시 중단되고 해당 예외를 처리할 수 있는 catch 블록으로 이동합니다.
2. Catch 블록 (포착 블록)
try 블록에서 발생한 특정 유형의 예외를 처리하는 영역입니다. 하나의 try 블록에 여러 개의 catch 블록을 연결하여 서로 다른 예외 유형에 대해 다른 대응 전략을 수립할 수 있습니다.
3. Finally 블록 (최종 블록)
try 또는 catch 블록이 실행된 후, 예외 발생 여부와 상관없이 반드시 한 번 실행되는 영역입니다. 주로 파일 닫기, 데이터베이스 연결 해제 등 정리 작업(Cleanup)에 사용됩니다.
예시 코드 (Python 기준)
try:
# 예외가 발생할 수 있는 코드
result = 10 / 0
except ZeroDivisionError as e:
# ZeroDivisionError 예외가 발생했을 때 처리
print(f"0으로 나눌 수 없습니다: {e}")
except Exception as e:
# 그 외의 다른 예외 발생 시 처리
print(f"예기치 않은 오류 발생: {e}")
finally:
# 항상 실행되는 정리 작업
print("연산 처리가 완료되었습니다.")
예외의 분류
예외는 일반적으로 두 가지 주요 범주로 나뉩니다.
1. 체크edException (Checked Exception)
컴파일 시점에 처리해야 하는 예외입니다. 호출하는 측에서 반드시 예외를 처리(try-catch)하거나 메서드 선언부에서 예외를 던짐(throws)을 명시해야 합니다. Java가 대표적인 예입니다. 주로 복구 가능한 오류(예: 파일 없음, 네트워크 오류)에 사용됩니다.
2. 언체크edException (Unchecked Exception)
런타임 시점에 발생하는 예외로, 컴파일 시 강제 처리가 요구되지 않습니다. 주로 프로그래머의 실수로 인한 논리적 오류(예: 배열 인덱스 초과, null 참조)에 해당합니다. Python의 Exception 클래스나 Java의 RuntimeException 계열이 이에 속합니다.
예외 처리의 모범 사례
올바른 예외 처리는 코드의 품질을 결정짓는 중요한 요소입니다. 다음 원칙들을 준수하는 것이 좋습니다.
- 구체적인 예외 처리: 너무 광범위한
Exception클래스를 잡기보다, 발생할 수 있는 구체적인 예외 클래스를 명시적으로 처리합니다. - 예외의 적절한 던지기: 호출한 쪽에서 처리할 수 없는 예외라면 적절히 래핑(Wrapping)하여 던지거나, 로깅 후 재처리합니다.
- 자원 자동 관리: Python의
with문이나 Java의try-with-resources를 사용하여 자원을 자동으로 관리하여finally블록의 복잡성을 줄입니다. - 로깅: 예외가 발생했을 때 디버깅에 도움이 되는 충분한 정보를 로그로 남깁니다.
- 예외 남용 금지: 제어 흐름(Control Flow)을 예외 처리로 대체하지 않습니다. 예외는 '예외적인' 상황에 사용해야 하며, 일반적인 조건 분기에는
if-else를 사용하는 것이 성능과 가독성 측면에서 더 우수합니다.
관련 문서 및 참고 자료
- [오류(Error)와 예외(Exception)의 차이]
- [로깅(Logging) 기법]
- [자원 관리(Resource Management)]
- [프로그래밍 언어별 예외 처리 문법 비교]
예외 처리는 단순한 오류 회피를 넘어, 견고하고 신뢰할 수 있는 소프트웨어를 설계하는 핵심 개념입니다. 개발자는 각 언어의 예외 처리 모델을 정확히 이해하고, 상황에 맞는 적절한 전략을 선택하여 코드의 안정성을 높여야 합니다.
예외 레벨과 처리 전략
예외의 심각도에 따라 레벨을 구분하면 시스템의 가용성을 높이고 효율적인 운영 관리가 가능합니다. 각 레벨은 발생 원인과 그에 따른 대응 방식이 다릅니다.
| 레벨 | 정의 | 대응 방식 | 예시 |
|---|---|---|---|
| Fatal | 시스템이 더 이상 작동할 수 없는 치명적 상태 | 즉시 프로세스 종료 및 관리자 긴급 알림 | 메모리 부족(OOM), 하드웨어 결함 |
| Error | 특정 기능 수행 중 발생한 심각한 문제 (복구 불가) | 해당 요청/트랜잭션 중단, 에러 로그 기록 및 사용자 알림 | DB 연결 유실, 필수 설정 파일 누락 |
| Warning | 잠재적인 문제가 있으나 시스템은 계속 작동 가능 | 경고 로그 기록, 대체 경로(Fallback) 시도 | API 응답 지연, 캐시 미스 후 DB 조회 |
| Info | 시스템의 상태 변화나 정상적인 흐름 내의 특이사항 | 단순 정보 기록, 모니터링 지표 반영 | 사용자 로그인/로그아웃, 배치 작업 시작 |
계층적 예외 설계
효율적인 예외 관리를 위해 상위 공통 예외 클래스를 정의하고, 이를 상속받아 구체적인 예외를 정의하는 계층 구조(Exception Hierarchy)를 설계합니다. 이를 통해 호출자는 특정 예외뿐만 아니라 상위 타입으로 여러 예외를 한 번에 처리할 수 있습니다.
예외 계층 구조 클래스 다이어그램
classDiagram
class Throwable {
<<Abstract>>
}
class Error {
<<Abstract>>
+VirtualMachineError
+OutOfMemoryError
}
class Exception {
<<Abstract>>
}
class RuntimeException {
<<Abstract>>
+NullPointerException
+IllegalArgumentException
}
class BusinessException {
<<Abstract>>
+InvalidUserInputException
+InsufficientBalanceException
}
class InfrastructureException {
<<Abstract>>
+DatabaseConnectionException
+NetworkTimeoutException
}
Throwable <|-- Error
Throwable <|-- Exception
Exception <|-- RuntimeException
Exception <|-- BusinessException
Exception <|-- InfrastructureException
시스템 오류와 애플리케이션 예외의 차이
예외의 분류에서 더 나아가, 시스템 레벨의 오류(Error)와 애플리케이션 레벨의 예외(Exception)는 복구 가능성 측면에서 명확히 구분됩니다.
- 시스템 오류 (Error): JVM이나 OS 레벨에서 발생하는 심각한 문제입니다. 애플리케이션 코드 수준에서
try-catch로 잡아서 복구하는 것이 거의 불가능하며, 발생 시 프로그램의 즉각적인 종료가 일반적입니다. (예:StackOverflowError,OutOfMemoryError) - 애플리케이션 예외 (Exception): 프로그램 로직 수행 중 발생하는 문제입니다. 적절한 처리 로직을 통해 복구하거나, 사용자에게 안내 메시지를 전달하여 정상적인 흐름으로 되돌릴 수 있습니다. (예:
FileNotFoundException,InvalidParameterException)
언어별 예외 구현 예시
언어마다 체크 예외(Checked)와 언체크 예외(Unchecked)를 구현하고 처리하는 방식에 차이가 있습니다.
Java (체크/언체크 구분)
// 1. 체크 예외: 반드시 throws 명시 또는 try-catch 필요
class MyCheckedException extends Exception {
public MyCheckedException(String message) { super(message); }
}
// 2. 언체크 예외: 명시적 처리 강제 없음
class MyUncheckedException extends RuntimeException {
public MyUncheckedException(String message) { super(message); }
}
public class ExceptionDemo {
public void checkMethod() throws MyCheckedException {
throw new MyCheckedException("처리 필수 예외");
}
public void uncheckedMethod() {
throw new MyUncheckedException("런타임 예외");
}
}
Python (모든 예외가 언체크 방식)
# 파이썬은 모든 예외가 런타임에 결정되는 언체크 방식입니다.
class BusinessError(Exception): # 공통 상위 예외
pass
class InsufficientFundsError(BusinessError): # 구체적 하위 예외
pass
def withdraw(amount):
if amount > 1000:
raise InsufficientFundsError("잔액이 부족합니다.")
try:
withdraw(2000)
except BusinessError as e: # 상위 클래스로 통합 처리 가능
print(f"비즈니스 로직 오류: {e}")
심각도 기반 로깅 전략
예외 처리의 모범 사례 중 로깅은 앞서 언급한 예외 레벨과 연동되어야 합니다. 단순히 모든 예외를 error 레벨로 남기는 것이 아니라, 상황에 맞는 로그 레벨을 설정하여 알람 피로도를 줄이고 가시성을 높여야 합니다.
- FATAL/ERROR: 즉시 해결이 필요한 상황. Sentry, Slack, PagerDuty 등 실시간 알림 시스템과 연동하여 담당자에게 즉시 통보합니다.
- WARN: 당장 서비스 중단은 아니지만, 방치할 경우 ERROR로 발전할 가능성이 있는 상황. 주기적인 로그 분석을 통해 개선 과제로 도출합니다.
- INFO/DEBUG: 개발 단계의 흐름 파악이나 운영 중의 단순 상태 확인용. 운영 환경에서는 로그 레벨을 높여(INFO 이상) 저장 공간을 최적화합니다.
이 문서는 AI 모델(gemma-4-31b)에 의해 생성된 콘텐츠입니다.
주의사항: AI가 생성한 내용은 부정확하거나 편향된 정보를 포함할 수 있습니다. 중요한 결정을 내리기 전에 반드시 신뢰할 수 있는 출처를 통해 정보를 확인하시기 바랍니다.