카테고리 없음

자바 공부#4_03. 예외 사용

코딩연습중 2025. 4. 19. 15:32

예외 처리를 할 때에 개발자마다 의견이 충돌하는 몇 가지 문제가 있다. 그 중에 주요 문제가

1. checked exception vs runtime exception

2. 커스텀 예외 vs 표준 예외.

이 두 가지이다.

왜 이러한 논쟁이 이루어지는지 찾아보았다.

(내가 공부하기 위해 작성한 글이며, 개인적인 의견이 포함되어 있음.)

 

1. Checked Exception vs Runtime Exception

 

 

Effective Java를 보면 복구 가능한 상황에서는 Checked Exception을 쓰라고 한다. 

Item 70의 내용을 요약하자면 다음과 같다.

checked exception을 사용하면 호출자는 반드시 예외를 catch하거나 throws를 사용하여 전파해야한다. 그렇기 때문에 checked exception은 '예외상황이 발생할 수 있음'을 알리는 강력한 수단이 되며, API 설계자는 이를 통해 '복구 가능한 상황이므로, 호출 자에서 반드시 대응하라'는 메시지를 전할 수 있다.
 Unchecked throwable (Runtime exception, Error)은  예외를 잡지 않아도 된다. 오히려 이런 예외들은 보통 복구가 불가능하고, 프로그램이 계속 돌아가는 것이 더 안 좋을 수 있다. 만약, 프로그램이 예외를 catch하지 않는다면, thread가 중단되며 에러 메시지를 출력하게 된다.  
 프로그램 상의 오류를 나타내기 위해서는 runtime exception을 사용해야 한다. 보통 runtime exception은 조건 위반을 알린다. (ex:  ArrayIndexOutOfBoundsException) 하지만 복구 가능한 상황인 건지, 단순한 프로그래밍 오류인지가 명확하다는 문제가 있다. 결론적으로는 API 설계자의 책임인 것이다. 만약 복구 가능한 상황이라고 판단되면 checked exception을, 그게 아니라면 runtime exception을 사용하라. 모호한 경우에는 unchecked exception을 사용하는 것이 낫다.  

 

 

그럼 복구 가능한 상황이면 무조건 Checked Exception을 써야 할까?

 이 부분에서 의문이 생겼다.

실제로 내가 지금까지 프로젝트 진행하면서 checked exception을 사용하는 경우, 대부분 throws를 붙여 계속 에러를 회피하는 경우가 많았고, catch를 통해 복구를 한 경우는 거의 없었다. catch/throws가 강제되는데 보통은 복구할 코드가 없어서 그런 것이다. 하지만 이렇게 되면 그냥 throws를 남발하는 것 아닌가? 실제로 복구할 의도가 없다면 왜 checked exception을 써야 하는가?

 그러다 "checked exception은 '예외상황이 발생할 수 있음'을 알리는 강력한 수단"이라는 말에 집중해봤다. 만약 대규모 프로젝트인 경우, 또는 공통 유틸 클래스, 외부 모듈과 같이 여러 팀이 사용하는 API를 설계할 때에는 예외가 발생할 수 있음을 명시적으로 알리고, 처리를 강제하는 것이 실수를 방지하고 안정성을 높일 수 있다. checked exception은 예외 처리가 강제되기 때문에 에러 누락의 위험이 줄어들어, 협업 시 프로젝트에서 안전장치의 역할을 할 수 있다. 또 예외 흐름이 명시적으로 드러나기 때문에 나중에 유지 보수 시에 플로우를 추적하기 쉽다는 장점이 있다. 

 

❗ 하지만 Trade-off도 존재한다.

 이렇게 보면 복구 가능한 상황에서는 무조건적으로 Checked Exception을 사용하는 것이 좋아보이지만, 실제로는 분명한 단점도 존재한다.

 Checked Exception은 호출자에게 예외 처리를 강제하는 만큼, 복구할 수 없는 예외까지도 일일이 throws로 전파하게 되는 경우가 많다. 이는 결과적으로 무분별한 throws 남용으로 이어져 코드가 복잡하고 지저분해질 수 있다.

또한, 서비스 계층에서 인프라 계층까지 예외가 계층적으로 전파되면서, 예외 흐름을 추적하기 어려워지고, DB나 외부 시스템과의 연동 문제 같은 구현 세부사항이 상위 계층까지 노출되면서 캡슐화 원칙도 위반하게 된다.

 예를 들어, SQLException과 같은 예외가 그대로 서비스 레이어에 노출된다면, 해당 계층이 내부에 어떤 기술을 사용하는지 알아야 하므로 추상화가 깨지고, 유지보수가 어려워지는 구조가 될 수 있다.

 실제로 많은 현대 프레임워크(Spring 포함)에서는 이러한 이유로 Checked Exception 사용을 지양한다. 대표적으로 Spring의 JdbcTemplate은 JDBC의 SQLException을 그대로 사용하지 않고, 이를 DataAccessException이라는 Runtime 기반 예외로 감싸서 처리하도록 하고 있다.
 이처럼 실무에서는 대부분 RuntimeException을 사용하고, 전역 예외 처리 방식(@ExceptionHandler 등)을 활용해 일괄 처리하는 패턴을 선호한다.

 

 결국 어떤 예외를 사용할지는 팀의 예외 처리 룰, 그리고 해당 API의 복잡도, 사용 환경에 따라 달라질 수 있다.
