Java 예외 - 실무에서의 처리 방안

예외 처리를 다루는데 어떤 것이 베스트 프랙티스인지 고민 상황이 생길 수 있다.
시간이 흘러 예외 처리에 대한 스타일 분야별 정리가 된 상황이다.

💡
스타일이 이미 정리되었다고 상황에 따라 적용하기 어려울 수 있다.

처리가 어려운 예외

개발자가 당장 해결이 어려운 에러로 시스템 에러가 있다. 에러의 원인이 개발자가 아니다. 시스템 오류로는 네트워크 통신 오류, 데이터베이스 서버 문제, 다른 애플리케이션 간 연결 오류 등이 있다. 이렇게 발생한 예외는 해결할 수 없으므로 코드도 다시 호출을 시도하는 방법밖에 없어서 오류로 반복되어 출력한다.

이러한 오류는 메시지로 명확하게 표시해주어야 한다. "현재 시스템/네트워크/데이터베이스에 연결에 오류가 발생하였습니다." 메시지를 출력하거나 웹이라면 오류페이지를 전달한다. 오류페이지는 "시스템 점검 중입니다." / "시스템 오류가 발생하였습니다. 복구 진행중입니다." 문구가 적힌 화면을 출력하도록 한다.

해당 문구 출력을 개발한 담당자는 이 에러를 처리할 수 없으므로 유관 부서로 전달한다. 그러면 상황을 전달 받은 다른 담당 개발자가 처리하여 상황을 빠르게 인지하고 처리한다.


체크 예외 실무

개발자가 실수로 발생할 수 있는 예외들을 컴파일러가 체크한다. 현재는 개발자가 처리 못하는 예외가 많고, 프로그램이 많이 복잡해져 체크 예외를 사용하는 것이 부담스러운 상황이다.

예를 들어 다음 코드가 있다고 생각해보자.

Service 객체

  • NetworkClient
  • DatabaseClient
  • 등등

Service 객체가 많은 Client의 라이브러리를 사용하고 있는데, 모두 예외가 터져 Service 객체에게 예외를 던지고 있다.

예외 잡아서 처리하기

try {
} catch (NetworkClientException e) {
    // 오류 발생 Service 객체에게 예외 전달
} catch (DatabaseClientException e) {
    // 오류 발생 Service 객체에게 예외 전달
} catch (Exception e) {
    // 오류 발생 Service 객체에게 예외 전달
}

그럼 Service 객체는 이 모든 예외들을 처리가 가능할까?
대부분의 예외는 처리가 불가능한 예외가 많다.
Service 객체는 빠져나올 수 없는 예외 지옥에 있게 된다.

  • 상대 네트워크, 데이터베이스 서버 문제가 발생한 경우 Service 객체가 잡아서 복구할 수 없는 상황.
  • Service는 본인이 처리 못하므로 예외를 던지는 결정 나을 수 있다.

모든 예외 던지기

class Service {
    void connect() throws NetworkException, DatabaseException, ... {
    
    }
}
  • 모든 예외를 던질 수 있다.
  • 호출한 스택들은 예외 라이브러리가 많을 수록 throws에 모두 기입해주어야 한다.

throws Exception

모든 예외를 던지는 코드에서 예외 라이브러리를 모두 기입하였는데,
한 줄로 최상위 Exception 예외를 넣어서 깔끔하게 표시하는 것도 생각이 들 수 있다.

그러나 Exception 넣게 되면 코드 체크에서 경고를 보낼 것이다.
Exception 사용에는 치명적인 문제가 있다.

Exception 뿐 아니라 그 자식인 NetworkException, DatabaseException 예외가 체크하게 되고 새로 생성되는 신규 예외들도 함께 잡히게 된다.
그리고 Exception 던진 경우 다른 체크 예외 기능을 무효화된다. 정말로 중요한 객체의 예외가 있었는데. 이 기능이 올바르게 동작하지 않을 수 있다는 것이다.

신규 클래스로 Service 호출시

만약 신규 Direct 클래스 를 생성하였을 때 Service 가 부담해야 했던 던진 예외들이 있었다. Service는 자신이 처리할 수 없으니 던지도록 결정했을 것이고, 던진 예외는 신규 Direct 클래스가 모두 부담 해야한다. 꼬리에 꼬리를 무는 문제가 생긴다.

체크 예외에 발생한 문제점

  • 처리할 수 없는 예외
    • 예외를 잡아 복구하는 것보다 처리에 불가능한 예외가 많다.
  • 체크 예외의 부담 전이
    • 처리 할 수 없는 예외는 밖으로 던지게 되고, throws 대상을 모두 적어야 한다.

특히 오픈소스 라이브러리에 체크된 모든 예외는 다룰 수 없다. 모든 것을 개발자가 바로 해결할 수 없는 예외이므로 무시하고 처리 가능한 예외부터 진행하는 것을 권장한다.


런타임(언체크) 예외 실무

체크 예외에 설명한 내용과 비슷하다. 다른 점이 있다면 체크 예외에 밖으로 던질 때는 throws 대상을 모두 적지 않아도 된다.

언체크 예외는 본인이 직접 잡아서 처리하는 방식을 선호한다.

직접 예외를 잡아 처리하는 예

try {

} catch (DddException e) {
    ...
}
  • 네트워크, 데이터베이스 서버에 문제가 발생한 경우, 예외를 잡아도 복구가 불가능하므로 기재하지 않는다.
  • 언체크 예외는 throws 선언이 없어도 자동으로 밖으로 던진다.
  • 처리가 가능한 예외 잡아 직접 처리한다.

예외 공통 처리

언체크 예외는 처리할 수 없는 모든 예외은 공통으로 처리하는 곳을 만들어 해결한다.
예외를 처리할 수 없으므로 오류 문구로 상대방에게 전달할 수 있도록 로그를 출력해 전달하는 것이 있다.

  • 공통 처리는 처리할 수 없으므로 제안하여 해결한다.
  • 처리 불가능한 예외 발생 시 바로 오류 페이지로 출력한다.
  • 네트워크, 데이터베이스 문제인 경우 개발자에게 전달할 수 있도록 자동으로 문자를 발송시킨다.