자바 개발자가 가장 신경 쓰기 귀찮아하는 것 중의 하나가 바로 예외처리입니다.
정상적인 결과와 흐름을 보여주는 코드를 만들기도 버거운데 예외상황까지 처리해야 한다는 사실이 부담스러울 수도 있습니다.
그래서 예외와 관련된 코드는 자주 엉망이 되거나 무성의하게 만들어지기 쉽습니다.
때론 잘못된 예외처리 코드 때문에 찾기 힘든 버그를 낳을 수도 있고, 생각지 않았던 예외상황이 발생했을 때 상상 이상으로 난처해질 수도 있습니다.
(p.279)
토비님은 코드를 확인할 때 예외 처리가 제대로 되어 있지 않으면 점수를 깎는다고 합니다.
추가로 학습 테스트나 예제를 작성하는 경우에도 예외 처리를 대충 해서는 안된다고 강조합니다.
예외 처리를 할 때 하지 말아야 할 방법으로는 크게 3가지가 있습니다.
- 예외를 잡고 아무것도 하지 않는 경우
- 예외를 잡고 단순하게 출력만 하는 경우
- 무의미하고 무책임하게 throws 하는 경우
예외를 잡고 아무것도 하지 않는 경우
try {
...
} catch (SQLException e) {
// 아무것도 하지 않음
}
예외가 발생하면 그것을 catch 블록을 써서 잡아내는 것까지는 좋은데 그 후 아무것도 하지 않고 별문제 없는 것처럼 넘어가 버리는 건 정말 위험한 일이다.
...
어떤 경우에도 위와 같은 코드를 만들면 안 된다. 습관이 되면 무서우니 코딩 연습이나 예제를 잠깐 만드는 경우라도 그래선 안 된다.
(p.281)
예외를 잡고 단순하게 출력만 하는 경우
추가로 예외를 잡고는 단순히 로그만 출력하는 코드도 좋지 않은 코드이므로 지양해야 한다고 말합니다.
try {
...
} catch (SQLException e) {
// 단순히 에러를 출력만 함
System.out.println(e);
}
예외를 처리할 때 반드시 지켜야 할 핵심 원칙은 한 가지다. 모든 예외는 적절하게 복구되든지 아니면 작업을 중단시키고 운영자 또는 개발자에게 분명하게 통보돼야 한다.
(p.281)
무의미하고 무책임하게 throws 하는 경우
API등에서 발생하는 예외를 일일이 하기 귀찮으니 모든 예외를 던져버리는 throws Exception
코드를 모든 메소드에 기계적으로 넣는 것입니다.
무책임하게 throws
하는 경우에는 다음과 같은 문제점이 있습니다.
- 예외적인 상황이 발생할 수 있다는 것인지 그냥 습관적으로 복사해서 붙여놓은 것인지 정확히 알 수 없다.
- 예외를
Exception
으로 작성했기 때문에 정확히 어떤 예외가 발생했는지 파악하기 어렵다.
예외의 종류와 특징
자바에서 throw를 통해 발생시킬 수 있는 예외는 크게 세 가지가 있습니다.
- Error
- Exception
- Checked Exception
- UnChecked Exception
Error
java.lang.Error
클래스의 서브 클래스들입니다.
주로 자바 VM에서 발생하는 것이기 때문에 애플리케이션 코드에서 잡으려고 하면 안 됩니다.
OutOfMemoryError
나 ThreadDeath
같은 에러는 catch
블록에서 잡아봤자 아무런 대응 방법이 없기 때문입니다.
따라서 시스템 레벨에서 특별한 작업을 하는 게 아니라면 애플리케이션에서는 이런 에러에 대한 처리는 신경 쓰지 않아도 됩니다.
Exception
java.lang.Exception
클래스와 그 서브클래스로 정의되는 예외들은 에러와 달리 개발자들이 만든 애플리케이션 코드의 작업 중에 예외상황이 발생했을 경우에 사용됩니다.
Checked Exception
(체크 예외)는 RuntimeException
클래스를 상속하지 않은 것들이고, UnChecked Exception
(언체크 예외) 는 RuntimeException
을 상속한 클래스들을 말합니다.
일반적으로 예외라고 하면 RuntimeException
을 상속하지 않은 체크 예외라고 생각해도 됩니다.
언체크 예외는 RuntimeException
을 상속했기 때문에 런타임 예외라고도 부릅니다.
에러와 마찬가지로 런타임 예외는 catch 문으로 잡거나 throws로 선언하지 않아도 됩니다.
물론 명시적으로 잡거나 throws로 선언해줘도 상관없습니다.런타임 예외는 주로 프로그램이 오류가 있을 때 발생하도록 의도된 것들입니다.
...
따라서 런타임 예외는 예상하지 못했던 예외상황에서 발생하는 게 아니기 때문에 굳이 catch나 throws를 사용하지 않아도 되도록 만든 것입니다.
(p.285)
위와 같이 토비님은 개발자들이 직접 예외 처리를 해야 하는 경우는 체크 예외뿐이라고 말합니다.
에러와 언체크 예외는 시스템 오류 또는 프로그램 오류가 있을 때 발생하기 때문에 아무리 개발자가 catch
나 throws
를 사용한다고 해도 복구할 수가 없습니다.
그리고 어찌저찌 복구한다고 하더라도 다른 곳에서 다시 에러/예외가 발생할 확률이 높아지는데, 이렇게 에러/예외가 발생하게 되면 오히려 디버깅이 더 어려워지기 때문에 토비 님은 차라리 복구하지 못할 에러/예외는 차라리 빠르게 예외가 발생하도록 두는 것이 더 좋다고 합니다.
예외 처리 방법
예외 처리 방법에는 크게 3가지가 있습니다.
- 예외 복구
- 예외 회피
- 예외 전환
이 중에서 가장 의미있는 것은 예외 전환이라고 생각합니다.
예외 전환이란 발생한 예외를 그대로 넘기는 게 아니라 적절한 예외로 전환해서 던진다는 특징이 있습니다.
예외 전환은 예외의 의미를 분명하게 해 줄 수 있기 때문에 개발자들은 예외 전환을 통해 보다 분명하게 예외 처리를 하는 것이 좋습니다.
public void add(final User user) {
try {
this.jdbcTemplate.update("insert into users(id, name, password) values (?, ?, ?)",
user.getId(), user.getName(), user.getPassword());
} catch (SQLException e) {
// 로그 출력
// Duplicate Entry 예외일경우 직접 작성한 예외로 전환
if(e.getErrorCode() == MysqlErrorNumbers.ER_DUP_ENTRY) {
// 원본 예외 담아서 반환
throw new DuplicateUserIdException().initCause(e);
} else {
throw e;
}
// 서비스 계층에서 SQLException 을 그대로 반환하면 어떤 상황에서 예외가 발생했는지 알기 어렵다.
// 따라서 좀 더 구체적인 예외로 전환해주는 것이 좋다.
// 하지만 주로 예외처리를 강제하는 체크 예외를 언체크 예외인 런타임 예외로 바꾸는 경우에 사용한다.
}
}
사용자의 등록 처리를 담당하는 add
메소드의 경우 중복 회원이 발생하면 예외가 발생하는데 이를 그냥 throws
하게 되면 SQLException
이 전달됩니다.
따라서 개발자는 SQLException
왜 발생했는지 파악하기 어렵기 때문에 이 경우는 의미를 분명하게 하여 DuplicateUserIdException
예외로 전환하여 던지는 것이 좋습니다.
그렇게 되면 개발자는 보다 쉽게 왜, 어떤 예외가 발생했는지 파악할 수 있게 됩니다.
...
그래서 대응이 불가능한 체크 예외라면 빨리 런타임 예외로 전환해서 던지는 게 낫다....
그럴 바에는 그냥 런타임 예외로 포장해 던져버려서 그 밖의 메서드들이 신경 쓰지 않게 해주는 편이 낫다.
(p.292)
4장을 읽으면서 나는 지금까지 어떻게 예외를 처리해왔는가를 돌아보게 되었습니다. 부분적으로는 예외 전환을 사용하려고 노력해왔는데 체크 예외 / 언체크 예외를 의식적으로 생각하면서 처리를 하지는 않았던 것 같습니다.
실제로 레거시 코드를 다루면서 예외를 발생하지 않는 코드이지만 throws Exception
이 붙어 있어 예외가 발생하는 코드인 줄 알고 나 또한 무의미하게 예외 처리를 하려고 했던 적이 있던 적이 있어 공감이 많이 되었던 것 같습니다.
'Spring > Toby's Spring Reading Club' 카테고리의 다른 글
1-6. AOP (1) (0) | 2023.01.09 |
---|---|
1-5장. 서비스 추상화 (0) | 2023.01.08 |
1-3장. 템플릿 (0) | 2023.01.07 |
1-2장. 테스트 (0) | 2022.09.19 |
1-1장. 오브젝트와 의존관계 (0) | 2022.09.05 |
댓글