중요한 것은 예외를 설계할 때, 그 상황에 맞게 “복구 가능성”, “처리 책임 주체”, “API 사용자 관점” 등을 종합적으로 판단하는 것이다.
하지만 Checked Exception 사용을 지양하는 추세인 건 사실이고, 되도록이면 Runtime Exception을 사용하는 것이 현실적으로 옳은 방향인 것 같다. 

 

 

 


 

 

 

2. 커스텀 예외 vs 표준 예외

표준 예외와 커스텀 예외를 비교하기 전에 표준 예외와 커스텀 예외에 대해 알아보겠다.

표준 예외는 JDK가 제공하는 예외 클래스들을 의미한다.

커스텀 예외는 개발자가 직접 표준 예외를 커스텀해서(상속 받아서) 만든 예외를 의미한다.

 

 

커스텀 예외를 사용하는 이유가 무엇일까?

 

1. 비즈니스 로직을 명확하게 표현 가능

  • 상황에 맞는 예외명을 사용하면 코드만 봐도 어떤 문제인지 이해하기 쉬움

2. 예외 처리 코드를 모아서 관리할 수 있음 (응집도 향상)

  • 커스텀 예외를 사용하여 예외에 필요한 메시지, 전달할 정보의 데이터, 데이터 가공 메서드들을 한 곳에서 관리 가능.                   →  책임이 분리된 깔끔한 코드 작성 가능
  • 예외별로 클래스를 나누면 유지보수도 쉬워짐.

3. 예외 발생 후처리가 용이함

  • 재사용성이 높은 표준 예외의 특징으로 인해 Exception 발생 위치를 정확하게 파악하기 힘들며 프로그래머가 의도하지 않은 예외까지 모두 잡아내 혼란을 야기할 수 있음.
  • 이러한 경우 커스텀 예외를 사용함으로써 Exception Handling에 혼란을 줄임. (예시 봐야 이해 쉬움.)

4. 상세한 예외 정보를 제공함

  • 예외 클래스에 메시지 외에 추가 필드(코드, 상태 등)를 정의 가능
  • 사용자나 개발자에게 상세 원인을 전달하기 쉬움

5. 예외 생성 비용을 줄임

  • 표준 예외는 디버깅 편의성을 위해 stack trace를 print함. (하지만 try/catch를 통해 의도적으로 handling 하는 경우, stack trace 사용하지 않게 됨.
  • stack trace를 생성하는 Throwable fillInStachTrace를 override하여 사용자 정의대로 trace 재정의함으로써 stack trace 생성 비용 줄일 수 있음.

 

커스텀 예외 사용의 단점

 

하지만 커스텀 예외도 trade-off가 존재한다. 

1. 예외 객체를 관리해야 함

  • 예외 객체가 도메인의 성질을 띠기 때문에, 예외 객체도 관리의 대상이 됨. 즉, 비즈니스 로직이나 도메인이 변경되면 예외 객체에도 영향을 미침.
  • 도메인 로직은 수정되었지만, 예외 객체가 이를 따라가지 못한다면 문제 발생.

2. 커뮤니케이션 오버헤드 발생.

  • 표준 예외 사용 시, 어떠한 맥락을 전달하는지 유추 가능.
  • 커스텀 예외 사용 시, 예외 객체의 구현까지 알아야 함.

 


 

 

표준 예외를 사용하는 이유가 무엇일까?

 

1. 배우기 쉽고 사용하기 편리한 API를 만들 수 있음

  • 상황에 맞는 예외명을 사용하면 코드만 봐도 어떤 문제인지 이해하기 쉬움

2. 가독성이 높음

  • 다양한 예외를 하나의 공통 상위 예외로 묶어서 구조화가 가능해짐
  • 예외별로 클래스를 나누면 유지보수도 쉬워짐.

3. 예외를 재사용하는 것이 좋다. 예외 클래스의 수가 적을수록 프로그램의 메모리 사용량이 줄고, 클래스 적재 시간도 줄어든다

 

 

상황에 따라서 어떤 걸 사용해야할지는 아직 더 고민해봐야겠다.

하지만 커스텀 예외를 사용할 때에는 커스텀 예외를 사용하는 목적에 대해서 다시 생각해보고 "정말 꼭 사용해야만 하는가?"하는 물음에 "그렇다"는 답을 낼 수 있을 때, 커스텀 예외의 필요성을 입증할 수 있을 때에만 사용하는 것이 좋겠다.

커스텀 예외와 표준 예외는 밑의 글에 더 잘 정리되어있다!

 

참고:

- Effective Java

- https://velog.io/@eastperson/Java%EC%9D%98-Checked-Exception%EC%9D%80-%EC%8B%A4%EC%88%98%EB%8B%A4-83omm70j

커스텀 예외 vs 표준 예외

- https://tecoble.techcourse.co.kr/post/2020-08-17-custom-exception/

- https://dev-ws.tistory.com/39

- https://ryumodrn.tistory.com/141

- https://parkadd.tistory.com/69

- https://velog.io/@alsgus92/JavaAndroid-Custom-Exception%EC%9D%98-%ED%95%84%EC%9A%94%EC%84%B1%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90