예외 처리 방법(Exception Handling)
예외를 처리하는 방법에는 예외 복구, 예외 처리 회피, 예외 전환 방법이 있다.
예외 복구
- 예외 상황을 파악하고 문제를 해결해서 정상 상태로 돌려놓는 방법
- 예외를 잡아서 일정 시간, 조건만큼 대기하고 다시 재시도를 반복한다.
- 최대 재시도 횟수를 넘기게 되는 경우 예외를 발생시킨다.
final int MAX_RETRY = 100;
public Object someMethod() {
int maxRetry = MAX_RETRY;
while(maxRetry > 0) {
try {
...
} catch(SomeException e) {
// 로그 출력. 정해진 시간만큼 대기한다.
} finally {
// 리소스 반납 및 정리 작업
}
}
// 최대 재시도 횟수를 넘기면 직접 예외를 발생시킨다.
throw new RetryFailedException();
}
예외복구는 예외가 발생하더라도 어플리케이션의 로직은 정상적으로 실행이 되게 하도록 처리한다는 의미 .
예외가 발생하면 일정 시간동안 대기를 시킨 후 다시 해당 로직을 재시도.
일정 횟수동안 재시도를 이런식으로 진행하며, 그래도 정상적인 응답이 오지 않는 경우 fail 처리하는 로직을 생각할 수 있다.
대부분의 상황에서 예외를 복구할 수 있는 경우는 거의 없기 때문에 자주 사용되지 않는다.
예외 처리 회피
- 예외 처리를 직접 담당하지 않고 호출한 쪽으로 던져 회피하는 방법
- 그래도 예외 처리의 필요성이 있다면 어느 정도는 처리하고 던지는 것이 좋다.
- 긴밀하게 역할을 분담하고 있는 관계가 아니라면 예외를 그냥 던지는 것은 무책임하다.
// 예시 1
public void add() throws SQLException {
// ...생략
}
// 예시 2
public void add() throws SQLException {
try {
// ... 생략
} catch(SQLException e) {
// 로그를 출력하고 다시 날린다!
throw e;
}
}
예외 되던지기 - re-throwing
- 한 메서드에서 발생할 수 있는 예외가 여럿인 경우 몇 개는 try-catch문을 통해서 메서드 내에서 자체적으로 처리
- 나머지는 선언부에 지정하여 호출한 메서드에서 처리하도록 함으로써 양쪽에서 나눠서 처리되도록 함
- 단 하나의 예외에 대해서도 예외가 발생한 메서드와 호출한 메서드 양쪽에서 처리하도록 할 수 있다.
- 예외를 처리한 후에 인위적으로 다시 발생시키는 방법
void someMethod() throws Exception; // 선언부에 예외 지정
// 다음과 같이도 가능
void someMethod() throws Exception1, Exception2;
예외를 처리한 후에 인위적으로 다시 발생시키는 방법
public void someMethod() throws Exception {
try {
callMethod();
} catch (Exception e) {
System.out.prinltln(e.getName() + "예외 처리");
throw e;
}
}
반환값이 있는 return 문의 경우 try블럭과 catch블럭에서 에외를 처리하고도 return 해야 한다.
public int someMethod() throws Exception {
try {
return callMethod();
} catch (Exception e) {
System.out.prinltln(e.getName() + "예외 처리");
return -1; // 또는
// throw new Exception(); // 대신 예외를 호출한 메서드로 전달
}
}
- finally 문이 잇따면, 최종적으로 finally 블럭 내의 return문 값이 반환된다.
예외 전환
- 예외 회피와 비슷하게 메서드 밖으로 예외를 던지지만, 그냥 던지지 않고 적절한 예외로 전환해서 넘기는 방법
- 조금 더 명확한 의미로 전달되기 위해 적합한 의미를 가진 예외로 변경한다.
- 예외 처리를 단순하게 만들기 위해 포장(wrap) 할 수도 있다.
- 또한 Checked Exception이 발생했을 경우 이를 Unchecked Exception으로 전환하여 호출한 메서드에서 예외처리를 일일이 선언하지 않아도 되도록 처리할 수 도 있다.
// 조금 더 명확한 예외로 던진다.
public void add(User user) throws DuplicateUserIdException, SQLException {
try {
// ...생략
} catch(SQLException e) {
if(e.getErrorCode() == MysqlErrorNumbers.ER_DUP_ENTRY) {
throw DuplicateUserIdException();
}
else throw e;
}
}
// 예외를 단순하게 포장한다.
public void someMethod() {
try {
// ...생략
}
catch(NamingException ne) {
throw new EJBException(ne);
}
catch(SQLException se) {
throw new EJBException(se);
}
catch(RemoteException re) {
throw new EJBException(re);
}
}
즉 , 예외 전환이란 발생한 예외에 대해서 또 다른 예외로 변경하여 던지는 것
연결된 예외 (chained exception) : 어떤 예외를 다른 예외로 감싸는 것
- 발생한 예외에 대해서 또 다른 예외로 변경할 때 사용한다 .
- 언제 사용?
- 세부적인 사항을 포괄적인 사항으로 포함시킬 때 사용
- cheked 예외를 unchecked 예외로 변경시 사용(try-catch문 사용을 줄일 수 있음)
- 한 예외가 다른 예외를 발생시킬 수 있다.
- 예외 A가 예외 B를 발생시키면, A는 B의 원인 예외(cause exception)
다음 예는 SpaceException을 원인 예외
로 하는 InstallException을 발생시키는 예제이다.
- SpaceException이 InstallException 발생시킨다.
try {
startInstall();
copyFiles();
} catch (SpaceException e) {
InstallException ie = new InstallException("설치중 예외발생"); // 예외 생성
ie.initCause(e); // InstallException의 원인 예외를 SpaceException으로 지정
throw ie; // InstallException을 발생시킨다.
} catch (MemoryException me) {
...
}
- ie.initCause(e) : InstallException의 발생 원인을 SpaceException로 지정
Throwable initCause(Throwable cause)
- 지정한 예외(cause)를 원인 예외로 등록
- Exception클래스의 조상인 Throwable클래스에 정의되어 있기 때문에 모든 예외에서 사용이 가능
Throwable getCause()
- 원인 예외를 반환
발생한 예외를 그냥 처리하면 될 텐데, 왜 원인 예외로 등록해서 다시 예외를 발생시킬까?
1. 큰 분류의 예외로 묶어서 다루기 위함
2. 상속관계를 무시할 수 있음
3. checked 예외를 unchecked 예외로 바꿀 수 있다
try {
startInstall(); // SpaceException 발생
copyFiles();
} catch (InstallException e) { // InstallException은
e.printStackTrace(); // SpaceException과 MemoryExcpetion의 조상
}
위와 같이 InstallException을 SpaceException과 MemoryException의 조상으로 해서 catch 블럭을 작성하면, 실제로 발생한 예외가 어떤 것인지 알 수 없다는 문제가 있다.
- SpaceException과 MemoryException의 상속관계를 변경해야 한다는 것도 부담이다.
- 만약 원인 예외를 포함하게 된다면 두 예외는 상속관계가 아니여도 상관없다.
checked 예외를 unchecked 예외로 바꿀 수 있다.
static void startInstall() throws SpaceException, MemoryException {
if (!enoughSpace()) // 충분한 설치 공간이 없으면
throw new SpaceException("설치할 공간이 부족합니다.");
if (!enoughMemory()) // 충분한 메모리가 없으면
throw new MemoryException("메모리가 부족합니다.");
}
static void startInstall() throws SpaceException {
if (!enoughSpace()) // 충분한 설치 공간이 없으면
throw new SpaceException("설치할 공간이 부족합니다.");
if (!enoughMemory()) // 충분한 메모리가 없으면
throw new RuntimeException(new MemoryException("메모리가 부족합니다."));
}
MemoryException은 Exception의 자손이므로 반드시 예외를 처리해야하는데, 이 예외를 RuntimeException으로 포장했기 때문에 unchecked 예외가 되었다.
- 그래서 startInstall()의 선언부에 MemoryException을 선언하지 않아도 된다.
RuntimeException 생성자에는 원인 예외를 등록하는 생성자가 존재한다
RuntimeException(Throwable cause);
- checked 예외를 unchecked 예외로 바꾸면 예외처리가 선택적이 되므로 억지로 예외처리 하지 않아도 된다.
정리
- 예외 복구 전략이 명확하고 복구가 가능하면 Checked Exception을 try-catch로
예외 복구
를 하거나, 코드의 흐름을 제어하는 것이 좋다. - 또한 Checked Exception이 발생했을 경우 이를 Unchecked Exception으로 전환하여 호출한 메서드에서 예외처리를 일일이 선언하지 않아도 되도록 처리할 수 도 있다.
- 더 구체적인 UnChecked Exception을 발생시키고 예외에 대한 메시지를 명확하게 전달하는 것이 효과적이다.
- 무책임하게 상위 메서드에 throw로 예외를 던지는 행위를 하지 않는 것이 좋다.
- 상위 메서드들의 책임이 그만큼 증가하기 때문이다.
- Checked Exception은 기본 트랜잭션 속성에서 Rollback을 진행하지 않는다.
참조
'Java > Java' 카테고리의 다른 글
Java 형식화 클래스 - Format, DecimalFormat, SimpleDateFormat (0) | 2022.12.08 |
---|---|
Java UUID (+ IN MySQL ) (0) | 2022.12.07 |
예외 처리란 (0) | 2022.11.27 |
자바가 제공하는 기본 예외들과 자주 사용하는 예외들 (0) | 2022.11.27 |
객체지향 관점에서의 is-a, has-a (0) | 2022.11.25 |