RuntimeException과 Exception, 어느걸 사용해야 할까?
예외처리를 고민하는 개발자에게
여는 글
안녕하세요. 오늘은 드디어 Java에 관한 포스팅입니다. 프로그래밍 언어 관련 포스팅으로 Java는 첫 포스팅이네요. 해당 포스팅에서는 예외처리에 대해 모든 것을 다루지는 않고, 어느 정도 배경지식이 있다는 전제하에, Exception
과 RuntimeException
을 각각 어떤 목적에 따라 사용하면 좋을지 설명하겠습니다.
예외처리
예외처리가 없는 코드는 실무에서 존재할 수가 없다는 것은 다 아실겁니다. 하지만, 실무 경험이 없는 분들 중 서비스 로직에만 집중하고, 예외처리는 고민하지 않는 분들이 있는데요.(제가 그랬습니다 ㅠ)
실무에서는 다양한 예외 상황에 직면 합니다. 네트워크 장애, 잘못된 사용자 입력, 서버 오작동, 논리적 오류 등등. 이러한 예외 상황 발생시에도 최대한 프로그램이 예기치 않게 종료되는 일은 없게끔 예외처리 코드를 작성해야 합니다.
예외 계층 구조
다들 잘 알고 있으시리라 생각되지만, Exception
과 RuntimeException
의 사용 목적을 구분지으려면 예외 계층 구조를 다시 짚고 넘어가야 합니다.
그림을 보시면 RuntimeException
은 Exception
하위에 위치해 있고, Exception
은 Checked Exception
그리고 Runtime Exception
은 Unchecked Exception
라 불립니다.
Checked Exception (Exception)
Checked Exception
은 컴파일러가 강제하는 예외입니다. 메서드에서 이러한 예외가 발생할 가능성이 있으면, 해당 메서드는 반드시 throws
키워드를 사용해 예외를 명시하거나, 메서드를 호출하는 코드에서 try-catch
로 처리해야 합니다. 이는 메서드를 사용하는 개발자에게 예외 처리의 의무를 부여하며, 외부 환경(파일 시스템, 네트워크 등)에서 발생할 수 있는 오류에 대해 사용합니다.
한마디로, ‘예측 가능한 예외에 대한 처리’라고 할 수 있습니다. 예를 들어, IOException
, SQLException
등은(인텔리제이에서 throws해야한다고 빨간줄 쳐주는거) 사전 복구가 가능하기 때문에 Checked Exception
으로 정의됩니다.
Unchecked Exception (RuntimeException)
Unchecked Exception
은 컴파일러가 예외 처리를 강제하지 않는 예외입니다. 이런 예외는 코드의 논리적 오류나 잘못된 입력값 같은 것들로 발생합니다. 이러한 예외는 프로그램의 정상적인 흐름 중에 발생하지만, 복구가 어려워 주로 프로그램을 중단시킵니다. 개발자는 필요에 따라 선택적으로 처리할 수 있습니다.
따라서, 예측 불가능한 예외에 대해서 처리를 개발자가 코드로 해결해야 할때는 Runtime Exception
을 사용해야 하는것이죠.
예외처리 오해와 진실
- “Checked Exception을 사용하면 모든 예외를 명시해야 하는데, 왜 모든 예외를 Checked로 처리하지 않나요?”
- “Checked Exception과 Unchecked Exception을 혼용해도 괜찮을까요?”
- “예외를 모두 RuntimeException으로 처리하면 편하지 않을까요?”
위 질문들에 대한 대답은 결국 Java의 설계 철학으로 귀결됩니다. 모든 예외를 Checked
로 처리하거나 모든 예외를 Unchecked
로 처리하면, 코드의 복잡도와 유지보수성 사이에서 균형을 잃을 수 있습니다. 이는 단순한 문법적 차원 이상의 문제로, Java의 예외 처리 설계는 다음과 같은 철학적 원칙에 기반합니다:
1. 왜 모든 예외를 Checked로 처리하지 않을까?
Java는 예외를 명확히 구분하여, 복구 가능한 예외를 호출자가 의도적으로 처리하도록 강제합니다. 하지만 모든 예외를 Checked
로 처리하면 코드가 지나치게 장황해져 가독성을 해칠 수 있습니다. 예를 들어, 호출자에게 try-catch
를 남발하게 하거나, 예외를 무의미하게 throws
로 선언해 코드를 복잡하게 만듭니다.
따라서, Checked Exception
은 복구 가능성이 있는 외부 오류(파일, 네트워크 등)에서만 사용해야 합니다. 이러한 오류는 외부 환경에 의해 발생하며, 재시도나 대체 로직을 통해 해결될 수 있기 때문입니다.
2. Checked와 Unchecked Exception을 혼용해도 괜찮을까?
Java는 Checked
와 Unchecked Exception
의 역할을 명확히 나누는 것을 권장합니다. 혼용 자체는 문제없지만, 일관성 있는 처리 전략이 필요합니다. 예를 들어, 네트워크 장애는 Checked Exception
으로 처리하고, 사용자 입력 오류는 Unchecked Exception
으로 처리하는 기준을 확실히 하는 것이 더 중요한 것이죠.
3. 모든 예외를 RuntimeException으로 처리하면 편하지 않을까?
모든 예외를 RuntimeException
으로 처리하면 코드가 간결해질 수 있지만, 이는 장기적으로 유지보수에 문제를 초래할 수 있습니다.
예를 들어, 네트워크 오류(복구 가능한 오류)를 RuntimeException으로 처리하면 복구 시도 없이 프로그램이 중단됩니다. 또한, API를 사용하는 개발자가 예외를 사전에 예측할 수 없어 API의 가독성과 신뢰도가 떨어집니다.
결론
구분 | Checked Exception | Unchecked Exception (RuntimeException) |
---|---|---|
사용 목적 | 외부 리소스 오류 처리 (파일, 네트워크 등) | 비즈니스 로직 또는 논리 오류 처리 |
복구 가능성 | 복구 가능성 높음 | 복구 불가 (프로그램 중단 필요) |
컴파일러 처리 강제 여부 | 강제 (throws 또는 try-catch 필요) | 강제되지 않음 |
적용 예시 | 네트워크 장애, 파일 읽기 오류 | 잘못된 파라미터, NullPointerException |
맺는글
실제 서비스 운영에서는 사용자의 행동을 예측하기 어렵습니다. 개발자 도구를 통해 스크립트를 조작하거나, 사이트 내에서 빈번히 앞뒤로 이동하는 사용자도 있습니다. 또한, 연속된 요청, 네트워크 오류로 인한 중복 요청, 쿠키나 로컬 스토리지 처리 문제 등 다양한 예외 상황이 발생할 수 있습니다. 서버는 이러한 예기치 못한 행동에 대비해야 하며, 이를 위한 예외 처리가 필수적입니다.
비록 서버의 모든 오류가 Exception
과 RuntimeException
만으로 해결되지는 않지만, 이 두 가지 예외 처리 방식은 Java에서 가장 기본적인 처리 도구입니다. 개발 초기 단계에서는 이 기본 원칙을 잘 이해하는 것이 중요하며, 이후 예외 처리 로직을 점차 고도화해 나가는 것이 바람직합니다.